Commit d6de6ebe authored by Lukas Appelhans's avatar Lukas Appelhans

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
{
......
This diff is collapsed.
......@@ -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?
void DatabasePrivate::init()
{
//FIXME: Also reload the file if needed in Shaman
try {
populateGroupCache(); /* populate group cache on startup */
valid = true;
......@@ -81,8 +79,9 @@ Group* DatabasePrivate::groupFromRow(SQLiteResource& table, int row)
// In this case, populate its fields
Group* group = Backend::instance()->d_func()->groupPool->group(name);
group->d_ptr->desc = table.getDataAt(row, "description").toString();
group->d_ptr->icon = table.getDataAt(row, "icon").toString();
group->d_ptr->setDescription(table.getDataAt(row, "description").toString());
group->d_ptr->setIcon(table.getDataAt(row, "icon").toString());
return group;
}
// Return the group from the pool
......@@ -111,74 +110,61 @@ Package* DatabasePrivate::packageFromRow(SQLiteResource& table, int row)
QString url = table.getDataAt(row, "url").toString();
QString md5 = table.getDataAt(row, "md5sum").toString();
package->d_func()->arch = table.getDataAt(row, "arch").toString();
package->d_func()->buildDate = QDateTime::fromTime_t(table.getDataAt(row, "builddate").toInt());
package->d_func()->installDate = QDateTime::fromTime_t(table.getDataAt(row, "installdate").toInt());
package->d_func()->desc = table.getDataAt(row, "description").toString();
package->d_func()->filename = table.getDataAt(row, "filename").toString();
package->d_func()->md5sum = md5.toUtf8();
package->d_func()->packager = table.getDataAt(row, "packager").toString();
package->d_func()->size = table.getDataAt(row, "size").toInt();
package->d_func()->isize = table.getDataAt(row, "installedsize").toInt();
package->d_func()->url = QUrl(url);
package->d_func()->version = table.getDataAt(row, "version").toString().toUtf8();
package->d_func()->screenshot = QUrl(table.getDataAt(row, "screenshoturl").toString());
package->d_func()->setArch(table.getDataAt(row, "arch").toString());
package->d_func()->setBuildDate(QDateTime::fromTime_t(table.getDataAt(row, "builddate").toInt()));
package->d_func()->setInstallDate(QDateTime::fromTime_t(table.getDataAt(row, "installdate").toInt()));
package->d_func()->setDescription(table.getDataAt(row, "description").toString());
package->d_func()->setFilename(table.getDataAt(row, "filename").toString());
package->d_func()->setMd5sum(md5.toUtf8());
package->d_func()->setPackager(table.getDataAt(row, "packager").toString());
package->d_func()->setSize(table.getDataAt(row, "size").toInt());
package->d_func()->setInstalledSize(table.getDataAt(row, "installedsize").toInt());
package->d_func()->setUrl(QUrl(url));
package->d_func()->setVersion(Package::Version(table.getDataAt(row, "version").toString().toUtf8()));
package->d_func()->setEpoch(table.getDataAt(row, "epoch").toInt());
package->d_func()->setScreenshot(QUrl(table.getDataAt(row, "screenshoturl").toString()));
// Fetch the provided package names
QStringList provides;
SQLiteResource res = dbConnection.query(QString("SELECT provides FROM provides WHERE package=%1").arg(id));
foreach (QVariant v, res.getColumn("provides")) {
provides << v.toString();
package->d_func()->addProvider(v.toString());
}
package->d_func()->providers = provides;
// Fetch the conflicting package names
QStringList conflicts;
res = dbConnection.query(QString("SELECT conflict FROM conflicts WHERE package=%1").arg(id));
foreach (QVariant v, res.getColumn("conflict")) {
conflicts << v.toString();
package->d_func()->addConflict(v.toString());
}
package->d_func()->conflicts = conflicts;
// Fetch the package names to which we depend on
QStringList depends;
res = dbConnection.query(QString("SELECT dependency FROM depends WHERE package=%1").arg(id));
foreach (QVariant v, res.getColumn("dependency")) {
depends << v.toString();
package->d_func()->addDependency(v.toString());
}
package->d_func()->deps = depends;
// Fetch the package names to which we optionally dependend on
QStringList optional;
res = dbConnection.query(QString("SELECT dependency FROM optional WHERE package=%1").arg(id));
foreach (QVariant v, res.getColumn("dependency")) {
optional << v.toString();
package->d_func()->addOptDependency(v.toString());
}
package->d_func()->optdepends = optional;
// Fetch the packages names that we replace
QStringList replaces;
res = dbConnection.query(QString("SELECT replaces FROM replaces WHERE package=%1").arg(id));
foreach (QVariant v, res.getColumn("replaces")) {
replaces << v.toString();
package->d_func()->addReplaces(v.toString());
}
package->d_func()->replaces = replaces;
// Fetch the mimetypes which we provide
QStringList mimetypes;
res = dbConnection.query(QString("SELECT mimetype FROM providesmimetype WHERE package=%1").arg(id));
foreach (QVariant v, res.getColumn("mimetype")) {
mimetypes << v.toString();
package->d_func()->addMimetype(v.toString());
}
package->d_func()->mimetypes = mimetypes;
// Fetch the licenses of this package
QStringList licenses;
res = dbConnection.query(QString("SELECT license FROM licensed WHERE package=%1").arg(id));
foreach (QVariant v, res.getColumn("license")) {
licenses << v.toString();
package->d_func()->addLicense(v.toString());
}
package->d_func()->licenses = licenses;
/*
* Groups
......@@ -187,44 +173,41 @@ Package* DatabasePrivate::packageFromRow(SQLiteResource& table, int row)
res = dbConnection.query(groupQuery);
for (int i = 0; i < res.getRowsCount(); i++) {
package->d_func()->groups << groupFromRow(res, i);
package->d_func()->addGroup(groupFromRow(res, i));
}
// Install Reason (if any)
switch (installreason) {
case 1:
// Explicit
package->d_func()->reason = Package::ExplicitlyInstalledReason;
package->d_func()->setInstallReason(Package::ExplicitlyInstalledReason);
break;
case 2:
// Dependency
package->d_func()->reason = Package::InstalledAsDependencyReason;
package->d_func()->setInstallReason(Package::InstalledAsDependencyReason);
break;
default:
// No reason
package->d_func()->reason = Package::NoReason;
package->d_func()->setInstallReason(Package::NoReason);
break;
}
// Check if it has a scriptlet
package->d_func()->hasScriptlet = false;
QString sql = QString("SELECT * FROM scriptlets WHERE package=%1").arg(id);
res = dbConnection.query(sql);
if (res.getRowsCount() > 0) {
package->d_func()->hasScriptlet = true;
package->d_func()->setScriptlet(res.getDataAt(0, "scriptlet").toString());
}
// Check if it has any hooks
package->d_func()->hasHooks = false;
sql = QString("SELECT * FROM belongshook JOIN hooks ON package=" + QString::number(id) + " AND belongshook.hookname=hooks.name;");
res = dbConnection.query(sql);
if (res.getRowsCount() > 0) {
// There is one
package->d_func()->hasHooks = true;
for (int row = 0; row < res.getRowsCount(); row++) {
package->d_func()->hooks << res.getDataAt(row, "name").toString();
package->d_func()->addHook(res.getDataAt(row, "name").toString());
}
}
......@@ -299,7 +282,7 @@ Database::Database(QString const& pathToDatabase)
: d_ptr(new DatabasePrivate(this, pathToDatabase))
{
Q_D(Database);
/*
* If the connection to the database wasn't established correctly, there's no point
* in going on with the initialization (althought it's a safe NOP if you do it)
......@@ -321,18 +304,19 @@ void Database::reinit()
d->init();
}
/*
* TODO: this and d->valid shouldn't be necessary
*/
bool Database::isValid() const
{
Q_D(const Database);
// Shield us from multithreaded disaster
QMutexLocker locker(d->mutex);
return d->valid;
}
Error Database::error() const
{
Q_D(const Database);
// Shield us from multithreaded disaster
QMutexLocker locker(d->mutex);
return d->error;
}
......
......@@ -22,13 +22,15 @@ class DeltaPrivate;
* \class Delta akabeidelta.h "akabeidelta.h"
*
* \brief This class describes a binary patch we use to upgrade from one package version to another.
*
*
* Akabei Deltas can either be loaded from the \c Database or from a local file (\c Backend). If both the
* path to the .delta.tar.xz-file and the original package are set, it's just a matter of running the command
* generated by \c xdeltaApplyCommand.
* @see setPathToDelta()
* @see setPathToSource()
* @see xdeltaApplyCommand
*
* This class is not thread-safe.
*/
class Delta {
Q_DISABLE_COPY(Delta)
......
......@@ -11,7 +11,6 @@
#ifndef AKABEI_AKABEIERROR_H
#define AKABEI_AKABEIERROR_H
#include <QMetaType>
#include <QString>
#include <QList>
#include <QSharedDataPointer>
......@@ -25,6 +24,8 @@ class Operation;
* \class Error akabeierror.h "akabeierror.h"
*
* \brief Class for describing an error which occurred.
*
* This class is not thread-safe.
*/
class Error
{
......@@ -124,6 +125,4 @@ class Error
}
Q_DECLARE_METATYPE(Akabei::Error::List)
#endif // AKABEI_AKABEIERROR_H
......@@ -29,25 +29,28 @@ Group::~Group()
QString Group::description() const
{
Q_D(const Group);
QReadLocker locker(d->mutex);
return d->desc;
}
QString Group::name() const
{
Q_D(const Group);
QReadLocker locker(d->mutex);
return d->name;
}
QString Group::iconName() const
{
Q_D(const Group);
QReadLocker locker(d->mutex);
return d->icon;
}
QList< Akabei::Package* > Group::packages()
{
Q_D(Group);
QReadLocker locker(d->mutex);
Helpers::PackageEventLoop e;
e.connect(Backend::instance(), SIGNAL(queryPackagesCompleted(QUuid,QList<Akabei::Package*>)),
&e, SLOT(requestQuit(QUuid,QList<Akabei::Package*>)));
......@@ -59,6 +62,7 @@ QList< Akabei::Package* > Group::packages()
QList< Package* > Group::search(const QString& token)
{
Q_D(Group);
QReadLocker locker(d->mutex);
Helpers::PackageEventLoop e;
e.connect(Backend::instance(), SIGNAL(queryPackagesCompleted(QUuid,QList<Akabei::Package*>)),
&e, SLOT(requestQuit(QUuid,QList<Akabei::Package*>)));
......
......@@ -27,6 +27,8 @@ class GroupPrivate;
* \brief This class describes a group of packages.
*
* Groups are usually loaded from a Database or from a \link Akabei::Backend .group-file \endlink.
*
* This class is thread-safe.
*/
class AKABEICORESHARED_EXPORT Group
{
......
/* This file is part of the Chakra project
Copyright (C) 2010 Dario Freddi <drf@chakra-project.org>
Copyright (C) 2012 Lukas Appelhans <boom1992@chakra-project.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
*/
#include <akabeigroup.h>
#include <QReadWriteLock>
namespace Akabei
{
......@@ -16,12 +17,26 @@ namespace Akabei
class GroupPrivate
{
public:
GroupPrivate(const QString &n) : name(n) {}
virtual ~GroupPrivate() {}
GroupPrivate(const QString &n) : name(n), mutex(new QReadWriteLock(QReadWriteLock::Recursive)) {}
virtual ~GroupPrivate() {
delete mutex;
}
void setDescription(const QString &d) {
QWriteLocker locker(mutex);
desc = d;
}
void setIcon(const QString &i) {
QWriteLocker locker(mutex);
icon = i;
}
private:
QString name;
QString desc;
QString icon;
QReadWriteLock *mutex;
friend class Group;
};
}
\ No newline at end of file
......@@ -402,7 +402,7 @@ int QueryPerformer::insertPackage(SQLiteConnection &dbConnection, Package* p)
dbConnection.query(sqlQuery);
int id = dbConnection.getLastRowId();
QueryHelper::packagePrivateFromPackage(p)->databaseId = id;
QueryHelper::packagePrivateFromPackage(p)->setDatabaseId(id);
foreach (Akabei::Group *group, p->groups()) {
// Can I assume the group already exists?
......
......@@ -27,24 +27,29 @@ Akabei::Hook::~Hook()
QString Akabei::Hook::name() const
{
Q_D(const Hook);
QReadLocker locker(d->mutex);
return d->name;
}
QString Akabei::Hook::content() const
{
Q_D(const Hook);
QReadLocker locker(d->mutex);
return d->content;
}
QList< Akabei::Package* > Akabei::Hook::packages() const
{
Q_D(const Hook);
QReadLocker locker(d->mutex);
return d->packages;
}
QString Akabei::Hook::path()
{
Q_D(Hook);
QWriteLocker locker(d->mutex);
if (d->file)
return d->file->fileName();
......
......@@ -21,16 +21,18 @@ class Package;
* \class Hook akabeihook.h "akabeihook.h"
*
* \brief This class describes a script which possibly gets executed once in a transaction.
*
*
* A Hook is basically a scriplet, but on a global scale.
* Even though multiple packages can request a hook to be executed,
* it will only be done once at the end of the transaction.
*
*
* For example, we have a bunch of packages wanting to run kbuildsycoca4 to update kde's plugin cache.
* Since this command is quite expensive it took a lot of time in pacman days to install multiple of
* Since this command is quite expensive it took a lot of time in pacman days to install multiple of
* those packages as every package was executing the command on itself.
* Now we just have a hook running kbuildsycoca4 inside the database and all those packages use it. It
* then gets executed only once at the end of the transaction.
*
* This class is thread-safe.
*/
class Hook
{
......@@ -38,31 +40,31 @@ class Hook