Sorry, this is a broken commit, but I've been working toward VFS integration, atomic mod activation/deactivation within the VFS, and overall stability. I'm committing because at this point it's "playable", but you'll still need to be aware of profiles and profile_selections in your sqlite3 database. At this point, I still haven't implemented a means to differentiate profiles in a meaningful way. (Also, ignore the Archive Test option in Main, that's still something I'm trying to solve wrt fully swapping from libarchive to libunarr.)

This commit is contained in:
Daniel O'Neill 2025-11-05 01:25:26 -08:00
parent 1e14743bc2
commit 4bd0e980f3
30 changed files with 2820 additions and 164 deletions

View file

@ -1,5 +1,6 @@
#include "database.h"
#include <QDir>
#include <QSqlError>
#include <QSqlQuery>
@ -106,6 +107,8 @@ QList<ModEntry *> Database::modManifest(int id)
ent->m_id = q.value(0).toInt();
ent->m_archivePath = q.value(1).toString();
ent->m_installedPath = q.value(2).toString();
ent->m_lparts = ent->m_installedPath.split( QDir::separator(), Qt::SkipEmptyParts );
ent->m_lpath = ent->m_lparts.join( QDir::separator() );
results << ent;
}
return results;

View file

@ -13,6 +13,8 @@ public:
int m_id;
QString m_archivePath;
QString m_installedPath;
QString m_lpath;
QStringList m_lparts;
ModEntry() : m_id{0} {}
/*

View file

@ -222,7 +222,7 @@ int FSProxy::open(const QString &qpath, bool createIfNeeded)
//qDebug() << "FSProxy::open: (cached)" << fullpath;
if( !f->open(QIODevice::ReadWrite) )
{
qDebug() << "FSProxy::open: failed:" << f->errorString();
qDebug() << "FSProxy::open: " << qpath << " failed:" << f->errorString();
f->deleteLater();
return -ENODEV;
}

459
FuseMounter/fusearchive.cpp Normal file
View file

@ -0,0 +1,459 @@
#include "fusearchive.h"
#include "FuseMounter/fuseinterface.h"
#include "fusebase.h"
#include <QDebug>
#include <QDir>
#include <QSqlQuery>
#include <time.h>
FuseAccessorArchive::FuseAccessorArchive(FuseInterface *parent, QSqlDatabase &database, int modId, const QString &modPath)
: FuseAccessorBase(parent, modPath, FuseAccessorBase::FH_ARCHIVE),
m_valid{false},
m_stream{nullptr},
m_archive{nullptr},
m_database{database},
m_id{modId}
{
m_stream = ar_open_file(m_resource.toStdString().c_str());
if( !m_stream ) {
qDebug() << "Failed to open archive stream:" << m_resource;
//return false;
return;
}
if( m_resource.endsWith("7z", Qt::CaseInsensitive) )
m_archive = ar_open_7z_archive(m_stream);
else if( m_resource.endsWith("rar", Qt::CaseInsensitive) )
m_archive = ar_open_rar_archive(m_stream);
else if( m_resource.endsWith("zip", Qt::CaseInsensitive) )
m_archive = ar_open_zip_archive(m_stream, false);
else {
qDebug() << "Unsupported archive type:" << m_resource;
return;
}
if( !m_archive )
qDebug() << "Failed to open archive:" << m_resource;
m_entries = readEntries();
m_manifest = readManifest();
m_valid = true;
//return NULL != m_archive;
}
FuseAccessorArchive::~FuseAccessorArchive()
{
if( m_archive )
ar_close_archive(m_archive);
if( m_stream )
ar_close(m_stream);
m_archive = NULL;
m_stream = NULL;
}
QMap< QString, FuseArchiveEntry > FuseAccessorArchive::readEntries()
{
QMap< QString, FuseArchiveEntry > results;
qDebug() << "FuseAccessorArchive::readEntries:" << m_resource;
while( ar_parse_entry(m_archive) )
{
const char *entryname = ar_entry_get_name(m_archive);
if( !entryname || strlen(entryname) <= 0 )
continue;
/*
if( (st->st_mode & S_IFMT) == S_IFDIR )
continue;
*/
time64_t filetime = ar_entry_get_filetime(m_archive) / 100000000;
FuseArchiveEntry ent;
ent.m_actual = QString::fromUtf8(entryname);
QString lpath = ent.m_actual.toLower();
QStringList lparts = lpath.split(QDir::separator(), Qt::SkipEmptyParts);
ent.m_path = lparts.join(QDir::separator());
ent.m_offset = ar_entry_get_offset(m_archive);
memset( &ent.m_stat, 0, sizeof(struct stat));
ent.m_stat.st_size = ar_entry_get_size(m_archive);
ent.m_stat.st_mode = S_IFREG | 0755;
ent.m_stat.st_nlink = 1;
ent.m_stat.st_uid = geteuid();
ent.m_stat.st_gid = getegid();
ent.m_stat.st_atim.tv_sec = filetime;
ent.m_stat.st_mtim.tv_sec = filetime;
ent.m_stat.st_ctim.tv_sec = filetime;
results.insert( lpath, ent );
qDebug().nospace() << " + Adding " << lpath << "/" << ent.m_stat.st_size << " bytes at offset " << ent.m_offset;
}
return results;
}
QList< FuseArchiveMapping > FuseAccessorArchive::readManifest()
{
QList< FuseArchiveMapping > results;
QSqlQuery q = QSqlQuery(m_database);
q.prepare("SELECT dest, lower(source) FROM files WHERE modId=?");
q.bindValue(0, m_id);
if( !q.exec() )
return results;
while( q.next() )
{
QString dest = q.value(0).toString();
QString source = q.value(1).toString();
if( !m_entries.contains(source) )
{
qDebug() << "FuseAccessorArchive::readManifest: Missing entry" << source << "in archive!";
continue;
}
FuseArchiveMapping mapping;
mapping.m_entry = m_entries[source];
mapping.m_actual = dest;
mapping.m_path = dest.toLower();
results.append(mapping);
}
return results;
}
QList< QPair< int, QString > > FuseAccessorArchive::activeArchives(QSqlDatabase &database, int profileId)
{
QString qstr = QString("SELECT modId, filename FROM mods WHERE moddir IS NULL AND modId IN (SELECT modId FROM profile_selections WHERE profileId=%1) ORDER BY idx DESC").arg(profileId);
qDebug() << qstr;
QSqlQuery q = QSqlQuery(qstr, database);
//QSqlQuery q = QSqlQuery("SELECT modId, filename FROM mods WHERE enabled != 0 ORDER BY idx DESC", m_database);
QList< QPair< int, QString > > results;
while( q.next() )
{
QPair< int, QString > pair( q.value(0).toInt(), q.value(1).toString() );
results << pair;
}
return results;
}
int FuseAccessorArchive::makeWritable(struct fuse_file_info *fi, const QString &origsource, const QString &path, const QByteArray &fullData, QIODeviceBase::OpenMode mode, const QByteArray &data, off_t offset)
{
FuseFHBase *fh = m_parent->makeWritableFromData(fullData, origsource, path, mode);
if( !fh )
return -1;
fi->fh = reinterpret_cast<quint64>(fh);
return fh->write(data, offset, fi);
}
/*******************/
int FuseAccessorArchive::getattr(const QString &path, struct stat *stbuf)
{
if( m_statCache.contains(path) )
{
if( NULL == m_statCache[path] )
return -ENOENT;
memcpy( stbuf, m_statCache[path], sizeof(struct stat) );
return 0;
}
QString lpath = path.toLower().mid(1).split( QDir::separator(), Qt::SkipEmptyParts ).join(QDir::separator());
int lplen = lpath.length();
if( lpath.isEmpty() )
{
memset( stbuf, 0, sizeof(struct stat) );
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
stbuf->st_uid = geteuid();
stbuf->st_gid = getegid();
time_t t = ::time(NULL);
stbuf->st_atim.tv_sec = t;
stbuf->st_ctim.tv_sec = t;
stbuf->st_mtim.tv_sec = t;
m_statCache[path] = new struct stat;
memcpy( m_statCache[path], stbuf, sizeof(struct stat) );
return 0;
}
for( const FuseArchiveMapping &mapping : std::as_const(m_manifest) )
{
int mplen = mapping.m_path.length();
if( mplen < lplen )
continue;
if( !mapping.m_path.startsWith(lpath) )
continue;
if( mplen > lplen && QDir::separator() == mapping.m_path.at(lplen) )
{
qDebug() << "FuseAccessorArchive::getattr: Found dir" << mapping.m_path << "in" << lpath;
memset( stbuf, 0, sizeof(struct stat) );
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
stbuf->st_uid = geteuid();
stbuf->st_gid = getegid();
time_t t = ::time(NULL);
stbuf->st_atim.tv_sec = t;
stbuf->st_ctim.tv_sec = t;
stbuf->st_mtim.tv_sec = t;
m_statCache[path] = new struct stat;
memcpy( m_statCache[path], stbuf, sizeof(struct stat) );
return 0;
}
if( mplen == lplen )
{
const FuseArchiveEntry &arcent = mapping.m_entry;
memcpy( stbuf, &arcent.m_stat, sizeof(struct stat) );
qDebug() << "FuseAccessorArchive::getattr: Found file" << mapping.m_path << "in" << lpath;
m_statCache[path] = new struct stat;
memcpy( m_statCache[path], stbuf, sizeof(struct stat) );
return 0;
}
}
//qDebug() << "Not found.";
m_statCache[path] = NULL;
return -ENOENT;
}
QStringList FuseAccessorArchive::readdir(const QString &path)
{
if( m_dirCache.contains(path) )
return m_dirCache[path];
QStringList results;
QString lpath = path.toLower().mid(1).split( QDir::separator(), Qt::SkipEmptyParts ).join(QDir::separator());
int lplen = lpath.length();
//qDebug() << "FuseAccessorArchive::readdir:" << m_resource << " / " << lpath;
for( const FuseArchiveMapping &mapping : std::as_const(m_manifest) )
{
int mplen = mapping.m_path.length();
if( lplen >= mplen )
continue;
if( !mapping.m_path.startsWith(lpath) )
continue;
QStringList parts = mapping.m_path.mid(lplen).toLower().split( QDir::separator(), Qt::SkipEmptyParts );
QString nent = parts.at(0);
if( results.contains(nent) )
continue;
qDebug() << "FuseAccessorArchive::readdir:" << m_resource << "- Found" << nent << "inside" << lpath;
results.append(nent);
}
m_dirCache.insert(path, results);
return results;
}
#define BLOCKSIZE 8192
FuseFHBase *FuseAccessorArchive::open(const QString &path, QIODeviceBase::OpenMode mode)
{
QString lpath = path.toLower().mid(1).split( QDir::separator(), Qt::SkipEmptyParts ).join(QDir::separator());
int lplen = lpath.length();
QString npath;
for( const FuseArchiveMapping &mapping : std::as_const(m_manifest) )
{
int mplen = mapping.m_path.length();
if( lplen != mplen )
continue;
if( lpath != mapping.m_path )
continue;
npath = mapping.m_path; //mapping.m_actual;
break;
}
if( npath.isEmpty() )
return NULL;
qDebug() << "FuseAccessorArchive::open:" << path << mode << "->" << npath;
FuseFHArchive *arcent = new FuseFHArchive(this, path, npath);
if( !arcent->open(mode) )
{
arcent->deleteLater();
return NULL;
}
//m_open_handles.insert( path, arcent );
return arcent;
}
bool FuseAccessorArchive::slurpData(const QString &path, const QString &origpath)
{
/*
if( m_parent->cacheGrab(path) )
return true;
*/
int plen = path.length();
FuseArchiveEntry entry;
bool foundIt = false;
for( const FuseArchiveMapping &mapping : std::as_const(m_manifest) )
{
int mplen = mapping.m_path.length();
//qDebug() << "cmp:" << path << "vs." << mapping.m_path;
if( plen != mplen )
continue;
if( path != mapping.m_path )
continue;
entry = mapping.m_entry;
foundIt = true;
break;
}
if( !foundIt ) {
qDebug() << "FuseAccessorArchive::slurpData: Couldn't find" << path;
return false;
}
m_mutex.lock();
qDebug() << "Cheaty skip to" << entry.m_offset << "bytes...";
if( !ar_parse_entry_at(m_archive, entry.m_offset) ) {
qDebug() << "Archive::getEntry: ar_parse_entry_at failed!";
m_mutex.unlock();
deleteLater();
return false;
}
char buff[BLOCKSIZE * 256];
size_t len = sizeof(buff);
if( len > (size_t)entry.m_stat.st_size )
len = entry.m_stat.st_size;
QByteArray data;
data.reserve(len);
while( ar_entry_uncompress(m_archive, buff, len ) ) {
data.append(buff, len);
len = entry.m_stat.st_size - data.length();
if( len <= 0 )
break;
if( len > sizeof(buff) )
len = sizeof(buff);
}
m_mutex.unlock();
qDebug() << "Caching" << path << "which is" << data.length() << "bytes.";
m_parent->cacheInsert(origpath, data);
return true;
}
QSharedPointer<QByteArray> FuseAccessorArchive::cacheGrab(const QString &path)
{
if(QSharedPointer<QByteArray> sptr = m_parent->cacheGrab(path))
return sptr;
return QSharedPointer<QByteArray>();
}
FuseFHArchive::FuseFHArchive(FuseAccessorBase *parent, const QString &path, const QString &newpath)
: FuseFHBase(parent, path),
m_accessCount{0},
m_newpath{newpath}
{
m_archive = qobject_cast<FuseAccessorArchive *>(parent);
}
FuseFHArchive::~FuseFHArchive()
{
FuseFHArchive::release();
}
bool FuseFHArchive::open(QIODeviceBase::OpenMode mode)
{
m_mode = mode;
qDebug() << "FuseFHArchive::open:" << m_path << "->" << m_newpath;
FuseAccessorArchive *accessor = qobject_cast<FuseAccessorArchive *>(m_parent);
//QString lpath = m_path.toLower();
//FuseArchiveEntry &entry = accessor->m_entries[lpath];
QSharedPointer<QByteArray> entry = m_archive->cacheGrab(m_path);
if( entry )
return true;
m_accessCount++;
m_lastAccess = time(NULL);
return accessor->slurpData(m_newpath, m_path);
}
QByteArray FuseFHArchive::read(size_t size, off_t offset)
{
m_lastAccess = time(NULL);
QSharedPointer<QByteArray> data = m_archive->cacheGrab(m_path);
if( !data )
{
if( !m_archive->slurpData(m_newpath, m_path) )
return QByteArray();
data = m_archive->cacheGrab(m_path);
if( !data )
return QByteArray();
}
return data->mid(offset, size);
}
int FuseFHArchive::write(const QByteArray &data, off_t offset, struct fuse_file_info *fi)
{
if( m_mode & QIODeviceBase::ReadOnly )
return -1;
//FuseArchiveEntry &entry = m_entries[lpath];
qDebug() << "FuseFHArchive::write: Upgrading" << m_newpath << "to writable" << m_path;
FuseAccessorArchive *accessor = qobject_cast<FuseAccessorArchive *>(m_parent);
QSharedPointer<QByteArray> entry = m_archive->cacheGrab(m_path);
if( !entry )
{
if( !m_archive->slurpData(m_newpath, m_path) )
return -EINVAL;
entry = m_archive->cacheGrab(m_path);
if( !entry )
return -EINVAL;
}
QByteArray dcopy(entry->constData(), entry->length());
int rval = accessor->makeWritable(fi, m_path, m_newpath, dcopy, m_mode, data, offset);
release();
deleteLater();
return rval;
}
off_t FuseFHArchive::lseek(off_t off, int whence)
{
Q_UNUSED(whence)
m_lastAccess = time(NULL);
QSharedPointer<QByteArray> data = m_archive->cacheGrab(m_path);
if( !data )
return -EINVAL;
return off > 0 && off < data->length() ? off : -EINVAL;
}
void FuseFHArchive::release()
{
/*
FuseAccessorArchive *accessor = qobject_cast<FuseAccessorArchive *>(m_parent);
accessor->m_open_handles.take(m_path)->deleteLater();
*/
}

