akabeidatabase.cpp 16.1 KB
Newer Older
Dario Freddi's avatar
Dario Freddi committed
1 2 3 4 5 6 7 8 9 10
/* This file is part of the Chakra project

   Copyright (C) 2010 Dario Freddi <drf@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.
*/

11 12 13 14 15 16 17 18
#include <akabeidatabase_p.h>

#include <akabeipackage_p.h>
#include <akabeigroup_p.h>
#include <akabeibackend_p.h>
#include <akabeihelpers_p.h>
#include <akabeidelta_p.h>
#include <akabeihook_p.h>
19
#include <akabeidebug.h>
Lisa's avatar
Lisa committed
20
#include <akabeiquery.h>
21

22
namespace Akabei {
Daniele Cocca's avatar
Daniele Cocca committed
23
DatabasePrivate::DatabasePrivate(Database* db, QString const& pathToDatabase)
24
    : q_ptr(db)
25
    , pathToDb(pathToDatabase)
26
    , valid(true)
Lukas Appelhans's avatar
Lukas Appelhans committed
27
    , mutex(new QMutex(QMutex::Recursive))
28
{
Lisa's avatar
Lisa committed
29 30 31
    if (!QFile::exists(pathToDatabase)) {
        qDebug() << "Caught this";
        error.setType(Error::DatabaseError);
32 33
        error.setDescription(QObject::tr("Cannot find database file on disk: %1!").arg(pathToDatabase));
        Akabei::ErrorQueue::instance()->appendError(error);
Lisa's avatar
Lisa committed
34 35 36
        valid = false;
        return;
    }
37 38
}

Daniele Cocca's avatar
Daniele Cocca committed
39 40
DatabasePrivate::~DatabasePrivate()
{
Lukas Appelhans's avatar
Lukas Appelhans committed
41
    delete mutex;
Daniele Cocca's avatar
Daniele Cocca committed
42
}
43 44 45

void DatabasePrivate::init()
{
46
    try {
47
        qDebug() << "Connect to db" << pathToDb;
48
        dbConnection.connectToDB(pathToDb, !pathToDb.endsWith(QLatin1String("local.db")));
49 50 51
        populateGroupCache(); /* populate group cache on startup */
        valid = true;
    } catch (SQLiteException &ex) {
Lukas Appelhans's avatar
Lukas Appelhans committed
52
        qDebug() << "Caught an error in init?!";
53 54 55 56
        valid = false;
        error.setType( Error::DatabaseError );
        error.setDescription( ex.what() );
        Akabei::ErrorQueue::instance()->appendError(error);
57 58 59
    }
}

60 61
QString Database::name() const
{
62 63
    Q_D(const Database);
    return d->pathToDb;
64 65 66 67 68
}

void DatabasePrivate::populateGroupCache()
{
    groupCache.clear();
Lisa's avatar
Lisa committed
69
    SQLiteResource table = dbConnection.query( Queries::allGroups() );
70

Lukas Appelhans's avatar
Lukas Appelhans committed
71
    for (int i = 0; i < table.getRowsCount(); i++) {
Daniele Cocca's avatar
Daniele Cocca committed
72
        Group* group = groupFromRow(table, i);
Lukas Appelhans's avatar
Lukas Appelhans committed
73
        groupCache[group->name()] = group;
74 75 76
    }
}

Daniele Cocca's avatar
Daniele Cocca committed
77
Group* DatabasePrivate::groupFromRow(SQLiteResource& table, int row)
78 79 80
{
    using namespace Tables::Groups;

81
    QString name = table.getDataAt(row, QStringLiteral("name")).toString();
82

83
    // Is our group still not into the pool?
84
    if (!Backend::instance()->d_func()->groupPool->contains(name)) {
85
        // In this case, populate its fields
Daniele Cocca's avatar
Daniele Cocca committed
86
        Group* group = Backend::instance()->d_func()->groupPool->group(name);
87

88 89
        group->d_ptr->setDescription(table.getDataAt(row, QStringLiteral("description")).toString());
        group->d_ptr->setIcon(table.getDataAt(row, QStringLiteral("icon")).toString());
Lukas Appelhans's avatar
Lukas Appelhans committed
90
        return group;
91
    }
92

93
    // Return the group from the pool
94
    return Backend::instance()->d_func()->groupPool->group(name);
95 96
}

Daniele Cocca's avatar
Daniele Cocca committed
97
Package* DatabasePrivate::packageFromRow(SQLiteResource& table, int row)
98 99
{
    Q_Q(Database);
100

101 102
    int id = table.getDataAt(row, QStringLiteral("id")).toInt();
    int installreason = table.getDataAt(row, QStringLiteral("installreason")).toInt();
103

104
    // Does the cache contain our package?
105
    if (packageCache.contains(id)) {
106
        // Simply return it
107
        return packageCache[id];
108 109
    }

110
    // Otherwise go ahead, create the package, add it to the cache, and return it.
111

112 113
    using namespace Tables::Packages;
    // We suppose the statement is actually valid. So let's create our package.
114
    Package *package = new Package(q, id, table.getDataAt(row, QStringLiteral("name")).toString());
115

116 117 118
    QString url = table.getDataAt(row, QStringLiteral("url")).toString();
    QString md5 = table.getDataAt(row, QStringLiteral("md5sum")).toString();
    QString screenshotUrl = table.getDataAt(row, QStringLiteral("screenshoturl")).toString();
119

120 121 122 123 124
    package->d_func()->setArch(table.getDataAt(row, QStringLiteral("arch")).toString());
    package->d_func()->setBuildDate(QDateTime::fromTime_t(table.getDataAt(row, QStringLiteral("builddate")).toInt()));
    package->d_func()->setInstallDate(QDateTime::fromTime_t(table.getDataAt(row, QStringLiteral("installdate")).toInt()));
    package->d_func()->setDescription(table.getDataAt(row, QStringLiteral("description")).toString());
    package->d_func()->setFilename(table.getDataAt(row, QStringLiteral("filename")).toString());
Lukas Appelhans's avatar
Lukas Appelhans committed
125
    package->d_func()->setMd5sum(md5.toUtf8());
126 127 128 129 130 131
    package->d_func()->setPackager(table.getDataAt(row, QStringLiteral("packager")).toString());
    package->d_func()->setSize(table.getDataAt(row, QStringLiteral("size")).toInt());
    package->d_func()->setInstalledSize(table.getDataAt(row, QStringLiteral("installedsize")).toInt());
    package->d_func()->setVersion(Package::Version(table.getDataAt(row, QStringLiteral("version")).toString().toUtf8()));
    package->d_func()->setEpoch(table.getDataAt(row, QStringLiteral("epoch")).toInt());
    if (screenshotUrl != QLatin1String("NULL")) {
132 133
        package->d_func()->setScreenshot(QUrl(screenshotUrl));
    }
134
    if (url != QLatin1String("NULL")) {
135 136
        package->d_func()->setUrl(QUrl(url));
    }
137 138

    // Fetch the provided package names
139
    SQLiteResource res = dbConnection.query(QStringLiteral("SELECT provides FROM provides WHERE package=%1").arg(id));
Lisa's avatar
Lisa committed
140
    foreach (const QVariant& v, res.getColumn("provides")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
141
        package->d_func()->addProvider(v.toString());
142 143 144
    }

    // Fetch the conflicting package names
145
    res = dbConnection.query(QStringLiteral("SELECT conflict FROM conflicts WHERE package=%1").arg(id));
Lisa's avatar
Lisa committed
146
    foreach (const QVariant& v, res.getColumn("conflict")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
147
        package->d_func()->addConflict(v.toString());
148 149 150
    }

    // Fetch the package names to which we depend on
151
    res = dbConnection.query(QStringLiteral("SELECT dependency FROM depends WHERE package=%1").arg(id));
Lisa's avatar
Lisa committed
152
    foreach (const QVariant& v, res.getColumn("dependency")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
153
        package->d_func()->addDependency(v.toString());
154
    }
155

156
    res = dbConnection.query(QStringLiteral("SELECT dependency FROM makedepends WHERE package=%1").arg(id));
Lisa's avatar
Lisa committed
157 158 159
    foreach (const QVariant& v, res.getColumn("dependency")) {
        package->d_func()->addMakeDependency(v.toString());
    }
160 161

    // Fetch the package names to which we optionally dependend on
162
    res = dbConnection.query(QStringLiteral("SELECT dependency FROM optional WHERE package=%1").arg(id));
Lisa's avatar
Lisa committed
163
    foreach (const QVariant& v, res.getColumn("dependency")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
164
        package->d_func()->addOptDependency(v.toString());
165 166 167
    }

    // Fetch the packages names that we replace
168
    res = dbConnection.query(QStringLiteral("SELECT replaces FROM replaces WHERE package=%1").arg(id));
Lisa's avatar
Lisa committed
169
    foreach (const QVariant& v, res.getColumn("replaces")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
170
        package->d_func()->addReplaces(v.toString());
171
    }
Daniele Cocca's avatar
Daniele Cocca committed
172

Lukas Appelhans's avatar
Lukas Appelhans committed
173
    // Fetch the mimetypes which we provide
174
    res = dbConnection.query(QStringLiteral("SELECT mimetype FROM providesmimetype WHERE package=%1").arg(id));
Lisa's avatar
Lisa committed
175
    foreach (const QVariant& v, res.getColumn("mimetype")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
176
        package->d_func()->addMimetype(v.toString());
Lukas Appelhans's avatar
Lukas Appelhans committed
177
    }
Daniele Cocca's avatar
Daniele Cocca committed
178

179
    // Fetch the licenses of this package
180
    res = dbConnection.query(QStringLiteral("SELECT license FROM licensed WHERE package=%1").arg(id));
Lisa's avatar
Lisa committed
181
    foreach (const QVariant& v, res.getColumn("license")) {
Lukas Appelhans's avatar
Lukas Appelhans committed
182
        package->d_func()->addLicense(v.toString());
183
    }
184

185 186 187
    /*
     * Groups
     */
188
    QString groupQuery = "SELECT * FROM belongsgroup JOIN groups ON groups.name=belongsgroup.groupname AND package=" + QString::number(id);
189
    res = dbConnection.query(groupQuery);
190

191
    for (int i = 0; i < res.getRowsCount(); i++) {
Lukas Appelhans's avatar
Lukas Appelhans committed
192
        package->d_func()->addGroup(groupFromRow(res, i));
193
    }
194

195
    // Install Reason (if any)
196
    switch (installreason) {
197
        case 1:
198
            // Explicit
Lukas Appelhans's avatar
Lukas Appelhans committed
199
            package->d_func()->setInstallReason(Package::ExplicitlyInstalledReason);
200 201
            break;
        case 2:
202
            // Dependency
Lukas Appelhans's avatar
Lukas Appelhans committed
203
            package->d_func()->setInstallReason(Package::InstalledAsDependencyReason);
204 205
            break;
        default:
206
            // No reason
Lukas Appelhans's avatar
Lukas Appelhans committed
207
            package->d_func()->setInstallReason(Package::NoReason);
208 209
            break;
    }
210

211
    // Check if it has a scriptlet
212
    QString sql = QStringLiteral("SELECT * FROM scriptlets WHERE package=%1").arg(id);
213
    res = dbConnection.query(sql);
214

Lukas Appelhans's avatar
Lukas Appelhans committed
215
    if (res.getRowsCount() > 0) {
216
        package->d_func()->setScriptlet(res.getDataAt(0, QStringLiteral("scriptlet")).toString());
217 218
    }

219
    // Check if it has any hooks
220
    sql = QString("SELECT * FROM belongshook JOIN hooks ON package=" + QString::number(id) + " AND belongshook.hookname=hooks.name;");
221
    res = dbConnection.query(sql);
222

Lukas Appelhans's avatar
Lukas Appelhans committed
223
    if (res.getRowsCount() > 0) {
224
        // There is one
225
        for (int row = 0; row < res.getRowsCount(); row++) {
226
            package->d_func()->addHook(res.getDataAt(row, QStringLiteral("name")).toString());
227
        }
228 229
    }

230 231 232 233
    // Attempt loading the archive from cache, if any
//     package->d_func()->attemptLoadFromCache();

    // Add the package to the cache
234
    packageCache[id] = package;
235
    return package;
236 237
}

Daniele Cocca's avatar
Daniele Cocca committed
238
Delta* DatabasePrivate::deltaFromRow(SQLiteResource& table, int row)
239
{
240
    QString const filename = table.getDataAt(row, QStringLiteral("filename")).toString();
241

242 243 244
    //Check if the deltaCache contains the dle
    if (deltaCache.contains(filename))
        return deltaCache[filename];
245

246 247
    using namespace Tables::Deltas;

248
    int packageId = table.getDataAt(row, QStringLiteral("id")).toInt();
249
    QString targetName;
250

251 252 253 254 255
    if (packageCache[packageId])
        targetName = packageCache[packageId]->name();

    // Otherwise go ahead, create the delta, add it to the cache, and return it.

256
    Delta *delta = new Delta(targetName, table.getDataAt(row, QStringLiteral("versionfrom")).toByteArray(), table.getDataAt(row, QStringLiteral("versionto")).toByteArray());
257

258 259
    delta->d_func()->filename = table.getDataAt(row, QStringLiteral("filename")).toString();
    delta->d_func()->md5sum = table.getDataAt(row, QStringLiteral("md5sum")).toByteArray();
260 261

    deltaCache[filename] = delta;
262 263 264
    return delta;
}

265
Akabei::Package::List DatabasePrivate::packagesFromHook(QString const& hook)
266
{
267
    Akabei::Package::List pkgs;
Lisa's avatar
Lisa committed
268
    SQLiteResource table = dbConnection.query( Queries::packagesFromHook(hook) );
269

Lukas Appelhans's avatar
Lukas Appelhans committed
270 271 272
    for (int row = 0; row < table.getRowsCount(); row++) {
        pkgs << packageFromRow(table, row);
    }
273

Lukas Appelhans's avatar
Lukas Appelhans committed
274
    return pkgs;
275
}
276

277
Hook* DatabasePrivate::hookFromRow(SQLiteResource& table, int row)
Lukas Appelhans's avatar
Lukas Appelhans committed
278
{
279
    QString name = table.getDataAt(row, QStringLiteral("name")).toString();
280

Lukas Appelhans's avatar
Lukas Appelhans committed
281 282 283
    //Check if the deltaCache contains the dle
    if (hookCache.contains(name))
        return hookCache[name];
284

Lukas Appelhans's avatar
Lukas Appelhans committed
285
    using namespace Tables::Hooks;
286

287
    QString const h = table.getDataAt(row, QStringLiteral("hook")).toString();
288
    Akabei::Package::List packages = packagesFromHook(name);
Lukas Appelhans's avatar
Lukas Appelhans committed
289 290

    // Otherwise go ahead, create the delta, add it to the cache, and return it.
Daniele Cocca's avatar
Daniele Cocca committed
291
    Hook* hook = new Hook(name, h, packages);
Lukas Appelhans's avatar
Lukas Appelhans committed
292 293 294 295 296
    hookCache[name] = hook;

    return hook;
}

Daniele Cocca's avatar
Daniele Cocca committed
297
Database::Database(QString const& pathToDatabase)
298
    : d_ptr(new DatabasePrivate(this, pathToDatabase))
299
{
300
    Q_D(Database);
Lukas Appelhans's avatar
Lukas Appelhans committed
301

302 303 304 305 306 307 308
    /*
     * 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)
     */
    if (isValid()) {
        d->init();
    }
309 310 311 312 313 314 315
}

Database::~Database()
{
    delete d_ptr;
}

316 317 318
void Database::reinit()
{
    Q_D(Database);
319

320
    d->init();
321 322
}

323 324 325
bool Database::isValid() const
{
    Q_D(const Database);
Lukas Appelhans's avatar
Lukas Appelhans committed
326 327
    // Shield us from multithreaded disaster
    QMutexLocker locker(d->mutex);
328
    return d->valid;
329 330
}

331 332 333
Error Database::error() const
{
    Q_D(const Database);
Lukas Appelhans's avatar
Lukas Appelhans committed
334 335
    // Shield us from multithreaded disaster
    QMutexLocker locker(d->mutex);
336 337 338
    return d->error;
}

339
Package::List Database::packages()
340
{
Lisa's avatar
Lisa committed
341
    return queryPackages( Queries::allPackages() );
342 343
}

344
Package::List Database::searchPackages(QString const& token, SearchType type)
345
{
Lukas Appelhans's avatar
Lukas Appelhans committed
346
    QString sql;
347

Lukas Appelhans's avatar
Lukas Appelhans committed
348 349
    switch (type) {
        case SearchNameLike:
350
            sql = Queries::selectPackages(QStringLiteral("name"), QStringLiteral("LIKE"), "%" + token + "%");
Lukas Appelhans's avatar
Lukas Appelhans committed
351 352
            break;
        case SearchNameEqual:
353
            sql = Queries::selectPackages(QStringLiteral("name"), QStringLiteral("="), token);
Lukas Appelhans's avatar
Lukas Appelhans committed
354 355
            break;
        case SearchDescriptionLike:
356
            sql = Queries::selectPackages(QStringLiteral("description"), QStringLiteral("LIKE"), "%" + token + "%");
Lukas Appelhans's avatar
Lukas Appelhans committed
357 358
            break;
        case SearchDescriptionEqual:
359
            sql = Queries::selectPackages(QStringLiteral("description"), QStringLiteral("="), token);
Lukas Appelhans's avatar
Lukas Appelhans committed
360 361
            break;
        case SearchNameAndDescriptionLike:
362
            sql = Queries::selectPackagesNameOrDescription("%" + token + "%", QStringLiteral("LIKE"));
Lukas Appelhans's avatar
Lukas Appelhans committed
363 364
            break;
        case SearchNameAndDescriptionEqual:
365
            sql = Queries::selectPackagesNameOrDescription(token, QStringLiteral("="));
Lukas Appelhans's avatar
Lukas Appelhans committed
366 367 368 369 370
            break;
    }
    return queryPackages(sql);
}

371
Package::List Database::searchPackages(QStringList const& pkgs, SearchType type)
Lukas Appelhans's avatar
Lukas Appelhans committed
372
{
373
    QString sql = QStringLiteral("SELECT * FROM packages WHERE ");
Daniele Cocca's avatar
Daniele Cocca committed
374

Lukas Appelhans's avatar
Lukas Appelhans committed
375 376 377
    for (QStringList::const_iterator it = pkgs.begin(); it < pkgs.end(); it++) {
        switch (type) {
            case SearchNameEqual:
378
                sql += "name=\'" + (*it) + "\' ";
Lukas Appelhans's avatar
Lukas Appelhans committed
379 380
                break;
            case SearchNameLike:
381
                sql += "name LIKE \'%" + (*it) + "%\' ";
Lukas Appelhans's avatar
Lukas Appelhans committed
382 383
                break;
            case SearchDescriptionEqual:
384
                sql += "description=\'" + (*it) + "\' ";
Lukas Appelhans's avatar
Lukas Appelhans committed
385 386
                break;
            case SearchDescriptionLike:
387
                sql += "description LIKE \'%" + (*it) + "%\' ";
Lukas Appelhans's avatar
Lukas Appelhans committed
388 389
                break;
            case SearchNameAndDescriptionEqual:
390
                sql += "name=\'" + (*it) + "\' OR description=\'" + (*it) + "\' ";
Lukas Appelhans's avatar
Lukas Appelhans committed
391 392
                break;
            case SearchNameAndDescriptionLike:
393
                sql += "name LIKE \'%" + (*it) + "%\' OR description LIKE \'%" + (*it) + "%\' ";
Lukas Appelhans's avatar
Lukas Appelhans committed
394
        }
Daniele Cocca's avatar
Daniele Cocca committed
395

Lukas Appelhans's avatar
Lukas Appelhans committed
396
        if ((it + 1) != pkgs.end()) {
397
            sql += QLatin1String("OR ");
Lukas Appelhans's avatar
Lukas Appelhans committed
398 399
        }
    }
400
    return queryPackages(sql);
401 402
}

403
Akabei::Package::List Database::queryPackages(QString const& sql)
404 405
{
    Q_D(Database);
406

407
    // Shield us from multithreaded disaster
408
    QMutexLocker locker(d->mutex);
409
    Akabei::Package::List retlist;
410

Lukas Appelhans's avatar
Lukas Appelhans committed
411 412 413 414 415
    try {
        SQLiteResource packages = d->dbConnection.query(sql);
        for (int i = 0; i < packages.getRowsCount(); i++) {
            retlist << d->packageFromRow(packages, i);
        }
416
    } catch (SQLiteException const& e) {
417
        akabeiDebug() << "Error while querying database" << name() << "for packages: " << e.what();
418 419
    }

420
    return retlist;
421 422
}

423 424
QList< Group* > Database::groups()
{
Lisa's avatar
Lisa committed
425
    QString sql = Queries::allGroups();
426 427 428
    return queryGroups(sql);
}

Daniele Cocca's avatar
Daniele Cocca committed
429
QList< Group* > Database::searchGroups(QString const& token)
430
{
431
    QString sql = Queries::selectGroupsNameOrDescription(token, QStringLiteral("LIKE"));
432 433 434
    return queryGroups(sql);
}

Daniele Cocca's avatar
Daniele Cocca committed
435
QList< Group* > Database::queryGroups(QString const& sql)
436 437 438
{
    Q_D(Database);

439
    // Shield us from multithreaded disaster
440
    QMutexLocker locker(d->mutex);
441 442
    QList<Group*> retlist;

Lukas Appelhans's avatar
Lukas Appelhans committed
443 444 445 446 447
    try {
        SQLiteResource table = d->dbConnection.query(sql);
        for (int i = 0; i < table.getRowsCount(); i++) {
            retlist << d->groupFromRow(table, i);
        }
448
    } catch (SQLiteException const& e) {
449
        akabeiDebug() << "Error while querying database for groups: " << e.what();
450 451 452 453 454
    }

    return retlist;
}

Daniele Cocca's avatar
Daniele Cocca committed
455
QList<Delta*> Database::queryDeltas(QString const& sql)
456 457 458 459 460 461
{
    Q_D(Database);
    QMutexLocker locker(d->mutex);

    QList<Delta*> retlist;

Lukas Appelhans's avatar
Lukas Appelhans committed
462 463 464 465 466
    try {
        SQLiteResource table = d->dbConnection.query(sql);
        for (int i = 0; i < table.getRowsCount(); i++) {
            retlist << d->deltaFromRow(table, i);
        }
467
    } catch (SQLiteException const& e) {
468
        akabeiDebug() << "Error while querying database for deltas: " << e.what();
Lukas Appelhans's avatar
Lukas Appelhans committed
469 470 471
    }
    return retlist;
}
472

Daniele Cocca's avatar
Daniele Cocca committed
473
QList<Hook*> Database::queryHooks(QString const& sql)
474 475 476 477 478 479
{
    Q_D(Database);
    QMutexLocker locker(d->mutex);

    QList<Hook*> retlist;

Lukas Appelhans's avatar
Lukas Appelhans committed
480 481 482 483 484
    try {
        SQLiteResource table = d->dbConnection.query(sql);
        for (int i = 0; i < table.getRowsCount(); i++) {
            retlist << d->hookFromRow(table, i);
        }
Daniele Cocca's avatar
Daniele Cocca committed
485
    } catch (SQLiteException const& e) {
486
        akabeiDebug() << "Error while querying database for hooks: " << e.what();
487 488 489
    }
    return retlist;
}
490 491
}