queueoperation.cpp 16.2 KB
Newer Older
1 2
/* This file is part of the Chakra project

Lukas Appelhans's avatar
Lukas Appelhans committed
3
   Copyright (C) 2011 Lukas Appelhans <l.appelhans@gmx.de>
4 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.
*/

#include "queueoperation.h"
12
#include "progressbar.h"
Lisa's avatar
Lisa committed
13
#include "utils.h"
14 15 16 17 18

#include <akabeiclientbackend.h>
#include <akabeiclienttransactionhandler.h>
#include <akabeiclientqueue.h>
#include <akabeiconfig.h>
Lisa's avatar
Lisa committed
19 20 21 22 23
#include <akabeioperationrunner.h>
#include <akabeihelpers.h>
#include <akabeidatabase.h>

#include <QTextStream>
24 25
#include <QFile>
#include <QDir>
Lisa's avatar
Lisa committed
26 27 28
#include <QTextStream>
#include <QCoreApplication>

29
#include <iostream>
30 31 32 33
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>

Lukas Appelhans's avatar
Lukas Appelhans committed
34 35
const int UPDATE_INTERVAL = 500;

36
QueueOperation::QueueOperation(APM::OperationName operation, QHash<APM::OptionName, QStringList> options, QObject * parent)
Lisa's avatar
Lisa committed
37 38 39
  : QObject(parent),
    m_operation(operation),
    m_options(options)
40
{
41
    if (options.contains(APM::Force))
42
        m_processingOptions |= Akabei::Force;
43
    if (options.contains(APM::InstallAsDeps))
44
        m_processingOptions |= Akabei::InstallAsDependencies;
45
    if (options.contains(APM::InstallAsExplicit))
46
        m_processingOptions |= Akabei::InstallAsExplicit;
47
    if (options.contains(APM::SkipDependencyCheck))
48
        m_processingOptions |= Akabei::SkipDependencies;
49
    if (options.contains(APM::DownloadOnly))
50
        m_processingOptions |= Akabei::DownloadOnly;
51
    if (options.contains(APM::RemoveConfig))
Lukas Appelhans's avatar
Lukas Appelhans committed
52
        m_processingOptions |= Akabei::RemoveConfigs;
53 54
    if (options.contains(APM::Recursive))
        m_processingOptions |= Akabei::Recursive;
55
    if (options.contains(APM::DatabaseOnly))
Lukas Appelhans's avatar
Lukas Appelhans committed
56
        m_processingOptions |= Akabei::DatabaseOnly;
57

58
    connect(AkabeiClient::Backend::instance()->transactionHandler(), SIGNAL(newTransactionMessage(QString)), SLOT(transactionMessage(QString)));
59 60 61

    connect(AkabeiClient::Backend::instance()->transactionHandler(), SIGNAL(transactionCreated(AkabeiClient::Transaction*)), SLOT(transactionCreated(AkabeiClient::Transaction*)));
    connect(AkabeiClient::Backend::instance()->transactionHandler(), SIGNAL(validationFinished(bool)), SLOT(validationFinished(bool)));
62
    connect(AkabeiClient::Backend::instance()->transactionHandler(), SIGNAL(finished(bool)), SLOT(transactionFinished(bool)));
63 64 65 66 67 68 69 70
}

QueueOperation::~QueueOperation()
{
}

void QueueOperation::start(AkabeiClient::PackageAction action, QList<Akabei::Package*> packages)
{
71
    QTextStream out(stdout);
Lukas Appelhans's avatar
Lukas Appelhans committed
72

73
    m_action = action;
74

Lisa's avatar
Lisa committed
75
    foreach (Akabei::Package * pkg, packages) {
76 77
        if (m_options.contains(APM::OnlyNeeded) && pkg->isInstalled())
            continue;
Lisa's avatar
Lisa committed
78
        qDebug() << "We add" << pkg->name() << "to the queue";
79 80
        AkabeiClient::Backend::instance()->queue()->addPackage(pkg, action);
    }
81

82 83 84 85 86 87 88 89
    // do not check the other action types, only when we are updating
    // we must verify if there are some new packages that replace
    // an existing one
    if (action == AkabeiClient::Update) {
        out << QObject::tr("Calculating replacements...") << endl;
        AkabeiClient::Backend::instance()->queue()->computeReplacements();
    }

90
    if (AkabeiClient::Backend::instance()->queue()->size() == 0) {
Lisa's avatar
Lisa committed
91 92 93 94 95 96
        // Just picking the best message for the job :)
        if (action == AkabeiClient::Update) {
            out << QObject::tr("System is up-to-date.") << endl;
        } else {
            out << QObject::tr("Nothing to do.") << endl;
        }
97 98 99 100
        emit finished();
        return;
    }

Lisa's avatar
Lisa committed
101
    out << QObject::tr("Calculating dependencies...") << endl;
102

Lukas Appelhans's avatar
Lukas Appelhans committed
103
    AkabeiClient::Backend::instance()->transactionHandler()->start(m_processingOptions);
104 105
}

