aboutsummaryrefslogtreecommitdiff
path: root/libs/uibase/src/utility.cpp
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-23 16:09:59 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-23 16:09:59 -0500
commit96a6ba196c72e955c3db822d93da38fce8cd7045 (patch)
tree3fcc40e939bd609c4cb7b84f05e85875dde64fc6 /libs/uibase/src/utility.cpp
parent6b9f0876cfbf6f8c723c86ec941ee70933aa883f (diff)
Fix VFS rmdir, prefix/launch issues, add version + beta channel
- VFS: add mo2_rmdir + OverwriteManager::removeDirectory so Wrye Bash shutil.rmtree stops failing with WinError 267 (issue #47). - FileRenamer: case-insensitive fallback via new resolvePathCaseInsensitive walks each path component; bulk hide in Conflict tab no longer dies with a misleading "Input/output error" when DirectoryEntry normalised parent dir case (issue #54). formatSystemMessage stops aliasing Windows error code 5 to errno 5 (EIO). - Versioning: FLUORINE_VERSION_* + fluorine_build_info.h generated from CMake; Linux createVersionInfo returns Fluorine's version. About dialog shows Fluorine version + MO2 engine + commit. Beta builds stamp "<semver>B<yyyymmddhhmm>" (issue #51). - CI: ci.yml determines channel from ref (v* tags = stable, main = beta), publishes a rolling `beta` GitHub release whose body carries a machine-parseable fluorine-meta block (timestamp, commit) for the in-app updater. - Updater: new FluorineUpdater polls GitHub API on startup, compares against embedded metadata, notifies on new release without auto-install. Channel toggle piggybacks on the existing "beta versions" checkbox. - Bethesda plugins: determineMyGamesPath no longer creates Documents/My Games/<GameName> for every possible title at plugin load; only games with a detected install path get their dirs created (issue #55). - Starfield: dataDirectory() on Linux now points at <game>/Data instead of My Games/Starfield/Data so the single FUSE mount lands where SFSE loads plugins; My Games Data becomes a secondary symlinked mapping (issue #56). - Prefix resolution: prefer Fluorine config, then explicit fluorine/prefix_path, then legacy Settings/* keys (with a warning) so auto-detected Heroic/Bottles prefixes can't silently override a user- configured Fluorine prefix (issue #52). compatDataPathFromPrefix no longer hands Proton the wrong parent dir for plain wine prefixes. - xrandr: ensureXrandrInstalled back-fills the helper for existing SLR installs, prefix init runs it before wineboot, SLR wrap exposes xrandr-bin via --filesystem= and prepends PATH inside the container (issue #49). - Process tracking: drop the wineserver-as-last-resort fallback that made MO2 hang on Proton's session manager after game exit. Rescan the prefix for matching game executables instead (covers reparented launcher children like f4se_loader → Fallout4.exe). Unlock button now kills wineserver for the prefix (SIGTERM then SIGKILL on timeout). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'libs/uibase/src/utility.cpp')
-rw-r--r--libs/uibase/src/utility.cpp56
1 files changed, 51 insertions, 5 deletions
diff --git a/libs/uibase/src/utility.cpp b/libs/uibase/src/utility.cpp
index c0070c6..40774ae 100644
--- a/libs/uibase/src/utility.cpp
+++ b/libs/uibase/src/utility.cpp
@@ -19,6 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <uibase/utility.h>
+#include <uibase/filesystemutilities.h>
#include <uibase/log.h>
#include <uibase/report.h>
#ifndef _WIN32
@@ -44,10 +45,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include <QtDebug>
#include <cerrno>
#include <cstring>
+#include <filesystem>
#include <format>
#include <iostream>
#include <memory>
#include <sstream>
+#include <system_error>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
@@ -532,18 +535,50 @@ namespace shell
Result Rename(const QFileInfo& src, const QFileInfo& dest, bool copyAllowed)
{
- if (QFile::rename(src.absoluteFilePath(), dest.absoluteFilePath())) {
+ const QString srcPath = src.absoluteFilePath();
+ const QString destPath = dest.absoluteFilePath();
+
+ std::error_code ec;
+ std::filesystem::rename(srcPath.toStdString(), destPath.toStdString(), ec);
+ if (!ec) {
return Result::makeSuccess();
}
+#ifndef _WIN32
+ // Case-sensitivity fallback: the source path may have been computed from a
+ // case-normalized index (e.g. DirectoryEntry's lowercased tree), so try
+ // resolving each component against the real filesystem.
+ if (ec.value() == ENOENT) {
+ const QString resolved = resolvePathCaseInsensitive(srcPath);
+ if (resolved != srcPath && QFileInfo::exists(resolved)) {
+ std::error_code ec2;
+ std::filesystem::rename(resolved.toStdString(), destPath.toStdString(),
+ ec2);
+ if (!ec2) {
+ return Result::makeSuccess();
+ }
+ ec = ec2;
+ }
+ }
+#endif
+
if (copyAllowed) {
- if (QFile::copy(src.absoluteFilePath(), dest.absoluteFilePath())) {
- QFile::remove(src.absoluteFilePath());
+ if (QFile::copy(srcPath, destPath)) {
+ QFile::remove(srcPath);
return Result::makeSuccess();
}
}
- return Result::makeFailure(ERROR_ACCESS_DENIED);
+ // Propagate the real errno text rather than a generic Windows code so the
+ // log line actually reflects what failed.
+ const int err = ec.value();
+ QString msg = QString::fromStdString(ec.message());
+ if (msg.isEmpty()) {
+ msg = QString("rename failed: errno=%1").arg(err);
+ }
+ return Result::makeFailure(err != 0 ? static_cast<DWORD>(err)
+ : ERROR_ACCESS_DENIED,
+ msg);
}
Result CreateDirectories(const QDir& dir)
@@ -1051,7 +1086,18 @@ std::wstring formatSystemMessage(DWORD id)
if (id == 0) {
return L"Success";
}
- // If it looks like an errno value (small numbers), use strerror
+
+ // Try the Windows-style mapping first: our callers use a mix of
+ // ERROR_ACCESS_DENIED/ERROR_FILE_NOT_FOUND/etc., which overlap the errno
+ // number space (e.g. Windows 5 == ACCESS_DENIED, but errno 5 == EIO). If
+ // formatError returns a known mapping, use it; otherwise fall back to
+ // strerror for real errno values.
+ const QString winMsg = shell::formatError(static_cast<int>(id));
+ if (!winMsg.startsWith(QStringLiteral("Unknown error "))) {
+ const std::string s = winMsg.toStdString();
+ return std::wstring(s.begin(), s.end());
+ }
+
if (id < 200) {
const char* msg = strerror(static_cast<int>(id));
if (msg) {