Skip to content

Commit 6cd0c6f

Browse files
authored
Merge pull request #19443 from hrydgard/more-zip-file-install-fixes
More zip file install fixes
2 parents d3fca5b + 58da0fa commit 6cd0c6f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+161
-39
lines changed

Common/File/AndroidContentURI.h

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ class AndroidContentURI {
6767
return root.empty() ? file : root;
6868
}
6969

70+
const std::string &Provider() const {
71+
return provider;
72+
}
73+
7074
bool IsTreeURI() const {
7175
return !root.empty();
7276
}

Common/File/FileUtil.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -1276,4 +1276,21 @@ void ChangeMTime(const Path &path, time_t mtime) {
12761276
#endif
12771277
}
12781278

1279+
bool IsProbablyInDownloadsFolder(const Path &filename) {
1280+
INFO_LOG(Log::Common, "IsProbablyInDownloadsFolder: Looking at %s (%s)...", filename.c_str(), filename.ToVisualString().c_str());
1281+
switch (filename.Type()) {
1282+
case PathType::CONTENT_URI:
1283+
{
1284+
AndroidContentURI uri(filename.ToString());
1285+
INFO_LOG(Log::Common, "Content URI provider: %s", uri.Provider().c_str());
1286+
if (containsNoCase(uri.Provider(), "download")) {
1287+
// like com.android.providers.downloads.documents
1288+
return true;
1289+
}
1290+
break;
1291+
}
1292+
}
1293+
return filename.FilePathContainsNoCase("download");
1294+
}
1295+
12791296
} // namespace File

Common/File/FileUtil.h

+4
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ bool CreateEmptyFile(const Path &filename);
122122
// TODO: Belongs in System or something.
123123
bool OpenFileInEditor(const Path &fileName);
124124

125+
// Uses some heuristics to determine if this is a folder that we would want to
126+
// write to.
127+
bool IsProbablyInDownloadsFolder(const Path &folder);
128+
125129
// TODO: Belongs in System or something.
126130
const Path &GetExeDirectory();
127131

