akabeibackend.cpp 30.9 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

35 36
#include <libarchive++/archivehandler.h>
#include <akabeihook.h>
37
#include "akabeilog.h"
38
#include <fcntl.h>
39

40 41
#include <polkit-qt-1/polkitqt1-authority.h>

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

47 48 49
namespace Akabei
{

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

    typedef QList< Package* > result_type;

60 61 62 63
    QList< Package* > operator()(Database *db)
    {
        return db->queryPackages(m_sql);
    }
64 65 66 67

    QString m_sql;
};

68 69 70 71
struct ConcurrentOrphanQuery
{
    ConcurrentOrphanQuery()
    {}
72

73
    typedef QList< Package* > result_type;
74

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

81 82 83
        foreach (Package* pkg, Backend::instance()->localDatabase()->packages()) {
            allPackages[ pkg->name() ] = pkg;
        }
84

85
        foreach (Package* pkg, allPackages.values()) {
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
            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]);
                    }
                }
105 106
            }
        }
107

108 109 110 111
        /* I do this separately to avoid issues in the foreach loop */
        foreach (const QString& p, toBeRemoved) {
            allPackages.remove(p);
        }
112

113 114 115 116
        return allPackages.values();
    }
};

117 118
struct ConcurrentGroupQuery
{
Daniele Cocca's avatar
Daniele Cocca committed
119 120 121 122
    ConcurrentGroupQuery(QString const& sql)
      : m_sql( sql )
    {
    }
123 124 125

    typedef QList< Group* > result_type;

126 127 128 129
    QList< Group* > operator()(Database *db)
    {
        return db->queryGroups(m_sql);
    }
130 131 132 133

    QString m_sql;
};

134
QHash<QString, Database*> BackendPrivate::performInitialization()
135
{
136 137
    QHash<QString, Database*> rethash;

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

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

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

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

181
    // Ok, add it to our rethash
182 183
    rethash.insert("local", localDb);

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

188 189
        if (database->isValid()) {
            // Ok, add it to our rethash
190
            rethash.insert(db, database);
191
        }
192 193
    }

194
    // Return it straight
195
    return rethash;
196
}
197

198 199
void BackendPrivate::addDatabase(const QString &name)
{
Daniele Cocca's avatar
Daniele Cocca committed
200
    if (name == "local") {
201
        return;
Daniele Cocca's avatar
Daniele Cocca committed
202
    }
203

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

206
    if (database->isValid()) {
207
        databases.append(database);
208
    }
209 210
}

211
void Backend::setStatus(Backend::Status s, QObject * cO, const char * cS)
212
{
213 214 215 216 217 218 219 220 221 222 223
    Q_D(Backend);

    qDebug() << "CALLED WITH" << s << d->status;
    if (s == d->status)
        return;

    qDebug() << "Now let's call this";

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

Lukas Appelhans's avatar
Lukas Appelhans committed
236 237 238
                connect(d->iface, SIGNAL(lockGranted(qlonglong)), SLOT(__k__lockGranted(qlonglong)));
                
                QDBusMessage mes = d->iface->call("getLock", QCoreApplication::applicationPid());
239 240
            } else {
                qDebug() << "Authorization not granted" << PolkitQt1::Authority::instance()->errorDetails();
Lisa's avatar
Lisa committed
241
                Akabei::ErrorQueue::instance()->appendError(Error(Error::PermissionError, tr("An authorization error occurred: %1").arg(PolkitQt1::Authority::instance()->errorDetails())));
242 243
                setStatus(Backend::StatusBroken);
            }
244 245
            
            AkabeiLogLine::initialize( Akabei::Config::instance()->useSyslog(), Akabei::Config::instance()->logFile() );
Lisa's avatar
Lisa committed
246
            return;
247 248
        } else {
            qDebug() << "WE HERE";
249

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

253
                if (!d->lockFileHandle->open(QFile::ReadWrite)) {
Lisa's avatar
Lisa committed
254
                    ErrorQueue::instance()->appendError( Error(Error::PermissionError, "Could not open lock file for writing") );
255 256 257
                    setStatus(Backend::StatusBroken);
                    return;
                }
258 259
            }

260 261 262 263 264 265
            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;
266

267
            setStatus(Backend::StatusWaitingForLock);
Lukas Appelhans's avatar
Lukas Appelhans committed
268

269 270 271 272 273 274 275 276
            if (fcntl(d->lockFileHandle->handle(), F_SETLK, &lockinfo) == -1) {
                akabeiDebug() << "No lock for us!";
                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();
            }
277
        }
