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:
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);