459 lines
13 KiB
C++
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();
|
|
*/
|
|
}
|