Skip to content

Commit

Permalink
implement automatic updates
Browse files Browse the repository at this point in the history
  • Loading branch information
p2r3 committed Dec 4, 2024
1 parent df4e5ec commit ca1727c
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ add_executable(SppliceCPP
main.cpp
globals.cpp
tools/curl.cpp
tools/update.cpp
tools/qt.cpp
tools/install.cpp
tools/package.cpp
Expand Down
3 changes: 3 additions & 0 deletions globals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ const std::filesystem::path REPO_PATH = APP_DIR / "repositories.txt";
int SPPLICE_INSTALL_STATE = 0; // 0 - idle; 1 - installing; 2 - installed
// TCP communication port between Portal 2 and Spplice (set during runtime)
int SPPLICE_NETCON_PORT = -1;

// Holds the current version's GitHub tag for automatic updates
const std::string SPPLICE_VERSION_TAG = "v0.6.3-alpha";
1 change: 1 addition & 0 deletions globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ extern std::ofstream LOGFILE;
extern const std::filesystem::path REPO_PATH;
extern int SPPLICE_INSTALL_STATE;
extern int SPPLICE_NETCON_PORT;
extern const std::string SPPLICE_VERSION_TAG;

#endif
5 changes: 5 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "tools/install.h"
#include "tools/package.h"
#include "tools/repo.h"
#include "tools/update.h"

// Fetch and display packages from the given repository URL asynchronously
void displayRepository (const std::string &url, const std::string &last, QVBoxLayout *container) {
Expand Down Expand Up @@ -162,6 +163,7 @@ int main (int argc, char *argv[]) {
}
// Open the log file
LOGFILE = std::ofstream(APP_DIR / "log.txt");
LOGFILE << "Spplice " << SPPLICE_VERSION_TAG << std::endl;

// Check for a CACHE_DIR override in cache_dir.txt
checkCacheOverride(APP_DIR / "cache_dir.txt");
Expand Down Expand Up @@ -193,6 +195,9 @@ int main (int argc, char *argv[]) {
// Initialize CURL
ToolsCURL::init();

// Check for updates on a separate thread
std::thread(ToolsUpdate::installUpdate).detach();

QVBoxLayout *packageContainer = windowUI.PackageListLayout;

// Connect the "Settings" button
Expand Down
129 changes: 129 additions & 0 deletions tools/update.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#include <iostream>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QMessageBox>

#ifdef TARGET_WINDOWS
#include <windows.h>
#else
#include <unistd.h>
#include <limits.h>
#endif

#include "curl.h" // ToolsCURL
#include "../globals.h" // Project globals

// Definitions for this source file
#include "update.h"

#ifndef TARGET_WINDOWS
const std::string updateBinary = "SppliceCPP";
#else
const std::string updateBinary = "autoupdate";
#endif

std::filesystem::path getExecutablePath () {

#ifdef TARGET_WINDOWS
wchar_t buffer[MAX_PATH];
DWORD size = GetModuleFileNameW(NULL, buffer, MAX_PATH);
if (size == 0 || size == MAX_PATH) return std::filesystem::path();
#else
char buffer[PATH_MAX];
ssize_t size = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1);
if (size == -1) return std::filesystem::path();
buffer[size] = '\0';
#endif

return std::filesystem::path(buffer);

}

void ToolsUpdate::installUpdate () {

// Download the GitHub releases manifest containing one item
std::string releasesString = ToolsCURL::downloadString("https://api.github.com/repos/p2r3/spplice-cpp/releases?per_page=1");
// If the download failed, do nothing
if (releasesString == "") {
LOGFILE << "[E] Failed to download GitHub releases list" << std::endl;
return;
}

// Parse the JSON to get an array of releases
QJsonDocument doc = QJsonDocument::fromJson(QString::fromStdString(releasesString).toUtf8());
QJsonArray releases = doc.array();
// Get the latest (pre)release
QJsonObject latest = releases[0].toObject();

// If we're up to date, do nothing
const std::string latestTag = latest["tag_name"].toString().toStdString();
if (SPPLICE_VERSION_TAG == latestTag) return;
LOGFILE << "[I] Update required (" << SPPLICE_VERSION_TAG << " -> " << latestTag << ")" << std::endl;

// Parse the assets array
QJsonArray assets = latest["assets"].toArray();
// Find the right binary for this platform
QJsonObject asset;
bool foundUpdateBinary = false;
for (const QJsonValue &curr : assets) {
asset = curr.toObject();
if (asset["name"].toString().toStdString() == updateBinary) {
foundUpdateBinary = true;
break;
}
}
// If no matching update binary was found, request a manual update
if (!foundUpdateBinary) {
QMessageBox::information(nullptr, "Spplice Update",
"This version of Spplice is obsolete and requires a manual update.\nGo to p2r3.com/spplice to download the latest version.");
return;
}

// Download the binary
const std::string url = asset["browser_download_url"].toString().toStdString();
LOGFILE << "[I] Downloading update from \"" << url << '"' << std::endl;

const std::filesystem::path updatePath = CACHE_DIR / "update_binary";
bool success = ToolsCURL::downloadFile(url, updatePath);
// If the download failed, do nothing
if (!success) {
LOGFILE << "[E] Failed to download update from \"" << url << '"' << std::endl;
return;
}

// Get the path of the currently running executable
const std::filesystem::path executablePath = getExecutablePath();
// If this failed, do nothing
if (executablePath.empty()) {
LOGFILE << "[E] Failed to find own executable." << std::endl;
return;
}
LOGFILE << "[I] Found own executable at " << executablePath << std::endl;

// Swap the running executable with the one we just downloaded
#ifdef TARGET_WINDOWS
// If on Windows, defer replacing until next reboot to avoid conflicts
if (MoveFileExW(updatePath.c_str(), executablePath.c_str(), MOVEFILE_DELAY_UNTIL_REBOOT)) {
LOGFILE << "[I] Scheduled update file replacement for next reboot" << std::endl;
} else {
LOGFILE << "[E] Failed to schedule update file replacement. ERRNO: " << GetLastError() << std::endl;
return;
}
QMessageBox::information(nullptr, "Spplice Update",
"Spplice has been updated.\nChanges will take effect after a system reboot.");
#else
// If on Linux, replace the executable right away
try {
std::filesystem::remove(executablePath);
std::filesystem::rename(updatePath, executablePath);
LOGFILE << "[I] Replaced " << executablePath << " with " << updatePath << std::endl;
} catch (const std::filesystem::filesystem_error& e) {
LOGFILE << "[E] Failed to replace executable post-update: " << e.what() << std::endl;
return;
}
QMessageBox::information(nullptr, "Spplice Update",
"Spplice has been updated.\nRestart Spplice to apply the changes.");
#endif

}
9 changes: 9 additions & 0 deletions tools/update.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef TOOLS_UPDATE_H
#define TOOLS_UPDATE_H

class ToolsUpdate {
public:
static void installUpdate ();
};

#endif

0 comments on commit ca1727c

Please sign in to comment.