106
void QueueOperation::transactionCreated(AkabeiClient::Transaction* t)
107
{
108
    m_transaction = t;
Lukas Appelhans's avatar
Lukas Appelhans committed
109

110
    QTextStream out(stdout);
111
    QTextStream err(stderr);
112
    if (!t->errors().isEmpty()) {
113
        foreach (Akabei::Error const& e, t->errors()) {
114
            err << Akabei::errorPrefix << e.description() << endl;
115
        }
Lisa's avatar
Lisa committed
116
        err << Akabei::errorPrefix << QObject::tr("An unresolveable error occurred!") << endl;
117 118 119 120 121
        emit finished();
        return;
    }
    if (!t->questions().isEmpty()) {
        foreach (AkabeiClient::TransactionQuestion *q, t->questions()) {
122
            if (m_options.contains(APM::NoConfirm)) {
123
                if (q->suggestedAnswer().letter != QString()) {
Lukas Appelhans's avatar
Lukas Appelhans committed
124 125 126 127 128 129 130 131 132 133 134
                    q->setAnswer(q->suggestedAnswer().letter);
                } else {
                    q->setAnswer(q->possibleAnswers().first().letter);
                }
                continue;
            }
            out << q->question() << "[";
            foreach (AkabeiClient::TransactionAnswer a, q->possibleAnswers()) {
                if (!(q->possibleAnswers().first() == a))
                    out << QObject::tr("/");
                if (q->suggestedAnswer() == a) {
135
                    out << QObject::tr("Default:") << a.message;
Lukas Appelhans's avatar
Lukas Appelhans committed
136 137 138 139 140
                } else {
                    out << a.message;
                }
                out << "(" << a.letter << ")";
            }
141

Lisa's avatar
Lisa committed
142
            out << QObject::tr("]");
143 144 145
            out.flush();
            std::string input;
            getline(std::cin, input);
Lukas Appelhans's avatar
Lukas Appelhans committed
146 147

            if (!input.empty()) {
148
                q->setAnswer(QString(input.c_str()));
149
            } else {
Lisa's avatar
Lisa committed
150
                q->setAnswer(q->suggestedAnswer().letter);
151
            }
152 153
        }
    }
154

155
    if (t->isValid()) {
156
        out << QObject::tr("Validating transaction...") << "\n";
157 158
        AkabeiClient::Backend::instance()->transactionHandler()->validate();
    } else {
Lisa's avatar
Lisa committed
159
        err << Akabei::errorPrefix << QObject::tr("Invalid transaction, quitting...") << endl;
160 161
        emit finished();
    }
162 163
}

