reapack

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

commit 34ee7554c503b841fd9b9bf972b66216ddcfe6e4
parent 3d8db4854e4c48f622351db0d4348b925cc2f59a
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Sun,  6 Dec 2015 23:48:52 -0500

show a report dialog when a transaction finishes

Diffstat:
Msrc/database.cpp | 9++++++++-
Msrc/database.hpp | 1+
Msrc/dialog.cpp | 20+++++++++++++++++---
Msrc/dialog.hpp | 23++++++++++++++++++++---
Msrc/package.cpp | 12+++++++++++-
Msrc/package.hpp | 1+
Msrc/progress.cpp | 2+-
Msrc/reapack.cpp | 3++-
Msrc/registry.cpp | 6+++---
Msrc/registry.hpp | 8+++++++-
Asrc/report.cpp | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/report.hpp | 24++++++++++++++++++++++++
Msrc/resource.hpp | 2++
Msrc/resource.rc | 15+++++++++++++--
Msrc/transaction.cpp | 53+++++++++++++++++++++++++++++------------------------
Msrc/transaction.hpp | 24++++++++++++++++++------
Msrc/version.cpp | 9++++++++-
Msrc/version.hpp | 8++++++++
Mtest/database.cpp | 10++++++++++
Mtest/package.cpp | 17+++++++++++++++++
Mtest/registry.cpp | 11++++++-----
Mtest/version.cpp | 23++++++++++++++++++++++-
22 files changed, 311 insertions(+), 53 deletions(-)

