commit 3c49cd0e6992bd4da9746bb1551367805a01caa4
parent 3b88d5745a67fd1f135ba41cdf03dde084dd10b8
Author: cfillion <cfillion@users.noreply.github.com>
Date: Mon, 14 Mar 2016 22:25:03 -0400
implement new "Clean up packages" feature
Diffstat:
14 files changed, 350 insertions(+), 15 deletions(-)
diff --git a/src/cleanup.cpp b/src/cleanup.cpp
@@ -0,0 +1,174 @@
+/* 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 orphaned 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
@@ -0,0 +1,60 @@
+/* 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/index.cpp b/src/index.cpp
@@ -134,12 +134,23 @@ void Index::addCategory(const Category *cat)
if(cat->packages().empty())
return;
+ m_catMap.insert({cat->name(), m_categories.size()});
m_categories.push_back(cat);
m_packages.insert(m_packages.end(),
cat->packages().begin(), cat->packages().end());
}
+const Category *Index::category(const string &name) const
+{
+ const auto it = m_catMap.find(name);
+
+ if(it == m_catMap.end())
+ return nullptr;
+ else
+ return category(it->second);
+}
+
void Index::addLink(const LinkType type, const Link &link)
{
if(boost::algorithm::starts_with(link.url, "http"))
@@ -187,5 +198,16 @@ void Category::addPackage(const Package *pkg)
else if(pkg->versions().empty())
return;
+ m_pkgMap.insert({pkg->name(), m_packages.size()});
m_packages.push_back(pkg);
}
+
+const Package *Category::package(const string &name) const
+{
+ const auto it = m_pkgMap.find(name);
+
+ if(it == m_pkgMap.end())
+ return nullptr;
+ else
+ return package(it->second);
+}
diff --git a/src/index.hpp b/src/index.hpp
@@ -21,6 +21,7 @@
#include <map>
#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
#include "package.hpp"
@@ -63,6 +64,7 @@ public:
void addCategory(const Category *cat);
const CategoryList &categories() const { return m_categories; }
const Category *category(size_t i) const { return m_categories[i]; }
+ const Category *category(const std::string &name) const;
const PackageList &packages() const { return m_packages; }
@@ -74,6 +76,8 @@ private:
LinkMap m_links;
CategoryList m_categories;
PackageList m_packages;
+
+ std::unordered_map<std::string, size_t> m_catMap;
};
class Category {
@@ -88,12 +92,15 @@ public:
void addPackage(const Package *pack);
const PackageList &packages() const { return m_packages; }
const Package *package(size_t i) const { return m_packages[i]; }
+ const Package *package(const std::string &name) const;
private:
const Index *m_index;
std::string m_name;
PackageList m_packages;
+
+ std::unordered_map<std::string, size_t> m_pkgMap;
};
#endif
diff --git a/src/listview.cpp b/src/listview.cpp
@@ -183,6 +183,12 @@ void ListView::clear()
m_rows.clear();
}
+void ListView::setSelected(const int index, const bool select)
+{
+ ListView_SetItemState(handle(), translate(index),
+ select ? LVIS_SELECTED : 0, LVIS_SELECTED);
+}
+
bool ListView::hasSelection() const
{
return selectionSize() > 0;
diff --git a/src/listview.hpp b/src/listview.hpp
@@ -45,6 +45,11 @@ public:
void sort();
void sortByColumn(int index, SortOrder order = AscendingOrder);
void clear();
+ void setSelected(int index, bool select);
+ void select(int index) { setSelected(index, true); }
+ void unselect(int index) { setSelected(index, false); }
+ void selectAll() { select(-1); }
+ void unselectAll() { unselect(-1); }
int selectionSize() const;
bool hasSelection() const;
@@ -52,6 +57,7 @@ public:
std::vector<int> selection() const;
int itemUnderMouse() const;
int rowCount() const { return (int)m_rows.size(); }
+ bool empty() const { return rowCount() < 1; }
void onSelect(const VoidSignal::slot_type &slot) { m_onSelect.connect(slot); }
void onActivate(const VoidSignal::slot_type &slot) { m_onActivate.connect(slot); }
diff --git a/src/main.cpp b/src/main.cpp
@@ -84,6 +84,9 @@ static void menuHook(const char *name, HMENU handle, int f)
menu.addAction(AUTO_STR("Import a repository..."),
NamedCommandLookup("_REAPACK_IMPORT"));
+ menu.addAction(AUTO_STR("Clean up packages..."),
+ NamedCommandLookup("_REAPACK_CLEANUP"));
+
menu.addAction(AUTO_STR("Manage repositories..."),
NamedCommandLookup("_REAPACK_MANAGE"));
@@ -159,6 +162,9 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
reapack->setupAction("REAPACK_IMPORT", "ReaPack: Import a repository...",
&reapack->importAction, bind(&ReaPack::importRemote, reapack));
+ reapack->setupAction("REAPACK_CLEANUP", "ReaPack: Clean up packages...",
+ &reapack->cleanupAction, bind(&ReaPack::cleanupPackages, reapack));
+
reapack->setupAction("REAPACK_MANAGE", "ReaPack: Manage repositories...",
&reapack->configAction, bind(&ReaPack::manageRemotes, reapack));
diff --git a/src/reapack.cpp b/src/reapack.cpp
@@ -18,6 +18,7 @@
#include "reapack.hpp"
#include "about.hpp"
+#include "cleanup.hpp"
#include "config.hpp"
#include "errors.hpp"
#include "filesystem.hpp"
@@ -61,9 +62,9 @@ static void CleanupTempFiles()
#endif
ReaPack::ReaPack(REAPER_PLUGIN_HINSTANCE instance)
- : syncAction(), importAction(), configAction(),
+ : syncAction(), importAction(), cleanupAction(), configAction(),
m_transaction(nullptr), m_progress(nullptr), m_manager(nullptr),
- m_import(nullptr), m_instance(instance)
+ m_import(nullptr), m_cleanup(nullptr), m_instance(instance)
{
m_mainWindow = GetMainHwnd();
m_useRootPath = new UseRootPath(GetResourcePath());
@@ -198,6 +199,12 @@ void ReaPack::uninstall(const Remote &remote)
});
}
+void ReaPack::uninstall(const Registry::Entry &entry)
+{
+ if(hitchhikeTransaction())
+ m_transaction->uninstall(entry);
+}
+
void ReaPack::importRemote()
{
if(m_import) {
@@ -302,10 +309,27 @@ void ReaPack::about(const Remote &remote, HWND parent)
void ReaPack::cleanupPackages()
{
+ if(m_cleanup) {
+ m_cleanup->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
+ );
+ return;
+ }
+
const vector<Remote> &remotes = m_config->remotes()->getEnabled();
fetchIndexes(remotes, [=] (const vector<IndexPtr> &indexes) {
- printf("%zu indexes loaded\n", indexes.size());
+ 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;
+ });
});
}
diff --git a/src/reapack.hpp b/src/reapack.hpp
@@ -19,6 +19,7 @@
#define REAPACK_REAPACK_HPP
#include "path.hpp"
+#include "registry.hpp"
#include <functional>
#include <map>
@@ -27,6 +28,7 @@
#include <reaper_plugin.h>
+class Cleanup;
class Config;
class DownloadQueue;
class Import;
@@ -49,6 +51,7 @@ public:
gaccel_register_t syncAction;
gaccel_register_t importAction;
+ gaccel_register_t cleanupAction;
gaccel_register_t configAction;
ReaPack(REAPER_PLUGIN_HINSTANCE);
@@ -64,11 +67,13 @@ public:
void enable(const Remote &r) { setRemoteEnabled(r, true); }
void disable(const Remote &r) { setRemoteEnabled(r, false); }
void uninstall(const Remote &);
+ void uninstall(const Registry::Entry &);
void importRemote();
void import(const Remote &);
void manageRemotes();
void aboutSelf();
void about(const Remote &, HWND parent);
+ void cleanupPackages();
void fetchIndexes(const std::vector<Remote> &,
const IndexesCallback &, HWND = nullptr);
@@ -91,6 +96,7 @@ private:
Progress *m_progress;
Manager *m_manager;
Import *m_import;
+ Cleanup *m_cleanup;
REAPER_PLUGIN_HINSTANCE m_instance;
HWND m_mainWindow;
diff --git a/src/resource.hpp b/src/resource.hpp
@@ -39,6 +39,7 @@
#define IDD_CONFIG_DIALOG 102
#define IDD_ABOUT_DIALOG 103
#define IDD_IMPORT_DIALOG 104
+#define IDD_CLEANUP_DIALOG 105
#define IDC_LABEL 200
#define IDC_LABEL2 201
diff --git a/src/resource.rc b/src/resource.rc
@@ -76,3 +76,17 @@ BEGIN
DEFPUSHBUTTON "&OK", IDOK, 201, 50, 40, 14
PUSHBUTTON "&Cancel", IDCANCEL, 244, 50, 40, 14
END
+
+IDD_CLEANUP_DIALOG DIALOGEX 0, 0, 350, 200
+STYLE DIALOG_STYLE
+FONT DIALOG_FONT
+CAPTION "ReaPack: Clean up packages"
+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
+END
diff --git a/src/transaction.cpp b/src/transaction.cpp
@@ -229,22 +229,26 @@ void Transaction::uninstall(const Remote &remote)
if(entries.empty())
return;
- vector<Path> allFiles;
+ for(const auto &entry : entries)
+ uninstall(entry);
- for(const auto &entry : entries) {
- const set<Path> &files = m_registry->getFiles(entry);
- for(const Path &path : files) {
- if(FS::exists(path))
- allFiles.push_back(path);
- }
+}
- registerInHost(false, entry);
+void Transaction::uninstall(const Registry::Entry &entry)
+{
+ vector<Path> files;
- // forget the package even if some files cannot be removed
- m_registry->forget(entry);
+ for(const Path &path : m_registry->getFiles(entry)) {
+ if(FS::exists(path))
+ files.push_back(path);
}
- RemoveTask *task = new RemoveTask(allFiles, this);
+ registerInHost(false, entry);
+
+ // forget the package even if some files cannot be removed
+ m_registry->forget(entry);
+
+ RemoveTask *task = new RemoveTask(files, this);
task->onCommit([=] { m_receipt.addRemovals(task->removedFiles()); });
addTask(task);
diff --git a/src/transaction.hpp b/src/transaction.hpp
@@ -59,6 +59,7 @@ public:
void synchronize(const Remote &, bool userAction = true);
void install(const Version *);
void uninstall(const Remote &);
+ void uninstall(const Registry::Entry &);
void registerAll(const Remote &);
void runTasks();
diff --git a/test/index.cpp b/test/index.cpp
@@ -94,7 +94,7 @@ TEST_CASE("unicode index path", M) {
REQUIRE(ri->name() == "Новая папка");
}
-TEST_CASE("add category", M) {
+TEST_CASE("add a category", M) {
Index ri("a");
Category *cat = new Category("a", &ri);
Package *pack = new Package(Package::ScriptType, "name", cat);
@@ -106,10 +106,12 @@ TEST_CASE("add category", M) {
cat->addPackage(pack);
CHECK(ri.categories().size() == 0);
+ CHECK(ri.category("a") == nullptr);
ri.addCategory(cat);
REQUIRE(ri.categories().size() == 1);
+ REQUIRE(ri.category("a") == cat);
REQUIRE(ri.packages() == cat->packages());
}
@@ -145,10 +147,12 @@ TEST_CASE("add a package", M) {
pack->addVersion(ver);
CHECK(cat.packages().size() == 0);
+ CHECK(cat.package("name") == nullptr);
cat.addPackage(pack);
REQUIRE(cat.packages().size() == 1);
+ REQUIRE(cat.package("name") == pack);
REQUIRE(pack->category() == &cat);
}