Skip to content

Commit

Permalink
implement external repo management
Browse files Browse the repository at this point in the history
  • Loading branch information
p2r3 committed Oct 15, 2024
1 parent 5157630 commit 0bb3b9f
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 43 deletions.
2 changes: 1 addition & 1 deletion autobuild.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions globals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
85 changes: 60 additions & 25 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#include <iostream>
#include <utility>
#include <vector>
#include <deque>
#include <filesystem>
#include <thread>
#include <chrono>
Expand All @@ -15,7 +14,7 @@
#include <QObject>
#include <QDialog>
#include "ui/mainwindow.h"
#include "ui/addrepository.h"
#include "ui/repositories.h"

// Project globals
#include "globals.h"
Expand All @@ -27,13 +26,10 @@
#include "tools/package.h"
#include "tools/repo.h"

std::deque<std::string> 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<const ToolsPackage::PackageData*> repository = ToolsRepo::fetchRepository(url);

// Keep track of added package count to order them properly
Expand All @@ -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);
Expand All @@ -82,41 +98,60 @@ 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<std::string> 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();
});

dialog->exec();

});

// 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<std::string> 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([]() {
Expand Down
8 changes: 7 additions & 1 deletion tools/package.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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]() {
Expand Down
3 changes: 2 additions & 1 deletion tools/package.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ class ToolsPackage {
std::vector<std::string> args;
std::string file;
std::string icon;
std::string repository;

PackageData (QJsonObject package);
PackageData (QJsonObject package, const std::string &url);

};

Expand Down
113 changes: 112 additions & 1 deletion tools/repo.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#include <iostream>
#include <fstream>
#include <filesystem>
#include <vector>
#include <string>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

#include "../globals.h"
#include "curl.h" // ToolsCURL
#include "package.h" // ToolsPackage

Expand All @@ -27,10 +31,117 @@ std::vector<const ToolsPackage::PackageData*> ToolsRepo::fetchRepository (const
// Create a vector, since a dynamic array is a bit more of a pain in the ass
std::vector<const ToolsPackage::PackageData*> 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<std::string> ToolsRepo::readFromFile () {

std::vector<std::string> 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);

}
3 changes: 3 additions & 0 deletions tools/repo.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
class ToolsRepo {
public:
static std::vector<const ToolsPackage::PackageData*> fetchRepository (const std::string &url);
static void writeToFile (const std::string &url);
static std::vector<std::string> readFromFile ();
static void removeFromFile (const std::string &url);
};

#endif
Loading

0 comments on commit 0bb3b9f

Please sign in to comment.