278 279

        AkabeiLogLine::initialize( Akabei::Config::instance()->useSyslog(), Akabei::Config::instance()->logFile() );
280
        return;
281
    } else if (d->status == StatusOnTransaction) {        
282 283 284 285
        if (Config::instance()->needsPrivileges()) {
            qDebug() << "We need privileges to remove lock";
            if (Akabei::Helpers::checkAuthorizationSync("org.chakraproject.akabeicorehelper.filesystem.lock")) {
                qDebug() << "Authorization granted";
Lukas Appelhans's avatar
Lukas Appelhans committed
286 287 288
                disconnect(d->iface, SIGNAL(lockGranted(qlonglong)), this, SLOT(__k__lockGranted(qlonglong)));
                
                QDBusMessage mes = d->iface->call("removeLock", QCoreApplication::applicationPid());
289 290 291
            } else {
                qDebug() << "Authorization not granted" << PolkitQt1::Authority::instance()->errorDetails();
                setStatus(Backend::StatusBroken);
Lisa's avatar
Lisa committed
292
                Akabei::ErrorQueue::instance()->appendError(Error(Error::PermissionError, PolkitQt1::Authority::instance()->errorDetails()));
Lukas Appelhans's avatar
Lukas Appelhans committed
293
                return;
294
            }
Lukas Appelhans's avatar
Lukas Appelhans committed
295 296 297 298 299 300 301
        } 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;
302

Lukas Appelhans's avatar
Lukas Appelhans committed
303 304 305
            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?!";
306
                return;
Lukas Appelhans's avatar
Lukas Appelhans committed
307
            }
308
        }
309
    }
310 311 312

    d->status = s;
    emit statusChanged(d->status);
313 314
}

315
void BackendPrivate::__k__initializationFinished()
316
{
317
    Q_Q(Backend);
318 319 320
    databases.clear();
    localDatabase = 0;

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

324
    // Delete the watcher
325 326 327
    initializationWatcher->deleteLater();

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

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

340
    // All the rest
341 342
    for (QHash< QString, Database* >::const_iterator i = rethash.constBegin(); i != rethash.constEnd(); ++i) {
        if (i.key() == "local") {
343
            // Skip
344 345 346 347 348 349
            continue;
        }

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

350
    // The backend is now ready
351
    q->setStatus(Akabei::Backend::StatusReady);
352 353
}

354 355 356 357
void BackendPrivate::packageQueryFinished()
{
    Q_Q(Backend);

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

366
    QFuture< QList< Package* > > const& future = queryPackageFuturePool[uuid]->future();
367
    sender()->deleteLater();
368

369 370 371
    QList< Package* > result;
    foreach (QList< Package* > res, future.results()) {
        result += res;
372
    }
373

374 375
    emit q->queryPackagesCompleted(uuid, result);
}
376

377 378 379
void BackendPrivate::orphanQueryFinished()
{
    Q_Q(Backend);
380

381
    QUuid uuid = QUuid( sender()->property("__Akabei_Query_Uuid").toString() );
382

383 384 385 386 387 388
    if (!orphanPackageFuturePool.contains(uuid)) {
        qWarning() << "No such UUID registered!";
        emit q->queryOrphansCompleted(uuid, QList< Package* >());
        sender()->deleteLater();
        return;
    }
389

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

393 394 395 396
    QList< Package* > result;
    foreach (QList< Package* > res, future.results()) {
        result += res;
    }
397 398
    result.removeAll(0);

399
    emit q->queryOrphansCompleted(uuid, result);
400 401 402 403 404 405
}

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

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

413
    QFuture< QList< Group* > > const& future = queryGroupFuturePool[uuid]->future();
414
    QList< Group* > result;
415

416 417 418 419
    /*
     * Since the same groups are repeated across different databases,
     * avoid adding duplicates to the final list.
     */
420
    foreach (QList< Group* > res, future.results()) {
421 422 423 424 425
        foreach (Group* group, res) {
            if (!result.contains(group)) {
                result << group;
            }
        }
426
    }
427

428
    emit q->queryGroupsCompleted(uuid, result);
429 430
}

Lukas Appelhans's avatar
Lukas Appelhans committed
431
void BackendPrivate::__k__lockGranted(qlonglong pid)
432 433
{
    Q_Q(Backend);
434 435 436 437
    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
438
    
439 440
    if (callbackObject && callbackSlot)
        QMetaObject::invokeMethod(callbackObject, callbackSlot);
441 442 443 444 445 446 447 448 449 450 451 452
}

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;

