reapack

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

commit 4dd103cbb07beacfa14394b979e0f4ff2a10c891
parent 9a8ef6677e6d3541f0138598f717043e81dab8b6
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Sat,  5 Dec 2015 21:07:40 -0500

show a progress dialog when running a transaction

Diffstat:
M.gitignore | 1+
MTupfile | 2+-
MTupfile.osx | 9+++++++--
Asrc/dialog.cpp | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/dialog.hpp | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/download.cpp | 21++++++++++-----------
Msrc/download.hpp | 16++++++++++++++--
Msrc/main.cpp | 16++++++++++++++--
Msrc/package.hpp | 1-
Asrc/progress.cpp | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/progress.hpp | 32++++++++++++++++++++++++++++++++
Msrc/reapack.cpp | 65++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/reapack.hpp | 6++++--
Asrc/resource.hpp | 11+++++++++++
Asrc/resource.rc | 10++++++++++
Msrc/transaction.cpp | 29++++++++++++++++++++---------
Msrc/transaction.hpp | 9++++++---
17 files changed, 411 insertions(+), 62 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -2,5 +2,6 @@ .tup/ bin/ build/ +src/resource.rc_mac* vendor/ !vendor/.gitkeep diff --git a/Tupfile b/Tupfile @@ -14,7 +14,7 @@ else endif endif -: foreach src/*.cpp |> !build |> build/%B.o +: foreach src/*.cpp | $(PREDEPS) |> !build |> build/%B.o : foreach $(WDLSOURCE) |> !build $(WDLFLAGS) |> build/wdl_%B.o : build/*.o |> !link $(SOFLAGS) |> $(SOTARGET) diff --git a/Tupfile.osx b/Tupfile.osx @@ -1,8 +1,10 @@ CXX := c++ -CXXFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter -pipe -fPIC +CXXFLAGS := -Wall -Wextra -Werror +CXXFLAGS += -Wno-unused-parameter -Wno-missing-field-initializers +CXXFLAGS += -Wno-unused-function CXXFLAGS += -fdiagnostics-color -fstack-protector-strong -CXXFLAGS += -O2 -std=c++14 +CXXFLAGS += -pipe -fPIC -O2 -std=c++14 CXXFLAGS += -Ivendor -Ivendor/WDL -Ivendor/WDL/WDL CXXFLAGS += -DWDL_NO_DEFINE_MINMAX @@ -21,3 +23,6 @@ TSTARGET := bin/test !build = |> $(CXX) $(CXXFLAGS) -c %f -o %o |> !link = |> $(CXX) $(CXXFLAGS) %f $(LDFLAGS) -o %o |> + +PREDEPS := src/resource.rc_mac_menu src/resource.rc_mac_dlg +: src/resource.rc |> php $(SWELL)/mac_resgen.php %f |> $(PREDEPS) diff --git a/src/dialog.cpp b/src/dialog.cpp @@ -0,0 +1,121 @@ +#include "dialog.hpp" + +DialogMap Dialog::s_instances; + +WDL_DLGRET Dialog::Proc(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam) +{ + Dialog *dlg = 0; + + const auto it = Dialog::s_instances.find(handle); + if(it != Dialog::s_instances.end()) + dlg = it->second; + + switch(msg) { + case WM_INITDIALOG: + dlg = (Dialog *)lParam; + dlg->m_handle = handle; + Dialog::s_instances[handle] = dlg; + + dlg->onInit(); + break; + case WM_SHOWWINDOW: + if(wParam) { + dlg->m_isVisible = true; + dlg->onShow(); + } + else { + dlg->m_isVisible = false; + dlg->onHide(); + } + break; + case WM_TIMER: + dlg->onTimer(); + break; + case WM_COMMAND: + dlg->onCommand(wParam, lParam); + break; + case WM_DESTROY: + dlg->onDestroy(); + break; + }; + + return false; +} + +Dialog::Dialog(const int templateId) + : m_template(templateId), m_parent(0), m_handle(0), m_isVisible(false) +{ +} + +void Dialog::init(REAPER_PLUGIN_HINSTANCE instance, HWND parent) +{ + m_parent = parent; + + CreateDialogParam(instance, MAKEINTRESOURCE(m_template), + m_parent, Proc, (LPARAM)this); +} + +void Dialog::Destroy(Dialog *dlg) +{ + if(dlg->isVisible()) + dlg->onHide(); + + delete dlg; +} + +Dialog::~Dialog() +{ + DestroyWindow(m_handle); + s_instances.erase(m_handle); +} + +void Dialog::show() +{ + center(); + + ShowWindow(m_handle, SW_SHOW); +} + +void Dialog::hide() +{ + ShowWindow(m_handle, SW_HIDE); +} + +void Dialog::center() +{ + RECT dialogRect, rect; + + GetWindowRect(m_handle, &dialogRect); + GetWindowRect(m_parent, &rect); + + OffsetRect(&dialogRect, -dialogRect.left, -dialogRect.top); + OffsetRect(&rect, -(dialogRect.right / 2), 0); + OffsetRect(&rect, 0, -(dialogRect.bottom / 2)); + + SetWindowPos(m_handle, HWND_TOP, rect.right / 2, rect.bottom / 1.4, + 0, 0, SWP_NOSIZE); +} + +void Dialog::onInit() +{ +} + +void Dialog::onShow() +{ +} + +void Dialog::onHide() +{ +} + +void Dialog::onTimer() +{ +} + +void Dialog::onCommand(WPARAM, LPARAM) +{ +} + +void Dialog::onDestroy() +{ +} diff --git a/src/dialog.hpp b/src/dialog.hpp @@ -0,0 +1,55 @@ +#ifndef REAPACK_DIALOG_HPP +#define REAPACK_DIALOG_HPP + +#include <map> + +#include <wdltypes.h> +#include <reaper_plugin.h> + +class Dialog; +typedef std::map<HWND, Dialog *> DialogMap; + +class Dialog { +public: + template<typename T> + static T *Create(REAPER_PLUGIN_HINSTANCE instance, HWND parent) + { + Dialog *dlg = new T(); + dlg->init(instance, parent); + + return dynamic_cast<T *>(dlg); + } + + static void Destroy(Dialog *); + + void init(REAPER_PLUGIN_HINSTANCE, HWND); + + HWND handle() const { return m_handle; } + bool isVisible() const { return m_isVisible; } + + void show(); + void hide(); + void center(); + +protected: + Dialog(const int templateId); + virtual ~Dialog(); + + virtual void onInit(); + virtual void onShow(); + virtual void onHide(); + virtual void onTimer(); + virtual void onCommand(WPARAM, LPARAM); + virtual void onDestroy(); + +private: + static WDL_DLGRET Proc(HWND, UINT, WPARAM, LPARAM); + static DialogMap s_instances; + + const int m_template; + HWND m_parent; + HWND m_handle; + + bool m_isVisible; +}; +#endif diff --git a/src/download.cpp b/src/download.cpp @@ -22,7 +22,7 @@ Download::~Download() s_active.erase(remove(s_active.begin(), s_active.end(), this)); // call stop after removing from the active list to prevent - // bad access from timeTick -> execCallbacks + // bad access from timeTick -> finishInMainThread stop(); // free the content buffer @@ -37,11 +37,6 @@ void Download::reset() m_contents.clear(); } -void Download::addCallback(const Download::Callback &callback) -{ - m_onFinish.connect(callback); -} - void Download::TimerTick() { vector<Download *> &activeDownloads = Download::s_active; @@ -54,11 +49,11 @@ void Download::TimerTick() if(!download->isFinished()) continue; - // this need to be done before execCallback in case one of the callback - // deletes the download object + // this need to be done before finishInMainThread in case one + // of the callbacks deletes the download object activeDownloads.erase(begin + i); - download->execCallbacks(); + download->finishInMainThread(); } if(Download::s_active.empty()) @@ -73,6 +68,8 @@ void Download::start() reset(); s_active.push_back(this); + m_onStart(); + plugin_register("timer", (void*)TimerTick); m_threadHandle = CreateThread(NULL, 0, Worker, (void *)this, 0, 0); @@ -168,7 +165,7 @@ void Download::finish(const int status, const string &contents) m_contents = contents; } -void Download::execCallbacks() +void Download::finishInMainThread() { WDL_MutexLock lock(&m_mutex); @@ -216,7 +213,9 @@ DownloadQueue::~DownloadQueue() void DownloadQueue::push(Download *dl) { - dl->addCallback([=]() { + m_onPush(dl); + + dl->onFinish([=]() { m_queue.pop(); delete dl; diff --git a/src/download.hpp b/src/download.hpp @@ -26,7 +26,8 @@ public: bool isFinished(); bool isAborted(); - void addCallback(const Callback &); + void onStart(const Callback &callback) { m_onStart.connect(callback); } + void onFinish(const Callback &callback) { m_onFinish.connect(callback); } void start(); void stop(); @@ -39,7 +40,7 @@ private: static DWORD WINAPI Worker(void *ptr); void finish(const int status, const std::string &contents); - void execCallbacks(); + void finishInMainThread(); void abort(); void reset(); @@ -52,6 +53,7 @@ private: std::string m_name; std::string m_url; + Signal m_onStart; Signal m_onFinish; WDL_Mutex m_mutex; @@ -59,14 +61,24 @@ private: class DownloadQueue { public: + typedef boost::signals2::signal<void (Download *)> Signal; + typedef Signal::slot_type Callback; + + DownloadQueue() {} + DownloadQueue(const DownloadQueue &) = delete; ~DownloadQueue(); void push(Download *); + size_t size() const { return m_queue.size(); } bool empty() const { return m_queue.empty(); } + void onPush(const Callback &callback) { m_onPush.connect(callback); } + private: std::queue<Download *> m_queue; + + Signal m_onPush; }; #endif diff --git a/src/main.cpp b/src/main.cpp @@ -4,6 +4,8 @@ #define REAPERAPI_IMPLEMENT #include <reaper_plugin_functions.h> +using namespace std; + static ReaPack reapack; static bool commandHook(const int id, const int flag) @@ -42,10 +44,10 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT( reapack.init(instance, rec); reapack.setupAction("REAPACK_SYNC", "ReaPack: Synchronize Packages", - &reapack.syncAction, std::bind(&ReaPack::synchronize, reapack)); + &reapack.syncAction, bind(&ReaPack::synchronize, reapack)); reapack.setupAction("REAPACK_IMPORT", - std::bind(&ReaPack::importRemote, reapack)); + bind(&ReaPack::importRemote, reapack)); rec->Register("hookcommand", (void *)commandHook); rec->Register("hookcustommenu", (void *)menuHook); @@ -54,3 +56,13 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT( return 1; } + +#ifdef __APPLE__ +#include "resource.hpp" + +#include <swell/swell-dlggen.h> +#include "resource.rc_mac_dlg" + +#include <swell/swell-menugen.h> +#include "resource.rc_mac_menu" +#endif diff --git a/src/package.hpp b/src/package.hpp @@ -4,7 +4,6 @@ #include "path.hpp" #include "version.hpp" -class Database; class Category; class Package; diff --git a/src/progress.cpp b/src/progress.cpp @@ -0,0 +1,69 @@ +#include "progress.hpp" + +#include "download.hpp" +#include "resource.hpp" +#include "transaction.hpp" + +using namespace std; + +Progress::Progress() + : Dialog(IDD_PROGRESS_DIALOG), + m_transaction(0), m_done(0), m_total(0), m_label(0), m_progress(0) +{ +} + +void Progress::setTransaction(Transaction *t) +{ + m_transaction = t; + + SetWindowText(m_label, ""); + + m_done = 0; + m_total = 0; + SendMessage(m_progress, PBM_SETPOS, m_done, 0); + + if(!m_transaction) + return; + + m_transaction->downloadQueue()->onPush( + bind(&Progress::addDownload, this, placeholders::_1)); +} + +void Progress::onInit() +{ + m_label = GetDlgItem(handle(), IDC_LABEL); + m_progress = GetDlgItem(handle(), IDC_PROGRESS); +} + +void Progress::onCommand(WPARAM wParam, LPARAM) +{ + const int commandId = LOWORD(wParam); + + switch(commandId) { + case IDCANCEL: + m_transaction->cancel(); + break; + } +} + +void Progress::addDownload(Download *dl) +{ + m_total++; + updateProgress(); + + dl->onStart([=] { + const string text = "Downloading: " + dl->name() + "\n" + dl->url(); + SetWindowText(m_label, text.c_str()); + }); + + dl->onFinish([=] { + m_done++; + updateProgress(); + }); +} + +void Progress::updateProgress() +{ + const double pos = m_done / m_total; + SendMessage(m_progress, PBM_SETPOS, pos * 100, 0); +} diff --git a/src/progress.hpp b/src/progress.hpp @@ -0,0 +1,32 @@ +#ifndef REAPACK_PROGRESS_HPP +#define REAPACK_PROGRESS_HPP + +#include "dialog.hpp" + +class Download; +class Transaction; + +class Progress : public Dialog { +public: + Progress(); + + void setTransaction(Transaction *t); + +protected: + void onInit() override; + void onCommand(WPARAM, LPARAM) override; + +private: + void addDownload(Download *); + void updateProgress(); + + Transaction *m_transaction; + + int m_done; + double m_total; + + HWND m_label; + HWND m_progress; +}; + +#endif diff --git a/src/reapack.cpp b/src/reapack.cpp @@ -1,5 +1,7 @@ #include "reapack.hpp" +#include "config.hpp" +#include "progress.hpp" #include "transaction.hpp" #include <reaper_plugin_functions.h> @@ -9,7 +11,7 @@ using namespace std; ReaPack::ReaPack() - : m_transaction(0) + : m_config(0), m_transaction(0), m_progress(0) { } @@ -17,13 +19,13 @@ void ReaPack::init(REAPER_PLUGIN_HINSTANCE instance, reaper_plugin_info_t *rec) { m_instance = instance; m_rec = rec; - m_mainHandle = GetMainHwnd(); + m_mainWindow = GetMainHwnd(); m_resourcePath.append(GetResourcePath()); - // wtf? If m_config is a member object different instances will be used - // in importRemote(), synchronize() and cleanup() m_config = new Config; m_config->read(m_resourcePath + "reapack.ini"); + + m_progress = Dialog::Create<Progress>(m_instance, m_mainWindow); } void ReaPack::cleanup() @@ -32,6 +34,8 @@ void ReaPack::cleanup() // and two times during shutdown on osx... cleanup() is called only once m_config->write(); delete m_config; + + Dialog::Destroy(m_progress); } int ReaPack::setupAction(const char *name, const ActionCallback &callback) @@ -65,33 +69,8 @@ bool ReaPack::execActions(const int id, const int) return true; } -Transaction *ReaPack::createTransaction() -{ - if(m_transaction) - return 0; - - m_transaction = new Transaction(m_config->registry(), m_resourcePath); - - m_transaction->onReady([=] { - // TODO: display the package list with the changelogs - m_transaction->run(); - }); - - m_transaction->onFinish([=] { - delete m_transaction; - m_transaction = 0; - - m_config->write(); - }); - - return m_transaction; -} - void ReaPack::synchronize() { - if(m_transaction) - return; - RemoteMap remotes = m_config->remotes(); if(remotes.empty()) { @@ -141,3 +120,31 @@ void ReaPack::importRemote() if(t) t->fetch(remote); } + +Transaction *ReaPack::createTransaction() +{ + if(m_transaction) + return 0; + + m_transaction = new Transaction(m_config->registry(), m_resourcePath); + + m_transaction->onReady([=] { + // TODO: display the package list with the changelogs + m_transaction->run(); + }); + + m_transaction->onFinish([=] { + m_progress->setTransaction(0); + m_progress->hide(); + + delete m_transaction; + m_transaction = 0; + + m_config->write(); + }); + + m_progress->setTransaction(m_transaction); + m_progress->show(); + + return m_transaction; +} diff --git a/src/reapack.hpp b/src/reapack.hpp @@ -4,14 +4,15 @@ #include <functional> #include <map> -#include "config.hpp" #include "path.hpp" #include <reaper_plugin.h> typedef std::function<void()> ActionCallback; +class Config; class Transaction; +class Progress; class ReaPack { public: @@ -37,10 +38,11 @@ private: Config *m_config; Transaction *m_transaction; + Progress *m_progress; REAPER_PLUGIN_HINSTANCE m_instance; reaper_plugin_info_t *m_rec; - HWND m_mainHandle; + HWND m_mainWindow; Path m_resourcePath; }; diff --git a/src/resource.hpp b/src/resource.hpp @@ -0,0 +1,11 @@ +#ifndef REAPACK_RESOURCE_HPP +#define REAPACK_RESOURCE_HPP + +#define PROGRESS_CLASS "msctls_progress32" + +#define IDD_PROGRESS_DIALOG 100 + +#define IDC_LABEL 200 +#define IDC_PROGRESS 201 + +#endif diff --git a/src/resource.rc b/src/resource.rc @@ -0,0 +1,10 @@ +#include "resource.hpp" + +IDD_PROGRESS_DIALOG DIALOGEX 0, 0, 260, 85 +STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_SYSMENU | WS_CAPTION +CAPTION "ReaPack: Download Progress" +BEGIN + LTEXT "File Name", IDC_LABEL, 5, 5, 250, 30 + CONTROL "", IDC_PROGRESS, PROGRESS_CLASS, 0x0, 5, 40, 250, 11 + PUSHBUTTON "Cancel", IDCANCEL, 105, 60, 50, 14 +END diff --git a/src/transaction.cpp b/src/transaction.cpp @@ -30,7 +30,7 @@ void Transaction::fetch(const RemoteMap &remotes) void Transaction::fetch(const Remote &remote) { Download *dl = new Download(remote.first, remote.second); - dl->addCallback([=]() { + dl->onFinish([=]() { if(dl->status() != 200) { addError(dl->contents(), dl->name()); return; @@ -47,17 +47,22 @@ void Transaction::fetch(const Remote &remote) file << dl->contents(); file.close(); - Database *db = Database::load(path.join().c_str()); - db->setName(dl->name()); + try { + Database *db = Database::load(path.join().c_str()); + db->setName(dl->name()); - m_databases.push_back(db); + m_databases.push_back(db); + } + catch(const reapack_error &e) { + addError(e.what(), dl->name()); + } }); m_queue.push(dl); // execute prepare after the download is deleted, in case finish is called // the queue will also not contain the download anymore - dl->addCallback(bind(&Transaction::prepare, this)); + dl->onFinish(bind(&Transaction::prepare, this)); } void Transaction::prepare() @@ -93,13 +98,19 @@ void Transaction::run() } } +void Transaction::cancel() +{ + ShowMessageBox("Not Implemented", "Cancel transaction", 0); +} + void Transaction::install(Package *pkg) { const string &url = pkg->lastVersion()->source(0)->url(); const Path path = m_root + pkg->targetPath(); + const string dbName = pkg->category()->database()->name(); - Download *dl = new Download(pkg->name(), url); - dl->addCallback([=] { + Download *dl = new Download(dbName + "/" + pkg->name(), url); + dl->onFinish([=] { if(dl->status() != 200) { addError(dl->contents(), dl->name()); return; @@ -123,7 +134,7 @@ void Transaction::install(Package *pkg) // execute finish after the download is deleted // this prevents the download queue from being deleted before the download - dl->addCallback(bind(&Transaction::finish, this)); + dl->onFinish(bind(&Transaction::finish, this)); } void Transaction::finish() @@ -136,5 +147,5 @@ void Transaction::finish() void Transaction::addError(const string &message, const string &title) { - ShowMessageBox(message.c_str(), title.c_str(), 0); + // ShowMessageBox(message.c_str(), title.c_str(), 0); } diff --git a/src/transaction.hpp b/src/transaction.hpp @@ -23,15 +23,18 @@ public: void fetch(const RemoteMap &); void fetch(const Remote &); - void prepare(); void run(); - void finish(); + void cancel(); const PackageList &packages() const { return m_packages; } + DownloadQueue *downloadQueue() { return &m_queue; } private: + void prepare(); + void finish(); + void install(Package *); - void addError(const std::string &, const std::string &); + void addError(const std::string &msg, const std::string &title); Registry *m_registry;