164
void QueueOperation::validationFinished(bool valid)
165 166
{
    QTextStream out(stdout);
167
    QTextStream err(stderr);
Lukas Appelhans's avatar
Lukas Appelhans committed
168

169
    if (!valid) {
Lisa's avatar
Lisa committed
170
        err << Akabei::errorPrefix << QObject::tr("The transaction could not be validated:") << endl;
171
        foreach (Akabei::Error const& e, m_transaction->errors()) {
172
            err << e.description() << endl;
173
        }
174
        err.flush();
175 176 177
        emit finished();
        return;
    }
178

179
    if (m_action != AkabeiClient::Update && m_transaction->toBeInstalled().isEmpty() && m_transaction->toBeReinstalled().isEmpty() && m_transaction->toBeRemoved().isEmpty() && m_transaction->toBeUpgraded().isEmpty() && m_transaction->toBeReplaced().isEmpty()) {
Lisa's avatar
Lisa committed
180
        err << Akabei::errorPrefix << QObject::tr("The targets specified for the operation are not valid. Quitting...") << endl;
Lisa's avatar
Lisa committed
181 182
        emit finished();
        return;
183
    } else if (m_action == AkabeiClient::Update && m_transaction->toBeUpgraded().isEmpty() && m_transaction->toBeReplaced().isEmpty()) {
Lisa's avatar
Lisa committed
184
        out << QObject::tr("System is up-to-date.") << endl;
185
        emit finished();
186 187
        return;
    }
188

Lisa's avatar
Lisa committed
189
    out << QObject::tr("Transaction validated successfully.") << endl;
190

Lukas Appelhans's avatar
Lukas Appelhans committed
191
    //First we check if the packages we're about to remove are needed somewhere
192
    //TODO: Keep it or change behavior?
Lukas Appelhans's avatar
Lukas Appelhans committed
193 194
    if (m_action == AkabeiClient::Remove && !(Akabei::Backend::instance()->operationRunner()->processingOptions() & Akabei::SkipDependencies)) {
        QSet<Akabei::Package*> conflict;
Lisa's avatar
Lisa committed
195
        foreach (AkabeiClient::QueueItem * item, AkabeiClient::Backend::instance()->queue()->items()) {
196
            foreach (Akabei::Package *p, item->requiredByTree()) {
197 198 199 200 201 202 203 204 205
                bool removeConflicting = false;
                foreach (AkabeiClient::QueueItem * item, AkabeiClient::Backend::instance()->queue()->items()) {
                    if (item->package()->name() == p->name()) {
                        removeConflicting = true;
                        break;
                    }
                }
                    
                if (!removeConflicting && m_transaction->toBeRemoved().contains(p))
206 207
                    conflict << p;
            }
208 209
        }
        if (!conflict.isEmpty()) {
Lisa's avatar
Lisa committed
210
            err << Akabei::errorPrefix << QObject::tr("The queue could not be validated, the following package has the package(s) marked to be removed as dependencies: ",
Lisa's avatar
Lisa committed
211 212
                                                "The queue could not be validated, the following packages have the package(s) marked to be removed as dependencies: ",
                                                conflict.count());
Lisa's avatar
Lisa committed
213
            foreach (Akabei::Package* pkg, conflict) {
Lukas Appelhans's avatar
Lukas Appelhans committed
214
                if (pkg != (*conflict.begin()))
215 216
                    err << ", ";
                err << pkg->name();
217
            }
218
            err << endl;
219 220 221
            emit finished();
            return;
        }
Lukas Appelhans's avatar
Lukas Appelhans committed
222
    }
223

224
    int downloadSize = 0;
Lukas Appelhans's avatar
Lukas Appelhans committed
225
    int installSize = 0;
Lukas Appelhans's avatar
Lukas Appelhans committed
226
    Akabei::Cache c;
227 228 229 230
    foreach (Akabei::Package *i, m_transaction->toBeInstalled()) {
        installSize += i->installedSize();
        if (!c.isPackageInCache(i->filename())) {
            downloadSize += i->size();
Lukas Appelhans's avatar
Lukas Appelhans committed
231
        }
232
    }
Lukas Appelhans's avatar
Lukas Appelhans committed
233

Lukas Appelhans's avatar
Lukas Appelhans committed
234
    foreach (Akabei::Package *i, m_transaction->toBeUpgraded()) { //FIXME: Keep toBeUpgraded a map, then we won't have to query the ld...
235
        Akabei::Package::List ps = Akabei::Backend::instance()->localDatabase()->searchPackages(i->name(), Akabei::SearchNameEqual);
236 237 238 239 240
        if (ps.isEmpty())
            continue;
        installSize += (i->installedSize() - ps.first()->installedSize());
        if (!c.isPackageInCache(i->filename())) {
            downloadSize += i->size();
241 242
        }
    }
243 244
    foreach (Akabei::Package *i, m_transaction->toBeRemoved()) {
        installSize -= i->installedSize();
Lukas Appelhans's avatar
Lukas Appelhans committed
245
    }
246
    if (!m_transaction->toBeRemoved().isEmpty()) {
247
        out << '\n' << QObject::tr("The following packages are going to be removed:") << '\n';
248 249
        foreach (Akabei::Package * pkg, m_transaction->toBeRemoved()) {
            if (pkg != *(m_transaction->toBeRemoved().begin()))
Lisa's avatar
Lisa committed
250
                out << QObject::tr(", ");
251
            out << pkg->name() << " (" << pkg->version().toByteArray() << ")";
252 253 254 255
        }
        out << endl;
    }
    if (!m_transaction->toBeInstalled().isEmpty()) {
256
        out << '\n' << QObject::tr("The following packages are going to be installed:") << '\n';
257 258
        foreach (Akabei::Package * pkg, m_transaction->toBeInstalled()) {
            if (pkg != *(m_transaction->toBeInstalled().begin()))
Lisa's avatar
Lisa committed
259
                out << QObject::tr(", ");
260
            out << pkg->name() << " (" << pkg->version().toByteArray() << ")";
261 262 263 264
        }
        out << endl;
    }
    if (!m_transaction->toBeUpgraded().isEmpty()) {
265
        out << '\n' << QObject::tr("The following packages are going to be upgraded:") << '\n';
266 267
        foreach (Akabei::Package* old, m_transaction->toBeUpgraded().keys()) {
            if (old != *(m_transaction->toBeUpgraded().keys().begin()))
Lisa's avatar
Lisa committed
268
                out << QObject::tr(", ");
269 270
            Akabei::Package* newer = m_transaction->toBeUpgraded().value(old);
            out << old->name() << " (" << old->version().toByteArray() << " -> " << newer->version().toByteArray() << ")";
271 272 273 274
        }
        out << endl;
    }
    if (!m_transaction->toBeReinstalled().isEmpty()) {
275
        out << '\n' << QObject::tr("The following packages are going to be reinstalled:") << '\n';
276 277
        foreach (Akabei::Package * pkg, m_transaction->toBeReinstalled()) {
            if (pkg != *(m_transaction->toBeReinstalled().begin()))
Lisa's avatar
Lisa committed
278
                out << QObject::tr(", ");
279
            out << pkg->name() << " (" << pkg->version().toByteArray() << ")";
280 281 282
        }
        out << endl;
    }
283
    if (!m_transaction->toBeReplaced().isEmpty()) {
284
        out << '\n' << QObject::tr("The following packages are going to be replaced:") << '\n';
285 286
        foreach (Akabei::Package* pkg, m_transaction->toBeReplaced().keys()) {
            QList<Akabei::Package*> older = m_transaction->toBeReplaced().value(pkg);
287
            out << pkg->name() << " (" << pkg->version().toByteArray() << ") " << QObject::tr("replaces") << " ";
288
            foreach(Akabei::Package* replaced, older){
289
                out << replaced->name() << " (" << replaced->version().toByteArray() << ") ";
290 291
                if (replaced != *(older.begin()))
                    out << QObject::tr(", ");
292
            }
293 294 295
        }
        out << endl;
    }
296 297
    out << endl;
    if (downloadSize > 0) {
Lisa's avatar
Lisa committed
298
        out << QObject::tr("Download size: %1").arg(Akabei::Helpers::formatByteSize(downloadSize)) << endl;
299 300
    }
    if (installSize > 0) {
Lisa's avatar
Lisa committed
301
        out << QObject::tr("Install size: %1").arg(Akabei::Helpers::formatByteSize(installSize)) << endl;
302
    } else if (installSize < 0) {
Lisa's avatar
Lisa committed
303
        out << QObject::tr("Freed size after transaction: %1").arg(Akabei::Helpers::formatByteSize(installSize * (-1))) << endl;
304
    }
305

306
    out << QObject::tr("Continue with processing? [Y/n]") << " ";
307
    out.flush();
308 309 310
    if (!m_options.contains(APM::NoConfirm)) {
        std::string input;
        getline(std::cin, input);
311 312
        
        /* No confirmation given */
Lisa's avatar
Lisa committed
313
        if (!input.empty() && input != QObject::tr("y").toStdString()) {
314 315 316
            emit finished();
            return;
        }
317
        
318
    } else {
Lisa's avatar
Lisa committed
319
        out << QObject::tr("Y", "(y)es, answer to a question") << endl;
320
    }
Lukas Appelhans's avatar
Lukas Appelhans committed
321 322

    m_bar.setColumns(ProgressBar::Status | ProgressBar::Details | ProgressBar::Progress | ProgressBar::Eta);
Lisa's avatar
Lisa committed
323
    m_bar.setProgressDescription(QObject::tr("Total progress:"));
Lukas Appelhans's avatar
Lukas Appelhans committed
324 325 326 327

    connect(&m_timer, SIGNAL(timeout()), SLOT(showProgress()));
    m_timer.setInterval(UPDATE_INTERVAL);
    m_timer.start();
328

329 330 331
    AkabeiClient::Backend::instance()->transactionHandler()->process();
}