146
FuseMounter/fusearchive.h Normal file
View file

@ -0,0 +1,146 @@
#ifndef FUSEARCHIVE_H
#define FUSEARCHIVE_H
#include <QCache>
#include <QMutex>
#include <QObject>
#include <QSqlDatabase>
extern "C" {
#include <fcntl.h>
#include <unarr.h>
#include <unistd.h>
}
#include "fusebase.h"
class ModLibrary;
class ModEntry;
class FuseArchiveEntry : public QObject {
public:
FuseArchiveEntry(QObject *parent=nullptr)
: QObject(parent)
{
memset(&m_stat, 0, sizeof(struct stat));
}
FuseArchiveEntry(const FuseArchiveEntry& other)
: QObject()
{
m_path = other.m_path;
m_actual = other.m_actual;
m_offset = other.m_offset;
memcpy( &m_stat, &other.m_stat, sizeof(struct stat));
}
FuseArchiveEntry& operator=(const FuseArchiveEntry& other) {
if (this != &other) {
m_path = other.m_path;
m_actual = other.m_actual;
m_offset = other.m_offset;
memcpy( &m_stat, &other.m_stat, sizeof(struct stat));
}
return *this;
}
FuseArchiveEntry &findChild(const QStringList &path);
QString m_path;
QString m_actual;
quint64 m_offset;
struct stat m_stat;
};
class FuseArchiveMapping : public QObject {
public:
FuseArchiveMapping(QObject *parent=nullptr)
: QObject(parent)
{
}
FuseArchiveMapping(const FuseArchiveMapping& other)
: QObject(),
m_path{other.m_path},
m_actual{other.m_actual},
m_entry{other.m_entry}
{
}
FuseArchiveMapping& operator=(const FuseArchiveMapping& other) {
if (this != &other) {
m_path = other.m_path;
m_actual = other.m_actual;
m_entry = other.m_entry;
}
return *this;
}
QString m_path;
QString m_actual;
FuseArchiveEntry m_entry;
};
class FuseAccessorArchive : public FuseAccessorBase
{
Q_OBJECT
protected:
friend class FuseFHArchive;
bool m_valid;
ar_stream *m_stream;
ar_archive *m_archive;
QMutex m_mutex;
QSqlDatabase &m_database;
int m_id;
QMap< QString, QStringList > m_dirCache;
QMap< QString, struct stat * > m_statCache;
QMap< QString, FuseArchiveEntry > m_entries;
QList< FuseArchiveMapping > m_manifest;
QMap< QString, FuseArchiveEntry > readEntries();
QList< FuseArchiveMapping > readManifest();
public:
explicit FuseAccessorArchive(FuseInterface *parent, QSqlDatabase &database, int modId, const QString &modPath );
virtual ~FuseAccessorArchive();
int getattr(const QString &path, struct stat *stbuf) override;
QStringList readdir(const QString &path) override;
FuseFHBase *open(const QString &path, QIODeviceBase::OpenMode mode) override;
static QList< QPair<int, QString> > activeArchives(QSqlDatabase &database, int profileId);
// This expects the new entry to be created, then return "write" on the opened handle, otherwise return the appropriate "write" error code.
int makeWritable(struct fuse_file_info *fi, const QString &origsource, const QString &path, const QByteArray &fullData, QIODeviceBase::OpenMode mode, const QByteArray &data, off_t offset);
bool slurpData(const QString &path, const QString &origpath);
QSharedPointer<QByteArray> cacheGrab(const QString &path);
};
class FuseFHArchive : public FuseFHBase
{
Q_OBJECT
FuseAccessorArchive *m_archive;
int m_accessCount;
time_t m_lastAccess;
QString m_newpath;
public:
explicit FuseFHArchive(FuseAccessorBase *parent, const QString &path, const QString &newpath);
virtual ~FuseFHArchive();
bool open(QIODeviceBase::OpenMode mode) override;
QByteArray read(size_t size, off_t offset) override;
int write(const QByteArray &data, off_t offset, fuse_file_info *fi) override;
off_t lseek(off_t off, int whence) override;
void release() override;
};
#endif // FUSEARCHIVE_H

22
FuseMounter/fusebase.cpp Normal file
View file

@ -0,0 +1,22 @@
#include "fusebase.h"
#include "fuseinterface.h"
FuseAccessorBase::FuseAccessorBase(FuseInterface *parent, const QString &resource, e_fh_type type)
: QObject(parent),
m_parent{parent},
m_resource{resource},
m_type{type}
{
}
FuseAccessorBase::~FuseAccessorBase()
{
}
FuseFHBase::FuseFHBase(FuseAccessorBase *parent, const QString &path)
: QObject(parent),
m_parent{parent},
m_path{path},
m_mode{QIODeviceBase::ReadOnly}
{
}

73
FuseMounter/fusebase.h Normal file
View file

@ -0,0 +1,73 @@
#ifndef FUSEBASE_H
#define FUSEBASE_H
#include <QFileDevice>
#include <QMap>
#include <QObject>
#include <QString>
class FuseInterface;
class FuseAccessorBase;
class FuseFHBase;
class FuseAccessorBase : public QObject
{
Q_OBJECT
public:
typedef enum { FH_UNKNOWN=1, FH_ARCHIVE=2, FH_PROXY=3, FH_SANDBOX=4 } e_fh_type;
protected:
FuseInterface *m_parent;
bool m_stopping;
QString m_resource;
e_fh_type m_type;
Q_ENUM(e_fh_type)
public:
explicit FuseAccessorBase(FuseInterface *parent, const QString &resource, e_fh_type type=FH_UNKNOWN);
virtual ~FuseAccessorBase();
e_fh_type getType() { return m_type; }
const QString &getResource() { return m_resource; }
FuseInterface *getInterface() { return m_parent; }
virtual int getattr(const QString &path, struct stat *stbuf) = 0;
virtual QStringList readdir(const QString &path) = 0;
virtual FuseFHBase *open(const QString &path, QIODeviceBase::OpenMode mode) = 0;
virtual int truncate(const QString &path, off_t offset) { Q_UNUSED(path) Q_UNUSED(offset) return -1; }
virtual int rename(const QString &path, const QString &newname) { Q_UNUSED(path) Q_UNUSED(newname) return -1; }
virtual int rmdir(const QString &path) { Q_UNUSED(path) return -1; }
virtual int unlink(const QString &path) { Q_UNUSED(path) return -1; }
virtual int mkdir(const QString &path, QFileDevice::Permissions mode) { Q_UNUSED(path) Q_UNUSED(mode) return -1; }
virtual int create(const QString &path, QFileDevice::Permissions mode) { Q_UNUSED(path) Q_UNUSED(mode) return -1; }
};
class FuseFHBase : public QObject
{
Q_OBJECT
protected:
friend class FuseInterface;
FuseAccessorBase *m_parent;
QString m_path;
QIODeviceBase::OpenMode m_mode;
public:
explicit FuseFHBase(FuseAccessorBase *parent, const QString &path);
virtual ~FuseFHBase() {}
FuseAccessorBase *getAccessor() { return m_parent; }
const QString &getPath() { return m_path; }
virtual bool open(QIODeviceBase::OpenMode mode) = 0;
virtual QByteArray read(size_t size, off_t offset) = 0;
virtual off_t lseek(off_t off, int whence) = 0;
virtual int write(const QByteArray &data, off_t offset, struct fuse_file_info *fi) { Q_UNUSED(data) Q_UNUSED(offset) Q_UNUSED(fi) return -1; }
virtual void release() {}
};
#endif // FUSEBASE_H

View file

