akabeivalidatorthread_p.cpp 13.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* 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.
*/


#include "akabeivalidatorthread_p.h"

#include "akabeiconfig.h"

#include <QFile>
17 18 19 20 21 22 23
#include <QtConcurrentMap>
#include <qeventloop.h>
#include <QtCore/QFutureWatcher>
#include "akabeibackend.h"
#include "akabeidatabase.h"
#include "akabeihelpers_p.h"
#include "akabeipackage.h"
24
#include "akabeioperation_p.h"
25 26
#include "akabeierror.h"
#include <QtCore/QDir>
27

28
namespace Akabei {
29

30 31
QString queryFromName(const QString &name)
{
32 33 34
    QString sql = "SELECT * FROM packages WHERE Name = \"%1\" OR Provides LIKE \"%u;%1;%u\"";
    sql.arg(name);
    return sql;
35 36
}

37 38 39 40 41 42 43 44 45 46 47
ValidatorThread::ValidatorThread(QObject* parent)
    : QThread(parent)
{

}

ValidatorThread::~ValidatorThread()
{

}

48 49 50 51 52
void ValidatorThread::streamValidationProgress(int )
{

}

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
QHash< QString, QString > ValidatorThread::versionedTargets(const QStringList &targets) const
{
    QHash< QString, QString > rethash;
    foreach (const QString &target, targets) {
        if (target.contains('<')) {
            rethash.insert(target.split('<').first(), '<' + target.split('<').last());
        } else if (target.contains('>')) {
            rethash.insert(target.split('>').first(), '>' + target.split('>').last());
        } else if (target.contains('=')) {
            rethash.insert(target.split('=').first(), target.split('=').last());
        } else {
            // No version costraint.
            rethash.insert(target, QString());
        }
    }

    return rethash;
}

72 73 74 75 76 77 78 79 80 81 82 83 84 85
QPair< QString, QString > ValidatorThread::versionedTarget(const QString& target) const
{
    if (target.contains('<')) {
        return qMakePair<QString, QString>(target.split('<').first(), '<' + target.split('<').last());
    } else if (target.contains('>')) {
        return qMakePair<QString, QString>(target.split('>').first(), '>' + target.split('>').last());
    } else if (target.contains('=')) {
        return qMakePair<QString, QString>(target.split('=').first(), target.split('=').last());
    } else {
        // No version costraint.
        return qMakePair<QString, QString>(target, QString());
    }
}

86 87 88 89 90 91 92 93 94 95
OperationPrivate* ValidatorThread::operationPrivateProxy(Operation* op)
{
    return op->d_func();
}

void concurrentValidate(Operation* op)
{
    ValidatorThread::operationPrivateProxy(op)->concurrentValidate();
}

96 97 98 99 100 101 102 103 104 105 106
void ValidatorThread::requestCancel()
{

}

void ValidatorThread::run()
{
    m_currentPhase = Operation::Phase1;
    processNextPhase();
}

107
QList< Operation* > ValidatorThread::joinOperations(Operation* op, bool shouldBeReady)
108
{
109
    QList<Operation*> retlist;
110
    if (op->status() != Operation::StatusReady) {
111
        retlist << op;
112 113 114 115
    }

    // Process children
    foreach (Operation *child, op->subOperations()) {
116
        retlist << joinOperations(child, shouldBeReady);
117
    }
118

119
    return retlist;
120 121 122 123 124 125
}

void ValidatorThread::processNextPhase()
{
    // First of all validate everything and compute dependencies. Recurse over one's self
    // until all dependencies are satisfied.
126 127 128 129 130 131 132 133 134
    QList<Operation*> allOps;
    QList<Operation*> processOps;
    foreach (Operation *op, m_operations[m_currentPhase]) {
        allOps << joinOperations(op, false);
        processOps << joinOperations(op, true);
    }

    if (processOps.isEmpty()) {
        // We can advance to the next phase, if any
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
        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:
                // If we're here, we're successfully over.
                emit validationFinished(true);
                return;
                break;
            default:
                // Urgh.. what?
                Error *error = new Error(Akabei::Error::AkabeiInternalError,
                                         tr("An internal error occurred during validation"));
                manageErrors(QList<Error*>() << error);
                return;
                break;
        }
        processNextPhase();
162 163 164 165 166 167 168 169
        return;
    }

    // Start the concurrent validation
    QEventLoop e;
    QFutureWatcher< void > watcher(this);
    connect(&watcher, SIGNAL(finished()), &e, SLOT(quit()));
    connect(&watcher, SIGNAL(progressValueChanged(int)), this, SLOT(streamValidationProgress(int)));
170
    QFuture< void > future = QtConcurrent::map(processOps, concurrentValidate);
171 172 173
    watcher.setFuture(future);
    e.exec();

174 175
    QList<Error*> errors;

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

