More InstallZip refactoring, add options for install directory where applicable

This commit is contained in:
Henrik Rydgård 2024-09-10 13:15:06 +02:00
parent d3fca5b8eb
commit 10d3b253a4
10 changed files with 117 additions and 39 deletions

View File

@ -67,6 +67,10 @@ public:
return root.empty() ? file : root; return root.empty() ? file : root;
} }
const std::string &Provider() const {
return provider;
}
bool IsTreeURI() const { bool IsTreeURI() const {
return !root.empty(); return !root.empty();
} }

View File

@ -1276,4 +1276,21 @@ void ChangeMTime(const Path &path, time_t mtime) {
#endif #endif
} }
bool IsProbablyInDownloadsFolder(const Path &filename) {
INFO_LOG(Log::Common, "IsProbablyInDownloadsFolder: Looking at %s (%s)...", filename.c_str(), filename.ToVisualString().c_str());
switch (filename.Type()) {
case PathType::CONTENT_URI:
{
AndroidContentURI uri(filename.ToString());
INFO_LOG(Log::Common, "Content URI provider: %s", uri.Provider().c_str());
if (containsNoCase(uri.Provider(), "download")) {
// like com.android.providers.downloads.documents
return true;
}
break;
}
}
return filename.FilePathContainsNoCase("download");
}
} // namespace File } // namespace File

View File

@ -122,6 +122,10 @@ bool CreateEmptyFile(const Path &filename);
// TODO: Belongs in System or something. // TODO: Belongs in System or something.
bool OpenFileInEditor(const Path &fileName); bool OpenFileInEditor(const Path &fileName);
// Uses some heuristics to determine if this is a folder that we would want to
// write to.
bool IsProbablyInDownloadsFolder(const Path &folder);
// TODO: Belongs in System or something. // TODO: Belongs in System or something.
const Path &GetExeDirectory(); const Path &GetExeDirectory();

View File

@ -50,6 +50,9 @@ public:
PathType Type() const { PathType Type() const {
return type_; return type_;
} }
bool IsLocalType() const {
return type_ == PathType::NATIVE || type_ == PathType::CONTENT_URI;
}
bool Valid() const { return !path_.empty(); } bool Valid() const { return !path_.empty(); }
bool IsRoot() const { return path_ == "/"; } // Special value - only path that can end in a slash. bool IsRoot() const { return path_ == "/"; } // Special value - only path that can end in a slash.

View File