@ -1,13 +1,840 @@
#include "fuseinterface.h"
#include "fusebase.h"
#include "fusearchive.h"
#include "fuseproxy.h"
#include "fusesandbox.h"
#include <errno.h>
#include <string.h>
#include <sys/mount.h>
FuseInterface::FuseInterface(QObject *parent)
: QObject{parent}
{}
#define CACHE_ENABLED true
//#define FUSE_DEBUG 1
FuseInterface::FuseInterface(QSqlDatabase &database, int profileId, const QString &gameDataDir, const QString &sandboxDir, const QString &mountpoint, QObject *parent)
: QObject{parent},
m_fuse_id{0},
m_fuse{nullptr},
m_fuse_session{nullptr},
m_notifier{nullptr},
m_database{database},
m_profileId{profileId},
m_gameDataDir{gameDataDir},
m_sandboxDir{sandboxDir},
m_mountpoint{mountpoint}
{
// Default to 512MB
m_cache_data.setMaxCost(512 * 1024 * 1024);
}
FuseInterface::~FuseInterface()
{
}
bool FuseInterface::mount() {
char mpoint[8192];
std::string mountStr = m_mountpoint.toStdString();
strncpy(mpoint, mountStr.c_str(), sizeof(mpoint)-1);
// Prefer options before mountpoint so fuse_parse_cmdline finds them reliably
char *argv[] = {
const_cast<char *>("quickmod"),
const_cast<char *>("-f"),
const_cast<char *>("-o"),
const_cast<char *>("auto_unmount"),
mpoint,
nullptr
};
int argc = sizeof(argv) / sizeof(argv[0]) - 1;
struct fuse_args args = FUSE_ARGS_INIT(argc, (char **)argv);
struct fuse_cmdline_opts opts;
if( 0 != fuse_parse_cmdline(&args, &opts) ) {
qCritical() << "Failed to parse options?";
return false;
}
struct fuse_operations qmfuse_oper;
memset( &qmfuse_oper, 0, sizeof(struct fuse_operations) );
qmfuse_oper.getattr = f_getattr;
qmfuse_oper.mkdir = f_mkdir;
qmfuse_oper.unlink = f_unlink;
qmfuse_oper.rmdir = f_rmdir;
//qmfuse_oper.truncate = f_truncate;
qmfuse_oper.open = f_open;
qmfuse_oper.read = f_read;
qmfuse_oper.write = f_write;
qmfuse_oper.release = f_release;
qmfuse_oper.readdir = f_readdir;
qmfuse_oper.create = f_create;
//qmfuse_oper.lseek = f_lseek;
size_t op_size = sizeof(qmfuse_oper);
m_fuse = fuse_new(&args, &qmfuse_oper, op_size, this);
if( !m_fuse ) {
qCritical() << "Failed to create new FUSE object.";
return false;
}
if( 0 != fuse_mount(m_fuse, mpoint) ) {
qCritical() << "Failed at fuse_mount!";
fuse_destroy(m_fuse);
return false;
}
m_fuse_session = fuse_get_session(m_fuse);
if( 0 != fuse_set_signal_handlers(m_fuse_session) ) {
qCritical() << "Failed to set FUSE signal handlers!";
return false;
}
m_fuse_id = fuse_session_fd(m_fuse_session);
if( m_notifier )
m_notifier->deleteLater();
m_notifier = new QSocketNotifier(m_fuse_id, QSocketNotifier::Read, this);
QObject::connect( m_notifier, &QSocketNotifier::activated, this, &FuseInterface::pump );
qDebug() << "Mounted OKAY.";
init();
return true;
}
int FuseInterface::pump()
{
int ret = 0;
do {
struct fuse_buf fbuf;
memset(&fbuf, 0, sizeof(struct fuse_buf));
if( fuse_session_exited(m_fuse_session) ) {
qDebug() << "Feh. Session closed!";
deleteLater();
return -1;
}
ret = fuse_session_receive_buf(m_fuse_session, &fbuf);
if( -EINTR == ret )
return 0; // Nothing to do.
if( 0 >= ret )
return ret;
fuse_session_process_buf(m_fuse_session, &fbuf);
} while(0 == ret);
return ret;
}
void FuseInterface::unmount()
{
if( m_notifier ) {
m_notifier->setEnabled(false);
m_notifier->deleteLater();
}
m_notifier = nullptr;
// Stop handling signals and mark session as exiting to quiesce activity
if( m_fuse_session ) {
fuse_remove_signal_handlers(m_fuse_session);
fuse_session_exit(m_fuse_session);
}
// Perform a lazy unmount to succeed even if handles are flushing
// This detaches the mount and completes once all refs are closed
if (!m_mountpoint.isEmpty()) {
::umount2(m_mountpoint.toStdString().c_str(), MNT_DETACH);
}
if( m_fuse )
{
fuse_unmount(m_fuse);
fuse_destroy(m_fuse);
}
m_fuse = nullptr;
m_fuse_session = nullptr;
deleteLater();
}
void FuseInterface::init()
{
clear();
m_mutex.lock();
QString sandboxDir = m_sandboxDir + QDir::separator() + QString::number(m_profileId);
FuseAccessorSandbox *sandbox = new FuseAccessorSandbox(this, sandboxDir);
qDebug() << "Adding sandbox dir path:" << sandboxDir;
append(sandbox);
QList< FuseProxyMapping > proxypaths = FuseAccessorProxy::proxyList(m_database, m_profileId);
FuseAccessorProxy *pent = new FuseAccessorProxy(this, proxypaths);
append(pent);
QList< QPair<int, QString> > activeArchives = FuseAccessorArchive::activeArchives(m_database, m_profileId);
for( const QPair<int, QString> &arc : std::as_const(activeArchives) )
{
qDebug() << "Adding archive:" << arc.second;
FuseAccessorArchive *aent = new FuseAccessorArchive(this, m_database, arc.first, arc.second);
append(aent);
}
FuseAccessorProxy *vanilla = new FuseAccessorProxy(this, m_gameDataDir);
qDebug() << "Adding game data dir path:" << m_gameDataDir;
append(vanilla);
m_mutex.unlock();
}
void FuseInterface::clear()
{
m_mutex.lock();
for( FuseAccessorBase *fab : std::as_const(m_accessors) )
{
fab->deleteLater();
}
m_accessors.clear();
m_dircache.clear();
for( struct stat *st : std::as_const(m_cache_stat) )
free(st);
m_cache_stat.clear();
m_cache_accessor.clear();
m_cache_data.clear();
// TODO: m_watcher?
m_stats.clear();
m_mutex.unlock();
}
void FuseInterface::prepend(FuseAccessorBase *accessor)
{
m_accessors.prepend(accessor);
}
void FuseInterface::append(FuseAccessorBase *accessor)
{
m_accessors.append(accessor);
}
void FuseInterface::remove(FuseAccessorBase *accessor)
{
m_accessors.removeOne(accessor);
accessor->deleteLater();
}
/*
int FuseInterface::utilQPermsToMode(QFileDevice::Permissions mode)
{
int smode = 0;
if( QFileDevice::ReadUser & mode ) smode += S_IRUSR;
if( QFileDevice::WriteUser & mode ) smode += S_IWUSR;
if( QFileDevice::ExeUser & mode ) smode += S_IXUSR;
if( QFileDevice::ReadGroup & mode ) smode += S_IRGRP;
if( QFileDevice::WriteGroup & mode ) smode += S_IWGRP;
if( QFileDevice::ExeGroup & mode ) smode += S_IXGRP;
if( QFileDevice::ReadOther & mode ) smode += S_IROTH;
if( QFileDevice::WriteOther & mode ) smode += S_IWOTH;
if( QFileDevice::ExeOther & mode ) smode += S_IXOTH;
return smode;
}
*/
FuseFHBase *FuseInterface::makeWritableFromSource(const QString &source, const QString &origsource, const QString &dest, QIODeviceBase::OpenMode mode)
{
m_mutex.lock();
for( FuseAccessorBase *fab : std::as_const(m_accessors) )
{
if( FuseAccessorBase::FH_SANDBOX != fab->getType() )
continue;
FuseAccessorSandbox *sandbox = reinterpret_cast<FuseAccessorSandbox *>(fab);
FuseFHBase *fh = sandbox->makeWritableFromSource(source, origsource, dest, mode);
m_mutex.unlock();
return fh;
}
m_mutex.unlock();
return NULL;
}
FuseFHBase *FuseInterface::makeWritableFromData(const QByteArray &source, const QString &origsource, const QString &dest, QIODeviceBase::OpenMode mode)
{
m_mutex.lock();
for( FuseAccessorBase *fab : std::as_const(m_accessors) )
{
if( FuseAccessorBase::FH_SANDBOX != fab->getType() )
continue;
FuseAccessorSandbox *sandbox = reinterpret_cast<FuseAccessorSandbox *>(fab);
FuseFHBase *fh = sandbox->makeWritableFromData(source, origsource, dest, mode);
m_mutex.unlock();
return fh;
}
m_mutex.unlock();
return NULL;
}
// don't look at me, don't look at me! guhhhh~~!!!
static QString humanSize(qint64 bytes)
{
static const char *suffixes[] = { "B", "KiB", "MiB", "GiB", "TiB" };
double size = bytes;
int i = 0;
while (size >= 1024.0 && i < 4) {
size /= 1024.0;
++i;
}
return QString::number(size, 'f', (i == 0 ? 0 : 2)) + ' ' + suffixes[i];
}
void FuseInterface::cacheInsert(const QString &path, QByteArray &data)
{
if( data.isEmpty() )
return;
QSharedPointer<QByteArray> sharedData = QSharedPointer<QByteArray>::create(std::move(data));
QSharedPointer<QByteArray> *val = new QSharedPointer<QByteArray>(std::move(sharedData));
m_cache_data.insert(path, val, val->data()->size());
qDebug() << " - m_cache_data now contains" << m_cache_data.size() << "entries, totalling" << humanSize(m_cache_data.totalCost()) << "bytes";
emit entryUpdated(path, "Archive", 1, 1);
}
QSharedPointer<QByteArray> FuseInterface::cacheGrab(const QString &path)
{
if( !m_cache_data.contains(path) )
return {};
auto *p = m_cache_data.object(path);
return *p;
}
int FuseInterface::getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
{
Q_UNUSED(fi)
int ret;
QString qpath(path);
if( CACHE_ENABLED && m_cache_stat.contains(qpath) )
{
if( !m_cache_stat[qpath] )
return -ENOENT;
memcpy( stbuf, m_cache_stat[qpath], sizeof(struct stat) );
return 0;
}
for( FuseAccessorBase *ent : std::as_const(m_accessors) )
{
ret = ent->getattr(qpath, stbuf);
if( 0 == ret ) {
struct stat *scache = (struct stat *)malloc(sizeof(struct stat));
memcpy( scache, stbuf, sizeof(struct stat) );
m_cache_stat.insert(qpath, scache);
m_cache_accessor.insert(qpath, ent);
return 0;
}
}
m_cache_stat.insert(qpath, NULL);
return -ENOENT;
}
int FuseInterface::create(const char *path, mode_t mode, struct fuse_file_info *fi)
{
Q_UNUSED(fi)
int ret = 1;
QString qpath(path);
FuseAccessorSandbox *sb = NULL;
m_mutex.lock();
for( FuseAccessorBase *fab : std::as_const(m_accessors) )
{
if( FuseAccessorBase::FH_SANDBOX != fab->getType() )
continue;
sb = qobject_cast<FuseAccessorSandbox *>(fab);
QFileDevice::Permissions qmode = (QFileDevice::Permissions)mode;
ret = sb->create(qpath, qmode);
break;
}
if( CACHE_ENABLED && m_cache_stat.contains(qpath) )
{
struct stat *st = m_cache_stat.take(qpath);
if( st )
free(st);
}
m_mutex.unlock();
if( 0 == ret && sb )
return open(path, fi);
return ret;
}
int FuseInterface::mkdir(const char *path, mode_t mode)
{
int ret = 1;
QString qpath(path);
m_mutex.lock();
for( FuseAccessorBase *fab : std::as_const(m_accessors) )
{
if( FuseAccessorBase::FH_SANDBOX != fab->getType() )
continue;
FuseAccessorSandbox *sb = qobject_cast<FuseAccessorSandbox *>(fab);
QFileDevice::Permissions qmode = (QFileDevice::Permissions)mode;
ret = sb->mkdir(qpath, qmode);
break;
}
if( CACHE_ENABLED ) {
if( m_cache_stat.contains(qpath) )
{
struct stat *st = m_cache_stat.take(qpath);
if( st )
free(st);
}
if( m_dircache.contains(qpath) )
m_dircache.remove(qpath);
}
m_mutex.unlock();
return ret;
}
int FuseInterface::unlink(const char *path)
{
int ret = 1;
QString qpath(path);
m_mutex.lock();
for( FuseAccessorBase *fab : std::as_const(m_accessors) )
{
if( FuseAccessorBase::FH_SANDBOX != fab->getType() )
continue;
FuseAccessorSandbox *sb = qobject_cast<FuseAccessorSandbox *>(fab);
ret = sb->unlink(qpath);
break;
}
if( CACHE_ENABLED ) {
if( m_cache_stat.contains(qpath) )
{
struct stat *st = m_cache_stat.take(qpath);
if( st )
free(st);
}
if( m_dircache.contains(qpath) )
m_dircache.remove(qpath);
}
m_mutex.unlock();
return ret;
}
int FuseInterface::rmdir(const char *path)
{
int ret = 1;
QString qpath(path);
m_mutex.lock();
for( FuseAccessorBase *fab : std::as_const(m_accessors) )
{
if( FuseAccessorBase::FH_SANDBOX != fab->getType() )
continue;
FuseAccessorSandbox *sb = qobject_cast<FuseAccessorSandbox *>(fab);
ret = sb->rmdir(qpath);
break;
}
if( CACHE_ENABLED ) {
if( m_cache_stat.contains(qpath) )
{
struct stat *st = m_cache_stat.take(qpath);
if( st )
free(st);
}
if( m_dircache.contains(qpath) )
m_dircache.remove(qpath);
}
m_mutex.unlock();
return ret;
}
int FuseInterface::rename(const char *path, const char *newname, unsigned int flags)
{
Q_UNUSED(path)
Q_UNUSED(newname)
Q_UNUSED(flags)
return 1;
}
int FuseInterface::truncate(const char *path, off_t offset, struct fuse_file_info *fi)
{
Q_UNUSED(fi)
int ret = 1;
QString qpath(path);
m_mutex.lock();
for( FuseAccessorBase *fab : std::as_const(m_accessors) )
{
if( FuseAccessorBase::FH_SANDBOX != fab->getType() )
continue;
FuseAccessorSandbox *sb = qobject_cast<FuseAccessorSandbox *>(fab);
ret = sb->truncate(qpath, offset);
break;
}
if( CACHE_ENABLED && m_cache_stat.contains(qpath) )
{
struct stat *st = m_cache_stat.take(qpath);
if( st )
free(st);
}
m_mutex.unlock();
return ret;
}
#ifndef FUSE_FILL_DIR_DEFAULTS
constexpr fuse_fill_dir_flags FUSE_FILL_DIR_DEFAULTS = static_cast<fuse_fill_dir_flags>(0);
#endif
int FuseInterface::readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags)
{
Q_UNUSED(offset)
Q_UNUSED(fi)
Q_UNUSED(flags)
QString qpath(path);
if( CACHE_ENABLED && m_dircache.contains(qpath) )
{
QStringList ents = m_dircache[qpath];
for( const QString &ent : std::as_const(ents) )
filler(buf, ent.toStdString().c_str(), NULL, 0, FUSE_FILL_DIR_DEFAULTS);
return 0;
}
QStringList ents, lents;
for( FuseAccessorBase *fab : std::as_const(m_accessors) )
{
QStringList dir = fab->readdir(QString(path));
for( const QStringView nent : std::as_const(dir) )
{
QString blorp = nent.toString();
QString lc = blorp.toLower();
if( lents.contains(lc) )
continue;
filler(buf, blorp.toStdString().c_str(), NULL, 0, FUSE_FILL_DIR_DEFAULTS);
ents.append(blorp);
lents.append(lc);
}
}
if( CACHE_ENABLED )
m_dircache[qpath] = ents;
return 0;
}
int FuseInterface::write(const char *path, const char *buf, size_t bufsz, off_t offset, struct fuse_file_info *fi)
{
Q_UNUSED(path)
FuseFHBase *fhbase = reinterpret_cast<FuseFHBase *>(fi->fh);
qDebug() << "FuseFHBase:" << fhbase;
//FuseAccessorBase *fabase = fhbase->getAccessor();
/*
qDebug() << "fabase:" << fabase;
qDebug() << "Type:" << fabase->getType();
*/
FuseFHBase *sfh = qobject_cast<FuseFHBase *>(fhbase);
QByteArray data(buf, bufsz);
return sfh->write(data, offset, fi);
}
int FuseInterface::open(const char *path, struct fuse_file_info *fi)
{
int fam = (fi->flags & O_ACCMODE);
QString qpath(path);
QIODeviceBase::OpenMode qmode;
switch( fam )
{
case O_RDWR:
qmode = QIODeviceBase::ReadWrite;
if( !( O_TRUNC & fi->flags ) )
qmode |= QIODeviceBase::Append;
break;
case O_WRONLY:
qmode = QIODeviceBase::WriteOnly;
if( !( O_TRUNC & fi->flags ) )
qmode |= QIODeviceBase::Append;
break;
default:
qmode = QIODeviceBase::ReadOnly;
}
if( !( O_CREAT & fi->flags ) )
qmode |= QIODeviceBase::ExistingOnly;
// STATS:
VFSStatEntry se { qpath, QDateTime::currentSecsSinceEpoch(), 0, 0 };
if( CACHE_ENABLED
&& QIODeviceBase::ReadOnly & qmode
&& m_cache_accessor.contains(qpath) )
{
FuseAccessorBase *fab = m_cache_accessor[qpath];
FuseFHBase *fh = fab->open(qpath, qmode);
if( fh ) {
fi->fh = reinterpret_cast<quint64>(fh);
m_open_handles.prepend(fh);
if( !m_stats.contains(qpath) ) {
qDebug() << "Adding1" << qpath;
m_stats.insert(qpath, se);
}
m_stats[qpath].openCount++;
m_stats[qpath].refCount++;
emit entryUpdated(qpath, fab->getResource(), m_stats[qpath].openCount, m_stats[qpath].refCount);
return 0;
}
}
for( FuseAccessorBase *fab : std::as_const(m_accessors) )
{
/*
// FIXME: No no, we iterate through all:
if( FuseAccessorBase::FH_SANDBOX != fab->getType()
&& QIODeviceBase::WriteOnly == qmode )
continue;
*/
FuseFHBase *fh = fab->open(qpath, qmode);
if( !fh )
continue;
fi->fh = reinterpret_cast<quint64>(fh);
m_open_handles.prepend(fh);
if( !m_stats.contains(qpath) ) {
qDebug() << "Adding2" << qpath;
m_stats.insert(qpath, se);
}
m_stats[qpath].openCount++;
m_stats[qpath].refCount++;
emit entryUpdated(qpath, fab->getResource(), m_stats[qpath].openCount, m_stats[qpath].refCount);
return 0;
}
return -EACCES;
}
int FuseInterface::release(const char *path, struct fuse_file_info *fi)
{
Q_UNUSED(path)
FuseFHBase *fhbase = reinterpret_cast<FuseFHBase *>(fi->fh);
QString qpath = fhbase->getPath();
VFSStatEntry se { qpath, QDateTime::currentSecsSinceEpoch(), 0, 0 };
if( !m_stats.contains(qpath) )
qDebug() << "Warning:" << qpath << "release, wasn't opened. Name was changed?";
else {
m_stats[qpath].refCount--;
emit entryUpdated(qpath, fhbase->getAccessor()->getResource(), m_stats[qpath].openCount, m_stats[qpath].refCount);
}
m_open_handles.removeOne(fhbase);
fhbase->release();
fhbase->deleteLater();
return 0;
}
int FuseInterface::read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
Q_UNUSED(path)
FuseFHBase *fhbase = reinterpret_cast<FuseFHBase *>(fi->fh);
QByteArray data = fhbase->read(size, offset);
if( data.isEmpty() )
return 0;
::memcpy( buf, (void *)data.constData(), data.length() );
return data.length();
}
off_t FuseInterface::lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
{
Q_UNUSED(path)
FuseFHBase *fhbase = reinterpret_cast<FuseFHBase *>(fi->fh);
//FuseAccessorBase *fabase = fhbase->getAccessor();
return fhbase->lseek(off, whence);
}
// FUSE glue:
int FuseInterface::f_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->getattr(path, stbuf, fi);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_getattr: %s -> %d\n", path, blorg);
#endif
return blorg;
}
int FuseInterface::f_create(const char *path, mode_t mode, struct fuse_file_info *fi)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->create(path, mode, fi);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_create: %d\n", blorg);
#endif
return blorg;
}
int FuseInterface::f_mkdir(const char *path, mode_t mode)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->mkdir(path, mode);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_mkdir: %d\n", blorg);
#endif
return blorg;
}
int FuseInterface::f_unlink(const char *path)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->unlink(path);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_unlink: %d\n", blorg);
#endif
return blorg;
}
int FuseInterface::f_rmdir(const char *path)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->rmdir(path);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_rmdir: %d\n", blorg);
#endif
return blorg;
}
int FuseInterface::f_rename(const char *path, const char *newname, unsigned int flags)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->rename(path, newname, flags);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_rename: %d\n", blorg);
#endif
return blorg;
}
int FuseInterface::f_truncate(const char *path, off_t offset, struct fuse_file_info *fi)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->truncate(path, offset, fi);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_truncate: %d\n", blorg);
#endif
return blorg;
}
int FuseInterface::f_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->readdir(path, buf, filler, offset, fi, flags);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_readdir: %d\n", blorg);
#endif
return blorg;
}
int FuseInterface::f_write(const char *path, const char *buf, size_t bufsz, off_t offset,
struct fuse_file_info *fi)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->write(path, buf, bufsz, offset, fi);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_write: %d\n", blorg);
#endif
return blorg;
}
int FuseInterface::f_open(const char *path, struct fuse_file_info *fi)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->open(path, fi);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_open: %d\n", blorg);
#endif
return blorg;
}
int FuseInterface::f_release(const char *path, struct fuse_file_info *fi)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->release(path, fi);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_release: %d\n", blorg);
#endif
return blorg;
}
int FuseInterface::f_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
int blorg = iface->read(path, buf, size, offset, fi);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_read: %d\n", blorg);
#endif
return blorg;
}
off_t FuseInterface::f_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
{
struct fuse_context *ctx = fuse_get_context();
FuseInterface *iface = static_cast<FuseInterface*>(ctx->private_data);
off_t blorg = iface->lseek(path, off, whence, fi);
#ifdef FUSE_DEBUG
fprintf(stderr, "f_lseek: %ld\n", blorg);
#endif
return blorg;
}