    // Check dependencies
    dependTargets.removeDuplicates();
200

201 202
    QHash< QString, QString > vrsDependTargets = versionedTargets(dependTargets);

203
    QList< Package* > missingDeps;
204 205
    QStringList unresolvableDeps;

206
    for (QHash< QString, QString >::const_iterator i = vrsDependTargets.constBegin(); i != vrsDependTargets.constEnd(); ++i) {
207
        // Is it in the current operation?
208 209 210 211 212 213 214 215 216 217 218 219
        if (targetNames.contains(i.key())) {
            // Is the version correct?
            if (i.value().isEmpty()) {
                // No version checks, so it's ok. Pass by.
                continue;
            } else {
                // Check if the version matches
                if (Package::Version(targetNames[i.key()]->targetVersion()).respectsConstraint(i.value())) {
                    // It does, dependency satisfied. Pass by.
                    continue;
                }
            }
220
        }
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243

        // Not in the current operation. Is it in the local database?
        QList<Package*> locals = Backend::instance()->localDatabase()->queryPackages(queryFromName(i.key()));
        if (locals.size() > 0) {
            // Ok. Now let's check if it has a version constraint.
            if (i.value().isEmpty()) {
                // No version checks around, just pass by.
                continue;
            } else {
                // Huzzah, check the version.
                bool found = false;
                foreach (Package *p, locals) {
                    if (p->version().respectsConstraint(i.value())) {
                        found = true;
                        break;
                    }
                }

                if (found) {
                    // Cool, pass by
                    continue;
                }
            }
244
        }
245

246 247 248 249
        // 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*>)));
250
        loop.setUuid(Backend::instance()->queryPackages(queryFromName(i.key())));
251
        loop.exec();
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
        Package *package = 0;
        // 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 (!i.value().isEmpty()) {
                // Check it
                if (!p->version().respectsConstraint(i.key())) {
                    // Not a good candidate, skip it
                    continue;
                }
            }

            // Is it matching our target strictly by name?
            if (p->name() == i.key()) {
                // It's 100% our first choice
                package = p;
            } else {
                // Add it only if there are no other targets
                if (!package) {
                    package = p;
                }
            }
274
        }
275 276 277 278 279 280 281 282 283

        if (package) {
            // A valid target was found. Add it to the missing dependencies and pass by
            missingDeps << package;
            continue;
        }

        // If we got here, all the possible checks have been done. The dependency is simply not satisfiable.
        unresolvableDeps << i.key();
284 285
    }

286
    foreach (const QString &uD, unresolvableDeps) {
287
        // failure.
288 289 290
        Error *error = new Error(Akabei::Error::UnresolvableDependenciesError,
                                 tr("A dependency is not resolvable: %1").arg(uD));
        errors << error;
291 292 293
    }

    if (!missingDeps.isEmpty()) {
294 295
        // Do a early recurse
        // TODO: Add missing targets here.
296 297 298 299
        processNextPhase();
        return;
    }

300
    // Ok, now check validity of stuff.
301
    QStringList filesystemAdditions;
302
    QStringList targetRemovals;
303
    QDir rootDir(Config::instance()->root());
304
    foreach (Operation *op, allOps) {
305
        filesystemAdditions << op->fileSystemAdditions();
306 307 308
        targetRemovals << op->targetRemovals();
        // Check filesystem conflicts
        foreach (const QString &target, op->fileSystemAdditions()) {
309
            if (QFile::exists(rootDir.absoluteFilePath(target))) {
310
                // Problem, FS conflict.
311 312 313
                Error *error = new Error(Akabei::Error::FilesystemConflictError,
                                 tr("%1 already exists in the filesystem").arg(rootDir.absoluteFilePath(target)));
                errors << error;
314 315 316 317 318
            }
        }
        foreach (const QString &target, op->conflictingTargets()) {
            if (targetNames.contains(target)) {
                // Problem, attempting to install a package conflicting with something in the transaction.
319 320 321 322
                Error *error = new Error(Akabei::Error::PackageConflictError,
                                         tr("%1 is about to be installed, but conflicts with %2, which is being "
                                            "installed as well.").arg(target).arg(op->targetName()));
                errors << error;
323 324 325 326
            }
            // Now check the local database
            if (Backend::instance()->localDatabase()->queryPackages(queryFromName(target)).size() > 0) {
                // Problem, trying to install a package conflicting with an installed package
327 328 329 330 331
                Error *error = new Error(Akabei::Error::PackageConflictError,
                                         tr("%1 is about to be installed, but conflicts with %2, which is installed")
                                         .arg(op->targetName())
                                .arg(Backend::instance()->localDatabase()->queryPackages(queryFromName(target)).first()->name()));
                errors << error;
332 333
            }
        }
334 335
    }

336
    // Check for internal conflicts, so easy thanks to the power of QStringList::removeDuplicates.
337 338 339
    int dupCount = filesystemAdditions.removeDuplicates();
    if (dupCount > 0) {
        // Internal conflicts
340 341 342 343
        // TODO: Be more verbose
        Error *error = new Error(Akabei::Error::FilesystemConflictError,
                                 tr("A file is contained in more than one package"));
        errors << error;
344 345
    }

346
    // Check if we are breaking dependencies somehow
347
    foreach (const QString &target, targetRemovals) {
348 349 350 351
        QString sql = "SELECT * FROM packages WHERE Depends LIKE \"%u;" + target + "%u\"";
        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) {
352
            // Check also that the package itself is not being removed and a target is not being added
353
            if (p->name() == target && !targetRemovals.contains(target)) {
354
                // Problem, attempting to remove a package which some installed packages depend on.
355 356 357 358
                Error *error = new Error(Akabei::Error::UnresolvableDependenciesError,
                                         tr("%1 depends on %2, which is being removed")
                                         .arg(p->name()).arg(target));
                errors << error;
359
            }
360 361 362
        }
    }

363 364 365 366 367 368 369 370
    // Good. Any errors?
    if (!errors.isEmpty()) {
        // Ah. report them back and interrupt here
        manageErrors(errors);
        return;
    }

    // Ok, The phase appears to be fully valid.
371 372
    // Recurse over ourselves
    processNextPhase();
373
}
374 375 376 377 378 379 380 381

void ValidatorThread::manageErrors(const QList< Error* >& errors)
{
    emit errorsOccurred(errors);
    emit validationFinished(false);
}

}