Trash handling is too high level for FileUtil, move it up.

This commit is contained in:
Henrik Rydgård
2025-04-15 17:27:41 +02:00
parent ef2451fc45
commit 71533ac2b7
15 changed files with 118 additions and 105 deletions
+1 -54
View File
@@ -42,13 +42,12 @@
#include "Common/StringUtils.h"
#include "Common/TimeUtil.h"
#include "Common/SysError.h"
#include "Common/System/Request.h"
#ifdef _WIN32
#include "Common/CommonWindows.h"
#include <sys/utime.h>
#include <shlobj.h> // for SHGetFolderPath
#include <shellapi.h>
#include <commdlg.h> // for GetSaveFileName
#include <io.h>
#include <direct.h> // getcwd
#if PPSSPP_PLATFORM(UWP)
@@ -1371,56 +1370,4 @@ bool IsProbablyInDownloadsFolder(const Path &filename) {
return filename.FilePathContainsNoCase("download");
}
// The Win32 implementation kinda belongs in ShellUtil.cpp but that's in the wrong project.
// Some reorganization is in order...
bool MoveFileToTrash(const Path &path) {
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
IFileOperation *pFileOp = nullptr;
HRESULT hr = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pFileOp));
if (FAILED(hr)) {
return false;
}
// Set operation flags
hr = pFileOp->SetOperationFlags(FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT);
if (FAILED(hr)) {
pFileOp->Release();
CoUninitialize();
return false;
}
// Create a shell item from the file path
IShellItem* pItem = nullptr;
hr = SHCreateItemFromParsingName(path.ToWString().c_str(), nullptr, IID_PPV_ARGS(&pItem));
if (SUCCEEDED(hr)) {
// Schedule the delete (move to recycle bin)
hr = pFileOp->DeleteItem(pItem, nullptr);
if (SUCCEEDED(hr)) {
hr = pFileOp->PerformOperations(); // Execute
}
pItem->Release();
}
pFileOp->Release();
return true;
#else
return false;
#endif
}
bool MoveFileToTrashOrDelete(const Path &path) {
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
return MoveFileToTrash(path);
#else
return Delete(path);
#endif
}
bool MoveDirectoryTreeToTrashOrDelete(const Path &path) {
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
return MoveFileToTrash(path); // works with directories
#else
return DeleteDirRecursively(path);
#endif
}
} // namespace File
-8
View File
@@ -233,12 +233,4 @@ inline bool ReadTextFileToString(const Path &path, std::string *str) {
// Return value must be delete[]-d.
uint8_t *ReadLocalFile(const Path &path, size_t *size);
// Moves to trash/recycle bin.
// This is only functional if SYSPROP_HAS_TRASH_BIN is true, otherwise will return false.
bool MoveFileToTrash(const Path &path);
// These move to trash if possible, otherwise permanently delete.
bool MoveFileToTrashOrDelete(const Path &path);
bool MoveDirectoryTreeToTrashOrDelete(const Path &path);
} // namespace
+5
View File
@@ -150,3 +150,8 @@ void System_RunCallbackInWndProc(void (*callback)(void *, void *), void *userdat
int64_t castUserData = (int64_t)userdata;
g_requestManager.MakeSystemRequest(SystemRequestType::RUN_CALLBACK_IN_WNDPROC, NO_REQUESTER_TOKEN, nullptr, nullptr, "", "", castPtr, castUserData);
}
void System_MoveToTrash(const Path &path) {
g_requestManager.MakeSystemRequest(SystemRequestType::MOVE_TO_TRASH, NO_REQUESTER_TOKEN, nullptr, nullptr, path.ToString(), "", 0);
}
+1
View File
@@ -189,6 +189,7 @@ inline void System_SendDebugScreenshot(std::string_view data, int height) {
g_requestManager.MakeSystemRequest(SystemRequestType::SEND_DEBUG_SCREENSHOT, NO_REQUESTER_TOKEN, nullptr, nullptr, data, "", height);
}
void System_MoveToTrash(const Path &path);
void System_RunCallbackInWndProc(void (*callback)(void *, void *), void *userdata);
// Non-inline to avoid including Path.h
+2
View File
@@ -98,6 +98,8 @@ enum class SystemRequestType {
MICROPHONE_COMMAND,
RUN_CALLBACK_IN_WNDPROC,
MOVE_TO_TRASH,
};
// Run a closure on the main thread. Used to safely implement UI that runs on another thread.
+6 -1
View File
@@ -49,6 +49,7 @@
#include "Common/GPU/Vulkan/VulkanLoader.h"
#include "Common/VR/PPSSPPVR.h"
#include "Common/System/OSD.h"
#include "Common/System/Request.h"
#include "Core/Config.h"
#include "Core/ConfigSettings.h"
#include "Core/ConfigValues.h"
@@ -1690,7 +1691,11 @@ bool Config::deleteGameConfig(const std::string& pGameId) {
Path fullIniFilePath = Path(getGameConfigFile(pGameId, &exists));
if (exists) {
File::MoveFileToTrashOrDelete(fullIniFilePath);
if (System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN)) {
System_MoveToTrash(fullIniFilePath);
} else {
File::Delete(fullIniFilePath);
}
}
return true;
}
+26
View File
@@ -318,6 +318,32 @@ bool UmdReplace(const Path &filepath, FileLoader **fileLoader, std::string &erro
return true;
}
// Close the return value with ZipClose (if non-null, of course).
struct zip *ZipOpenPath(const Path &fileName) {
int error = 0;
// Need to special case for content URI here, similar to OpenCFile.
struct zip *z;
#if PPSSPP_PLATFORM(ANDROID)
if (fileName.Type() == PathType::CONTENT_URI) {
int fd = File::OpenFD(fileName, File::OPEN_READ);
z = zip_fdopen(fd, 0, &error);
} else
#endif
{ // continuation of above else in the ifdef
z = zip_open(fileName.c_str(), 0, &error);
}
if (!z) {
ERROR_LOG(Log::HLE, "Failed to open ZIP file '%s', error code=%i", fileName.c_str(), error);
}
return z;
}
void ZipClose(struct zip *z) {
if (z)
zip_close(z);
}
bool DetectZipFileContents(const Path &fileName, ZipFileInfo *info) {
struct zip *z = ZipOpenPath(fileName);
if (!z) {
+3 -3
View File
@@ -155,9 +155,6 @@ Path ResolvePBPFile(const Path &filename);
IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorString);
// Can modify the string filename, as it calls IdentifyFile above.
bool LoadFile(FileLoader **fileLoaderPtr, IdentifiedFileType type, std::string *error_string);
bool UmdReplace(const Path &filepath, FileLoader **fileLoader, std::string &error);
@@ -187,5 +184,8 @@ struct ZipFileInfo {
std::string contentName;
};
struct zip *ZipOpenPath(const Path &fileName);
void ZipClose(zip *z);
bool DetectZipFileContents(const Path &fileName, ZipFileInfo *info);
void DetectZipFileContents(struct zip *z, ZipFileInfo *info);
-26
View File
@@ -52,32 +52,6 @@
GameManager g_GameManager;
// Close the return value with ZipClose (if non-null, of course).
struct zip *ZipOpenPath(Path fileName) {
int error = 0;
// Need to special case for content URI here, similar to OpenCFile.
struct zip *z;
#if PPSSPP_PLATFORM(ANDROID)
if (fileName.Type() == PathType::CONTENT_URI) {
int fd = File::OpenFD(fileName, File::OPEN_READ);
z = zip_fdopen(fd, 0, &error);
} else
#endif
{ // continuation of above else in the ifdef
z = zip_open(fileName.c_str(), 0, &error);
}
if (!z) {
ERROR_LOG(Log::HLE, "Failed to open ZIP file '%s', error code=%i", fileName.c_str(), error);
}
return z;
}
void ZipClose(struct zip *z) {
if (z)
zip_close(z);
}
GameManager::GameManager() {
}
+1 -4
View File
@@ -27,7 +27,6 @@
#include <optional>
#include "Common/Net/HTTPClient.h"
#include "Common/File/Path.h"
enum class GameManagerState {
IDLE,
@@ -45,6 +44,7 @@ struct ZipFileTask {
struct zip;
class FileLoader;
class Path;
struct ZipFileInfo;
class GameManager {
@@ -119,7 +119,4 @@ private:
extern GameManager g_GameManager;
struct zip *ZipOpenPath(Path fileName);
void ZipClose(zip *z);
bool CanExtractWithoutOverwrite(struct zip *z, const Path &destination, int maxOkFiles);
+28 -7
View File
@@ -30,6 +30,7 @@
#include "Common/File/FileUtil.h"
#include "Common/File/Path.h"
#include "Common/Render/ManagedTexture.h"
#include "Common/System/Request.h"
#include "Common/StringUtils.h"
#include "Common/TimeUtil.h"
#include "Core/FileSystems/ISOFileSystem.h"
@@ -86,6 +87,26 @@ bool IsReasonableEbootDirectory(Path path) {
return true;
}
static bool MoveFileToTrashOrDelete(const Path &path) {
if (System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN)) {
// TODO: Way to see if it succeeded
System_MoveToTrash(path);
return true;
} else {
return File::Delete(path);
}
}
static bool MoveDirectoryTreeToTrashOrDelete(const Path &path) {
if (System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN)) {
// TODO: Way to see if it succeeded
System_MoveToTrash(path);
return true;
} else {
return File::DeleteDirRecursively(path);
}
}
bool GameInfo::Delete() {
switch (fileType) {
case IdentifiedFileType::PSP_ISO:
@@ -94,7 +115,7 @@ bool GameInfo::Delete() {
// Just delete the one file (TODO: handle two-disk games as well somehow).
Path fileToRemove = filePath_;
INFO_LOG(Log::System, "Deleting file %s", fileToRemove.c_str());
File::MoveFileToTrashOrDelete(fileToRemove);
MoveFileToTrashOrDelete(fileToRemove);
g_recentFiles.Remove(filePath_.ToString());
return true;
}
@@ -108,14 +129,14 @@ bool GameInfo::Delete() {
// This can happen if the PBP is misplaced, see issue #20187
if (!IsReasonableEbootDirectory(directoryToRemove)) {
// Just delete the eboot.
File::MoveFileToTrashOrDelete(filePath_);
MoveFileToTrashOrDelete(filePath_);
g_recentFiles.Remove(filePath_.ToString());
return true;
}
// Delete the whole tree. We better be sure, see IsReasonableEbootDirectory.
INFO_LOG(Log::System, "Deleting directory %s", directoryToRemove.c_str());
if (!File::MoveDirectoryTreeToTrashOrDelete(directoryToRemove)) {
if (!MoveDirectoryTreeToTrashOrDelete(directoryToRemove)) {
ERROR_LOG(Log::System, "Failed to delete file");
return false;
}
@@ -133,7 +154,7 @@ bool GameInfo::Delete() {
{
const Path &fileToRemove = filePath_;
INFO_LOG(Log::System, "Deleting file %s", fileToRemove.c_str());
File::MoveFileToTrashOrDelete(fileToRemove);
MoveFileToTrashOrDelete(fileToRemove);
g_recentFiles.Remove(filePath_.ToString());
return true;
}
@@ -142,10 +163,10 @@ bool GameInfo::Delete() {
{
const Path &ppstPath = filePath_;
INFO_LOG(Log::System, "Deleting file %s", ppstPath.c_str());
File::MoveFileToTrashOrDelete(ppstPath);
MoveFileToTrashOrDelete(ppstPath);
const Path screenshotPath = filePath_.WithReplacedExtension(".ppst", ".jpg");
if (File::Exists(screenshotPath)) {
File::MoveFileToTrashOrDelete(screenshotPath);
MoveFileToTrashOrDelete(screenshotPath);
}
return true;
}
@@ -331,7 +352,7 @@ bool GameInfo::DeleteAllSaveData() {
std::vector<Path> saveDataDir = GetSaveDataDirectories();
for (size_t j = 0; j < saveDataDir.size(); j++) {
INFO_LOG(Log::System, "Deleting savedata from %s", saveDataDir[j].c_str());
if (!File::MoveDirectoryTreeToTrashOrDelete(saveDataDir[j])) {
if (!MoveDirectoryTreeToTrashOrDelete(saveDataDir[j])) {
ERROR_LOG(Log::System, "Failed to delete savedata %s", saveDataDir[j].c_str());
}
}
+38
View File
@@ -11,6 +11,7 @@
#include "ShellUtil.h"
#include <shobjidl.h> // For IFileDialog and related interfaces
#include <shellapi.h>
#include <shlobj.h>
#include <commdlg.h>
#include <cderr.h>
@@ -18,6 +19,43 @@
namespace W32Util {
bool MoveToTrash(const Path &path) {
IFileOperation *pFileOp = nullptr;
HRESULT hr = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pFileOp));
if (FAILED(hr)) {
return false;
}
// Set operation flags
hr = pFileOp->SetOperationFlags(FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT);
if (FAILED(hr)) {
pFileOp->Release();
return false;
}
// Create a shell item from the file path
IShellItem* pItem = nullptr;
hr = SHCreateItemFromParsingName(path.ToWString().c_str(), nullptr, IID_PPV_ARGS(&pItem));
if (SUCCEEDED(hr)) {
// Schedule the delete (move to recycle bin)
hr = pFileOp->DeleteItem(pItem, nullptr);
if (SUCCEEDED(hr)) {
hr = pFileOp->PerformOperations(); // Execute
}
pItem->Release();
}
pFileOp->Release();
if (SUCCEEDED(hr)) {
INFO_LOG(Log::IO, "Moved file to trash successfully: %s", path.c_str());
return true;
} else {
WARN_LOG(Log::IO, "Failed to move file to trash: %s", path.c_str());
return false;
}
}
std::string BrowseForFolder2(HWND parent, std::string_view title, std::string_view initialPath) {
const std::wstring wtitle = ConvertUTF8ToWString(title);
const std::wstring initialDir = ConvertUTF8ToWString(initialPath);
+1 -1
View File
@@ -10,13 +10,13 @@ class Path;
namespace W32Util {
std::string BrowseForFolder2(HWND parent, std::string_view title, std::string_view initialPath);
bool BrowseForFileName(bool _bLoad, HWND _hParent, const wchar_t*_pTitle,
const wchar_t *_pInitialFolder, const wchar_t *_pFilter, const wchar_t*_pExtension,
std::string& _strFileName);
std::vector<std::string> BrowseForFileNameMultiSelect(bool _bLoad, HWND _hParent, const wchar_t*_pTitle,
const wchar_t*_pInitialFolder, const wchar_t*_pFilter, const wchar_t*_pExtension);
bool MoveToTrash(const Path &path);
std::string UserDocumentsPath();
bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameTitle, const Path &icoFile);
+5
View File
@@ -720,6 +720,11 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
MainWindow::RunCallbackInWndProc(func, userdata);
return true;
}
case SystemRequestType::MOVE_TO_TRASH:
{
W32Util::MoveToTrash(Path(param1));
return true;
}
default:
return false;
}