View file

@ -1,10 +1,15 @@
#ifndef FUSEINTERFACE_H
#define FUSEINTERFACE_H
#include <QObject>
#include <QDir>
#include <QSocketNotifier>
#include <QCache>
#include <QDateTime>
#include <QDir>
#include <QFileSystemWatcher>
#include <QMutex>
#include <QObject>
#include <QSharedPointer>
#include <QSocketNotifier>
#include <QSqlDatabase>
extern "C" {
#define FUSE_USE_VERSION 31
@ -13,12 +18,21 @@ extern "C" {
#include <fuse3/fuse_lowlevel.h>
#include <fcntl.h>
#include <stdarg.h>
#include <unarr.h>
#include <unistd.h>
}
class ArchiveManager;
class FSProxy;
class Database;
//#include "database.h"
struct VFSStatEntry {
QString path;
time_t lastAccess;
quint32 openCount;
quint32 refCount;
};
class FuseAccessorBase;
class FuseFHBase;
class FuseInterface : public QObject
{
Q_OBJECT
@ -26,20 +40,90 @@ class FuseInterface : public QObject
int m_fuse_id;
struct fuse *m_fuse;
struct fuse_session *m_fuse_session;
QSocketNotifier *m_notifier;
QMutex m_mutex;
ArchiveManager *m_archive;
FSProxy *m_proxy;
Database *m_database;
QSqlDatabase &m_database;
int m_profileId;
QString m_gameDataDir;
QString m_sandboxDir;
QString m_mountpoint;
QMap< QString, QStringList > m_dircache;
QMap<QString, struct stat *> m_shortcuts;
QSocketNotifier *m_notifier;
QList< FuseAccessorBase * > m_accessors;
QList< FuseFHBase * > m_open_handles;
QMap< QString, QStringList > m_dircache;
QMap<QString, struct stat *> m_cache_stat;
QMap<QString, FuseAccessorBase *> m_cache_accessor;
QCache< QString, QSharedPointer<QByteArray> > m_cache_data;
QFileSystemWatcher m_watcher;
QHash< QString, VFSStatEntry > m_stats;
public:
explicit FuseInterface(QObject *parent = nullptr);
explicit FuseInterface(QSqlDatabase &database, int profileId, const QString &gameDataDir, const QString &sandboxDir, const QString &mountpoint, QObject *parent=nullptr);
~FuseInterface();
Q_INVOKABLE bool mount();
Q_INVOKABLE int pump();
Q_INVOKABLE void unmount();
Q_INVOKABLE void clear();
Q_INVOKABLE void prepend(FuseAccessorBase *accessor);
Q_INVOKABLE void append(FuseAccessorBase *accessor);
Q_INVOKABLE void remove(FuseAccessorBase *accessor);
QSqlDatabase &getDatabase() { return m_database; }
int getProfileId() { return m_profileId; }
//int utilQPermsToMode(QFileDevice::Permissions perms);
FuseFHBase *makeWritableFromSource(const QString &source, const QString &origsource, const QString &dest, QIODeviceBase::OpenMode mode);
FuseFHBase *makeWritableFromData(const QByteArray &source, const QString &origsource, const QString &dest, QIODeviceBase::OpenMode mode);
void cacheInsert(const QString &path, QByteArray &data);
QSharedPointer<QByteArray> cacheGrab(const QString &path);
protected:
void init();
// FUSE stuff:
int getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi);
int create(const char *path, mode_t mode, struct fuse_file_info *fi);
int mkdir(const char *path, mode_t mode);
int unlink(const char *path);
int rmdir(const char *path);
int rename(const char *path, const char *newname, unsigned int flags);
int truncate(const char *path, off_t offset, struct fuse_file_info *fi);
int readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags);
int write(const char *path, const char *buf, size_t bufsz, off_t offset, struct fuse_file_info *fi);
int open(const char *path, struct fuse_file_info *fi);
int release(const char *path, struct fuse_file_info *fi);
int read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi);
off_t lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi);
// FUSE hooks:
static int f_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi);
static int f_create(const char *path, mode_t mode, struct fuse_file_info *fi);
static int f_mkdir(const char *path, mode_t mode);
static int f_unlink(const char *path);
static int f_rmdir(const char *path);
static int f_rename(const char *path, const char *newname, unsigned int flags);
static int f_truncate(const char *path, off_t offset, struct fuse_file_info *fi);
static int f_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags);
static int f_write(const char *path, const char *buf, size_t bufsz, off_t offset,
struct fuse_file_info *fi);
static int f_open(const char *path, struct fuse_file_info *fi);
static int f_release(const char *path, struct fuse_file_info *fi);
static int f_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi);
static off_t f_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi);
signals:
void entryUpdated(const QString &path, const QString &facility, quint32 count, quint8 refcount);
};
Q_DECLARE_METATYPE(FuseInterface *)
#endif // FUSEINTERFACE_H

