1 /* -*- mode: c++; c-basic-offset:4 -*-
2 crypto/verifychecksumscontroller.cpp
4 This file is part of Kleopatra, the KDE keymanager
5 Copyright (c) 2010 Klarälvdalens Datakonsult AB
7 Kleopatra is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 Kleopatra is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 In addition, as a special exception, the copyright holders give
22 permission to link the code of this program with any edition of
23 the Qt library by Trolltech AS, Norway (or with modified versions
24 of Qt that use the same license as Qt), and distribute linked
25 combinations including the two. You must obey the GNU General
26 Public License in all respects for all of the code used other than
27 Qt. If you modify this file, you may extend this exception to
28 your version of the file, but you are not obligated to do so. If
29 you do not wish to do so, delete this exception statement from
33 #include <config-kleopatra.h>
35 #include "verifychecksumscontroller.h"
37 #ifndef QT_NO_DIRMODEL
39 #include <crypto/gui/verifychecksumsdialog.h>
41 #include <utils/input.h>
42 #include <utils/output.h>
43 #include <utils/classify.h>
44 #include <utils/kleo_assert.h>
46 #include <kleo/stl_util.h>
47 #include <kleo/checksumdefinition.h>
49 #include <KLocalizedString>
51 #include "kleopatra_debug.h"
56 #include <QProgressDialog>
60 #include <boost/bind.hpp>
61 #include <boost/function.hpp>
63 #include <gpg-error.h>
70 using namespace Kleo::Crypto
;
71 using namespace Kleo::Crypto::Gui
;
72 using namespace boost
;
75 static const bool HAVE_UNIX
= true;
77 static const bool HAVE_UNIX
= false;
80 static const QLatin1String
CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id");
82 static const Qt::CaseSensitivity fs_cs
= HAVE_UNIX
? Qt::CaseSensitive
: Qt::CaseInsensitive
; // can we use QAbstractFileEngine::caseSensitive()?
85 static QStringList
fs_sort(QStringList l
)
87 int (*QString_compare
)(const QString
&, const QString
&, Qt::CaseSensitivity
) = &QString::compare
;
88 kdtools::sort(l
, boost::bind(QString_compare
, _1
, _2
, fs_cs
) < 0);
92 static QStringList
fs_intersect(QStringList l1
, QStringList l2
)
94 int (*QString_compare
)(const QString
&, const QString
&, Qt::CaseSensitivity
) = &QString::compare
;
98 std::set_intersection(l1
.begin(), l1
.end(),
100 std::back_inserter(result
),
101 boost::bind(QString_compare
, _1
, _2
, fs_cs
) < 0);
106 static QList
<QRegExp
> get_patterns(const std::vector
< shared_ptr
<ChecksumDefinition
> > &checksumDefinitions
)
108 QList
<QRegExp
> result
;
109 Q_FOREACH (const shared_ptr
<ChecksumDefinition
> &cd
, checksumDefinitions
)
111 Q_FOREACH (const QString
&pattern
, cd
->patterns()) {
112 result
.push_back(QRegExp(pattern
, fs_cs
));
119 struct matches_any
: std::unary_function
<QString
, bool> {
120 const QList
<QRegExp
> m_regexps
;
121 explicit matches_any(const QList
<QRegExp
> ®exps
) : m_regexps(regexps
) {}
122 bool operator()(const QString
&s
) const
124 return kdtools::any(m_regexps
, boost::bind(&QRegExp::exactMatch
, _1
, s
));
127 struct matches_none_of
: std::unary_function
<QString
, bool> {
128 const QList
<QRegExp
> m_regexps
;
129 explicit matches_none_of(const QList
<QRegExp
> ®exps
) : m_regexps(regexps
) {}
130 bool operator()(const QString
&s
) const
132 return kdtools::none_of(m_regexps
, boost::bind(&QRegExp::exactMatch
, _1
, s
));
137 class VerifyChecksumsController::Private
: public QThread
140 friend class ::Kleo::Crypto::VerifyChecksumsController
;
141 VerifyChecksumsController
*const q
;
143 explicit Private(VerifyChecksumsController
*qq
);
147 void baseDirectories(const QStringList
&);
148 void progress(int, int, const QString
&);
149 void status(const QString
&file
, Kleo::Crypto::Gui::VerifyChecksumsDialog::Status
);
152 void slotOperationFinished()
155 dialog
->setProgress(100, 100);
156 dialog
->setErrors(errors
);
160 q
->setLastError(gpg_error(GPG_ERR_GENERAL
),
161 errors
.join(QLatin1String("\n")));
162 q
->emitDoneOrError();
166 void run() Q_DECL_OVERRIDE
;
169 QPointer
<VerifyChecksumsDialog
> dialog
;
170 mutable QMutex mutex
;
171 const std::vector
< shared_ptr
<ChecksumDefinition
> > checksumDefinitions
;
174 volatile bool canceled
;
177 VerifyChecksumsController::Private::Private(VerifyChecksumsController
*qq
)
181 checksumDefinitions(ChecksumDefinition::getChecksumDefinitions()),
186 connect(this, SIGNAL(progress(int,int,QString
)),
187 q
, SIGNAL(progress(int,int,QString
)));
188 connect(this, SIGNAL(finished()),
189 q
, SLOT(slotOperationFinished()));
192 VerifyChecksumsController::Private::~Private()
194 qCDebug(KLEOPATRA_LOG
);
197 VerifyChecksumsController::VerifyChecksumsController(QObject
*p
)
198 : Controller(p
), d(new Private(this))
203 VerifyChecksumsController::VerifyChecksumsController(const shared_ptr
<const ExecutionContext
> &ctx
, QObject
*p
)
204 : Controller(ctx
, p
), d(new Private(this))
209 VerifyChecksumsController::~VerifyChecksumsController()
211 qCDebug(KLEOPATRA_LOG
);
214 void VerifyChecksumsController::setFiles(const QStringList
&files
)
216 kleo_assert(!d
->isRunning());
217 kleo_assert(!files
.empty());
218 const QMutexLocker
locker(&d
->mutex
);
222 void VerifyChecksumsController::start()
226 const QMutexLocker
locker(&d
->mutex
);
228 d
->dialog
= new VerifyChecksumsDialog
;
229 d
->dialog
->setAttribute(Qt::WA_DeleteOnClose
);
230 d
->dialog
->setWindowTitle(i18nc("@title:window", "Verify Checksum Results"));
232 connect(d
->dialog
, SIGNAL(canceled()),
233 this, SLOT(cancel()));
234 connect(d
.get(), SIGNAL(baseDirectories(QStringList
)),
235 d
->dialog
, SLOT(setBaseDirectories(QStringList
)));
236 connect(d
.get(), SIGNAL(progress(int,int,QString
)),
237 d
->dialog
, SLOT(setProgress(int,int)));
238 connect(d
.get(), SIGNAL(status(QString
,Kleo::Crypto::Gui::VerifyChecksumsDialog::Status
)),
239 d
->dialog
, SLOT(setStatus(QString
,Kleo::Crypto::Gui::VerifyChecksumsDialog::Status
)));
251 void VerifyChecksumsController::cancel()
253 qCDebug(KLEOPATRA_LOG
);
254 const QMutexLocker
locker(&d
->mutex
);
265 shared_ptr
<ChecksumDefinition
> checksumDefinition
;
270 static QStringList
filter_checksum_files(QStringList l
, const QList
<QRegExp
> &rxs
)
272 l
.erase(std::remove_if(l
.begin(), l
.end(),
273 matches_none_of(rxs
)),
287 static QString
decode(const QString
&encoded
)
290 decoded
.reserve(encoded
.size());
292 Q_FOREACH (const QChar ch
, encoded
)
294 switch (ch
.toLatin1()) {
295 case '\\': decoded
+= QLatin1Char('\\'); break;
296 case 'n': decoded
+= QLatin1Char('\n'); break;
298 qCDebug(KLEOPATRA_LOG
) << "invalid escape sequence" << '\\' << ch
<< "(interpreted as '" << ch
<< "')";
304 if (ch
== QLatin1Char('\\')) {
313 static std::vector
<File
> parse_sum_file(const QString
&fileName
)
315 std::vector
<File
> files
;
317 if (f
.open(QIODevice::ReadOnly
)) {
319 QRegExp
rx(QLatin1String("(\\?)([a-f0-9A-F]+) ([ *])([^\n]+)\n*"));
321 const QString line
= s
.readLine();
322 if (rx
.exactMatch(line
)) {
323 assert(!rx
.cap(4).endsWith(QLatin1Char('\n')));
325 rx
.cap(1) == QLatin1String("\\") ? decode(rx
.cap(4)) : rx
.cap(4),
326 rx
.cap(2).toLatin1(),
327 rx
.cap(3) == QLatin1String("*"),
329 files
.push_back(file
);
336 static quint64
aggregate_size(const QDir
&dir
, const QStringList
&files
)
339 Q_FOREACH (const QString
&file
, files
) {
340 n
+= QFileInfo(dir
.absoluteFilePath(file
)).size();
345 static shared_ptr
<ChecksumDefinition
> filename2definition(const QString
&fileName
,
346 const std::vector
< shared_ptr
<ChecksumDefinition
> > &checksumDefinitions
)
348 Q_FOREACH (const shared_ptr
<ChecksumDefinition
> &cd
, checksumDefinitions
)
350 Q_FOREACH (const QString
&pattern
, cd
->patterns())
351 if (QRegExp(pattern
, fs_cs
).exactMatch(fileName
)) {
354 return shared_ptr
<ChecksumDefinition
>();
359 struct less_dir
: std::binary_function
<QDir
, QDir
, bool> {
360 bool operator()(const QDir
&lhs
, const QDir
&rhs
) const
362 return QString::compare(lhs
.absolutePath(), rhs
.absolutePath(), fs_cs
) < 0 ;
365 struct less_file
: std::binary_function
<QString
, QString
, bool> {
366 bool operator()(const QString
&lhs
, const QString
&rhs
) const
368 return QString::compare(lhs
, rhs
, fs_cs
) < 0 ;
371 struct sumfile_contains_file
: std::unary_function
<QString
, bool> {
373 const QString fileName
;
374 sumfile_contains_file(const QDir
&dir_
, const QString
&fileName_
)
375 : dir(dir_
), fileName(fileName_
) {}
376 bool operator()(const QString
&sumFile
) const
378 const std::vector
<File
> files
= parse_sum_file(dir
.absoluteFilePath(sumFile
));
379 qCDebug(KLEOPATRA_LOG
) << "find_sums_by_input_files: found " << files
.size()
380 << " files listed in " << qPrintable(dir
.absoluteFilePath(sumFile
));
381 Q_FOREACH (const File
&file
, files
) {
382 const bool isSameFileName
= (QString::compare(file
.name
, fileName
, fs_cs
) == 0);
383 qCDebug(KLEOPATRA_LOG
) << "find_sums_by_input_files: "
384 << qPrintable(file
.name
) << " == "
385 << qPrintable(fileName
) << " ? "
387 if (isSameFileName
) {
398 // add all sumfiles \in dir(file)
399 // inputs.prepend( all dirs \in dir(file) )
400 // ELSE IF is_sum_file(file)
402 // ELSE IF \exists sumfile in dir(file) \where sumfile \contains file
405 // error: no checksum found for "file"
407 static QStringList
find_base_directiories(const QStringList
&files
)
410 // Step 1: find base dirs:
412 std::set
<QDir
, less_dir
> dirs
;
413 Q_FOREACH (const QString
&file
, files
) {
414 const QFileInfo
fi(file
);
415 const QDir dir
= fi
.isDir() ? QDir(file
) : fi
.dir() ;
419 // Step 1a: collapse direct child directories
424 std::set
<QDir
, less_dir
>::iterator it
= dirs
.begin();
425 while (it
!= dirs
.end()) {
427 if (dir
.cdUp() && dirs
.count(dir
)) {
436 return kdtools::transform
<QStringList
>(dirs
, mem_fn(&QDir::absolutePath
));
439 static std::vector
<SumFile
> find_sums_by_input_files(const QStringList
&files
, QStringList
&errors
,
440 const function
<void(int)> &progress
,
441 const std::vector
< shared_ptr
<ChecksumDefinition
> > &checksumDefinitions
)
443 const QList
<QRegExp
> patterns
= get_patterns(checksumDefinitions
);
445 const matches_any
is_sum_file(patterns
);
447 std::map
<QDir
, std::set
<QString
, less_file
>, less_dir
> dirs2sums
;
449 // Step 1: find the sumfiles we need to check:
451 std::deque
<QString
> inputs(files
.begin(), files
.end());
454 while (!inputs
.empty()) {
455 const QString file
= inputs
.front();
456 qCDebug(KLEOPATRA_LOG
) << "find_sums_by_input_files: considering " << qPrintable(file
);
458 const QFileInfo
fi(file
);
459 const QString fileName
= fi
.fileName();
461 qCDebug(KLEOPATRA_LOG
) << "find_sums_by_input_files: it's a directory";
463 const QStringList sumfiles
= filter_checksum_files(dir
.entryList(QDir::Files
), patterns
);
464 qCDebug(KLEOPATRA_LOG
) << "find_sums_by_input_files: found " << sumfiles
.size()
465 << " sum files: " << qPrintable(sumfiles
.join(QStringLiteral(", ")));
466 dirs2sums
[ dir
].insert(sumfiles
.begin(), sumfiles
.end());
467 const QStringList dirs
= dir
.entryList(QDir::Dirs
| QDir::NoDotAndDotDot
);
468 qCDebug(KLEOPATRA_LOG
) << "find_sums_by_input_files: found " << dirs
.size()
469 << " subdirs, prepending";
470 kdtools::transform(dirs
,
471 std::inserter(inputs
, inputs
.begin()),
472 boost::bind(&QDir::absoluteFilePath
, cref(dir
), _1
));
473 } else if (is_sum_file(fileName
)) {
474 qCDebug(KLEOPATRA_LOG
) << "find_sums_by_input_files: it's a sum file";
475 dirs2sums
[fi
.dir()].insert(fileName
);
477 qCDebug(KLEOPATRA_LOG
) << "find_sums_by_input_files: it's something else; checking whether we'll find a sumfile for it...";
478 const QDir dir
= fi
.dir();
479 const QStringList sumfiles
= filter_checksum_files(dir
.entryList(QDir::Files
), patterns
);
480 qCDebug(KLEOPATRA_LOG
) << "find_sums_by_input_files: found " << sumfiles
.size()
481 << " potential sumfiles: " << qPrintable(sumfiles
.join(QStringLiteral(", ")));
482 const QStringList::const_iterator it
= kdtools::find_if(sumfiles
, sumfile_contains_file(dir
, fileName
));
483 if (it
== sumfiles
.end()) {
484 errors
.push_back(i18n("Cannot find checksums file for file %1", file
));
486 dirs2sums
[dir
].insert(*it
);
489 if (!progress
.empty()) {
494 // Step 2: convert into vector<SumFile>:
496 std::vector
<SumFile
> sumfiles
;
497 sumfiles
.reserve(dirs2sums
.size());
499 for (std::map
<QDir
, std::set
<QString
, less_file
>, less_dir
>::const_iterator it
= dirs2sums
.begin(), end
= dirs2sums
.end() ; it
!= end
; ++it
) {
501 if (it
->second
.empty()) {
505 const QDir
&dir
= it
->first
;
507 Q_FOREACH (const QString
&sumFileName
, it
->second
) {
509 const std::vector
<File
> summedfiles
= parse_sum_file(dir
.absoluteFilePath(sumFileName
));
511 const SumFile sumFile
= {
514 aggregate_size(it
->first
, kdtools::transform
<QStringList
>(summedfiles
, mem_fn(&File::name
))),
515 filename2definition(sumFileName
, checksumDefinitions
),
517 sumfiles
.push_back(sumFile
);
521 if (!progress
.empty()) {
529 static QStringList
c_lang_environment()
531 QStringList env
= QProcess::systemEnvironment();
532 env
.erase(std::remove_if(env
.begin(), env
.end(),
533 boost::bind(&QRegExp::exactMatch
,
534 QRegExp(QLatin1String("^LANG=.*"), fs_cs
), _1
)),
536 env
.push_back(QLatin1String("LANG=C"));
540 static const struct {
542 VerifyChecksumsDialog::Status status
;
543 } statusStrings
[] = {
544 { "OK", VerifyChecksumsDialog::OK
},
545 { "FAILED", VerifyChecksumsDialog::Failed
},
547 static const size_t numStatusStrings
= sizeof statusStrings
/ sizeof * statusStrings
;
549 static VerifyChecksumsDialog::Status
string2status(const QByteArray
&str
)
551 for (unsigned int i
= 0 ; i
< numStatusStrings
; ++i
)
552 if (str
== statusStrings
[i
].string
) {
553 return statusStrings
[i
].status
;
555 return VerifyChecksumsDialog::Unknown
;
558 static QString
process(const SumFile
&sumFile
, bool *fatal
, const QStringList
&env
,
559 const function
<void(const QString
&, VerifyChecksumsDialog::Status
)> &status
)
562 p
.setEnvironment(env
);
563 p
.setWorkingDirectory(sumFile
.dir
.absolutePath());
564 p
.setReadChannel(QProcess::StandardOutput
);
566 const QString absFilePath
= sumFile
.dir
.absoluteFilePath(sumFile
.sumFile
);
568 const QString program
= sumFile
.checksumDefinition
->verifyCommand();
569 sumFile
.checksumDefinition
->startVerifyCommand(&p
, QStringList(absFilePath
));
571 QByteArray remainder
; // used for filenames with newlines in them
572 while (p
.state() != QProcess::NotRunning
) {
573 p
.waitForReadyRead();
574 while (p
.canReadLine()) {
575 const QByteArray line
= p
.readLine();
576 const int colonIdx
= line
.lastIndexOf(':');
578 remainder
+= line
; // no colon -> probably filename with a newline
581 const QString file
= QFile::decodeName(remainder
+ line
.left(colonIdx
));
583 const VerifyChecksumsDialog::Status result
= string2status(line
.mid(colonIdx
+ 1).trimmed());
584 status(sumFile
.dir
.absoluteFilePath(file
), result
);
587 qCDebug(KLEOPATRA_LOG
) << "[" << &p
<< "] Exit code " << p
.exitCode();
589 if (p
.exitStatus() != QProcess::NormalExit
|| p
.exitCode() != 0) {
590 if (fatal
&& p
.error() == QProcess::FailedToStart
) {
593 if (p
.error() == QProcess::UnknownError
)
594 return i18n("Error while running %1: %2", program
,
595 QString::fromLocal8Bit(p
.readAllStandardError().trimmed().constData()));
597 return i18n("Failed to execute %1: %2", program
, p
.errorString());
606 static QDebug
operator<<(QDebug s
, const SumFile
&sum
)
608 return s
<< "SumFile(" << sum
.dir
<< "->" << sum
.sumFile
<< "<-(" << sum
.totalSize
<< ')' << ")\n";
612 void VerifyChecksumsController::Private::run()
615 QMutexLocker
locker(&mutex
);
617 const QStringList files
= this->files
;
618 const std::vector
< shared_ptr
<ChecksumDefinition
> > checksumDefinitions
= this->checksumDefinitions
;
625 // Step 0: find base directories:
628 Q_EMIT
baseDirectories(find_base_directiories(files
));
631 // Step 1: build a list of work to do (no progress):
634 const QString scanning
= i18n("Scanning directories...");
635 Q_EMIT
progress(0, 0, scanning
);
637 const function
<void(int)> progressCb
= boost::bind(&Private::progress
, this, _1
, 0, scanning
);
638 const function
<void(const QString
&, VerifyChecksumsDialog::Status
)>
639 statusCb
= boost::bind(&Private::status
, this, _1
, _2
);
640 const std::vector
<SumFile
> sumfiles
= find_sums_by_input_files(files
, errors
, progressCb
, checksumDefinitions
);
642 Q_FOREACH (const SumFile
&sumfile
, sumfiles
) {
643 qCDebug(KLEOPATRA_LOG
) << sumfile
;
648 Q_EMIT
progress(0, 0, i18n("Calculating total size..."));
651 = kdtools::accumulate_transform(sumfiles
, mem_fn(&SumFile::totalSize
), Q_UINT64_C(0));
656 // Step 2: perform work (with progress reporting):
659 const QStringList env
= c_lang_environment();
661 // re-scale 'total' to fit into ints (wish QProgressDialog would use quint64...)
662 const quint64 factor
= total
/ std::numeric_limits
<int>::max() + 1 ;
665 Q_FOREACH (const SumFile
&sumFile
, sumfiles
) {
666 Q_EMIT
progress(done
/ factor
, total
/ factor
,
667 i18n("Verifying checksums (%2) in %1", sumFile
.checksumDefinition
->label(), sumFile
.dir
.path()));
669 const QString error
= process(sumFile
, &fatal
, env
, statusCb
);
670 if (!error
.isEmpty()) {
671 errors
.push_back(error
);
673 done
+= sumFile
.totalSize
;
674 if (fatal
|| canceled
) {
678 Q_EMIT
progress(done
/ factor
, total
/ factor
, i18n("Done."));
685 this->errors
= errors
;
687 // mutex unlocked by QMutexLocker
691 #include "moc_verifychecksumscontroller.cpp"
692 #include "verifychecksumscontroller.moc"
694 #endif // QT_NO_DIRMODEL