reapack

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

commit 733fc3ada8908e4bbbee9e07fe2dfd85851c4305
parent 06e61c2dfcb1b660ff24da0ff2a3f7ac0b0410dd
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Fri,  1 Apr 2016 19:38:57 -0400

Merge branch 'browser'

Diffstat:
Msrc/about.cpp | 69+++++++++++++++++++++------------------------------------------------
Msrc/about.hpp | 16+---------------
Asrc/browser.cpp | 655+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/browser.hpp | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/cleanup.cpp | 174-------------------------------------------------------------------------------
Dsrc/cleanup.hpp | 60------------------------------------------------------------
Msrc/config.cpp | 6+++++-
Msrc/config.hpp | 3+++
Msrc/dialog.cpp | 4++--
Msrc/dialog.hpp | 4++--
Msrc/import.cpp | 2+-
Msrc/import.hpp | 2+-
Msrc/listview.cpp | 26++++++++++++++++++++------
Msrc/listview.hpp | 2++
Msrc/main.cpp | 8++++----
Msrc/manager.cpp | 59+++++++++++++++++++++++++++++++++++++++++------------------
Msrc/manager.hpp | 7++++++-
Msrc/menu.cpp | 51+++++++++++++++++++++++++++++++++++++++++----------
Msrc/menu.hpp | 18++++++++++--------
Msrc/package.cpp | 21+++++++++++++++++++++
Msrc/package.hpp | 2++
Msrc/progress.cpp | 2+-
Msrc/progress.hpp | 2+-
Msrc/reapack.cpp | 58+++++++++++++++++++++++++++++++++++++++-------------------
Msrc/reapack.hpp | 12+++++++-----
Msrc/registry.cpp | 62+++++++++++++++++++++++++++++++++-----------------------------
Msrc/registry.hpp | 4+++-
Msrc/report.cpp | 44+++++++++++++++++++++++++++++++++++---------
Msrc/report.hpp | 14+++++++++++++-
Msrc/resource.hpp | 12+++++++++++-
Msrc/resource.rc | 31+++++++++++++++++++++----------
Msrc/transaction.cpp | 68++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/transaction.hpp | 5+++--
Msrc/version.cpp | 12++++++++++--
Msrc/version.hpp | 3++-
Mtest/package.cpp | 22++++++++++++++++++++++
Mtest/registry.cpp | 23++++++++++++++++-------
Mtest/version.cpp | 8++++----
38 files changed, 1230 insertions(+), 472 deletions(-)