Common/File/Path.h

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class Path {
5050
PathType Type() const {
5151
return type_;
5252
}
53+
bool IsLocalType() const {
54+
return type_ == PathType::NATIVE || type_ == PathType::CONTENT_URI;
55+
}
5356

5457
bool Valid() const { return !path_.empty(); }
5558
bool IsRoot() const { return path_ == "/"; } // Special value - only path that can end in a slash.

Common/StringUtils.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ long parseLong(std::string s) {
9393
return value;
9494
}
9595

96+
bool containsNoCase(std::string_view haystack, std::string_view needle) {
97+
auto pred = [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); };
98+
auto found = std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), pred);
99+
return found != haystack.end();
100+
}
101+
96102
bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args)
97103
{
98104
int writtenCount = vsnprintf(out, outsize, format, args);

Common/StringUtils.h

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ inline bool equalsNoCase(std::string_view str, std::string_view key) {
6969
return strncasecmp(str.data(), key.data(), key.size()) == 0;
7070
}
7171

72+
bool containsNoCase(std::string_view haystack, std::string_view needle);
73+
7274
void DataToHexString(const uint8_t *data, size_t size, std::string *output);
7375
void DataToHexString(int indent, uint32_t startAddr, const uint8_t* data, size_t size, std::string* output);
7476

Core/Util/GameManager.cpp

+40-37
Original file line numberDiff line numberDiff line change
@@ -389,23 +389,41 @@ void GameManager::InstallZipContents(ZipFileTask task) {
389389
// Examine the URL to guess out what we're installing.
390390
// TODO: Bad idea due to Android content api where we don't always get the filename.
391391
if (urlExtension == ".cso" || urlExtension == ".iso" || urlExtension == ".chd") {
392-
// It's a raw ISO or CSO file. We just copy it to the destination.
393-
std::string shortFilename = task.url.GetFilename();
394-
bool success = InstallRawISO(task.fileName, shortFilename, task.deleteAfter);
392+
// It's a raw ISO or CSO file. We just copy it to the destination, which is the
393+
// currently selected directory in the game browser. Note: This might not be a good option!
394+
Path destPath = Path(g_Config.currentDirectory) / task.url.GetFilename();
395+
if (!File::Exists(destPath)) {
396+
// Fall back to the root of the memstick.
397+
destPath = g_Config.memStickDirectory;
398+
}
399+
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
400+
401+
// TODO: To save disk space, we should probably attempt a move first, if deleteAfter is true.
402+
// TODO: Update the progress bar continuously.
403+
bool success = File::Copy(task.fileName, destPath);
404+
395405
if (!success) {
396406
ERROR_LOG(Log::HLE, "Raw ISO install failed");
397407
// 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
398408
SetInstallError("Failed to install raw ISO");
399409
}
410+
if (task.deleteAfter) {
411+
File::Delete(task.fileName);
412+
}
413+
g_OSD.RemoveProgressBar("install", success, 0.5f);
414+
installProgress_ = 1.0f;
415+
InstallDone();
400416
return;
401417
}
402418

403419
int error = 0;
404420

405421
struct zip *z = ZipOpenPath(task.fileName);
406422
if (!z) {
407-
g_OSD.RemoveProgressBar("install", false, 0.5f);
423+
g_OSD.RemoveProgressBar("install", false, 1.5f);
408424
SetInstallError(sy->T("Unable to open zip file"));
425+
installProgress_ = 1.0f;
426+
InstallDone();
409427
return;
410428
}
411429

@@ -424,15 +442,17 @@ void GameManager::InstallZipContents(ZipFileTask task) {
424442
{
425443
Path pspGame = GetSysDirectory(DIRECTORY_GAME);
426444
INFO_LOG(Log::HLE, "Installing '%s' into '%s'", task.fileName.c_str(), pspGame.c_str());
427-
// InstallZipContents contains code to close (and delete) z.
445+
// InstallZipContents contains code to close z.
428446
success = ExtractZipContents(z, pspGame, zipInfo, false);
429447
break;
430448
}
431449
case ZipFileContents::ISO_FILE:
432-
INFO_LOG(Log::HLE, "Installing '%s' into its containing directory", task.fileName.c_str());
450+
{
451+
INFO_LOG(Log::HLE, "Installing '%s' into '%s'", task.fileName.c_str(), task.destination.c_str());
433452
// InstallZippedISO contains code to close z.
434-
success = InstallZippedISO(z, zipInfo.isoFileIndex, task.fileName, task.deleteAfter);
453+
success = InstallZippedISO(z, zipInfo.isoFileIndex, task.destination);
435454
break;
455+
}
436456
case ZipFileContents::TEXTURE_PACK:
437457
{
438458
// InstallMemstickGame contains code to close z, and works for textures too.
@@ -468,10 +488,16 @@ void GameManager::InstallZipContents(ZipFileTask task) {
468488
break;
469489
}
470490

491+
// Common functionality.
471492
if (task.deleteAfter && success) {
472493
File::Delete(task.fileName);
473494
}
474495
g_OSD.RemoveProgressBar("install", success, 0.5f);
496+
installProgress_ = 1.0f;
497+
InstallDone();
498+
if (success) {
499+
ResetInstallError();
500+
}
475501
}
476502

477503
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
765791
INFO_LOG(Log::HLE, "Unzipped %d files (%d bytes / %d).", info.numFiles, (int)bytesCopied, (int)allBytes);
766792
zip_close(z);
767793
z = nullptr;
768-
installProgress_ = 1.0f;
769-
InstallDone();
770-
ResetInstallError();
771-
g_OSD.RemoveProgressBar("install", true, 0.5f);
772794
return true;
773795

774796
bail:
@@ -782,7 +804,6 @@ bool GameManager::ExtractZipContents(struct zip *z, const Path &dest, const ZipF
782804
File::DeleteDir(iter);
783805
}
784806
SetInstallError(sy->T("Storage full"));
785-
g_OSD.RemoveProgressBar("install", false, 0.5f);
786807
return false;
787808
}
788809

@@ -842,7 +863,7 @@ bool GameManager::InstallMemstickZip(struct zip *z, const Path &zipfile, const P
842863
return true;
843864
}
844865

845-
bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &zipfile, bool deleteAfter) {
866+
bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &destDir) {
846867
// Let's place the output file in the currently selected Games directory.
847868
std::string fn = zip_get_name(z, isoFileIndex, 0);
848869
size_t nameOffset = fn.rfind('/');
@@ -866,7 +887,12 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
866887
name = name.substr(2);
867888
}
868889

869-
Path outputISOFilename = Path(g_Config.currentDirectory) / name;
890+
Path outputISOFilename = destDir;
891+
if (outputISOFilename.empty()) {
892+
outputISOFilename = Path(g_Config.currentDirectory);
893+
}
894+
outputISOFilename = outputISOFilename / name;
895+
870896
size_t bytesCopied = 0;
871897
bool success = false;
872898
auto di = GetI18NCategory(I18NCat::DIALOG);
@@ -876,10 +902,6 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
876902
success = true;
877903
}
878904
zip_close(z);
879-
if (success && deleteAfter) {
880-
File::Delete(zipfile);
881-
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
882-
}
883905
g_OSD.RemoveProgressBar("install", success, 0.5f);
884906

