quickmod/FuseMounter/fusearchive.cpp

459 lines
13 KiB
C++

#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();
*/
}