@ -93,6 +93,12 @@ long parseLong(std::string s) {
return value; return value;
} }
bool containsNoCase(std::string_view haystack, std::string_view needle) {
auto pred = [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); };
auto found = std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), pred);
return found != haystack.end();
}
bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args) bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args)
{ {
int writtenCount = vsnprintf(out, outsize, format, args); int writtenCount = vsnprintf(out, outsize, format, args);

View File

@ -69,6 +69,8 @@ inline bool equalsNoCase(std::string_view str, std::string_view key) {
return strncasecmp(str.data(), key.data(), key.size()) == 0; return strncasecmp(str.data(), key.data(), key.size()) == 0;
} }
bool containsNoCase(std::string_view haystack, std::string_view needle);
void DataToHexString(const uint8_t *data, size_t size, std::string *output); void DataToHexString(const uint8_t *data, size_t size, std::string *output);
void DataToHexString(int indent, uint32_t startAddr, const uint8_t* data, size_t size, std::string* output); void DataToHexString(int indent, uint32_t startAddr, const uint8_t* data, size_t size, std::string* output);

View File

@ -389,14 +389,30 @@ void GameManager::InstallZipContents(ZipFileTask task) {
// Examine the URL to guess out what we're installing. // Examine the URL to guess out what we're installing.
// TODO: Bad idea due to Android content api where we don't always get the filename. // TODO: Bad idea due to Android content api where we don't always get the filename.
if (urlExtension == ".cso" || urlExtension == ".iso" || urlExtension == ".chd") { if (urlExtension == ".cso" || urlExtension == ".iso" || urlExtension == ".chd") {
// It's a raw ISO or CSO file. We just copy it to the destination. // It's a raw ISO or CSO file. We just copy it to the destination, which is the
std::string shortFilename = task.url.GetFilename(); // currently selected directory in the game browser. Note: This might not be a good option!
bool success = InstallRawISO(task.fileName, shortFilename, task.deleteAfter); Path destPath = Path(g_Config.currentDirectory) / task.url.GetFilename();
if (!File::Exists(destPath)) {
// Fall back to the root of the memstick.
destPath = g_Config.memStickDirectory;
}
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
// TODO: To save disk space, we should probably attempt a move first, if deleteAfter is true.
// TODO: Update the progress bar continuously.
bool success = File::Copy(task.fileName, destPath);
if (!success) { if (!success) {
ERROR_LOG(Log::HLE, "Raw ISO install failed"); ERROR_LOG(Log::HLE, "Raw ISO install failed");
// This shouldn't normally happen at all (only when putting ISOs in a store, which is not a normal use case), so skipping the translation string // This shouldn't normally happen at all (only when putting ISOs in a store, which is not a normal use case), so skipping the translation string
SetInstallError("Failed to install raw ISO"); SetInstallError("Failed to install raw ISO");
} }
if (task.deleteAfter) {
File::Delete(task.fileName);
}
g_OSD.RemoveProgressBar("install", success, 0.5f);
installProgress_ = 1.0f;
InstallDone();
return; return;
} }
@ -404,8 +420,10 @@ void GameManager::InstallZipContents(ZipFileTask task) {
struct zip *z = ZipOpenPath(task.fileName); struct zip *z = ZipOpenPath(task.fileName);
if (!z) { if (!z) {
g_OSD.RemoveProgressBar("install", false, 0.5f); g_OSD.RemoveProgressBar("install", false, 1.5f);
SetInstallError(sy->T("Unable to open zip file")); SetInstallError(sy->T("Unable to open zip file"));
installProgress_ = 1.0f;
InstallDone();
return; return;
} }
@ -424,15 +442,17 @@ void GameManager::InstallZipContents(ZipFileTask task) {
{ {
Path pspGame = GetSysDirectory(DIRECTORY_GAME); Path pspGame = GetSysDirectory(DIRECTORY_GAME);
INFO_LOG(Log::HLE, "Installing '%s' into '%s'", task.fileName.c_str(), pspGame.c_str()); INFO_LOG(Log::HLE, "Installing '%s' into '%s'", task.fileName.c_str(), pspGame.c_str());
// InstallZipContents contains code to close (and delete) z. // InstallZipContents contains code to close z.
success = ExtractZipContents(z, pspGame, zipInfo, false); success = ExtractZipContents(z, pspGame, zipInfo, false);
break; break;
} }
case ZipFileContents::ISO_FILE: case ZipFileContents::ISO_FILE:
INFO_LOG(Log::HLE, "Installing '%s' into its containing directory", task.fileName.c_str()); {
INFO_LOG(Log::HLE, "Installing '%s' into '%s'", task.fileName.c_str(), task.destination.c_str());
// InstallZippedISO contains code to close z. // InstallZippedISO contains code to close z.
success = InstallZippedISO(z, zipInfo.isoFileIndex, task.fileName, task.deleteAfter); success = InstallZippedISO(z, zipInfo.isoFileIndex, task.destination);
break; break;
}
case ZipFileContents::TEXTURE_PACK: case ZipFileContents::TEXTURE_PACK:
{ {
// InstallMemstickGame contains code to close z, and works for textures too. // InstallMemstickGame contains code to close z, and works for textures too.
@ -468,10 +488,16 @@ void GameManager::InstallZipContents(ZipFileTask task) {
break; break;
} }
// Common functionality.
if (task.deleteAfter && success) { if (task.deleteAfter && success) {
File::Delete(task.fileName); File::Delete(task.fileName);
} }
g_OSD.RemoveProgressBar("install", success, 0.5f); g_OSD.RemoveProgressBar("install", success, 0.5f);
installProgress_ = 1.0f;
InstallDone();
if (success) {
ResetInstallError();
}
} }
bool GameManager::DetectTexturePackDest(struct zip *z, int iniIndex, Path &dest) { bool GameManager::DetectTexturePackDest(struct zip *z, int iniIndex, Path &dest) {
@ -765,10 +791,6 @@ bool GameManager::ExtractZipContents(struct zip *z, const Path &dest, const ZipF
INFO_LOG(Log::HLE, "Unzipped %d files (%d bytes / %d).", info.numFiles, (int)bytesCopied, (int)allBytes); INFO_LOG(Log::HLE, "Unzipped %d files (%d bytes / %d).", info.numFiles, (int)bytesCopied, (int)allBytes);
zip_close(z); zip_close(z);
z = nullptr; z = nullptr;
installProgress_ = 1.0f;
InstallDone();
ResetInstallError();
g_OSD.RemoveProgressBar("install", true, 0.5f);
return true; return true;
bail: bail:
@ -782,7 +804,6 @@ bail:
File::DeleteDir(iter); File::DeleteDir(iter);
} }
SetInstallError(sy->T("Storage full")); SetInstallError(sy->T("Storage full"));
g_OSD.RemoveProgressBar("install", false, 0.5f);
return false; return false;
} }
@ -842,7 +863,7 @@ bool GameManager::InstallMemstickZip(struct zip *z, const Path &zipfile, const P
return true; return true;
} }
bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &zipfile, bool deleteAfter) { bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &destDir) {
// Let's place the output file in the currently selected Games directory. // Let's place the output file in the currently selected Games directory.
std::string fn = zip_get_name(z, isoFileIndex, 0); std::string fn = zip_get_name(z, isoFileIndex, 0);
size_t nameOffset = fn.rfind('/'); size_t nameOffset = fn.rfind('/');
@ -866,7 +887,12 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
name = name.substr(2); name = name.substr(2);
} }
Path outputISOFilename = Path(g_Config.currentDirectory) / name; Path outputISOFilename = destDir;
if (outputISOFilename.empty()) {
outputISOFilename = Path(g_Config.currentDirectory);
}
outputISOFilename = outputISOFilename / name;
size_t bytesCopied = 0; size_t bytesCopied = 0;
bool success = false; bool success = false;
auto di = GetI18NCategory(I18NCat::DIALOG); auto di = GetI18NCategory(I18NCat::DIALOG);
@ -876,10 +902,6 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
success = true; success = true;
} }
zip_close(z); zip_close(z);
if (success && deleteAfter) {
File::Delete(zipfile);
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
}
g_OSD.RemoveProgressBar("install", success, 0.5f); g_OSD.RemoveProgressBar("install", success, 0.5f);
z = 0; z = 0;
@ -910,25 +932,6 @@ bool GameManager::UninstallGameOnThread(const std::string &name) {
return true; return true;
} }
bool GameManager::InstallRawISO(const Path &file, const std::string &originalName, bool deleteAfter) {
Path destPath = Path(g_Config.currentDirectory) / originalName;
auto di = GetI18NCategory(I18NCat::DIALOG);
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
// TODO: To save disk space, we should probably attempt a move first.
if (File::Copy(file, destPath)) {
if (deleteAfter) {
File::Delete(file);
}
g_OSD.RemoveProgressBar("install", true, 0.5f);
} else {
g_OSD.RemoveProgressBar("install", false, 0.5f);
}
installProgress_ = 1.0f;
InstallDone();
ResetInstallError();
return true;
}
void GameManager::ResetInstallError() { void GameManager::ResetInstallError() {
if (!InstallInProgress()) { if (!InstallInProgress()) {
installError_.clear(); installError_.clear();

View File

@ -64,6 +64,7 @@ struct ZipFileTask {
std::optional<ZipFileInfo> zipFileInfo; std::optional<ZipFileInfo> zipFileInfo;
Path url; // Same as filename if installing from disk. Probably not really useful. Path url; // Same as filename if installing from disk. Probably not really useful.
Path fileName; Path fileName;
Path destination; // If set, will override the default destination.
bool deleteAfter; bool deleteAfter;
}; };
@ -117,8 +118,7 @@ private:
bool ExtractZipContents(struct zip *z, const Path &dest, const ZipFileInfo &info, bool allowRoot); bool ExtractZipContents(struct zip *z, const Path &dest, const ZipFileInfo &info, bool allowRoot);
bool InstallMemstickZip(struct zip *z, const Path &zipFile, const Path &dest, const ZipFileInfo &info); bool InstallMemstickZip(struct zip *z, const Path &zipFile, const Path &dest, const ZipFileInfo &info);
bool InstallZippedISO(struct zip *z, int isoFileIndex, const Path &zipfile, bool deleteAfter); bool InstallZippedISO(struct zip *z, int isoFileIndex, const Path &destDir);
bool InstallRawISO(const Path &zipFile, const std::string &originalName, bool deleteAfter);
void UninstallGame(const std::string &name); void UninstallGame(const std::string &name);
void InstallDone(); void InstallDone();

View File

@ -20,8 +20,10 @@
#include "Common/UI/ViewGroup.h" #include "Common/UI/ViewGroup.h"
#include "Common/StringUtils.h" #include "Common/StringUtils.h"
#include "Common/File/FileUtil.h"
#include "Common/Data/Text/I18n.h" #include "Common/Data/Text/I18n.h"
#include "Common/Data/Text/Parsers.h" #include "Common/Data/Text/Parsers.h"
#include "Core/Config.h"
#include "Core/System.h" #include "Core/System.h"
#include "Core/Util/GameManager.h" #include "Core/Util/GameManager.h"
#include "Core/Loaders.h" #include "Core/Loaders.h"
@ -65,6 +67,10 @@ void InstallZipScreen::CreateViews() {
doneView_ = nullptr; doneView_ = nullptr;
installChoice_ = nullptr; installChoice_ = nullptr;
existingSaveView_ = nullptr; existingSaveView_ = nullptr;
destFolders_.clear();
std::vector<Path> destOptions;
if (z) { if (z) {
DetectZipFileContents(z, &zipFileInfo_); // Even if this fails, it sets zipInfo->contents. DetectZipFileContents(z, &zipFileInfo_); // Even if this fails, it sets zipInfo->contents.
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE || zipFileInfo_.contents == ZipFileContents::PSP_GAME_DIR) { if (zipFileInfo_.contents == ZipFileContents::ISO_FILE || zipFileInfo_.contents == ZipFileContents::PSP_GAME_DIR) {
@ -78,6 +84,21 @@ void InstallZipScreen::CreateViews() {
doneView_ = leftColumn->Add(new TextView("")); doneView_ = leftColumn->Add(new TextView(""));
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE) {
const bool isInDownloads = File::IsProbablyInDownloadsFolder(zipPath_);
Path parent;
if (!isInDownloads && zipPath_.CanNavigateUp()) {
parent = zipPath_.NavigateUp();
destFolders_.push_back(parent);
}
if (g_Config.currentDirectory.IsLocalType() && File::Exists(g_Config.currentDirectory) && g_Config.currentDirectory != parent) {
destFolders_.push_back(g_Config.currentDirectory);
}
destFolders_.push_back(g_Config.memStickDirectory);
} else {
destFolders_.push_back(GetSysDirectory(DIRECTORY_GAME));
}
installChoice_ = rightColumnItems->Add(new Choice(iz->T("Install"))); installChoice_ = rightColumnItems->Add(new Choice(iz->T("Install")));
installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall); installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall);
returnToHomebrew_ = true; returnToHomebrew_ = true;
@ -102,6 +123,8 @@ void InstallZipScreen::CreateViews() {
Path savedataDir = GetSysDirectory(DIRECTORY_SAVEDATA); Path savedataDir = GetSysDirectory(DIRECTORY_SAVEDATA);
bool overwrite = !CanExtractWithoutOverwrite(z, savedataDir, 50); bool overwrite = !CanExtractWithoutOverwrite(z, savedataDir, 50);
destFolders_.push_back(savedataDir);
leftColumn->Add(new NoticeView(NoticeLevel::WARN, di->T("Confirm Overwrite"), "")); leftColumn->Add(new NoticeView(NoticeLevel::WARN, di->T("Confirm Overwrite"), ""));
int columnWidth = 300; int columnWidth = 300;
@ -143,6 +166,15 @@ void InstallZipScreen::CreateViews() {
leftColumn->Add(new TextView(er->T("Error reading file"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE))); leftColumn->Add(new TextView(er->T("Error reading file"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE)));
} }
if (destFolders_.size() > 1) {
leftColumn->Add(new TextView(iz->T("Install into folder")));
for (int i = 0; i < (int)destFolders_.size(); i++) {
leftColumn->Add(new RadioButton(&destFolderChoice_, i, destFolders_[i].ToVisualString()));
}
} else if (destFolders_.size() == 1 && zipFileInfo_.contents != ZipFileContents::SAVE_DATA) {
leftColumn->Add(new TextView(StringFromFormat("%s %s", iz->T_cstr("Install into folder:"), destFolders_[0].ToVisualString().c_str())));
}
// OK so that EmuScreen will handle it right. // OK so that EmuScreen will handle it right.
backChoice_ = rightColumnItems->Add(new Choice(di->T("Back"))); backChoice_ = rightColumnItems->Add(new Choice(di->T("Back")));
backChoice_->OnClick.Handle<UIScreen>(this, &UIScreen::OnOK); backChoice_->OnClick.Handle<UIScreen>(this, &UIScreen::OnOK);
@ -166,6 +198,9 @@ UI::EventReturn InstallZipScreen::OnInstall(UI::EventParams &params) {
task.fileName = zipPath_; task.fileName = zipPath_;
task.deleteAfter = deleteZipFile_; task.deleteAfter = deleteZipFile_;
task.zipFileInfo = zipFileInfo_; task.zipFileInfo = zipFileInfo_;
if (!destFolders_.empty() && destFolderChoice_ < destFolders_.size()) {
task.destination = destFolders_[destFolderChoice_];
}
if (g_GameManager.InstallZipOnThread(task)) { if (g_GameManager.InstallZipOnThread(task)) {
installStarted_ = true; installStarted_ = true;
if (installChoice_) { if (installChoice_) {

View File

@ -19,6 +19,8 @@
#include <functional> #include <functional>
#include "Common/File/Path.h"
#include "Common/UI/View.h" #include "Common/UI/View.h"
#include "Common/UI/UIScreen.h" #include "Common/UI/UIScreen.h"
@ -46,6 +48,8 @@ private:
SavedataView *existingSaveView_ = nullptr; SavedataView *existingSaveView_ = nullptr;
Path savedataToOverwrite_; Path savedataToOverwrite_;
Path zipPath_; Path zipPath_;
std::vector<Path> destFolders_;
int destFolderChoice_ = 0;
ZipFileInfo zipFileInfo_{}; ZipFileInfo zipFileInfo_{};
bool returnToHomebrew_ = true; bool returnToHomebrew_ = true;
bool installStarted_ = false; bool installStarted_ = false;