queueoperation.cpp 12.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/* This file is part of the Chakra project

   Copyright (C) 2011 Lukas Appelhans

   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"

#include <akabeiclientbackend.h>
#include <akabeiclienttransactionhandler.h>
#include <akabeiclientqueue.h>
#include <QTextStream>
#include <kio/global.h>
#include <akabeiconfig.h>
#include <klocale.h>
#include <QFile>
#include <QDir>
#include <akabeioperationrunner.h>
#include <iostream>
#include <kdebug.h>
25 26 27
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
Lukas Appelhans's avatar
Lukas Appelhans committed
28
#include <QCoreApplication>
29 30 31 32 33 34 35 36

int getcols()
{
    struct winsize ws;

    ioctl(1, TIOCGWINSZ, &ws);
    return ws.ws_col;
}
37 38

QueueOperation::QueueOperation(QList<APM::Operation> operations, QMultiHash< APM::Operation, QString > options, QObject * parent)
Lukas Appelhans's avatar
Lukas Appelhans committed
39 40
  : QObject(parent),
    m_operations(operations)
41 42 43 44 45 46 47 48 49 50 51
{
    if (operations.contains(APM::Force))
        m_processingOptions |= Akabei::Force;
    if (operations.contains(APM::AsDeps))
        m_processingOptions |= Akabei::InstallAsDependencies;
    if (operations.contains(APM::AsExplicit))
        m_processingOptions |= Akabei::InstallAsExplicit;
    if (operations.contains(APM::SkipDependencyCheck))
        m_processingOptions |= Akabei::SkipDependencies;
    if (operations.contains(APM::DownloadOnly))
        m_processingOptions |= Akabei::DownloadOnly;
Lukas Appelhans's avatar
Lukas Appelhans committed
52 53 54 55
    if (operations.contains(APM::RemoveConfig))
        m_processingOptions |= Akabei::RemoveConfigs;
    if (operations.contains(APM::DatabaseOnly)) 
        m_processingOptions |= Akabei::DatabaseOnly;
56 57 58 59 60 61 62 63 64 65 66 67 68
}

QueueOperation::~QueueOperation()
{

}

void QueueOperation::start(AkabeiClient::PackageAction action, QList<Akabei::Package*> packages)
{
    m_action = action;
    foreach (Akabei::Package * pkg, packages) {
        AkabeiClient::Backend::instance()->queue()->addPackage(pkg, action);
    }
Lukas Appelhans's avatar
Lukas Appelhans committed
69 70 71
    
    QTextStream out(stdout);
    out << i18n("Validating transaction...") << endl;
72

73
    connect(AkabeiClient::Backend::instance()->transactionHandler(), SIGNAL(validationFinished(bool, QList<AkabeiClient::TransactionQuestion*>)), SLOT(validationFinished(bool, QList<AkabeiClient::TransactionQuestion*>)));
74 75 76 77 78
    connect(AkabeiClient::Backend::instance()->transactionHandler(), SIGNAL(errorsOccurred(QList<Akabei::Error*>&)), SLOT(errors(QList<Akabei::Error*>&)));
    connect(AkabeiClient::Backend::instance()->transactionHandler(), SIGNAL(newTransactionMessage(QString)), SLOT(transactionMessage(QString)));
    AkabeiClient::Backend::instance()->transactionHandler()->validate(m_processingOptions);
}

79
void QueueOperation::validationFinished(bool valid, QList<AkabeiClient::TransactionQuestion*> questions)
80 81 82 83 84
{
    QTextStream out(stdout);
    if (!valid) {
        out << i18n("The transaction could not be validated") << endl;
        out.flush();
Lukas Appelhans's avatar
Lukas Appelhans committed
85
        QCoreApplication::instance()->quit();
86
        return;
Lukas Appelhans's avatar
Lukas Appelhans committed
87 88
    }
    
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
    if (!questions.isEmpty()) { //TODO Rework and abstract this
        foreach (AkabeiClient::TransactionQuestion *q, questions) {
            out << q->question() << "[";
            if (q->possibleAnswers() & AkabeiClient::TransactionQuestion::Yes) {
                if (q->correctAnswer() == AkabeiClient::TransactionQuestion::Yes)
                    out << i18n("Y");
                else
                    out << i18n("y");
                
                if (q->possibleAnswers() & AkabeiClient::TransactionQuestion::No)
                    out << i18n("/");
            }
            if (q->possibleAnswers() & AkabeiClient::TransactionQuestion::No) {
                if (q->correctAnswer() == AkabeiClient::TransactionQuestion::No)
                    out << i18n("N");
                else
                    out << i18n("n");
            }
            out.flush();
            std::string input;
            getline(std::cin, input);
            if (!input.empty() && (((input == i18n("y").toStdString() || input == i18n("Y").toStdString()) && q->correctAnswer() == AkabeiClient::TransactionQuestion::No) ||
                                     ((input == i18n("n").toStdString() || input == i18n("N").toStdString()) && q->correctAnswer() == AkabeiClient::TransactionQuestion::Yes))) {
                emit finished();
                return;
            }
        }
    }
    
Lukas Appelhans's avatar
Lukas Appelhans committed
118
    //First we check if the packages we're about to remove are needed somewhere
119 120 121
    if (m_action == AkabeiClient::Remove && !m_operations.contains(APM::Cascade) && !(Akabei::Backend::instance()->operationRunner()->processingOptions() & Akabei::SkipDependencies)) {
        QList<Akabei::Package*> conflict;
        foreach (AkabeiClient::QueueItem * item, AkabeiClient::Backend::instance()->queue()->items()) {
Lukas Appelhans's avatar
Lukas Appelhans committed
122
            conflict << item->requiredByTree();
123 124
        }
        if (!conflict.isEmpty()) {
Lukas Appelhans's avatar
Lukas Appelhans committed
125
            out << i18n("The queue could not be validated, the following packages have the packages marked to be removed as dependencies: ");//FIXME: Pluralize
126 127 128 129 130 131 132 133 134
            foreach (Akabei::Package* pkg, conflict) {
                if (pkg != conflict.first())
                    out << ", ";
                out << pkg->name();
            }
            out << endl;
            emit finished();
            return;
        }
135
    }
Lukas Appelhans's avatar
Lukas Appelhans committed
136

137
    int downloadSize = 0;
Lukas Appelhans's avatar
Lukas Appelhans committed
138 139
    int installSize = 0;
    QSet<Akabei::Package*> queuePkgs;//Use a set as it does not contain duplicates
140
    foreach (AkabeiClient::QueueItem * item, AkabeiClient::Backend::instance()->queue()->items()) {
Lukas Appelhans's avatar
Lukas Appelhans committed
141 142
        queuePkgs << item->package();
        if (!QFile::exists(Akabei::Config::instance()->cacheDir().absoluteFilePath(item->package()->filename()))) { //In case the package is already cached do nothing
143
            downloadSize += item->package()->size();
Lukas Appelhans's avatar
Lukas Appelhans committed
144
        }
145
        installSize += item->package()->installedSize();
146
        if (!(Akabei::Backend::instance()->operationRunner()->processingOptions() & Akabei::SkipDependencies)) {
Lukas Appelhans's avatar
Lukas Appelhans committed
147 148
            foreach (Akabei::Package * dep, m_action == AkabeiClient::Remove ? item->requiredByTree() : item->dependencyTree()) {
                queuePkgs << dep;
149 150 151 152 153 154
                if (dep->database() != Akabei::Backend::instance()->localDatabase() && !QFile::exists(Akabei::Config::instance()->cacheDir().absoluteFilePath(dep->filename())))
                    downloadSize += dep->size();
                installSize += dep->installedSize();
            }
        }
    }
Lukas Appelhans's avatar
Lukas Appelhans committed
155 156 157 158 159 160
    out << i18n("Packages: ");
    foreach (Akabei::Package * pkg, queuePkgs) {
        if (pkg != *(queuePkgs.begin()))
            out << i18n(", ");
        out << pkg->name();
    }
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
    out << endl << endl;
    if (m_action == AkabeiClient::Install) {
        out << i18n("Download size: %1", KIO::convertSize(downloadSize)) << endl;
        out << i18n("Installed size: %1", KIO::convertSize(installSize)) << endl;
    } else {
        out << i18n("Size of packages about to be removed: %1", KIO::convertSize(installSize)) << endl;
    }
    out.flush();
    
    out << i18n("Continue with processing?[Y/n]");
    out.flush();
    std::string input;
    getline(std::cin, input);
    if (!input.empty() && input != i18n("y").toStdString()) {
        emit finished();
        return;
    }
178 179
    connect(AkabeiClient::Backend::instance()->transactionHandler(), SIGNAL(progressChanged(int)), SLOT(showProgress()));
    connect(AkabeiClient::Backend::instance()->transactionHandler(), SIGNAL(packageProgressChanged(Akabei::Package*,int)), SLOT(packageProgressChanged(Akabei::Package*,int)));
180 181 182 183
    connect(AkabeiClient::Backend::instance()->transactionHandler(), SIGNAL(finished()), SLOT(transationFinished()));
    AkabeiClient::Backend::instance()->transactionHandler()->process();
}

184
void QueueOperation::showProgress()
185
{
186
    //I'm sorry that this method looks a bit complicated, but I really see no other way for this... :/
187
    int progress = AkabeiClient::Backend::instance()->transactionHandler()->progress();
188
    
189 190
    const unsigned int hashlen = getcols();

191
    QString status;
192 193
    switch (AkabeiClient::Backend::instance()->transactionHandler()->phase()) {
        case AkabeiClient::TransactionHandler::Validating:
194
            status = i18n("Validating...");
195 196
            break;
        case AkabeiClient::TransactionHandler::RunningPreHooks:
197
            status = i18n("Running PreHooks...");
198 199
            break;
        case AkabeiClient::TransactionHandler::Downloading:
200
            status = i18n("Downloading...");
201 202
            break;
        case AkabeiClient::TransactionHandler::Processing:
203
            status = i18n("Processing...");
204 205
            break;
        case AkabeiClient::TransactionHandler::RunningScriptlets:
206
            status = i18n("Running Scriplets...");
207 208
            break;
        case AkabeiClient::TransactionHandler::RunningPostHooks:
209
            status = i18n("Running PostHooks...");
210 211
            break;
        case AkabeiClient::TransactionHandler::Cleaning:
212
            status = i18n("Cleaning...");
213 214
            break;
        default:
215
            status = i18n("Finished!");
216 217
            break;
    };
218 219
    QHash<Akabei::Package*, int>::iterator i = m_progresses.begin();
    QHash<Akabei::Package*, int>::iterator end = m_progresses.end();
220 221 222 223
    QStringList additional;
    int counter = 1;
    int additionalLenght = 0;
    QString pkgs;
224
    for (; i != end; i++) {
225 226 227 228 229
        QString pkgOutput = " | " + i.key()->name() + "%" + QString::number(counter);
        additional << QString(" " + QString(i.key()->version().toByteArray().data()) + " (" + QString::number(i.value()) + "%)");
        additionalLenght = additionalLenght + additional.last().length();
        pkgs += pkgOutput;
        counter++;
230
    }
231 232
    
    QString startOut =  " | " + i18n("Total progress:") + " [";
233 234 235
    
    QString endOut = "] " + QString::number(progress) + "%";
    
236
    int pBarLength = startOut.length() + endOut.length() + 10;
237
    
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
    bool showPBar = false;
    QString output;
    output += status;
    int length = output.length();
    if ((output.length() + pBarLength) < hashlen) {
        length = length + pBarLength;
        showPBar = true;
    }
    if ((length + pkgs.length() - (additional.count() * 2)) <= hashlen) {
        counter = 1;
        foreach (const QString &add, additional) {
            if ((length + pkgs.length() + add.length() - 2) <= hashlen) { //-2 is the argument e.g. "%1"
                pkgs.replace("%" + QString::number(counter), add);
            } else {
                pkgs.replace("%" + QString::number(counter), QString());
            }
            counter++;
255
        }
256
        output += pkgs;
257
    }
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
    
    if (showPBar) {
        QString pBarOut = startOut;
        int pBar = qMax(10, (int)(hashlen - output.length() - endOut.length() - startOut.length()));
        
        const unsigned int hash = progress * pBar / 100;
        for (unsigned int i = pBar; i > 1; --i) {
            /* if special progress bar enabled */
            if (i > pBar - hash) {
                pBarOut += "#";
            } else {
                pBarOut += "-";
            }
        }
        pBarOut += endOut;
        output += pBarOut;
    }
    
    QTextStream out(stdout);
    out.reset();
    
    out << output;
    out.flush();
    
