akabeibackend.cpp 32.7 KB
Newer Older
Dario Freddi's avatar
Dario Freddi committed
1 2 3
/* This file is part of the Chakra project

   Copyright (C) 2010 Dario Freddi <drf@chakra-project.org>
4
   Copyright (C) 2012 Lukas Appelhans <boom1992@chakra-project.org>
Dario Freddi's avatar
Dario Freddi committed
5 6 7 8 9 10 11

   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.
*/

12 13 14 15 16 17 18 19 20
#include <akabeibackend_p.h>

#include <akabeiconfig.h>
#include <akabeidatabase.h>
#include <akabeigroup.h>
#include <akabeipackage_p.h>
#include <akabeihelpers_p.h>
#include <akabeidelta_p.h>
#include <akabeigroup_p.h>
Lisa's avatar
Lisa committed
21
#include <akabeiquery.h>
22 23 24 25 26 27

#include <QHash>
#include <QtConcurrentRun>
#include <QtConcurrentMap>
#include <QDir>
#include <QFutureWatcher>
28
#include <QVariant>
Lukas Appelhans's avatar
Lukas Appelhans committed
29 30
#include <QTranslator>
#include <QCoreApplication>
31 32
#include <QDBusInterface>
#include <QDBusConnectionInterface>
33
#include <QDBusMetaType>
34
#include <QMetaEnum>
35

36 37
#include <memory>

38 39
#include <libarchive++/archivehandler.h>
#include <akabeihook.h>
40
#include "akabeilog.h"
41
#include <fcntl.h>
42

43 44
#include <polkit-qt-1/polkitqt1-authority.h>

Dario Freddi's avatar
Dario Freddi committed
45 46 47
Q_DECLARE_METATYPE(QUuid)
Q_DECLARE_METATYPE(QList< Akabei::Package* >)
Q_DECLARE_METATYPE(QList< Akabei::Group* >)
Lukas Appelhans's avatar
Lukas Appelhans committed
48
Q_DECLARE_METATYPE(Akabei::Error::List)
Dario Freddi's avatar
Dario Freddi committed
49

50 51 52
namespace Akabei
{

53
// For concurrent queries
54 55
struct ConcurrentPackageQuery
{
Daniele Cocca's avatar
Daniele Cocca committed
56 57 58 59
    ConcurrentPackageQuery(QString const& sql)
      : m_sql( sql )
    {
    }
60 61 62

    typedef QList< Package* > result_type;

63 64 65 66
    QList< Package* > operator()(Database *db)
    {
        return db->queryPackages(m_sql);
    }
67 68 69 70

    QString m_sql;
};

71 72 73 74
struct ConcurrentOrphanQuery
{
    ConcurrentOrphanQuery()
    {}
75

76
    typedef QList< Package* > result_type;
77

78 79 80 81
    /* Computes the list of orphan packages */
    QList< Package* > operator()()
    {
        QHash< QString, Package* > allPackages;
82 83
        QSet<QString> toBeRemoved;

84 85 86
        foreach (Package* pkg, Backend::instance()->localDatabase()->packages()) {
            allPackages[ pkg->name() ] = pkg;
        }
87

88 89
        for (auto it = allPackages.constBegin() , end = allPackages.constEnd(); it != end; ++it) {
            auto pkg = it.value();
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
            if (pkg->installReason() == Package::ExplicitlyInstalledReason) {
                Package::List stack;

                stack.push_front(pkg);

                while (!stack.isEmpty()) {
                    Package* current = stack.takeFirst();

                    if (toBeRemoved.contains(current->name()))
                        continue;

                    toBeRemoved.insert(current->name());

                    foreach (const QString &dep, current->dependencies()) {
                        QPair<QString, QString> versioned = Helpers::versionedTarget(dep);
                        if (allPackages[versioned.first])
                            stack.append(allPackages[versioned.first]);
                    }
                }
109 110
            }
        }
111

112 113 114 115
        /* I do this separately to avoid issues in the foreach loop */
        foreach (const QString& p, toBeRemoved) {
            allPackages.remove(p);
        }
116

117 118 119 120
        return allPackages.values();
    }
};

121 122
struct ConcurrentGroupQuery
{
Daniele Cocca's avatar
Daniele Cocca committed
123 124 125 126
    ConcurrentGroupQuery(QString const& sql)
      : m_sql( sql )
    {
    }
127 128 129

    typedef QList< Group* > result_type;

130 131 132 133
    QList< Group* > operator()(Database *db)
    {
        return db->queryGroups(m_sql);
    }
134 135 136 137