diff --git a/src/database.cpp b/src/database.cpp @@ -4,6 +4,8 @@ #include <WDL/tinyxml/tinyxml.h> +using namespace std; + Database *Database::load(const char *file) { TiXmlDocument doc(file); @@ -49,7 +51,7 @@ void Database::addCategory(Category *cat) cat->packages().begin(), cat->packages().end()); } -Category::Category(const std::string &name) +Category::Category(const string &name) : m_database(0), m_name(name) { if(m_name.empty()) @@ -62,6 +64,11 @@ Category::~Category() delete pack; } +string Category::fullName() const +{ + return m_database ? m_database->name() + "/" + m_name : m_name; +} + void Category::addPackage(Package *pack) { if(pack->type() == Package::UnknownType) diff --git a/src/database.hpp b/src/database.hpp @@ -44,6 +44,7 @@ public: ~Category(); const std::string &name() const { return m_name; } + std::string fullName() const; void setDatabase(Database *db) { m_database = db; } Database *database() const { return m_database; } diff --git a/src/dialog.cpp b/src/dialog.cpp @@ -55,12 +55,21 @@ Dialog::Dialog(const int templateId) // can't call reimplemented virtual methods here during object construction } -void Dialog::init(REAPER_PLUGIN_HINSTANCE instance, HWND parent) +INT_PTR Dialog::init(REAPER_PLUGIN_HINSTANCE inst, HWND parent, Modality mode) { m_parent = parent; - CreateDialogParam(instance, MAKEINTRESOURCE(m_template), - m_parent, Proc, (LPARAM)this); + switch(mode) { + case Modeless: + CreateDialogParam(inst, MAKEINTRESOURCE(m_template), + m_parent, Proc, (LPARAM)this); + return true; + case Modal: + return DialogBoxParam(inst, MAKEINTRESOURCE(m_template), + m_parent, Proc, (LPARAM)this); + } + + return false; // makes MSVC happy. } void Dialog::Destroy(Dialog *dlg) @@ -110,6 +119,11 @@ void Dialog::setEnabled(const bool enabled) EnableWindow(m_handle, enabled); } +HWND Dialog::getItem(const int idc) +{ + return GetDlgItem(m_handle, idc); +} + void Dialog::onInit() { } diff --git a/src/dialog.hpp b/src/dialog.hpp @@ -11,18 +11,33 @@ typedef std::map<HWND, Dialog *> DialogMap; class Dialog { public: - template<typename T> + enum Modality { + Modeless, + Modal, + }; + + template<class T> static T *Create(REAPER_PLUGIN_HINSTANCE instance, HWND parent) { Dialog *dlg = new T(); - dlg->init(instance, parent); + dlg->init(instance, parent, Dialog::Modeless); return dynamic_cast<T *>(dlg); } + template<class T, class... Args> + static INT_PTR Show(REAPER_PLUGIN_HINSTANCE i, HWND parent, Args&&... args) + { + Dialog *dlg = new T(args...); + INT_PTR ret = dlg->init(i, parent, Dialog::Modal); + Destroy(dlg); + + return ret; + } + static void Destroy(Dialog *); - void init(REAPER_PLUGIN_HINSTANCE, HWND); + INT_PTR init(REAPER_PLUGIN_HINSTANCE, HWND, const Modality); HWND handle() const { return m_handle; } @@ -37,6 +52,8 @@ protected: Dialog(const int templateId); virtual ~Dialog(); + HWND getItem(const int idc); + virtual void onInit(); virtual void onShow(); virtual void onHide(); diff --git a/src/package.cpp b/src/package.cpp @@ -3,6 +3,10 @@ #include "database.hpp" #include "errors.hpp" +#include <sstream> + +using namespace std; + Package::Type Package::convertType(const char *type) { if(!strcmp(type, "script")) @@ -11,13 +15,18 @@ Package::Type Package::convertType(const char *type) return UnknownType; } -Package::Package(const Type type, const std::string &name) +Package::Package(const Type type, const string &name) : m_category(0), m_type(type), m_name(name) { if(m_name.empty()) throw reapack_error("empty package name"); } +string Package::fullName() const +{ + return m_category ? m_category->fullName() + "/" + m_name : m_name; +} + Package::~Package() { for(Version *ver : m_versions) @@ -29,6 +38,7 @@ void Package::addVersion(Version *ver) if(ver->sources().empty()) return; + ver->setPackage(this); m_versions.insert(ver); } diff --git a/src/package.hpp b/src/package.hpp @@ -26,6 +26,7 @@ public: Type type() const { return m_type; } const std::string &name() const { return m_name; } + std::string fullName() const; void addVersion(Version *ver); const VersionSet &versions() const { return m_versions; } diff --git a/src/progress.cpp b/src/progress.cpp @@ -33,7 +33,7 @@ void Progress::setTransaction(Transaction *t) void Progress::onInit() { - m_label = GetDlgItem(handle(), IDC_LABEL); + m_label = getItem(IDC_LABEL); m_progress = GetDlgItem(handle(), IDC_PROGRESS); } diff --git a/src/reapack.cpp b/src/reapack.cpp @@ -2,6 +2,7 @@ #include "config.hpp" #include "progress.hpp" +#include "report.hpp" #include "transaction.hpp" #include <reaper_plugin_functions.h> @@ -139,7 +140,7 @@ Transaction *ReaPack::createTransaction() if(m_transaction->packages().empty()) ShowMessageBox("Nothing to do!", "ReaPack", 0); else - ShowMessageBox("Synchronization complete!", "ReaPack", 0); + Dialog::Show<Report>(m_instance, m_mainWindow, m_transaction); m_progress->setEnabled(true); m_progress->setTransaction(0); diff --git a/src/registry.cpp b/src/registry.cpp @@ -20,13 +20,13 @@ void Registry::push(const std::string &key, const std::string &value) m_map[key] = value; } -string Registry::versionOf(Package *pkg) const +Registry::Status Registry::query(Package *pkg) const { const string key = pkg->targetPath().join(); const auto it = m_map.find(key); if(it == m_map.end()) - return std::string(); + return Uninstalled; - return it->second; + return it->second == pkg->lastVersion()->name() ? UpToDate : UpdateAvailable; } diff --git a/src/registry.hpp b/src/registry.hpp @@ -10,11 +10,17 @@ class Registry { public: typedef std::map<std::string, std::string> Map; + enum Status { + UpToDate, + UpdateAvailable, + Uninstalled, + }; + void push(Package *pkg); void push(const std::string &key, const std::string &value); size_t size() const { return m_map.size(); } - std::string versionOf(Package *pkg) const; + Status query(Package *pkg) const; Map::const_iterator begin() const { return m_map.begin(); } Map::const_iterator end() const { return m_map.end(); } diff --git a/src/report.cpp b/src/report.cpp @@ -0,0 +1,83 @@ +#include "report.hpp" + +#include "resource.hpp" +#include "transaction.hpp" + +#include <sstream> + +using namespace std; + +static const string SEP(10, '='); + +Report::Report(Transaction *transaction) + : Dialog(IDD_REPORT_DIALOG), m_transaction(transaction) +{ +} + +void Report::onInit() +{ + const size_t newPacks = m_transaction->newPackages().size(); + const size_t updates = m_transaction->updates().size(); + const size_t errors = m_transaction->errors().size(); + + ostringstream text; + + text + << newPacks << " new packages, " + << updates << " updates and " + << errors << " errors" + << "\n" + ; + + if(errors) + formatErrors(text); + + if(newPacks) + formatErrors(text); + + if(updates) + formatUpdates(text); + + SetDlgItemText(handle(), IDC_REPORT, text.str().c_str()); +} + +void Report::onCommand(WPARAM wParam, LPARAM) +{ + const int commandId = LOWORD(wParam); + + switch(commandId) { + case IDOK: + case IDCANCEL: + EndDialog(handle(), true); + break; + } +} + +void Report::formatNewPackages(ostringstream &text) +{ + text << "\n" << SEP << " New packages: " << SEP << "\n"; + + for(Package *pkg : m_transaction->newPackages()) + text << "\n- " << pkg->lastVersion()->fullName() << "\n"; +} + +void Report::formatUpdates(ostringstream &text) +{ + text << "\n" << SEP << " Updates: " << SEP << "\n"; + + for(Package *pkg : m_transaction->updates()) { + Version *ver = pkg->lastVersion(); + text << "\n- " << ver->fullName() << "\n"; + + if(!ver->changelog().empty()) + text << ver->changelog() << "\n"; + } +} + +void Report::formatErrors(ostringstream &text) +{ + text << "\n" << SEP << " Errors: " << SEP << "\n"; + + for(const string &msg : m_transaction->errors()) + text << "\n- " << msg << "\n"; +} diff --git a/src/report.hpp b/src/report.hpp @@ -0,0 +1,24 @@ +#ifndef REAPACK_REPORT_HPP +#define REAPACK_REPORT_HPP + +#include "dialog.hpp" + +class Transaction; + +class Report : public Dialog { +public: + Report(Transaction *); + +protected: + void onInit() override; + void onCommand(WPARAM, LPARAM) override; + +private: + void formatNewPackages(std::ostringstream &); + void formatUpdates(std::ostringstream &); + void formatErrors(std::ostringstream &); + + Transaction *m_transaction; +}; + +#endif diff --git a/src/resource.hpp b/src/resource.hpp @@ -8,8 +8,10 @@ #endif #define IDD_PROGRESS_DIALOG 100 +#define IDD_REPORT_DIALOG 101 #define IDC_LABEL 200 #define IDC_PROGRESS 201 +#define IDC_REPORT 202 #endif diff --git a/src/resource.rc b/src/resource.rc @@ -4,11 +4,22 @@ #include "winres.h" #endif -IDD_PROGRESS_DIALOG DIALOGEX 0, 0, 260, 85 +IDD_PROGRESS_DIALOG DIALOGEX 0, 0, 260, 80 STYLE DS_MODALFRAME | DS_SHELLFONT | WS_POPUP | WS_SYSMENU | WS_CAPTION FONT 8, "MS Shell Dlg" BEGIN LTEXT "File Name", IDC_LABEL, 5, 5, 250, 30 - CONTROL "", IDC_PROGRESS, PROGRESS_CLASS, 0x0, 5, 35, 250, 11 + CONTROL "", IDC_PROGRESS, PROGRESS_CLASS, 0x0, 5, 40, 250, 11 PUSHBUTTON "Cancel", IDCANCEL, 105, 60, 50, 14, NOT WS_TABSTOP END + +IDD_REPORT_DIALOG DIALOGEX 0, 0, 240, 260 +STYLE DS_MODALFRAME | DS_SHELLFONT | WS_POPUP | WS_SYSMENU | WS_CAPTION +CAPTION "ReaPack" +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "Synchronization complete!", IDC_LABEL, 5, 5, 230, 10 + EDITTEXT IDC_REPORT, 6, 18, 228, 215, WS_VSCROLL | ES_MULTILINE | + ES_READONLY | NOT WS_TABSTOP + DEFPUSHBUTTON "OK", IDOK, 95, 240, 50, 14 +END diff --git a/src/transaction.cpp b/src/transaction.cpp @@ -9,7 +9,7 @@ using namespace std; Transaction::Transaction(Registry *reg, const Path &root) - : m_registry(reg), m_root(root) + : m_registry(reg), m_root(root), m_new(0), m_updates(0) { m_dbPath = m_root + "ReaPack"; RecursiveCreateDirectory(m_dbPath.join().c_str(), 0); @@ -32,7 +32,7 @@ void Transaction::fetch(const Remote &remote) Download *dl = new Download(remote.first, remote.second); dl->onFinish([=]() { if(dl->status() != 200) { - addError(dl->contents(), dl->name()); + addError(dl->contents(), dl->url()); return; } @@ -40,7 +40,7 @@ void Transaction::fetch(const Remote &remote) ofstream file(path.join()); if(file.bad()) { - addError(strerror(errno), dl->name()); + addError(strerror(errno), path.join()); return; } @@ -54,7 +54,7 @@ void Transaction::fetch(const Remote &remote) m_databases.push_back(db); } catch(const reapack_error &e) { - addError(e.what(), dl->name()); + addError(e.what(), dl->url()); } }); @@ -72,30 +72,30 @@ void Transaction::prepare() for(Database *db : m_databases) { for(Package *pkg : db->packages()) { - bool hasLatest = m_registry->versionOf(pkg) == pkg->lastVersion()->name(); + Registry::Status status = m_registry->query(pkg); bool exists = file_exists(installPath(pkg).join().c_str()); - if(!hasLatest || !exists) - m_packages.push_back(pkg); + if(status == Registry::UpToDate && exists) + continue; + + m_packages.push_back({pkg, status}); } } - if(m_packages.empty()) { + if(m_packages.empty()) finish(); - return; - } - - m_onReady(); + else + m_onReady(); } void Transaction::run() { - for(Package *pkg : m_packages) { + for(const PackageEntry &pkg : m_packages) { try { install(pkg); } catch(const reapack_error &e) { - addError(e.what(), pkg->name()); + addError(e.what(), pkg.first->fullName()); } } } @@ -105,18 +105,18 @@ void Transaction::cancel() m_queue.abort(); } -void Transaction::install(Package *pkg) +void Transaction::install(const PackageEntry &pkgEntry) { + Package *pkg = pkgEntry.first; + Version *ver = pkg->lastVersion(); + const Path path = installPath(pkg); - const string &url = pkg->lastVersion()->source(0)->url(); - const string dbName = pkg->category()->database()->name(); - const string name = dbName + "\n" + pkg->name() + - " v" + pkg->lastVersion()->name(); + const Registry::Status status = pkgEntry.second; - Download *dl = new Download(name, url); + Download *dl = new Download(ver->fullName(), ver->source(0)->url()); dl->onFinish([=] { if(dl->status() != 200) { - addError(dl->contents(), dl->name()); + addError(dl->contents(), dl->url()); return; } @@ -124,20 +124,25 @@ void Transaction::install(Package *pkg) ofstream file(path.join()); if(file.bad()) { - addError(strerror(errno), dl->name()); + addError(strerror(errno), path.join()); return; } file << dl->contents(); file.close(); + if(status == Registry::UpdateAvailable) + m_updates.push_back(pkg); + else + m_new.push_back(pkg); + m_registry->push(pkg); }); m_queue.push(dl); // execute finish after the download is deleted - // this prevents the download queue from being deleted before the download + // this prevents the download queue from being deleted before the download is dl->onFinish(bind(&Transaction::finish, this)); } @@ -156,5 +161,5 @@ void Transaction::finish() void Transaction::addError(const string &message, const string &title) { - // ShowMessageBox(message.c_str(), title.c_str(), 0); + m_errors.push_back(title + ":\n" + message); } diff --git a/src/transaction.hpp b/src/transaction.hpp @@ -9,11 +9,16 @@ #include <boost/signals2.hpp> +typedef std::vector<std::string> ErrorList; + class Transaction { public: typedef boost::signals2::signal<void ()> Signal; typedef Signal::slot_type Callback; + typedef std::pair<Package *, Registry::Status> PackageEntry; + typedef std::vector<PackageEntry> PackageEntryList; + Transaction(Registry *reg, const Path &root); ~Transaction(); @@ -26,26 +31,33 @@ public: void run(); void cancel(); - const PackageList &packages() const { return m_packages; } DownloadQueue *downloadQueue() { return &m_queue; } + const PackageEntryList &packages() const { return m_packages; } + const PackageList &newPackages() const { return m_new; } + const PackageList &updates() const { return m_updates; } + const ErrorList &errors() const { return m_errors; } + private: void prepare(); void finish(); - void install(Package *); + void install(const PackageEntry &); void addError(const std::string &msg, const std::string &title); Path installPath(Package *) const; Registry *m_registry; - DatabaseList m_databases; - PackageList m_packages; + Path m_root; + Path m_dbPath; + DatabaseList m_databases; DownloadQueue m_queue; + PackageEntryList m_packages; + PackageList m_new; + PackageList m_updates; + ErrorList m_errors; - Path m_root; - Path m_dbPath; Signal m_onReady; Signal m_onFinish; diff --git a/src/version.cpp b/src/version.cpp @@ -1,6 +1,7 @@ #include "version.hpp" #include "errors.hpp" +#include "package.hpp" #include <algorithm> #include <cmath> @@ -9,7 +10,7 @@ using namespace std; Version::Version(const std::string &str) - : m_name(str), m_code(0) + : m_name(str), m_code(0), m_package(0) { static const regex pattern("(\\d+)"); @@ -40,6 +41,12 @@ Version::~Version() delete source; } +string Version::fullName() const +{ + const string fName = "v" + m_name; + return m_package ? m_package->fullName() + " " + fName : fName; +} + void Version::addSource(Source *source) { const Source::Platform p = source->platform(); diff --git a/src/version.hpp b/src/version.hpp @@ -8,14 +8,20 @@ class Source; typedef std::vector<Source *> SourceList; +class Package; + class Version { public: Version(const std::string &); ~Version(); const std::string &name() const { return m_name; } + std::string fullName() const; size_t code() const { return m_code; } + void setPackage(Package *pkg) { m_package = pkg; } + Package *package() const { return m_package; } + void setChangelog(const std::string &); const std::string &changelog() const { return m_changelog; } @@ -29,6 +35,8 @@ private: std::string m_name; size_t m_code; + Package *m_package; + std::string m_changelog; SourceList m_sources; }; diff --git a/test/database.cpp b/test/database.cpp @@ -142,3 +142,13 @@ TEST_CASE("empty category name", M) { REQUIRE(string(e.what()) == "empty category name"); } } + +TEST_CASE("category full name", M) { + Category cat("Category Name"); + REQUIRE(cat.fullName() == "Category Name"); + + Database db; + db.setName("Database Name"); + cat.setDatabase(&db); + REQUIRE(cat.fullName() == "Database Name/Category Name"); +} diff --git a/test/package.cpp b/test/package.cpp @@ -36,6 +36,7 @@ TEST_CASE("package versions are sorted", M) { alpha->addSource(sourceB); pack.addVersion(final); + REQUIRE(final->package() == &pack); CHECK(pack.versions().size() == 1); pack.addVersion(alpha); @@ -103,3 +104,19 @@ TEST_CASE("script target path without category", M) { REQUIRE(string(e.what()) == "category or database is unset"); } } + +TEST_CASE("full name", M) { + Database db; + db.setName("Database Name"); + + Category cat("Category Name"); + + Package pack(Package::ScriptType, "file.name"); + REQUIRE(pack.fullName() == "file.name"); + + pack.setCategory(&cat); + REQUIRE(pack.fullName() == "Category Name/file.name"); + + cat.setDatabase(&db); + REQUIRE(pack.fullName() == "Database Name/Category Name/file.name"); +} diff --git a/test/registry.cpp b/test/registry.cpp @@ -20,19 +20,19 @@ static const char *M = "[registry]"; ver->addSource(src); \ pkg.addVersion(ver); -TEST_CASE("version of uninstalled", M) { +TEST_CASE("query uninstalled package", M) { MAKE_PACKAGE Registry reg; - REQUIRE(reg.versionOf(&pkg) == string()); + REQUIRE(reg.query(&pkg) == Registry::Uninstalled); } -TEST_CASE("version of installed", M) { +TEST_CASE("query up to date pacakge", M) { MAKE_PACKAGE Registry reg; reg.push(&pkg); - REQUIRE(reg.versionOf(&pkg) == "1.0"); + REQUIRE(reg.query(&pkg) == Registry::UpToDate); } TEST_CASE("bump version", M) { @@ -44,6 +44,7 @@ TEST_CASE("bump version", M) { Registry reg; reg.push(&pkg); pkg.addVersion(ver2); + REQUIRE(reg.query(&pkg) == Registry::UpdateAvailable); reg.push(&pkg); - REQUIRE(reg.versionOf(&pkg) == "2.0"); + REQUIRE(reg.query(&pkg) == Registry::UpToDate); } diff --git a/test/version.cpp b/test/version.cpp @@ -1,8 +1,11 @@ #include <catch.hpp> -#include <errors.hpp> #include <version.hpp> +#include <database.hpp> +#include <errors.hpp> +#include <package.hpp> + #include <string> using namespace std; @@ -76,6 +79,24 @@ TEST_CASE("version with 5 components", M) { } } +TEST_CASE("version full name", M) { + Version ver("1.0"); + REQUIRE(ver.fullName() == "v1.0"); + + Package pkg(Package::UnknownType, "file.name"); + ver.setPackage(&pkg); + REQUIRE(ver.fullName() == "file.name v1.0"); + + Category cat("Category Name"); + pkg.setCategory(&cat); + REQUIRE(ver.fullName() == "Category Name/file.name v1.0"); + + Database db; + db.setName("Database Name"); + cat.setDatabase(&db); + REQUIRE(ver.fullName() == "Database Name/Category Name/file.name v1.0"); +} + TEST_CASE("convert platforms", M) { SECTION("unknown") { REQUIRE(Source::convertPlatform("hello") == Source::UnknownPlatform);