315
FuseMounter/fuseproxy.cpp Normal file
View file

@ -0,0 +1,315 @@
#include <QDir>
#include <QFileInfo>
#include <QSqlQuery>
#include <QUrl>
#include <sys/stat.h>
#include "fuseinterface.h"
#include "fuseproxy.h"
FuseAccessorProxy::FuseAccessorProxy(FuseInterface *parent, const QString &resource, e_fh_type type)
: FuseAccessorBase(parent, resource, type)
{
}
FuseAccessorProxy::FuseAccessorProxy(FuseInterface *parent, QList< FuseProxyMapping > entries)
: FuseAccessorBase(parent, {}, FuseAccessorBase::FH_PROXY),
m_entries{entries}
{
}
FuseAccessorProxy::~FuseAccessorProxy()
{
}
QString FuseAccessorProxy::resolvePath(const QString &root, const QString &path, bool justPath)
{
//QUrl upath("file://" + m_resource + path);
QString qpath = path; //upath.toLocalFile();
//qpath.replace("//", "/");
QStringList parts = qpath.split(QDir::separator(), Qt::SkipEmptyParts);
QString lastPart;
if( justPath )
lastPart = parts.takeLast();
if( justPath && lastPart.isEmpty() )
lastPart = parts.takeLast();
//QString bothparts = root;
QString sane;
for( const QString &part : std::as_const(parts) ) {
if( part.length() < 1 )
continue;
QDir cur( root + QDir::separator() + sane );
QStringList entries = cur.entryList(QDir::AllEntries|QDir::NoDotAndDotDot);
QString thisPiece = part;
for( const QString &edir : std::as_const(entries) )
{
if( 0 == part.compare(edir, Qt::CaseInsensitive) )
{
thisPiece = edir;
break;
}
}
if( !sane.isEmpty() )
sane.append(QDir::separator() );
sane.append( thisPiece );
/*
bothparts.append( QDir::separator() );
bothparts.append( thisPiece );
*/
}
/*
bothparts.replace("//", "/");
qDebug() << "FuseAccessorProxy::resolvePath:" << path << "=>" << qpath << "=>" << bothparts;
return bothparts;
*/
//qDebug() << "FuseAccessorProxy::resolvePath:" << path << "=>" << sane;
return sane;
}
QString FuseAccessorProxy::fullPath(const QString &path)
{
QUrl upath("file://" + m_resource + path);
QString qpath = upath.toLocalFile();
return qpath;
}
QList<FuseProxyMapping> FuseAccessorProxy::proxyList(QSqlDatabase &database, int profileId)
{
QList< FuseProxyMapping > results;
//QString qstr = QString("SELECT moddir FROM mods WHERE enabled != 0 AND NOT moddir IS NULL AND modId IN (SELECT modId FROM profile_selections WHERE profileId=%1) ORDER BY idx DESC").arg(profileId);
QString qstr = QString("SELECT moddir, modId FROM mods WHERE NOT moddir IS NULL AND modId IN (SELECT modId FROM profile_selections WHERE profileId=%1) ORDER BY idx DESC").arg(profileId);
qDebug() << qstr;
QSqlQuery q = QSqlQuery(qstr, database);
while( q.next() )
{
QString basedir = q.value(0).toString();
int modId = q.value(1).toInt();
QString nqstr = QString("SELECT fileId, LOWER(CONCAT('/', dest)), CONCAT('/', source) FROM files WHERE modId=%1").arg(modId);
qDebug() << nqstr;
QSqlQuery nq = QSqlQuery(nqstr, database);
FuseProxyMapping m = {};
while( nq.next() )
{
m.modId = modId;
m.fileId = nq.value(0).toInt();
m.basedir = basedir;
m.mapped = nq.value(1).toString();
m.actual = nq.value(2).toString();
m.lmapped = m.mapped.toLower();
m.lparts = m.lmapped.split(QDir::separator());
results.append(m);
}
}
return results;
}
int FuseAccessorProxy::makeWritable(struct fuse_file_info *fi, const QString &path, QIODeviceBase::OpenMode mode, const QByteArray &data, off_t offset)
{
QString qpath = m_resource + QDir::separator() + resolvePath( m_resource, path );
FuseFHBase *fh = m_parent->makeWritableFromSource(qpath, path, path, mode);
if( !fh )
return -1;
fi->fh = reinterpret_cast<quint64>(fh);
return fh->write(data, offset, fi);
}
/***********/
int FuseAccessorProxy::getattr(const QString &path, struct stat *stbuf)
{
if( !m_entries.isEmpty() ) {
QString lpath = path.toLower();
QStringList lparts = lpath.split(QDir::separator());
for( const FuseProxyMapping &ent : std::as_const(m_entries) )
{
bool nomatch = false;
for( int x=1; !nomatch && x < lparts.length() && x < ent.lparts.length(); x++ ) {
if( lparts[x] != ent.lparts[x] )
nomatch = true;
}
if( nomatch )
continue;
if( lparts.length() < ent.lparts.length() )
{
qDebug() << "FuseAccessorProxy::getattr: Found dir" << lparts.join(QDir::separator()) << "in" << ent.lparts.join(QDir::separator());
memset( stbuf, 0, sizeof(struct stat) );
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
stbuf->st_uid = geteuid();
stbuf->st_gid = getegid();
time_t t = ::time(NULL);
stbuf->st_atim.tv_sec = t;
stbuf->st_ctim.tv_sec = t;
stbuf->st_mtim.tv_sec = t;
return 0;
}
QString qpath = ent.basedir + QDir::separator() + ent.actual;
qDebug() << "FuseAccessorProxy::getattr:" << qpath;
int ret = ::stat(qpath.toStdString().c_str(), stbuf);
return ret;
}
}
QString qpath = m_resource + QDir::separator() + resolvePath( m_resource, path );
int ret = ::stat(qpath.toStdString().c_str(), stbuf);
return ret;
}
QStringList FuseAccessorProxy::readdir(const QString &path)
{
QStringList results;
if( !m_entries.isEmpty() ) {
QString lpath = path.toLower();
for( const FuseProxyMapping &ent : std::as_const(m_entries) )
{
if( !ent.lmapped.startsWith(lpath) )
continue;
qDebug() << "readdir:" << lpath << ent.lmapped << ent.actual;
int trim = lpath.length();
QStringList sent = ent.mapped.mid(trim).split(QDir::separator());
// Eg: lpath="/data" & ent.mapped="/data/meshes/..." -> sent[0] = "", sent[1] = "meshes"
if( sent.at(0).isEmpty() )
sent.removeFirst();
//qDebug() << "sent:" << sent;
//results.append(m_entries[ent]);
if( sent.isEmpty() )
continue;
if( results.contains(sent[0]) )
continue;
qDebug() << "adding:" << sent[0];
results.append(sent[0]);
}
return results;
}
QString qpath = m_resource + QDir::separator() + resolvePath( m_resource, path );
QDir dir(qpath);
results << dir.entryList();
//return dir.entryList();
return results;
}
FuseFHBase *FuseAccessorProxy::open(const QString &path, QIODeviceBase::OpenMode mode)
{
QString qpath = m_resource + QDir::separator() + resolvePath( m_resource, path );
if( !m_entries.isEmpty() ) {
QString lpath = path.toLower();
QStringList lparts = lpath.split(QDir::separator());
for( const FuseProxyMapping &ent : std::as_const(m_entries) )
{
bool nomatch = false;
for( int x=1; !nomatch && x < lparts.length() && x < ent.lparts.length(); x++ ) {
if( lparts[x] != ent.lparts[x] )
nomatch = true;
}
if( nomatch )
continue;
if( lparts.length() < ent.lparts.length() )
{
qDebug() << "FuseAccessorArchive::open: Found dir" << lparts.join(QDir::separator()) << "in" << ent.lparts.join(QDir::separator());
return NULL;
}
qpath = ent.basedir + ent.actual;
qDebug() << "FuseAccessorArchive::open:" << qpath;
break;
}
}
QFileInfo fi( qpath );
if( !fi.exists() && QIODeviceBase::ReadOnly & mode ) {
//qDebug() << "FuseAccessorProxy::open:"<<qpath<<"doesn't exist";
return NULL;
}
else if( fi.exists() && !(QIODeviceBase::ReadOnly & mode) )
{
qDebug() << "FuseAccessorProxy::open: ** Make writable:" << qpath << "--==>" << path << "--" << m_resource;
return m_parent->makeWritableFromSource(qpath, path, path, mode);
}
FuseFHProxy *f = new FuseFHProxy(this, path, qpath);
if( !f->open(mode) )
{
qDebug() << "FuseAccessorProxy::open: Can't open" << qpath;
f->deleteLater();
return NULL;
}
//m_open_handles.insert( path, f );
return f;
}
FuseFHProxy::FuseFHProxy(FuseAccessorProxy *parent, const QString &path, const QString &newpath)
: FuseFHBase(parent, path)
{
//QString qpath = m_parent->fullPath(path);
m_file.setFileName(newpath);
}
FuseFHProxy::~FuseFHProxy()
{
FuseFHProxy::release();
}
bool FuseFHProxy::open(QIODeviceBase::OpenMode mode)
{
m_mode = mode;
return m_file.open(mode);
}
QByteArray FuseFHProxy::read(size_t size, off_t offset)
{
if( offset >= 0 && !m_file.seek(offset) )
return QByteArray();
return m_file.read(size);
}
off_t FuseFHProxy::lseek(off_t off, int whence)
{
Q_UNUSED(whence)
if( !m_file.seek(off) )
return -EINVAL;
return off;
}
int FuseFHProxy::write(const QByteArray &data, off_t offset, struct fuse_file_info *fi)
{
if( m_mode & QIODeviceBase::ReadOnly )
return -1;
//FuseArchiveEntry &entry = m_entries[lpath];
qDebug() << "FuseFHArchive::write: Upgrading" << m_path << "to writable...";
FuseAccessorProxy *accessor = qobject_cast<FuseAccessorProxy *>(m_parent);
int rval = accessor->makeWritable(fi, m_path, m_mode, data, offset);
release();
deleteLater();
return rval;
}
void FuseFHProxy::release()
{
if( m_file.isOpen() )
m_file.close();
}

67
FuseMounter/fuseproxy.h Normal file
View file

@ -0,0 +1,67 @@
#ifndef FUSEPROXY_H
#define FUSEPROXY_H
#include <QFile>
#include <QList>
#include <QObject>
#include <QSqlDatabase>
#include <QString>
#include "fusebase.h"
typedef struct {
int modId;
int fileId;
QString basedir;
QString actual;
QString mapped;
QString lmapped;
QStringList lparts;
} FuseProxyMapping;
class FuseAccessorProxy : public FuseAccessorBase
{
Q_OBJECT
QList< FuseProxyMapping > m_entries;
public:
explicit FuseAccessorProxy(FuseInterface *parent, const QString &resource, e_fh_type type=FH_PROXY);
explicit FuseAccessorProxy(FuseInterface *parent, QList< FuseProxyMapping > entries);
virtual ~FuseAccessorProxy();
int getattr(const QString &path, struct stat *stbuf) override;
QStringList readdir(const QString &path) override;
FuseFHBase *open(const QString &path, QIODeviceBase::OpenMode mode) override;
QString fullPath(const QString &path);
QString resolvePath(const QString &root, const QString &path, bool justPath=false);
static QList<FuseProxyMapping> proxyList(QSqlDatabase &database, int profileId);
// This expects the new entry to be created, then return "write" on the opened handle, otherwise return the appropriate "write" error code.
int makeWritable(struct fuse_file_info *fi, const QString &path, QIODeviceBase::OpenMode mode, const QByteArray &data, off_t offset);
};
class FuseFHProxy : public FuseFHBase
{
Q_OBJECT
public:
explicit FuseFHProxy(FuseAccessorProxy *parent, const QString &path, const QString &newpath);
virtual ~FuseFHProxy();
bool open(QIODeviceBase::OpenMode mode) override;
QByteArray read(size_t size, off_t offset) override;
off_t lseek(off_t off, int whence) override;
int write(const QByteArray &data, off_t offset, fuse_file_info *fi) override;
void release() override;
QFile m_file;
};
#endif // FUSEPROXY_H