453
    fcntl(lockFileHandle->handle(), F_SETLKW, &lockinfo);
454 455
}

456 457 458
///////////////////////////

GroupPool::GroupPool()
Lukas Appelhans's avatar
Lukas Appelhans committed
459
  : m_mutex(new QMutex())
460 461 462 463 464
{
}

GroupPool::~GroupPool()
{
Lukas Appelhans's avatar
Lukas Appelhans committed
465
    delete m_mutex;
466 467 468 469
}

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

    return m_pool[name];
}

Lukas Appelhans's avatar
Lukas Appelhans committed
478
bool GroupPool::contains(const QString &name) const
479
{
Lukas Appelhans's avatar
Lukas Appelhans committed
480
    QMutexLocker locker(m_mutex);
481 482 483
    return m_pool.contains(name);
}

484 485 486 487 488
///////////////////////////

class BackendHelper
{
public:
Daniele Cocca's avatar
Daniele Cocca committed
489 490 491 492 493 494
    BackendHelper()
      : q( 0 )
    {
    }

    virtual ~BackendHelper() {
495 496
        delete q;
    }
Daniele Cocca's avatar
Daniele Cocca committed
497 498

    Backend* q;
499 500 501 502
};

Q_GLOBAL_STATIC(BackendHelper, s_globalBackend)

Daniele Cocca's avatar
Daniele Cocca committed
503
Backend* Backend::instance()
504
{
505 506 507
    if (!s_globalBackend()->q) {
        new Backend;
    }
508 509 510 511 512 513 514 515 516 517

    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
518 519 520
    qRegisterMetaType<QUuid>();
    qRegisterMetaType< QList< Akabei::Package* > >();
    qRegisterMetaType< QList< Akabei::Group* > >();
Lukas Appelhans's avatar
Lukas Appelhans committed
521 522
    qRegisterMetaType< Akabei::Error::List >();
    qRegisterMetaType< Akabei::Backend::Status >();
523
    qDBusRegisterMetaType< QList< QVariantMap > >();
524

525
    qInstallMsgHandler(akabeiDebug);
Lukas Appelhans's avatar
Lukas Appelhans committed
526

527
    // We need at least 2 threads in QThreadPool to make everything work correctly.
528
    if (QThreadPool::globalInstance()->maxThreadCount() < 2) {
529
        akabeiDebug() << "Set max thread count to 2";
530 531
        QThreadPool::globalInstance()->setMaxThreadCount(2);
    }
532 533 534 535 536 537
}

Backend::~Backend()
{
}

Lukas Appelhans's avatar
Lukas Appelhans committed
538 539 540 541 542 543 544
void Backend::setLocale(const QString& locale)
{
    QTranslator * translator = new QTranslator(this);
    translator->load("akabeicore_" + locale + ".qm", "/usr/share/akabeicore/translations/");//FIXME: Use translations from install dir
    QCoreApplication::instance()->installTranslator(translator);
}

545 546 547
void Backend::initialize()
{
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
548
    QWriteLocker locker(d->mutex);
Lukas Appelhans's avatar
Lukas Appelhans committed
549
    qDebug() << "We are running in threadSSSSSSSSSSSSSSSSSSSSSSSSS" << QThread::currentThread();
550

551
    setStatus(Backend::StatusInitializing);
552

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

556 557
    QFuture< QHash< QString, Database* > > future = QtConcurrent::run(d, &BackendPrivate::performInitialization);
    d->initializationWatcher->setFuture(future);
558 559
}

560 561 562 563
void Backend::deInit()
{
    setStatus(Backend::StatusBare); /* remove the lock */
}
564

565 566
QList< Database* > Backend::databases()
{
567
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
568
    QReadLocker locker(d->mutex);
569
    return d->databases;
570 571 572 573 574
}

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

