diff --git a/src/archive_tasks.cpp b/src/archive_tasks.cpp index 7502659b..4584db3e 100644 --- a/src/archive_tasks.cpp +++ b/src/archive_tasks.cpp @@ -27,6 +27,7 @@ #include "transaction.hpp" #include +#include #include static const Path ARCHIVE_TOC("toc"); @@ -54,16 +55,11 @@ bool ExportTask::start() std::vector jobs; - for(const Remote &remote : g_reapack->config()->remotes.getEnabled()) { - bool addedRemote = false; + for(const Remote &remote : g_reapack->config()->remotes) { + toc << "REPO " << remote.toString() << '\n'; + jobs.push_back(new FileCompressor(Index::pathFor(remote.name()), writer)); for(const Registry::Entry &entry : tx()->registry()->getEntries(remote.name())) { - if(!addedRemote) { - toc << "REPO " << remote.toString() << '\n'; - jobs.push_back(new FileCompressor(Index::pathFor(remote.name()), writer)); - addedRemote = true; - } - toc << "PACK " << quoted(entry.category) << '\x20' << quoted(entry.package) << '\x20' @@ -107,3 +103,22 @@ void ExportTask::rollback() { FS::remove(m_path.temp()); } + +void ExportIndexTask::commit() +{ + std::ofstream index{m_path, std::ios::out | std::ios::trunc}; + if (!index.good()) + return tx()->receipt()->addError({"Error opening index file", m_path}); + + for(const Remote &remote : g_reapack->config()->remotes) { + index << "REPO " << remote.toString() << '\n'; + + for(const Registry::Entry &entry : tx()->registry()->getEntries(remote.name())) { + index << "PACK " + << quoted(entry.category) << '\x20' + << quoted(entry.package) << '\x20' + << quoted(entry.version.toString()) << '\x20' + << entry.flags << '\n'; + } + } +} diff --git a/src/install.cpp b/src/install.cpp index f37ac1c9..2e79e1d0 100644 --- a/src/install.cpp +++ b/src/install.cpp @@ -135,3 +135,37 @@ void InstallTask::rollback() m_fail = true; } + +InstallFromIndexTask::InstallFromIndexTask(PackageFromIndex&& pkg_, Transaction *tx) + : Task(tx), pkg(std::move(pkg_)) { } + +bool InstallFromIndexTask::start() +{ + const IndexPtr index = tx()->loadIndex({pkg.remote}); + if(!index) return false; + + const ErrorInfo err = { + String::format("%s/%s/%s v%s", + pkg.remote.name().c_str(), pkg.category.c_str(), pkg.name.c_str(), pkg.version.c_str()), + "Package cannot be found or is incompatible with your operating system"}; + + const Package *const pkg_handle = [&]() -> const Package * { + for(const Package *other : index->packages()) + if(other->name() == pkg.name && other->category()->name() == pkg.category) + return other; + return nullptr; + }(); + + if(!pkg_handle) { + tx()->receipt()->addError(err); + return false; + } else if(version = pkg_handle->findVersion({pkg.version}); !version) { + tx()->receipt()->addError(err); + return false; + } + return true; +} + +void InstallFromIndexTask::commit() { + tx()->install(version, pkg.flags); +} diff --git a/src/manager.cpp b/src/manager.cpp index 9758b6ff..f64feb7e 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -32,9 +32,15 @@ #include "transaction.hpp" #include "win32.hpp" +#include +#include + static const Win32::char_type *ARCHIVE_FILTER = L("ReaPack Offline Archive (*.ReaPackArchive)\0*.ReaPackArchive\0"); static const Win32::char_type *ARCHIVE_EXT = L("ReaPackArchive"); +static const Win32::char_type *INDEX_FILTER = + L("ReaPack Index (*.txt)\0*.txt\0"); +static const Win32::char_type *INDEX_EXT = L("txt"); enum { ACTION_UNINSTALL = 80, ACTION_ABOUT, ACTION_REFRESH, ACTION_COPYURL, @@ -42,7 +48,7 @@ enum { ACTION_AUTOINSTALL_OFF, ACTION_AUTOINSTALL_ON, ACTION_AUTOINSTALL, ACTION_BLEEDINGEDGE, ACTION_PROMPTOBSOLETE, ACTION_SYNONYMS, ACTION_NETCONFIG, ACTION_RESETCONFIG, ACTION_IMPORT_REPO, ACTION_IMPORT_ARCHIVE, - ACTION_EXPORT_ARCHIVE, + ACTION_EXPORT_ARCHIVE, ACTION_IMPORT_INDEX, ACTION_EXPORT_INDEX }; enum { TIMER_ABOUT = 1, }; @@ -150,6 +156,13 @@ void Manager::onCommand(const int id, int) case ACTION_EXPORT_ARCHIVE: exportArchive(); break; + case ACTION_IMPORT_INDEX: + importIndex(); + refresh(); + break; + case ACTION_EXPORT_INDEX: + exportIndex(); + break; case ACTION_AUTOINSTALL: toggle(m_autoInstall, g_reapack->config()->install.autoInstall); break; @@ -470,6 +483,9 @@ void Manager::importExport() Menu menu; menu.addAction("Import &repositories...", ACTION_IMPORT_REPO); menu.addSeparator(); + menu.addAction("Import index", ACTION_IMPORT_INDEX); + menu.addAction("Export index", ACTION_EXPORT_INDEX); + menu.addSeparator(); menu.addAction("Import offline archive...", ACTION_IMPORT_ARCHIVE); menu.addAction("&Export offline archive...", ACTION_EXPORT_ARCHIVE); @@ -492,7 +508,7 @@ void Manager::importArchive() { const char *title = "Import offline archive"; - const std::string &path = FileDialog::getOpenFileName(handle(), instance(), + const std::string path = FileDialog::getOpenFileName(handle(), instance(), title, Path::DATA.prependRoot(), ARCHIVE_FILTER, ARCHIVE_EXT); if(path.empty()) @@ -510,7 +526,7 @@ void Manager::importArchive() void Manager::exportArchive() { - const std::string &path = FileDialog::getSaveFileName(handle(), instance(), + const std::string path = FileDialog::getSaveFileName(handle(), instance(), "Export offline archive", Path::DATA.prependRoot(), ARCHIVE_FILTER, ARCHIVE_EXT); if(!path.empty()) { @@ -521,6 +537,67 @@ void Manager::exportArchive() } } +void Manager::importIndex() +{ + Transaction * const tx = g_reapack->setupTransaction(); + if(!tx) return; + Receipt * const receipt = tx->receipt(); + + const std::string path = FileDialog::getOpenFileName(handle(), instance(), + "Import index", Path::root(), INDEX_FILTER, INDEX_EXT); + + std::ifstream index{path}; + if(!index.good()) { + receipt->addError({"Error opening index file", path}); + tx->runTasks(); + return; + } + + Remote remote; + std::string line; + std::vector packages; + + while(std::getline(index, line)) { + switch(line[0]) { + case 'R': + if(remote = Remote::fromString(line.substr(5)); remote.isNull()) + receipt->addError({"Error adding remote", remote.toString()}); + else { + g_reapack->addSetRemote(remote); + // strange but above function doesn't fetch index files despite issuing same SynchronizeTask + tx->fetchIndexes({remote}, true); + } + break; + case 'P': { + auto& ref = packages.emplace_back(PackageFromIndex{remote}); + std::istringstream{line.substr(5)} + >> quoted(ref.category) >> quoted(ref.name) >> quoted(ref.version) + >> ref.flags; + break; + } + default: + receipt->addError({line, "Invalid index entry"}); + } + } + + g_reapack->commitConfig(true); // runs tx + + for(PackageFromIndex& pkg : packages) + tx->installFromIndex(std::move(pkg)); + tx->runTasks(); +} + +void Manager::exportIndex() +{ + const std::string path = FileDialog::getSaveFileName(handle(), instance(), + "Export index", Path::root(), INDEX_FILTER, INDEX_EXT); + + if(Transaction *tx = g_reapack->setupTransaction()) { + tx->exportIndex(path); + tx->runTasks(); + } +} + void Manager::launchBrowser() { const auto promptApply = [this] { diff --git a/src/manager.hpp b/src/manager.hpp index 4f414337..7963c311 100644 --- a/src/manager.hpp +++ b/src/manager.hpp @@ -71,6 +71,8 @@ class Manager : public Dialog { void setupNetwork(); void importArchive(); void exportArchive(); + void importIndex(); + void exportIndex(); void aboutRepo(bool focus = true); void setChange(int); diff --git a/src/task.hpp b/src/task.hpp index 23cd0644..f4642b47 100644 --- a/src/task.hpp +++ b/src/task.hpp @@ -102,6 +102,24 @@ class InstallTask : public Task { std::unordered_set m_waiting; }; +struct PackageFromIndex { + Remote remote; + std::string name, category, version; + int flags; +}; + +class InstallFromIndexTask : public Task { +public: + InstallFromIndexTask(PackageFromIndex &&, Transaction *); + + bool start() override; + void commit() override; + +private: + PackageFromIndex pkg; + const Version * version = nullptr; +}; + class UninstallTask : public Task { public: UninstallTask(const Registry::Entry &, Transaction *); @@ -142,4 +160,13 @@ class ExportTask : public Task { TempPath m_path; }; +class ExportIndexTask : public Task { +public: + inline ExportIndexTask(std::string_view path, Transaction *tx): Task(tx), m_path(path) {} + +protected: + void commit() final; + std::string m_path; +}; + #endif diff --git a/src/transaction.cpp b/src/transaction.cpp index 4a50ca24..21379855 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -112,6 +112,11 @@ void Transaction::install(const Version *ver, const Registry::Entry &oldEntry, m_nextQueue.push(std::make_shared(ver, flags, oldEntry, reader, this)); } +void Transaction::installFromIndex(PackageFromIndex&& pkg) +{ + m_nextQueue.push(std::make_shared(std::move(pkg), this)); +} + void Transaction::setFlags(const Registry::Entry &entry, const int flags) { m_nextQueue.push(std::make_shared(entry, flags, this)); @@ -142,6 +147,11 @@ void Transaction::exportArchive(const std::string &path) m_nextQueue.push(std::make_shared(path, this)); } +void Transaction::exportIndex(std::string_view path) +{ + m_nextQueue.push(std::make_shared(path, this)); +} + bool Transaction::runTasks() { do { diff --git a/src/transaction.hpp b/src/transaction.hpp index 69cde980..bc5f2413 100644 --- a/src/transaction.hpp +++ b/src/transaction.hpp @@ -58,10 +58,12 @@ class Transaction { void install(const Version *, int flags = 0, const ArchiveReaderPtr & = nullptr); void install(const Version *, const Registry::Entry &oldEntry, int flags = false, const ArchiveReaderPtr & = nullptr); + void installFromIndex(PackageFromIndex&& pkg); void setFlags(const Registry::Entry &, int flags); void uninstall(const Remote &); void uninstall(const Registry::Entry &); void exportArchive(const std::string &path); + void exportIndex(std::string_view path); bool runTasks(); bool isCancelled() const { return m_isCancelled; } @@ -75,6 +77,7 @@ class Transaction { protected: friend SynchronizeTask; friend InstallTask; + friend InstallFromIndexTask; friend UninstallTask; IndexPtr loadIndex(const Remote &);