    QString m_sql;
};

138
QHash<QString, Database*> BackendPrivate::performInitialization()
139
{
140 141
    QHash<QString, Database*> rethash;

142
    // Check for the existence of all the needed directories
143 144
    QDir dbDir(Config::instance()->databasePath());
    if (!dbDir.exists()) {
Lisa's avatar
Lisa committed
145
        Error initError;
146
        initError.setType(Error::BackendInitializationError);
147
        initError.setDescription(QObject::tr("Database dir %1 does not exist").arg(Config::instance()->databasePath()));
Lisa's avatar
Lisa committed
148
        ErrorQueue::instance()->appendError(initError);
149
        // Fail
150 151
        return QHash<QString, Database*>();
    }
Lisa's avatar
Lisa committed
152
    
153 154 155

    QDir cacheDir(Config::instance()->cachePath());
    if (!cacheDir.exists()) {
Lisa's avatar
Lisa committed
156
        Error initError;
157
        initError.setType(Error::BackendInitializationError);
158
        initError.setDescription(QObject::tr("Cache dir %1 does not exist").arg(Config::instance()->cachePath()));
Lisa's avatar
Lisa committed
159
        ErrorQueue::instance()->appendError(initError);
160
        // Fail
161 162 163 164 165
        return QHash<QString, Database*>();
    }

    QDir rootDir(Config::instance()->root());
    if (!rootDir.exists()) {
Lisa's avatar
Lisa committed
166
        Error initError;
167
        initError.setType(Error::BackendInitializationError);
168
        initError.setDescription(QObject::tr("Root dir %1 does not exist").arg(Config::instance()->root()));
Lisa's avatar
Lisa committed
169
        ErrorQueue::instance()->appendError(initError);
170
        // Fail
171 172
        return QHash<QString, Database*>();
    }
Lukas Appelhans's avatar
Lukas Appelhans committed
173

174
    Database* localDb = new Database( dbDir.absoluteFilePath(QStringLiteral("local.db")) );
Lisa's avatar
Lisa committed
175
    
176
    if (!localDb->isValid()) {
Lisa's avatar
Lisa committed
177 178
        qDebug() << "Local db";
        Error initError;
179
        initError.setType(Error::BackendInitializationError);
180
        initError.setDescription(QObject::tr("Error while loading the local database from %1: %2").arg(dbDir.path(), localDb->error().description()));
Lisa's avatar
Lisa committed
181
        ErrorQueue::instance()->appendError(initError);
182 183 184
        return QHash<QString, Database*>();
    }

185
    // Ok, add it to our rethash
186
    rethash.insert(QStringLiteral("local"), localDb);
187

188
    // Now process all the various databases
Daniele Cocca's avatar
Daniele Cocca committed
189
    foreach (QString const& db, Config::instance()->databases()) {
190
        Database* database = new Database(dbDir.absoluteFilePath(db + ".db"));
Lukas Appelhans's avatar
Lukas Appelhans committed
191

192 193
        if (database->isValid()) {
            // Ok, add it to our rethash
194
            rethash.insert(db, database);
195
        }
196 197
    }

198
    // Return it straight
199
    return rethash;
200
}
201

202 203
void BackendPrivate::addDatabase(const QString &name)
{
204
    if (name == QLatin1String("local")) {
205
        return;
Daniele Cocca's avatar
Daniele Cocca committed
206
    }
207

208
    Database* database = new Database(Akabei::Config::instance()->databaseDir().absoluteFilePath(name + ".db"));
Lukas Appelhans's avatar
Lukas Appelhans committed
209

210
    if (database->isValid()) {
211
        databases.append(database);
212
    }
213 214
}

215
void Backend::setStatus(Backend::Status s, QObject * cO, const char * cS)
216
{
217 218
    Q_D(Backend);

219
    akabeiDebug() << "Current status: " << Akabei::Backend::statusToString(d->status) << " next status: " << Akabei::Backend::statusToString(s);
220 221 222 223 224 225
    if (s == d->status)
        return;

    if (s == Backend::StatusOnTransaction && d->status != Akabei::Backend::StatusWaitingForLock) {
        d->callbackObject = cO;
        d->callbackSlot = cS;
226
        
227
        if (Config::instance()->needsPrivileges()) {
228
            akabeiDebug() << "We need privileges to get lock";
229
            if (Akabei::Helpers::checkAuthorizationSync(QStringLiteral("org.chakraproject.akabeicorehelper.filesystem.lock"))) {
230
                setStatus(Backend::StatusWaitingForLock);
231
                akabeiDebug()  << "Authorization granted";
Lukas Appelhans's avatar
Lukas Appelhans committed
232
                if (!d->iface) {
233
                    d->iface = new QDBusInterface(QStringLiteral("org.chakraproject.akabeicorehelper"), QStringLiteral("/filesystem"), QStringLiteral("org.chakraproject.akabeicorehelper.filesystem"), QDBusConnection::systemBus());
Lukas Appelhans's avatar
Lukas Appelhans committed
234 235 236
                }
                
                d->iface->setProperty("root", Config::instance()->root());
237

Lukas Appelhans's avatar
Lukas Appelhans committed
238 239
                connect(d->iface, SIGNAL(lockGranted(qlonglong)), SLOT(__k__lockGranted(qlonglong)));
                
240
                d->iface->asyncCall(QStringLiteral("getLock"), QCoreApplication::applicationPid());
241
            } else {
242
                akabeiDebug() << "Authorization not granted" << PolkitQt1::Authority::instance()->errorDetails();
Lisa's avatar
Lisa committed
243
                Akabei::ErrorQueue::instance()->appendError(Error(Error::PermissionError, tr("An authorization error occurred: %1").arg(PolkitQt1::Authority::instance()->errorDetails())));
244 245
                setStatus(Backend::StatusBroken);
            }
246 247
            
            AkabeiLogLine::initialize( Akabei::Config::instance()->useSyslog(), Akabei::Config::instance()->logFile() );
Lisa's avatar
Lisa committed
248
            return;
249
        } else {
250
            akabeiDebug() << "We have privileges, now try get the lock";
251

252
            if (!d->lockFileHandle) {
253
                d->lockFileHandle = new QFile(Akabei::Config::instance()->rootDir().absoluteFilePath(QStringLiteral("var/local/akabei.lck")));
254

255
                if (!d->lockFileHandle->open(QFile::ReadWrite)) {
256
                    ErrorQueue::instance()->appendError( Error(Error::PermissionError, QStringLiteral("Could not open lock file for writing")) );
257 258 259
                    setStatus(Backend::StatusBroken);
                    return;
                }
260 261
            }

262 263 264 265 266 267
            struct flock lockinfo;
            memset(&lockinfo, 0, sizeof(struct flock));
            lockinfo.l_type = F_WRLCK;
            lockinfo.l_whence = SEEK_SET;
            lockinfo.l_start = 0;
            lockinfo.l_len = 0;
268

269
            setStatus(Backend::StatusWaitingForLock);
Lukas Appelhans's avatar
Lukas Appelhans committed
270

271
            if (fcntl(d->lockFileHandle->handle(), F_SETLK, &lockinfo) == -1) {
272
                akabeiDebug() << "Already locked, waiting for it...!";
273 274 275 276 277 278
                connect(&d->lockWatcher, SIGNAL(finished()), this, SLOT(__k__lockGranted()));//TODO: Check if this works!!!!!
                QFuture<void> lockF = QtConcurrent::run(d, &BackendPrivate::getLock);
                d->lockWatcher.setFuture(lockF);
            } else {
                d->__k__lockGranted();
            }
279
        }
280 281

        AkabeiLogLine::initialize( Akabei::Config::instance()->useSyslog(), Akabei::Config::instance()->logFile() );
282
        return;
283
    } else if (d->status == StatusOnTransaction) {        
284
        if (Config::instance()->needsPrivileges()) {
285
            akabeiDebug() << "We need privileges to remove lock";
286
            if (Akabei::Helpers::checkAuthorizationSync(QStringLiteral("org.chakraproject.akabeicorehelper.filesystem.lock"))) {
287
                akabeiDebug() << "Authorization granted";
Lukas Appelhans's avatar
Lukas Appelhans committed
288 289
                disconnect(d->iface, SIGNAL(lockGranted(qlonglong)), this, SLOT(__k__lockGranted(qlonglong)));
                
290
                QDBusMessage mes = d->iface->call(QStringLiteral("removeLock"), QCoreApplication::applicationPid());
291
            } else {
292
                akabeiDebug() << "Authorization not granted" << PolkitQt1::Authority::instance()->errorDetails();
293
                setStatus(Backend::StatusBroken);
Lisa's avatar
Lisa committed
294
                Akabei::ErrorQueue::instance()->appendError(Error(Error::PermissionError, PolkitQt1::Authority::instance()->errorDetails()));
Lukas Appelhans's avatar
Lukas Appelhans committed
295
                return;
296
            }
Lukas Appelhans's avatar
Lukas Appelhans committed
297 298 299 300 301 302 303
        } else {
            struct flock lockinfo;
            memset(&lockinfo, 0, sizeof(struct flock));
            lockinfo.l_type = F_UNLCK;
            lockinfo.l_whence = SEEK_SET;
            lockinfo.l_start = 0;
            lockinfo.l_len = 0;
304

Lukas Appelhans's avatar
Lukas Appelhans committed
305 306 307
            if (fcntl(d->lockFileHandle->handle(), F_SETLK, &lockinfo) == -1) {
                Akabei::ErrorQueue::instance()->appendError(Error(Error::FilesystemError, tr("Could not remove lock file properly!")));
                akabeiDebug() << "lock could not get released?!";
308
                return;
Lukas Appelhans's avatar
Lukas Appelhans committed
309
            }
310
        }
311
    }
312 313

    d->status = s;
314
    qDebug() << "Status changed to " << Akabei::Backend::statusToString(s);
315
    emit statusChanged(d->status);
316 317
}

318
void BackendPrivate::__k__initializationFinished()
319
{
320
    Q_Q(Backend);
321
    databases.clear();
322
    localDatabase = nullptr;
323

324
    // Let's see what we got
325
    QHash< QString, Database* > const& rethash = initializationWatcher->result();
326

327
    // Delete the watcher
328 329 330
    initializationWatcher->deleteLater();

    if (rethash.isEmpty()) {
331
        // Broken
332
        q->setStatus(Akabei::Backend::StatusBroken);
333 334 335
        return;
    }

336
    localDatabase = rethash[QStringLiteral("local")];
337
    if (!localDatabase) {
338
        // Something very wrong here
339
        q->setStatus(Akabei::Backend::StatusBroken);
340 341 342
        return;
    }

343
    // All the rest
Fabian Kosmale's avatar
Fabian Kosmale committed
344
    // TODO: check if this can be done with copy_if
345
    for (QHash< QString, Database* >::const_iterator i = rethash.constBegin(); i != rethash.constEnd(); ++i) {
346
        if (i.key() == QLatin1String("local")) {
347
            // Skip
348 349 350 351 352 353
            continue;
        }

        databases.append(i.value());
    }

354
    // The backend is now ready
355
    q->setStatus(Akabei::Backend::StatusReady);
356 357
}

358 359 360 361
void BackendPrivate::packageQueryFinished()
{
    Q_Q(Backend);

362
    QUuid uuid = QUuid(sender()->property("__Akabei_Query_Uuid").toString());
363 364 365
    if (!queryPackageFuturePool.contains(uuid)) {
        qWarning() << "No such UUID registered!";
        emit q->queryPackagesCompleted(uuid, QList<Package*>());
Dario Freddi's avatar
Dario Freddi committed
366
        sender()->deleteLater();
367 368 369
        return;
    }

370
    QFuture< QList< Package* > > const& future = queryPackageFuturePool[uuid]->future();
371
    sender()->deleteLater();
372

373
    QList< Package* > result;
Fabian Kosmale's avatar
Fabian Kosmale committed
374
    // TODO: why do we append one by one here?
Fabian Kosmale's avatar
Fabian Kosmale committed
375
    foreach (const QList< Package* >& res, future.results()) {
376
        result += res;
377
    }
378

379 380
    emit q->queryPackagesCompleted(uuid, result);
}
381

382 383 384
void BackendPrivate::orphanQueryFinished()
{
    Q_Q(Backend);
385

386
    QUuid uuid = QUuid( sender()->property("__Akabei_Query_Uuid").toString() );
387

388 389 390 391 392 393
    if (!orphanPackageFuturePool.contains(uuid)) {
        qWarning() << "No such UUID registered!";
        emit q->queryOrphansCompleted(uuid, QList< Package* >());
        sender()->deleteLater();
        return;
    }
394

395
    QFuture< QList< Package* > > const& future = orphanPackageFuturePool[uuid]->future();
Dario Freddi's avatar
Dario Freddi committed
396
    sender()->deleteLater();
397

398
    QList< Package* > result;
Fabian Kosmale's avatar
Fabian Kosmale committed
399
    foreach (const QList< Package* >& res, future.results()) {
400 401
        result += res;
    }
402
    result.removeAll(nullptr);
403

404
    emit q->queryOrphansCompleted(uuid, result);
405 406 407 408 409 410
}

void BackendPrivate::groupQueryFinished()
{
    Q_Q(Backend);

411
    QUuid uuid = QUuid(sender()->property("__Akabei_Query_Uuid").toString());
412 413
    if (!queryGroupFuturePool.contains(uuid)) {
        qWarning() << "No such UUID registered!";
Lukas Appelhans's avatar
Lukas Appelhans committed
414
        emit q->queryGroupsCompleted(uuid, QList<Group*>());
415 416 417
        return;
    }

418
    QFuture< QList< Group* > > const& future = queryGroupFuturePool[uuid]->future();
Lisa's avatar
Lisa committed
419
    GroupPool pool;
420

421 422 423 424
    /*
     * Since the same groups are repeated across different databases,
     * avoid adding duplicates to the final list.
     */
Lisa's avatar
Lisa committed
425
    
Fabian Kosmale's avatar
Fabian Kosmale committed
426
    foreach (const QList< Group* >& groupList, future.results()) {
Lisa's avatar
Lisa committed
427 428
        foreach (Group* g, groupList) {
            pool.group( g->name() );
429
        }
430
    }
431

Lisa's avatar
Lisa committed
432
    emit q->queryGroupsCompleted(uuid, pool.allGroups());
433 434
}

Lukas Appelhans's avatar
Lukas Appelhans committed
435
void BackendPrivate::__k__lockGranted(qlonglong pid)
436 437
{
    Q_Q(Backend);
438 439 440 441
    qDebug() << "LOCK GRANTED TO" << pid << "We got" << QCoreApplication::applicationPid();
    if (pid != QCoreApplication::applicationPid())
        return;
    q->setStatus(Backend::StatusOnTransaction);
Lukas Appelhans's avatar
Lukas Appelhans committed
442
    
443 444
    if (callbackObject && callbackSlot)
        QMetaObject::invokeMethod(callbackObject, callbackSlot);
445 446 447 448 449 450 451 452 453 454 455 456
}

void BackendPrivate::getLock()
{
    struct flock lockinfo;
    memset(&lockinfo, 0, sizeof(struct flock));

    lockinfo.l_type = F_WRLCK;
    lockinfo.l_whence = SEEK_SET;
    lockinfo.l_start = 0;
    lockinfo.l_len = 0;

457
    fcntl(lockFileHandle->handle(), F_SETLKW, &lockinfo);
458 459
}

460 461 462
///////////////////////////

GroupPool::GroupPool()
Lukas Appelhans's avatar
Lukas Appelhans committed
463
  : m_mutex(new QMutex())
464 465 466 467 468
{
}

GroupPool::~GroupPool()
{
Lukas Appelhans's avatar
Lukas Appelhans committed
469
    delete m_mutex;
470 471 472 473
}

Group *GroupPool::group(const QString &name)
{
Lukas Appelhans's avatar
Lukas Appelhans committed
474
    QMutexLocker locker(m_mutex);
475 476 477 478 479 480 481
    if (!m_pool.contains(name)) {
        m_pool.insert(name, new Group(name));
    }

    return m_pool[name];
}

Lukas Appelhans's avatar
Lukas Appelhans committed
482
bool GroupPool::contains(const QString &name) const
483
{
Lukas Appelhans's avatar
Lukas Appelhans committed
484
    QMutexLocker locker(m_mutex);
485 486 487
    return m_pool.contains(name);
}

Lisa's avatar
Lisa committed
488 489 490 491 492 493
QList< Group* > GroupPool::allGroups() const
{
    QMutexLocker locker(m_mutex);
    return m_pool.values();
}

494 495 496 497 498
///////////////////////////

class BackendHelper
{
public:
Daniele Cocca's avatar
Daniele Cocca committed
499
    BackendHelper()
500
      : q( nullptr )
Daniele Cocca's avatar
Daniele Cocca committed
501 502 503 504
    {
    }

