aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-31 13:20:21 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-31 13:27:20 -0500
commit51419a3aa4f037869e2c9dd52377277bc4695010 (patch)
tree21798001a460f24638911a5ccf98bfff53e2b50e
parent5c4603f12a727625ece236fc43846330fad3b9f2 (diff)
fix updater restart and log cleanup
-rwxr-xr-xdocker/build-inner.sh34
-rw-r--r--libs/plugin_python/src/proxy/CMakeLists.txt2
-rw-r--r--src/src/mainwindow.cpp2
-rw-r--r--src/src/organizercore.cpp91
-rw-r--r--src/src/organizercore.h12
-rw-r--r--src/src/settingsdialogupdates.cpp41
6 files changed, 163 insertions, 19 deletions
diff --git a/docker/build-inner.sh b/docker/build-inner.sh
index a392645..9c05783 100755
--- a/docker/build-inner.sh
+++ b/docker/build-inner.sh
@@ -173,6 +173,8 @@ SKIP_PATTERN="linux-vdso|ld-linux|libc\.so|libm\.so|libdl\.so|librt\.so|libpthre
SKIP_PATTERN="${SKIP_PATTERN}|libGL\.so|libEGL|libGLX|libGLdispatch|libdrm|libvulkan|libX11|libxcb|libwayland-client|libwayland-server|libwayland-cursor|libwayland-egl|libxkbcommon"
# libpython — user provides via system Python; do not bundle.
SKIP_PATTERN="${SKIP_PATTERN}|libpython"
+# OpenSSL should come from the host so we don't pin users to a stale TLS stack.
+SKIP_PATTERN="${SKIP_PATTERN}|libssl\.so|libcrypto\.so"
collect_deps() {
ldd "$1" 2>/dev/null | grep "=>" | awk '{print $3}' | grep "^/" | sort -u
@@ -211,6 +213,31 @@ for _xcb_cursor in /lib/x86_64-linux-gnu/libxcb-cursor.so* \
echo "Bundled ${_xcb_cursor}"
done
+# xdg-mime's KDE path calls an unversioned `qtpaths` helper. Bundle it so
+# users do not need distro Qt tools packages just to register nxm:// links.
+QT6_BIN_DIR=""
+for _candidate in \
+ "${Qt6_DIR:-}/bin" \
+ "/opt/qt6/6.11.1/gcc_64/bin" \
+ "/usr/lib/qt6/bin"; do
+ if [ -d "${_candidate}" ]; then
+ QT6_BIN_DIR="${_candidate}"
+ break
+ fi
+done
+for _qtpaths in \
+ "${QT6_BIN_DIR}/qtpaths" \
+ "${QT6_BIN_DIR}/qtpaths6" \
+ "$(command -v qtpaths 2>/dev/null || true)" \
+ "$(command -v qtpaths6 2>/dev/null || true)"; do
+ if [ -n "${_qtpaths}" ] && [ -x "${_qtpaths}" ]; then
+ cp -Lf "${_qtpaths}" "${OUT_DIR}/lib/qtpaths"
+ chmod +x "${OUT_DIR}/lib/qtpaths"
+ echo "Bundled qtpaths from ${_qtpaths}"
+ break
+ fi
+done
+
# ── Qt6 platform plugins ──
# Prefer aqtinstall location (Docker), then system, then qtpaths6 fallback.
QT6_PLUGIN_DIR=""
@@ -530,6 +557,11 @@ if [ "${HERE_REAL}" != "${DST_REAL}" ]; then
exit 1
fi
+ # Do not retain stale bundled OpenSSL runtimes from older releases.
+ # TLS should resolve against the host so certificate handling can be
+ # updated by the OS instead of being pinned to our old package.
+ rm -f "${BIN_DST}"/lib/libssl.so* "${BIN_DST}"/lib/libcrypto.so* 2>/dev/null || true
+
# Refresh the manifest at the destination so the next update has it.
cp -af "${MANIFEST}" "${BIN_DST}/fluorine-manifest.txt"
echo "${CURRENT_VER}" > "${MARKER}"
@@ -560,7 +592,7 @@ fi
# Run from the synced location.
RUN="${BIN_DST}"
-export PATH="${RUN}:${PATH}"
+export PATH="${RUN}/lib:${RUN}:${PATH}"
# Steam game mode injects its scout/soldier runtime into LD_LIBRARY_PATH.
# Those old libraries (libssl, libz, etc.) break Python extension modules
# and Qt internals that don't have RPATH pointing to our bundled libs.
diff --git a/libs/plugin_python/src/proxy/CMakeLists.txt b/libs/plugin_python/src/proxy/CMakeLists.txt
index f7e6f31..c666b6c 100644
--- a/libs/plugin_python/src/proxy/CMakeLists.txt
+++ b/libs/plugin_python/src/proxy/CMakeLists.txt
@@ -49,8 +49,6 @@ if(WIN32)
file(GLOB dlls_to_install
${Python_HOME}/dlls/libffi*.dll
${Python_HOME}/dlls/sqlite*.dll
- ${Python_HOME}/dlls/libssl*.dll
- ${Python_HOME}/dlls/libcrypto*.dll
${Python_HOME}/python${Python_VERSION_MAJOR}*.dll)
install(FILES ${dlls_to_install} DESTINATION ${DLL_DIRS})
endif()
diff --git a/src/src/mainwindow.cpp b/src/src/mainwindow.cpp
index f8f7e97..ca92499 100644
--- a/src/src/mainwindow.cpp
+++ b/src/src/mainwindow.cpp
@@ -1457,6 +1457,8 @@ void MainWindow::paintEvent(QPaintEvent* event)
void MainWindow::onBeforeClose()
{
storeSettings();
+ m_ArchiveListWriter.writeImmediately(true);
+ m_OrganizerCore.pluginsWriter().writeImmediately(true);
}
void MainWindow::closeEvent(QCloseEvent* event)
diff --git a/src/src/organizercore.cpp b/src/src/organizercore.cpp
index d8e6da4..3721cf4 100644
--- a/src/src/organizercore.cpp
+++ b/src/src/organizercore.cpp
@@ -52,9 +52,12 @@
#include <thread>
#include <QApplication>
#include <QCoreApplication>
+#include <QDateTime>
#include <QDialog>
#include <QDialogButtonBox>
+#include <QDirIterator>
#include <QFile>
+#include <QFileInfo>
#include <QMessageBox>
#include <QNetworkInterface>
#include <QProcess>
@@ -91,6 +94,41 @@ using namespace MOBase;
static env::CoreDumpTypes g_coreDumpType = env::CoreDumpTypes::Mini;
+namespace
+{
+
+QString uniqueFilePath(const QDir& dir, const QString& fileName)
+{
+ QString candidate = dir.filePath(fileName);
+ if (!QFileInfo::exists(candidate)) {
+ return candidate;
+ }
+
+ const QFileInfo info(fileName);
+ const QString base = info.completeBaseName();
+ const QString suffix = info.suffix();
+ const QString timestamp =
+ QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_hhmmss"));
+
+ for (int i = 1; i < 1000; ++i) {
+ const QString numbered =
+ suffix.isEmpty()
+ ? QStringLiteral("%1_%2_%3").arg(base, timestamp).arg(i)
+ : QStringLiteral("%1_%2_%3.%4").arg(base, timestamp).arg(i).arg(suffix);
+ candidate = dir.filePath(numbered);
+ if (!QFileInfo::exists(candidate)) {
+ return candidate;
+ }
+ }
+
+ return dir.filePath(
+ suffix.isEmpty()
+ ? QStringLiteral("%1_%2").arg(base, timestamp)
+ : QStringLiteral("%1_%2.%3").arg(base, timestamp, suffix));
+}
+
+} // namespace
+
template <typename InputIterator>
QStringList toStringList(InputIterator current, InputIterator end)
{
@@ -756,6 +794,58 @@ void OrganizerCore::prepareVFS()
void OrganizerCore::unmountVFS()
{
m_USVFS.unmount();
+ movePGPatcherLogsToLogsFolder();
+}
+
+void OrganizerCore::movePGPatcherLogsToLogsFolder()
+{
+ const QString dataPath = qApp->property("dataPath").toString();
+ if (dataPath.isEmpty()) {
+ log::warn("PGPatcher log cleanup skipped: dataPath is not set");
+ return;
+ }
+
+ QDir logsDir(QDir(dataPath).filePath(QString::fromStdWString(AppConfig::logPath())));
+ if (!logsDir.exists() && !QDir().mkpath(logsDir.absolutePath())) {
+ log::warn("PGPatcher log cleanup skipped: failed to create '{}'",
+ logsDir.absolutePath());
+ return;
+ }
+
+ const QStringList roots = {
+ QDir::fromNativeSeparators(m_Settings.paths().overwrite()),
+ QDir::fromNativeSeparators(m_Settings.paths().mods()),
+ };
+
+ int moved = 0;
+ for (const QString& root : roots) {
+ if (root.isEmpty() || !QDir(root).exists()) {
+ continue;
+ }
+
+ QDirIterator it(root, QStringList{QStringLiteral("PGPatcher.log")},
+ QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot,
+ QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ const QString source = it.next();
+ const QString destination =
+ uniqueFilePath(logsDir, QFileInfo(source).fileName());
+
+ if (QFile::rename(source, destination) ||
+ (QFile::copy(source, destination) && QFile::remove(source))) {
+ ++moved;
+ log::info("Moved PGPatcher log '{}' -> '{}'", source, destination);
+ } else {
+ log::warn("Failed to move PGPatcher log '{}' -> '{}'", source,
+ destination);
+ }
+ }
+ }
+
+ if (moved > 0) {
+ log::info("PGPatcher log cleanup moved {} file(s) to '{}'", moved,
+ logsDir.absolutePath());
+ }
}
void OrganizerCore::trackOverwriteMove(const QString& relativePath,
@@ -2770,6 +2860,7 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode)
// and tears down the FUSE session. This mirrors Windows behaviour where
// USVFS is only active while a hooked process is running.
m_USVFS.unmount();
+ movePGPatcherLogsToLogsFolder();
// Restore write permissions on the game directory. In rare cases
// (crashes, unclean Wine shutdown) file permissions can be changed to
diff --git a/src/src/organizercore.h b/src/src/organizercore.h
index 494e835..973ee64 100644
--- a/src/src/organizercore.h
+++ b/src/src/organizercore.h
@@ -534,11 +534,13 @@ private:
const QString& customOverwrite);
std::vector<Mapping> fileMapping(const QString& dataPath, const QString& relPath,
- const MOShared::DirectoryEntry* base,
- const MOShared::DirectoryEntry* directoryEntry,
- int createDestination);
-
-private slots:
+ const MOShared::DirectoryEntry* base,
+ const MOShared::DirectoryEntry* directoryEntry,
+ int createDestination);
+
+ void movePGPatcherLogsToLogsFolder();
+
+private slots:
void onDirectoryRefreshed();
void downloadRequested(QNetworkReply* reply, QString gameName, int modID,
diff --git a/src/src/settingsdialogupdates.cpp b/src/src/settingsdialogupdates.cpp
index 8f76bae..57f5957 100644
--- a/src/src/settingsdialogupdates.cpp
+++ b/src/src/settingsdialogupdates.cpp
@@ -2,6 +2,7 @@
#include "fluorineupdater.h"
#include "settings.h"
+#include "shared/util.h"
#include "ui_settingsdialog.h"
#include <fluorine_build_info.h>
@@ -10,7 +11,6 @@
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
-#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
@@ -36,6 +36,17 @@
#include <algorithm>
+namespace
+{
+
+bool isLauncherFile(const QString& path)
+{
+ const QFileInfo info(path);
+ return info.isFile();
+}
+
+} // namespace
+
UpdatesSettingsTab::UpdatesSettingsTab(Settings& s, SettingsDialog& d)
: SettingsTab(s, d)
{
@@ -324,22 +335,29 @@ void UpdatesSettingsTab::onInstall()
QDir extract(extractDir);
QString newLauncher =
extract.absoluteFilePath(QStringLiteral("fluorine-manager"));
- if (!QFileInfo::exists(newLauncher)) {
+ if (!isLauncherFile(newLauncher)) {
const QStringList tops = extract.entryList(
QDir::Dirs | QDir::NoDotAndDotDot);
for (const QString& top : tops) {
const QString candidate = extract.absoluteFilePath(
top + QStringLiteral("/fluorine-manager"));
- if (QFileInfo::exists(candidate)) {
+ if (isLauncherFile(candidate)) {
newLauncher = candidate;
break;
}
}
}
- if (!QFileInfo::exists(newLauncher)) {
+ if (isLauncherFile(newLauncher)) {
+ QFile::setPermissions(
+ newLauncher,
+ QFile::permissions(newLauncher) | QFile::ExeOwner |
+ QFile::ExeGroup | QFile::ExeOther);
+ }
+ if (!isLauncherFile(newLauncher) ||
+ !QFileInfo(newLauncher).isExecutable()) {
m_statusLabel->setText(
tr("<i>Install failed:</i> launcher not found in "
- "extracted archive"));
+ "extracted archive, or it is not executable"));
m_progressBar->setVisible(false);
m_installButton->setEnabled(true);
m_checkNowButton->setEnabled(true);
@@ -367,14 +385,13 @@ void UpdatesSettingsTab::onInstall()
QStringLiteral(
"#!/usr/bin/env bash\n"
"set -u\n"
- "OLD_PID=%1\n"
- "NEW_LAUNCHER=%2\n"
+ "OLD_PID=\"$1\"\n"
+ "NEW_LAUNCHER=\"$2\"\n"
"for _ in $(seq 1 200); do\n"
" if ! kill -0 \"$OLD_PID\" 2>/dev/null; then break; fi\n"
" sleep 0.1\n"
"done\n"
- "exec \"$NEW_LAUNCHER\"\n")
- .arg(QString::number(currentPid), newLauncher);
+ "exec \"$NEW_LAUNCHER\"\n");
helper.write(script.toUtf8());
helper.close();
QFile::setPermissions(
@@ -388,7 +405,9 @@ void UpdatesSettingsTab::onInstall()
// Detach the helper so it survives our exit.
QProcess::startDetached(QStringLiteral("/usr/bin/env"),
- {QStringLiteral("bash"), helperPath});
+ {QStringLiteral("bash"), helperPath,
+ QString::number(currentPid),
+ newLauncher});
MOBase::log::info(
"update installer: spawned helper to restart into {}",
@@ -396,7 +415,7 @@ void UpdatesSettingsTab::onInstall()
// Give the signal a beat to propagate, then quit cleanly.
QTimer::singleShot(250, qApp,
- []() { QCoreApplication::quit(); });
+ []() { ExitModOrganizer(Exit::Force); });
});
extractor->start();
});