Backend::Status Backend::status() const
{
    Q_D(const Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
582
    QReadLocker locker(d->mutex);
583 584 585
    return d->status;
}

586 587
QUuid Backend::groups()
{
Lisa's avatar
Lisa committed
588
    QString sql = Queries::allGroups();
589 590 591 592 593
    return queryGroups(sql);
}

QUuid Backend::packages()
{
Lisa's avatar
Lisa committed
594
    QString sql = Queries::allPackages();
595 596 597 598 599 600
    return queryPackages(sql);
}

QUuid Backend::queryGroups(const QString& sql)
{
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
601
    QWriteLocker locker(d->mutex);
602 603 604 605 606 607

    QUuid uuid = QUuid::createUuid();

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

Dario Freddi's avatar
Dario Freddi committed
608
    QFutureWatcher< QList< Group* > > *watcher = new QFutureWatcher< QList< Group* > >();
609
    watcher->setProperty("__Akabei_Query_Uuid", uuid.toString());
610 611 612 613 614
    connect(watcher, SIGNAL(finished()), d, SLOT(groupQueryFinished()));
    d->queryGroupFuturePool.insert(uuid, watcher);

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

    return uuid;
617 618 619 620 621
}

QUuid Backend::queryPackages(const QString& sql)
{
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
622
    QWriteLocker locker(d->mutex);
623 624 625 626 627 628

    QUuid uuid = QUuid::createUuid();

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

Daniele Cocca's avatar
Daniele Cocca committed
629
    QFutureWatcher< QList< Package* > >* watcher = new QFutureWatcher< QList< Package* > >();
630
    watcher->setProperty("__Akabei_Query_Uuid", uuid.toString());
631 632 633 634 635
    connect(watcher, SIGNAL(finished()), d, SLOT(packageQueryFinished()));
    d->queryPackageFuturePool.insert(uuid, watcher);

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

    return uuid;
638 639
}

640 641 642
QUuid Backend::orphanPackages()
{
    Q_D(Backend);
643

644
    QUuid uuid = QUuid::createUuid();
645

646 647 648 649
    QFutureWatcher< QList< Package* > >* watcher = new QFutureWatcher< QList< Package* > >();
    watcher->setProperty("__Akabei_Query_Uuid", uuid.toString());
    connect(watcher, SIGNAL(finished()), d, SLOT(orphanQueryFinished()));
    d->orphanPackageFuturePool.insert(uuid, watcher);
650

651 652 653 654 655
    QFuture< QList< Package* > > future = QtConcurrent::run( ConcurrentOrphanQuery() );
    watcher->setFuture(future);
    return uuid;
}

656 657
QUuid Backend::searchGroups(const QString& token)
{
Lisa's avatar
Lisa committed
658
    return queryGroups( Queries::selectPackagesNameOrDescription("%" + token + "%", "LIKE") );
659
}
Daniele Cocca's avatar
Daniele Cocca committed
660

Lukas Appelhans's avatar
Lukas Appelhans committed
661 662
//TODO: Maybe abstract the query generation, because the same is used in database as well
QUuid Backend::searchPackages(const QString& token, SearchType type)
663
{
Lukas Appelhans's avatar
Lukas Appelhans committed
664
    QString sql;
665

Lukas Appelhans's avatar
Lukas Appelhans committed
666 667
    switch (type) {
        case SearchNameLike:
Lisa's avatar
Lisa committed
668
            sql = Queries::selectPackages("name", "LIKE", "%" + token + "%");
Lukas Appelhans's avatar
Lukas Appelhans committed
669 670
            break;
        case SearchNameEqual:
671
            sql = Queries::selectPackages("name", "=", token);
Lukas Appelhans's avatar
Lukas Appelhans committed
672 673
            break;
        case SearchDescriptionLike:
Lisa's avatar
Lisa committed
674
            sql = Queries::selectPackages("description", "LIKE", "%" + token + "%");
Lukas Appelhans's avatar
Lukas Appelhans committed
675 676
            break;
        case SearchDescriptionEqual:
677
            sql = Queries::selectPackages("description", "=", token);
Lukas Appelhans's avatar
Lukas Appelhans committed
678 679
            break;
        case SearchNameAndDescriptionLike:
Lisa's avatar
Lisa committed
680
            sql = Queries::selectPackagesNameOrDescription("%" + token + "%", "LIKE");
Lukas Appelhans's avatar
Lukas Appelhans committed
681 682
            break;
        case SearchNameAndDescriptionEqual:
683
            sql = Queries::selectPackagesNameOrDescription(token, "=");
Lukas Appelhans's avatar
Lukas Appelhans committed
684 685
            break;
    }
686 687 688
    return queryPackages(sql);
}

Lukas Appelhans's avatar
Lukas Appelhans committed
689
QUuid Backend::searchPackages(const QStringList &pkgs, SearchType type)
Lisa's avatar
Lisa committed
690 691
{
    QString sql = "SELECT * FROM packages WHERE ";
692

Lukas Appelhans's avatar
Lukas Appelhans committed
693
    for (QStringList::const_iterator it = pkgs.begin(); it < pkgs.end(); it++) {
Lukas Appelhans's avatar
Lukas Appelhans committed
694 695 696 697 698 699 700 701 702 703 704
        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
705
                sql += "description LIKE \"%" + (*it) + "%\" ";
Lukas Appelhans's avatar
Lukas Appelhans committed
706 707 708 709 710 711 712
                break;
            case SearchNameAndDescriptionEqual:
                sql += "name=\"" + (*it) + "\" OR description=\"" + (*it) + "\" ";
                break;
            case SearchNameAndDescriptionLike:
                sql += "name LIKE \"%" + (*it) + "%\" OR description LIKE \"%" + (*it) + "%\" ";
        }
713

Lukas Appelhans's avatar
Lukas Appelhans committed
714
        if ((it + 1) != pkgs.end()) {
Lisa's avatar
Lisa committed
715 716 717
            sql += "OR ";
        }
    }
718

Lisa's avatar
Lisa committed
719 720 721
    return queryPackages(sql);
}