201
FuseMounter/fusesandbox.cpp Normal file
View file

@ -0,0 +1,201 @@
#include <QDir>
#include <QFileInfo>
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
#include "fuseinterface.h"
#include "fusesandbox.h"
FuseAccessorSandbox::FuseAccessorSandbox(FuseInterface *parent, const QString &resource)
: FuseAccessorProxy(parent, resource, FuseAccessorBase::FH_SANDBOX)
{
//m_resource = m_resource + QDir::separator() + QString::number(parent->getProfileId());
}
FuseAccessorSandbox::~FuseAccessorSandbox()
{
}
FuseFHBase *FuseAccessorSandbox::open(const QString &path, QIODeviceBase::OpenMode mode)
{
QString qpath = m_resource + QDir::separator() + resolvePath( m_resource, path );
QFileInfo fi(qpath);
if( !fi.exists() ) {
if( mode & QIODeviceBase::ReadOnly ) {
//qDebug() << "FuseAccessorSandbox::open:" << path << mode << "- Nah. You get null.";
return NULL;
}
QStringList parts = qpath.split( QDir::separator() );
QString dname = parts.takeLast();
if( dname.isEmpty() )
dname = parts.takeLast();
QDir dir( m_resource );
dir.mkpath( parts.join(QDir::separator()) );
}
FuseFHSandbox *f = new FuseFHSandbox(this, path, qpath);
if( !f->open(mode) )
{
qDebug() << "FuseAccessorSandbox::open:" << path << mode << "- Open failed. You get null:" << qpath;
f->deleteLater();
return NULL;
}
//m_open_handles.insert( path, f );
return f;
}
int FuseAccessorSandbox::truncate(const QString &path, off_t offset)
{
QString qpath = m_resource + QDir::separator() + resolvePath( m_resource, path );
qDebug() << "FuseAccessorSandbox::truncate:" << path << "@" << offset;
return QFile::resize(qpath, offset) ? 0 : 1;
}
/*
int FuseAccessorSandbox::rename(const QString &path, const QString &newname)
{
return 1;
}
*/
int FuseAccessorSandbox::rmdir(const QString &path)
{
QString qpath = m_resource + QDir::separator() + resolvePath( m_resource, path );
QStringList parts = qpath.split( QDir::separator(), Qt::SkipEmptyParts );
QString dname = parts.takeLast();
if( dname.isEmpty() )
dname = parts.takeLast();
QString parentDir = QDir::separator() + parts.join(QDir::separator());
QDir dir( parentDir );
//qDebug() << "rmdir:" << dname << "from" << parentDir;
return dir.rmdir(dname) ? 0 : 1;
}
int FuseAccessorSandbox::unlink(const QString &path)
{
QString qpath = m_resource + QDir::separator() + resolvePath( m_resource, path );
QStringList parts = qpath.split( QDir::separator() );
QString fname = parts.takeLast();
if( fname.isEmpty() )
fname = parts.takeLast();
QDir dir( parts.join(QDir::separator()) );
qDebug() << "FuseAccessorSandbox::unlink:" << path;
return dir.remove(fname) ? 0 : 1;
}
int FuseAccessorSandbox::mkdir(const QString &path, QFileDevice::Permissions mode)
{
Q_UNUSED(mode)
QString qpath = resolvePath(m_resource, path);
QDir dir( m_resource );
qDebug() << "FuseAccessorSandbox::mkdir:" << path << mode << "=>" << m_resource << qpath;
if( !dir.mkpath(qpath) )
return 1;
return 0;
}
int FuseAccessorSandbox::create(const QString &path, QFileDevice::Permissions mode)
{
Q_UNUSED(mode)
QString qpath = m_resource + QDir::separator() + resolvePath( m_resource, path );
QStringList parts = qpath.split( QDir::separator() );
QString dname = parts.takeLast();
if( dname.isEmpty() )
dname = parts.takeLast();
QDir dir( m_resource );
dir.mkpath( parts.join(QDir::separator()) );
mode_t smode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO;
//smode += m_parent->utilQPermsToMode(mode);
// TODO: Maybe just open it right here?
qDebug() << "FuseAccessorSandbox::create:" << path << mode;
int fd = ::creat(qpath.toStdString().c_str(), smode);
if( fd > 0 )
close(fd);
return fd > 0 ? 0 : 1;
}
FuseFHBase *FuseAccessorSandbox::makeWritableFromSource(const QString &source, const QString &origpath, const QString &dest, QIODeviceBase::OpenMode mode)
{
QString qdest = m_resource + QDir::separator() + resolvePath(m_resource, dest);
QFileInfo qsf(source);
if( !qsf.exists() ) {
qWarning() << "FuseAccessorSandbox::makeWritableFromSource: Couldn't access file at" << source;
return NULL;
}
QStringList parts = dest.toLower().split(QDir::separator(), Qt::SkipEmptyParts);
QString dfname = parts.takeLast();
QDir ddir(m_resource);
ddir.mkpath( parts.join(QDir::separator()) );
QFile::copy(source, qdest);
qDebug() << "FuseAccessorSandbox::makeWritableFromSource: cp" << source << qdest;
FuseFHSandbox *ent = new FuseFHSandbox(this, origpath, qdest);
if( !ent->open(mode) )
{
ent->deleteLater();
return NULL;
}
return ent;
}
FuseFHBase *FuseAccessorSandbox::makeWritableFromData(const QByteArray &source, const QString &origpath, const QString &dest, QIODeviceBase::OpenMode mode)
{
QString qdest = m_resource + QDir::separator() + resolvePath(m_resource, dest);
QStringList parts = dest.toLower().split(QDir::separator(), Qt::SkipEmptyParts);
QString dfname = parts.takeLast();
QDir ddir(m_resource);
ddir.mkpath( parts.join(QDir::separator()) );
qDebug() << "FuseAccessorSandbox::makeWritableFromData:" << qdest << mode;
QFile f(qdest);
if( !f.open(QIODeviceBase::WriteOnly) ) {
qDebug() << "FuseAccessorSandbox::makeWritableFromData: open failed.";
return NULL;
}
f.write(source);
f.close();
FuseFHSandbox *ent = new FuseFHSandbox(this, origpath, qdest);
if( !ent->open(mode) )
{
ent->deleteLater();
return NULL;
}
return ent;
}
FuseFHSandbox::FuseFHSandbox(FuseAccessorSandbox *parent, const QString &path, const QString &newpath)
: FuseFHProxy(parent, path, newpath)
{
}
FuseFHSandbox::~FuseFHSandbox()
{
}
int FuseFHSandbox::write(const QByteArray &data, off_t offset, fuse_file_info *fi)
{
Q_UNUSED(fi)
m_file.seek(offset);
return m_file.write(data);
}

43
FuseMounter/fusesandbox.h Normal file
View file

@ -0,0 +1,43 @@
#ifndef FUSESANDBOX_H
#define FUSESANDBOX_H
#include <QObject>
#include <QFileDevice>
#include "fusebase.h"
#include "fuseproxy.h"
class FuseAccessorSandbox : public FuseAccessorProxy
{
Q_OBJECT
public:
explicit FuseAccessorSandbox(FuseInterface *parent, const QString &resource);
virtual ~FuseAccessorSandbox();
FuseFHBase *open(const QString &path, QIODeviceBase::OpenMode mode) override;
int truncate(const QString &path, off_t offset) override;
//int rename(const QString &path, const QString &newname) override;
int rmdir(const QString &path) override;
int unlink(const QString &path) override;
int mkdir(const QString &path, QFileDevice::Permissions mode) override;
int create(const QString &path, QFileDevice::Permissions mode) override;
// Help our other friends out~
FuseFHBase *makeWritableFromSource(const QString &source, const QString &origpath, const QString &dest, QIODeviceBase::OpenMode mode);
FuseFHBase *makeWritableFromData(const QByteArray &source, const QString &origpath, const QString &dest, QIODeviceBase::OpenMode mode);
};
class FuseFHSandbox : public FuseFHProxy
{
Q_OBJECT
public:
explicit FuseFHSandbox(FuseAccessorSandbox *parent, const QString &path, const QString &newpath);
virtual ~FuseFHSandbox();
int write(const QByteArray &data, off_t offset=0, struct fuse_file_info *fi=nullptr) override;
};
#endif // FUSESANDBOX_H

View file

