Commit d6de6ebe authored by Lukas Appelhans's avatar Lukas Appelhans
Browse files

Make akabeicore thread-safe

parent e850531d
......@@ -331,8 +331,8 @@ QVariant SQLiteResource::getDataAt(int rowNumber, const QString& columnName)
{
QMutexLocker locker(&d->mutex);
if (d->table.isEmpty() || rowNumber >= d->table.size() || d->table[rowNumber].count(columnName) == 0) {
QString error = QString("Access to non-existent data. Trying to access %1.").arg(columnName);
throw SQLiteException(error.toUtf8().constData());
std::string error = QString("Access to non-existent data. Trying to access %1.").arg(columnName).toStdString();
throw SQLiteException(error.c_str());
}
return d->table[rowNumber][columnName];
......
......@@ -71,6 +71,8 @@ private:
/**
* @class SQLiteConnection
* @brief manages a connection with a database using the sqlite C library
*
* This class is not thread-safe.
*/
class SQLiteConnection
{
......
......@@ -33,6 +33,7 @@
Q_DECLARE_METATYPE(QUuid)
Q_DECLARE_METATYPE(QList< Akabei::Package* >)
Q_DECLARE_METATYPE(QList< Akabei::Group* >)
Q_DECLARE_METATYPE(Akabei::Error::List)
namespace Akabei
{
......@@ -258,15 +259,18 @@ void BackendPrivate::getLock()
///////////////////////////
GroupPool::GroupPool()
: m_mutex(new QMutex())
{
}
GroupPool::~GroupPool()
{
delete m_mutex;
}
Group *GroupPool::group(const QString &name)
{
QMutexLocker locker(m_mutex);
if (!m_pool.contains(name)) {
m_pool.insert(name, new Group(name));
}
......@@ -274,8 +278,9 @@ Group *GroupPool::group(const QString &name)
return m_pool[name];
}
bool GroupPool::contains(const QString &name)
bool GroupPool::contains(const QString &name) const
{
QMutexLocker locker(m_mutex);
return m_pool.contains(name);
}
......@@ -316,6 +321,8 @@ Backend::Backend(QObject* parent)
qRegisterMetaType<QUuid>();
qRegisterMetaType< QList< Akabei::Package* > >();
qRegisterMetaType< QList< Akabei::Group* > >();
qRegisterMetaType< Akabei::Error::List >();
qRegisterMetaType< Akabei::Backend::Status >();
qInstallMsgHandler(akabeiDebug);
......@@ -333,6 +340,7 @@ Backend::~Backend()
void Backend::initialize()
{
Q_D(Backend);
QWriteLocker locker(d->mutex);
d->localDatabaseFileHandler = new QFile(Akabei::Config::instance()->rootDir().absoluteFilePath("var/local/akabei.lck"));
......@@ -365,24 +373,28 @@ void Backend::initialize()
Error Backend::initError()
{
Q_D(Backend);
QReadLocker locker(d->mutex);
return d->initError;
}
QList< Database* > Backend::databases()
{
Q_D(Backend);
QReadLocker locker(d->mutex);
return d->databases;
}
Database* Backend::localDatabase()
{
Q_D(Backend);
QReadLocker locker(d->mutex);
return d->localDatabase;
}
Backend::Status Backend::status() const
{
Q_D(const Backend);
QReadLocker locker(d->mutex);
return d->status;
}
......@@ -401,6 +413,7 @@ QUuid Backend::packages()
QUuid Backend::queryGroups(const QString& sql)
{
Q_D(Backend);
QWriteLocker locker(d->mutex);
QUuid uuid = QUuid::createUuid();
......@@ -421,6 +434,7 @@ QUuid Backend::queryGroups(const QString& sql)
QUuid Backend::queryPackages(const QString& sql)
{
Q_D(Backend);
QWriteLocker locker(d->mutex);
QUuid uuid = QUuid::createUuid();
......@@ -507,6 +521,7 @@ QUuid Backend::searchPackages(const QStringList &pkgs, SearchType type)
OperationRunner* Backend::operationRunner()
{
Q_D(Backend);
QReadLocker locker(d->mutex);
return d->runner;
}
......@@ -519,9 +534,7 @@ Package* Backend::loadPackageFromFile(const QString& path)
}
Package* retpackage = new Package;
retpackage->d_func()->hasScriptlet = false;
retpackage->d_func()->hasHooks = false;
retpackage->d_func()->pathToArchive = path;
retpackage->setPathToArchive(path);
try {
ArchiveHandler pkg(path);
......@@ -554,37 +567,37 @@ Package* Backend::loadPackageFromFile(const QString& path)
// Ok, now let's analyze
if (key == "pkgname") {
retpackage->d_func()->name = value;
retpackage->d_func()->setName(value);
} else if (key == "pkgver") {
retpackage->d_func()->version = value.toUtf8();
retpackage->d_func()->setVersion(Package::Version(value.toUtf8()));
} else if (key == "pkgdesc") {
retpackage->d_func()->desc = value;
retpackage->d_func()->setDescription(value);
} else if (key == "group") {
retpackage->d_func()->groups << d->groupPool->group(value);
retpackage->d_func()->addGroup(d->groupPool->group(value));
} else if (key == "url") {
retpackage->d_func()->url = QUrl(value);
retpackage->d_func()->setUrl(QUrl(value));
} else if (key == "license") {
retpackage->d_func()->licenses << value;
retpackage->d_func()->addLicense(value);
} else if (key == "builddate") {
retpackage->d_func()->buildDate = QDateTime::fromTime_t(value.toUInt());
retpackage->d_func()->setBuildDate(QDateTime::fromTime_t(value.toUInt()));
} else if (key == "packager") {
retpackage->d_func()->packager = value;
retpackage->d_func()->setPackager(value);
} else if (key == "arch") {
retpackage->d_func()->arch = value;
retpackage->d_func()->setArch(value);
} else if (key == "size") {
retpackage->d_func()->size = value.toInt();
retpackage->d_func()->setSize(value.toInt());
} else if (key == "depend") {
retpackage->d_func()->deps << value;
retpackage->d_func()->addDependency(value);
} else if (key == "optdepend") {
retpackage->d_func()->optdepends << value;
retpackage->d_func()->addOptDependency(value);
} else if (key == "conflict") {
retpackage->d_func()->conflicts << value;
retpackage->d_func()->addConflict(value);
} else if (key == "replaces") {
retpackage->d_func()->replaces << value;
retpackage->d_func()->addReplaces(value);
} else if (key == "provides") {
retpackage->d_func()->providers << value;
retpackage->d_func()->addProvider(value);
} else if (key == "hook") {
retpackage->d_func()->hooks << value;
retpackage->d_func()->addHook(value);
} else if (key == "backup") {
/*
......@@ -592,45 +605,44 @@ Package* Backend::loadPackageFromFile(const QString& path)
*/
QString md5 = pkg.md5(value);
if (!md5.isEmpty()) {
retpackage->d_func()->backup.insert(value, md5);
retpackage->d_func()->addBackup(value, md5);
}
} else if (key == "makepkgopt") {
// retpackage->d_func()->arch = value;
} else if (key == "screenshot") {
retpackage->d_func()->screenshot = QUrl(value);
retpackage->d_func()->setScreenshot(QUrl(value));
} else if (key == "mimetype") {
retpackage->d_func()->mimetypes << value;
retpackage->d_func()->addMimetype(value);
} else if (key == "hook") {
retpackage->d_func()->hooks << value;
retpackage->d_func()->hasHooks = true;
retpackage->d_func()->addHook(value);
} else if (key == "epoch") {
retpackage->d_func()->version.d->epoch = value.toInt();
retpackage->d_func()->setEpoch(value.toInt());
} else if (key == "gitfolder") {
retpackage->d_func()->gitFolder = value;
retpackage->d_func()->setGitFolder(value);
} else if (key == "gitrepository") {
retpackage->d_func()->gitRepo = value;
retpackage->d_func()->setGitRepo(value);
} else if (key == "gitbranch") {
retpackage->d_func()->gitBranch = value;
retpackage->d_func()->setGitBranch(value);
} else if (key == "gitrepo") { //This is legacy support
QStringList split = value.split("-");
if (!split.isEmpty()) {
retpackage->d_func()->gitRepo = split.takeFirst();
retpackage->d_func()->gitBranch = split.isEmpty() ? "master" : split.first();
retpackage->d_func()->setGitRepo(split.takeFirst());
retpackage->d_func()->setGitBranch(split.isEmpty() ? "master" : split.first());
}
}
}
if (pkg.getEntries().contains(".INSTALL")) {
retpackage->d_func()->scriptlet = pkg.readTextFile(".INSTALL");
retpackage->d_func()->hasScriptlet = true;
retpackage->d_func()->setScriptlet(pkg.readTextFile(".INSTALL"));
}
retpackage->d_func()->files = pkg.getEntries();
retpackage->d_func()->isize = 0;
foreach (QString const& entry, retpackage->d_func()->files) {
retpackage->d_func()->isize += pkg.getEntrySize(entry);
int isize = 0;
foreach (QString const& entry, pkg.getEntries()) {
retpackage->d_func()->addFile(entry);
isize += pkg.getEntrySize(entry);
}
retpackage->d_func()->setInstalledSize(isize);
} catch (ArchiveException& e) {
akabeiDebug() << "Error during access in archive: " << e.what();
......@@ -644,19 +656,15 @@ Package* Backend::loadPackageFromFile(const QString& path)
}
// Now add md5sum and filename, of course
retpackage->d_func()->filename = path.split('/').last();
retpackage->d_func()->md5sum = Helpers::md5sumOfFile(path);
retpackage->d_func()->setFilename(path.split('/').last());
retpackage->d_func()->setMd5sum(Helpers::md5sumOfFile(path));
// And size
QFileInfo finfo(path);
retpackage->d_func()->size = finfo.size();
// Fill some fields
retpackage->d_func()->installDate = QDateTime();
retpackage->d_func()->reason = Package::NoReason;
retpackage->d_func()->setSize(finfo.size());
// We already computed md5 and validation, so set em up
retpackage->d_func()->_p_md5checked = true;
retpackage->d_func()->_p_md5checked = true;//We don't need to lock for setting these properties, as our object is guaranteed to only be written on by us
retpackage->d_func()->_p_validated = true;
return retpackage;
......@@ -761,9 +769,9 @@ Group* Backend::loadGroupFromFile(const QString &path)
if (split.first() == "Name") {
group = d->groupPool->group(split.last());
} else if (group != 0 && split.first() == "Description") {
group->d_ptr->desc = split.last();
group->d_ptr->setDescription(split.last());
} else if (group != 0 && split.first() == "Iconname") {
group->d_ptr->icon = split.last();
group->d_ptr->setIcon(split.last());
}
}
return group;
......
......@@ -12,6 +12,7 @@
#define AKABEIBACKEND_H
#include <QObject>
#include <QMetaType>
#include <akabeicore_global.h>
#include "akabeierror.h"
......@@ -55,6 +56,8 @@ class BackendPrivate;
* If you want to do install/remove/upgrade a package without manually handling all the operations, you should
* use \c AkabeiClient::Backend. This is recommended, as the user should always have the same operations run during
* e.g. an install operation on a system.
*
* This class is thread-safe.
*/
class AKABEICORESHARED_EXPORT Backend : public QObject
{
......@@ -175,7 +178,7 @@ class AKABEICORESHARED_EXPORT Backend : public QObject
* @param path the path of the file containing the hook information
*/
Hook *loadHookFromFile(const QString &path, QList<Package*> pkgs);
/**
* @returns what went wrong during initialization
*/
......@@ -223,4 +226,6 @@ class AKABEICORESHARED_EXPORT Backend : public QObject
}
Q_DECLARE_METATYPE(Akabei::Backend::Status)
#endif // AKABEIBACKEND_H
......@@ -20,6 +20,7 @@
#include <QFutureWatcher>
#include <QUuid>
#include <QFile>
#include <QReadWriteLock>
namespace Akabei
{
......@@ -30,13 +31,15 @@ class GroupPool
virtual ~GroupPool();
Group *group(const QString &name);
bool contains(const QString &name);
bool contains(const QString &name) const;
private:
GroupPool();
QHash<QString, Group*> m_pool;
QMutex * m_mutex;
friend class BackendPrivate;
};
......@@ -51,6 +54,7 @@ class BackendPrivate : public QObject
: QObject(p)
, q_ptr(p)
, status(Backend::StatusBare)
, mutex(new QReadWriteLock(QReadWriteLock::Recursive))
, localDatabase(0)
, localDatabaseFileHandler(0)
, fsWatcher(new QFileSystemWatcher(p))
......@@ -61,9 +65,11 @@ class BackendPrivate : public QObject
qDeleteAll(databases);
delete localDatabase;
delete groupPool;
delete mutex;
}
Backend::Status status;
QReadWriteLock *mutex;
Database *localDatabase;
QFile * localDatabaseFileHandler;
......@@ -78,7 +84,7 @@ class BackendPrivate : public QObject
QHash< QUuid, QFutureWatcher< QList < Group* > >* > queryGroupFuturePool;
OperationRunner *runner;
Error initError;
void __k__initializationFinished();
......
......@@ -22,6 +22,16 @@
namespace Akabei
{
/**
* \class Cache akabeicache.h "akabeicache.h"
*
* \brief This class represents the package cache.
*
* This class is used to manage the cache. It takes care of cleaning unused/old packages from it
* when configured so and can be used to get information if packages are cached.
*
* This class is not thread-safe.
*/
class Cache
{
public:
......
......@@ -25,6 +25,7 @@ class Config::Private
: root('/')
, maxCacheSize(0)
, debug(false)
, mutex(QReadWriteLock::Recursive)
{}
~Private() {}
......@@ -41,8 +42,10 @@ class Config::Private
int maxCacheSize;
Cache::MaxSizePolicy policy;
bool debug;
QReadWriteLock mutex;
};
class ConfigHelper
......@@ -83,124 +86,150 @@ Config::~Config()
QString Config::root() const
{
QReadLocker locker(&d->mutex);
return d->root;
}
QDir Config::rootDir() const
{
QReadLocker locker(&d->mutex);
return d->rootDir;
}
QString Config::cachePath() const
{
QReadLocker locker(&d->mutex);
return d->cachePath;
}
QDir Config::cacheDir() const
{
QReadLocker locker(&d->mutex);
return d->cacheDir;
}
QString Config::databasePath() const
{
QReadLocker locker(&d->mutex);
return d->databasePath;
}
QDir Config::databaseDir() const
{
QReadLocker locker();
return d->databaseDir;
}
QStringList Config::databases() const
{
QReadLocker locker(&d->mutex);
return d->databases;
}
QStringList Config::noUpgrade() const
{
QReadLocker locker(&d->mutex);
return d->noUpgradeFiles;
}
QStringList Akabei::Config::noExtract() const
{
QReadLocker locker(&d->mutex);
return d->noExtractFiles;
}
QStringList Config::keepPackages() const
{
QReadLocker locker(&d->mutex);
return d->keepPackages;
}
int Config::maxCacheSize() const
{
QReadLocker locker(&d->mutex);
return d->maxCacheSize;
}
Cache::MaxSizePolicy Config::cachePolicy() const
{
QReadLocker locker(&d->mutex);
return d->policy;
}
bool Config::debug()
{
QReadLocker locker(&d->mutex);
return d->debug;
}
void Config::setCachePath(const QString& path)
{
QWriteLocker locker(&d->mutex);
d->cachePath = path;
d->cacheDir = QDir(d->cachePath);
}
void Config::setDatabasePath(const QString& path)
{
QWriteLocker locker(&d->mutex);
d->databasePath = path;
d->databaseDir = QDir(d->databasePath);
}
void Config::setDatabases(const QStringList& dbs)
{
QReadLocker locker(&d->mutex);
foreach (const QString &db, dbs) {
if (!d->databases.contains(db))
if (!d->databases.contains(db)) {
Akabei::Backend::instance()->d_ptr->addDatabase(db);
}
}
locker.unlock();//Needed, because we can only acquire a readlock until the addDatabase, otherwise we deadlock ourselves
QWriteLocker wlocker(&d->mutex);
d->databases = dbs;
}
void Config::setRoot(const QString& root)
{
QWriteLocker locker(&d->mutex);
d->root = root;
d->rootDir = QDir(d->root);
}
void Config::setNoUpgrade(const QStringList& files)
{
QWriteLocker locker(&d->mutex);
d->noUpgradeFiles = files;
}
void Config::setNoExtract(const QStringList& files)
{
QWriteLocker locker(&d->mutex);
d->noExtractFiles = files;
}
void Config::setKeepPackages(const QStringList& pkgs)
{
QWriteLocker locker(&d->mutex);
d->keepPackages = pkgs;
}
void Config::setMaxCacheSize(int size)
{
QWriteLocker locker(&d->mutex);
d->maxCacheSize = size;
}
void Config::setCachePolicy(Cache::MaxSizePolicy policy)
{
QWriteLocker locker(&d->mutex);
d->policy = policy;
}
void Config::setDebug(bool debug)
{
QWriteLocker locker(&d->mutex);
d->debug = debug;
}
......
......@@ -22,6 +22,8 @@ namespace Akabei {
* \class Config akabeiconfig.h "akabeiconfig.h"
*
* \brief This class just holds certain config values which akabeicore needs to be working.
*
* This class is thread-safe.
*/
class AKABEICORESHARED_EXPORT Config
{
......@@ -133,12 +135,12 @@ class AKABEICORESHARED_EXPORT Config
* Sets the policy to be applied to the cache
*/
void setCachePolicy(Cache::MaxSizePolicy policy);
/**
* @returns whether to display debugging prints
*/
bool debug();
/**
* Sets whether to display debugging prints
*/
......
......@@ -24,11 +24,11 @@ DatabasePrivate::DatabasePrivate(Database* db, QString const& pathToDatabase)
: q_ptr(db)
, pathToDb(pathToDatabase)
, valid(true)
, mutex(new QMutex())
, mutex(new QMutex(QMutex::Recursive))
{
try {
dbConnection.connectToDB(pathToDb, false);
} catch (SQLiteException &ex) {
} catch (SQLiteException &ex) {
valid = false;
error.setType( Error::DatabaseError );
error.setDescription( ex.what() );
......@@ -40,10 +40,8 @@ DatabasePrivate::~DatabasePrivate()
delete mutex;
}
// FIXME: what do we need this method for?