722 723 724
OperationRunner* Backend::operationRunner()
{
    Q_D(Backend);
Lukas Appelhans's avatar
Lukas Appelhans committed
725
    QReadLocker locker(d->mutex);
726 727 728
    return d->runner;
}

729 730 731
Package* Backend::loadPackageFromFile(const QString& path)
{
    Q_D(Backend);
732

733 734 735
    if (!QFile::exists(path)) {
        return 0;
    }
736

Daniele Cocca's avatar
Daniele Cocca committed
737
    Package* retpackage = new Package;
Lukas Appelhans's avatar
Lukas Appelhans committed
738
    retpackage->setPathToArchive(path);
739

740
    try {
741
        ArchiveHandler pkg(path);
742 743 744 745
        /* 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. */

746
        foreach (QString const& line, pkg.readTextFile(".PKGINFO").split('\n')) {
747 748 749
            if (!line.contains('=') || line.startsWith('#')) {
                // Skip this line, not interesting
                continue;
750
            }
751 752 753 754 755 756 757 758 759 760 761 762 763

            // 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
764
                continue;
765 766 767
            }

            // Strip whitespaces here instead
Lisa's avatar
Lisa committed
768
            value = value.simplified();
769 770 771

            // Ok, now let's analyze
            if (key == "pkgname") {
Lukas Appelhans's avatar
Lukas Appelhans committed
772
                retpackage->d_func()->setName(value);
773
            } else if (key == "pkgver") {
Lukas Appelhans's avatar
Lukas Appelhans committed
774
                retpackage->d_func()->setVersion(Package::Version(value.toUtf8()));
775
            } else if (key == "pkgdesc") {
Lukas Appelhans's avatar
Lukas Appelhans committed
776
                retpackage->d_func()->setDescription(value);
777
            } else if (key == "group") {
Lukas Appelhans's avatar
Lukas Appelhans committed
778
                retpackage->d_func()->addGroup(d->groupPool->group(value));
779
            } else if (key == "url") {
Lukas Appelhans's avatar
Lukas Appelhans committed
780
                retpackage->d_func()->setUrl(QUrl(value));
781
            } else if (key == "license") {
Lukas Appelhans's avatar
Lukas Appelhans committed
782
                retpackage->d_func()->addLicense(value);
783
            } else if (key == "builddate") {
Lukas Appelhans's avatar
Lukas Appelhans committed
784
                retpackage->d_func()->setBuildDate(QDateTime::fromTime_t(value.toUInt()));
785
            } else if (key == "packager") {
Lukas Appelhans's avatar
Lukas Appelhans committed
786
                retpackage->d_func()->setPackager(value);
787
            } else if (key == "arch") {
Lukas Appelhans's avatar
Lukas Appelhans committed
788
                retpackage->d_func()->setArch(value);
789
            } else if (key == "size") {
Lukas Appelhans's avatar
Lukas Appelhans committed
790
                retpackage->d_func()->setSize(value.toInt());
791
            } else if (key == "depend") {
Lukas Appelhans's avatar
Lukas Appelhans committed
792
                retpackage->d_func()->addDependency(value);
Lisa's avatar
Lisa committed
793 794
            } else if (key == "makedepend") {
                retpackage->d_func()->addMakeDependency(value);
795
            } else if (key == "optdepend") {
Lukas Appelhans's avatar
Lukas Appelhans committed
796
                retpackage->d_func()->addOptDependency(value);
797
            } else if (key == "conflict") {
Lukas Appelhans's avatar
Lukas Appelhans committed
798
                retpackage->d_func()->addConflict(value);
799
            } else if (key == "replaces") {
Lukas Appelhans's avatar
Lukas Appelhans committed
800
                retpackage->d_func()->addReplaces(value);
801
            } else if (key == "provides") {
Lukas Appelhans's avatar
Lukas Appelhans committed