From 0bb3b9ff15aee8a5a97eb7eea0f7b6a9478cfb68 Mon Sep 17 00:00:00 2001 From: p2r3 Date: Tue, 15 Oct 2024 20:35:27 +0300 Subject: [PATCH] implement external repo management --- autobuild.sh | 2 +- globals.cpp | 8 ++ globals.h | 2 + main.cpp | 85 ++++++++++++----- tools/package.cpp | 8 +- tools/package.h | 3 +- tools/repo.cpp | 113 ++++++++++++++++++++++- tools/repo.h | 3 + ui/{AddRepository.ui => Repositories.ui} | 113 ++++++++++++++++++++--- 9 files changed, 294 insertions(+), 43 deletions(-) rename ui/{AddRepository.ui => Repositories.ui} (53%) diff --git a/autobuild.sh b/autobuild.sh index f4c9d5a..20c34ca 100755 --- a/autobuild.sh +++ b/autobuild.sh @@ -29,7 +29,7 @@ cd ui ../qt5build/linux/bin/uic -o packageitem.h PackageItem.ui ../qt5build/linux/bin/uic -o errordialog.h ErrorDialog.ui ../qt5build/linux/bin/uic -o packageinfo.h PackageInfo.ui -../qt5build/linux/bin/uic -o addrepository.h AddRepository.ui +../qt5build/linux/bin/uic -o repositories.h Repositories.ui cd .. # Build application dependencies if not present diff --git a/globals.cpp b/globals.cpp index 0d54abe..8329d65 100644 --- a/globals.cpp +++ b/globals.cpp @@ -3,6 +3,14 @@ // Points to the system-specific designated temporary file path const std::filesystem::path TEMP_DIR = std::filesystem::temp_directory_path() / "spplice-cpp"; +// Points to the system-specific designated application directory +#ifndef TARGET_WINDOWS +const std::filesystem::path APP_DIR = (std::filesystem::path(std::getenv("HOME")) / ".config") / "spplice-cpp"; +#else +const std::filesystem::path APP_DIR = std::filesystem::path(std::getenv("APPDATA")) / "spplice-cpp"; +#endif +// Points to the external repository file +const std::filesystem::path REPO_PATH = APP_DIR / "repositories.txt"; // Holds the current package installation state int SPPLICE_INSTALL_STATE = 0; // 0 - idle; 1 - installing; 2 - installed // TCP communication port between Portal 2 and Spplice diff --git a/globals.h b/globals.h index e68aa73..7a51bce 100644 --- a/globals.h +++ b/globals.h @@ -7,6 +7,8 @@ // #define TARGET_WINDOWS extern const std::filesystem::path TEMP_DIR; +extern const std::filesystem::path APP_DIR; +extern const std::filesystem::path REPO_PATH; extern int SPPLICE_INSTALL_STATE; extern const int SPPLICE_NETCON_PORT; diff --git a/main.cpp b/main.cpp index 51ca1dd..b74b954 100644 --- a/main.cpp +++ b/main.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -15,7 +14,7 @@ #include #include #include "ui/mainwindow.h" -#include "ui/addrepository.h" +#include "ui/repositories.h" // Project globals #include "globals.h" @@ -27,13 +26,10 @@ #include "tools/package.h" #include "tools/repo.h" -std::deque repositoryURLs = { - "https://p2r3.github.io/spplice-repo/index.json" -}; +// Fetch and display packages from the given repository URL +void displayRepository (const std::string &url, QVBoxLayout *container) { -void addRepository (const std::string url, QVBoxLayout *container) { - - // Fetch the repository packages + // Fetch the repository packages // TODO: Make this asynchronous std::vector repository = ToolsRepo::fetchRepository(url); // Keep track of added package count to order them properly @@ -51,14 +47,34 @@ void addRepository (const std::string url, QVBoxLayout *container) { } +// Remove all packages of the given repository URL from the list +void hideRepository (const std::string &url, QVBoxLayout *container) { + + for (int i = 0; i < container->count(); i ++) { + QWidget *child = container->itemAt(i)->widget(); + if (child->property("packageRepository").toString().toStdString() == url) { + container->removeWidget(child); + delete child; + i --; + } + } + +} + int main (int argc, char *argv[]) { - try { + try { // Ensure TEMP_DIR exists std::filesystem::create_directories(TEMP_DIR); } catch (const std::filesystem::filesystem_error& e) { std::cerr << "Failed to create temporary directory " << TEMP_DIR << ": " << e.what() << std::endl; } + try { // Ensure APP_DIR exists + std::filesystem::create_directories(APP_DIR); + } catch (const std::filesystem::filesystem_error& e) { + std::cerr << "Failed to create application directory " << APP_DIR << ": " << e.what() << std::endl; + } + qputenv("QT_FONT_DPI", QByteArray("96")); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); @@ -82,22 +98,37 @@ int main (int argc, char *argv[]) { // Connect the "Add Repository" button QObject::connect(windowUI.TitleButtonR, &QPushButton::clicked, [packageContainer]() { - // Create new repository entry dialog + // Create repository management dialog QDialog *dialog = new QDialog; Ui::RepoDialog dialogUI; dialogUI.setupUi(dialog); - QLineEdit *urlTextBox = dialogUI.RepoURL; + QLineEdit *urlInput = dialogUI.AddInput; + QComboBox *dropdown = dialogUI.RemoveDropdown; - // Connect the "OK" button - QObject::connect(dialogUI.DialogButton, &QPushButton::clicked, [packageContainer, urlTextBox, dialog]() { - addRepository(urlTextBox->text().toStdString(), packageContainer); + // List external repositories in dropdown + std::vector repositories = ToolsRepo::readFromFile(); + for (const std::string &url : repositories) { + dropdown->addItem(QString::fromStdString(url)); + } + + // Define URL submit behavior + auto submitURL = [packageContainer, urlInput, dialog]() { + const std::string url = urlInput->text().toStdString(); + displayRepository(url, packageContainer); + ToolsRepo::writeToFile(url); dialog->hide(); - }); + }; - // Connect the event of pressing return - QObject::connect(urlTextBox, &QLineEdit::returnPressed, [packageContainer, urlTextBox, dialog]() { - addRepository(urlTextBox->text().toStdString(), packageContainer); + // Connect the "Add" button and text input return event + QObject::connect(dialogUI.AddButton, &QPushButton::clicked, submitURL); + QObject::connect(urlInput, &QLineEdit::returnPressed, submitURL); + + // Connect the "Remove" button + QObject::connect(dialogUI.RemoveButton, &QPushButton::clicked, [packageContainer, dropdown, dialog]() { + const std::string url = dropdown->currentText().toStdString(); + hideRepository(url, packageContainer); + ToolsRepo::removeFromFile(url); dialog->hide(); }); @@ -105,18 +136,22 @@ int main (int argc, char *argv[]) { }); - // Fetch packages from each repository - for (const std::string &url : repositoryURLs) { - addRepository(url, packageContainer); + // Display the main application window + window.setWindowTitle("Spplice"); + window.show(); + + // Load the global repository + displayRepository("https://p2r3.github.io/spplice-repo/index.json", packageContainer); + + // Load additional repositories from file + std::vector repositories = ToolsRepo::readFromFile(); + for (const std::string &url : repositories) { + displayRepository(url, packageContainer); } // Clean up CURL on program termination std::atexit(ToolsCURL::cleanup); - // Display the main application window - window.setWindowTitle("Spplice"); - window.show(); - // Ensure that no package is installed if Portal 2 is running when exiting Spplice #ifndef TARGET_WINDOWS std::atexit([]() { diff --git a/tools/package.cpp b/tools/package.cpp index a6d8d07..a1ad232 100644 --- a/tools/package.cpp +++ b/tools/package.cpp @@ -26,7 +26,10 @@ #include "package.h" // Constructs the PackageData instance from a JSON object -ToolsPackage::PackageData::PackageData (QJsonObject package) { +ToolsPackage::PackageData::PackageData (QJsonObject package, const std::string &repoURL) { + + // Keep track of the repository which this package came from + this->repository = repoURL; // These properties should be available even in old repositories this->title = package["title"].toString().toStdString(); @@ -195,6 +198,9 @@ QWidget* ToolsPackage::createPackageItem (const ToolsPackage::PackageData *packa itemUI.PackageTitle->setText(QString::fromStdString(package->title)); itemUI.PackageDescription->setText(QString::fromStdString(package->description)); + // Tag the item with the package's repository + item->setProperty("packageRepository", QString::fromStdString(package->repository)); + // Connect the install button QPushButton *installButton = itemUI.PackageInstallButton; QObject::connect(installButton, &QPushButton::clicked, [installButton, package]() { diff --git a/tools/package.h b/tools/package.h index dad8308..c379356 100644 --- a/tools/package.h +++ b/tools/package.h @@ -19,8 +19,9 @@ class ToolsPackage { std::vector args; std::string file; std::string icon; + std::string repository; - PackageData (QJsonObject package); + PackageData (QJsonObject package, const std::string &url); }; diff --git a/tools/repo.cpp b/tools/repo.cpp index 0c8e31c..0f1f3e3 100644 --- a/tools/repo.cpp +++ b/tools/repo.cpp @@ -1,9 +1,13 @@ #include +#include +#include #include #include #include #include #include + +#include "../globals.h" #include "curl.h" // ToolsCURL #include "package.h" // ToolsPackage @@ -27,10 +31,117 @@ std::vector ToolsRepo::fetchRepository (const // Create a vector, since a dynamic array is a bit more of a pain in the ass std::vector repository; for (int i = 0; i < packageCount; i ++) { - ToolsPackage::PackageData *package = new ToolsPackage::PackageData(packages[i].toObject()); + ToolsPackage::PackageData *package = new ToolsPackage::PackageData(packages[i].toObject(), url); repository.push_back(package); } return repository; } + +// Adds the given URL to the repository list file +void ToolsRepo::writeToFile (const std::string &url) { + + // Read the repository list + std::ifstream readFile(REPO_PATH); + + // Exit early if the URL is already in the repository list + if (readFile.is_open()) { + std::string line; + while (std::getline(readFile, line)) { + if (line == url) return; + } + } else { + std::cerr << "Failed to open " << REPO_PATH << " for reading." << std::endl; + } + + std::ofstream file(REPO_PATH, std::ios::app); + + // Append the URL to the end of the file + if (file.is_open()) { + file << url << std::endl; + file.close(); + } else { + std::cerr << "Failed to open " << REPO_PATH << " for writing." << std::endl; + } + +} + +// Returns a list of repositories stored in the file +std::vector ToolsRepo::readFromFile () { + + std::vector output; + + // Check if the file exists + if (!std::filesystem::exists(REPO_PATH)) { + std::cout << REPO_PATH << " does not exist, creating it..." << std::endl; + + // If it doesn't, write a blank file and exit + std::ofstream file(REPO_PATH); + if (!file.is_open()) { + std::cerr << "Failed to create " << REPO_PATH << std::endl; + } + + return output; + } + + // Read the repository list + std::ifstream file(REPO_PATH); + + if (!file.is_open()) { + std::cerr << "Failed to open " << REPO_PATH << " for reading." << std::endl; + return output; + } + + // Each line is a repository URL, add it to the container + std::string line; + while (std::getline(file, line)) { + output.push_back(line); + } + + file.close(); + return output; + +} + +// Removes the given URL from the repository list file +void ToolsRepo::removeFromFile (const std::string &url) { + + // Check if the file exists + if (!std::filesystem::exists(REPO_PATH)) { + std::cout << REPO_PATH << " does not exist." << std::endl; + return; + } + + // Use a temporary file to store the new repository list + const std::filesystem::path tempPath = REPO_PATH.string() + ".tmp"; + std::ofstream tempFile(tempPath); + + if (!tempFile.is_open()) { + std::cerr << "Failed to open " << tempPath << " for writing." << std::endl; + return; + } + + // Read the repository list + std::ifstream file(REPO_PATH); + + if (!file.is_open()) { + std::cerr << "Failed to open " << REPO_PATH << " for reading." << std::endl; + return; + } + + // Each line is a repository URL, add it to the container + std::string line; + while (std::getline(file, line)) { + if (line == url) continue; + tempFile << line << std::endl; + } + + file.close(); + tempFile.close(); + + // Replace the original file with the temporary one + std::filesystem::remove(REPO_PATH); + std::filesystem::rename(REPO_PATH.string() + ".tmp", REPO_PATH); + +} diff --git a/tools/repo.h b/tools/repo.h index 95045db..c39753a 100644 --- a/tools/repo.h +++ b/tools/repo.h @@ -7,6 +7,9 @@ class ToolsRepo { public: static std::vector fetchRepository (const std::string &url); + static void writeToFile (const std::string &url); + static std::vector readFromFile (); + static void removeFromFile (const std::string &url); }; #endif diff --git a/ui/AddRepository.ui b/ui/Repositories.ui similarity index 53% rename from ui/AddRepository.ui rename to ui/Repositories.ui index d9a72db..6d782c6 100644 --- a/ui/AddRepository.ui +++ b/ui/Repositories.ui @@ -7,11 +7,11 @@ 0 0 480 - 160 + 240 - New Repository + Manage Repositories #RepoDialog { @@ -20,10 +20,10 @@ #DialogTitle { color: #17C0E9; } -#DialogText { +QLabel { color: #ffffff; } -#RepoURL, #DialogButton { +QPushButton, QLineEdit, QComboBox { background: rgb(50, 50, 50); color: #ffffff; border: 2px solid rgb(80, 80, 80); @@ -61,7 +61,7 @@ - New Repository + Manage Repositories Qt::AlignCenter @@ -69,7 +69,20 @@ - + + + Qt::Vertical + + + + 20 + 40 + + + + + + Quicksand Medium @@ -84,15 +97,18 @@ Qt::AlignHCenter|Qt::AlignTop + + true + - - - 0 + + + 15 - + Qt::Horizontal @@ -105,17 +121,17 @@ - + - + Quicksand Medium - OK + Add true @@ -123,7 +139,7 @@ - + Qt::Horizontal @@ -137,6 +153,75 @@ + + + + + Quicksand Medium + 12 + + + + Remove repositories using the dropdown below. + + + Qt::AlignCenter + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Remove + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + +