6299: Show error message when downloaded map cannot be saved r=def- a=Robyt3

Check if deleting the old map file or renaming the temporary downloaded map fails. If so, show an error message which indicates that the user should delete the map file manually.

Sometimes downloaded map files seem to end up with wrong permissions, ownership or with read-only flag set, which makes the client unable to delete them.

![screenshot_2023-01-22_17-19-12](https://user-images.githubusercontent.com/23437060/213927019-ff49cb72-f60a-4c1a-b48b-d34e40d1420e.png)

Closes #5825.

## Checklist

- [X] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2023-01-23 11:41:51 +00:00 committed by GitHub
commit b8e7160555
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 263 additions and 159 deletions

View file

@ -2333,6 +2333,21 @@ int fs_removedir(const char *path)
#endif
}
int fs_is_file(const char *path)
{
#if defined(CONF_FAMILY_WINDOWS)
WCHAR wPath[IO_MAX_PATH_LENGTH];
dbg_assert(MultiByteToWideChar(CP_UTF8, 0, path, -1, wPath, std::size(wPath)) > 0, "MultiByteToWideChar failure");
DWORD attributes = GetFileAttributesW(wPath);
return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
#else
struct stat sb;
if(stat(path, &sb) == -1)
return 0;
return S_ISREG(sb.st_mode) ? 1 : 0;
#endif
}
int fs_is_dir(const char *path)
{
#if defined(CONF_FAMILY_WINDOWS)

View file

@ -1786,18 +1786,24 @@ int str_time_float(float secs, int format, char *buffer, int buffer_size);
*/
void str_escape(char **dst, const char *src, const char *end);
/* Group: Filesystem */
/**
* @defgroup Filesystem
*
* Utilities for accessing the file system.
*/
/*
Function: fs_listdir
Lists the files in a directory
Parameters:
dir - Directory to list
cb - Callback function to call for each entry
type - Type of the directory
user - Pointer to give to the callback
*/
/**
* Lists the files and folders in a directory.
*
* @ingroup Filesystem
*
* @param dir Directory to list.
* @param cb Callback function to call for each entry.
* @param type Type of the directory.
* @param user Pointer to give to the callback.
*
* @remark The strings are treated as zero-terminated strings.
*/
typedef int (*FS_LISTDIR_CALLBACK)(const char *name, int is_dir, int dir_type, void *user);
void fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user);
@ -1808,174 +1814,207 @@ typedef struct
time_t m_TimeModified; // seconds since UNIX Epoch
} CFsFileInfo;
/*
Function: fs_listdir_fileinfo
Lists the files in a directory and gets additional file information
Parameters:
dir - Directory to list
cb - Callback function to call for each entry
type - Type of the directory
user - Pointer to give to the callback
*/
/**
* Lists the files and folders in a directory and gets additional file information.
*
* @ingroup Filesystem
*
* @param dir Directory to list.
* @param cb Callback function to call for each entry.
* @param type Type of the directory.
* @param user Pointer to give to the callback.
*
* @remark The strings are treated as zero-terminated strings.
*/
typedef int (*FS_LISTDIR_CALLBACK_FILEINFO)(const CFsFileInfo *info, int is_dir, int dir_type, void *user);
void fs_listdir_fileinfo(const char *dir, FS_LISTDIR_CALLBACK_FILEINFO cb, int type, void *user);
/*
Function: fs_makedir
Creates a directory
Parameters:
path - Directory to create
Returns:
Returns 0 on success. Negative value on failure.
Remarks:
Does not create several directories if needed. "a/b/c" will result
in a failure if b or a does not exist.
*/
/**
* Creates a directory.
*
* @ingroup Filesystem
*
* @param path Directory to create.
*
* @return 0 on success. Negative value on failure.
*
* @remark Does not create several directories if needed. "a/b/c" will
* result in a failure if b or a does not exist.
*
* @remark The strings are treated as zero-terminated strings.
*/
int fs_makedir(const char *path);
/*
Function: fs_removedir
Removes a directory
Parameters:
path - Directory to remove
Returns:
Returns 0 on success. Negative value on failure.
Remarks:
Cannot remove a non-empty directory.
*/
/**
* Removes a directory.
*
* @ingroup Filesystem
*
* @param path Directory to remove.
*
* @return 0 on success. Negative value on failure.
*
* @remark Cannot remove a non-empty directory.
*
* @remark The strings are treated as zero-terminated strings.
*/
int fs_removedir(const char *path);
/*
Function: fs_makedir_rec_for
Recursively create directories for a file
Parameters:
path - File for which to create directories
Returns:
Returns 0 on success. Negative value on failure.
*/
/**
* Recursively create directories for a file.
*
* @ingroup Filesystem
*
* @param path - File for which to create directories.
*
* @return 0 on success. Negative value on failure.
*
* @remark The strings are treated as zero-terminated strings.
*/
int fs_makedir_rec_for(const char *path);
/*
Function: fs_storage_path
Fetches per user configuration directory.
Returns:
Returns 0 on success. Negative value on failure.
Remarks:
- Returns ~/.appname on UNIX based systems
- Returns ~/Library/Applications Support/appname on macOS
- Returns %APPDATA%/Appname on Windows based systems
*/
/**
* Fetches per user configuration directory.
*
* @ingroup Filesystem
*
* @param appname Name of the application.
* @param path Buffer that will receive the storage path.
* @param max Size of the buffer.
*
* @return 0 on success. Negative value on failure.
*
* @remark Returns ~/.appname on UNIX based systems.
* @remark Returns ~/Library/Applications Support/appname on macOS.
* @remark Returns %APPDATA%/Appname on Windows based systems.
*
* @remark The strings are treated as zero-terminated strings.
*/
int fs_storage_path(const char *appname, char *path, int max);
/*
Function: fs_is_dir
Checks if directory exists
/**
* Checks if a file exists.
*
* @ingroup Filesystem
*
* @param path the path to check.
*
* @return 1 if a file with the given path exists,
* 0 on failure or if the file does not exist.
*
* @remark The strings are treated as zero-terminated strings.
*/
int fs_is_file(const char *path);
Returns:
Returns 1 on success, 0 on failure.
*/
/**
* Checks if a folder exists.
*
* @ingroup Filesystem
*
* @param path the path to check.
*
* @return 1 if a folder with the given path exists,
* 0 on failure or if the folder does not exist.
*
* @remark The strings are treated as zero-terminated strings.
*/
int fs_is_dir(const char *path);
/*
Function: fs_is_relative_path
Checks whether a given path is relative or absolute.
Returns:
Returns 1 if relative, 0 if absolute.
*/
/**
* Checks whether a given path is relative or absolute.
*
* @ingroup Filesystem
*
* @param path Path to check.
*
* @return 1 if relative, 0 if absolute.
*
* @remark The strings are treated as zero-terminated strings.
*/
int fs_is_relative_path(const char *path);
/*
Function: fs_chdir
Changes current working directory
Returns:
Returns 0 on success, 1 on failure.
*/
/**
* Changes the current working directory.
*
* @ingroup Filesystem
*
* @param path New working directory path.
*
* @return 0 on success, 1 on failure.
*
* @remark The strings are treated as zero-terminated strings.
*/
int fs_chdir(const char *path);
/*
Function: fs_getcwd
Gets the current working directory.
Returns:
Returns a pointer to the buffer on success, 0 on failure.
*/
/**
* Gets the current working directory.
*
* @ingroup Filesystem
*
* @param buffer Buffer that will receive the current working directory.
* @param buffer_size Size of the buffer.
*
* @return Pointer to the buffer on success, nullptr on failure.
*
* @remark The strings are treated as zero-terminated strings.
*/
char *fs_getcwd(char *buffer, int buffer_size);
/*
Function: fs_parent_dir
Get the parent directory of a directory
Parameters:
path - The directory string
Returns:
Returns 0 on success, 1 on failure.
Remarks:
- The string is treated as zero-terminated string.
*/
/**
* Get the parent directory of a directory.
*
* @ingroup Filesystem
*
* @param path Path of the directory. The parent will be store in this buffer as well.
*
* @return 0 on success, 1 on failure.
*
* @remark The strings are treated as zero-terminated strings.
*/
int fs_parent_dir(char *path);
/*
Function: fs_remove
Deletes the file with the specified name.
Parameters:
filename - The file to delete
Returns:
Returns 0 on success, 1 on failure.
Remarks:
- The strings are treated as zero-terminated strings.
- Returns an error if the path specifies a directory name.
*/
/**
* Deletes a file.
*
* @ingroup Filesystem
*
* @param filename Path of the file to delete.
*
* @return 0 on success, 1 on failure.
*
* @remark The strings are treated as zero-terminated strings.
* @remark Returns an error if the path specifies a directory name.
*/
int fs_remove(const char *filename);
/*
Function: fs_rename
Renames the file or directory. If the paths differ the file will be moved.
Parameters:
oldname - The current name
newname - The new name
Returns:
Returns 0 on success, 1 on failure.
Remarks:
- The strings are treated as zero-terminated strings.
*/
/**
* Renames the file or directory. If the paths differ the file will be moved.
*
* @ingroup Filesystem
*
* @param oldname The current path of a file or directory.
* @param newname The new path for the file or directory.
*
* @return 0 on success, 1 on failure.
*
* @remark The strings are treated as zero-terminated strings.
*/
int fs_rename(const char *oldname, const char *newname);
/*
Function: fs_file_time
Gets the creation and the last modification date of a file.
Parameters:
name - The filename.
created - Pointer to time_t
modified - Pointer to time_t
Returns:
0 on success, non-zero on failure
Remarks:
- Returned time is in seconds since UNIX Epoch
*/
/**
* Gets the creation and the last modification date of a file or directory.
*
* @ingroup Filesystem
*
* @param name Path of a file or directory.
* @param created Pointer where the creation time will be stored.
* @param modified Pointer where the modification time will be stored.
*
* @return 0 on success, non-zero on failure.
*
* @remark The strings are treated as zero-terminated strings.
* @remark Returned time is in seconds since UNIX Epoch.
*/
int fs_file_time(const char *name, time_t *created, time_t *modified);
/*

View file

@ -2284,8 +2284,18 @@ void CClient::FinishMapDownload()
m_MapdownloadTotalsize = -1;
SHA256_DIGEST *pSha256 = m_MapdownloadSha256Present ? &m_MapdownloadSha256 : 0;
Storage()->RemoveFile(m_aMapdownloadFilename, IStorage::TYPE_SAVE);
Storage()->RenameFile(m_aMapdownloadFilenameTemp, m_aMapdownloadFilename, IStorage::TYPE_SAVE);
bool FileSuccess = true;
if(Storage()->FileExists(m_aMapdownloadFilename, IStorage::TYPE_SAVE))
FileSuccess &= Storage()->RemoveFile(m_aMapdownloadFilename, IStorage::TYPE_SAVE);
FileSuccess &= Storage()->RenameFile(m_aMapdownloadFilenameTemp, m_aMapdownloadFilename, IStorage::TYPE_SAVE);
if(!FileSuccess)
{
ResetMapDownload();
char aError[128 + IO_MAX_PATH_LENGTH];
str_format(aError, sizeof(aError), Localize("Could not save downloaded map. Try manually deleting this file: %s"), m_aMapdownloadFilename);
DisconnectWithReason(aError);
return;
}
// load map
const char *pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, pSha256, m_MapdownloadCrc);

View file

@ -433,6 +433,44 @@ public:
return 0;
}
template<typename F>
bool GenericExists(const char *pFilename, int Type, F &&CheckFunction)
{
TranslateType(Type, pFilename);
char aBuffer[IO_MAX_PATH_LENGTH];
if(Type == TYPE_ALL)
{
// check all available directories
for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
{
if(CheckFunction(GetPath(i, pFilename, aBuffer, sizeof(aBuffer))))
return true;
}
return false;
}
else if(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths))
{
// check wanted directory
return CheckFunction(GetPath(Type, pFilename, aBuffer, sizeof(aBuffer)));
}
else
{
dbg_assert(false, "Type invalid");
return false;
}
}
bool FileExists(const char *pFilename, int Type) override
{
return GenericExists(pFilename, Type, fs_is_file);
}
bool FolderExists(const char *pFilename, int Type) override
{
return GenericExists(pFilename, Type, fs_is_dir);
}
bool ReadFile(const char *pFilename, int Type, void **ppResult, unsigned *pResultLen) override
{
IOHANDLE File = OpenFile(pFilename, IOFLAG_READ, Type);

View file

@ -25,15 +25,15 @@ public:
/**
* Translates to TYPE_SAVE if a path is relative
* and to TYPE_ABSOLUTE if a path is absolute.
* Only usable with OpenFile, ReadFile, ReadFileStr
* and GetCompletePath.
* Only usable with OpenFile, ReadFile, ReadFileStr,
* GetCompletePath, FileExists and FolderExists.
*/
TYPE_SAVE_OR_ABSOLUTE = -3,
/**
* Translates to TYPE_ALL if a path is relative
* and to TYPE_ABSOLUTE if a path is absolute.
* Only usable with OpenFile, ReadFile, ReadFileStr
* and GetCompletePath.
* Only usable with OpenFile, ReadFile, ReadFileStr,
* GetCompletePath, FileExists and FolderExists.
*/
TYPE_ALL_OR_ABSOLUTE = -4,
@ -45,6 +45,8 @@ public:
virtual void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) = 0;
virtual void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) = 0;
virtual IOHANDLE OpenFile(const char *pFilename, int Flags, int Type, char *pBuffer = nullptr, int BufferSize = 0) = 0;
virtual bool FileExists(const char *pFilename, int Type) = 0;
virtual bool FolderExists(const char *pFilename, int Type) = 0;
virtual bool ReadFile(const char *pFilename, int Type, void **ppResult, unsigned *pResultLen) = 0;
virtual char *ReadFileStr(const char *pFilename, int Type) = 0;
virtual bool FindFile(const char *pFilename, const char *pPath, int Type, char *pBuffer, int BufferSize) = 0;