Lukas Appelhans's avatar
Lukas Appelhans committed
332
QString currentPhase()
333
{
Lukas Appelhans's avatar
Lukas Appelhans committed
334 335
    switch (AkabeiClient::Backend::instance()->transactionHandler()->transactionProgress()->phase()) {
        case AkabeiClient::TransactionProgress::Validating:
Lisa's avatar
Lisa committed
336
            return QObject::tr("Validating...");
Lukas Appelhans's avatar
Lukas Appelhans committed
337
        case AkabeiClient::TransactionProgress::RunningPreHooks:
Lisa's avatar
Lisa committed
338
            return QObject::tr("Running PreHooks...");
Lukas Appelhans's avatar
Lukas Appelhans committed
339
        case AkabeiClient::TransactionProgress::Downloading:
Lisa's avatar
Lisa committed
340
            return QObject::tr("Downloading...");
Lukas Appelhans's avatar
Lukas Appelhans committed
341
        case AkabeiClient::TransactionProgress::Processing:
Lisa's avatar
Lisa committed
342
            return QObject::tr("Processing...");
Lukas Appelhans's avatar
Lukas Appelhans committed
343
        case AkabeiClient::TransactionProgress::RunningScriptlets:
Lisa's avatar
Lisa committed
344
            return QObject::tr("Running Scriplets...");
Lukas Appelhans's avatar
Lukas Appelhans committed
345
        case AkabeiClient::TransactionProgress::RunningPostHooks:
Lisa's avatar
Lisa committed
346
            return QObject::tr("Running PostHooks...");
Lukas Appelhans's avatar
Lukas Appelhans committed
347
        case AkabeiClient::TransactionProgress::Cleaning:
Lisa's avatar
Lisa committed
348
            return QObject::tr("Cleaning...");
Lisa's avatar
Lisa committed
349
        default:
Lisa's avatar
Lisa committed
350
            return QObject::tr("Finished!");
351
    };
Lukas Appelhans's avatar
Lukas Appelhans committed
352 353 354 355 356 357
}

