Skip to content

Commit

Permalink
Rework memstick moves between devices to copy, verify and then delete
Browse files Browse the repository at this point in the history
  • Loading branch information
hrydgard committed Jan 22, 2024
1 parent 9d9f03e commit 6e587f5
Show file tree
Hide file tree
Showing 46 changed files with 203 additions and 61 deletions.
156 changes: 105 additions & 51 deletions Core/Util/MemStick.cpp
Expand Up @@ -69,7 +69,7 @@ struct FileSuffix {
u64 fileSize;
};

static bool ListFileSuffixesRecursively(const Path &root, const Path &folder, std::vector<std::string> &dirSuffixes, std::vector<FileSuffix> &fileSuffixes) {
static bool ListFileSuffixesRecursively(const Path &root, const Path &folder, std::vector<std::string> &dirSuffixes, std::vector<FileSuffix> &fileSuffixes, MoveProgressReporter &progressReporter) {
std::vector<File::FileInfo> files;
if (!File::GetFilesInDir(folder, &files)) {
return false;
Expand All @@ -81,7 +81,8 @@ static bool ListFileSuffixesRecursively(const Path &root, const Path &folder, st
if (root.ComputePathTo(file.fullName, dirSuffix)) {
if (!dirSuffix.empty()) {
dirSuffixes.push_back(dirSuffix);
ListFileSuffixesRecursively(root, folder / file.name, dirSuffixes, fileSuffixes);
ListFileSuffixesRecursively(root, folder / file.name, dirSuffixes, fileSuffixes, progressReporter);
progressReporter.SetProgress(file.name, fileSuffixes.size(), (size_t)-1);
}
} else {
ERROR_LOG_REPORT(SYSTEM, "Failed to compute PathTo from '%s' to '%s'", root.c_str(), folder.c_str());
Expand All @@ -101,16 +102,16 @@ static bool ListFileSuffixesRecursively(const Path &root, const Path &folder, st

bool MoveChildrenFast(const Path &moveSrc, const Path &moveDest, MoveProgressReporter &progressReporter) {
std::vector<File::FileInfo> files;
progressReporter.SetStatus("Starting move...");
if (!File::GetFilesInDir(moveSrc, &files)) {
return false;
}

for (auto file : files) {
for (size_t i = 0; i < files.size(); i++) {
auto &file = files[i];
// Construct destination path
Path fileSrc = file.fullName;
Path fileDest = moveDest / file.name;
progressReporter.SetStatus(file.name);
progressReporter.SetProgress(file.name, i, files.size());
INFO_LOG(SYSTEM, "About to move PSP data from '%s' to '%s'", fileSrc.c_str(), fileDest.c_str());
bool result = File::MoveIfFast(fileSrc, fileDest);
if (!result) {
Expand All @@ -121,6 +122,21 @@ bool MoveChildrenFast(const Path &moveSrc, const Path &moveDest, MoveProgressRep
return true;
}

std::string MoveProgressReporter::Format() {
std::string str;
{
std::lock_guard<std::mutex> guard(mutex_);
if (max_ > 0) {
str = StringFromFormat("(%d/%d) ", count_, max_);
} else if (max_ < 0) {
str = StringFromFormat("(%d) ", count_);
}
str += progress_;
}
return str;
}


MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressReporter &progressReporter) {
auto ms = GetI18NCategory(I18NCat::MEMSTICK);
if (moveSrc.GetFilename() != "PSP") {
Expand All @@ -137,7 +153,7 @@ MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressR
// We loop through the files/dirs in the source directory and just try to move them, it should work.
if (MoveChildrenFast(moveSrc, moveDest, progressReporter)) {
INFO_LOG(SYSTEM, "Quick-move succeeded");
progressReporter.SetStatus(ms->T("Done!"));
progressReporter.SetProgress(ms->T("Done!"));
return new MoveResult{
true, ""
};
Expand All @@ -152,75 +168,113 @@ MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressR
std::vector<std::string> directorySuffixesToCreate;

// NOTE: It's correct to pass moveSrc twice here, it's to keep the root in the recursion.
if (!ListFileSuffixesRecursively(moveSrc, moveSrc, directorySuffixesToCreate, fileSuffixesToMove)) {
if (!ListFileSuffixesRecursively(moveSrc, moveSrc, directorySuffixesToCreate, fileSuffixesToMove, progressReporter)) {
// TODO: Handle failure listing files.
std::string error = "Failed to read old directory";
INFO_LOG(SYSTEM, "%s", error.c_str());
progressReporter.SetStatus(ms->T(error.c_str()));
progressReporter.SetProgress(ms->T(error.c_str()));
return new MoveResult{ false, error };
}

bool dryRun = false; // Useful for debugging.

size_t failedFiles = 0;
size_t skippedFiles = 0;

// We're not moving huge files like ISOs during this process, unless
// they can be directly moved, without rewriting the file.
const uint64_t BIG_FILE_THRESHOLD = 24 * 1024 * 1024;

if (!moveSrc.empty()) {
// Better not interrupt the app while this is happening!
if (moveSrc.empty()) {
// Shouldn't happen.
return new MoveResult{ true, "", };
}

// Create all the necessary directories.
for (auto &dirSuffix : directorySuffixesToCreate) {
Path dir = moveDest / dirSuffix;
if (dryRun) {
INFO_LOG(SYSTEM, "dry run: Would have created dir '%s'", dir.c_str());
} else {
INFO_LOG(SYSTEM, "Creating dir '%s'", dir.c_str());
progressReporter.SetStatus(dirSuffix);
// Just ignore already-exists errors.
File::CreateDir(dir);
}
// Better not interrupt the app while this is happening!

// Create all the necessary directories.
for (size_t i = 0; i < directorySuffixesToCreate.size(); i++) {
const auto &dirSuffix = directorySuffixesToCreate[i];
Path dir = moveDest / dirSuffix;
if (dryRun) {
INFO_LOG(SYSTEM, "dry run: Would have created dir '%s'", dir.c_str());
} else {
INFO_LOG(SYSTEM, "Creating dir '%s'", dir.c_str());
progressReporter.SetProgress(dirSuffix);
// Just ignore already-exists errors.
File::CreateDir(dir);
}
}

for (auto &fileSuffix : fileSuffixesToMove) {
progressReporter.SetStatus(StringFromFormat("%s (%s)", fileSuffix.suffix.c_str(), NiceSizeFormat(fileSuffix.fileSize).c_str()));
for (size_t i = 0; i < fileSuffixesToMove.size(); i++) {
const auto &fileSuffix = fileSuffixesToMove[i];
progressReporter.SetProgress(StringFromFormat("%s (%s)", fileSuffix.suffix.c_str(), NiceSizeFormat(fileSuffix.fileSize).c_str()),
(int)i, (int)fileSuffixesToMove.size());

Path from = moveSrc / fileSuffix.suffix;
Path to = moveDest / fileSuffix.suffix;
Path from = moveSrc / fileSuffix.suffix;
Path to = moveDest / fileSuffix.suffix;

if (dryRun) {
INFO_LOG(SYSTEM, "dry run: Would have moved '%s' to '%s' (%d bytes)", from.c_str(), to.c_str(), (int)fileSuffix.fileSize);
if (dryRun) {
INFO_LOG(SYSTEM, "dry run: Would have moved '%s' to '%s' (%d bytes)", from.c_str(), to.c_str(), (int)fileSuffix.fileSize);
} else {
// Remove the "from" prefix from the path.
// We have to drop down to string operations for this.
if (!File::Copy(from, to)) {
ERROR_LOG(SYSTEM, "Failed to copy file '%s' to '%s'", from.c_str(), to.c_str());
failedFiles++;
// Should probably just bail?
} else {
// Remove the "from" prefix from the path.
// We have to drop down to string operations for this.
if (!File::Copy(from, to)) {
ERROR_LOG(SYSTEM, "Failed to copy file '%s' to '%s'", from.c_str(), to.c_str());
failedFiles++;
// Should probably just bail?
} else {
INFO_LOG(SYSTEM, "Copied file '%s' to '%s'", from.c_str(), to.c_str());
}
INFO_LOG(SYSTEM, "Copied file '%s' to '%s'", from.c_str(), to.c_str());
}
}
}

// Delete all the old, now hopefully empty, directories.
// Hopefully DeleteDir actually fails if it contains a file...
for (auto &dirSuffix : directorySuffixesToCreate) {
Path dir = moveSrc / dirSuffix;
if (dryRun) {
INFO_LOG(SYSTEM, "dry run: Would have deleted dir '%s'", dir.c_str());
} else {
INFO_LOG(SYSTEM, "Deleting dir '%s'", dir.c_str());
progressReporter.SetStatus(dirSuffix);
if (File::Exists(dir)) {
File::DeleteDir(dir);
}
}
if (failedFiles) {
return new MoveResult{ false, "", failedFiles };
}

// After the whole move, verify that all the files arrived correctly.
// If there's a single error, we do not delete the source data.
bool ok = true;
for (size_t i = 0; i < fileSuffixesToMove.size(); i++) {
const auto &fileSuffix = fileSuffixesToMove[i];
progressReporter.SetProgress(ms->T("Checking..."), (int)i, (int)fileSuffixesToMove.size());

Path to = moveDest / fileSuffix.suffix;

File::FileInfo info;
if (!File::GetFileInfo(to, &info)) {
ok = false;
break;
}

if (fileSuffix.fileSize != info.size) {
ERROR_LOG(SYSTEM, "Mismatched size in target file %s. Verification failed.", fileSuffix.suffix.c_str());
ok = false;
failedFiles++;
break;
}
}

return new MoveResult{ true, "", failedFiles };
if (!ok) {
return new MoveResult{ false, "", failedFiles };
}

INFO_LOG(SYSTEM, "Verification complete");

// Delete all the old, now hopefully empty, directories.
// Hopefully DeleteDir actually fails if it contains a file...
for (size_t i = 0; i < directorySuffixesToCreate.size(); i++) {
const auto &dirSuffix = directorySuffixesToCreate[i];
Path dir = moveSrc / dirSuffix;
if (dryRun) {
INFO_LOG(SYSTEM, "dry run: Would have deleted dir '%s'", dir.c_str());
} else {
INFO_LOG(SYSTEM, "Deleting dir '%s'", dir.c_str());
progressReporter.SetProgress(dirSuffix, i, directorySuffixesToCreate.size());
if (File::Exists(dir)) {
File::DeleteDir(dir);
}
}
}
return new MoveResult{ true, "", 0 };
}
12 changes: 7 additions & 5 deletions Core/Util/MemStick.h
Expand Up @@ -3,24 +3,26 @@
#include "Common/File/Path.h"

#include <mutex>
#include <string_view>

// Utility functions moved out from MemstickScreen.

class MoveProgressReporter {
public:
void SetStatus(const std::string &value) {
void SetProgress(std::string_view value, size_t count = 0, size_t maxVal = 0) {
std::lock_guard<std::mutex> guard(mutex_);
progress_ = value;
count_ = (int)count;
max_ = (int)maxVal;
}

std::string Get() {
std::lock_guard<std::mutex> guard(mutex_);
return progress_;
}
std::string Format();

private:
std::string progress_;
std::mutex mutex_;
int count_;
int max_;
};

struct MoveResult {
Expand Down
10 changes: 5 additions & 5 deletions UI/MemStickScreen.cpp
Expand Up @@ -518,7 +518,7 @@ void ConfirmMemstickMoveScreen::CreateViews() {
}

if (moveDataTask_) {
progressView_ = leftColumn->Add(new TextView(progressReporter_.Get()));
progressView_ = leftColumn->Add(new TextView(progressReporter_.Format()));
} else {
progressView_ = nullptr;
}
Expand All @@ -545,19 +545,19 @@ void ConfirmMemstickMoveScreen::update() {

if (moveDataTask_) {
if (progressView_) {
progressView_->SetText(progressReporter_.Get());
progressView_->SetText(progressReporter_.Format());
}

MoveResult *result = moveDataTask_->Poll();

if (result) {
if (result->success) {
progressReporter_.SetStatus(iz->T("Done!"));
progressReporter_.SetProgress(iz->T("Done!"));
INFO_LOG(SYSTEM, "Move data task finished successfully!");
// Succeeded!
FinishFolderMove();
} else {
progressReporter_.SetStatus(iz->T("Failed to move some files!"));
progressReporter_.SetProgress(iz->T("Failed to move some files!"));
INFO_LOG(SYSTEM, "Move data task failed!");
// What do we do here? We might be in the middle of a move... Bad.
RecreateViews();
Expand All @@ -575,7 +575,7 @@ UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams &params) {
// If the directory itself is called PSP, don't go below.

if (moveData_) {
progressReporter_.SetStatus(T(I18NCat::MEMSTICK, "Starting move..."));
progressReporter_.SetProgress(T(I18NCat::MEMSTICK, "Starting move..."));

moveDataTask_ = Promise<MoveResult *>::Spawn(&g_threadManager, [&]() -> MoveResult * {
Path moveSrc = g_Config.memStickDirectory;
Expand Down
2 changes: 2 additions & 0 deletions assets/lang/ar_AE.ini
Expand Up @@ -848,12 +848,14 @@ Wlan = ‎الواي فاي
[MemStick]
Already contains PSP data = Already contains PSP data
Cancelled - try again = Cancelled - try again
Checking... = Checking...
Create or Choose a PSP folder = Create or Choose a PSP folder
Current = Current
DataCanBeShared = Data can be shared between PPSSPP regular/Gold
DataCannotBeShared = Data CANNOT be shared between PPSSPP regular/Gold!
DataWillBeLostOnUninstall = Warning! Data will be lost when you uninstall PPSSPP!
DataWillStay = Data will stay even if you uninstall PPSSPP.
Deleting... = Deleting...
Done! = Done!
EasyUSBAccess = Easy USB access
Failed to move some files! = Failed to move some files!
Expand Down
2 changes: 2 additions & 0 deletions assets/lang/az_AZ.ini
Expand Up @@ -840,12 +840,14 @@ Wlan = WLAN
[MemStick]
Already contains PSP data = Already contains PSP data
Cancelled - try again = Cancelled - try again
Checking... = Checking...
Create or Choose a PSP folder = Create or Choose a PSP folder
Current = Current
DataCanBeShared = Data can be shared between PPSSPP regular/Gold
DataCannotBeShared = Data CANNOT be shared between PPSSPP regular/Gold!
DataWillBeLostOnUninstall = Warning! Data will be lost when you uninstall PPSSPP!
DataWillStay = Data will stay even if you uninstall PPSSPP.
Deleting... = Deleting...
Done! = Done!
EasyUSBAccess = Easy USB access
Failed to move some files! = Failed to move some files!
Expand Down
2 changes: 2 additions & 0 deletions assets/lang/bg_BG.ini
Expand Up @@ -840,12 +840,14 @@ Wlan = WLAN
[MemStick]
Already contains PSP data = Already contains PSP data
Cancelled - try again = Cancelled - try again
Checking... = Checking...
Create or Choose a PSP folder = Create or Choose a PSP folder
Current = Current
DataCanBeShared = Data can be shared between PPSSPP regular/Gold
DataCannotBeShared = Data CANNOT be shared between PPSSPP regular/Gold!
DataWillBeLostOnUninstall = Warning! Data will be lost when you uninstall PPSSPP!
DataWillStay = Data will stay even if you uninstall PPSSPP.
Deleting... = Deleting...
Done! = Done!
EasyUSBAccess = Easy USB access
Failed to move some files! = Failed to move some files!
Expand Down
2 changes: 2 additions & 0 deletions assets/lang/ca_ES.ini
Expand Up @@ -840,12 +840,14 @@ Wlan = WLAN
[MemStick]
Already contains PSP data = Already contains PSP data
Cancelled - try again = Cancelled - try again
Checking... = Checking...
Create or Choose a PSP folder = Create or Choose a PSP folder
Current = Current
DataCanBeShared = Data can be shared between PPSSPP regular/Gold
DataCannotBeShared = Data CANNOT be shared between PPSSPP regular/Gold!
DataWillBeLostOnUninstall = Warning! Data will be lost when you uninstall PPSSPP!
DataWillStay = Data will stay even if you uninstall PPSSPP.
Deleting... = Deleting...
Done! = Done!
EasyUSBAccess = Easy USB access
Failed to move some files! = Failed to move some files!
Expand Down
2 changes: 2 additions & 0 deletions assets/lang/cz_CZ.ini
Expand Up @@ -840,12 +840,14 @@ Wlan = WLAN
[MemStick]
Already contains PSP data = Already contains PSP data
Cancelled - try again = Cancelled - try again
Checking... = Checking...
Create or Choose a PSP folder = Create or Choose a PSP folder
Current = Current
DataCanBeShared = Data can be shared between PPSSPP regular/Gold
DataCannotBeShared = Data CANNOT be shared between PPSSPP regular/Gold!
DataWillBeLostOnUninstall = Warning! Data will be lost when you uninstall PPSSPP!
DataWillStay = Data will stay even if you uninstall PPSSPP.
Deleting... = Deleting...
Done! = Done!
EasyUSBAccess = Easy USB access
Failed to move some files! = Failed to move some files!
Expand Down
2 changes: 2 additions & 0 deletions assets/lang/da_DK.ini
Expand Up @@ -840,12 +840,14 @@ Wlan = WLAN
[MemStick]
Already contains PSP data = Already contains PSP data
Cancelled - try again = Cancelled - try again
Checking... = Checking...
Create or Choose a PSP folder = Create or Choose a PSP folder
Current = Current
DataCanBeShared = Data can be shared between PPSSPP regular/Gold
DataCannotBeShared = Data CANNOT be shared between PPSSPP regular/Gold!
DataWillBeLostOnUninstall = Warning! Data will be lost when you uninstall PPSSPP!
DataWillStay = Data will stay even if you uninstall PPSSPP.
Deleting... = Deleting...
Done! = Done!
EasyUSBAccess = Easy USB access
Failed to move some files! = Failed to move some files!
Expand Down

0 comments on commit 6e587f5

Please sign in to comment.