akabeivalidatorrunnable_p.cpp 16.9 KB
Newer Older
1 2 3
/* This file is part of the Chakra project

   Copyright (C) 2010 Dario Freddi <drf@chakra-project.org>
4
   Copyright (C) 2011 Lukas Appelhans <boom1992@chakra-project.org>
5 6 7 8 9 10 11 12

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


13 14 15 16 17 18 19 20 21
#include <akabeivalidatorrunnable_p.h>

#include <akabeiconfig.h>
#include <akabeibackend.h>
#include <akabeidatabase.h>
#include <akabeihelpers.h>
#include <akabeipackage.h>
#include <akabeioperation_p.h>
#include <akabeierror.h>
Lisa's avatar
Lisa committed
22
#include "akabeiquery.h"
23 24 25 26 27 28

#include <QFile>
#include <QtConcurrentMap>
#include <QEventLoop>
#include <QFutureWatcher>
#include <QDir>
Lukas Appelhans's avatar
Lukas Appelhans committed
29 30
#include <QTimer>
#include <QCoreApplication>
31

32 33 34
#define ERRORS_CHECKPOINT()  if (!errors.isEmpty()) { \
        manageErrors(errors); \
        return;  }
35

AlmAck's avatar
AlmAck committed
36
static QPointer<QFutureWatcher< void > > s_validateFutureWatcher;
Lukas Appelhans's avatar
Lukas Appelhans committed
37
static Akabei::ProcessingOptions s_options;//FIXME: I think we can put all processingoptions into operations only!
38

39
namespace Akabei {
40

41 42
QString queryFromName(const QString &name)
{
43
    QString sql = "SELECT * FROM packages WHERE name=\"" + name + "\" UNION SELECT a.* FROM packages as a JOIN (SELECT package FROM provides WHERE provides=\"" + name + "\") AS t ON a.id=t.package";
44
    //sql.arg(name);
45
    return sql;
46 47
}

Lukas Appelhans's avatar
Lukas Appelhans committed
48 49 50
ValidatorWorker::ValidatorWorker(const QHash<Operation::Phase, QList< Operation* > > &ops, QObject* parent)
  : QObject(parent),
    m_operations(ops)
51
{
Lukas Appelhans's avatar
Lukas Appelhans committed
52

53 54
}

Lukas Appelhans's avatar
Lukas Appelhans committed
55
ValidatorWorker::~ValidatorWorker()
56
{
Lukas Appelhans's avatar
Lukas Appelhans committed
57

58 59
}

Lukas Appelhans's avatar
Lukas Appelhans committed
60
void ValidatorWorker::streamValidationProgress(int )
61 62 63 64
{

}

Lukas Appelhans's avatar
Lukas Appelhans committed
65 66 67 68 69 70 71
void ValidatorWorker::run()
{
    m_currentPhase = Operation::Phase1;
    processNextPhase();
}

OperationPrivate* ValidatorWorker::operationPrivateProxy(Operation* op)
72 73 74 75 76 77
{
    return op->d_func();
}

void concurrentValidate(Operation* op)
{
Lukas Appelhans's avatar
Lukas Appelhans committed
78 79
    ValidatorWorker::operationPrivateProxy(op)->setProcessingOptions(s_options);
    ValidatorWorker::operationPrivateProxy(op)->concurrentValidate();
80

81
    // How did it go?
82
    if (op->status() == Operation::StatusReady) {
83
        // It's ok!
84 85 86
    } else if (!s_validateFutureWatcher.isNull()) {
        s_validateFutureWatcher.data()->cancel();
    }
87 88
}

Lukas Appelhans's avatar
Lukas Appelhans committed
89
void ValidatorWorker::increasePriority(Operation* parent, Operation* toIncrease)
90 91 92 93 94 95 96 97
{
    if (parent->priority() > toIncrease->priority()) {
        toIncrease->setPriority(parent->priority() + 1);
    } else {
        toIncrease->setPriority(toIncrease->priority() + 1);
    }
}

Lukas Appelhans's avatar
Lukas Appelhans committed
98
QList< Operation* > ValidatorWorker::joinOperations(Operation* op, bool shouldBeReady)
99
{
100
    QList<Operation*> retlist;
101
    if (op->status() != Operation::StatusReady) {
102
        retlist << op;
103 104
    }

105 106
    // Process children
    foreach (Operation *child, op->preOperations()) {
107
        retlist << joinOperations(child, shouldBeReady);
108
    }
109

110
    foreach (Operation *child, op->postOperations()) {
111
        retlist << joinOperations(child, shouldBeReady);
112
    }
113

114
    return retlist;
115 116
}

Lukas Appelhans's avatar
Lukas Appelhans committed
117
void ValidatorWorker::processNextPhase()
118
{
119 120
    // First of all validate everything and compute dependencies. Recurse over one's self
    // until all dependencies are satisfied.
121 122
    QList<Operation*> allOps;
    QList<Operation*> processOps;
123
    foreach (Operation *op, m_operations[m_currentPhase]) {
124 125 126 127 128
        allOps << joinOperations(op, false);
        processOps << joinOperations(op, true);
    }

    if (processOps.isEmpty()) {
129
        // We can advance to the next phase, if any
130 131 132 133 134 135 136 137 138 139 140 141 142 143
        switch (m_currentPhase) {
            case Operation::Phase1:
                m_currentPhase = Operation::Phase2;
                break;
            case Operation::Phase2:
                m_currentPhase = Operation::Phase3;
                break;
            case Operation::Phase3:
                m_currentPhase = Operation::Phase4;
                break;
            case Operation::Phase4:
                m_currentPhase = Operation::Phase5;
                break;
            case Operation::Phase5:
Lukas Appelhans's avatar
Lukas Appelhans committed
144
                //Now we need to move all ops back to the main app thread
145 146
                for (auto it = m_operations.constBegin(), end = m_operations.constEnd(); it != end; ++it ) {
                    const auto ops = it.value();
Lukas Appelhans's avatar
Lukas Appelhans committed
147 148 149
                    foreach (Operation * op, ops)
                        op->moveToThread(QCoreApplication::instance()->thread());
                }
150

151
                // If we're here, we're successfully over.
152
                emit validationFinished(true, m_operations);
153 154 155
                return;
                break;
            default:
156
                // Urgh.. what?
157 158
                manageErrors(Error::List() << Error(Error::AkabeiInternalError,
						    tr("An internal error occurred during validation")));
159 160 161 162
                return;
                break;
        }
        processNextPhase();
163 164 165
        return;
    }

166
    // Start the concurrent validation
167
    QEventLoop e;
168 169 170
    s_validateFutureWatcher = new QFutureWatcher<void>();
    connect(s_validateFutureWatcher.data(), SIGNAL(finished()), &e, SLOT(quit()));
    connect(s_validateFutureWatcher.data(), SIGNAL(progressValueChanged(int)), this, SLOT(streamValidationProgress(int)));
171
    QFuture< void > future = QtConcurrent::map(processOps, concurrentValidate);
172
    s_validateFutureWatcher.data()->setFuture(future);
173
    e.exec();
174 175 176

    Error::List errors;

177
    if (s_validateFutureWatcher.data()->isCanceled()) {
178 179
        // Manage errors...
        foreach (Operation *op, processOps) {
180
            errors << op->errors();
181
        }
182
    }
183

184
    s_validateFutureWatcher.data()->deleteLater();
185

186
    // Good. Any errors?
187
    ERRORS_CHECKPOINT()
188

189
    // Now populate target names and dependencies
190 191
    QHash< QString, Operation* > processTargetNames;
    QHash< QString, Operation* > targetNames;
192
    foreach (Operation *op, allOps) {
193 194 195
        foreach (const QString &target, op->targetAdditions()) {
            if (!targetNames.contains(target)) {
                targetNames[target] = op;
196
                processTargetNames[target] = op;
197
            } else {
198
                // Problem. There are two operations trying to add the same target.
199
                errors << Error(Error::DuplicateTargetError,
Lukas Appelhans's avatar
Lukas Appelhans committed
200
                                tr("The target %1 is being added by %2 and %3")
201 202 203
                                    .arg(target,
                                    targetNames[target]->targetName(),
                                    op->targetName()));
204 205
            }
        }
206 207
    }

208
    // Good. Any errors?
209 210
    ERRORS_CHECKPOINT()

211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
    QList< Package* > missingDeps;
    QStringList unresolvableDeps;

    for (QHash<QString, Operation*>::const_iterator i = processTargetNames.constBegin(); i != processTargetNames.constEnd();++i) {
        foreach (const QString &depend, i.value()->targetDependencies()) {
            QPair< QString, QString > dependency = Helpers::versionedTarget(depend);
            // Is it in the current operation?
            if (targetNames.contains(dependency.first)) {
                // Is the version correct?
                if (dependency.second.isEmpty()) {
                    // No version checks, so it's ok.
                    // Increase the priority of the operation.
                    increasePriority(i.value(), targetNames[dependency.first]);
                    continue;
                } else {
                    // Check if the version matches
                    if (Package::Version::respectsConstraint(targetNames[dependency.first]->targetVersion(), dependency.second)) {
                        // It does, dependency satisfied.
229
                        // Increase the priority of the operation.
230 231 232
                        increasePriority(i.value(), targetNames[dependency.first]);
                        continue;
                    }
233
                }
234
            }
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249
            // Not in the current operation. Is it in the local database?
            QList<Package*> locals = Backend::instance()->localDatabase()->queryPackages(queryFromName(dependency.first));
            if (locals.size() > 0) {
                // Ok. Now let's check if it has a version constraint.
                if (dependency.second.isEmpty()) {
                    // No version checks around, so it's ok
                    continue;
                } else {
                    // Huzzah, check the version.
                    bool found = false;
                    foreach (Package *p, locals) {
                        if (p->version().respectsConstraint(dependency.second)) {
                            found = true;
                            break;
250
                        }
251
                    }
252

253 254 255
                    if (found) {
                        // Cool, pass by
                        continue;
256
                    }
257
                }
258
            }
259

260 261 262 263 264 265
            // The dependency is hereby missing. Is it resolvable? Let's see.
            Helpers::PackageEventLoop loop;
            connect(Backend::instance(), SIGNAL(queryPackagesCompleted(QUuid,QList<Akabei::Package*>)),
                    &loop, SLOT(requestQuit(QUuid,QList<Akabei::Package*>)));
            loop.setUuid(Backend::instance()->queryPackages(queryFromName(dependency.first)));
            loop.exec();
266
            Package *package = nullptr;
267 268 269 270 271 272 273 274
            // Ok, we just need to install it. Let's loop the result and add the best target
            foreach (Package *p, loop.result()) {
                // Version constraint?
                if (!dependency.second.isEmpty()) {
                    // Check it
                    if (!p->version().respectsConstraint(dependency.second)) {
                        // Not a good candidate, skip it
                        continue;
275
                    }
276
                }
277

278 279 280 281 282 283 284
                // Is it matching our target strictly by name?
                if (p->name() == dependency.first) {
                    // It's 100% our first choice
                    package = p;
                } else {
                    // Add it only if there are no other targets
                    if (!package) {
285 286
                        package = p;
                    }
287
                }
288
            }
289

290 291 292 293
            if (package) {
                // A valid target was found. Add it to the missing dependencies and pass by
                missingDeps << package;
                continue;
294
            }
295 296 297

            // If we got here, all the possible checks have been done. The dependency is simply not satisfiable.
            unresolvableDeps << dependency.first;
298
        }
299

300 301
        foreach (const QString &uD, unresolvableDeps) {
            // failure.
302
            errors << Error(Error::UnresolvableDependenciesError,
303
                tr("A dependency of %1 is not resolvable: %2").arg(i.key(), uD));
304
        }
305

306 307
        // Good. Any errors?
        ERRORS_CHECKPOINT()
308

309 310 311 312 313
        if (!missingDeps.isEmpty()) {
            // Do a early recurse
            processNextPhase();
            return;
        }
314 315
    }

316
    // Ok, now check validity of stuff.
317
    QStringList filesystemAdditions;
318
    QStringList targetRemovals;
319
    QDir rootDir(Config::instance()->root());
320
    foreach (Operation *op, allOps) {
321
        filesystemAdditions << op->fileSystemAdditions();
322
        targetRemovals << op->targetRemovals();
323
        // Check filesystem conflicts
324
        foreach (const QString &target, op->fileSystemAdditions()) {
325
            if (QFile::exists(rootDir.absoluteFilePath(target))) {
326
                // Is it a directory?
327
                QFileInfo testDir(rootDir.absoluteFilePath(target));
328 329
                if (testDir.isDir()) {
                    // It's fine
Dario Freddi's avatar
Dario Freddi committed
330
                    continue;
331
                }
332

333
                // Problem, FS conflict.
334
                errors << Error(Error::FilesystemConflictError,
Lukas Appelhans's avatar
Lukas Appelhans committed
335 336
                                tr("%1 already exists in the filesystem")
                                    .arg(rootDir.absoluteFilePath(target)));
337 338 339
            }
        }
        foreach (const QString &target, op->conflictingTargets()) {
340 341
            // if is a pkg to remove or replace skip the checks
            bool skip = false;
Fabian Kosmale's avatar
Fabian Kosmale committed
342
            foreach (const QString& targetRemove, targetRemovals){
343 344 345 346
                if(targetRemove.contains(target)) skip = true;
            }
            if (skip) continue;

347
            if (targetNames.contains(target)) {
348
                // Corner case: the conflict might be a provides/conflicts problem. Let's check.
349
                if (targetNames[target] == op) {
350
                    // Ok, then it's fine
351
                } else {
352
                    // Problem, attempting to install a package conflicting with something in the transaction.
353
                    errors << Error(Error::PackageConflictError,
Lukas Appelhans's avatar
Lukas Appelhans committed
354
                                    tr("%1 is about to be installed, but conflicts with %2, which is being installed as well.")
355
                                        .arg(target, op->targetName()));
Dario Freddi's avatar
Dario Freddi committed
356
                }
357
            }
358
            // Now check the local database
Dario Freddi's avatar
Dario Freddi committed
359 360
            foreach (Package *p, Backend::instance()->localDatabase()->queryPackages(queryFromName(target))) {
                if (p->name() == target) {
361
                    // Problem, trying to install a package conflicting with an installed package
362
                    errors << Error(Error::PackageConflictError,
Lukas Appelhans's avatar
Lukas Appelhans committed
363
                                   tr("%1 is about to be installed, but conflicts with %2, which is installed")
364
                                       .arg(op->targetName(), Backend::instance()->localDatabase()->queryPackages(queryFromName(target)).first()->name()));
Dario Freddi's avatar
Dario Freddi committed
365
                }
366 367
            }
        }
368 369
    }

370
    // Good. Any errors?
371 372
    ERRORS_CHECKPOINT()

373
    // Check for internal conflicts
Dario Freddi's avatar
Dario Freddi committed
374 375 376 377 378 379 380 381 382 383 384
    {
        int n = filesystemAdditions.size();
        QSet<QString> seen;
        QSet<QString> seenAndValidated;
        seen.reserve(n);
        for (int i = 0; i < n; ++i) {
            const QString &s = filesystemAdditions.at(i);
            if (seen.contains(s)) {
                if (!seenAndValidated.contains(s)) {
                    continue;
                }
385
                // Argh! Check it.
Dario Freddi's avatar
Dario Freddi committed
386 387 388
                if (s.endsWith('/')) {
                    seenAndValidated.insert(s);
                } else {
389
                    // Real conflict here
390 391
                    errors << Error(Error::FilesystemConflictError,
				    tr("%1 is contained in more than one package").arg(s));
Dario Freddi's avatar
Dario Freddi committed
392 393 394 395 396
                }
                continue;
            }
            seen.insert(s);
        }
397 398
    }

399
    // Good. Any errors?
400 401
    ERRORS_CHECKPOINT()

402 403 404
    // Check if we are breaking dependencies somehow
    foreach (const QString &target, targetRemovals) {
        QString rT = Helpers::versionedTarget(target).first;
Lisa's avatar
Lisa committed
405
        QString sql = Queries::packageDependencies(rT, "LIKE");
406 407 408 409 410 411 412 413 414 415 416 417 418
        QList<Package*> ret = Backend::instance()->localDatabase()->queryPackages(sql);
        // Iterate: we might have found targets that are not the ones we're looking for (due to version handling)
        foreach (Package *p, ret) {
            bool reallyDepends = false;
            foreach (const QString &dep, p->dependencies()) {
                if (Helpers::unversionedTarget(dep) == rT)
                    reallyDepends = true;
            }
            if (!reallyDepends)
                continue;
            // Check also that the package itself is not being removed and a target is not being added
            if (!targetRemovals.contains(p->name())) {
                // Problem, attempting to remove a package which some installed packages depend on.
419
                errors << Error(Error::UnresolvableDependenciesError,
Lukas Appelhans's avatar
Lukas Appelhans committed
420
                                tr("%1 depends on %2, which is being removed")
421
                                    .arg(p->name(), target));;
422
            }
423
        }
424
    }
425

426 427 428
    // Good. Any errors?
    ERRORS_CHECKPOINT()

429 430
    // Ok, The phase appears to be fully valid.
    // Recurse over ourselves
431
    processNextPhase();
432
}
433

Lukas Appelhans's avatar
Lukas Appelhans committed
434
void ValidatorWorker::manageErrors(Akabei::Error::List const& errors)
435 436
{
    emit errorsOccurred(errors);
437
    emit validationFinished(false, QHash<Operation::Phase, QList< Operation* > >());
Lukas Appelhans's avatar
Lukas Appelhans committed
438 439 440 441 442 443
}

ValidatorRunnable::ValidatorRunnable(const QHash<Operation::Phase, QList< Operation* > > &ops, ProcessingOptions processingOptions)
  : QObject()
  , QRunnable()
  , m_operations(ops)
444
  , m_worker(nullptr)
Lukas Appelhans's avatar
Lukas Appelhans committed
445
  , m_thread(new QThread())
Lukas Appelhans's avatar
Lukas Appelhans committed
446 447 448
{
    s_options = processingOptions;
}
449

Lukas Appelhans's avatar
Lukas Appelhans committed
450 451 452 453
ValidatorRunnable::~ValidatorRunnable()
{
    if (m_worker)
        m_worker->deleteLater();
Lukas Appelhans's avatar
Lukas Appelhans committed
454
    delete m_thread;
Lukas Appelhans's avatar
Lukas Appelhans committed
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
}

ValidatorWorker* ValidatorRunnable::worker()
{
    return m_worker;
}

void ValidatorRunnable::requestCancel()
{

}

void ValidatorRunnable::run()
{
    //Don't use a parent here, otherwise we break thread affinity!!!
    m_worker = new ValidatorWorker(m_operations);
Lukas Appelhans's avatar
Lukas Appelhans committed
471
    m_worker->moveToThread(m_thread);
472 473
    for (auto it = m_operations.constBegin(), end = m_operations.constEnd(); it != end; ++it ) {
        const auto ops = it.value();
Lukas Appelhans's avatar
Lukas Appelhans committed
474 475 476
        foreach (Operation * op, ops)
            op->moveToThread(m_thread);
    }
Lukas Appelhans's avatar
Lukas Appelhans committed
477
    emit ready();
Lukas Appelhans's avatar
Lukas Appelhans committed
478 479 480
   // m_worker->run();
    QTimer::singleShot(0, m_worker, SLOT(run()));
    m_thread->start();
481 482
}

483
}
Dario Freddi's avatar
Dario Freddi committed
484

AlmAck's avatar
AlmAck committed
485
#include <moc_akabeivalidatorrunnable_p.cpp>