    virtual ~BackendHelper() {
505 506
        delete q;
    }
Daniele Cocca's avatar
Daniele Cocca committed
507 508

    Backend* q;
509 510 511 512
};

Q_GLOBAL_STATIC(BackendHelper, s_globalBackend)

Daniele Cocca's avatar
Daniele Cocca committed
513
Backend* Backend::instance()
514
{
515 516 517
    if (!s_globalBackend()->q) {
        new Backend;
    }
518 519 520 521 522 523 524 525 526 527

    return s_globalBackend()->q;
}

Backend::Backend(QObject* parent)
    : QObject(parent)
    , d_ptr(new BackendPrivate(this))
{
    Q_ASSERT(!s_globalBackend()->q);
    s_globalBackend()->q = this;
Dario Freddi's avatar
Dario Freddi committed
528 529 530
    qRegisterMetaType<QUuid>();
    qRegisterMetaType< QList< Akabei::Package* > >();
    qRegisterMetaType< QList< Akabei::Group* > >();
Lukas Appelhans's avatar
Lukas Appelhans committed
531 532
    qRegisterMetaType< Akabei::Error::List >();
    qRegisterMetaType< Akabei::Backend::Status >();
533
    qDBusRegisterMetaType< QList< QVariantMap > >();
534

Luca Giambonini's avatar
Luca Giambonini committed
535
    qInstallMessageHandler(akabeiDebug);
Lukas Appelhans's avatar
Lukas Appelhans committed
536

537
    // We need at least 2 threads in QThreadPool to make everything work correctly.
538
    if (QThreadPool::globalInstance()->maxThreadCount() < 2) {
539
        akabeiDebug() << "Set max thread count to 2";
540 541
        QThreadPool::globalInstance()->setMaxThreadCount(2);
    }
542 543 544 545 546 547
}

Backend::~Backend()
{
}

Lukas Appelhans's avatar
Lukas Appelhans committed
548 549
void Backend::setLocale(const QString& locale)
{
550
    auto  translator = new QTranslator(this);
551
    translator->load("akabeicore_" + locale + ".qm", QStringLiteral("/usr/share/akabeicore/translations/"));//FIXME: Use translations from install dir
Lukas Appelhans's avatar
Lukas Appelhans committed
552 553 554
    QCoreApplication::instance()->installTranslator(translator);
}

555 556 557
void Backend::initialize()
{
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
558
    QWriteLocker locker(d->mutex);
Lukas Appelhans's avatar
Lukas Appelhans committed
559
    qDebug() << "We are running in threadSSSSSSSSSSSSSSSSSSSSSSSSS" << QThread::currentThread();
560

561
    setStatus(Backend::StatusInitializing);
562

563 564
    d->initializationWatcher = new QFutureWatcher< QHash< QString, Database* > >(this);
    QObject::connect(d->initializationWatcher, SIGNAL(finished()), this, SLOT(__k__initializationFinished()));
565

566 567
    QFuture< QHash< QString, Database* > > future = QtConcurrent::run(d, &BackendPrivate::performInitialization);
    d->initializationWatcher->setFuture(future);
568 569
}

570 571 572 573
void Backend::deInit()
{
    setStatus(Backend::StatusBare); /* remove the lock */
}
574

575 576
QList< Database* > Backend::databases()
{
577
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
578
    QReadLocker locker(d->mutex);
579
    return d->databases;
580 581 582 583 584
}

Database* Backend::localDatabase()
{
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
585
    QReadLocker locker(d->mutex);
586 587 588 589 590 591
    return d->localDatabase;
}

Backend::Status Backend::status() const
{
    Q_D(const Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
592
    QReadLocker locker(d->mutex);
593 594 595
    return d->status;
}

596 597 598 599 600 601 602
QString Backend::statusToString(Backend::Status status)
    {
    int index = metaObject()->indexOfEnumerator("Status");
    QMetaEnum metaEnum = metaObject()->enumerator(index);
    return metaEnum.valueToKey(status);
    }

603 604
QUuid Backend::groups()
{
Lisa's avatar
Lisa committed
605
    QString sql = Queries::allGroups();
606 607 608 609 610
    return queryGroups(sql);
}

QUuid Backend::packages()
{
Lisa's avatar
Lisa committed
611
    QString sql = Queries::allPackages();
612 613 614 615 616 617
    return queryPackages(sql);
}

QUuid Backend::queryGroups(const QString& sql)
{
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
618
    QWriteLocker locker(d->mutex);
619 620 621 622 623 624

    QUuid uuid = QUuid::createUuid();

    QList<Database*> allDbs;
    allDbs << d->localDatabase << d->databases;

625
    auto watcher = new QFutureWatcher< QList< Group* > >();
626
    watcher->setProperty("__Akabei_Query_Uuid", uuid.toString());
627
    connect(watcher, &QFutureWatcherBase::finished, d, &BackendPrivate::groupQueryFinished);
628 629 630 631
    d->queryGroupFuturePool.insert(uuid, watcher);

    QFuture< QList< Group* > > future = QtConcurrent::mapped(allDbs, ConcurrentGroupQuery(sql));
    watcher->setFuture(future);
632 633

    return uuid;
634 635 636 637 638
}

QUuid Backend::queryPackages(const QString& sql)
{
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
639
    QWriteLocker locker(d->mutex);
640 641 642 643 644 645

    QUuid uuid = QUuid::createUuid();

    QList<Database*> allDbs;
    allDbs << d->localDatabase << d->databases;

646
    auto  watcher = new QFutureWatcher< QList< Package* > >();
647
    watcher->setProperty("__Akabei_Query_Uuid", uuid.toString());
648
    connect(watcher, &QFutureWatcherBase::finished, d, &BackendPrivate::packageQueryFinished);
649 650 651 652
    d->queryPackageFuturePool.insert(uuid, watcher);

    QFuture< QList< Package* > > future = QtConcurrent::mapped(allDbs, ConcurrentPackageQuery(sql));
    watcher->setFuture(future);
653 654

    return uuid;
655 656
}

657 658 659
QUuid Backend::orphanPackages()
{
    Q_D(Backend);
660

661
    QUuid uuid = QUuid::createUuid();
662

663
    auto  watcher = new QFutureWatcher< QList< Package* > >();
664
    watcher->setProperty("__Akabei_Query_Uuid", uuid.toString());
665
    connect(watcher, &QFutureWatcherBase::finished, d, &BackendPrivate::orphanQueryFinished);
666
    d->orphanPackageFuturePool.insert(uuid, watcher);
667

668 669 670 671 672
    QFuture< QList< Package* > > future = QtConcurrent::run( ConcurrentOrphanQuery() );
    watcher->setFuture(future);
    return uuid;
}

673 674
QUuid Backend::searchGroups(const QString& token)
{
675
    return queryGroups( Queries::selectPackagesNameOrDescription("%" + token + "%", QStringLiteral("LIKE")) );
676
}
Daniele Cocca's avatar
Daniele Cocca committed
677

Lukas Appelhans's avatar
Lukas Appelhans committed
678 679
//TODO: Maybe abstract the query generation, because the same is used in database as well
QUuid Backend::searchPackages(const QString& token, SearchType type)
680
{
Lukas Appelhans's avatar
Lukas Appelhans committed
681
    QString sql;
682

Lukas Appelhans's avatar
Lukas Appelhans committed
683 684
    switch (type) {
        case SearchNameLike:
685
            sql = Queries::selectPackages(QStringLiteral("name"), QStringLiteral("LIKE"), "%" + token + "%");
Lukas Appelhans's avatar
Lukas Appelhans committed
686 687
            break;
        case SearchNameEqual:
688
            sql = Queries::selectPackages(QStringLiteral("name"), QStringLiteral("="), token);
Lukas Appelhans's avatar
Lukas Appelhans committed
689 690
            break;
        case SearchDescriptionLike:
691
            sql = Queries::selectPackages(QStringLiteral("description"), QStringLiteral("LIKE"), "%" + token + "%");
Lukas Appelhans's avatar
Lukas Appelhans committed
692 693
            break;
        case SearchDescriptionEqual:
694
            sql = Queries::selectPackages(QStringLiteral("description"), QStringLiteral("="), token);
Lukas Appelhans's avatar
Lukas Appelhans committed
695 696
            break;
        case SearchNameAndDescriptionLike:
697
            sql = Queries::selectPackagesNameOrDescription("%" + token + "%", QStringLiteral("LIKE"));
Lukas Appelhans's avatar
Lukas Appelhans committed
698 699
            break;
        case SearchNameAndDescriptionEqual:
700
            sql = Queries::selectPackagesNameOrDescription(token, QStringLiteral("="));
Lukas Appelhans's avatar
Lukas Appelhans committed
701 702
            break;
    }
703 704 705
    return queryPackages(sql);
}

Lukas Appelhans's avatar
Lukas Appelhans committed
706
QUuid Backend::searchPackages(const QStringList &pkgs, SearchType type)
Lisa's avatar
Lisa committed
707
{
708
    QString sql = QStringLiteral("SELECT * FROM packages WHERE ");
709

Lukas Appelhans's avatar
Lukas Appelhans committed
710
    for (QStringList::const_iterator it = pkgs.begin(); it < pkgs.end(); it++) {
Lukas Appelhans's avatar
Lukas Appelhans committed
711 712 713 714 715 716 717 718 719 720 721
        switch (type) {
            case SearchNameEqual:
                sql += "name=\"" + (*it) + "\" ";
                break;
            case SearchNameLike:
                sql += "name LIKE \"%" + (*it) + "%\" ";
                break;
            case SearchDescriptionEqual:
                sql += "description=\"" + (*it) + "\" ";
                break;
            case SearchDescriptionLike:
Lisa's avatar
Lisa committed
722
                sql += "description LIKE \"%" + (*it) + "%\" ";
Lukas Appelhans's avatar
Lukas Appelhans committed
723 724 725 726 727 728 729
                break;
            case SearchNameAndDescriptionEqual:
                sql += "name=\"" + (*it) + "\" OR description=\"" + (*it) + "\" ";
                break;
            case SearchNameAndDescriptionLike:
                sql += "name LIKE \"%" + (*it) + "%\" OR description LIKE \"%" + (*it) + "%\" ";
        }
730

Lukas Appelhans's avatar
Lukas Appelhans committed
731
        if ((it + 1) != pkgs.end()) {
732
            sql += QLatin1String("OR ");
Lisa's avatar
Lisa committed
733 734
        }
    }
735

Lisa's avatar
Lisa committed
736 737 738
    return queryPackages(sql);
}

739 740 741
OperationRunner* Backend::operationRunner()
{
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
742
    QReadLocker locker(d->mutex);
743 744 745
    return d->runner;
}

746 747 748
Package* Backend::loadPackageFromFile(const QString& path)
{
    Q_D(Backend);
749

750
    if (!QFile::exists(path)) {
751
        return nullptr;
752
    }
753

754
    std::unique_ptr<Package> retpackage(new Package);
Lukas Appelhans's avatar
Lukas Appelhans committed
755
    retpackage->setPathToArchive(path);
756

757
    try {
758
        ArchiveHandler pkg(path);
759 760 761 762
        /* If full is false, only read through the archive until we find our needed
         * metadata. If it is true, read through the entire archive, which serves
         * as a verfication of integrity and allows us to create the filelist. */

763
        foreach (QString const& line, pkg.readTextFile(".PKGINFO").split('\n')) {
764 765 766
            if (!line.contains('=') || line.startsWith('#')) {
                // Skip this line, not interesting
                continue;
767
            }
768 769 770 771 772 773 774 775 776 777 778 779 780

            // Separate the two strings
            QStringList splitted = line.split('=');
            QString key = splitted.first();
            // There might be an equal, so
            splitted.removeAt(0);
            QString value = splitted.join(QChar('='));

            // Just remove all spaces ignorantly here
            key = key.remove(' ');
            // Recheck if it's commented over
            if (key.startsWith('#')) {
                // Pass by
Lisa's avatar
Lisa committed
781
                continue;
782 783 784
            }

            // Strip whitespaces here instead
Lisa's avatar
Lisa committed
785
            value = value.simplified();
786 787

            // Ok, now let's analyze
788
            if (key == QLatin1String("pkgname")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
789
                retpackage->d_func()->setName(value);
790
            } else if (key == QLatin1String("pkgver")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
791
                retpackage->d_func()->setVersion(Package::Version(value.toUtf8()));
792
            } else if (key == QLatin1String("pkgdesc")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
793
                retpackage->d_func()->setDescription(value);
794
            } else if (key == QLatin1String("group")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
795
                retpackage->d_func()->addGroup(d->groupPool->group(value));
796
            } else if (key == QLatin1String("url")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
797
                retpackage->d_func()->setUrl(QUrl(value));
798
            } else if (key == QLatin1String("license")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
799
                retpackage->d_func()->addLicense(value);
800
            } else if (key == QLatin1String("builddate")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
801
                retpackage->d_func()->setBuildDate(QDateTime::fromTime_t(value.toUInt()));
802
            } else if (key == QLatin1String("packager")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
803
                retpackage->d_func()->setPackager(value);
804
            } else if (key == QLatin1String("arch")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
805
                retpackage->d_func()->setArch(value);
806
            } else if (key == QLatin1String("size")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
807
                retpackage->d_func()->setSize(value.toInt());
808
            } else if (key == QLatin1String("depend")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
809
                retpackage->d_func()->addDependency(value);
810
            } else if (key == QLatin1String("makedepend")) {
Lisa's avatar
Lisa committed
811
                retpackage->d_func()->addMakeDependency(value);
812
            } else if (key == QLatin1String("optdepend")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
813
                retpackage->d_func()->addOptDependency(value);
814
            } else if (key == QLatin1String("conflict")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
815
                retpackage->d_func()->addConflict(value);
816
            } else if (key == QLatin1String("replaces")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
817
                retpackage->d_func()->addReplaces(value);
818
            } else if (key == QLatin1String("provides")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
819
                retpackage->d_func()->addProvider(value);
820
            } else if (key == QLatin1String("hook")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
821
                retpackage->d_func()->addHook(value);
822
            } else if (key == QLatin1String("backup")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
823

824 825 826 827 828
                /*
                 * TODO: make the application know why the md5 function failed
                 */
                QString md5 = pkg.md5(value);
                if (!md5.isEmpty()) {
Lukas Appelhans's avatar
Lukas Appelhans committed
829
                    retpackage->d_func()->addBackup(value, md5);
830
                }
831
            } else if (key == QLatin1String("ultimateowner")) {
832
                retpackage->d_func()->addUltimatelyOwnedFile(value);
833
            } else if (key == QLatin1String("makepkgopt")) {
834
    //                     retpackage->d_func()->arch = value;
835
            } else if (key == QLatin1String("screenshot")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
836
                retpackage->d_func()->setScreenshot(QUrl(value));
837
            } else if (key == QLatin1String("mimetype")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
838
                retpackage->d_func()->addMimetype(value);
839
            } else if (key == QLatin1String("hook")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
840
                retpackage->d_func()->addHook(value);
841
            } else if (key == QLatin1String("epoch")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
842
                retpackage->d_func()->setEpoch(value.toInt());
843
            } else if (key == QLatin1String("gitfolder")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
844
                retpackage->d_func()->setGitFolder(value);
845
            } else if (key == QLatin1String("gitrepository")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
846
                retpackage->d_func()->setGitRepo(value);
847
            } else if (key == QLatin1String("gitbranch")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
848
                retpackage->d_func()->setGitBranch(value);
849 850
            } else if (key == QLatin1String("gitrepo")) { //This is legacy support
                QStringList split = value.split(QStringLiteral("-"));
851
                if (!split.isEmpty()) {
Lukas Appelhans's avatar
Lukas Appelhans committed
852
                    retpackage->d_func()->setGitRepo(split.takeFirst());
853
                    retpackage->d_func()->setGitBranch(split.isEmpty() ? QStringLiteral("master") : split.first());
854
                }
855 856
            }
        }
857

858 859
        if (pkg.getEntries().contains(QStringLiteral(".INSTALL"))) {
            retpackage->d_func()->setScriptlet(pkg.readTextFile(QStringLiteral(".INSTALL")));
860
        }
Lukas Appelhans's avatar
Lukas Appelhans committed
861

Lukas Appelhans's avatar
Lukas Appelhans committed
862 863 864 865
        int isize = 0;
        foreach (QString const& entry, pkg.getEntries()) {
            retpackage->d_func()->addFile(entry);
            isize += pkg.getEntrySize(entry);
866
        }
Lukas Appelhans's avatar
Lukas Appelhans committed
867
        retpackage->d_func()->setInstalledSize(isize);