void QueueOperation::showProgress()
{
    AkabeiClient::TransactionProgress * tp = AkabeiClient::Backend::instance()->transactionHandler()->transactionProgress();

358 359 360
    m_bar.setProgress(tp->progress());
    m_bar.setStatus(currentPhase());
    m_bar.setEta(tp->estimatedTime());
Lukas Appelhans's avatar
Lukas Appelhans committed
361

362
    QMap<QString, QString> details;
363
    QMap<Akabei::Package*, AkabeiClient::DownloadInformation> downloadInformation;
Lukas Appelhans's avatar
Lukas Appelhans committed
364 365 366 367 368
    if (tp->phase() == AkabeiClient::TransactionProgress::Downloading)
        downloadInformation = tp->downloadInformation();
    QMap<Akabei::Package*, int> progresses = tp->currentPackages();
    QMap<Akabei::Package*, int>::iterator i = progresses.begin();
    QMap<Akabei::Package*, int>::iterator end = progresses.end();
369
    for (; i != end; i++) {
Lukas Appelhans's avatar
Lukas Appelhans committed
370
        if (tp->phase() == AkabeiClient::TransactionProgress::Downloading) {
371
            details.insert(i.key()->name(), QString::number(i.value()) + "% / " + QString::number((downloadInformation[i.key()].downloadSpeed() / 1024)) + "KiB/s");
372
        } else {
373
            details.insert(i.key()->name(), QString::number(i.value()) + "%");
374
        }
375
    }
376
    m_bar.setDetails(details);
377

378
    QTextStream out(stdout);
379
    out << m_bar.line();
Lukas Appelhans's avatar
Lukas Appelhans committed
380
    out.flush();
381 382
}

383 384 385
void QueueOperation::transactionFinished(bool success)
{
    m_timer.stop();
Lukas Appelhans's avatar
Lukas Appelhans committed
386
    if (success) {
387
        QTextStream out(stdout);
Lisa's avatar
Lisa committed
388
        out << m_bar.replaceMessage(QObject::tr("The transaction finished successfully!")) << endl;
389
    } else {
390
        QTextStream err(stderr);
Lisa's avatar
Lisa committed
391 392 393
        
        int n = Akabei::Backend::instance()->operationRunner()->errors().count();
        err << m_bar.replaceMessage(Akabei::errorPrefix + QObject::tr("%n error(s) occurred during the transaction: ", 0, n)) << endl;
394 395

        foreach (Akabei::Error const& e, m_transaction->errors()) {
396
            err << e.description() << endl;
397 398
        }
    }
399
    
400 401 402
    emit finished();
}

403 404 405
void QueueOperation::transactionMessage(const QString& message)
{
    QTextStream out(stdout);
Lukas Appelhans's avatar
Lukas Appelhans committed
406
    out << endl << m_bar.replaceMessage(message) << endl;
407
}