@ -260,6 +260,9 @@ static int qmfuse_truncate(const char *path, off_t offset, struct fuse_file_info
return ret;
}
#ifndef FUSE_FILL_DIR_DEFAULTS
# define FUSE_FILL_DIR_DEFAULTS (enum fuse_fill_dir_flags)0
#endif
static int qmfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags)
@ -273,7 +276,7 @@ static int qmfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
{
QStringList ents = g_dircache[qpath];
for( const QString &e : ents )
filler(buf, e.toStdString().c_str(), NULL, 0, FUSE_FILL_DIR_PLUS);
filler(buf, e.toStdString().c_str(), NULL, 0, FUSE_FILL_DIR_DEFAULTS);
return 0;
}
@ -288,7 +291,7 @@ static int qmfuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
QString nent = parts.last();
//dolog("readdir(3): proxy: %s\n", nent.toStdString().c_str());
filesshared.append(nent);
filler(buf, nent.toStdString().c_str(), NULL, 0, FUSE_FILL_DIR_PLUS);
filler(buf, nent.toStdString().c_str(), NULL, 0, FUSE_FILL_DIR_DEFAULTS);
}
/* Now the mods: */
@ -402,6 +405,16 @@ static int qmfuse_open(const char *path, struct fuse_file_info *fi)
{
// Make a proxy override...
dolog("open(2): trying to open for write: %s\n", qpath.toStdString().c_str());
/* FIXME: We need to make the "created" a format override, check that first, then arcs, extracted, then vanilla.
* !!!!!!!!!!!!!
*
QByteArray *src = archive->getEntry(mod->m_archivePath);
QFile f(qpath);
f.open(QIODeviceBase::WriteOnly);
f.write(*src);
f.close();
*/
int fd = g_proxy->open(qpath, true);
if( fd <= 0 ) {
dolog("open(2): no dice, for some reason.\n");

View file

@ -1,5 +1,5 @@
# quickmod
Basic mod manager for Fallout 4, Fallout 4 VR, Skyrim SE/AE, Skyrim VR, Fallout New Vegas, and Cyberpunk 2077 on **Linux**
Basic mod manager for Fallout 4, Fallout 4 VR, Skyrim SE/AE, Skyrim VR, Fallout New Vegas, and Cyberpunk 2077 on **Linux** (and maybe BSDs)
**This app is ragged-edge at the moment, and this project is published because it finally works at all.**

View file

@ -5,7 +5,7 @@
<hr></hr>
</center>
<p>
A basic mod manager for Steam + Proton on Linux (and I guess BSD).
A basic mod manager for Steam + Proton on Linux (and I guess BSD maybe).
</p><p>
Currently supported games:
<ul>
@ -14,5 +14,6 @@ Currently supported games:
<li>Fallout 4</li>
<li>Fallout 4 VR</li>
<li>Fallout: New Vegas</li>
<li>Cyberpunk 2077</li>
</ul>
</p>

View file

@ -21,5 +21,7 @@
<file>qml/game.js</file>
<file>qml/mods.js</file>
<file>qml/GameSettings.qml</file>
<file>qml/VFSStats.qml</file>
<file>qml/TableViewWithHeader.qml</file>
</qresource>
</RCC>

128
qml/TableViewWithHeader.qml Normal file
View file

@ -0,0 +1,128 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Controls.Material
ScrollView {
id: root
clip: true
// these are bound by the parent page
property variant model
property variant proxy
property int sortColumn: -1
property bool sortAscending: true
ColumnLayout {
anchors.fill: parent
spacing: 0
// ---------- HEADER ----------
Rectangle {
Layout.fillWidth: true
height: 28
color: Qt.lighter(Material.background, 1.05)
border.color: "#ccc"
RowLayout {
anchors.fill: parent
spacing: 1
Repeater {
model: root.model ? root.model.columns : []
delegate: Rectangle {
color: "transparent"
border.color: "#ccc"
Layout.fillHeight: true
Layout.preferredWidth: fontMetrics.boundingRect(modelData.title).width + 60
Row {
anchors.centerIn: parent
spacing: 4
Label { text: modelData.title; font.bold: true }
Label {
text: (root.sortColumn === index)
? (root.sortAscending ? "▲" : "▼")
: ""
font.pixelSize: 10
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.sortColumn === index)
root.sortAscending = !root.sortAscending
else
root.sortAscending = true
root.sortColumn = index
if (root.proxy)
root.proxy.sort(index, root.sortAscending
? Qt.AscendingOrder
: Qt.DescendingOrder)
}
}
}
}
}
}
// ---------- TABLE ----------
TableView {
id: table
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
columnSpacing: 1
rowSpacing: 1
reuseItems: true
boundsBehavior: Flickable.StopAtBounds
model: root.proxy
delegate: Rectangle {
implicitHeight: 26
color: styleData.selected
? Material.color(Material.Blue, Material.Shade100)
: "transparent"
Label {
text: modelData
anchors.left: parent.left
anchors.leftMargin: 8
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
width: parent.width - 8
}
}
columnWidthProvider: function(col) {
const columns = root.model ? root.model.columns : []
if (!columns.length) return 100
const headerText = columns[col].title
const headerWidth = fontMetrics.boundingRect(headerText).width + 20
let maxCellWidth = headerWidth
const rowsToCheck = Math.min(100, table.rows)
for (let i = 0; i < rowsToCheck; ++i) {
const value = table.proxy.data(table.model.index(i, col))
if (value)
maxCellWidth = Math.max(
maxCellWidth,
fontMetrics.boundingRect(value.toString()).width + 20
)
}
return maxCellWidth
}
Component.onCompleted: {
const cols = root.model ? root.model.columns : []
for (let i = 0; i < cols.length; ++i)
table.addColumn({ role: "display" }) // DisplayRole matches modelData
}
FontMetrics { id: fontMetrics }
}
}
}

51
qml/VFSStats.qml Normal file
View file

@ -0,0 +1,51 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Layouts
ColumnLayout {
anchors.fill: parent
spacing: 8
// --- Filter bar ---
RowLayout {
Layout.fillWidth: true
//Layout.minimumHeight: childrenRect.height
spacing: 8
Label {
text: qsTr("Filter:")
verticalAlignment: Text.AlignVCenter
}
TextField {
id: searchBox
placeholderText: qsTr("Type to filter...")
Layout.fillWidth: true
onTextChanged: VFSProxy.filterRegularExpression = Qt.regExp(text, "i");
}
CheckBox {
id: cbOnlyActive
text: qsTr('Show Only Open')
onToggled: VFSProxy.onlyActive = checked
}
}
// --- Table section ---
TableViewWithHeader {
id: vfsTable
Layout.fillWidth: true
Layout.fillHeight: true
model: VFSModel
proxy: VFSProxy
onSortColumnChanged: resort();
onSortAscendingChanged: resort();
function resort() {
VFSProxy.sort(sortColumn, sortAscending ? Qt.AscendingOrder : Qt.DescendingOrder);
}
}
}

View file

@ -68,6 +68,50 @@ ApplicationWindow {
text: qsTr("&Quit");
onTriggered: Qt.quit();
}
Action {
id: testExtract
text: qsTr("Test Extract...");
property var arc
function extractProgress(pos, total, src, dst) {
console.log(`Progress: [${src} => ${dst}] ${pos}/${total}`);
}
function extractComplete(success, results) {
console.log(`Done (${success}). Results:`);
console.log(JSON.stringify(results, null, 2));
}
function handleList(success, results) {
console.log(`Parsed the zip (${success}):`);
console.log(JSON.stringify(results, null, 2));
if( true !== success )
return;
let matrix = {
'Pictures/generated/overwatch/ComfyUI_temp_iqmfq_00158_.png': '/tmp/overwatch1.png',
'Pictures/generated/space/ComfyUI_temp_iqmfq_00145_.png': '/tmp/space1.png',
'Pictures/generated/fallout/ComfyUI_temp_ooois_00004_.png': '/tmp/fallout1.png'
};
let result = arc.extract(matrix, function(ret, rresults) {
testExtract.extractComplete(ret, rresults);
}, function(pos, total, src, dst) {
testExtract.extractProgress(pos, total, src, dst);
} );
}
onTriggered: {
arc = File.archive("/tmp/blorp.zip");
arc.list( function(lres, lresults) {
console.log("buh.");
testExtract.handleList(lres, lresults);
} );
//testExtract.handleList(true, []);
}
}
}
Menu {
@ -137,6 +181,9 @@ ApplicationWindow {
text: qsTr('Plugins')
enabled: currentGameEntry && currentGameEntry['plugins']
}
TabButton {
text: qsTr('VFS')
}
}
ComboBox {
id: profilesMenu
@ -157,16 +204,20 @@ ApplicationWindow {
id: cbVFS
text: qsTr('VFS')
icon.name: 'drive-harddisk'
property var mountObj
onToggled: {
if( !checked )
FUSEManager.unmount();
else
{
if( !checked ) {
mountObj.unmount();
Qt.callLater(function() {
mountObj = null;
});
} else {
let sobj = gameSettings.objFor(currentGame);
if( (''+currentGame).length > 0 && (''+sobj.vfsPath).length > 0 && sobj.profileId > 0 )
{
console.log(`Trying to mount: [${currentGame}] [${sobj.vfsPath}] [${sobj.profileId}]`);
FUSEManager.mount(db.conn, currentGame, sobj.profileId);
mountObj = FUSEManager.create(db.conn, currentGame, sobj.profileId);
mountObj.mount();
}
}
}
@ -223,9 +274,15 @@ ApplicationWindow {
Plugins.writeLoadOrder(plugins);
}
}
VFSStats {
id: vfsStats
}
/*
LaunchPage {
id: launchPage
}
*/
}
Settings {

View file

@ -4,10 +4,19 @@ QT += core quick widgets sql dbus qml
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
QMAKE_LDFLAGS += -fPIE
#QMAKE_CXX = clang++
#QMAKE_LINK = clang++
QMAKE_LDFLAGS += -fPIE -g
#QMAKE_CXXFLAGS_WARN_ON += -Wshadow
LIBS += -larchive
debug {
#CONFIG+=sanitizer sanitize_address sanitize_undefined
#CONFIG+=sanitize_thread sanitize_memory
}
SOURCES += \
FuseMounter/fuseinterface.cpp \
src/file.cpp \
src/http.cpp \
src/main.cpp \
@ -17,10 +26,10 @@ SOURCES += \
src/process.cpp \
src/utils.cpp \
src/sqldatabase.cpp \
src/sqldatabasemodel.cpp
src/sqldatabasemodel.cpp \
src/vfstablemodel.cpp
HEADERS += \
FuseMounter/fuseinterface.h \
src/file.h \
src/fomodreader.h \
src/http.h \
@ -29,11 +38,12 @@ HEADERS += \
src/process.h \
src/utils.h \
src/sqldatabase.h \
src/sqldatabasemodel.h
src/sqldatabasemodel.h \
src/vfstablemodel.h
RESOURCES += qml.qrc
LIBS += -larchive
# LIBS += -larchive
# /**
# * For FUSE-based mod archive VFS:
@ -42,8 +52,8 @@ DEFINES += WITH_FUSE
CONFIG += c++17 cmdline link_pkgconfig
PKGCONFIG += fuse3 libunarr
QMAKE_CXXFLAGS += -D_FILE_OFFSET_BITS=64 -O3
#QMAKE_CXXFLAGS += -D_FILE_OFFSET_BITS=64 -g -O0
#QMAKE_CXXFLAGS += -D_FILE_OFFSET_BITS=64 -O3
QMAKE_CXXFLAGS += -D_FILE_OFFSET_BITS=64 -g -O0
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
@ -51,11 +61,16 @@ QMAKE_CXXFLAGS += -D_FILE_OFFSET_BITS=64 -O3
SOURCES += \
src/fusemanager.cpp \
FuseMounter/archivemanager.cpp \
FuseMounter/database.cpp \
FuseMounter/fsproxy.cpp \
FuseMounter/fusestuff.cpp \
FuseMounter/modarchive.cpp
FuseMounter/fusebase.cpp \
FuseMounter/fuseinterface.cpp \
FuseMounter/fusearchive.cpp \
FuseMounter/fuseproxy.cpp \
FuseMounter/fusesandbox.cpp
#FuseMounter/archivemanager.cpp \
#FuseMounter/database.cpp \
#FuseMounter/fsproxy.cpp \
#FuseMounter/fusestuff.cpp \
#FuseMounter/modarchive.cpp
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
@ -64,11 +79,16 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
HEADERS += \
src/fusemanager.h \
FuseMounter/archivemanager.h \
FuseMounter/database.h \
FuseMounter/fsproxy.h \
FuseMounter/fusestuff.h \
FuseMounter/modarchive.h
FuseMounter/fusebase.h \
FuseMounter/fuseinterface.h \
FuseMounter/fusearchive.h \
FuseMounter/fuseproxy.h \
FuseMounter/fusesandbox.h
# FuseMounter/archivemanager.h \
# FuseMounter/database.h \
# FuseMounter/fsproxy.h \
# FuseMounter/fusestuff.h \
# FuseMounter/modarchive.h
# /*************************/
# /* End of FUSE-based VFS */

View file

@ -101,10 +101,10 @@ static int arc_cb_close(struct archive *archive, void *userdata)
ArchiveWorker::ArchiveWorker(ArchiveController *parent, File *file, const QString &archivePath)
: QThread(parent),
m_controller{parent},
m_file{file},
m_archivePath{archivePath},
m_stopRequested{false}
m_controller{parent},
m_file{file},
m_archivePath{archivePath},
m_stopRequested{false}
{
m_fd = new QFile(m_archivePath);
if( !m_fd->open(QIODevice::ReadOnly) )
@ -181,7 +181,7 @@ void ArchiveListWorker::run()
ArchiveExtractToMemoryWorker::ArchiveExtractToMemoryWorker(ArchiveController *parent, File *file, const QString &archivePath, const QStringList &targets)
: ArchiveWorker(parent, file, archivePath),
m_targets{targets}
m_targets{targets}
{
}
@ -255,7 +255,7 @@ void ArchiveExtractToMemoryWorker::run()
ArchiveExtractToFileWorker::ArchiveExtractToFileWorker(ArchiveController *parent, File *file, const QString &archivePath, const QVariantMap &matrix)
: ArchiveWorker(parent, file, archivePath),
m_matrix{matrix}
m_matrix{matrix}
{
}
@ -285,7 +285,7 @@ void ArchiveExtractToFileWorker::run()
lowKeys << iter.key().toLower();
}
/*
/*
for( QString &target : m_matrix.keys() )
{
keys << target;
@ -351,8 +351,8 @@ void ArchiveExtractToFileWorker::run()
ArchiveController::ArchiveController(const QString &archivePath, File *file)
: QObject(file),
m_file{file},
m_archivePath{archivePath}
m_file{file},
m_archivePath{archivePath}
{
}
@ -472,7 +472,7 @@ void ArchiveController::workerProgress(qreal processed, qreal total, const QStri
File::File(QQmlApplicationEngine *engine)
: m_engine{engine},
m_simulate{false}
m_simulate{false}
{
}
@ -512,7 +512,7 @@ QVariant File::stat(const QString &path)
res["owner"] = info.owner();
res["ownerId"] = info.ownerId();
res["path"] = info.path();
/*
/*
QFile::Permissions perms = info.permissions();
QVariantMap permMap;
if( perms & QFileDevice::ReadOwner )

View file

@ -126,7 +126,7 @@ public:
Q_INVOKABLE bool mkdir(const QString &path, bool createParents=false);
Q_INVOKABLE bool mkfilepath(const QString &path);
Q_INVOKABLE QVariant dirContents(const QString &path);
/*
/*
Q_INVOKABLE QVariant archiveList(const QString &archivePath);
Q_INVOKABLE QByteArray extract(const QString &archivePath, const QString &filePath);
Q_INVOKABLE bool extractSourceDest(const QString &archivePath, const QString &srcFile, const QString &destFile);

View file

@ -1,15 +1,18 @@
#include "fusemanager.h"
#include <stdarg.h>
#include <stdio.h>
//#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mount.h>
/*
#include "../FuseMounter/archivemanager.h"
#include "../FuseMounter/database.h"
#include "../FuseMounter/fusestuff.h"
#include "../FuseMounter/fsproxy.h"
*/
#include "../FuseMounter/fuseinterface.h"
#include <QSettings>
#include <QSocketNotifier>
@ -20,102 +23,31 @@
#include "src/sqldatabase.h"
FUSEManager::FUSEManager(QObject *parent)
: QObject{parent},
m_mounted{false},
m_fuse_fd{-1},
m_fuse_notifier{nullptr},
m_db{nullptr},
m_proxy{nullptr},
m_manager{nullptr}
{
: QObject{parent}{
}
FUSEManager::~FUSEManager()
{
unmount();
if( m_db )
delete m_db;
if( m_proxy )
delete m_proxy;
if( m_manager )
delete m_manager;
for( FuseInterface *iface : m_interfaces )
iface->deleteLater();
}
bool FUSEManager::mount(const QVariant &dbconn, const QString &gameName, int profileId)
FuseInterface *FUSEManager::create(const QVariant &dbconn, const QString &gameName, int profileId)
{
QSettings s;
s.beginGroup(gameName);
QString dbpath = s.value("dbPath").toString();
//QString dbpath = s.value("dbPath").toString();
QString gamedir = s.value("gamePath").toString();
QString modsdir = s.value("modsPath").toString();
//QString modsdir = s.value("modsPath").toString();
QString destPath = s.value("vfsPath").toString();
QString createPath = s.value("vfsCreatedPath").toString();
m_currentGameName = gameName;
m_targetDir = destPath;
m_profileId = profileId;
SqlDatabaseConnection *db = dbconn.value<SqlDatabaseConnection *>();
m_db = new Database(this);
m_db->setDatabase(db->m_db);
FuseInterface *iface = new FuseInterface(db->m_db, profileId, gamedir, createPath, destPath, this);
m_interfaces.append(iface);
connect( iface, &QObject::destroyed, this, [this, iface](QObject *obj){ Q_UNUSED(obj) m_interfaces.removeOne(iface); } );
connect( iface, &FuseInterface::entryUpdated, this, &FUSEManager::entryUpdated );
if( !m_proxy )
m_proxy = new FSProxy(this);
m_proxy->clear();
m_proxy->setup(m_db, m_profileId, m_currentGameName, gamedir, createPath);
if( m_manager ) {
m_manager->deleteLater();
m_manager = NULL;
}
m_manager = new ArchiveManager(m_db, modsdir, m_profileId, this);
qmfuse_set_archive(m_manager);
qmfuse_set_proxy(m_proxy);
qmfuse_set_database(m_db);
m_fuse_fd = qmfuse_main(m_targetDir.toStdString().c_str());
if( m_fuse_notifier )
m_fuse_notifier->deleteLater();
m_fuse_notifier = new QSocketNotifier(m_fuse_fd, QSocketNotifier::Read, this);
QObject::connect( m_fuse_notifier, &QSocketNotifier::activated, this, &FUSEManager::handle_fuse_event );
m_mounted = true;
return true;
}
void FUSEManager::unmount()
{
qmfuse_unmount();
::umount2( m_targetDir.toStdString().c_str(), MNT_DETACH );
m_mounted = false;
if( m_fuse_notifier ) {
m_fuse_notifier->setEnabled(false);
m_fuse_notifier->deleteLater();
m_fuse_notifier = NULL;
}
if( m_fuse_fd > 0 )
::close(m_fuse_fd);
::umount2( m_targetDir.toStdString().c_str(), 0 );
}
void FUSEManager::handle_fuse_event()
{
int ret;
do {
ret = qmfuse_pump();
} while(ret > 0);
if( ret < 0 ) {
unmount();
}
return iface;
}

View file

@ -3,39 +3,24 @@
#include <QJSValue>
#include <QObject>
#include <QSocketNotifier>
class ArchiveManager;
class Database;
class FSProxy;
#include "FuseMounter/fuseinterface.h"
class SqlDatabaseConnection;
class FUSEManager : public QObject
{
Q_OBJECT
QString m_currentGameName;
QString m_targetDir;
int m_profileId;
bool m_mounted;
int m_fuse_fd;
QSocketNotifier *m_fuse_notifier;
Database *m_db;
FSProxy *m_proxy;
ArchiveManager *m_manager;
QList< FuseInterface * > m_interfaces;
public:
explicit FUSEManager(QObject *parent = nullptr);
~FUSEManager();
Q_INVOKABLE bool mount(const QVariant &dbhandle, const QString &gameName, int profileId);
Q_INVOKABLE void unmount();
private slots:
void handle_fuse_event();
Q_INVOKABLE FuseInterface *create(const QVariant &dbhandle, const QString &gameName, int profileId);
signals:
void entryUpdated(const QString &path, const QString &facility, quint32 count, quint8 refcount);
};
#endif // FUSEMANAGER_H

View file

@ -18,7 +18,9 @@
#include "sqldatabasemodel.h"
#include "utils.h"
#ifdef WITH_FUSE
# include <QSortFilterProxyModel>
# include "fusemanager.h"
# include "vfstablemodel.h"
#endif
#define SERVICE_NAME "org.oneill.Quickmod"
@ -104,6 +106,33 @@ int main(int argc, char *argv[])
#ifdef WITH_FUSE
FUSEManager *fuse = new FUSEManager();
engine.rootContext()->setContextProperty("FUSEManager", fuse);
VFSTableModel *vfsModel = new VFSTableModel();
QSortFilterProxyModel *vfsProxy = new QSortFilterProxyModel();
vfsProxy->setSourceModel(vfsModel);
vfsProxy->setDynamicSortFilter(true);
vfsProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
vfsProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
vfsProxy->setFilterKeyColumn(-1);
//vfsProxy->setFilterRole(Qt::DisplayRole);
QObject::connect( fuse, &FUSEManager::entryUpdated, vfsModel, &VFSTableModel::upsertEntry );
engine.rootContext()->setContextProperty("VFSModel", vfsModel);
engine.rootContext()->setContextProperty("VFSProxy", vfsProxy);
QObject::connect(fuse, &FUSEManager::entryUpdated,
[vfsModel, vfsProxy](const QString &path, const QString &facility, int count, int refcount)
{
qDebug().noquote() << "entryUpdated:" << path
<< facility << count << refcount
<< "rows(model)=" << vfsModel->rowCount()
<< "rows(proxy)=" << vfsProxy->rowCount();
qDebug() << "vfsModel:" << vfsModel->roleNames();
qDebug() << "vfsProxy:" << vfsProxy->roleNames();
});
QObject::connect(&engine, &QQmlEngine::destroyed, vfsModel, &QObject::deleteLater);
QObject::connect(&engine, &QQmlEngine::destroyed, vfsProxy, &QObject::deleteLater);
#endif
qmlRegisterUncreatableType<ArchiveExtractToFileWorker>("org.ONeill.ArchiveExtractToFileWorker", 1, 0, "ArchiveExtractToFileWorker", "Instantiated with Archive.extract(matrix, funcFinished)");
@ -121,7 +150,7 @@ int main(int argc, char *argv[])
if (!QDBusConnection::sessionBus().registerService(SERVICE_NAME)) {
fprintf(stderr, "%s\n",
qPrintable(QDBusConnection::sessionBus().lastError().message()));
exit(1);
//exit(1);
}
QDBusConnection::sessionBus().registerObject("/", nxmHandler, QDBusConnection::ExportAllSlots);

View file

@ -17,7 +17,8 @@ ProcessObject *Process::launch(const QString &path, const QStringList &args, con
ProcessObject *p = new ProcessObject(this);
QProcessEnvironment penv = QProcessEnvironment::systemEnvironment();
for( QString k : env.keys() )
QStringList keys = env.keys();
for( const QString &k : keys )
penv.insert(k, env[k].toString());
p->setProcessEnvironment(penv);

View file

@ -64,7 +64,7 @@ try_sql_query:
generateRoleNames();
QSqlQueryModel::setQuery(m_query);
QSqlQueryModel::setQuery(std::move(m_query));
if( lastError().isValid() )
{
qWarning() << "SqlDatabaseModel::setSqlQuery: setQuery Error:" << lastError();
@ -100,7 +100,7 @@ QVariant SqlDatabaseModel::get( int rowIdx, int columnIdx )
void SqlDatabaseModel::select()
{
m_query.exec();
QSqlQueryModel::setQuery(m_query);
QSqlQueryModel::setQuery(std::move(m_query));
emit layoutChanged();
}

111
src/vfstablemodel.cpp Normal file
View file

@ -0,0 +1,111 @@
#include "vfstablemodel.h"
VFSTableModel::VFSTableModel(QObject *parent)
: QAbstractTableModel(parent)
{
// column definitions (DisplayRole = header, UserRole = data role)
m_columns = {
QMap<int, QVariant>{ {Qt::DisplayRole, "Path"}, {Qt::UserRole, "path"} },
QMap<int, QVariant>{ {Qt::DisplayRole, "Facility"}, {Qt::UserRole, "facility"} },
QMap<int, QVariant>{ {Qt::DisplayRole, "Count"}, {Qt::UserRole, "count"} },
QMap<int, QVariant>{ {Qt::DisplayRole, "RefCount"}, {Qt::UserRole, "refcount"} }
};
// build roleNames map
int base = Qt::UserRole;
for (int i = 0; i < m_columns.size(); ++i) {
const QByteArray role = m_columns[i].value(Qt::UserRole).toByteArray();
m_roleNames[base + i] = role;
}
emit columnsChanged();
}
int VFSTableModel::rowCount(const QModelIndex &) const
{
return m_rows.size();
}
int VFSTableModel::columnCount(const QModelIndex &) const
{
return m_columns.size();
}
QVariant VFSTableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() ||
index.row() >= m_rows.size() ||
index.column() >= m_columns.size())
return {};
const VFSEntry &e = m_rows[index.row()];
if( Qt::DisplayRole != role )
return {};
// Allow both DisplayRole and specific UserRole access
switch( index.column() )
{
case 0:
return e.path;
case 1:
return e.facility;
case 2:
return e.count;
case 3:
return e.refcount;
}
return {};
}
QVariant VFSTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal &&
section >= 0 && section < m_columns.size())
return m_columns[section].value(role);
return {};
}
QHash<int, QByteArray> VFSTableModel::roleNames() const
{
return m_roleNames;
}
QVariantList VFSTableModel::columns()
{
QVariantList out;
for (const auto &col : m_columns) {
QVariantMap map;
map.insert("title", col.value(Qt::DisplayRole).toString());
map.insert("role", col.value(Qt::UserRole).toString());
out.append(map);
}
return out;
}
void VFSTableModel::upsertEntry(const QString &path,
const QString &facility,
int count,
int refcount)
{
auto it = m_index.find(path);
if (it == m_index.end()) {
const int row = m_rows.size();
beginInsertRows({}, row, row);
m_rows.append({path, facility, count, refcount});
m_index[path] = row;
endInsertRows();
} else {
const int row = it.value();
m_rows[row] = {path, facility, count, refcount};
emit dataChanged(index(row, 0), index(row, columnCount() - 1));
}
}
void VFSTableModel::clear()
{
beginResetModel();
m_rows.clear();
m_index.clear();
endResetModel();
}

84
src/vfstablemodel.h Normal file
View file

@ -0,0 +1,84 @@
#ifndef VFSTABLEMODEL_H
#define VFSTABLEMODEL_H
#include <QAbstractTableModel>
#include <QSortFilterProxyModel>
#include <QHash>
#include <QMap>
#include <QVariant>
#include <QVector>
#include <QString>
struct VFSEntry {
QString path;
QString facility;
int count = 0;
int refcount = 0;
};
class VFSTableModel : public QAbstractTableModel
{
Q_OBJECT
Q_PROPERTY(QVariantList columns READ columns NOTIFY columnsChanged)
public:
explicit VFSTableModel(QObject *parent = nullptr);
// core model interface
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QHash<int, QByteArray> roleNames() const override;
// QML integration
QVariantList columns();
public slots:
void upsertEntry(const QString &path, const QString &facility, int count, int refcount);
void clear();
signals:
void columnsChanged();
private:
QVector<VFSEntry> m_rows;
QHash<QString, int> m_index; // path → row index
QVector<QMap<int, QVariant>> m_columns;
QHash<int, QByteArray> m_roleNames;
};
class VFSSortProxy : public QSortFilterProxyModel {
Q_OBJECT
Q_PROPERTY(bool onlyActive READ onlyActive WRITE setOnlyActive NOTIFY onlyActiveChanged)
public:
using QSortFilterProxyModel::QSortFilterProxyModel;
bool onlyActive() const { return m_onlyActive; }
void setOnlyActive(bool v) {
if (m_onlyActive == v)
return;
m_onlyActive = v;
invalidateFilter();
emit onlyActiveChanged();
}
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &srcParent) const override {
if (!m_onlyActive)
return true;
const QModelIndex idx = sourceModel()->index(sourceRow, 3, srcParent); // refcount column
int refcount = sourceModel()->data(idx, Qt::DisplayRole).toInt();
return refcount > 0;
}
signals:
void onlyActiveChanged();
private:
bool m_onlyActive = false;
};
#endif // VFSTABLEMODEL_H