aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_bsplugins/src/TESData/FileInfo.h
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-12 21:04:15 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-12 21:04:15 -0500
commit3949dcfce95af4bd305f258ff5b170d7d50435f6 (patch)
tree4c768be256c40f31794c3311f0a2c22933a6705a /libs/installer_bsplugins/src/TESData/FileInfo.h
parent892070f3dec1dc690983fd862880e37e711c2516 (diff)
VFS perf fixes, icon/stylesheet compat, download filename fix
VFS: - Open backing fd at mo2_open time for read-only handles so mo2_read can splice without re-opening the file on every read call. - Bump RLIMIT_NOFILE at mount time to fit the resulting fd pressure when the game keeps hundreds of BSAs open. - Dedicated node_cache_mutex so resolveByInode / mo2_lookup stop racing unordered_map writes while multiple tree_mutex readers are active. - max_read=1MB + matching conn->max_read and raised max_readahead/max_write so the kernel merges Wine's small sequential reads into bigger FUSE requests. - Per-op wall-clock counters in mo2_init() logs so we can distinguish VFS latency from game-side work. Proton launch: - Write dxvk.conf to the prefix and set DXVK_CONFIG_FILE to force dxvk.enableGraphicsPipelineLibrary=False, avoiding long GPL compile stalls on first run. UI / plugin compat: - Clamp IconDelegate's per-icon width to [8, 16] so narrow content columns don't trigger QIcon::pixmap(0,0) returning null and logging "failed to load icon" every repaint. - Pre-create lowercase symlinks in the stylesheet directory so QSS files authored on Windows resolve url(foo.svg) when on-disk files are Foo.svg. - Flip content icons empty-chessboard.png and facegen.png to 8-bit RGBA so Qt's built-in PNG reader loads them uniformly. - Add mobase.Version.canonicalString shim for legacy Python plugins that still call the old VersionInfo method on appVersion()'s return. - BSPluginList: compare normalized plugin name lists instead of byte hashes so the post-run case-fix refresh stops spuriously firing the "load order changed" dialog. - BSPluginList disabled by default. Download manager: - Parse CDN URL with QUrl + QUrlQuery and read the filename from the response-content-disposition query param (RFC 6266 quoted / unquoted / ext-value forms), not QFileInfo on the raw URL. - When the API or URL gives us a CDN object key (no archive extension), prefer the actual Content-Disposition header from the live response in metaDataChanged and downloadFinished. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'libs/installer_bsplugins/src/TESData/FileInfo.h')
-rw-r--r--libs/installer_bsplugins/src/TESData/FileInfo.h222
1 files changed, 222 insertions, 0 deletions
diff --git a/libs/installer_bsplugins/src/TESData/FileInfo.h b/libs/installer_bsplugins/src/TESData/FileInfo.h
new file mode 100644
index 0000000..57c5d60
--- /dev/null
+++ b/libs/installer_bsplugins/src/TESData/FileInfo.h
@@ -0,0 +1,222 @@
+#ifndef TESDATA_FILEINFO_H
+#define TESDATA_FILEINFO_H
+
+#include <ifiletree.h>
+#include <memoizedlock.h>
+
+#include <boost/container/flat_set.hpp>
+
+#include <QDateTime>
+#include <QSet>
+#include <QString>
+
+namespace TESData
+{
+
+class PluginList;
+
+class FileInfo
+{
+public:
+ enum EConflictFlag : uint
+ {
+ CONFLICT_NONE = 0x0,
+ CONFLICT_OVERRIDE = 0x1,
+ CONFLICT_OVERRIDDEN = 0x2,
+ CONFLICT_ARCHIVE_OVERWRITE = 0x4,
+ CONFLICT_ARCHIVE_OVERWRITTEN = 0x8,
+
+ CONFLICT_MIXED = CONFLICT_OVERRIDE | CONFLICT_OVERRIDDEN,
+ CONFLICT_ARCHIVE_MIXED = CONFLICT_ARCHIVE_OVERWRITE | CONFLICT_ARCHIVE_OVERWRITTEN,
+ };
+
+ enum EFlag : uint
+ {
+ FLAG_NONE = 0x000,
+ FLAG_PROBLEMATIC = 0x001,
+ FLAG_INFORMATION = 0x002,
+ FLAG_INI = 0x004,
+ FLAG_BSA = 0x008,
+ FLAG_MASTER = 0x010,
+ FLAG_MEDIUM = 0x020,
+ FLAG_LIGHT = 0x040,
+ FLAG_BLUEPRINT = 0x080,
+ FLAG_CLEAN = 0x100,
+ };
+
+ struct FileSystemData
+ {
+ QString name;
+
+ bool hasMasterExtension;
+ bool hasLightExtension;
+
+ bool forceLoaded;
+ bool forceEnabled;
+ bool forceDisabled;
+
+ bool hasIni;
+ boost::container::flat_set<QString, MOBase::FileNameComparator> archives;
+ };
+
+ struct Metadata
+ {
+ QString author;
+ QString description;
+
+ bool isMasterFlagged;
+ bool isMediumFlagged;
+ bool isLightFlagged;
+ bool isBlueprintFlagged;
+ bool hasNoRecords;
+
+ int formVersion;
+ float headerVersion;
+
+ QStringList masters;
+ mutable boost::container::flat_set<QString, MOBase::FileNameComparator> masterUnset;
+ };
+
+ struct State
+ {
+ bool enabled;
+ int priority = -1;
+ QString index;
+ int loadOrder;
+ QString group;
+
+ bool operator<(const State& other) const { return (loadOrder < other.loadOrder); }
+ };
+
+ struct Conflicts
+ {
+ EConflictFlag m_CurrentConflictState = CONFLICT_NONE;
+ QSet<int> m_OverridingList;
+ QSet<int> m_OverriddenList;
+ QSet<int> m_OverwritingArchiveList;
+ QSet<int> m_OverwrittenArchiveList;
+ };
+
+ FileInfo(PluginList* pluginList, const QString& name, bool forceLoaded,
+ bool forceEnabled, bool forceDisabled, bool lightSupported);
+
+ [[nodiscard]] const QString& name() const { return m_FileSystemData.name; }
+
+ [[nodiscard]] bool hasMasterExtension() const
+ {
+ return m_FileSystemData.hasMasterExtension;
+ }
+
+ [[nodiscard]] bool hasLightExtension() const
+ {
+ return m_FileSystemData.hasLightExtension;
+ }
+
+ [[nodiscard]] bool forceLoaded() const { return m_FileSystemData.forceLoaded; }
+ [[nodiscard]] bool forceEnabled() const { return m_FileSystemData.forceEnabled; }
+ [[nodiscard]] bool forceDisabled() const { return m_FileSystemData.forceDisabled; }
+
+ [[nodiscard]] bool hasIni() const { return m_FileSystemData.hasIni; }
+ void setHasIni(bool hasIni) { m_FileSystemData.hasIni = hasIni; }
+ [[nodiscard]] const auto& archives() const { return m_FileSystemData.archives; }
+ void addArchive(const QString& archive) { m_FileSystemData.archives.insert(archive); }
+
+ [[nodiscard]] const QString& author() const { return m_Metadata.author; }
+ void setAuthor(const QString& author) { m_Metadata.author = author; }
+ [[nodiscard]] const QString& description() const { return m_Metadata.description; }
+ void setDescription(const QString& text) { m_Metadata.description = text; }
+ [[nodiscard]] bool isMasterFlagged() const { return m_Metadata.isMasterFlagged; }
+ void setMasterFlagged(bool value) { m_Metadata.isMasterFlagged = value; }
+ [[nodiscard]] bool isMediumFlagged() const { return m_Metadata.isMediumFlagged; }
+ void setMediumFlagged(bool value) { m_Metadata.isMediumFlagged = value; }
+ [[nodiscard]] bool isLightFlagged() const { return m_Metadata.isLightFlagged; }
+ void setLightFlagged(bool value) { m_Metadata.isLightFlagged = value; }
+ [[nodiscard]] bool isBlueprintFlagged() const { return m_Metadata.isBlueprintFlagged; }
+ void setBlueprintFlagged(bool value) { m_Metadata.isBlueprintFlagged = value; }
+ [[nodiscard]] bool hasNoRecords() const { return m_Metadata.hasNoRecords; }
+ void setHasNoRecords(bool value) { m_Metadata.hasNoRecords = value; }
+ [[nodiscard]] int formVersion() const { return m_Metadata.formVersion; }
+ void setFormVersion(int value) { m_Metadata.formVersion = value; }
+ [[nodiscard]] float headerVersion() const { return m_Metadata.headerVersion; }
+ void setHeaderVersion(float value) { m_Metadata.headerVersion = value; }
+
+ [[nodiscard]] const auto& masters() const { return m_Metadata.masters; }
+ void addMaster(const QString& master) { m_Metadata.masters.push_back(master); }
+
+ [[nodiscard]] bool hasMissingMasters() const
+ {
+ return !m_Metadata.masterUnset.empty();
+ }
+
+ [[nodiscard]] const auto& missingMasters() const { return m_Metadata.masterUnset; }
+
+ template <std::ranges::input_range R>
+ void setMissingMasters(R&& range) const
+ {
+ m_Metadata.masterUnset.clear();
+ m_Metadata.masterUnset.insert(std::begin(range), std::end(range));
+ }
+
+ [[nodiscard]] bool enabled() const { return m_State.enabled; }
+ void setEnabled(bool enabled) { m_State.enabled = enabled; }
+ [[nodiscard]] int priority() const { return m_State.priority; }
+ void setPriority(int priority)
+ {
+ m_State.priority = priority;
+ m_Conflicts.invalidate();
+ }
+ [[nodiscard]] const QString& index() const { return m_State.index; }
+ void setIndex(const QString& index) { m_State.index = index; }
+ [[nodiscard]] int loadOrder() const { return m_State.loadOrder; }
+ void setLoadOrder(int loadOrder) { m_State.loadOrder = loadOrder; }
+ [[nodiscard]] const QString& group() const { return m_State.group; }
+ void setGroup(const QString& group) { m_State.group = group; }
+
+ [[nodiscard]] EConflictFlag conflictState() const
+ {
+ return m_Conflicts.value().m_CurrentConflictState;
+ }
+
+ [[nodiscard]] const auto& getPluginOverriding() const
+ {
+ return m_Conflicts.value().m_OverridingList;
+ }
+
+ [[nodiscard]] const auto& getPluginOverridden() const
+ {
+ return m_Conflicts.value().m_OverriddenList;
+ }
+
+ [[nodiscard]] const auto& getPluginOverwritingArchive() const
+ {
+ return m_Conflicts.value().m_OverwritingArchiveList;
+ }
+
+ [[nodiscard]] const auto& getPluginOverwrittenArchive() const
+ {
+ return m_Conflicts.value().m_OverwrittenArchiveList;
+ }
+
+ [[nodiscard]] bool isMasterFile() const;
+ [[nodiscard]] bool isMediumFile() const;
+ [[nodiscard]] bool isSmallFile() const;
+ [[nodiscard]] bool isBlueprintFile() const;
+ [[nodiscard]] bool isAlwaysEnabled() const;
+ [[nodiscard]] bool canBeToggled() const;
+ [[nodiscard]] bool mustLoadAfter(const FileInfo& other) const;
+
+ void invalidateConflicts() const { m_Conflicts.invalidate(); }
+
+private:
+ [[nodiscard]] Conflicts doConflictCheck() const;
+
+ PluginList* m_PluginList;
+ FileSystemData m_FileSystemData;
+ Metadata m_Metadata;
+ State m_State;
+ mutable MOBase::MemoizedLocked<Conflicts> m_Conflicts;
+};
+
+} // namespace TESData
+
+#endif // TESDATA_FILEINFO_H