From b3aa6252ce86b7412cac69d29721af0bf130ceea Mon Sep 17 00:00:00 2001 From: jacob1 Date: Sun, 27 Jun 2021 18:25:08 -0400 Subject: [PATCH] Add button in options menu to migrate to shared data directory a18855301306 Summary of migrated files will be shown to user in a popup, and a log file with every moved file will be left in the original directory stamps, saves, scripts, screenshots, and powder.pref will be migrated. Recordings are not. --- src/PowderToySDL.cpp | 27 ++-- src/common/Platform.cpp | 147 +++++++++++++++++++- src/common/Platform.h | 4 + src/gui/filebrowser/FileBrowserActivity.cpp | 4 +- src/gui/options/OptionsView.cpp | 35 +++-- 5 files changed, 182 insertions(+), 35 deletions(-) diff --git a/src/PowderToySDL.cpp b/src/PowderToySDL.cpp index 40a84cc70..f9ad3de07 100644 --- a/src/PowderToySDL.cpp +++ b/src/PowderToySDL.cpp @@ -738,32 +738,34 @@ int main(int argc, char * argv[]) return 1; } + Platform::originalCwd = Platform::GetCwd(); + std::map arguments = readArguments(argc, argv); - if(arguments["ddir"].length()) + if (arguments["ddir"].length()) { #ifdef WIN int failure = _chdir(arguments["ddir"].c_str()); #else int failure = chdir(arguments["ddir"].c_str()); #endif - if (failure) - { + if (!failure) + Platform::sharedCwd = Platform::GetCwd(); + else perror("failed to chdir to requested ddir"); - } } else { + char *ddir = SDL_GetPrefPath(NULL, "The Powder Toy"); #ifdef WIN struct _stat s; - if(_stat("powder.pref", &s) != 0) + if (_stat("powder.pref", &s) != 0) #else struct stat s; - if(stat("powder.pref", &s) != 0) + if (stat("powder.pref", &s) != 0) #endif { - char *ddir = SDL_GetPrefPath(NULL, "The Powder Toy"); - if(ddir) + if (ddir) { #ifdef WIN int failure = _chdir(ddir); @@ -773,10 +775,17 @@ int main(int argc, char * argv[]) if (failure) { perror("failed to chdir to default ddir"); + SDL_free(ddir); + ddir = nullptr; } - SDL_free(ddir); } } + + if (ddir) + { + Platform::sharedCwd = ddir; + SDL_free(ddir); + } } scale = Client::Ref().GetPrefInteger("Scale", 1); diff --git a/src/common/Platform.cpp b/src/common/Platform.cpp index 31a3998ff..df3dfa523 100644 --- a/src/common/Platform.cpp +++ b/src/common/Platform.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #ifdef WIN @@ -29,11 +30,13 @@ namespace Platform { +std::string originalCwd; +std::string sharedCwd; + ByteString GetCwd() { - char cwdTemp[PATH_MAX]; - getcwd(cwdTemp, PATH_MAX); - return cwdTemp; + char *cwd = getcwd(NULL, 0); + return cwd == nullptr ? "" : cwd; } ByteString ExecutableName() @@ -324,11 +327,13 @@ std::vector DirectorySearch(ByteString directory, ByteString search, { ByteString currentFileName = ByteString(directoryEntry->d_name); if (currentFileName.length()>4) - directoryList.push_back(directory+currentFileName); + directoryList.push_back(currentFileName); } closedir(directoryHandle); #endif + search = search.ToLower(); + std::vector searchResults; for (std::vector::iterator iter = directoryList.begin(), end = directoryList.end(); iter != end; ++iter) { @@ -339,7 +344,7 @@ std::vector DirectorySearch(ByteString directory, ByteString search, if (filename.EndsWith(*extIter)) { extensionMatch = true; - tempfilename = filename.SubstrFromEnd(0, (*extIter).size()).ToUpper(); + tempfilename = filename.SubstrFromEnd(0, (*extIter).size()).ToLower(); break; } } @@ -355,6 +360,138 @@ std::vector DirectorySearch(ByteString directory, ByteString search, return searchResults; } +String DoMigration(ByteString fromDir, ByteString toDir) +{ + if (fromDir.at(fromDir.length() - 1) != '/') + fromDir = fromDir + '/'; + if (toDir.at(toDir.length() - 1) != '/') + toDir = toDir + '/'; + + std::ofstream logFile(fromDir + "/migrationlog.txt", std::ios::out); + logFile << "Running migration of data from " << fromDir + " to " << toDir << std::endl; + + // Get lists of files to migrate + auto stamps = DirectorySearch(fromDir + "stamps", "", { ".stm" }); + auto saves = DirectorySearch(fromDir + "Saves", "", { ".cps", ".stm" }); + auto scripts = DirectorySearch(fromDir + "scripts", "", { ".lua", ".txt" }); + auto downloadedScripts = DirectorySearch(fromDir + "scripts/downloaded", "", { ".lua" }); + bool hasScriptinfo = FileExists(toDir + "scripts/downloaded/scriptinfo"); + auto screenshots = DirectorySearch(fromDir, "powdertoy-", { ".png" }); + bool hasAutorun = FileExists(fromDir + "autorun.lua"); + bool hasPref = FileExists(fromDir + "powder.pref"); + + if (stamps.empty() && saves.empty() && scripts.empty() && downloadedScripts.empty() && screenshots.empty() && !hasAutorun && !hasPref) + { + logFile << "Nothing to migrate."; + return "Nothing to migrate. This button is used to migrate data from pre-96.0 TPT installations to the shared directory"; + } + + StringBuilder result; + std::stack dirsToDelete; + + // Migrate a list of files + auto migrateList = [&](std::vector list, ByteString directory, String niceName) { + result << '\n' << niceName << ": "; + if (!list.empty() && !directory.empty()) + MakeDirectory(toDir + directory); + int migratedCount = 0, failedCount = 0; + for (auto &item : list) + { + std::string from = fromDir + directory + "/" + item; + std::string to = toDir + directory + "/" + item; + if (!FileExists(to)) + { + if (rename(from.c_str(), to.c_str())) + { + failedCount++; + logFile << "failed to move " << from << " to " << to << std::endl; + } + else + { + migratedCount++; + logFile << "moved " << from << " to " << to << std::endl; + } + } + else + { + logFile << "skipping " << from << "(already exists)" << std::endl; + } + } + + dirsToDelete.push(directory); + result << "\bt" << migratedCount << " migratated\x0E, \br" << failedCount << " failed\x0E"; + int duplicates = list.size() - migratedCount - failedCount; + if (duplicates) + result << ", " << list.size() - migratedCount - failedCount << " skipped (duplicate)"; + }; + + // Migrate a single file + auto migrateFile = [&fromDir, &toDir, &result, &logFile](ByteString filename) { + ByteString from = fromDir + filename; + ByteString to = toDir + filename; + if (!FileExists(to)) + { + if (rename(from.c_str(), to.c_str())) + { + logFile << "failed to move " << from << " to " << to << std::endl; + result << "\n\br" << filename.FromUtf8() << " migration failed\x0E"; + } + else + { + logFile << "moved " << from << " to " << to << std::endl; + result << '\n' << filename.FromUtf8() << " migrated"; + } + } + else + { + logFile << "skipping " << from << "(already exists)" << std::endl; + result << '\n' << filename.FromUtf8() << " skipped (already exists)"; + } + + if (!DeleteFile(fromDir + filename)) { + logFile << "failed to delete " << filename << std::endl; + } + }; + + // Do actual migration + DeleteFile(fromDir + "stamps/stamps.def"); + migrateList(stamps, "stamps", "Stamps"); + migrateList(saves, "Saves", "Saves"); + if (!scripts.empty()) + migrateList(scripts, "scripts", "Scripts"); + if (!hasScriptinfo && !downloadedScripts.empty()) + { + migrateList(downloadedScripts, "scripts/downloaded", "Downloaded scripts"); + migrateFile("scripts/downloaded/scriptinfo"); + } + if (!screenshots.empty()) + migrateList(screenshots, "", "Screenshots"); + if (hasAutorun) + migrateFile("autorun.lua"); + if (hasPref) + migrateFile("powder.pref"); + + // Delete leftover directories + while (!dirsToDelete.empty()) + { + ByteString toDelete = dirsToDelete.top(); + if (!DeleteDirectory(fromDir + toDelete)) { + logFile << "failed to delete " << toDelete << std::endl; + } + dirsToDelete.pop(); + } + + // chdir into the new directory + chdir(toDir.c_str()); + + if (scripts.size()) + Client::Ref().RescanStamps(); + + logFile << std::endl << std::endl << "Migration complete. Results: " << result.Build().ToUtf8(); + logFile.close(); + + return result.Build(); +} #ifdef WIN ByteString WinNarrow(const std::wstring &source) diff --git a/src/common/Platform.h b/src/common/Platform.h index 3e593b930..6cc41c6c0 100644 --- a/src/common/Platform.h +++ b/src/common/Platform.h @@ -39,11 +39,15 @@ namespace Platform */ bool MakeDirectory(ByteString dir); std::vector DirectorySearch(ByteString directory, ByteString search, std::vector extensions); + String DoMigration(ByteString fromDir, ByteString toDir); #ifdef WIN ByteString WinNarrow(const std::wstring &source); std::wstring WinWiden(const ByteString &source); #endif + + extern std::string originalCwd; + extern std::string sharedCwd; } #endif diff --git a/src/gui/filebrowser/FileBrowserActivity.cpp b/src/gui/filebrowser/FileBrowserActivity.cpp index 0d957ede6..cf7552a97 100644 --- a/src/gui/filebrowser/FileBrowserActivity.cpp +++ b/src/gui/filebrowser/FileBrowserActivity.cpp @@ -44,10 +44,10 @@ class LoadFilesTask: public Task notifyProgress(-1); for(std::vector::iterator iter = files.begin(), end = files.end(); iter != end; ++iter) { - SaveFile * saveFile = new SaveFile(*iter); + SaveFile * saveFile = new SaveFile(directory + *iter); try { - std::vector data = Client::Ref().ReadFile(*iter); + std::vector data = Client::Ref().ReadFile(directory + *iter); GameSave * tempSave = new GameSave(data); saveFile->SetGameSave(tempSave); saveFiles.push_back(saveFile); diff --git a/src/gui/options/OptionsView.cpp b/src/gui/options/OptionsView.cpp index ee62abba1..aa1631af5 100644 --- a/src/gui/options/OptionsView.cpp +++ b/src/gui/options/OptionsView.cpp @@ -2,22 +2,19 @@ #include #include -#ifdef WIN -#include -#define getcwd _getcwd -#else -#include -#endif #include "SDLCompat.h" #include "OptionsController.h" #include "OptionsModel.h" +#include "client/Client.h" #include "common/Platform.h" #include "graphics/Graphics.h" #include "gui/Style.h" #include "simulation/ElementDefs.h" +#include "gui/dialogues/ConfirmPrompt.h" +#include "gui/dialogues/InformationMessage.h" #include "gui/interface/Button.h" #include "gui/interface/Checkbox.h" #include "gui/interface/DropDown.h" @@ -305,8 +302,6 @@ OptionsView::OptionsView(): scrollPanel->AddChild(tempLabel); scrollPanel->AddChild(perfectCirclePressure); - //perfectCirclePressure - currentY+=20; decoSpace = new ui::DropDown(ui::Point(8, currentY), ui::Point(60, 16)); decoSpace->SetActionCallback({ [this] { c->SetDecoSpace(decoSpace->GetOption().second); } }); @@ -324,23 +319,25 @@ OptionsView::OptionsView(): currentY+=20; ui::Button * dataFolderButton = new ui::Button(ui::Point(8, currentY), ui::Point(90, 16), "Open Data Folder"); dataFolderButton->SetActionCallback({ [] { - auto *cwd = getcwd(NULL, 0); - if (cwd) - { + ByteString cwd = Platform::GetCwd(); + if (!cwd.empty()) Platform::OpenURI(cwd); - } else - { fprintf(stderr, "cannot open data folder: getcwd(...) failed\n"); - } } }); scrollPanel->AddChild(dataFolderButton); - tempLabel = new ui::Label(ui::Point(dataFolderButton->Position.X+dataFolderButton->Size.X+3, currentY), ui::Point(1, 16), "\bg- Open the data and preferences folder"); - autowidth(tempLabel); - tempLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft; - tempLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; - scrollPanel->AddChild(tempLabel); + ui::Button * migrationButton = new ui::Button(ui::Point(Size.X - 178, currentY), ui::Point(163, 16), "Migrate to shared data directory"); + migrationButton->SetActionCallback({ [] { + ByteString from = Platform::originalCwd; + ByteString to = Platform::sharedCwd; + new ConfirmPrompt("Do Migration?", "This will migrate all stamps, saves, and scripts from\n\bt" + from.FromUtf8() + "\bw\nto the shared data directory at\n\bt" + to.FromUtf8() + "\bw\n\n" + + "Files that already exist will not be overwritten.", { [=] () { + String ret = Platform::DoMigration(from, to); + new InformationMessage("Migration Complete", ret, false); + } }); + } }); + scrollPanel->AddChild(migrationButton); ui::Button * tempButton = new ui::Button(ui::Point(0, Size.Y-16), ui::Point(Size.X, 16), "OK"); tempButton->SetActionCallback({ [this] { c->Exit(); } });