akabeivalidatorrunnable_p.cpp 16.6 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>
29 30
#include <QTimer>
#include <QCoreApplication>
31

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

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
}

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

53 54
}

55
ValidatorWorker::~ValidatorWorker()
56
{
57

58 59
}

60
void ValidatorWorker::streamValidationProgress(int )
61 62 63 64
{

}

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)
{
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
}

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);
    }
}

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
}

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:
144
                // If we're here, we're successfully over.
145
                emit validationFinished(true, m_operations);
146 147 148
                return;
                break;
            default:
149
                // Urgh.. what?
150 151
                manageErrors(Error::List() << Error(Error::AkabeiInternalError,
						    tr("An internal error occurred during validation")));
152 153 154 155
                return;
                break;
        }
        processNextPhase();
156 157 158
        return;
    }

159
    // Start the concurrent validation
160
    QEventLoop e;
161
    s_validateFutureWatcher = new QFutureWatcher<void>();
162 163
    connect(s_validateFutureWatcher.data(), &QFutureWatcherBase::finished, &e, &QEventLoop::quit);
    connect(s_validateFutureWatcher.data(), &QFutureWatcherBase::progressValueChanged, this, &ValidatorWorker::streamValidationProgress);
164
    QFuture< void > future = QtConcurrent::map(processOps, concurrentValidate);
165
    s_validateFutureWatcher.data()->setFuture(future);
166
    e.exec();
167 168 169

    Error::List errors;

170
    if (s_validateFutureWatcher.data()->isCanceled()) {
171 172
        // Manage errors...
        foreach (Operation *op, processOps) {
173
            errors << op->errors();
174
        }
175
    }
176

177
    s_validateFutureWatcher.data()->deleteLater();
178

179
    // Good. Any errors?
180
    ERRORS_CHECKPOINT()
181

182
    // Now populate target names and dependencies
183 184
    QHash< QString, Operation* > processTargetNames;
    QHash< QString, Operation* > targetNames;
185
    foreach (Operation *op, allOps) {
186 187 188
        foreach (const QString &target, op->targetAdditions()) {
            if (!targetNames.contains(target)) {
                targetNames[target] = op;
189
                processTargetNames[target] = op;
190
            } else {
191
                // Problem. There are two operations trying to add the same target.
192
                errors << Error(Error::DuplicateTargetError,
193
                                tr("The target %1 is being added by %2 and %3")
194 195 196
                                    .arg(target,
                                    targetNames[target]->targetName(),
                                    op->targetName()));
197 198
            }
        }
199 200
    }

201
    // Good. Any errors?
202 203
    ERRORS_CHECKPOINT()

204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
    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.
222
                        // Increase the priority of the operation.
223 224 225
                        increasePriority(i.value(), targetNames[dependency.first]);
                        continue;
                    }
226
                }
227
            }
228

229 230 231 232 233 234 235 236 237 238 239 240 241 242
            // 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;
243
                        }
244
                    }
245

246 247 248
                    if (found) {
                        // Cool, pass by
                        continue;
249
                    }
250
                }
251
            }
252

253 254
            // The dependency is hereby missing. Is it resolvable? Let's see.
            Helpers::PackageEventLoop loop;
255 256
            connect(Backend::instance(), &Backend::queryPackagesCompleted,
                    &loop, &Helpers::PackageEventLoop::requestQuit);
257 258
            loop.setUuid(Backend::instance()->queryPackages(queryFromName(dependency.first)));
            loop.exec();
259
            Package *package = nullptr;
260 261 262 263 264 265 266 267
            // 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;
268
                    }
269
                }
270

271 272 273 274 275 276 277
                // 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) {
278 279
                        package = p;
                    }
280
                }
281
            }
282

283 284 285 286
            if (package) {
                // A valid target was found. Add it to the missing dependencies and pass by
                missingDeps << package;
                continue;
287
            }
288 289 290

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

293 294
        foreach (const QString &uD, unresolvableDeps) {
            // failure.
295
            errors << Error(Error::UnresolvableDependenciesError,
296
                tr("A dependency of %1 is not resolvable: %2").arg(i.key(), uD));
297
        }
