reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

commit 5e57f9adf0e5ce82f32dd8a93b19c716d6bbfe92
parent e1c78d21ea332398bf8ca2542863408ecaf6b274
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Tue, 23 Aug 2016 22:28:25 -0400

Merge branch 'task-redesign'

Diffstat:
Msrc/browser.cpp | 11+++--------
Msrc/reapack.cpp | 6++----
Msrc/receipt.cpp | 19++++++++++++-------
Msrc/receipt.hpp | 8+++++++-
Msrc/report.cpp | 3+--
Msrc/task.cpp | 143++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/task.hpp | 68++++++++++++++++++++++++++++----------------------------------------
Msrc/transaction.cpp | 244++++++++++++++++++++++++-------------------------------------------------------
Msrc/transaction.hpp | 29++++++++++++-----------------
9 files changed, 234 insertions(+), 297 deletions(-)

diff --git a/src/browser.cpp b/src/browser.cpp @@ -1012,19 +1012,14 @@ bool Browser::apply() const Version *target = *entry->target; if(target) - tx->install(target); + tx->install(target, entry->pin.value_or(false)); else tx->uninstall(entry->regEntry); entry->target = boost::none; } - - if(entry->pin) { - if(entry->regEntry) - tx->setPinned(entry->regEntry, *entry->pin); - else - tx->setPinned(entry->package, *entry->pin); - + else if(entry->pin) { + tx->setPinned(entry->regEntry, *entry->pin); entry->pin = boost::none; } } diff --git a/src/reapack.cpp b/src/reapack.cpp @@ -432,15 +432,13 @@ Transaction *ReaPack::setupTransaction() Dialog::Destroy(m_progress); m_progress = nullptr; - const Receipt &receipt = m_tx->receipt(); - - if(m_tx->isCancelled() || receipt.empty()) + if(m_tx->isCancelled() || m_tx->receipt()->empty()) return; LockDialog managerLock(m_manager); LockDialog browserLock(m_browser); - Dialog::Show<Report>(m_instance, m_mainWindow, receipt); + Dialog::Show<Report>(m_instance, m_mainWindow, *m_tx->receipt()); }); m_tx->setCleanupHandler([=] { diff --git a/src/receipt.cpp b/src/receipt.cpp @@ -17,7 +17,9 @@ #include "receipt.hpp" -#include "transaction.hpp" +#include "index.hpp" + +using namespace std; Receipt::Receipt() : m_enabled(false), m_needRestart(false) @@ -26,11 +28,11 @@ Receipt::Receipt() bool Receipt::empty() const { - return - m_installs.empty() && - m_updates.empty() && - m_removals.empty() && - m_errors.empty(); + return + m_installs.empty() && + m_updates.empty() && + m_removals.empty() && + m_errors.empty(); } void Receipt::addTicket(const InstallTicket &ticket) @@ -43,9 +45,12 @@ void Receipt::addTicket(const InstallTicket &ticket) m_installs.push_back(ticket); break; } + + m_indexes.insert(ticket.version->package()->category() + ->index()->shared_from_this()); } -void Receipt::addRemovals(const std::set<Path> &pathList) +void Receipt::addRemovals(const set<Path> &pathList) { m_removals.insert(pathList.begin(), pathList.end()); } diff --git a/src/receipt.hpp b/src/receipt.hpp @@ -20,18 +20,22 @@ #include <set> #include <string> +#include <unordered_set> #include <vector> #include "registry.hpp" +class Index; class Path; +typedef std::shared_ptr<const Index> IndexPtr; + struct InstallTicket { enum Type { Install, Upgrade }; Type type; const Version *version; - Registry::Entry regEntry; + Registry::Entry previous; }; class Receipt { @@ -68,6 +72,8 @@ private: std::vector<InstallTicket> m_updates; std::set<Path> m_removals; std::vector<Error> m_errors; + + std::unordered_set<IndexPtr> m_indexes; // keep them alive! }; #endif diff --git a/src/report.cpp b/src/report.cpp @@ -111,7 +111,6 @@ void Report::printUpdates() for(const InstallTicket &ticket : m_receipt.updates()) { const Package *pkg = ticket.version->package(); - const Registry::Entry &regEntry = ticket.regEntry; const VersionSet &versions = pkg->versions(); if(m_stream.tellp() != start) @@ -120,7 +119,7 @@ void Report::printUpdates() m_stream << pkg->fullName() << ":\r\n"; for(const Version *ver : versions | boost::adaptors::reversed) { - if(*ver <= regEntry.version) + if(*ver <= ticket.previous.version) break; m_stream << *ver; diff --git a/src/task.cpp b/src/task.cpp @@ -18,69 +18,70 @@ #include "task.hpp" #include "config.hpp" +#include "errors.hpp" #include "filesystem.hpp" -#include "source.hpp" +#include "index.hpp" #include "transaction.hpp" -#include "version.hpp" using namespace std; -Task::Task(Transaction *transaction) - : m_transaction(transaction), m_isCancelled(false) +Task::Task(Transaction *tx) : m_tx(tx) { } -void Task::start() +InstallTask::InstallTask(const Version *ver, const bool pin, + const Registry::Entry &re, Transaction *tx) + : Task(tx), m_version(ver), m_pin(pin), m_oldEntry(move(re)), + m_index(ver->package()->category()->index()->shared_from_this()) { - doStart(); } -void Task::commit() +bool InstallTask::start() { - if(m_isCancelled) - return; - - if(doCommit()) - m_onCommit(); -} - -void Task::rollback() -{ - m_isCancelled = true; + // get current files before overwriting the entry + m_oldFiles = tx()->registry()->getFiles(m_oldEntry); - // it's the transaction queue's job to abort the running downloads, not ours + // prevent file conflicts (don't worry, the registry push is reverted) + try { + vector<Path> conflicts; + tx()->registry()->push(m_version, &conflicts); - doRollback(); -} + if(!conflicts.empty()) { + for(const Path &path : conflicts) { + tx()->receipt()->addError({"Conflict: " + path.join() + + " is already owned by another package", m_version->fullName()}); + } -InstallTask::InstallTask(const Version *ver, - const vector<Registry::File> &oldFiles, Transaction *t) - : Task(t), m_version(ver), m_oldFiles(move(oldFiles)) -{ -} + return false; + } + } + catch(const reapack_error &e) { + tx()->receipt()->addError({e.what(), m_version->fullName()}); + return false; + } -void InstallTask::doStart() -{ const auto &sources = m_version->sources(); for(auto it = sources.begin(); it != sources.end();) { const Path &path = it->first; const Source *src = it->second; - const NetworkOpts &opts = transaction()->config()->network; + const NetworkOpts &opts = tx()->config()->network; Download *dl = new Download(src->fullName(), src->url(), opts); dl->onFinish(bind(&InstallTask::saveSource, this, dl, src)); - transaction()->downloadQueue()->push(dl); + tx()->downloadQueue()->push(dl); // skip duplicate files do { it++; } while(it != sources.end() && path == it->first); } + + return true; } void InstallTask::saveSource(Download *dl, const Source *src) { - if(isCancelled()) + if(dl->state() == Download::Aborted) return; const Path &targetPath = src->targetPath(); @@ -96,57 +97,101 @@ void InstallTask::saveSource(Download *dl, const Source *src) if(old != m_oldFiles.end()) m_oldFiles.erase(old); - if(!transaction()->saveFile(dl, tmpPath)) { + if(!tx()->saveFile(dl, tmpPath)) { rollback(); return; } } -bool InstallTask::doCommit() +void InstallTask::commit() { - for(const Registry::File &file : m_oldFiles) - FS::remove(file.path); - for(const PathGroup &paths : m_newFiles) { #ifdef _WIN32 + // TODO: rename to .old FS::remove(paths.target); #endif if(!FS::rename(paths.temp, paths.target)) { - transaction()->addError("Cannot rename to target: " + FS::lastError(), - paths.target.join()); + tx()->receipt()->addError({"Cannot rename to target: " + FS::lastError(), + paths.target.join()}); // it's a bit late to rollback here as some files might already have been // overwritten. at least we can delete the temporary files rollback(); - return false; + return; } } - return true; + for(const Registry::File &file : m_oldFiles) { + if(FS::remove(file.path)) + tx()->receipt()->addRemoval(file.path); + + if(file.main) + tx()->registerFile({false, m_oldEntry, file}); + } + + InstallTicket::Type type; + + if(m_oldEntry && m_oldEntry.version < *m_version) + type = InstallTicket::Upgrade; + else + type = InstallTicket::Install; + + tx()->receipt()->addTicket({type, m_version, m_oldEntry}); + + const Registry::Entry newEntry = tx()->registry()->push(m_version); + + if(newEntry.type == Package::ExtensionType) + tx()->receipt()->setRestartNeeded(true); + + if(m_pin) + tx()->registry()->setPinned(newEntry, true); + + tx()->registerAll(true, newEntry); } -void InstallTask::doRollback() +void InstallTask::rollback() { for(const PathGroup &paths : m_newFiles) FS::removeRecursive(paths.temp); +} - m_newFiles.clear(); +UninstallTask::UninstallTask(const Registry::Entry &re, Transaction *tx) + : Task(tx), m_entry(move(re)) +{ } -RemoveTask::RemoveTask(const vector<Path> &files, Transaction *t) - : Task(t), m_files(move(files)) +bool UninstallTask::start() { + tx()->registry()->getFiles(m_entry).swap(m_files); + + return true; } -bool RemoveTask::doCommit() +void UninstallTask::commit() { - for(const Path &path : m_files) { - if(FS::removeRecursive(path)) - m_removedFiles.insert(path); + for(const auto &file : m_files) { + if(!FS::exists(file.path)) + continue; + + if(FS::removeRecursive(file.path)) + tx()->receipt()->addRemoval(file.path); else - transaction()->addError(FS::lastError(), path.join()); + tx()->receipt()->addError({FS::lastError(), file.path.join()}); + + if(file.main) + tx()->registerFile({false, m_entry, file}); } - return true; + tx()->registry()->forget(m_entry); +} + +PinTask::PinTask(const Registry::Entry &re, const bool pin, Transaction *tx) + : Task(tx), m_entry(move(re)), m_pin(pin) +{ +} + +void PinTask::commit() +{ + tx()->registry()->setPinned(m_entry, m_pin); } diff --git a/src/task.hpp b/src/task.hpp @@ -21,55 +21,40 @@ #include "path.hpp" #include "registry.hpp" -#include <boost/signals2.hpp> #include <set> #include <vector> class Download; +class Index; class Source; class Transaction; class Version; +typedef std::shared_ptr<const Index> IndexPtr; + class Task { public: - typedef boost::signals2::signal<void ()> Signal; - typedef Signal::slot_type Callback; - Task(Transaction *parent); virtual ~Task() {} - void onCommit(const Callback &callback) { m_onCommit.connect(callback); } - bool isCancelled() const { return m_isCancelled; } - - void start(); - void commit(); - void rollback(); + virtual bool start() { return true; } + virtual void commit() = 0; + virtual void rollback() {} protected: - Transaction *transaction() const { return m_transaction; } - - virtual void doStart() = 0; - virtual bool doCommit() = 0; - virtual void doRollback() = 0; + Transaction *tx() const { return m_tx; } private: - Transaction *m_transaction; - bool m_isCancelled; - - Signal m_onCommit; + Transaction *m_tx; }; class InstallTask : public Task { public: - InstallTask(const Version *ver, const std::vector<Registry::File> &oldFiles, - Transaction *); + InstallTask(const Version *ver, bool pin, const Registry::Entry &, Transaction *); - const std::vector<Registry::File> &removedFiles() const { return m_oldFiles; } - -protected: - void doStart() override; - bool doCommit() override; - void doRollback() override; + bool start() override; + void commit() override; + void rollback() override; private: struct PathGroup { Path target; Path temp; }; @@ -77,34 +62,37 @@ private: void saveSource(Download *, const Source *); const Version *m_version; + bool m_pin; + Registry::Entry m_oldEntry; + IndexPtr m_index; // keep in memory std::vector<Registry::File> m_oldFiles; std::vector<PathGroup> m_newFiles; }; -class RemoveTask : public Task { +class UninstallTask : public Task { public: - RemoveTask(const std::vector<Path> &files, Transaction *); - - const std::set<Path> &removedFiles() const { return m_removedFiles; } + UninstallTask(const Registry::Entry &, Transaction *); protected: - void doStart() override {} - bool doCommit() override; - void doRollback() override {} + bool start() override; + void commit() override; private: - std::vector<Path> m_files; + Registry::Entry m_entry; + std::vector<Registry::File> m_files; std::set<Path> m_removedFiles; }; -class DummyTask : public Task { +class PinTask : public Task { public: - DummyTask(Transaction *t) : Task(t) {} + PinTask(const Registry::Entry &, bool pin, Transaction *); protected: - void doStart() override {} - bool doCommit() override { return true; } - void doRollback() override {} + void commit() override; + +private: + Registry::Entry m_entry; + bool m_pin; }; #endif diff --git a/src/transaction.cpp b/src/transaction.cpp @@ -40,25 +40,11 @@ Transaction::Transaction(Config *config) m_downloadQueue.onAbort([=] { m_isCancelled = true; - - // clear the registration queue queue<HostTicket>().swap(m_regQueue); - - for(const TaskPtr &task : m_tasks) - task->rollback(); - - // some downloads may run for a few ms more - if(m_downloadQueue.idle()) - finish(); }); // run tasks after fetching indexes - m_downloadQueue.onDone([=] { - if(m_tasks.empty()) - finish(); - else - runTasks(); - }); + m_downloadQueue.onDone(bind(&Transaction::runTasks, this)); } void Transaction::synchronize(const Remote &remote, @@ -82,7 +68,7 @@ void Transaction::synchronize(const Remote &remote, } catch(const reapack_error &e) { // index file is invalid (load error) - addError(e.what(), remote.name()); + m_receipt.addError({e.what(), remote.name()}); return; } @@ -112,7 +98,7 @@ void Transaction::synchronize(const Package *pkg, const InstallOpts &opts) else if(regEntry.pinned || *latest < regEntry.version) return; - install(latest, regEntry); + m_currentQueue.push(make_shared<InstallTask>(latest, false, regEntry, this)); } void Transaction::fetchIndex(const Remote &remote, const function<void()> &cb) @@ -132,71 +118,10 @@ void Transaction::fetchIndex(const Remote &remote, const function<void()> &cb) }); } -void Transaction::install(const Version *ver) +void Transaction::install(const Version *ver, const bool pin) { - install(ver, m_registry.getEntry(ver->package())); -} - -void Transaction::install(const Version *ver, - const Registry::Entry &regEntry) -{ - InstallTicket::Type type; - - if(regEntry && regEntry.version < *ver) - type = InstallTicket::Upgrade; - else - type = InstallTicket::Install; - - // get current files before overwriting the entry - const auto &currentFiles = m_registry.getFiles(regEntry); - - // prevent file conflicts (don't worry, the registry push is reverted in runTasks) - try { - vector<Path> conflicts; - m_registry.push(ver, &conflicts); - - if(!conflicts.empty()) { - for(const Path &path : conflicts) { - addError("Conflict: " + path.join() + - " is already owned by another package", - ver->fullName()); - } - - return; - } - } - catch(const reapack_error &e) { - // handle database error from Registry::push - addError(e.what(), ver->fullName()); - return; - } - - // all green! (pronounce with a japanese accent) - IndexPtr ri = ver->package()->category()->index()->shared_from_this(); - if(!m_indexes.count(ri)) - m_indexes.insert(ri); - - auto task = make_shared<InstallTask>(ver, currentFiles, this); - - task->onCommit([=] { - m_receipt.addTicket({type, ver, regEntry}); - - for(const Registry::File &file : task->removedFiles()) { - m_receipt.addRemoval(file.path); - - if(file.main) - m_regQueue.push({false, regEntry, file}); - } - - const Registry::Entry newEntry = m_registry.push(ver); - - if(newEntry.type == Package::ExtensionType) - m_receipt.setRestartNeeded(true); - - registerInHost(true, newEntry); - }); - - addTask(task); + const auto &oldEntry = m_registry.getEntry(ver->package()); + m_currentQueue.push(make_shared<InstallTask>(ver, pin, oldEntry, this)); } void Transaction::registerAll(const Remote &remote) @@ -204,34 +129,15 @@ void Transaction::registerAll(const Remote &remote) const vector<Registry::Entry> &entries = m_registry.getEntries(remote.name()); for(const auto &entry : entries) - registerInHost(remote.isEnabled(), entry); + registerAll(remote.isEnabled(), entry); if(!remote.isEnabled()) inhibit(remote); } -void Transaction::setPinned(const Package *pkg, const bool pinned) -{ - // pkg may or may not be installed yet at this point, - // waiting for the install task to be commited before querying the registry - - auto task = make_shared<DummyTask>(this); - - task->onCommit([=] { - const Registry::Entry &entry = m_registry.getEntry(pkg); - if(entry) - m_registry.setPinned(entry, pinned); - }); - - addTask(task); -} - void Transaction::setPinned(const Registry::Entry &entry, const bool pinned) { - auto task = make_shared<DummyTask>(this); - task->onCommit(bind(&Registry::setPinned, m_registry, entry, pinned)); - - addTask(task); + m_currentQueue.push(make_shared<PinTask>(entry, pinned, this)); } void Transaction::uninstall(const Remote &remote) @@ -242,121 +148,119 @@ void Transaction::uninstall(const Remote &remote) if(FS::exists(indexPath)) { if(!FS::remove(indexPath)) - addError(FS::lastError(), indexPath.join()); + m_receipt.addError({FS::lastError(), indexPath.join()}); } - const vector<Registry::Entry> &entries = m_registry.getEntries(remote.name()); - - if(entries.empty()) - return; - - for(const auto &entry : entries) + for(const auto &entry : m_registry.getEntries(remote.name())) uninstall(entry); } void Transaction::uninstall(const Registry::Entry &entry) { - vector<Path> files; - - for(const Registry::File &file : m_registry.getFiles(entry)) { - if(FS::exists(file.path)) - files.push_back(file.path); - if(file.main) - m_regQueue.push({false, entry, file}); - } - - auto task = make_shared<RemoveTask>(files, this); - - task->onCommit([=] { - m_registry.forget(entry); - m_receipt.addRemovals(task->removedFiles()); - }); - - addTask(task); + m_currentQueue.push(make_shared<UninstallTask>(entry, this)); } bool Transaction::saveFile(Download *dl, const Path &path) { if(dl->state() != Download::Success) { - addError(dl->contents(), dl->url()); + m_receipt.addError({dl->contents(), dl->url()}); return false; } if(!FS::write(path, dl->contents())) { - addError(FS::lastError(), path.join()); + m_receipt.addError({FS::lastError(), path.join()}); return false; } return true; } -void Transaction::finish() +bool Transaction::runTasks() { - // called when the download queue is done, or if there is nothing to do + if(!m_currentQueue.empty()) { + m_taskQueues.push(m_currentQueue); + queue<TaskPtr>().swap(m_currentQueue); + } - if(!m_isCancelled) { - for(const TaskPtr &task : m_tasks) - task->commit(); + // do nothing if there are running tasks + if(!commitTasks()) + return false; - m_registry.commit(); + while(!m_taskQueues.empty()) { + m_registry.savepoint(); - registerQueued(); - } + TaskQueue &queue = m_taskQueues.front(); - assert(m_downloadQueue.idle()); - assert(m_taskQueue.empty()); - assert(m_regQueue.empty()); + while(!queue.empty()) { + const TaskPtr &task = queue.front(); - m_onFinish(); - m_cleanupHandler(); -} + if(task->start()) + m_runningTasks.push(task); -void Transaction::addError(const string &message, const string &title) -{ - m_receipt.addError({message, title}); -} + queue.pop(); + } -bool Transaction::allFilesExists(const set<Path> &list) const -{ - for(const Path &path : list) { - if(!FS::exists(path)) + m_registry.restore(); + m_taskQueues.pop(); + + if(!commitTasks()) return false; } + finish(); return true; } -void Transaction::addTask(const TaskPtr &task) +bool Transaction::commitTasks() { - m_tasks.push_back(task); - m_taskQueue.push(task.get()); + // wait until all running tasks are ready + if(!m_downloadQueue.idle()) + return false; + + // finish current tasks + while(!m_runningTasks.empty()) { + if(m_isCancelled) + m_runningTasks.front()->rollback(); + else + m_runningTasks.front()->commit(); + + m_runningTasks.pop(); + } + + return true; } -bool Transaction::runTasks() +void Transaction::finish() { - m_registry.restore(); - - while(!m_taskQueue.empty()) { - m_taskQueue.front()->start(); - m_taskQueue.pop(); + if(!m_isCancelled) { + m_registry.commit(); + registerQueued(); } - // get ready for new tasks - m_registry.savepoint(); + assert(m_downloadQueue.idle()); + assert(m_currentQueue.empty()); + assert(m_taskQueues.empty()); + assert(m_regQueue.empty()); - // return false if transaction is still in progress - if(!m_downloadQueue.idle()) - return false; + m_onFinish(); + m_cleanupHandler(); +} + +bool Transaction::allFilesExists(const set<Path> &list) const +{ + for(const Path &path : list) { + if(!FS::exists(path)) + return false; + } - finish(); return true; } -void Transaction::registerInHost(const bool add, const Registry::Entry &entry) +void Transaction::registerAll(const bool add, const Registry::Entry &entry) { // don't actually do anything until commit() – which will calls registerQueued for(const Registry::File &file : m_registry.getMainFiles(entry)) - m_regQueue.push({add, entry, file}); + registerFile({add, entry, file}); } void Transaction::registerQueued() @@ -401,8 +305,10 @@ void Transaction::registerScript(const HostTicket &reg) const string &fullPath = Path::prefixRoot(reg.file.path).join(); const bool isLast = m_regQueue.size() == 1; - if(!AddRemoveReaScript(reg.add, section, fullPath.c_str(), isLast) && reg.add) - addError("This script could not be registered in REAPER.", reg.file.path.join()); + if(!AddRemoveReaScript(reg.add, section, fullPath.c_str(), isLast) && reg.add) { + m_receipt.addError({"This script could not be registered in REAPER.", + reg.file.path.join()}); + } } void Transaction::inhibit(const Remote &remote) diff --git a/src/transaction.hpp b/src/transaction.hpp @@ -30,14 +30,13 @@ #include <unordered_set> class Config; -class Index; class Path; class Remote; class Task; struct InstallOpts; -typedef std::shared_ptr<const Index> IndexPtr; typedef std::shared_ptr<Task> TaskPtr; +typedef std::queue<TaskPtr> TaskQueue; struct HostTicket { bool add; Registry::Entry entry; Registry::File file; }; @@ -53,8 +52,7 @@ public: void synchronize(const Remote &, boost::optional<bool> forceAutoInstall = boost::none); - void install(const Version *); - void setPinned(const Package *, bool pinned); + void install(const Version *, bool pin = false); void setPinned(const Registry::Entry &, bool pinned); void uninstall(const Remote &); void uninstall(const Registry::Entry &); @@ -62,28 +60,25 @@ public: bool runTasks(); bool isCancelled() const { return m_isCancelled; } - const Receipt &receipt() const { return m_receipt; } - DownloadQueue *downloadQueue() { return &m_downloadQueue; } + Receipt *receipt() { return &m_receipt; } + Registry *registry() { return &m_registry; } const Config *config() { return m_config; } + DownloadQueue *downloadQueue() { return &m_downloadQueue; } + void registerAll(bool add, const Registry::Entry &); + void registerFile(const HostTicket &t) { m_regQueue.push(t); } bool saveFile(Download *, const Path &); - void addError(const std::string &msg, const std::string &title); private: void fetchIndex(const Remote &, const std::function<void ()> &); void synchronize(const Package *, const InstallOpts &); - void install(const Version *, const Registry::Entry &); - void addTask(const TaskPtr &); - bool allFilesExists(const std::set<Path> &) const; - - void registerInHost(bool add, const Registry::Entry &); void registerQueued(); void registerScript(const HostTicket &); - - void finish(); void inhibit(const Remote &); + bool commitTasks(); + void finish(); bool m_isCancelled; const Config *m_config; @@ -92,11 +87,11 @@ private: std::unordered_set<std::string> m_syncedRemotes; std::unordered_set<std::string> m_inhibited; - std::unordered_set<IndexPtr> m_indexes; - std::vector<TaskPtr> m_tasks; DownloadQueue m_downloadQueue; - std::queue<Task *> m_taskQueue; + TaskQueue m_currentQueue; + TaskQueue m_runningTasks; + std::queue<TaskQueue> m_taskQueues; std::queue<HostTicket> m_regQueue; VoidSignal m_onFinish;