diff --git a/include/SDL3/SDL_filesystem.h b/include/SDL3/SDL_filesystem.h index b0a0f19fb..ff6ba13e6 100644 --- a/include/SDL3/SDL_filesystem.h +++ b/include/SDL3/SDL_filesystem.h @@ -327,6 +327,18 @@ extern SDL_DECLSPEC int SDLCALL SDL_RemovePath(const char *path); */ extern SDL_DECLSPEC int SDLCALL SDL_RenamePath(const char *oldpath, const char *newpath); +/** + * Copy a file. + * + * \param oldpath the old path. + * \param newpath the new path. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + */ +extern SDL_DECLSPEC int SDLCALL SDL_CopyFile(const char *oldpath, const char *newpath); + /** * Get information about a filesystem path. * diff --git a/include/SDL3/SDL_storage.h b/include/SDL3/SDL_storage.h index bf8138f7b..f95030adf 100644 --- a/include/SDL3/SDL_storage.h +++ b/include/SDL3/SDL_storage.h @@ -83,6 +83,9 @@ typedef struct SDL_StorageInterface /* Rename a path, optional for read-only storage */ int (SDLCALL *rename)(void *userdata, const char *oldpath, const char *newpath); + /* Copy a file, optional for read-only storage */ + int (SDLCALL *copy)(void *userdata, const char *oldpath, const char *newpath); + /* Get the space remaining, optional for read-only storage */ Uint64 (SDLCALL *space_remaining)(void *userdata); } SDL_StorageInterface; @@ -337,6 +340,21 @@ extern SDL_DECLSPEC int SDLCALL SDL_RemoveStoragePath(SDL_Storage *storage, cons */ extern SDL_DECLSPEC int SDLCALL SDL_RenameStoragePath(SDL_Storage *storage, const char *oldpath, const char *newpath); +/** + * Copy a file in a writable storage container. + * + * \param storage a storage container. + * \param oldpath the old path. + * \param newpath the new path. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_StorageReady + */ +extern SDL_DECLSPEC int SDLCALL SDL_CopyStorageFile(SDL_Storage *storage, const char *oldpath, const char *newpath); + /** * Get information about a filesystem path in a storage container. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index a3bd4b50e..bed88ca50 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -55,7 +55,9 @@ SDL3_0.0.0 { SDL_ConvertPixelsAndColorspace; SDL_ConvertSurface; SDL_ConvertSurfaceAndColorspace; + SDL_CopyFile; SDL_CopyProperties; + SDL_CopyStorageFile; SDL_CreateAudioStream; SDL_CreateColorCursor; SDL_CreateCondition; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 4d53f3df9..d25a219f2 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -80,7 +80,9 @@ #define SDL_ConvertPixelsAndColorspace SDL_ConvertPixelsAndColorspace_REAL #define SDL_ConvertSurface SDL_ConvertSurface_REAL #define SDL_ConvertSurfaceAndColorspace SDL_ConvertSurfaceAndColorspace_REAL +#define SDL_CopyFile SDL_CopyFile_REAL #define SDL_CopyProperties SDL_CopyProperties_REAL +#define SDL_CopyStorageFile SDL_CopyStorageFile_REAL #define SDL_CreateAudioStream SDL_CreateAudioStream_REAL #define SDL_CreateColorCursor SDL_CreateColorCursor_REAL #define SDL_CreateCondition SDL_CreateCondition_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index ebd5a3524..83ba097e4 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -100,7 +100,9 @@ SDL_DYNAPI_PROC(int,SDL_ConvertPixels,(int a, int b, SDL_PixelFormat c, const vo SDL_DYNAPI_PROC(int,SDL_ConvertPixelsAndColorspace,(int a, int b, SDL_PixelFormat c, SDL_Colorspace d, SDL_PropertiesID e, const void *f, int g, SDL_PixelFormat h, SDL_Colorspace i, SDL_PropertiesID j, void *k, int l),(a,b,c,d,e,f,g,h,i,j,k,l),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_ConvertSurface,(SDL_Surface *a, SDL_PixelFormat b),(a,b),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_ConvertSurfaceAndColorspace,(SDL_Surface *a, SDL_PixelFormat b, SDL_Palette *c, SDL_Colorspace d, SDL_PropertiesID e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(int,SDL_CopyFile,(const char *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_CopyProperties,(SDL_PropertiesID a, SDL_PropertiesID b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_CopyStorageFile,(SDL_Storage *a, const char *b, const char *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_CreateAudioStream,(const SDL_AudioSpec *a, const SDL_AudioSpec *b),(a,b),return) SDL_DYNAPI_PROC(SDL_Cursor*,SDL_CreateColorCursor,(SDL_Surface *a, int b, int c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_Condition*,SDL_CreateCondition,(void),(),return) diff --git a/src/filesystem/SDL_filesystem.c b/src/filesystem/SDL_filesystem.c index b1ba4266f..9eb44d486 100644 --- a/src/filesystem/SDL_filesystem.c +++ b/src/filesystem/SDL_filesystem.c @@ -43,6 +43,16 @@ int SDL_RenamePath(const char *oldpath, const char *newpath) return SDL_SYS_RenamePath(oldpath, newpath); } +int SDL_CopyFile(const char *oldpath, const char *newpath) +{ + if (!oldpath) { + return SDL_InvalidParamError("oldpath"); + } else if (!newpath) { + return SDL_InvalidParamError("newpath"); + } + return SDL_SYS_CopyFile(oldpath, newpath); +} + int SDL_CreateDirectory(const char *path) { /* TODO: Recursively create subdirectories */ diff --git a/src/filesystem/SDL_sysfilesystem.h b/src/filesystem/SDL_sysfilesystem.h index 9f02a1e8d..5dfb3ebc3 100644 --- a/src/filesystem/SDL_sysfilesystem.h +++ b/src/filesystem/SDL_sysfilesystem.h @@ -30,6 +30,7 @@ extern char *SDL_SYS_GetUserFolder(SDL_Folder folder); int SDL_SYS_EnumerateDirectory(const char *path, const char *dirname, SDL_EnumerateDirectoryCallback cb, void *userdata); int SDL_SYS_RemovePath(const char *path); int SDL_SYS_RenamePath(const char *oldpath, const char *newpath); +int SDL_SYS_CopyFile(const char *oldpath, const char *newpath); int SDL_SYS_CreateDirectory(const char *path); int SDL_SYS_GetPathInfo(const char *path, SDL_PathInfo *info); diff --git a/src/filesystem/dummy/SDL_sysfsops.c b/src/filesystem/dummy/SDL_sysfsops.c index cad93bdbf..2b34a185c 100644 --- a/src/filesystem/dummy/SDL_sysfsops.c +++ b/src/filesystem/dummy/SDL_sysfsops.c @@ -43,6 +43,11 @@ int SDL_SYS_RenamePath(const char *oldpath, const char *newpath) return SDL_Unsupported(); } +int SDL_SYS_CopyFile(const char *oldpath, const char *newpath) +{ + return SDL_Unsupported(); +} + int SDL_SYS_CreateDirectory(const char *path) { return SDL_Unsupported(); diff --git a/src/filesystem/posix/SDL_sysfsops.c b/src/filesystem/posix/SDL_sysfsops.c index 7f73b29bd..80c040c44 100644 --- a/src/filesystem/posix/SDL_sysfsops.c +++ b/src/filesystem/posix/SDL_sysfsops.c @@ -79,6 +79,73 @@ int SDL_SYS_RenamePath(const char *oldpath, const char *newpath) return 0; } +int SDL_SYS_CopyFile(const char *oldpath, const char *newpath) +{ + char *buffer = NULL; + char *tmppath = NULL; + SDL_IOStream *input = NULL; + SDL_IOStream *output = NULL; + const size_t maxlen = 4096; + size_t len; + int retval = -1; + + if (SDL_asprintf(&tmppath, "%s.tmp", newpath) < 0) { + goto done; + } + + input = SDL_IOFromFile(oldpath, "rb"); + if (!input) { + goto done; + } + + output = SDL_IOFromFile(tmppath, "wb"); + if (!output) { + goto done; + } + + buffer = (char *)SDL_malloc(maxlen); + if (!buffer) { + goto done; + } + + while ((len = SDL_ReadIO(input, buffer, maxlen)) > 0) { + if (SDL_WriteIO(output, buffer, len) < len) { + goto done; + } + } + if (SDL_GetIOStatus(input) != SDL_IO_STATUS_EOF) { + goto done; + } + + SDL_CloseIO(input); + input = NULL; + + if (SDL_CloseIO(output) < 0) { + goto done; + } + output = NULL; + + if (SDL_RenamePath(tmppath, newpath) < 0) { + SDL_RemovePath(tmppath); + goto done; + } + + retval = 0; + +done: + if (output) { + SDL_CloseIO(output); + SDL_RemovePath(tmppath); + } + if (input) { + SDL_CloseIO(input); + } + SDL_free(tmppath); + SDL_free(buffer); + + return retval; +} + int SDL_SYS_CreateDirectory(const char *path) { const int rc = mkdir(path, 0770); diff --git a/src/filesystem/windows/SDL_sysfsops.c b/src/filesystem/windows/SDL_sysfsops.c index 70d3c9696..b4e9b4db9 100644 --- a/src/filesystem/windows/SDL_sysfsops.c +++ b/src/filesystem/windows/SDL_sysfsops.c @@ -131,6 +131,25 @@ int SDL_SYS_RenamePath(const char *oldpath, const char *newpath) return !rc ? WIN_SetError("Couldn't rename path") : 0; } +int SDL_SYS_CopyFile(const char *oldpath, const char *newpath) +{ + WCHAR *woldpath = WIN_UTF8ToStringW(oldpath); + if (!woldpath) { + return -1; + } + + WCHAR *wnewpath = WIN_UTF8ToStringW(newpath); + if (!wnewpath) { + SDL_free(woldpath); + return -1; + } + + const BOOL rc = CopyFileW(woldpath, wnewpath, TRUE); + SDL_free(wnewpath); + SDL_free(woldpath); + return !rc ? WIN_SetError("Couldn't copy path") : 0; +} + int SDL_SYS_CreateDirectory(const char *path) { WCHAR *wpath = WIN_UTF8ToStringW(path); diff --git a/src/storage/SDL_storage.c b/src/storage/SDL_storage.c index b61f1a1ee..47adcfe7c 100644 --- a/src/storage/SDL_storage.c +++ b/src/storage/SDL_storage.c @@ -291,6 +291,24 @@ int SDL_RenameStoragePath(SDL_Storage *storage, const char *oldpath, const char return storage->iface.rename(storage->userdata, oldpath, newpath); } +int SDL_CopyStorageFile(SDL_Storage *storage, const char *oldpath, const char *newpath) +{ + CHECK_STORAGE_MAGIC() + + if (!oldpath) { + return SDL_InvalidParamError("oldpath"); + } + if (!newpath) { + return SDL_InvalidParamError("newpath"); + } + + if (!storage->iface.copy) { + return SDL_Unsupported(); + } + + return storage->iface.copy(storage->userdata, oldpath, newpath); +} + int SDL_GetStoragePathInfo(SDL_Storage *storage, const char *path, SDL_PathInfo *info) { SDL_PathInfo dummy; diff --git a/src/storage/generic/SDL_genericstorage.c b/src/storage/generic/SDL_genericstorage.c index 9f174238e..859786dfb 100644 --- a/src/storage/generic/SDL_genericstorage.c +++ b/src/storage/generic/SDL_genericstorage.c @@ -160,6 +160,21 @@ static int GENERIC_RenameStoragePath(void *userdata, const char *oldpath, const return result; } +static int GENERIC_CopyStorageFile(void *userdata, const char *oldpath, const char *newpath) +{ + int result = -1; + + char *fulloldpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, oldpath); + char *fullnewpath = GENERIC_INTERNAL_CreateFullPath((char *)userdata, newpath); + if (fulloldpath && fullnewpath) { + result = SDL_CopyFile(fulloldpath, fullnewpath); + } + SDL_free(fulloldpath); + SDL_free(fullnewpath); + + return result; +} + static Uint64 GENERIC_GetStorageSpaceRemaining(void *userdata) { /* TODO: There's totally a way to query a folder root's quota... */ @@ -176,6 +191,7 @@ static const SDL_StorageInterface GENERIC_title_iface = { NULL, /* mkdir */ NULL, /* remove */ NULL, /* rename */ + NULL, /* copy */ NULL /* space_remaining */ }; @@ -219,6 +235,7 @@ static const SDL_StorageInterface GENERIC_user_iface = { GENERIC_CreateStorageDirectory, GENERIC_RemoveStoragePath, GENERIC_RenameStoragePath, + GENERIC_CopyStorageFile, GENERIC_GetStorageSpaceRemaining }; @@ -257,6 +274,7 @@ static const SDL_StorageInterface GENERIC_file_iface = { GENERIC_CreateStorageDirectory, GENERIC_RemoveStoragePath, GENERIC_RenameStoragePath, + GENERIC_CopyStorageFile, GENERIC_GetStorageSpaceRemaining }; diff --git a/src/storage/steam/SDL_steamstorage.c b/src/storage/steam/SDL_steamstorage.c index 9944cda27..c4295145b 100644 --- a/src/storage/steam/SDL_steamstorage.c +++ b/src/storage/steam/SDL_steamstorage.c @@ -140,6 +140,7 @@ static const SDL_StorageInterface STEAM_user_iface = { NULL, /* mkdir */ NULL, /* remove */ NULL, /* rename */ + NULL, /* copy */ STEAM_GetStorageSpaceRemaining }; diff --git a/test/testfilesystem.c b/test/testfilesystem.c index 286de004a..9d840efd3 100644 --- a/test/testfilesystem.c +++ b/test/testfilesystem.c @@ -109,6 +109,8 @@ int main(int argc, char *argv[]) if (base_path) { const char * const *globlist; + SDL_IOStream *stream; + const char *text = "foo\n"; if (SDL_EnumerateDirectory(base_path, enum_callback, NULL) < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Base path enumeration failed!"); @@ -140,6 +142,42 @@ int main(int argc, char *argv[]) } else if (SDL_RemovePath("testfilesystem-test") == -1) { /* THIS SHOULD NOT FAIL! Removing a directory that is already gone should succeed here. */ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_RemovePath('testfilesystem-test') failed: %s", SDL_GetError()); } + + stream = SDL_IOFromFile("testfilesystem-A", "wb"); + if (stream) { + SDL_WriteIO(stream, text, SDL_strlen(text)); + SDL_CloseIO(stream); + + if (SDL_RenamePath("testfilesystem-A", "testfilesystem-B") < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_RenamePath('testfilesystem-A', 'testfilesystem-B') failed: %s", SDL_GetError()); + } else if (SDL_CopyFile("testfilesystem-B", "testfilesystem-A") < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CopyFile('testfilesystem-B', 'testfilesystem-A') failed: %s", SDL_GetError()); + } else { + size_t sizeA, sizeB; + char *textA, *textB; + + textA = (char *)SDL_LoadFile("testfilesystem-A", &sizeA); + if (!textA || sizeA != SDL_strlen(text) || SDL_strcmp(textA, text) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Contents of testfilesystem-A didn't match, expected %s, got %s\n", text, textA); + } + SDL_free(textA); + + textB = (char *)SDL_LoadFile("testfilesystem-B", &sizeB); + if (!textB || sizeB != SDL_strlen(text) || SDL_strcmp(textB, text) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Contents of testfilesystem-B didn't match, expected %s, got %s\n", text, textB); + } + SDL_free(textB); + } + + if (SDL_RemovePath("testfilesystem-A") < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_RemovePath('testfilesystem-A') failed: %s", SDL_GetError()); + } + if (SDL_RemovePath("testfilesystem-B") < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_RemovePath('testfilesystem-B') failed: %s", SDL_GetError()); + } + } else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_IOFromFile('testfilesystem-A', 'w') failed: %s", SDL_GetError()); + } } SDL_Quit();