885907
z = 0;
@@ -910,25 +932,6 @@ bool GameManager::UninstallGameOnThread(const std::string &name) {
910932
return true;
911933
}
912934

913-
bool GameManager::InstallRawISO(const Path &file, const std::string &originalName, bool deleteAfter) {
914-
Path destPath = Path(g_Config.currentDirectory) / originalName;
915-
auto di = GetI18NCategory(I18NCat::DIALOG);
916-
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
917-
// TODO: To save disk space, we should probably attempt a move first.
918-
if (File::Copy(file, destPath)) {
919-
if (deleteAfter) {
920-
File::Delete(file);
921-
}
922-
g_OSD.RemoveProgressBar("install", true, 0.5f);
923-
} else {
924-
g_OSD.RemoveProgressBar("install", false, 0.5f);
925-
}
926-
installProgress_ = 1.0f;
927-
InstallDone();
928-
ResetInstallError();
929-
return true;
930-
}
931-
932935
void GameManager::ResetInstallError() {
933936
if (!InstallInProgress()) {
934937
installError_.clear();

Core/Util/GameManager.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ struct ZipFileTask {
6464
std::optional<ZipFileInfo> zipFileInfo;
6565
Path url; // Same as filename if installing from disk. Probably not really useful.
6666
Path fileName;
67+
Path destination; // If set, will override the default destination.
6768
bool deleteAfter;
6869
};
6970

@@ -117,8 +118,7 @@ class GameManager {
117118

118119
bool ExtractZipContents(struct zip *z, const Path &dest, const ZipFileInfo &info, bool allowRoot);
119120
bool InstallMemstickZip(struct zip *z, const Path &zipFile, const Path &dest, const ZipFileInfo &info);
120-
bool InstallZippedISO(struct zip *z, int isoFileIndex, const Path &zipfile, bool deleteAfter);
121-
bool InstallRawISO(const Path &zipFile, const std::string &originalName, bool deleteAfter);
121+
bool InstallZippedISO(struct zip *z, int isoFileIndex, const Path &destDir);
122122
void UninstallGame(const std::string &name);
123123

124124
void InstallDone();

UI/InstallZipScreen.cpp

+35
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
#include "Common/UI/ViewGroup.h"
2121

2222
#include "Common/StringUtils.h"
23+
#include "Common/File/FileUtil.h"
2324
#include "Common/Data/Text/I18n.h"
2425
#include "Common/Data/Text/Parsers.h"
26+
#include "Core/Config.h"
2527
#include "Core/System.h"
2628
#include "Core/Util/GameManager.h"
2729
#include "Core/Loaders.h"
@@ -65,6 +67,10 @@ void InstallZipScreen::CreateViews() {
6567
doneView_ = nullptr;
6668
installChoice_ = nullptr;
6769
existingSaveView_ = nullptr;
70+
destFolders_.clear();
71+
72+
std::vector<Path> destOptions;
73+
6874
if (z) {
6975
DetectZipFileContents(z, &zipFileInfo_); // Even if this fails, it sets zipInfo->contents.
7076
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE || zipFileInfo_.contents == ZipFileContents::PSP_GAME_DIR) {
@@ -78,6 +84,21 @@ void InstallZipScreen::CreateViews() {
7884

7985
doneView_ = leftColumn->Add(new TextView(""));
8086

87+
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE) {
88+
const bool isInDownloads = File::IsProbablyInDownloadsFolder(zipPath_);
89+
Path parent;
90+
if (!isInDownloads && zipPath_.CanNavigateUp()) {
91+
parent = zipPath_.NavigateUp();
92+
destFolders_.push_back(parent);
93+
}
94+
if (g_Config.currentDirectory.IsLocalType() && File::Exists(g_Config.currentDirectory) && g_Config.currentDirectory != parent) {
95+
destFolders_.push_back(g_Config.currentDirectory);
96+
}
97+
destFolders_.push_back(g_Config.memStickDirectory);
98+
} else {
99+
destFolders_.push_back(GetSysDirectory(DIRECTORY_GAME));
100+
}
101+
81102
installChoice_ = rightColumnItems->Add(new Choice(iz->T("Install")));
82103
installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall);
83104
returnToHomebrew_ = true;
@@ -102,6 +123,8 @@ void InstallZipScreen::CreateViews() {
102123
Path savedataDir = GetSysDirectory(DIRECTORY_SAVEDATA);
103124
bool overwrite = !CanExtractWithoutOverwrite(z, savedataDir, 50);
104125

126+
destFolders_.push_back(savedataDir);
127+
105128
leftColumn->Add(new NoticeView(NoticeLevel::WARN, di->T("Confirm Overwrite"), ""));
106129

107130
int columnWidth = 300;
@@ -143,6 +166,15 @@ void InstallZipScreen::CreateViews() {
143166
leftColumn->Add(new TextView(er->T("Error reading file"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE)));
144167
}
145168

169+
if (destFolders_.size() > 1) {
170+
leftColumn->Add(new TextView(iz->T("Install into folder")));
171+
for (int i = 0; i < (int)destFolders_.size(); i++) {
172+
leftColumn->Add(new RadioButton(&destFolderChoice_, i, destFolders_[i].ToVisualString()));
173+
}
174+
} else if (destFolders_.size() == 1 && zipFileInfo_.contents != ZipFileContents::SAVE_DATA) {
175+
leftColumn->Add(new TextView(StringFromFormat("%s %s", iz->T_cstr("Install into folder:"), destFolders_[0].ToVisualString().c_str())));
176+
}
177+
146178
// OK so that EmuScreen will handle it right.
147179
backChoice_ = rightColumnItems->Add(new Choice(di->T("Back")));
148180
backChoice_->OnClick.Handle<UIScreen>(this, &UIScreen::OnOK);
@@ -166,6 +198,9 @@ UI::EventReturn InstallZipScreen::OnInstall(UI::EventParams &params) {
166198
task.fileName = zipPath_;
167199
task.deleteAfter = deleteZipFile_;
168200
task.zipFileInfo = zipFileInfo_;
201+
if (!destFolders_.empty() && destFolderChoice_ < destFolders_.size()) {
202+
task.destination = destFolders_[destFolderChoice_];
203+
}
169204
if (g_GameManager.InstallZipOnThread(task)) {
170205
installStarted_ = true;
171206
if (installChoice_) {

UI/InstallZipScreen.h

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
#include <functional>
2121

22+
#include "Common/File/Path.h"
23+
2224
#include "Common/UI/View.h"
2325
#include "Common/UI/UIScreen.h"
2426

@@ -46,6 +48,8 @@ class InstallZipScreen : public UIDialogScreenWithBackground {
4648
SavedataView *existingSaveView_ = nullptr;
4749
Path savedataToOverwrite_;
4850
Path zipPath_;
51+
std::vector<Path> destFolders_;
52+
int destFolderChoice_ = 0;
4953
ZipFileInfo zipFileInfo_{};
5054
bool returnToHomebrew_ = true;
5155
bool installStarted_ = false;

assets/lang/ar_AE.ini

+1
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,7 @@ Delete ZIP file = ‎مسح الملف المضغوط
714714
Existing data = Existing data
715715
Install = ‎تثبيت
716716
Install game from ZIP file? = ‎تثبيت اللعبة من الملف المضغوط ?
717+
Install in folder = Install in folder
717718
Install textures from ZIP file? = Install textures from ZIP file?
718719
Installation failed = فشل في التثبيت
719720
Installed! = ‎مثبت!

assets/lang/az_AZ.ini

+1
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ Delete ZIP file = Delete ZIP file
706706
Existing data = Existing data
707707
Install = Install
708708
Install game from ZIP file? = Install game from ZIP file?
709+
Install in folder = Install in folder
709710
Install textures from ZIP file? = Install textures from ZIP file?
710711
Installation failed = Installation failed
711712
Installed! = Installed!

assets/lang/bg_BG.ini

+1
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ Delete ZIP file = Изтрий ZIP архива
706706
Existing data = Existing data
707707
Install = Инсталирай
708708
Install game from ZIP file? = Инсталирай игра от ZIP архив?
709+
Install in folder = Install in folder
709710
Install textures from ZIP file? = Install textures from ZIP file?
710711
Installation failed = Installation failed
711712
Installed! = Инсталирано!

assets/lang/ca_ES.ini

+1
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ Delete ZIP file = Delete ZIP file
706706
Existing data = Existing data
707707
Install = Install
708708
Install game from ZIP file? = Install game from ZIP file?
709+
Install in folder = Install in folder
709710
Install textures from ZIP file? = Install textures from ZIP file?
710711
Installation failed = Installation failed
711712
Installed! = Installed!

assets/lang/cz_CZ.ini

+1
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ Delete ZIP file = Smazat soubor ZIP
706706
Existing data = Existing data
707707
Install = Nainstalovat
708708
Install game from ZIP file? = Instalovat hru ze souboru ZIP?
709+
Install in folder = Install in folder
709710
Install textures from ZIP file? = Install textures from ZIP file?
710711
Installation failed = Installation failed
711712
Installed! = Instalováno!

0 commit comments

Comments
 (0)