298

299 300
        // Good. Any errors?
        ERRORS_CHECKPOINT()
301

302 303 304 305 306
        if (!missingDeps.isEmpty()) {
            // Do a early recurse
            processNextPhase();
            return;
        }
307 308
    }

309
    // Ok, now check validity of stuff.
310
    QStringList filesystemAdditions;
311
    QStringList targetRemovals;
312
    QDir rootDir(Config::instance()->root());
313
    foreach (Operation *op, allOps) {
314
        filesystemAdditions << op->fileSystemAdditions();
315
        targetRemovals << op->targetRemovals();
316
        // Check filesystem conflicts
317
        foreach (const QString &target, op->fileSystemAdditions()) {
318
            if (QFile::exists(rootDir.absoluteFilePath(target))) {
319
                // Is it a directory?
320
                QFileInfo testDir(rootDir.absoluteFilePath(target));
321 322
                if (testDir.isDir()) {
                    // It's fine
323
                    continue;
324
                }
325

326 327 328 329 330 331 332 333 334 335 336 337
                // check if this file is being removed in an other op
                // other pkg in the queue can provide this file
                QStringList fileSystemRemovals;
                foreach (Operation *subop, allOps) {
                    fileSystemRemovals << subop->fileSystemRemovals();
                }
                if (fileSystemRemovals.contains(target) == false){
                    // Problem, FS conflict.
                    errors << Error(Error::FilesystemConflictError,
                                    tr("[Target %1] %2 already exists in the filesystem")
                                        .arg(op->targetName(), rootDir.absoluteFilePath(target)));
                }
338 339 340
            }
        }
        foreach (const QString &target, op->conflictingTargets()) {
341 342
            // if is a pkg to remove or replace skip the checks
            bool skip = false;
343
            foreach (const QString& targetRemove, targetRemovals){
344 345 346 347
                if(targetRemove.contains(target)) skip = true;
            }
            if (skip) continue;

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

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

374
    // Check for internal conflicts
Dario Freddi's avatar
Dario Freddi committed
375 376 377 378 379 380 381 382 383 384 385
    {
        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;
                }
386
                // Argh! Check it.
Dario Freddi's avatar
Dario Freddi committed
387 388 389
                if (s.endsWith('/')) {
                    seenAndValidated.insert(s);
                } else {
390
                    // Real conflict here
391 392
                    errors << Error(Error::FilesystemConflictError,
				    tr("%1 is contained in more than one package").arg(s));
Dario Freddi's avatar
Dario Freddi committed
393 394 395 396 397
                }
                continue;
            }
            seen.insert(s);
        }
398 399
    }

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

403 404 405
    // Check if we are breaking dependencies somehow
    foreach (const QString &target, targetRemovals) {
        QString rT = Helpers::versionedTarget(target).first;
406
        QString sql = Queries::packageDependencies(rT, QStringLiteral("LIKE"));
407 408 409 410 411 412 413 414 415 416 417 418 419
        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.
420
                errors << Error(Error::UnresolvableDependenciesError,
421
                                tr("%1 depends on %2, which is being removed")
422
                                    .arg(p->name(), target));;
423
            }
424
        }
425
    }
426

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

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

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

ValidatorRunnable::ValidatorRunnable(const QHash<Operation::Phase, QList< Operation* > > &ops, ProcessingOptions processingOptions)
  : QObject()
  , QRunnable()
  , m_operations(ops)
445
  , m_worker(nullptr)
446 447 448
{
    s_options = processingOptions;
}
449

450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
ValidatorRunnable::~ValidatorRunnable()
{
    if (m_worker)
        m_worker->deleteLater();
}

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

void ValidatorRunnable::requestCancel()
{

}

void ValidatorRunnable::run()
{
468
    // with QRunnable we are running this operations inside a thread
469 470
    m_worker = new ValidatorWorker(m_operations);
    emit ready();
471
    m_worker->run();
472 473
}

474
}
Dario Freddi's avatar
Dario Freddi committed
475

476
#include <moc_akabeivalidatorrunnable_p.cpp>