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:
parent
1e14743bc2
commit
4bd0e980f3
30 changed files with 2820 additions and 164 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ public:
|
|||
int m_id;
|
||||
QString m_archivePath;
|
||||
QString m_installedPath;
|
||||
QString m_lpath;
|
||||
QStringList m_lparts;
|
||||
|
||||
ModEntry() : m_id{0} {}
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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
459
FuseMounter/fusearchive.cpp
Normal 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
146
FuseMounter/fusearchive.h
Normal 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
22
FuseMounter/fusebase.cpp
Normal 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
73
FuseMounter/fusebase.h
Normal 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
315
FuseMounter/fuseproxy.cpp
Normal 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
67
FuseMounter/fuseproxy.h
Normal 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
201
FuseMounter/fusesandbox.cpp
Normal 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
43
FuseMounter/fusesandbox.h
Normal 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
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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.**
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
2
qml.qrc
2
qml.qrc
|
|
@ -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
128
qml/TableViewWithHeader.qml
Normal 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
51
qml/VFSStats.qml
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
qml/main.qml
67
qml/main.qml
|
|
@ -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 {
|
||||
|
|
|
|||
56
quickmod.pro
56
quickmod.pro
|
|
@ -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 */
|
||||
|
|
|
|||
22
src/file.cpp
22
src/file.cpp
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
31
src/main.cpp
31
src/main.cpp
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
111
src/vfstablemodel.cpp
Normal 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
84
src/vfstablemodel.h
Normal 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
|
||||
Loading…
Reference in a new issue