282 283 284 285 286 287 288
    if (progress == 100) {
        out << endl;
    } else {
        out << "\r";
    }
}

289 290 291 292 293 294 295 296 297 298
void QueueOperation::packageProgressChanged(Akabei::Package* package, int progress)
{
    foreach (Akabei::Package * p, m_progresses.keys()) { //TODO: Optimize... only execute like once in a while ;)s
        if (AkabeiClient::Backend::instance()->transactionHandler()->processedPackages().contains(p))
            m_progresses.remove(p);
    }
    m_progresses.insert(package, progress);
    showProgress();
}

299 300 301 302 303 304 305 306 307
void QueueOperation::transationFinished()
{
    QTextStream out(stdout);
    if (Akabei::Backend::instance()->operationRunner()->errors().isEmpty()) {
        out << i18n("The transaction finished successfully!") << endl;
    } else {
        out << i18np("There has been an error during the transaction.", "There have been errors during the transaction.", Akabei::Backend::instance()->operationRunner()->errors().count());
    }
    emit finished();
Lukas Appelhans's avatar
Lukas Appelhans committed
308
//    QCoreApplication::instance()->quit();
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
}

void QueueOperation::errors(QList<Akabei::Error*>& errors)
{
    kDebug() << "yoyoyo";
    QTextStream out(stdout);
    out << i18n("Errors occurred: ") << endl;
    foreach (Akabei::Error * err, errors) {
        out << err->description() << endl;
    }
    out.flush();
}

void QueueOperation::transactionMessage(const QString& message)
{
    QTextStream out(stdout);
    out << message << endl;
}