mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 09:39:33 +01:00
MemStickScreen: Readable folder names, say what the move is doing
- Folders show as "Internal storage / PSP" or "SD card (1234-ABCD) / PSP", and the app's own folder as "App private data", instead of content URIs and /storage/emulated/0/Android/data/... paths. Here and in the settings. - The move's progress names the pass it's in (moving, finding files, copying, checking, deleting the originals, putting things back) with the count, on a line of its own above the file, across the whole width. - A successful move shows "Done!" for three seconds before restarting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
86fcd95a4f
commit
1c0d8a2b96
5 files changed
+115
-24
No files matched your search
+31
-4
@@ -72,6 +72,18 @@ static std::string LocationKey(const Path &path) {
|
||||
return key;
|
||||
}
|
||||
|
||||
bool SplitStorageLocation(const Path &path, std::string *volume, std::string *pathInVolume) {
|
||||
const std::string key = LocationKey(path);
|
||||
const size_t colon = key.find(':');
|
||||
// A Windows drive letter ("C:/...") isn't a volume ID.
|
||||
if (key.empty() || key[0] == '/' || colon == std::string::npos || colon < 2) {
|
||||
return false;
|
||||
}
|
||||
*volume = key.substr(0, colon);
|
||||
*pathInVolume = key.substr(colon + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// True if child is strictly inside parent. Case-insensitive, like Android's shared storage.
|
||||
static bool KeyIsInside(std::string_view child, std::string_view parent) {
|
||||
if (parent.empty() || child.size() <= parent.size() || !startsWithNoCase(child, parent)) {
|
||||
@@ -297,7 +309,17 @@ std::string MoveProgressReporter::Format() {
|
||||
std::string str;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
if (max_ > 0) {
|
||||
if (!phase_.empty()) {
|
||||
str = phase_;
|
||||
if (max_ > 0) {
|
||||
str += StringFromFormat(" (%d/%d)", count_ + 1, max_);
|
||||
} else if (max_ < 0) {
|
||||
str += StringFromFormat(" (%d/?)", count_ + 1);
|
||||
}
|
||||
if (!progress_.empty()) {
|
||||
str += "\n";
|
||||
}
|
||||
} else if (max_ > 0) {
|
||||
str = StringFromFormat("(%d/%d) ", count_ + 1, max_);
|
||||
} else if (max_ < 0) {
|
||||
str = StringFromFormat("(%d/?) ", count_ + 1);
|
||||
@@ -386,6 +408,7 @@ MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressR
|
||||
// Anything that fails from here on puts back what was changed.
|
||||
auto fail = [&](const char *why, size_t failedFiles) {
|
||||
ERROR_LOG(Log::System, "Move failed: %s. Putting things back.", why);
|
||||
progressReporter.SetPhase(ms->T("Putting things back..."));
|
||||
MoveResult *result = new MoveResult{ false, why, failedFiles };
|
||||
result->partlyMoved = !UndoMove(moveSrc, moveDest, undo);
|
||||
return result;
|
||||
@@ -397,12 +420,13 @@ MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressR
|
||||
|
||||
// First, we try the cheapest and safest way to move: Can we move files directly within the same device?
|
||||
// We loop through the files/dirs in the source directory and just try to move them, it should work.
|
||||
progressReporter.SetPhase(ms->T("Moving..."));
|
||||
if (MoveChildrenFast(moveSrc, moveDest, progressReporter, undo)) {
|
||||
INFO_LOG(Log::System, "Quick-move succeeded after %0.1f ms", moveStart.ElapsedMs());
|
||||
if (beforeDeletingOriginals && !beforeDeletingOriginals()) {
|
||||
return fail("Couldn't switch to the new folder", 0);
|
||||
}
|
||||
progressReporter.SetProgress(StringFromFormat("%s (%0.3f)", T_cstr(I18NCat::DIALOG, "Done!"), moveStart.ElapsedSeconds()));
|
||||
progressReporter.SetPhase(T(I18NCat::DIALOG, "Done!"));
|
||||
return new MoveResult{ true, "" };
|
||||
} else {
|
||||
INFO_LOG(Log::System, "Quick move denied after %0.1f ms, falling back to slow move.", moveStart.ElapsedMs());
|
||||
@@ -416,14 +440,15 @@ MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressR
|
||||
std::vector<FileSuffix> fileSuffixesToMove;
|
||||
std::vector<std::string> directorySuffixesToCreate;
|
||||
|
||||
progressReporter.SetPhase(ms->T("Finding files..."));
|
||||
// NOTE: It's correct to pass moveSrc twice here, it's to keep the root in the recursion.
|
||||
if (!ListFileSuffixesRecursively(moveSrc, moveSrc, directorySuffixesToCreate, fileSuffixesToMove, progressReporter)) {
|
||||
progressReporter.SetProgress(ms->T("Failed to read old directory"));
|
||||
return fail("Failed to read old directory", 0);
|
||||
}
|
||||
|
||||
// Better not interrupt the app while this is happening!
|
||||
|
||||
progressReporter.SetPhase(ms->T("Copying..."));
|
||||
// Create all the necessary directories.
|
||||
for (size_t i = 0; i < directorySuffixesToCreate.size(); i++) {
|
||||
const auto &dirSuffix = directorySuffixesToCreate[i];
|
||||
@@ -463,9 +488,10 @@ MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressR
|
||||
|
||||
// After the whole move, verify that all the files arrived correctly.
|
||||
// If there's a single error, we do not delete the source data.
|
||||
progressReporter.SetPhase(ms->T("Checking..."));
|
||||
for (size_t i = 0; i < fileSuffixesToMove.size(); i++) {
|
||||
const auto &fileSuffix = fileSuffixesToMove[i];
|
||||
progressReporter.SetProgress(ms->T("Checking..."), i, fileSuffixesToMove.size());
|
||||
progressReporter.SetProgress(fileSuffix.suffix, i, fileSuffixesToMove.size());
|
||||
|
||||
Path to = moveDest / fileSuffix.suffix;
|
||||
|
||||
@@ -492,6 +518,7 @@ MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressR
|
||||
|
||||
// Delete the originals: the files, then the folders. On content URIs DeleteDir would take the files
|
||||
// along anyway, but on native paths it only removes empty folders.
|
||||
progressReporter.SetPhase(ms->T("Deleting from old folder..."));
|
||||
for (size_t i = 0; i < fileSuffixesToMove.size(); i++) {
|
||||
const Path from = moveSrc / fileSuffixesToMove[i].suffix;
|
||||
progressReporter.SetProgress(fileSuffixesToMove[i].suffix, i, fileSuffixesToMove.size());
|
||||
|
||||
+16
-2
@@ -16,14 +16,23 @@ public:
|
||||
count_ = (int)count;
|
||||
max_ = (int)maxVal;
|
||||
}
|
||||
// What the move is doing (like copying or checking), shown above the item it's on.
|
||||
void SetPhase(std::string_view phase) {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
phase_ = phase;
|
||||
progress_.clear();
|
||||
count_ = 0;
|
||||
max_ = 0;
|
||||
}
|
||||
|
||||
std::string Format();
|
||||
|
||||
private:
|
||||
std::string phase_;
|
||||
std::string progress_;
|
||||
std::mutex mutex_;
|
||||
int count_;
|
||||
int max_;
|
||||
int count_ = 0;
|
||||
int max_ = 0;
|
||||
};
|
||||
|
||||
struct MoveResult {
|
||||
@@ -53,6 +62,11 @@ enum class MemstickFolderCheck {
|
||||
MemstickFolderCheck CheckNewMemstickFolder(const Path &newFolder, const Path ¤tFolder);
|
||||
|
||||
bool FolderSeemsToBeUsed(const Path &newMemstickFolder);
|
||||
|
||||
// For showing a location to the user: splits Android shared storage, as a content URI or a native path, into
|
||||
// its volume ("primary" for internal storage, otherwise an ID like "1234-ABCD") and the path inside it.
|
||||
// False for anything else.
|
||||
bool SplitStorageLocation(const Path &path, std::string *volume, std::string *pathInVolume);
|
||||
// The checks SwitchMemstickFolderTo() starts with, to run before moving any data.
|
||||
bool CanSwitchMemstickFolderTo(const Path &newMemstickFolder);
|
||||
// Makes the next start use newMemstickFolder. SwitchMemstickFolderTo() does this and more.
|
||||
|
||||
+61
-17
@@ -41,6 +41,7 @@
|
||||
#include "Common/File/DiskFree.h"
|
||||
|
||||
#include "Common/Thread/ThreadManager.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/UI/Notice.h"
|
||||
|
||||
#include "Core/Config.h"
|
||||
@@ -60,6 +61,35 @@ static std::string FormatSpaceString(int64_t space) {
|
||||
}
|
||||
}
|
||||
|
||||
// The memory stick folder in the app's own storage.
|
||||
static Path PrivateMemstickFolder() {
|
||||
#if PPSSPP_PLATFORM(UWP)
|
||||
return g_Config.internalDataDirectory;
|
||||
#else
|
||||
return Path(g_extFilesDir);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string GetMemstickFolderDisplayName(const Path &folder) {
|
||||
auto ms = GetI18NCategory(I18NCat::MEMSTICK);
|
||||
if (!folder.empty() && folder == PrivateMemstickFolder()) {
|
||||
return std::string(ms->T("App private data"));
|
||||
}
|
||||
std::string volume;
|
||||
std::string inVolume;
|
||||
if (!SplitStorageLocation(folder, &volume, &inVolume)) {
|
||||
return folder.ToVisualString();
|
||||
}
|
||||
std::string name;
|
||||
if (volume == "primary") {
|
||||
name = ms->T("Internal storage");
|
||||
} else {
|
||||
// Could also be a USB drive, which is rare enough. The ID tells them apart.
|
||||
name = ApplySafeSubstitutions(ms->T("SD card (%1)"), volume);
|
||||
}
|
||||
return inVolume.empty() ? name : name + " / " + ReplaceAll(inVolume, "/", " / ");
|
||||
}
|
||||
|
||||
MemStickScreen::MemStickScreen(bool initialSetup)
|
||||
: UITwoPaneBaseDialogScreen(Path(),
|
||||
TwoPaneFlags::SettingsToTheRight | TwoPaneFlags::ContentsCanScroll | (initialSetup ? TwoPaneFlags::NoBackButton : TwoPaneFlags::Default)),
|
||||
@@ -231,7 +261,7 @@ void MemStickScreen::CreateContentViews(UI::ViewGroup *parent) {
|
||||
privateFolder->SetHintText(GetExplanationString(CHOICE_PRIVATE_DIRECTORY));
|
||||
|
||||
if ((choice_ == CHOICE_BROWSE_FOLDER || choice_ == CHOICE_SET_MANUAL) && !g_Config.memStickDirectory.empty()) {
|
||||
groups->Add(new TextView(StringFromFormat("%s: %s", ms->T_cstr("Current"), g_Config.memStickDirectory.ToVisualString().c_str()), ALIGN_LEFT, false));
|
||||
groups->Add(new TextView(StringFromFormat("%s: %s", ms->T_cstr("Current"), GetMemstickFolderDisplayName(g_Config.memStickDirectory).c_str()), ALIGN_LEFT | FLAG_WRAP_TEXT, false));
|
||||
}
|
||||
|
||||
INFO_LOG(Log::System, "MemStickScreen: initialSetup=%d", (int)initialSetup_);
|
||||
@@ -362,11 +392,7 @@ void MemStickScreen::SetFolderManually(UI::EventParams ¶ms) {
|
||||
}
|
||||
|
||||
void MemStickScreen::UseInternalStorage(UI::EventParams ¶ms) {
|
||||
#if PPSSPP_PLATFORM(UWP)
|
||||
Path pendingMemStickFolder = g_Config.internalDataDirectory;
|
||||
#else
|
||||
Path pendingMemStickFolder = Path(g_extFilesDir);
|
||||
#endif
|
||||
Path pendingMemStickFolder = PrivateMemstickFolder();
|
||||
|
||||
if (initialSetup_) {
|
||||
// There's not gonna be any files here in this case since it's a fresh install.
|
||||
@@ -502,22 +528,24 @@ void ConfirmMemstickMoveScreen::CreateViews() {
|
||||
auto sy = GetI18NCategory(I18NCat::SYSTEM);
|
||||
auto ms = GetI18NCategory(I18NCat::MEMSTICK);
|
||||
|
||||
root_ = new LinearLayout(ORIENT_HORIZONTAL);
|
||||
// The columns, and below them the progress of a move, across the whole width so file names fit.
|
||||
root_ = new LinearLayout(ORIENT_VERTICAL);
|
||||
ViewGroup *columns = root_->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
|
||||
|
||||
const Path &oldMemstickFolder = g_Config.memStickDirectory;
|
||||
|
||||
Spacer *spacerColumn = new Spacer(new LinearLayoutParams(20.0, FILL_PARENT, 0.0f));
|
||||
ViewGroup *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
|
||||
ViewGroup *rightColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
|
||||
root_->Add(spacerColumn);
|
||||
root_->Add(leftColumn);
|
||||
root_->Add(rightColumn);
|
||||
columns->Add(spacerColumn);
|
||||
columns->Add(leftColumn);
|
||||
columns->Add(rightColumn);
|
||||
|
||||
leftColumn->Add(new TextView(ms->T("Selected PSP Data Folder"), ALIGN_LEFT, false));
|
||||
if (!initialSetup_) {
|
||||
leftColumn->Add(new NoticeView(NoticeLevel::INFO, ms->T("PPSSPP will restart after the change"), ""));
|
||||
}
|
||||
leftColumn->Add(new TextView(newMemstickFolder_.ToVisualString(), ALIGN_LEFT, false));
|
||||
leftColumn->Add(new TextView(GetMemstickFolderDisplayName(newMemstickFolder_), ALIGN_LEFT | FLAG_WRAP_TEXT, false));
|
||||
|
||||
// TODO: Add spinner
|
||||
newFreeSpaceView_ = leftColumn->Add(new TextView(ApplySafeSubstitutions("%1: ...", ms->T("Free space")), ALIGN_LEFT, false));
|
||||
@@ -555,11 +583,20 @@ void ConfirmMemstickMoveScreen::CreateViews() {
|
||||
|
||||
if (!oldMemstickFolder.empty()) {
|
||||
rightColumn->Add(new TextView(std::string(ms->T("Current")) + ":", ALIGN_LEFT, false));
|
||||
rightColumn->Add(new TextView(oldMemstickFolder.ToVisualString(), ALIGN_LEFT, false));
|
||||
rightColumn->Add(new TextView(GetMemstickFolderDisplayName(oldMemstickFolder), ALIGN_LEFT | FLAG_WRAP_TEXT, false));
|
||||
}
|
||||
|
||||
if (restartTime_ > 0.0) {
|
||||
auto di = GetI18NCategory(I18NCat::DIALOG);
|
||||
leftColumn->Add(new NoticeView(NoticeLevel::SUCCESS, di->T("Done!"), ""));
|
||||
progressView_ = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (moveDataTask_) {
|
||||
progressView_ = leftColumn->Add(new TextView(progressReporter_.Format()));
|
||||
// What it's doing and the count on the first line, the file it's on below.
|
||||
progressView_ = root_->Add(new TextView(progressReporter_.Format(), ALIGN_LEFT | FLAG_WRAP_TEXT, false,
|
||||
new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(20, 10))));
|
||||
} else {
|
||||
progressView_ = nullptr;
|
||||
}
|
||||
@@ -589,7 +626,7 @@ void ConfirmMemstickMoveScreen::OnMoveDataClick(UI::EventParams ¶ms) {
|
||||
bool ConfirmMemstickMoveScreen::key(const KeyInput &key) {
|
||||
// The Back choice is hidden during a move, but the back key would still close the screen, blocking the
|
||||
// UI thread in the destructor until the move is done, and skipping FinishFolderMove().
|
||||
if (moveDataTask_ && UI::IsEscapeKey(key)) {
|
||||
if ((moveDataTask_ || restartTime_ > 0.0) && UI::IsEscapeKey(key)) {
|
||||
return true;
|
||||
}
|
||||
return UIBaseDialogScreen::key(key);
|
||||
@@ -608,6 +645,12 @@ void ConfirmMemstickMoveScreen::update() {
|
||||
UIBaseDialogScreen::update();
|
||||
auto ms = GetI18NCategory(I18NCat::MEMSTICK);
|
||||
|
||||
if (restartTime_ > 0.0 && time_now_d() >= restartTime_) {
|
||||
restartTime_ = 0.0;
|
||||
System_RestartApp("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (moveDataTask_) {
|
||||
if (progressView_) {
|
||||
progressView_->SetText(progressReporter_.Format());
|
||||
@@ -727,9 +770,10 @@ void ConfirmMemstickMoveScreen::FinishFolderMove() {
|
||||
PostLoadConfig();
|
||||
|
||||
if (!initialSetup_) {
|
||||
// We restart the app here, to get the new settings.
|
||||
INFO_LOG(Log::System, "Not initial setup. Restarting!");
|
||||
System_RestartApp("");
|
||||
// We restart the app to get the new settings, once the user has seen that it worked.
|
||||
INFO_LOG(Log::System, "Not initial setup. Restarting in a moment.");
|
||||
restartTime_ = time_now_d() + 3.0;
|
||||
RecreateViews();
|
||||
} else {
|
||||
// This is initial setup, we now switch to the main screen, if we were successful
|
||||
// (which we better have been...)
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
|
||||
class NoticeView;
|
||||
|
||||
// A memory stick folder as shown to the user: "Internal storage / PSP" rather than a content URI or a
|
||||
// /storage/emulated/0 path, and the app's own folder by name.
|
||||
std::string GetMemstickFolderDisplayName(const Path &folder);
|
||||
|
||||
// MemStickScreen - let's you configure your memory stick directory.
|
||||
// Currently only useful for Android.
|
||||
class MemStickScreen : public UITwoPaneBaseDialogScreen {
|
||||
@@ -125,6 +129,8 @@ private:
|
||||
bool initialSetup_;
|
||||
// The data is already in the new folder.
|
||||
bool dataMoved_ = false;
|
||||
// Set when everything worked: restart then (time_now_d()), after showing that.
|
||||
double restartTime_ = 0.0;
|
||||
|
||||
MoveProgressReporter progressReporter_;
|
||||
UI::TextView *progressView_ = nullptr;
|
||||
|
||||
@@ -292,7 +292,7 @@ void BuildSystemSettings(SettingsPage *page, SystemPageState *state) {
|
||||
auto addMemstickPath = [host, &page]() {
|
||||
page->Custom("Memory Stick folder", [host](UI::ViewGroup *parent) -> UI::View * {
|
||||
Choice *memstickPath = parent->Add(new ChoiceWithCallbackValueDisplay(T(I18NCat::SYSTEM, "Memory Stick folder"), []() {
|
||||
return g_Config.memStickDirectory.ToVisualString();
|
||||
return GetMemstickFolderDisplayName(g_Config.memStickDirectory);
|
||||
}));
|
||||
memstickPath->OnClick.Add([host](UI::EventParams &e) {
|
||||
host->PageScreenManager()->push(new MemStickScreen(false));
|
||||
|
||||
Reference in new issue
Block a user