From f65ded4501872447111eee2360ecc1a0397652b1 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Thu, 14 Dec 2023 15:51:20 +0100 Subject: [PATCH 1/2] implement MoveFileToRecycleBin on Linux --- Source/Engine/Content/Content.cpp | 2 +- .../Engine/Platform/Linux/LinuxFileSystem.cpp | 66 +++++++++++++++++++ .../Engine/Platform/Linux/LinuxFileSystem.h | 3 + 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 982ae599f..24bae188b 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -585,7 +585,7 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id) } } -#if PLATFORM_WINDOWS +#if PLATFORM_WINDOWS || PLATFORM_LINUX // Safety way - move file to the recycle bin if (FileSystem::MoveFileToRecycleBin(path)) { diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index 97cde4a1c..8ff650f9f 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -24,6 +24,8 @@ #include #include +#include "ThirdParty/curl/curl.h" + const DateTime UnixEpoch(1970, 1, 1); bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array& filenames) @@ -511,6 +513,70 @@ out_error: return true; } +bool LinuxFileSystem::MoveFileToRecycleBin(const StringView& path) +{ + String trashDir; + GetSpecialFolderPath(SpecialFolder::LocalAppData, trashDir); + trashDir += TEXT("/Trash"); + String filesDir = trashDir + TEXT("/files"); + String infoDir = trashDir + TEXT("/info"); + String trashName = getBaseName(path); + String dst = filesDir / trashName; + + int fd = -1; + if (FileExists(dst)) + { + String ext = GetExtension(path); + dst = filesDir / getNameWithoutExtension(path) / TEXT("XXXXXX.") / ext; + char *base = dst.ToStringAnsi().Get(); + char *extension = GetExtension(path).ToStringAnsi().Get(); + int templateLength = strlen(base) + 7 + strlen(extension); + char *templateString = dst.ToStringAnsi().Get(); + fd = mkstemps(templateString, ext.Length()); + dst = filesDir / String(templateString); + trashName = getBaseName(dst); + } + if (fd != -1) + close(fd); + + if (!MoveFile(dst, path, true)) + { + // not MoveFile means success so write the info file + String infoFile = infoDir / trashName + TEXT(".trashinfo"); + StringBuilder trashInfo; + const char *ansiPath = path.ToStringAnsi().Get(); + char *encoded = curl_escape(ansiPath, strlen(ansiPath)); + DateTime now = DateTime::Now(); + String rfcDate = String::Format(TEXT("{0}-{1:0>2}-{2:0>2}T{3:0>2}:{4:0>2}:{5:0>2}"), + now.GetYear(), now.GetMonth(), now.GetDay(), now.GetHour(), now.GetMinute(), now.GetSecond()); + trashInfo.AppendLine(TEXT("[Trash Info]")).Append(TEXT("Path=")).Append(encoded).Append(TEXT('\n')); + trashInfo.Append(TEXT("DeletionDate=")).Append(rfcDate).Append(TEXT("\n\0")); + free(encoded); + // a failure to write the info file is considered non-fatal according to the FreeDesktop.org specification + FileBase::WriteAllText(infoFile, trashInfo, Encoding::ANSI); + // return false on success as the Windows pendant does + return false; + } + return true; +} + +String LinuxFileSystem::getBaseName(const StringView& path) +{ + String baseName = path.Substring(path.FindLast('/') + 1); + if (baseName.IsEmpty()) + baseName = path; + return baseName; +} + +String LinuxFileSystem::getNameWithoutExtension(const StringView& path) +{ + String baseName = getBaseName(path); + const int pos = baseName.FindLast('.'); + if (pos > 0) + return baseName.Left(pos); + return baseName; +} + bool LinuxFileSystem::getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern) { size_t pathLength; diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.h b/Source/Engine/Platform/Linux/LinuxFileSystem.h index 6184d6720..0abe88084 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.h +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.h @@ -24,6 +24,7 @@ public: static bool GetChildDirectories(Array& results, const String& directory); static bool FileExists(const StringView& path); static bool DeleteFile(const StringView& path); + static bool MoveFileToRecycleBin(const StringView& path); static uint64 GetFileSize(const StringView& path); static bool IsReadOnly(const StringView& path); static bool SetReadOnly(const StringView& path, bool isReadOnly); @@ -50,6 +51,8 @@ private: static bool getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern); static bool getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern); + static String getBaseName(const StringView& path); + static String getNameWithoutExtension(const StringView& path); }; #endif From 8c45659b2a31e8af2488f377420749a8dc630a19 Mon Sep 17 00:00:00 2001 From: nothingTVatYT Date: Thu, 14 Dec 2023 17:29:08 +0100 Subject: [PATCH 2/2] use own encoder and some more error handling --- .../Engine/Platform/Linux/LinuxFileSystem.cpp | 74 ++++++++++++++----- .../Engine/Platform/Linux/LinuxFileSystem.h | 1 + 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp index 8ff650f9f..da422f29a 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.cpp +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.cpp @@ -24,8 +24,6 @@ #include #include -#include "ThirdParty/curl/curl.h" - const DateTime UnixEpoch(1970, 1, 1); bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array& filenames) @@ -436,6 +434,7 @@ bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, boo return false; } } + LOG(Error, "Cannot copy {} to {}", src, dst); return true; } return false; @@ -518,22 +517,26 @@ bool LinuxFileSystem::MoveFileToRecycleBin(const StringView& path) String trashDir; GetSpecialFolderPath(SpecialFolder::LocalAppData, trashDir); trashDir += TEXT("/Trash"); - String filesDir = trashDir + TEXT("/files"); - String infoDir = trashDir + TEXT("/info"); + const String filesDir = trashDir + TEXT("/files"); + const String infoDir = trashDir + TEXT("/info"); String trashName = getBaseName(path); String dst = filesDir / trashName; int fd = -1; if (FileExists(dst)) { - String ext = GetExtension(path); - dst = filesDir / getNameWithoutExtension(path) / TEXT("XXXXXX.") / ext; - char *base = dst.ToStringAnsi().Get(); - char *extension = GetExtension(path).ToStringAnsi().Get(); - int templateLength = strlen(base) + 7 + strlen(extension); - char *templateString = dst.ToStringAnsi().Get(); - fd = mkstemps(templateString, ext.Length()); - dst = filesDir / String(templateString); + const String ext = GetExtension(path); + dst = filesDir / getNameWithoutExtension(path) + TEXT("XXXXXX.") + ext; + const char *templateString = dst.ToStringAnsi().Get(); + char writableName[strlen(templateString) + 1]; + strcpy(writableName, templateString); + fd = mkstemps(writableName, ext.Length() + 1); + if (fd < 0) + { + LOG(Error, "Cannot create a temporary file as {0}, errno={1}", String(writableName), errno); + return true; + } + dst = String(writableName); trashName = getBaseName(dst); } if (fd != -1) @@ -542,16 +545,23 @@ bool LinuxFileSystem::MoveFileToRecycleBin(const StringView& path) if (!MoveFile(dst, path, true)) { // not MoveFile means success so write the info file - String infoFile = infoDir / trashName + TEXT(".trashinfo"); + const String infoFile = infoDir / trashName + TEXT(".trashinfo"); StringBuilder trashInfo; const char *ansiPath = path.ToStringAnsi().Get(); - char *encoded = curl_escape(ansiPath, strlen(ansiPath)); - DateTime now = DateTime::Now(); - String rfcDate = String::Format(TEXT("{0}-{1:0>2}-{2:0>2}T{3:0>2}:{4:0>2}:{5:0>2}"), - now.GetYear(), now.GetMonth(), now.GetDay(), now.GetHour(), now.GetMinute(), now.GetSecond()); + // in the worst case the length will be tripled + const int maxLength = strlen(ansiPath) * 3 + 1; + char encoded[maxLength]; + if (!UrnEncodePath(ansiPath, encoded, maxLength)) + { + // unlikely but better keep something + strcpy(encoded, ansiPath); + } + const DateTime now = DateTime::Now(); + const String rfcDate = String::Format(TEXT("{0}-{1:0>2}-{2:0>2}T{3:0>2}:{4:0>2}:{5:0>2}"), + now.GetYear(), now.GetMonth(), now.GetDay(), now.GetHour(), now.GetMinute(), now.GetSecond()); trashInfo.AppendLine(TEXT("[Trash Info]")).Append(TEXT("Path=")).Append(encoded).Append(TEXT('\n')); trashInfo.Append(TEXT("DeletionDate=")).Append(rfcDate).Append(TEXT("\n\0")); - free(encoded); + // a failure to write the info file is considered non-fatal according to the FreeDesktop.org specification FileBase::WriteAllText(infoFile, trashInfo, Encoding::ANSI); // return false on success as the Windows pendant does @@ -577,6 +587,34 @@ String LinuxFileSystem::getNameWithoutExtension(const StringView& path) return baseName; } +bool LinuxFileSystem::UrnEncodePath(const char *path, char *result, const int maxLength) +{ + static auto digits = "0123456789ABCDEF"; + const char *src = path; + char *dest = result; + while (*src) + { + if (*src <= 0x20 || *src > 0x7f || *src == '%') + { + if (dest - result + 4 > maxLength) + return false; + *dest++ = '%'; + *dest++ = digits[*src>>4 & 0xf]; + *dest = digits[*src & 0xf]; + } + else + { + *dest = *src; + } + src++; + dest++; + if (dest - result == maxLength) + return false; + } + *dest = 0; + return true; +} + bool LinuxFileSystem::getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern) { size_t pathLength; diff --git a/Source/Engine/Platform/Linux/LinuxFileSystem.h b/Source/Engine/Platform/Linux/LinuxFileSystem.h index 0abe88084..ca02b4121 100644 --- a/Source/Engine/Platform/Linux/LinuxFileSystem.h +++ b/Source/Engine/Platform/Linux/LinuxFileSystem.h @@ -49,6 +49,7 @@ public: private: + static bool UrnEncodePath(const char *path, char *result, int maxLength); static bool getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern); static bool getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern); static String getBaseName(const StringView& path);