diff --git a/src/about.cpp b/src/about.cpp @@ -24,12 +24,12 @@ #include "menu.hpp" #include "reapack.hpp" #include "registry.hpp" +#include "report.hpp" #include "resource.hpp" #include "richedit.hpp" #include "tabbar.hpp" #include <boost/algorithm/string/replace.hpp> -#include <boost/range/adaptor/reversed.hpp> #include <sstream> using namespace std; @@ -53,15 +53,8 @@ void About::onInit() m_cats->onSelect(bind(&About::updatePackages, this)); -#ifdef _WIN32 - // dirty hacks... - const int NAME_SIZE = 330; -#else - const int NAME_SIZE = 382; -#endif - m_packages = createControl<ListView>(IDC_PACKAGES, ListView::Columns{ - {AUTO_STR("Name"), NAME_SIZE}, + {AUTO_STR("Name"), 382}, {AUTO_STR("Version"), 80}, {AUTO_STR("Author"), 90}, }); @@ -80,12 +73,12 @@ void About::onInit() populate(); #ifdef LVSCW_AUTOSIZE_USEHEADER - m_cats->resizeColumn(0, LVSCW_AUTOSIZE_USEHEADER); - m_packages->resizeColumn(2, LVSCW_AUTOSIZE_USEHEADER); + m_cats->resizeColumn(m_cats->columnCount() - 1, LVSCW_AUTOSIZE_USEHEADER); + m_packages->resizeColumn(m_packages->columnCount() - 1, LVSCW_AUTOSIZE_USEHEADER); #endif } -void About::onCommand(const int id) +void About::onCommand(const short id, short) { switch(id) { case IDC_WEBSITE: @@ -109,6 +102,7 @@ void About::onCommand(const int id) openLink(m_websiteLinks[id & 0xff]); else if(id >> 8 == IDC_DONATE) openLink(m_donationLinks[id & 0xff]); + break; } } @@ -192,10 +186,9 @@ void About::updatePackages() const Version *lastVer = pkg->lastVersion(); const auto_string &name = make_autostring(pkg->name()); const auto_string &version = make_autostring(lastVer->name()); - const auto_string &author = make_autostring(lastVer->author()); + const auto_string &author = make_autostring(lastVer->displayAuthor()); - m_packages->addRow({name, version, - author.empty() ? AUTO_STR("Unknown") : author}); + m_packages->addRow({name, version, author}); } m_currentCat = catIndex; @@ -240,8 +233,6 @@ void About::updateInstalledFiles() stream << path.join() << "\r\n"; SetWindowText(m_installedFiles, make_autostring(stream.str()).c_str()); - - hide(getControl(IDC_INSTALL)); } } @@ -249,22 +240,23 @@ void About::selectLink(const int ctrl, const std::vector<const Link *> &links) { const int count = (int)links.size(); - if(count > 1) { - Menu menu; + m_tabs->setFocus(); - for(int i = 0; i < count; i++) { - const string &name = boost::replace_all_copy(links[i]->name, "&", "&&"); - menu.addAction(make_autostring(name).c_str(), i | (ctrl << 8)); - } + if(count == 1) { + openLink(links.front()); + return; + } + + Menu menu; - RECT rect; - GetWindowRect(getControl(ctrl), &rect); - menu.show(rect.left, rect.bottom - 1, handle()); + for(int i = 0; i < count; i++) { + const string &name = boost::replace_all_copy(links[i]->name, "&", "&&"); + menu.addAction(make_autostring(name).c_str(), i | (ctrl << 8)); } - else if(count == 1) - openLink(links.front()); - m_tabs->setFocus(); + RECT rect; + GetWindowRect(getControl(ctrl), &rect); + menu.show(rect.left, rect.bottom - 1, handle()); } void About::openLink(const Link *link) @@ -283,22 +275,3 @@ void About::packageHistory() const Package *pkg = m_packagesData->at(index); Dialog::Show<History>(instance(), handle(), pkg); } - -History::History(const Package *pkg) - : ReportDialog(), m_package(pkg) -{ -} - -void History::fillReport() -{ - SetWindowText(handle(), AUTO_STR("Package History")); - SetWindowText(getControl(IDC_LABEL), - make_autostring(m_package->name()).c_str()); - - for(const Version *ver : m_package->versions() | boost::adaptors::reversed) { - if(stream().tellp()) - stream() << NL; - - printVersion(ver); - } -} diff --git a/src/about.hpp b/src/about.hpp @@ -23,12 +23,9 @@ #include <memory> #include <vector> -#include "report.hpp" - class Index; class ListView; class Package; -class ReportBase; class RichEdit; class TabBar; struct Link; @@ -43,7 +40,7 @@ public: protected: void onInit() override; - void onCommand(int) override; + void onCommand(short, short) override; void onContextMenu(HWND, int x, int y) override; private: @@ -68,15 +65,4 @@ private: const std::vector<const Package *> *m_packagesData; }; -class History : public ReportDialog { -public: - History(const Package *); - -protected: - void fillReport() override; - -private: - const Package *m_package; -}; - #endif diff --git a/src/browser.cpp b/src/browser.cpp @@ -0,0 +1,655 @@ +/* ReaPack: Package manager for REAPER + * Copyright (C) 2015-2016 Christian Fillion + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "browser.hpp" + +#include "encoding.hpp" +#include "errors.hpp" +#include "index.hpp" +#include "menu.hpp" +#include "reapack.hpp" +#include "report.hpp" +#include "resource.hpp" + +#include <boost/algorithm/string.hpp> +#include <boost/range/adaptor/reversed.hpp> + +using namespace std; + +enum Action { + ACTION_VERSION = 80, + ACTION_LATEST = 300, + ACTION_LATEST_ALL, + ACTION_REINSTALL, + ACTION_REINSTALL_ALL, + ACTION_UNINSTALL, + ACTION_UNINSTALL_ALL, + ACTION_HISTORY, + ACTION_ABOUT, + ACTION_RESET_ALL, +}; + +Browser::Browser(const vector<IndexPtr> &indexes, ReaPack *reapack) + : Dialog(IDD_BROWSER_DIALOG), m_indexes(indexes), m_reapack(reapack), + m_checkFilter(false), m_currentIndex(-1) +{ +} + +void Browser::onInit() +{ + m_action = getControl(IDC_ACTION); + m_apply = getControl(IDAPPLY); + m_filterHandle = getControl(IDC_FILTER); + m_display = getControl(IDC_DISPLAY); + + disable(m_apply); + + SendMessage(m_display, CB_ADDSTRING, 0, (LPARAM)AUTO_STR("All")); + SendMessage(m_display, CB_ADDSTRING, 0, (LPARAM)AUTO_STR("Queued")); + SendMessage(m_display, CB_ADDSTRING, 0, (LPARAM)AUTO_STR("Installed")); + SendMessage(m_display, CB_ADDSTRING, 0, (LPARAM)AUTO_STR("Out of date")); + SendMessage(m_display, CB_ADDSTRING, 0, (LPARAM)AUTO_STR("Obsolete")); + SendMessage(m_display, CB_ADDSTRING, 0, (LPARAM)AUTO_STR("Uninstalled")); + SendMessage(m_display, CB_SETCURSEL, 0, 0); + + m_types = { + {Package::ScriptType, getControl(IDC_SCRIPTS)}, + {Package::EffectType, getControl(IDC_EFFECTS)}, + {Package::ExtensionType, getControl(IDC_EXTENSIONS)}, + }; + + for(const auto &pair : m_types) + SendMessage(pair.second, BM_SETCHECK, BST_CHECKED, 0); + + m_list = createControl<ListView>(IDC_PACKAGES, ListView::Columns{ + {AUTO_STR(""), 23}, + {AUTO_STR("Package Name"), 380}, + {AUTO_STR("Category"), 150}, + {AUTO_STR("Version"), 80}, + {AUTO_STR("Author"), 95}, + {AUTO_STR("Type"), 70}, + }); + + m_list->onActivate([=] { history(m_list->itemUnderMouse()); }); + m_list->sortByColumn(1); + + reload(); + +#ifdef LVSCW_AUTOSIZE_USEHEADER + m_list->resizeColumn(m_list->columnCount() - 1, LVSCW_AUTOSIZE_USEHEADER); +#endif + + m_filterTimer = startTimer(200); +} + +void Browser::onCommand(const short id, const short event) +{ + namespace arg = std::placeholders; + + switch(id) { + case IDC_DISPLAY: + if(event != CBN_SELCHANGE) + break; + case IDC_SCRIPTS: + case IDC_EFFECTS: + case IDC_EXTENSIONS: + fillList(); + break; + case IDC_FILTER: + m_checkFilter = true; + break; + case IDC_CLEAR: + SetWindowText(m_filterHandle, AUTO_STR("")); + checkFilter(); + break; + case IDC_SELECT: + m_list->selectAll(); + SetFocus(m_list->handle()); + break; + case IDC_UNSELECT: + m_list->unselectAll(); + SetFocus(m_list->handle()); + break; + case IDC_ACTION: + selectionMenu(); + break; + case ACTION_LATEST: + installLatest(m_currentIndex); + break; + case ACTION_LATEST_ALL: + selectionDo(bind(&Browser::installLatest, this, arg::_1, false)); + break; + case ACTION_REINSTALL: + reinstall(m_currentIndex); + break; + case ACTION_REINSTALL_ALL: + selectionDo(bind(&Browser::reinstall, this, arg::_1, false)); + break; + case ACTION_UNINSTALL: + uninstall(m_currentIndex); + break; + case ACTION_UNINSTALL_ALL: + selectionDo(bind(&Browser::uninstall, this, arg::_1, false)); + break; + case ACTION_HISTORY: + history(m_currentIndex); + break; + case ACTION_ABOUT: + about(m_currentIndex); + break; + case ACTION_RESET_ALL: + selectionDo(bind(&Browser::resetAction, this, arg::_1)); + break; + case IDOK: + case IDAPPLY: + if(confirm()) { + apply(); + + if(id == IDAPPLY) + break; + } + else + break; + case IDCANCEL: + close(); + break; + default: + if(id >> 8 == ACTION_VERSION) + installVersion(m_currentIndex, id & 0xff); + break; + } +} + +void Browser::onTimer(const int id) +{ + if(id == m_filterTimer) + checkFilter(); +} + +void Browser::onContextMenu(HWND target, const int x, const int y) +{ + if(target != m_list->handle()) + return; + + SetFocus(m_list->handle()); + + m_currentIndex = m_list->itemUnderMouse(); + const Entry *entry = getEntry(m_currentIndex); + + if(!entry) + return; + + Menu menu; + + if(entry->test(InstalledFlag)) { + if(entry->test(OutOfDateFlag)) { + auto_char installLabel[255] = {}; + auto_snprintf(installLabel, sizeof(installLabel), AUTO_STR("U&pdate to v%s"), + make_autostring(entry->latest->name()).c_str()); + + const UINT actionIndex = menu.addAction(installLabel, ACTION_LATEST); + if(isTarget(entry, entry->latest)) + menu.check(actionIndex); + } + + auto_char reinstallLabel[255] = {}; + auto_snprintf(reinstallLabel, sizeof(reinstallLabel), AUTO_STR("&Reinstall v%s"), + make_autostring(entry->regEntry.versionName).c_str()); + + const UINT actionIndex = menu.addAction(reinstallLabel, ACTION_REINSTALL); + if(!entry->current || entry->test(ObsoleteFlag)) + menu.disable(actionIndex); + else if(isTarget(entry, entry->current)) + menu.check(actionIndex); + } + else { + auto_char installLabel[255] = {}; + auto_snprintf(installLabel, sizeof(installLabel), AUTO_STR("&Install v%s"), + make_autostring(entry->latest->name()).c_str()); + + const UINT actionIndex = menu.addAction(installLabel, ACTION_LATEST); + if(isTarget(entry, entry->latest)) + menu.check(actionIndex); + } + + Menu versionMenu = menu.addMenu(AUTO_STR("Versions")); + const UINT versionIndex = menu.size() - 1; + if(entry->test(ObsoleteFlag)) + menu.disable(versionIndex); + else { + const auto &versions = entry->package->versions(); + int verIndex = (int)versions.size(); + for(const Version *ver : versions | boost::adaptors::reversed) { + const UINT actionIndex = versionMenu.addAction( + make_autostring(ver->name()).c_str(), --verIndex | (ACTION_VERSION << 8)); + + if(hasAction(entry) ? isTarget(entry, ver) : ver == entry->current) + versionMenu.checkRadio(actionIndex); + } + } + + const UINT uninstallIndex = + menu.addAction(AUTO_STR("&Uninstall"), ACTION_UNINSTALL); + if(!entry->test(InstalledFlag)) + menu.disable(uninstallIndex); + else if(isTarget(entry, nullptr)) + menu.check(uninstallIndex); + + menu.addSeparator(); + + menu.setEnabled(!entry->test(ObsoleteFlag), + menu.addAction(AUTO_STR("Package &History"), ACTION_HISTORY)); + + auto_char aboutLabel[255] = {}; + const auto_string &name = make_autostring(getValue(RemoteColumn, *entry)); + auto_snprintf(aboutLabel, sizeof(aboutLabel), + AUTO_STR("&About %s..."), name.c_str()); + menu.addAction(aboutLabel, ACTION_ABOUT); + + menu.show(x, y, handle()); +} + +void Browser::selectionMenu() +{ + RECT rect; + GetWindowRect(m_action, &rect); + + const Entry *entry = nullptr; + if(m_list->selectionSize() == 1) + entry = getEntry(m_list->currentIndex()); + + Menu menu; + + menu.addAction(AUTO_STR("&Install/update selection"), ACTION_LATEST_ALL); + menu.addAction(AUTO_STR("&Reinstall selection"), ACTION_REINSTALL_ALL); + menu.addAction(AUTO_STR("&Uninstall selection"), ACTION_UNINSTALL_ALL); + menu.addAction(AUTO_STR("&Clear queued action"), ACTION_RESET_ALL); + + if(!m_list->hasSelection()) + menu.disableAll(); + + menu.show(rect.left, rect.bottom - 1, handle()); +} + +void Browser::checkFilter() +{ + if(!m_checkFilter) + return; + + m_checkFilter = false; + + auto_string wideFilter(4096, 0); + GetWindowText(m_filterHandle, &wideFilter[0], (int)wideFilter.size()); + wideFilter.resize(wideFilter.find(AUTO_STR('\0'))); + + const string &filter = from_autostring(wideFilter); + + if(filter != m_filter) { + m_filter = filter; + fillList(); + } +} + +void Browser::reload() +{ + try { + Registry reg(Path::prefixRoot(Path::REGISTRY)); + + m_currentIndex = -1; + m_actions.clear(); + m_entries.clear(); + + for(IndexPtr index : m_indexes) { + for(const Package *pkg : index->packages()) + m_entries.push_back(makeEntry(pkg, reg.getEntry(pkg))); + + // obsolete packages + for(const Registry::Entry &entry : reg.getEntries(index->name())) { + const Category *cat = index->category(entry.category); + + if(cat && cat->package(entry.package)) + continue; + + m_entries.push_back({InstalledFlag | ObsoleteFlag, entry}); + } + } + + fillList(); + } + catch(const reapack_error &e) { + const auto_string &desc = make_autostring(e.what()); + auto_char msg[255] = {}; + auto_snprintf(msg, sizeof(msg), + AUTO_STR("ReaPack could not open its package registry.\r\n") + AUTO_STR("Retry later when all installation task are completed.\r\n") + AUTO_STR("\r\nError description: %s"), + desc.c_str()); + MessageBox(handle(), msg, AUTO_STR("ReaPack"), MB_OK); + } +} + +auto Browser::makeEntry(const Package *pkg, const Registry::Entry &regEntry) + -> Entry +{ + const Version *latest = pkg->lastVersion(); + const Version *current = nullptr; + + int flags = 0; + + if(regEntry.id) { + flags |= InstalledFlag; + + if(regEntry.versionCode < latest->code()) + flags |= OutOfDateFlag; + + for(const Version *ver : pkg->versions()) { + if(ver->code() == regEntry.versionCode) { + current = ver; + break; + } + } + } + else + flags |= UninstalledFlag; + + return {flags, regEntry, pkg, latest, current}; +} + +void Browser::fillList() +{ + InhibitControl freeze(m_list); + + m_list->clear(); + m_visibleEntries.clear(); + + for(size_t i = 0; i < m_entries.size(); i++) { + const Entry &entry = m_entries[i]; + + if(!match(entry)) + continue; + + m_list->addRow(makeRow(entry)); + m_visibleEntries.push_back(i); + } + + m_list->sort(); +} + +ListView::Row Browser::makeRow(const Entry &entry) const +{ + const string &state = getValue(StateColumn, entry); + const string &name = getValue(NameColumn, entry); + const string &category = getValue(CategoryColumn, entry); + const string &version = getValue(VersionColumn, entry); + const string &author = getValue(AuthorColumn, entry); + const string &type = getValue(TypeColumn, entry); + + return { + make_autostring(state), make_autostring(name), make_autostring(category), + make_autostring(version), make_autostring(author), make_autostring(type) + }; +} + +string Browser::getValue(const Column col, const Entry &entry) const +{ + const Package *pkg = entry.package; + const Version *ver = entry.latest; + const Registry::Entry &regEntry = entry.regEntry; + + string display; + + switch(col) { + case StateColumn: { + if(entry.test(ObsoleteFlag)) + display += 'o'; + else if(entry.test(OutOfDateFlag)) + display += 'u'; + else if(entry.test(InstalledFlag)) + display += 'i'; + else + display += '\x20'; + + if(hasAction(&entry)) + display += isTarget(&entry, nullptr) ? 'U' : 'I'; + + return display; + } + case NameColumn: + return pkg ? pkg->name() : regEntry.package; + case CategoryColumn: + return pkg ? pkg->category()->name() : regEntry.category; + case VersionColumn: + if(entry.test(InstalledFlag)) + display = regEntry.versionName; + + if(ver && ver->code() != regEntry.versionCode) { + if(!display.empty()) + display += '\x20'; + + display += '(' + ver->name() + ')'; + } + + return display; + case AuthorColumn: + return ver ? ver->displayAuthor() : regEntry.author; + case TypeColumn: + return pkg ? pkg->displayType() : Package::displayType(regEntry.type); + case RemoteColumn: + return pkg ? pkg->category()->index()->name() : regEntry.remote; + } + + return {}; // for MSVC +} + +bool Browser::match(const Entry &entry) const +{ + using namespace boost; + + switch(getDisplay()) { + case All: + break; + case Queued: + if(!hasAction(&entry)) + return false; + break; + case Installed: + if(!entry.test(InstalledFlag)) + return false; + break; + case OutOfDate: + if(!entry.test(OutOfDateFlag)) + return false; + break; + case Uninstalled: + if(!entry.test(UninstalledFlag)) + return false; + break; + case Obsolete: + if(!entry.test(ObsoleteFlag)) + return false; + break; + } + + const Package::Type type = + entry.latest ? entry.package->type() : entry.regEntry.type; + + const auto typeIt = m_types.find(type); + + if(typeIt == m_types.end() || + SendMessage(typeIt->second, BM_GETCHECK, 0, 0) == BST_UNCHECKED) + return false; + + const string &name = getValue(NameColumn, entry); + const string &category = getValue(CategoryColumn, entry); + const string &author = getValue(AuthorColumn, entry); + + return icontains(name, m_filter) || icontains(category, m_filter) || + icontains(author, m_filter); +} + +auto Browser::getEntry(const int listIndex) const -> const Entry * +{ + if(listIndex < 0 || listIndex >= (int)m_visibleEntries.size()) + return nullptr; + + return &m_entries[m_visibleEntries[listIndex]]; +} + +void Browser::history(const int index) const +{ + const Entry *entry = getEntry(index); + + if(entry && entry->package) + Dialog::Show<History>(instance(), handle(), entry->package); +} + +void Browser::about(const int index) const +{ + if(const Entry *entry = getEntry(index)) + m_reapack->about(getValue(RemoteColumn, *entry), handle()); +} + +void Browser::installLatest(const int index, const bool toggle) +{ + const Entry *entry = getEntry(index); + + if(entry && entry->latest && entry->latest != entry->current) + setAction(index, entry->latest, toggle); +} + +void Browser::reinstall(const int index, const bool toggle) +{ + const Entry *entry = getEntry(index); + + if(entry && entry->current) + setAction(index, entry->current, toggle); +} + +void Browser::installVersion(const int index, const size_t verIndex) +{ + const Entry *entry = getEntry(index); + const auto versions = entry->package->versions(); + + if(verIndex >= versions.size()) + return; + + const Version *target = entry->package->version(verIndex); + + if(target == entry->current) + resetAction(index); + else + setAction(index, target); +} + +void Browser::uninstall(const int index, const bool toggle) +{ + const Entry *entry = getEntry(index); + + if(entry && entry->test(InstalledFlag)) + setAction(index, nullptr, toggle); +} + +void Browser::resetAction(const int index) +{ + const Entry *entry = getEntry(index); + const auto it = m_actions.find(entry); + + if(it == m_actions.end()) + return; + + m_actions.erase(it); + + if(getDisplay() == Queued) + m_list->removeRow(index); + else + m_list->replaceRow(index, makeRow(*entry)); + + if(m_actions.empty()) + disable(m_apply); +} + +bool Browser::isTarget(const Entry *entry, const Version *target) const +{ + const auto it = m_actions.find(entry); + + if(it == m_actions.end()) + return false; + else + return it->second == target; +} + +void Browser::setAction(const int index, const Version *target, const bool toggle) +{ + const Entry *entry = getEntry(index); + + if(toggle && isTarget(entry, target)) + resetAction(index); + else if(entry) { + m_actions[entry] = target; + m_list->replaceRow(index, makeRow(*entry)); + enable(m_apply); + } +} + +void Browser::selectionDo(const std::function<void (int)> &func) +{ + for(const int index : m_list->selection()) + func(index); +} + +auto Browser::getDisplay() const -> Display +{ + return (Display)SendMessage(m_display, CB_GETCURSEL, 0, 0); +} + +bool Browser::confirm() const +{ + if(m_actions.empty()) + return true; + + const size_t count = m_actions.size(); + + auto_char msg[255] = {}; + auto_snprintf(msg, sizeof(msg), + AUTO_STR("Confirm execution of %zu action%s?\n"), + count, count == 1 ? AUTO_STR("") : AUTO_STR("s")); + + const auto_char *title = AUTO_STR("ReaPack Query"); + const int btn = MessageBox(handle(), msg, title, MB_YESNO); + + return btn == IDYES; +} + +void Browser::apply() +{ + if(m_actions.empty()) + return; + + disable(m_apply); + + for(const auto &pair : m_actions) { + if(pair.second) + m_reapack->install(pair.second); + else + m_reapack->uninstall(pair.first->regEntry); + } + + m_actions.clear(); + m_reapack->runTasks(); + + fillList(); // update state column +} diff --git a/src/browser.hpp b/src/browser.hpp @@ -0,0 +1,131 @@ +/* ReaPack: Package manager for REAPER + * Copyright (C) 2015-2016 Christian Fillion + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef REAPACK_BROWSER_HPP +#define REAPACK_BROWSER_HPP + +#include "dialog.hpp" + +#include "listview.hpp" +#include "registry.hpp" + +#include <functional> +#include <map> +#include <memory> +#include <string> +#include <vector> + +class Index; +class ListView; +class ReaPack; +class Version; + +typedef std::shared_ptr<const Index> IndexPtr; + +class Browser : public Dialog { +public: + Browser(const std::vector<IndexPtr> &, ReaPack *); + void reload(); + +protected: + void onInit() override; + void onCommand(short, short) override; + void onContextMenu(HWND, int x, int y) override; + void onTimer(int) override; + +private: + enum Flag { + UninstalledFlag = 1<<1, + InstalledFlag = 1<<2, + OutOfDateFlag = 1<<3, + ObsoleteFlag = 1<<4, + }; + + struct Entry { + int flags; + Registry::Entry regEntry; + const Package *package; + const Version *latest; + const Version *current; + + bool test(Flag f) const { return (flags & f) != 0; } + }; + + enum Column { + StateColumn, + NameColumn, + CategoryColumn, + VersionColumn, + AuthorColumn, + TypeColumn, + RemoteColumn, + }; + + enum Display { + All, + Queued, + Installed, + OutOfDate, + Obsolete, + Uninstalled, + }; + + static Entry makeEntry(const Package *, const Registry::Entry &); + + bool match(const Entry &) const; + void checkFilter(); + void fillList(); + std::string getValue(Column, const Entry &entry) const; + ListView::Row makeRow(const Entry &) const; + const Entry *getEntry(int) const; + void selectionMenu(); + bool hasAction(const Entry *e) const { return m_actions.count(e) > 0; } + bool isTarget(const Entry *, const Version *) const; + void setAction(const int index, const Version *, bool toggle = true); + void selectionDo(const std::function<void (int)> &); + Display getDisplay() const; + bool confirm() const; + void apply(); + + void installLatest(int index, bool toggle = true); + void reinstall(int index, bool toggle = true); + void installVersion(int index, size_t verIndex); + void uninstall(int index, bool toggle = true); + void resetAction(int index); + void history(int index) const; + void about(int index) const; + + std::vector<IndexPtr> m_indexes; + ReaPack *m_reapack; + bool m_checkFilter; + int m_currentIndex; + + int m_filterTimer; + std::string m_filter; + std::vector<Entry> m_entries; + std::vector<size_t> m_visibleEntries; + std::map<const Entry *, const Version *> m_actions; + + HWND m_filterHandle; + HWND m_display; + std::map<int, HWND> m_types; + ListView *m_list; + HWND m_action; + HWND m_apply; +}; + +#endif diff --git a/src/cleanup.cpp b/src/cleanup.cpp @@ -1,174 +0,0 @@ -/* ReaPack: Package manager for REAPER - * Copyright (C) 2015-2016 Christian Fillion - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "cleanup.hpp" - -#include "encoding.hpp" -#include "errors.hpp" -#include "index.hpp" -#include "listview.hpp" -#include "menu.hpp" -#include "reapack.hpp" -#include "resource.hpp" - -using namespace std; - -enum { ACTION_SELECT = 300, ACTION_UNSELECT }; - -Cleanup::Cleanup(const std::vector<IndexPtr> &indexes, ReaPack *reapack) - : Dialog(IDD_CLEANUP_DIALOG), m_indexes(indexes), m_reapack(reapack) -{ -} - -void Cleanup::onInit() -{ - (void)m_reapack; - m_ok = getControl(IDOK); - m_stateLabel = getControl(IDC_LABEL2); - - disable(m_ok); - - m_list = createControl<ListView>(IDC_LIST, ListView::Columns{ - {AUTO_STR("Name"), 560}, - }); - - m_list->sortByColumn(0); - m_list->onSelect(bind(&Cleanup::onSelectionChanged, this)); - - try { - populate(); - } - catch(const reapack_error &e) { - const auto_string &desc = make_autostring(e.what()); - auto_char msg[255] = {}; - auto_snprintf(msg, sizeof(msg), - AUTO_STR("The file list is currently unavailable.\x20") - AUTO_STR("Retry later when all installation task are completed.\r\n") - AUTO_STR("\r\nError description: %s"), - desc.c_str()); - MessageBox(handle(), msg, AUTO_STR("AAA"), MB_OK); - } - -#ifdef LVSCW_AUTOSIZE_USEHEADER - m_list->resizeColumn(0, LVSCW_AUTOSIZE_USEHEADER); -#endif - - onSelectionChanged(); - - if(m_list->empty()) - startTimer(10); -} - -void Cleanup::onCommand(const int id) -{ - switch(id) { - case ACTION_SELECT: - m_list->selectAll(); - break; - case ACTION_UNSELECT: - m_list->unselectAll(); - break; - case IDOK: - if(confirm()) - apply(); - else - break; - case IDCANCEL: - close(); - break; - } -} - -void Cleanup::onContextMenu(HWND target, const int x, const int y) -{ - if(target != m_list->handle()) - return; - - Menu menu; - menu.addAction(AUTO_STR("&Select all"), ACTION_SELECT); - menu.addAction(AUTO_STR("&Unselect all"), ACTION_UNSELECT); - menu.show(x, y, handle()); -} - -void Cleanup::onTimer(const int id) -{ - stopTimer(id); - - MessageBox(handle(), AUTO_STR("No obsolete package found!"), - AUTO_STR("ReaPack"), MB_OK); - - close(); -} - -void Cleanup::populate() -{ - Registry reg(Path::prefixRoot(Path::REGISTRY)); - - for(IndexPtr index : m_indexes) { - for(const Registry::Entry &entry : reg.getEntries(index->name())) { - const Category *cat = index->category(entry.category); - - if(cat && cat->package(entry.package)) - continue; - - const string row = entry.remote + "/" + entry.category + "/" + entry.package; - m_list->addRow({make_autostring(row)}); - - m_entries.push_back(entry); - } - } - - m_list->sort(); -} - -void Cleanup::onSelectionChanged() -{ - const int selectionSize = m_list->selectionSize(); - - auto_char state[255] = {}; - auto_snprintf(state, sizeof(state), AUTO_STR("%d of %d package%s selected"), - selectionSize, m_list->rowCount(), - selectionSize == 1 ? AUTO_STR("") : AUTO_STR("s")); - - SetWindowText(m_stateLabel, state); - - setEnabled(selectionSize > 0, m_ok); -} - -bool Cleanup::confirm() const -{ - const int count = m_list->selectionSize(); - - auto_char msg[255] = {}; - auto_snprintf(msg, sizeof(msg), - AUTO_STR("Uninstall %d package%s?\n") - AUTO_STR("Every file they contain will be removed from your computer."), - count, count == 1 ? AUTO_STR("") : AUTO_STR("s")); - - const auto_char *title = AUTO_STR("ReaPack Query"); - const int btn = MessageBox(handle(), msg, title, MB_YESNO); - - return btn == IDYES; -} - -void Cleanup::apply() -{ - for(const int i : m_list->selection()) - m_reapack->uninstall(m_entries[i]); - - m_reapack->runTasks(); -} diff --git a/src/cleanup.hpp b/src/cleanup.hpp @@ -1,60 +0,0 @@ -/* ReaPack: Package manager for REAPER - * Copyright (C) 2015-2016 Christian Fillion - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef REAPACK_CLEANUP_HPP -#define REAPACK_CLEANUP_HPP - -#include "dialog.hpp" - -#include "registry.hpp" - -#include <memory> -#include <vector> - -class Index; -class ListView; -class ReaPack; - -typedef std::shared_ptr<const Index> IndexPtr; - -class Cleanup : public Dialog { -public: - Cleanup(const std::vector<IndexPtr> &, ReaPack *); - -protected: - void onInit() override; - void onCommand(int) override; - void onContextMenu(HWND, int x, int y) override; - void onTimer(int) override; - -private: - void populate(); - void onSelectionChanged(); - bool confirm() const; - void apply(); - - std::vector<IndexPtr> m_indexes; - ReaPack *m_reapack; - - HWND m_ok; - HWND m_stateLabel; - ListView *m_list; - - std::vector<Registry::Entry> m_entries; -}; - -#endif diff --git a/src/config.cpp b/src/config.cpp @@ -32,6 +32,7 @@ using namespace std; static const char *GLOBAL_GRP = "reapack"; static const char *VERSION_KEY = "version"; +static const char *AUTOINSTALL_KEY = "autoInstall"; static const char *SIZE_KEY = "size"; @@ -46,7 +47,7 @@ static string ArrayKey(const string &key, const size_t i) static const int BUFFER_SIZE = 2083; Config::Config() - : m_isFirstRun(false), m_version(0), m_remotesIniSize(0) + : m_isFirstRun(false), m_version(0), m_autoInstall(false), m_remotesIniSize(0) { } @@ -84,6 +85,8 @@ void Config::read(const Path &path) migrate(); + m_autoInstall = getUInt(GLOBAL_GRP, AUTOINSTALL_KEY) > 0; + readRemotes(); restoreSelfRemote(); } @@ -91,6 +94,7 @@ void Config::read(const Path &path) void Config::write() { setUInt(GLOBAL_GRP, VERSION_KEY, m_version); + setUInt(GLOBAL_GRP, AUTOINSTALL_KEY, m_autoInstall); writeRemotes(); } diff --git a/src/config.hpp b/src/config.hpp @@ -32,6 +32,8 @@ public: void write(); bool isFirstRun() const { return m_isFirstRun; } + bool autoInstall() const { return m_autoInstall; } + void setAutoInstall(const bool enable) { m_autoInstall = enable; } RemoteList *remotes() { return &m_remotes; } private: @@ -48,6 +50,7 @@ private: std::string m_path; bool m_isFirstRun; size_t m_version; + bool m_autoInstall; void readRemotes(); void restoreSelfRemote(); diff --git a/src/dialog.cpp b/src/dialog.cpp @@ -71,7 +71,7 @@ WDL_DLGRET Dialog::Proc(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam) dlg->onTimer((int)wParam); break; case WM_COMMAND: - dlg->onCommand(LOWORD(wParam)); + dlg->onCommand(LOWORD(wParam), HIWORD(wParam)); break; case WM_NOTIFY: dlg->onNotify((LPNMHDR)lParam, lParam); @@ -242,7 +242,7 @@ void Dialog::onTimer(int) { } -void Dialog::onCommand(const int id) +void Dialog::onCommand(const short id, short) { switch(id) { case IDOK: diff --git a/src/dialog.hpp b/src/dialog.hpp @@ -114,8 +114,8 @@ protected: virtual void onInit(); virtual void onShow(); virtual void onHide(); - virtual void onTimer(int); - virtual void onCommand(int); + virtual void onTimer(int id); + virtual void onCommand(short id, short event); virtual void onNotify(LPNMHDR, LPARAM); virtual void onContextMenu(HWND, int x, int y); diff --git a/src/import.cpp b/src/import.cpp @@ -55,7 +55,7 @@ void Import::onInit() #endif } -void Import::onCommand(const int id) +void Import::onCommand(const short id, short) { switch(id) { case IDOK: diff --git a/src/import.hpp b/src/import.hpp @@ -34,7 +34,7 @@ public: protected: void onInit() override; - void onCommand(int) override; + void onCommand(short, short) override; void onTimer(int) override; private: diff --git a/src/listview.cpp b/src/listview.cpp @@ -48,7 +48,7 @@ void ListView::addColumn(const Column &col) LVCOLUMN item{}; item.mask |= LVCF_WIDTH; - item.cx = col.width; + item.cx = adjustWidth(col.width); item.mask |= LVCF_TEXT; item.pszText = const_cast<auto_char *>(col.text.c_str()); @@ -111,7 +111,7 @@ void ListView::removeRow(const int userIndex) void ListView::resizeColumn(const int index, const int width) { - ListView_SetColumnWidth(handle(), index, width); + ListView_SetColumnWidth(handle(), index, adjustWidth(width)); } void ListView::sort() @@ -277,7 +277,7 @@ void ListView::handleColumnClick(LPARAM lParam) int ListView::translate(const int userIndex) const { - if(m_sortColumn < 0) + if(m_sortColumn < 0 || userIndex < 0) return userIndex; for(int viewIndex = 0; viewIndex < rowCount(); viewIndex++) { @@ -295,13 +295,27 @@ int ListView::translate(const int userIndex) const int ListView::translateBack(const int internalIndex) const { - if(m_sortColumn < 0) + if(m_sortColumn < 0 || internalIndex < 0) return internalIndex; LVITEM item{}; item.iItem = internalIndex; item.mask |= LVIF_PARAM; - ListView_GetItem(handle(), &item); - return (int)item.lParam; + if(ListView_GetItem(handle(), &item)) + return (int)item.lParam; + else + return -1; +} + +int ListView::adjustWidth(const int points) +{ +#ifdef _WIN32 + if(points < 1) + return points; + else + return (int)ceil(points * 0.863); // magic number to make pretty sizes... +#else + return points; +#endif } diff --git a/src/listview.hpp b/src/listview.hpp @@ -57,6 +57,7 @@ public: std::vector<int> selection() const; int itemUnderMouse() const; int rowCount() const { return (int)m_rows.size(); } + int columnCount() const { return m_columnSize; } bool empty() const { return rowCount() < 1; } void onSelect(const VoidSignal::slot_type &slot) { m_onSelect.connect(slot); } @@ -66,6 +67,7 @@ protected: void onNotify(LPNMHDR, LPARAM) override; private: + static int adjustWidth(int); void setExStyle(int style, bool enable); void addColumn(const Column &); void setSortArrow(bool); diff --git a/src/main.cpp b/src/main.cpp @@ -81,8 +81,8 @@ static void menuHook(const char *name, HMENU handle, int f) menu.addAction(AUTO_STR("&Synchronize packages"), NamedCommandLookup("_REAPACK_SYNC")); - menu.addAction(AUTO_STR("&Clean up packages..."), - NamedCommandLookup("_REAPACK_CLEANUP")); + menu.addAction(AUTO_STR("&Browse packages..."), + NamedCommandLookup("_REAPACK_BROWSE")); menu.addAction(AUTO_STR("&Import a repository..."), NamedCommandLookup("_REAPACK_IMPORT")); @@ -159,8 +159,8 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT( reapack->setupAction("REAPACK_SYNC", "ReaPack: Synchronize packages", &reapack->syncAction, bind(&ReaPack::synchronizeAll, reapack)); - reapack->setupAction("REAPACK_CLEANUP", "ReaPack: Clean up packages...", - &reapack->cleanupAction, bind(&ReaPack::cleanupPackages, reapack)); + reapack->setupAction("REAPACK_BROWSE", "ReaPack: Browse packages...", + &reapack->browseAction, bind(&ReaPack::browsePackages, reapack)); reapack->setupAction("REAPACK_IMPORT", "ReaPack: Import a repository...", &reapack->importAction, bind(&ReaPack::importRemote, reapack)); diff --git a/src/manager.cpp b/src/manager.cpp @@ -29,35 +29,36 @@ using namespace std; -enum { ACTION_ENABLE = 80, ACTION_DISABLE, ACTION_UNINSTALL, ACTION_ABOUT }; +enum { ACTION_ENABLE = 80, ACTION_DISABLE, ACTION_UNINSTALL, ACTION_ABOUT, + ACTION_AUTOINSTALL }; Manager::Manager(ReaPack *reapack) - : Dialog(IDD_CONFIG_DIALOG), m_reapack(reapack), m_list(0) + : Dialog(IDD_CONFIG_DIALOG), + m_reapack(reapack), m_config(reapack->config()), m_list(0) { } void Manager::onInit() { - // It would be better to not hard-code the column sizes like that... -#ifndef _WIN32 - const int URL_WIDTH = 360; -#else - const int URL_WIDTH = 300; -#endif - m_apply = getControl(IDAPPLY); disable(m_apply); m_list = createControl<ListView>(IDC_LIST, ListView::Columns{ {AUTO_STR("Name"), 110}, - {AUTO_STR("URL"), URL_WIDTH}, + {AUTO_STR("URL"), 350}, {AUTO_STR("State"), 60}, }); m_list->onActivate([=] { about(m_list->currentIndex()); }); + + refresh(); + +#ifdef LVSCW_AUTOSIZE_USEHEADER + m_list->resizeColumn(m_list->columnCount() - 1, LVSCW_AUTOSIZE_USEHEADER); +#endif } -void Manager::onCommand(const int id) +void Manager::onCommand(const short id, short) { switch(id) { case IDC_IMPORT: @@ -72,6 +73,13 @@ void Manager::onCommand(const int id) case ACTION_UNINSTALL: uninstall(); break; + case ACTION_AUTOINSTALL: + m_autoInstall = !m_autoInstall.value_or(m_config->autoInstall()); + enable(m_apply); + break; + case IDC_OPTIONS: + options(); + break; case IDOK: case IDAPPLY: if(confirm()) { @@ -151,16 +159,12 @@ void Manager::refresh() InhibitControl lock(m_list); m_list->clear(); - for(const Remote &remote : *m_reapack->config()->remotes()) { + for(const Remote &remote : *m_config->remotes()) { if(!m_uninstall.count(remote)) m_list->addRow(makeRow(remote)); } m_list->sort(); - -#ifdef LVSCW_AUTOSIZE_USEHEADER - m_list->resizeColumn(2, LVSCW_AUTOSIZE_USEHEADER); -#endif } void Manager::setRemoteEnabled(const bool enabled) @@ -225,6 +229,22 @@ void Manager::about(const int index) m_reapack->about(getRemote(index), handle()); } +void Manager::options() +{ + RECT rect; + GetWindowRect(getControl(IDC_OPTIONS), &rect); + + Menu menu; + + const UINT index = menu.addAction( + AUTO_STR("&Install new packages automatically"), ACTION_AUTOINSTALL); + + if(m_autoInstall.value_or(m_config->autoInstall())) + menu.check(index); + + menu.show(rect.left, rect.bottom - 1, handle()); +} + bool Manager::confirm() const { if(m_uninstall.empty()) @@ -246,6 +266,9 @@ bool Manager::confirm() const void Manager::apply() { + if(m_autoInstall) + m_config->setAutoInstall(m_autoInstall.value()); + for(const auto &pair : m_enableOverrides) { const Remote &remote = pair.first; const bool enable = pair.second; @@ -259,7 +282,7 @@ void Manager::apply() for(const Remote &remote : m_uninstall) m_reapack->uninstall(remote); - m_reapack->config()->write(); + m_config->write(); m_reapack->runTasks(); } @@ -288,5 +311,5 @@ Remote Manager::getRemote(const int index) const const ListView::Row &row = m_list->row(index); const string &remoteName = from_autostring(row[0]); - return m_reapack->config()->remotes()->get(remoteName); + return m_config->remotes()->get(remoteName); } diff --git a/src/manager.hpp b/src/manager.hpp @@ -22,9 +22,11 @@ #include "listview.hpp" +#include <boost/optional.hpp> #include <map> #include <set> +class Config; class ReaPack; class Remote; @@ -36,7 +38,7 @@ public: protected: void onInit() override; - void onCommand(int) override; + void onCommand(short, short) override; void onContextMenu(HWND, int x, int y) override; private: @@ -47,6 +49,7 @@ private: bool isRemoteEnabled(const Remote &) const; void uninstall(); void about(int index); + void options(); bool confirm() const; void apply(); @@ -54,10 +57,12 @@ private: HWND m_apply; ReaPack *m_reapack; + Config *m_config; ListView *m_list; std::map<Remote, bool> m_enableOverrides; std::set<Remote> m_uninstall; + boost::optional<bool> m_autoInstall; }; #endif diff --git a/src/menu.cpp b/src/menu.cpp @@ -17,6 +17,10 @@ #include "menu.hpp" +#ifndef MIIM_FTYPE // for SWELL +#define MIIM_FTYPE MIIM_TYPE +#endif + Menu::Menu(HMENU handle) : m_handle(handle), m_ownership(!handle) { @@ -91,9 +95,10 @@ void Menu::show(const int x, const int y, HWND parent) const x, y, 0, parent, nullptr); } -void Menu::disable() +void Menu::disableAll() { - setEnabled(false); + for(UINT i = 0; i < m_size; i++) + setEnabled(false, i); } void Menu::disable(const UINT index) @@ -101,9 +106,10 @@ void Menu::disable(const UINT index) setEnabled(false, index); } -void Menu::enable() +void Menu::enableAll() { - setEnabled(true); + for(UINT i = 0; i < m_size; i++) + setEnabled(false, i); } void Menu::enable(const UINT index) @@ -111,21 +117,46 @@ void Menu::enable(const UINT index) setEnabled(true, index); } -void Menu::setEnabled(const bool enabled) +void Menu::setEnabled(const bool enabled, const UINT index) { - for(UINT i = 0; i < m_size; i++) - setEnabled(enabled, i); + MENUITEMINFO mii{}; + mii.cbSize = sizeof(MENUITEMINFO); + + if(!GetMenuItemInfo(m_handle, index, true, &mii)) + return; + + mii.fMask |= MIIM_STATE; + mii.fState |= enabled ? MFS_ENABLED : MFS_DISABLED; + + SetMenuItemInfo(m_handle, index, true, &mii); } -void Menu::setEnabled(const bool enabled, const UINT index) +void Menu::check(const UINT index) { MENUITEMINFO mii{}; mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask |= MIIM_STATE; + + if(!GetMenuItemInfo(m_handle, index, true, &mii)) + return; - GetMenuItemInfo(m_handle, index, true, &mii); + mii.fState |= MFS_CHECKED; + + SetMenuItemInfo(m_handle, index, true, &mii); +} +void Menu::checkRadio(const UINT index) +{ + MENUITEMINFO mii{}; + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask |= MIIM_FTYPE; mii.fMask |= MIIM_STATE; - mii.fState |= enabled ? MFS_ENABLED : MFS_DISABLED; + + if(!GetMenuItemInfo(m_handle, index, true, &mii)) + return; + + mii.fType |= MFT_RADIOCHECK; + mii.fState |= MFS_CHECKED; SetMenuItemInfo(m_handle, index, true, &mii); } diff --git a/src/menu.hpp b/src/menu.hpp @@ -31,21 +31,23 @@ public: Menu(HMENU handle = 0); ~Menu(); - unsigned int size() { return m_size; } + UINT size() { return m_size; } bool empty() const { return m_size == 0; } - unsigned int addAction(const auto_char *label, int commandId); + UINT addAction(const auto_char *label, int commandId); void addSeparator(); Menu addMenu(const auto_char *label); void show(int x, int y, HWND parent) const; - void enable(); - void enable(unsigned int); - void disable(); - void disable(unsigned int); - void setEnabled(bool); - void setEnabled(bool, unsigned int); + void enableAll(); + void enable(UINT); + void disableAll(); + void disable(UINT); + void setEnabled(bool, UINT); + + void check(UINT); + void checkRadio(UINT); private: void append(MENUITEMINFO &); diff --git a/src/package.cpp b/src/package.cpp @@ -36,6 +36,22 @@ Package::Type Package::typeFor(const char *type) return UnknownType; } +string Package::displayType(const Type type) +{ + switch(type) { + case UnknownType: + return "Unknown"; + case ScriptType: + return "Script"; + case ExtensionType: + return "Extension"; + case EffectType: + return "Effect"; + } + + return {}; // MSVC is stupid +} + Package::Package(const Type type, const string &name, Category *cat) : m_category(cat), m_type(type), m_name(name) { @@ -48,6 +64,11 @@ string Package::fullName() const return m_category ? m_category->fullName() + "/" + m_name : m_name; } +string Package::displayType() const +{ + return displayType(m_type); +} + Package::~Package() { for(const Version *ver : m_versions) diff --git a/src/package.hpp b/src/package.hpp @@ -36,12 +36,14 @@ public: }; static Type typeFor(const char *); + static std::string displayType(Type); Package(const Type, const std::string &name, Category * = nullptr); ~Package(); Category *category() const { return m_category; } Type type() const { return m_type; } + std::string displayType() const; const std::string &name() const { return m_name; } std::string fullName() const; diff --git a/src/progress.cpp b/src/progress.cpp @@ -38,7 +38,7 @@ void Progress::onInit() SetWindowText(m_label, AUTO_STR("Initializing...")); } -void Progress::onCommand(const int id) +void Progress::onCommand(const short id, short) { switch(id) { case IDCANCEL: diff --git a/src/progress.hpp b/src/progress.hpp @@ -31,7 +31,7 @@ public: protected: void onInit() override; - void onCommand(int) override; + void onCommand(short, short) override; private: void addDownload(Download *); diff --git a/src/reapack.cpp b/src/reapack.cpp @@ -18,7 +18,7 @@ #include "reapack.hpp" #include "about.hpp" -#include "cleanup.hpp" +#include "browser.hpp" #include "config.hpp" #include "errors.hpp" #include "filesystem.hpp" @@ -62,9 +62,9 @@ static void CleanupTempFiles() #endif ReaPack::ReaPack(REAPER_PLUGIN_HINSTANCE instance) - : syncAction(), cleanupAction(), importAction(), configAction(), - m_transaction(nullptr), m_progress(nullptr), m_manager(nullptr), - m_import(nullptr), m_cleanup(nullptr), m_instance(instance) + : syncAction(), browseAction(),importAction(), configAction(), + m_transaction(nullptr), m_progress(nullptr), m_browser(nullptr), + m_import(nullptr), m_manager(nullptr), m_instance(instance) { m_mainWindow = GetMainHwnd(); m_useRootPath = new UseRootPath(GetResourcePath()); @@ -147,7 +147,7 @@ void ReaPack::synchronizeAll() return; for(const Remote &remote : remotes) - t->synchronize(remote); + t->synchronize(remote, m_config->autoInstall()); t->runTasks(); } @@ -177,6 +177,14 @@ void ReaPack::setRemoteEnabled(const Remote &original, const bool enable) }); } +void ReaPack::install(const Version *ver) +{ + if(!hitchhikeTransaction()) + return; + + m_transaction->install(ver); +} + void ReaPack::uninstall(const Remote &remote) { if(remote.isProtected()) @@ -252,6 +260,8 @@ void ReaPack::import(const Remote &remote) } else { enable(existing); + runTasks(); + m_config->write(); return; @@ -277,7 +287,6 @@ void ReaPack::manageRemotes() } m_manager = Dialog::Create<Manager>(m_instance, m_mainWindow, this); - m_manager->refresh(); m_manager->show(); m_manager->setCloseHandler([=] (INT_PTR) { @@ -288,7 +297,12 @@ void ReaPack::manageRemotes() void ReaPack::aboutSelf() { - about(m_config->remotes()->get("ReaPack"), m_mainWindow); + about("ReaPack", m_mainWindow); +} + +void ReaPack::about(const string &remoteName, HWND parent) +{ + about(m_config->remotes()->get(remoteName), parent); } void ReaPack::about(const Remote &remote, HWND parent) @@ -301,22 +315,25 @@ void ReaPack::about(const Remote &remote, HWND parent) if(ret == About::InstallResult) { enable(remote); - if(m_transaction) - m_transaction->synchronize(remote); + + if(m_transaction) // transaction is created by enable() + m_transaction->synchronize(remote, true); + + runTasks(); } }, parent); } -void ReaPack::cleanupPackages() +void ReaPack::browsePackages() { - if(m_cleanup) { - m_cleanup->setFocus(); + if(m_browser) { + m_browser->setFocus(); return; } else if(m_transaction) { ShowMessageBox( "This feature cannot be used while packages are being installed. " - "Try again later.", "Clean up packages", MB_OK + "Try again later.", "Browse packages", MB_OK ); return; } @@ -324,11 +341,11 @@ void ReaPack::cleanupPackages() const vector<Remote> &remotes = m_config->remotes()->getEnabled(); fetchIndexes(remotes, [=] (const vector<IndexPtr> &indexes) { - m_cleanup = Dialog::Create<Cleanup>(m_instance, m_mainWindow, indexes, this); - m_cleanup->show(); - m_cleanup->setCloseHandler([=] (INT_PTR) { - Dialog::Destroy(m_cleanup); - m_cleanup = nullptr; + m_browser = Dialog::Create<Browser>(m_instance, m_mainWindow, indexes, this); + m_browser->show(); + m_browser->setCloseHandler([=] (INT_PTR) { + Dialog::Destroy(m_browser); + m_browser = nullptr; }); }); } @@ -480,12 +497,15 @@ Transaction *ReaPack::createTransaction() return; LockDialog managerLock(m_manager); - LockDialog cleanupLock(m_cleanup); + LockDialog cleanupLock(m_browser); if(m_transaction->taskCount() == 0 && !receipt->hasErrors()) ShowMessageBox("Nothing to do!", "ReaPack", 0); else Dialog::Show<Report>(m_instance, m_mainWindow, receipt); + + if(m_browser && m_transaction->taskCount() > 0) + m_browser->reload(); }); m_transaction->setCleanupHandler([=] { diff --git a/src/reapack.hpp b/src/reapack.hpp @@ -28,7 +28,7 @@ #include <reaper_plugin.h> -class Cleanup; +class Browser; class Config; class DownloadQueue; class Import; @@ -50,7 +50,7 @@ public: static const std::string BUILDTIME; gaccel_register_t syncAction; - gaccel_register_t cleanupAction; + gaccel_register_t browseAction; gaccel_register_t importAction; gaccel_register_t configAction; @@ -66,14 +66,16 @@ public: void setRemoteEnabled(const Remote &, bool enable); void enable(const Remote &r) { setRemoteEnabled(r, true); } void disable(const Remote &r) { setRemoteEnabled(r, false); } + void install(const Version *); void uninstall(const Remote &); void uninstall(const Registry::Entry &); void importRemote(); void import(const Remote &); void manageRemotes(); void aboutSelf(); + void about(const std::string &, HWND parent); void about(const Remote &, HWND parent); - void cleanupPackages(); + void browsePackages(); void fetchIndexes(const std::vector<Remote> &, const IndexesCallback &, HWND = nullptr); @@ -94,9 +96,9 @@ private: Config *m_config; Transaction *m_transaction; Progress *m_progress; - Manager *m_manager; + Browser *m_browser; Import *m_import; - Cleanup *m_cleanup; + Manager *m_manager; REAPER_PLUGIN_HINSTANCE m_instance; HWND m_mainWindow; diff --git a/src/registry.cpp b/src/registry.cpp @@ -34,21 +34,22 @@ Registry::Registry(const Path &path) // entry queries m_insertEntry = m_db.prepare( - "INSERT INTO entries VALUES(NULL, ?, ?, ?, ?, ?);" + "INSERT INTO entries(remote, category, package, type, version, author)" + "VALUES(?, ?, ?, ?, ?, ?);" ); m_updateEntry = m_db.prepare( - "UPDATE entries SET type = ?, version = ? WHERE id = ?" + "UPDATE entries SET type = ?, version = ?, author = ? WHERE id = ?" ); m_findEntry = m_db.prepare( - "SELECT id, remote, category, package, type, version FROM entries " + "SELECT id, remote, category, package, type, version, author FROM entries " "WHERE remote = ? AND category = ? AND package = ? " "LIMIT 1" ); m_allEntries = m_db.prepare( - "SELECT id, category, package, type, version " + "SELECT id, category, package, type, version, author " "FROM entries WHERE remote = ?" ); m_forgetEntry = m_db.prepare("DELETE FROM entries WHERE id = ?"); @@ -90,6 +91,7 @@ void Registry::migrate() " package TEXT NOT NULL," " type INTEGER NOT NULL," " version TEXT NOT NULL," + " author TEXT NOT NULL," " UNIQUE(remote, category, package)" ");" @@ -106,7 +108,8 @@ void Registry::migrate() // current schema version break; default: - throw reapack_error("database was created with a newer version of ReaPack"); + throw reapack_error( + "The package registry was created by a newer version of ReaPack"); } m_db.commit(); @@ -133,7 +136,8 @@ auto Registry::push(const Version *ver, vector<Path> *conflicts) -> Entry if(entryId) { m_updateEntry->bind(1, pkg->type()); m_updateEntry->bind(2, ver->name()); - m_updateEntry->bind(3, entryId); + m_updateEntry->bind(3, ver->displayAuthor()); + m_updateEntry->bind(4, entryId); m_updateEntry->exec(); } else { @@ -142,6 +146,7 @@ auto Registry::push(const Version *ver, vector<Path> *conflicts) -> Entry m_insertEntry->bind(3, pkg->name()); m_insertEntry->bind(4, pkg->type()); m_insertEntry->bind(5, ver->name()); + m_insertEntry->bind(6, ver->displayAuthor()); m_insertEntry->exec(); entryId = m_db.lastInsertId(); @@ -180,18 +185,13 @@ auto Registry::push(const Version *ver, vector<Path> *conflicts) -> Entry else { release(); return {entryId, ri->name(), cat->name(), - pkg->name(), pkg->type(), ver->code()}; + pkg->name(), pkg->type(), ver->name(), ver->code(), ver->displayAuthor()}; } } auto Registry::getEntry(const Package *pkg) const -> Entry { - int id = 0; - string remote; - string category; - string package; - Package::Type type = Package::UnknownType; - Version::Code version = 0; + Entry entry{}; const Category *cat = pkg->category(); const Index *ri = cat->index(); @@ -203,17 +203,19 @@ auto Registry::getEntry(const Package *pkg) const -> Entry m_findEntry->exec([&] { int col = 0; - id = m_findEntry->intColumn(col++); - remote = m_findEntry->stringColumn(col++); - category = m_findEntry->stringColumn(col++); - package = m_findEntry->stringColumn(col++); - type = static_cast<Package::Type>(m_findEntry->intColumn(col++)); - Version::parse(m_findEntry->stringColumn(col++), &version); + entry.id = m_findEntry->intColumn(col++); + entry.remote = m_findEntry->stringColumn(col++); + entry.category = m_findEntry->stringColumn(col++); + entry.package = m_findEntry->stringColumn(col++); + entry.type = static_cast<Package::Type>(m_findEntry->intColumn(col++)); + entry.versionName = m_findEntry->stringColumn(col++); + Version::parse(entry.versionName, &entry.versionCode); + entry.author = m_findEntry->stringColumn(col++); return false; }); - return {id, remote, category, package, type, version}; + return entry; } auto Registry::getEntries(const string &remoteName) const -> vector<Entry> @@ -224,15 +226,17 @@ auto Registry::getEntries(const string &remoteName) const -> vector<Entry> m_allEntries->exec([&] { int col = 0; - const int id = m_allEntries->intColumn(col++); - const string &category = m_allEntries->stringColumn(col++); - const string &package = m_allEntries->stringColumn(col++); - const Package::Type type = - static_cast<Package::Type>(m_allEntries->intColumn(col++)); - Version::Code version = 0; - Version::parse(m_allEntries->stringColumn(col++), &version); - - list.push_back({id, remoteName, category, package, type, version}); + Entry entry{}; + entry.id = m_allEntries->intColumn(col++); + entry.remote = remoteName; + entry.category = m_allEntries->stringColumn(col++); + entry.package = m_allEntries->stringColumn(col++); + entry.type = static_cast<Package::Type>(m_allEntries->intColumn(col++)); + entry.versionName = m_allEntries->stringColumn(col++); + Version::parse(entry.versionName, &entry.versionCode); + entry.author = m_allEntries->stringColumn(col++); + + list.push_back(entry); return true; }); diff --git a/src/registry.hpp b/src/registry.hpp @@ -38,7 +38,9 @@ public: std::string category; std::string package; Package::Type type; - Version::Code version; + std::string versionName; + Version::Code versionCode; + std::string author; }; Registry(const Path &path = Path()); diff --git a/src/report.cpp b/src/report.cpp @@ -60,7 +60,7 @@ void ReportDialog::printVersion(const Version *ver) if(!ver->author().empty()) stream() << " by " << ver->author(); - const string &date = ver->formattedDate(); + const string &date = ver->displayTime(); if(!date.empty()) stream() << " – " << date; @@ -92,7 +92,7 @@ void Report::fillReport() const size_t errors = m_receipt->errors().size(); stream() - << installs << " new packages, " + << installs << " installed packages, " << updates << " updates, " << removals << " removed files and " << errors << " errors" @@ -111,7 +111,7 @@ void Report::fillReport() printErrors(); if(installs) - printNewPackages(); + printInstalls(); if(updates) printUpdates(); @@ -120,9 +120,9 @@ void Report::fillReport() printRemovals(); } -void Report::printNewPackages() +void Report::printInstalls() { - printHeader("New packages"); + printHeader("Installed packages"); for(const InstallTicket &ticket : m_receipt->installs()) stream() << ticket.version->fullName() << NL; @@ -132,21 +132,24 @@ void Report::printUpdates() { printHeader("Updates"); + const auto start = stream().tellp(); + 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(stream().tellp() != start) + stream() << NL; + stream() << pkg->fullName() << ':' << NL; for(const Version *ver : versions | boost::adaptors::reversed) { - if(ver->code() <= regEntry.version) + if(ver->code() <= regEntry.versionCode) break; printVersion(ver); } - - stream() << NL; } } @@ -154,10 +157,14 @@ void Report::printErrors() { printHeader("Errors"); + const auto start = stream().tellp(); + for(const Receipt::Error &err : m_receipt->errors()) { + if(stream().tellp() != start) + stream() << NL; + stream() << err.title << ':' << NL; printIndented(err.message); - stream() << "\n"; } } @@ -168,3 +175,22 @@ void Report::printRemovals() for(const Path &path : m_receipt->removals()) stream() << path.join() << NL; } + +History::History(const Package *pkg) + : ReportDialog(), m_package(pkg) +{ +} + +void History::fillReport() +{ + SetWindowText(handle(), AUTO_STR("Package History")); + SetWindowText(getControl(IDC_LABEL), + make_autostring(m_package->name()).c_str()); + + for(const Version *ver : m_package->versions() | boost::adaptors::reversed) { + if(stream().tellp()) + stream() << NL; + + printVersion(ver); + } +} diff --git a/src/report.hpp b/src/report.hpp @@ -22,6 +22,7 @@ #include <sstream> +class Package; class Receipt; class Version; @@ -53,7 +54,7 @@ protected: void fillReport() override; private: - void printNewPackages(); + void printInstalls(); void printUpdates(); void printErrors(); void printRemovals(); @@ -61,4 +62,15 @@ private: const Receipt *m_receipt; }; +class History : public ReportDialog { +public: + History(const Package *); + +protected: + void fillReport() override; + +private: + const Package *m_package; +}; + #endif diff --git a/src/resource.hpp b/src/resource.hpp @@ -39,7 +39,7 @@ #define IDD_CONFIG_DIALOG 102 #define IDD_ABOUT_DIALOG 103 #define IDD_IMPORT_DIALOG 104 -#define IDD_CLEANUP_DIALOG 105 +#define IDD_BROWSER_DIALOG 105 #define IDC_LABEL 200 #define IDC_LABEL2 201 @@ -57,5 +57,15 @@ #define IDC_PACKAGES 220 #define IDC_GROUPBOX 221 #define IDC_URL 222 +#define IDC_SCRIPTS 223 +#define IDC_EFFECTS 224 +#define IDC_EXTENSIONS 225 +#define IDC_FILTER 226 +#define IDC_CLEAR 227 +#define IDC_DISPLAY 228 +#define IDC_SELECT 229 +#define IDC_UNSELECT 230 +#define IDC_ACTION 231 +#define IDC_OPTIONS 232 #endif diff --git a/src/resource.rc b/src/resource.rc @@ -34,6 +34,7 @@ BEGIN CONTROL "", IDC_LIST, WC_LISTVIEW, LVS_REPORT | LVS_SHOWSELALWAYS | WS_BORDER | WS_TABSTOP, 5, 18, 320, 136 PUSHBUTTON "&Import...", IDC_IMPORT, 5, 160, 45, 14 + PUSHBUTTON "&Options...", IDC_OPTIONS, 53, 160, 45, 14 DEFPUSHBUTTON "&OK", IDOK, 198, 160, 40, 14 PUSHBUTTON "&Cancel", IDCANCEL, 241, 160, 40, 14 PUSHBUTTON "&Apply", IDAPPLY, 284, 160, 40, 14 @@ -59,7 +60,7 @@ BEGIN WS_VSCROLL | ES_MULTILINE | ES_READONLY | NOT WS_TABSTOP PUSHBUTTON "&Website", IDC_WEBSITE, 5, 250, 45, 14 PUSHBUTTON "&Donate...", IDC_DONATE, 54, 250, 45, 14 - PUSHBUTTON "&Install this repository", IDC_INSTALL, 286, 250, 120, 14 + PUSHBUTTON "&Install/update this repository", IDC_INSTALL, 286, 250, 120, 14 DEFPUSHBUTTON "&Close", IDOK, 409, 250, 45, 14 END @@ -77,16 +78,26 @@ BEGIN PUSHBUTTON "&Cancel", IDCANCEL, 244, 50, 40, 14 END -IDD_CLEANUP_DIALOG DIALOGEX 0, 0, 350, 200 +IDD_BROWSER_DIALOG DIALOGEX 0, 0, 500, 250 STYLE DIALOG_STYLE FONT DIALOG_FONT -CAPTION "ReaPack: Clean up packages" +CAPTION "ReaPack: Package Browser" BEGIN - LTEXT "The following packages could not be found in any enabled the repositories:", - IDC_LABEL, 5, 5, 340, 10 - CONTROL "", IDC_LIST, WC_LISTVIEW, LVS_REPORT | LVS_SHOWSELALWAYS | - LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP, 5, 18, 340, 156 - LTEXT "", IDC_LABEL2, 5, 183, 150, 10 - DEFPUSHBUTTON "&Uninstall selected packages", IDOK, 176, 180, 120, 14 - PUSHBUTTON "&Cancel", IDCANCEL, 299, 180, 45, 14 + LTEXT "Filter:", IDC_LABEL, 5, 7, 20, 10 + EDITTEXT IDC_FILTER, 25, 4, 200, 14, ES_AUTOHSCROLL | WS_TABSTOP + PUSHBUTTON "Cl&ear", IDC_CLEAR, 227, 4, 32, 14 + LTEXT "Display:", IDC_LABEL2, 266, 7, 30, 10 + COMBOBOX IDC_DISPLAY, 294, 5, 55, 54, CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Type:", IDC_LABEL3, 356, 7, 20, 10 + CHECKBOX "&Scripts", IDC_SCRIPTS, 376, 4, 35, 14, BS_AUTOCHECKBOX | WS_TABSTOP + CHECKBOX "&Effects", IDC_EFFECTS, 413, 4, 35, 14, BS_AUTOCHECKBOX | WS_TABSTOP + CHECKBOX "&Extensions", IDC_EXTENSIONS, 450, 4, 50, 14, BS_AUTOCHECKBOX | WS_TABSTOP + CONTROL "", IDC_PACKAGES, WC_LISTVIEW, LVS_REPORT | LVS_SHOWSELALWAYS | + WS_BORDER | WS_TABSTOP, 5, 22, 490, 204 + PUSHBUTTON "&Select all", IDC_SELECT, 5, 230, 45, 14 + PUSHBUTTON "&Unselect all", IDC_UNSELECT, 53, 230, 45, 14 + PUSHBUTTON "&Action...", IDC_ACTION, 101, 230, 45, 14 + DEFPUSHBUTTON "&OK", IDOK, 368, 230, 40, 14 + PUSHBUTTON "&Cancel", IDCANCEL, 411, 230, 40, 14 + PUSHBUTTON "&Apply", IDAPPLY, 454, 230, 40, 14 END diff --git a/src/transaction.cpp b/src/transaction.cpp @@ -49,7 +49,7 @@ Transaction::Transaction() if(m_installQueue.empty()) finish(); else - installQueued(); + runTasks(); }); } @@ -61,11 +61,10 @@ Transaction::~Transaction() delete m_registry; } -void Transaction::synchronize(const Remote &remote, const bool isUserAction) +void Transaction::synchronize(const Remote &remote, const bool autoInstall) { - // show the report dialog even if no task are ran - if(isUserAction) - m_receipt.setEnabled(true); + // show the report dialog or "nothing to do" even if no task are ran + m_receipt.setEnabled(true); fetchIndex(remote, [=] { IndexPtr ri; @@ -87,12 +86,29 @@ void Transaction::synchronize(const Remote &remote, const bool isUserAction) m_registry->savepoint(); for(const Package *pkg : ri->packages()) - install(pkg->lastVersion()); + synchronize(pkg, autoInstall); m_registry->restore(); }); } +void Transaction::synchronize(const Package *pkg, const bool autoInstall) +{ + const auto &regEntry = m_registry->getEntry(pkg); + + if(!regEntry.id && !autoInstall) + return; + + const Version *ver = pkg->lastVersion(); + + if(regEntry.versionCode == ver->code()) { + if(allFilesExists(ver->files())) + return; // latest version is really installed, nothing to do here! + } + + install(ver, regEntry); +} + void Transaction::fetchIndex(const Remote &remote, const IndexCallback &cb) { // add the callback to the list, and start the download if it's the first one @@ -127,19 +143,20 @@ void Transaction::saveIndex(Download *dl, const string &name) void Transaction::install(const Version *ver) { - const Package *pkg = ver->package(); - const Registry::Entry &regEntry = m_registry->getEntry(pkg); + install(ver, m_registry->getEntry(ver->package())); +} - InstallTicket::Type type = InstallTicket::Install; +void Transaction::install(const Version *ver, + const Registry::Entry &regEntry) +{ + m_receipt.setEnabled(true); - if(regEntry.id) { - if(regEntry.version == ver->code()) { - if(allFilesExists(ver->files())) - return; // latest version is really installed, nothing to do here! - } - else if(regEntry.version < ver->code()) - type = InstallTicket::Upgrade; - } + InstallTicket::Type type; + + if(regEntry.id && regEntry.versionCode < ver->code()) + type = InstallTicket::Upgrade; + else + type = InstallTicket::Install; // prevent file conflicts (don't worry, the registry push is reverted later) try { @@ -163,23 +180,13 @@ void Transaction::install(const Version *ver) } // all green! (pronounce with a japanese accent) - IndexPtr ri = pkg->category()->index()->shared_from_this(); + IndexPtr ri = ver->package()->category()->index()->shared_from_this(); if(!m_indexes.count(ri)) m_indexes.insert(ri); m_installQueue.push({type, ver, regEntry}); } -void Transaction::installQueued() -{ - while(!m_installQueue.empty()) { - installTicket(m_installQueue.front()); - m_installQueue.pop(); - } - - runTasks(); -} - void Transaction::installTicket(const InstallTicket &ticket) { const Version *ver = ticket.version; @@ -312,6 +319,11 @@ void Transaction::addTask(Task *task) void Transaction::runTasks() { + while(!m_installQueue.empty()) { + installTicket(m_installQueue.front()); + m_installQueue.pop(); + } + while(!m_taskQueue.empty()) { m_taskQueue.front()->start(); m_taskQueue.pop(); diff --git a/src/transaction.hpp b/src/transaction.hpp @@ -56,7 +56,7 @@ public: void onFinish(const VoidSignal::slot_type &slot) { m_onFinish.connect(slot); } void setCleanupHandler(const CleanupHandler &cb) { m_cleanupHandler = cb; } - void synchronize(const Remote &, bool userAction = true); + void synchronize(const Remote &, bool autoInstall); void install(const Version *); void uninstall(const Remote &); void uninstall(const Registry::Entry &); @@ -78,7 +78,8 @@ private: void fetchIndex(const Remote &, const IndexCallback &cb); void saveIndex(Download *, const std::string &remoteName); - void installQueued(); + void synchronize(const Package *, bool autoInstall); + void install(const Version *, const Registry::Entry &); void installTicket(const InstallTicket &); void addTask(Task *); diff --git a/src/version.cpp b/src/version.cpp @@ -72,10 +72,18 @@ Version::~Version() string Version::fullName() const { - const string fName = "v" + m_name; + const string fName = 'v' + m_name; return m_package ? m_package->fullName() + " " + fName : fName; } +string Version::displayAuthor() const +{ + if(m_author.empty()) + return "Unknown"; + else + return m_author; +} + void Version::addSource(Source *source) { const Source::Platform p = source->platform(); @@ -135,7 +143,7 @@ void Version::setTime(const char *iso8601) m_time.tm_mday = day; } -string Version::formattedDate() const +string Version::displayTime() const { if(m_time.tm_year == 0) return {}; diff --git a/src/version.hpp b/src/version.hpp @@ -44,10 +44,11 @@ public: void setAuthor(const std::string &author) { m_author = author; } const std::string &author() const { return m_author; } + std::string displayAuthor() const; void setTime(const char *iso8601); const std::tm &time() const { return m_time; } - std::string formattedDate() const; + std::string displayTime() const; void setChangelog(const std::string &); const std::string &changelog() const { return m_changelog; } diff --git a/test/package.cpp b/test/package.cpp @@ -26,6 +26,28 @@ TEST_CASE("package type from string", M) { REQUIRE(Package::typeFor("effect") == Package::EffectType); } +TEST_CASE("package type to string", M) { + SECTION("unknown") { + REQUIRE("Unknown" == Package::displayType(Package::UnknownType)); + REQUIRE("Unknown" == Package(Package::UnknownType, "test").displayType()); + } + + SECTION("script") { + REQUIRE("Script" == Package::displayType(Package::ScriptType)); + REQUIRE("Script" == Package(Package::ScriptType, "test").displayType()); + } + + SECTION("extension") { + REQUIRE("Extension" == Package::displayType(Package::ExtensionType)); + REQUIRE("Extension" == Package(Package::ExtensionType, "test").displayType()); + } + + SECTION("effect") { + REQUIRE("Effect" == Package::displayType(Package::EffectType)); + REQUIRE("Effect" == Package(Package::EffectType, "test").displayType()); + } +} + TEST_CASE("empty package name", M) { try { Package pack(Package::ScriptType, string()); diff --git a/test/registry.cpp b/test/registry.cpp @@ -18,6 +18,7 @@ static const char *M = "[registry]"; Category cat("Category Name", &ri); \ Package pkg(Package::ScriptType, "Hello", &cat); \ Version *ver = new Version("1.0", &pkg); \ + ver->setAuthor("John Doe"); \ Source *src = new Source(Source::GenericPlatform, "file", "url", ver); \ ver->addSource(src); \ pkg.addVersion(ver); @@ -29,10 +30,10 @@ TEST_CASE("query uninstalled package", M) { const Registry::Entry &res = reg.getEntry(&pkg); REQUIRE(res.id == 0); - REQUIRE(res.version == 0); + REQUIRE(res.versionCode == 0); } -TEST_CASE("query installed pacakge", M) { +TEST_CASE("query installed package", M) { MAKE_PACKAGE Registry reg; @@ -43,7 +44,9 @@ TEST_CASE("query installed pacakge", M) { REQUIRE(entry.category == "Category Name"); REQUIRE(entry.package == "Hello"); REQUIRE(entry.type == Package::ScriptType); - REQUIRE(entry.version == Version("1.0").code()); + REQUIRE(entry.versionName == "1.0"); + REQUIRE(entry.versionCode == Version("1.0").code()); + REQUIRE(entry.author == "John Doe"); const Registry::Entry &selectEntry = reg.getEntry(&pkg); REQUIRE(selectEntry.id == entry.id); @@ -51,7 +54,9 @@ TEST_CASE("query installed pacakge", M) { REQUIRE(selectEntry.category == entry.category); REQUIRE(selectEntry.package == entry.package); REQUIRE(selectEntry.type == entry.type); - REQUIRE(selectEntry.version == entry.version); + REQUIRE(selectEntry.versionName == entry.versionName); + REQUIRE(selectEntry.versionCode == entry.versionCode); + REQUIRE(selectEntry.author == entry.author); } TEST_CASE("bump version", M) { @@ -65,11 +70,13 @@ TEST_CASE("bump version", M) { pkg.addVersion(ver2); const Registry::Entry &entry1 = reg.getEntry(&pkg); - REQUIRE(entry1.version == Version("1.0").code()); + REQUIRE(entry1.versionName == "1.0"); + CHECK(entry1.author == "John Doe"); reg.push(ver2); const Registry::Entry &entry2 = reg.getEntry(&pkg); - REQUIRE(entry2.version == Version("2.0").code()); + REQUIRE(entry2.versionName == "2.0"); + CHECK(entry2.author == "Unknown"); REQUIRE(entry2.id == entry1.id); } @@ -104,7 +111,9 @@ TEST_CASE("query all packages", M) { REQUIRE(entries[0].category == "Category Name"); REQUIRE(entries[0].package == "Hello"); REQUIRE(entries[0].type == Package::ScriptType); - REQUIRE(entries[0].version == Version("1.0").code()); + REQUIRE(entries[0].versionName == "1.0"); + REQUIRE(entries[0].versionCode == Version("1.0").code()); + REQUIRE(entries[0].author == "John Doe"); } TEST_CASE("forget registry entry", M) { diff --git a/test/version.cpp b/test/version.cpp @@ -219,25 +219,25 @@ TEST_CASE("version date", M) { CHECK(ver.time().tm_year == 0); CHECK(ver.time().tm_mon == 0); CHECK(ver.time().tm_mday == 0); - CHECK(ver.formattedDate() == ""); + CHECK(ver.displayTime() == ""); SECTION("valid") { ver.setTime("2016-02-12T01:16:40Z"); REQUIRE(ver.time().tm_year == 2016 - 1900); REQUIRE(ver.time().tm_mon == 2 - 1); REQUIRE(ver.time().tm_mday == 12); - REQUIRE(ver.formattedDate() != ""); + REQUIRE(ver.displayTime() != ""); } SECTION("garbage") { ver.setTime("hello world"); REQUIRE(ver.time().tm_year == 0); - REQUIRE(ver.formattedDate() == ""); + REQUIRE(ver.displayTime() == ""); } SECTION("out of range") { ver.setTime("2016-99-99T99:99:99Z"); - ver.formattedDate(); // no crash + ver.displayTime(); // no crash } }