diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 761722894..94d7975f5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,408 +1,409 @@
 # SPDX-FileCopyrightText: none
 # SPDX-License-Identifier: BSD-3-Clause
 add_subdirectory(icons)
 add_subdirectory(mimetypes)
 
 include_directories(${CMAKE_CURRENT_BINARY_DIR})
 include_directories(${CMAKE_CURRENT_SOURCE_DIR})
 
 if (NOT DISABLE_KWATCHGNUPG)
     add_subdirectory(kwatchgnupg)
 endif()
 add_subdirectory(libkleopatraclient)
 add_subdirectory(conf)
 add_subdirectory(kconf_update)
 
 if(WIN32)
   set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_win.cpp)
   set(_kleopatra_extra_SRCS
       utils/gnupg-registry.c
       selftest/registrycheck.cpp
       utils/windowsprocessdevice.cpp
       utils/userinfo_win.cpp
   )
 else()
   set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_unix.cpp)
   set(_kleopatra_extra_SRCS)
 endif()
 
 set(_kleopatra_uiserver_SRCS
     uiserver/sessiondata.cpp
     uiserver/uiserver.cpp
     ${_kleopatra_extra_uiserver_SRCS}
     uiserver/assuanserverconnection.cpp
     uiserver/echocommand.cpp
     uiserver/decryptverifycommandemailbase.cpp
     uiserver/decryptverifycommandfilesbase.cpp
     uiserver/signcommand.cpp
     uiserver/signencryptfilescommand.cpp
     uiserver/prepencryptcommand.cpp
     uiserver/prepsigncommand.cpp
     uiserver/encryptcommand.cpp
     uiserver/selectcertificatecommand.cpp
     uiserver/importfilescommand.cpp
     uiserver/createchecksumscommand.cpp
     uiserver/verifychecksumscommand.cpp
 
     selftest/uiservercheck.cpp
   )
 
 if(ASSUAN2_FOUND)
   include_directories(${ASSUAN2_INCLUDES})
   set(_kleopatra_uiserver_extra_libs ${ASSUAN2_LIBRARIES})
 else()
   include_directories(${ASSUAN_INCLUDES})
   if(WIN32)
     set(_kleopatra_uiserver_extra_libs ${ASSUAN_VANILLA_LIBRARIES})
   else()
     set(_kleopatra_uiserver_extra_libs ${ASSUAN_PTHREAD_LIBRARIES})
   endif()
 endif()
 
 if(HAVE_GPG_ERR_SOURCE_KLEO)
   add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO)
   add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO)
 else()
   add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1)
   add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1)
 endif()
 
 if(KF5IdentityManagement_FOUND AND KF5MailTransport_FOUND AND KF5MailTransportAkonadi_FOUND)
   set(_kleopatra_mail_libs
       KF5::IdentityManagement # Export OpenPGP keys using WKS
       KF5::MailTransport
       KF5::MailTransportAkonadi
   )
   add_definitions(-DMAILAKONADI_ENABLED)
 endif()
 
 ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui)
 
 set(_kleopatra_SRCS
   utils/accessibility.cpp
   utils/gui-helper.cpp
   utils/filedialog.cpp
   utils/kdpipeiodevice.cpp
   utils/headerview.cpp
   utils/scrollarea.cpp
   utils/dragqueen.cpp
   utils/multivalidator.cpp
   utils/systemtrayicon.cpp
 
   utils/path-helper.cpp
   utils/input.cpp
   utils/output.cpp
   utils/validation.cpp
   utils/wsastarter.cpp
   utils/iodevicelogger.cpp
   utils/log.cpp
   utils/action_data.cpp
   utils/types.cpp
   utils/archivedefinition.cpp
   utils/auditlog.cpp
   utils/clipboardmenu.cpp
   utils/kuniqueservice.cpp
   utils/tags.cpp
   utils/writecertassuantransaction.cpp
   utils/keyparameters.cpp
   utils/userinfo.cpp
   utils/keys.cpp
 
   selftest/selftest.cpp
   selftest/enginecheck.cpp
   selftest/gpgconfcheck.cpp
   selftest/gpgagentcheck.cpp
   selftest/libkleopatrarccheck.cpp
   selftest/compliancecheck.cpp
 
   ${_kleopatra_extra_SRCS}
 
   view/errorlabel.cpp
   view/formtextinput.cpp
   view/htmllabel.cpp
   view/keylistcontroller.cpp
   view/keytreeview.cpp
   view/searchbar.cpp
   view/smartcardwidget.cpp
   view/openpgpkeycardwidget.cpp
   view/padwidget.cpp
   view/pgpcardwidget.cpp
   view/pivcardwidget.cpp
   view/p15cardwidget.cpp
   view/netkeywidget.cpp
   view/nullpinwidget.cpp
   view/tabwidget.cpp
   view/keycacheoverlay.cpp
   view/urllabel.cpp
   view/waitwidget.cpp
   view/welcomewidget.cpp
 
   dialogs/certificateselectiondialog.cpp
   dialogs/certifywidget.cpp
   dialogs/expirydialog.cpp
   dialogs/lookupcertificatesdialog.cpp
   dialogs/ownertrustdialog.cpp
   dialogs/selftestdialog.cpp
   dialogs/certifycertificatedialog.cpp
   dialogs/revokecertificationwidget.cpp
   dialogs/revokecertificationdialog.cpp
   dialogs/adduseriddialog.cpp
   dialogs/deletecertificatesdialog.cpp
   dialogs/setinitialpindialog.cpp
   dialogs/certificatedetailsdialog.cpp
   dialogs/certificatedetailswidget.cpp
   dialogs/trustchainwidget.cpp
   dialogs/weboftrustwidget.cpp
   dialogs/weboftrustdialog.cpp
   dialogs/exportdialog.cpp
   dialogs/subkeyswidget.cpp
   dialogs/gencardkeydialog.cpp
   dialogs/updatenotification.cpp
   dialogs/pivcardapplicationadministrationkeyinputdialog.cpp
   dialogs/certificatedetailsinputwidget.cpp
   dialogs/createcsrforcardkeydialog.cpp
   dialogs/groupdetailsdialog.cpp
   dialogs/editgroupdialog.cpp
   dialogs/revokekeydialog.cpp
 
   crypto/controller.cpp
   crypto/certificateresolver.cpp
   crypto/sender.cpp
   crypto/recipient.cpp
   crypto/task.cpp
   crypto/taskcollection.cpp
   crypto/decryptverifytask.cpp
   crypto/decryptverifyemailcontroller.cpp
   crypto/decryptverifyfilescontroller.cpp
   crypto/autodecryptverifyfilescontroller.cpp
   crypto/encryptemailtask.cpp
   crypto/encryptemailcontroller.cpp
   crypto/newsignencryptemailcontroller.cpp
   crypto/signencrypttask.cpp
   crypto/signencryptfilescontroller.cpp
   crypto/signemailtask.cpp
   crypto/signemailcontroller.cpp
   crypto/createchecksumscontroller.cpp
   crypto/verifychecksumscontroller.cpp
+  crypto/checksumsutils_p.cpp
 
   crypto/gui/wizard.cpp
   crypto/gui/wizardpage.cpp
 
   crypto/gui/certificateselectionline.cpp
   crypto/gui/certificatelineedit.cpp
   crypto/gui/signingcertificateselectionwidget.cpp
   crypto/gui/signingcertificateselectiondialog.cpp
 
   crypto/gui/resultitemwidget.cpp
   crypto/gui/resultlistwidget.cpp
   crypto/gui/resultpage.cpp
 
   crypto/gui/newresultpage.cpp
   crypto/gui/signencryptfileswizard.cpp
 
   crypto/gui/signencryptemailconflictdialog.cpp
 
   crypto/gui/decryptverifyoperationwidget.cpp
   crypto/gui/decryptverifyfileswizard.cpp
   crypto/gui/decryptverifyfilesdialog.cpp
 
   crypto/gui/objectspage.cpp
   crypto/gui/resolverecipientspage.cpp
   crypto/gui/signerresolvepage.cpp
   crypto/gui/encryptemailwizard.cpp
   crypto/gui/signemailwizard.cpp
   crypto/gui/signencryptwidget.cpp
   crypto/gui/signencryptwizard.cpp
   crypto/gui/unknownrecipientwidget.cpp
 
   crypto/gui/verifychecksumsdialog.cpp
 
   commands/command.cpp
   commands/gnupgprocesscommand.cpp
   commands/detailscommand.cpp
   commands/exportcertificatecommand.cpp
   commands/exportgroupscommand.cpp
   commands/importcertificatescommand.cpp
   commands/importcertificatefromfilecommand.cpp
   commands/importcertificatefromclipboardcommand.cpp
   commands/importcertificatefromdatacommand.cpp
   commands/importcertificatefromkeyservercommand.cpp
   commands/lookupcertificatescommand.cpp
   commands/reloadkeyscommand.cpp
   commands/refreshcertificatecommand.cpp
   commands/refreshx509certscommand.cpp
   commands/refreshopenpgpcertscommand.cpp
   commands/deletecertificatescommand.cpp
   commands/decryptverifyfilescommand.cpp
   commands/signencryptfilescommand.cpp
   commands/signencryptfoldercommand.cpp
   commands/encryptclipboardcommand.cpp
   commands/signclipboardcommand.cpp
   commands/decryptverifyclipboardcommand.cpp
   commands/clearcrlcachecommand.cpp
   commands/dumpcrlcachecommand.cpp
   commands/dumpcertificatecommand.cpp
   commands/importcrlcommand.cpp
   commands/changeexpirycommand.cpp
   commands/changeownertrustcommand.cpp
   commands/changeroottrustcommand.cpp
   commands/changepassphrasecommand.cpp
   commands/certifycertificatecommand.cpp
   commands/revokecertificationcommand.cpp
   commands/selftestcommand.cpp
   commands/exportsecretkeycommand.cpp
   commands/exportsecretkeycommand_old.cpp
   commands/exportsecretsubkeycommand.cpp
   commands/exportopenpgpcertstoservercommand.cpp
   commands/exportopenpgpcerttoprovidercommand.cpp
   commands/adduseridcommand.cpp
   commands/newcertificatecommand.cpp
   commands/setinitialpincommand.cpp
   commands/learncardkeyscommand.cpp
   commands/checksumcreatefilescommand.cpp
   commands/checksumverifyfilescommand.cpp
   commands/exportpaperkeycommand.cpp
   commands/importpaperkeycommand.cpp
   commands/genrevokecommand.cpp
   commands/keytocardcommand.cpp
   commands/cardcommand.cpp
   commands/pivgeneratecardkeycommand.cpp
   commands/changepincommand.cpp
   commands/authenticatepivcardapplicationcommand.cpp
   commands/setpivcardapplicationadministrationkeycommand.cpp
   commands/certificatetopivcardcommand.cpp
   commands/importcertificatefrompivcardcommand.cpp
   commands/createopenpgpkeyfromcardkeyscommand.cpp
   commands/createcsrforcardkeycommand.cpp
   commands/revokekeycommand.cpp
   commands/revokeuseridcommand.cpp
 
   ${_kleopatra_uiserver_files}
 
   conf/configuredialog.cpp
   conf/groupsconfigdialog.cpp
   conf/groupsconfigpage.cpp
   conf/groupsconfigwidget.cpp
 
   newcertificatewizard/advancedsettingsdialog.cpp
   newcertificatewizard/chooseprotocolpage.cpp
   newcertificatewizard/enterdetailspage.cpp
   newcertificatewizard/keyalgo.cpp
   newcertificatewizard/keycreationpage.cpp
   newcertificatewizard/listwidget.cpp
   newcertificatewizard/newcertificatewizard.cpp
   newcertificatewizard/resultpage.cpp
   newcertificatewizard/wizardpage.cpp
 
   smartcard/readerstatus.cpp
   smartcard/card.cpp
   smartcard/openpgpcard.cpp
   smartcard/netkeycard.cpp
   smartcard/pivcard.cpp
   smartcard/p15card.cpp
   smartcard/keypairinfo.cpp
   smartcard/utils.cpp
   smartcard/deviceinfowatcher.cpp
 
   accessibility/accessiblerichtextlabel.cpp
   accessibility/accessiblewidgetfactory.cpp
 
   aboutdata.cpp
   systrayicon.cpp
   kleopatraapplication.cpp
   mainwindow.cpp
   main.cpp
   kleopatra.qrc
 )
 
 if(WIN32)
   configure_file (versioninfo.rc.in versioninfo.rc)
   set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc ${_kleopatra_SRCS})
 endif()
 
 set (_kleopatra_SRCS conf/kleopageconfigdialog.cpp ${_kleopatra_SRCS})
 
 ecm_qt_declare_logging_category(_kleopatra_SRCS HEADER kleopatra_debug.h IDENTIFIER KLEOPATRA_LOG CATEGORY_NAME org.kde.pim.kleopatra
         DESCRIPTION "kleopatra (kleopatra)"
         OLD_CATEGORY_NAMES log_kleopatra
         EXPORT KLEOPATRA
     )
 
 
 if(KLEO_MODEL_TEST)
   add_definitions(-DKLEO_MODEL_TEST)
   set(_kleopatra_SRCS ${_kleopatra_SRCS} models/modeltest.cpp)
 endif()
 
 ki18n_wrap_ui(_kleopatra_SRCS
   dialogs/ownertrustdialog.ui
   dialogs/selectchecklevelwidget.ui
   dialogs/selftestdialog.ui
   dialogs/setinitialpindialog.ui
   dialogs/trustchainwidget.ui
   dialogs/subkeyswidget.ui
   newcertificatewizard/listwidget.ui
 )
 
 kconfig_add_kcfg_files(_kleopatra_SRCS
   kcfg/tooltippreferences.kcfgc
   kcfg/emailoperationspreferences.kcfgc
   kcfg/fileoperationspreferences.kcfgc
   kcfg/smimevalidationpreferences.kcfgc
   kcfg/tagspreferences.kcfgc
   kcfg/settings.kcfgc
 )
 
 file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kleopatra.png")
 ecm_add_app_icon(_kleopatra_SRCS ICONS ${ICONS_SRCS})
 
 add_executable(kleopatra_bin ${_kleopatra_SRCS} ${_kleopatra_uiserver_SRCS})
 
 # For the ConfigureDialog & KCMs
 target_link_libraries(kleopatra_bin kcm_kleopatra_static)
 
 #if (COMPILE_WITH_UNITY_CMAKE_SUPPORT)
 #    set_target_properties(kleopatra_bin PROPERTIES UNITY_BUILD ON)
 #endif()
 set_target_properties(kleopatra_bin PROPERTIES OUTPUT_NAME kleopatra)
 
 if (WIN32)
   set(_kleopatra_platform_libs "secur32")
 endif ()
 
 target_link_libraries(kleopatra_bin
   Gpgmepp
   QGpgme
   ${_kleopatra_extra_libs}
   KF5::Libkleo
   KF5::Mime
   KF5::I18n
   KF5::XmlGui
   KF5::IconThemes
   KF5::WindowSystem
   KF5::CoreAddons
   KF5::ItemModels
   KF5::Crash
   ${_kleopatra_mail_libs}
   Qt${QT_MAJOR_VERSION}::Network
   Qt${QT_MAJOR_VERSION}::PrintSupport # Printing secret keys
   ${_kleopatra_uiserver_extra_libs}
   ${_kleopatra_dbusaddons_libs}
   kleopatraclientcore
   ${_kleopatra_platform_libs}
 )
 
 install(TARGETS kleopatra_bin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
 
 install(
   PROGRAMS data/org.kde.kleopatra.desktop data/kleopatra_import.desktop
   DESTINATION ${KDE_INSTALL_APPDIR}
 )
 install(FILES data/org.kde.kleopatra.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
 install(
   PROGRAMS data/kleopatra_signencryptfiles.desktop
         data/kleopatra_signencryptfolders.desktop
         data/kleopatra_decryptverifyfiles.desktop
         data/kleopatra_decryptverifyfolders.desktop
   DESTINATION ${KDE_INSTALL_DATADIR}/kio/servicemenus
 )
diff --git a/src/crypto/checksumsutils_p.h b/src/crypto/checksumsutils_p.cpp
similarity index 71%
copy from src/crypto/checksumsutils_p.h
copy to src/crypto/checksumsutils_p.cpp
index 1b1e61221..6b213c73b 100644
--- a/src/crypto/checksumsutils_p.h
+++ b/src/crypto/checksumsutils_p.cpp
@@ -1,136 +1,108 @@
 /* -*- mode: c++; c-basic-offset:4 -*-
-    crypto/checksumsutils_p.h
+    crypto/checksumsutils_p.cpp
 
     This file is part of Kleopatra, the KDE keymanager
     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
-#pragma once
-
+#include "checksumsutils_p.h"
 #include <Libkleo/ChecksumDefinition>
 
 #include "kleopatra_debug.h"
 
 #include <QFile>
-#include <QRegularExpression>
 #include <QTextStream>
 
-#ifdef Q_OS_UNIX
-// Can we use QAbstractFileEngine::caseSensitive()?
-static const Qt::CaseSensitivity fs_cs = Qt::CaseSensitive;
-
-static const QRegularExpression::PatternOption s_regex_cs = QRegularExpression::NoPatternOption;
-#else
-static const Qt::CaseSensitivity fs_cs = Qt::CaseInsensitive;
-static const QRegularExpression::PatternOption s_regex_cs = QRegularExpression::CaseInsensitiveOption;
-#endif
-
-static std::vector<QRegularExpression> get_patterns(const std::vector<std::shared_ptr<Kleo::ChecksumDefinition>> &checksumDefinitions)
+std::vector<QRegularExpression> ChecksumsUtils::get_patterns(const std::vector<std::shared_ptr<Kleo::ChecksumDefinition>> &checksumDefinitions)
 {
     std::vector<QRegularExpression> result;
     for (const auto &cd : checksumDefinitions) {
         if (!cd) {
             continue;
         }
         const QStringList &patterns = cd->patterns();
         result.reserve(result.size() + patterns.size());
         std::transform(patterns.cbegin(), patterns.cend(), std::back_inserter(result), [](const QString &pattern) {
             return QRegularExpression(QRegularExpression::anchoredPattern(pattern), s_regex_cs);
         });
     }
 
     return result;
 }
 
-struct matches_any : std::unary_function<QString, bool> {
-    const std::vector<QRegularExpression> m_regexps;
-    explicit matches_any(const std::vector<QRegularExpression> &regexps) : m_regexps(regexps) {}
-    bool operator()(const QString &s) const
-    {
-        return std::any_of(m_regexps.cbegin(), m_regexps.cend(),
-                           [&s](const QRegularExpression &rx) { return rx.match(s).hasMatch(); });
-    }
-};
-
-struct File {
-    QString name;
-    QByteArray checksum;
-    bool binary;
-};
-
 static QString decode(const QString &encoded)
 {
     QString decoded;
     decoded.reserve(encoded.size());
     bool shift = false;
     for (const QChar ch : encoded)
         if (shift) {
             switch (ch.toLatin1()) {
             case '\\': decoded += QLatin1Char('\\'); break;
             case 'n':  decoded += QLatin1Char('\n'); break;
             default:
                 qCDebug(KLEOPATRA_LOG) << "invalid escape sequence" << '\\' << ch << "(interpreted as '" << ch << "')";
                 decoded += ch;
                 break;
             }
             shift = false;
         } else {
             if (ch == QLatin1Char('\\')) {
                 shift = true;
             } else {
                 decoded += ch;
             }
         }
     return decoded;
 }
 
-static std::vector<File> parse_sum_file(const QString &fileName)
+std::vector<ChecksumsUtils::File> ChecksumsUtils::parse_sum_file(const QString &fileName)
 {
     std::vector<File> files;
     QFile f(fileName);
     if (!f.open(QIODevice::ReadOnly)) {
         return {};
     }
 
     QTextStream s(&f);
     static const QRegularExpression rx(QRegularExpression::anchoredPattern(uR"((\\?)([a-f0-9A-F]+) ([ *])([^\\n]+)\\n*)"));
     while (!s.atEnd()) {
         const QString line = s.readLine();
         QRegularExpressionMatch match = rx.match(line);
         if (!match.hasMatch()) {
             continue;
         }
 
         Q_ASSERT(!match.capturedView(4).endsWith(QLatin1Char('\n')));
         const File file = {
             match.capturedView(1) == QLatin1Char('\\') ? decode(match.captured(4)) : match.captured(4),
             match.capturedView(2).toLatin1(),
             match.capturedView(3) == QLatin1Char('*'),
         };
         files.push_back(file);
     }
 
     return files;
 }
 
-static std::shared_ptr<Kleo::ChecksumDefinition> filename2definition(const QString &fileName,
+std::shared_ptr<Kleo::ChecksumDefinition> ChecksumsUtils::filename2definition(const QString &fileName,
         const std::vector<std::shared_ptr<Kleo::ChecksumDefinition>> &checksumDefinitions)
 {
     auto matchFileName = [&fileName](const std::shared_ptr<Kleo::ChecksumDefinition> &cd) {
         if (!cd) {
             return false;
         }
 
         const QStringList &patterns = cd->patterns();
         return std::any_of(patterns.cbegin(), patterns.cend(), [&fileName](const QString &pattern) {
             const QRegularExpression re(QRegularExpression::anchoredPattern(pattern), s_regex_cs);
             return re.match(fileName).hasMatch();
         });
     };
 
     auto it = std::find_if(checksumDefinitions.cbegin(), checksumDefinitions.cend(), matchFileName);
 
     return it != checksumDefinitions.cend() ? *it : std::shared_ptr<Kleo::ChecksumDefinition>{};
 }
diff --git a/src/crypto/checksumsutils_p.h b/src/crypto/checksumsutils_p.h
index 1b1e61221..1e89f84a5 100644
--- a/src/crypto/checksumsutils_p.h
+++ b/src/crypto/checksumsutils_p.h
@@ -1,136 +1,55 @@
 /* -*- mode: c++; c-basic-offset:4 -*-
     crypto/checksumsutils_p.h
 
     This file is part of Kleopatra, the KDE keymanager
     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
 #pragma once
 
-#include <Libkleo/ChecksumDefinition>
-
 #include "kleopatra_debug.h"
 
-#include <QFile>
 #include <QRegularExpression>
-#include <QTextStream>
 
+namespace Kleo
+{
+class ChecksumDefinition;
+}
+
+namespace ChecksumsUtils {
 #ifdef Q_OS_UNIX
 // Can we use QAbstractFileEngine::caseSensitive()?
 static const Qt::CaseSensitivity fs_cs = Qt::CaseSensitive;
 
 static const QRegularExpression::PatternOption s_regex_cs = QRegularExpression::NoPatternOption;
 #else
 static const Qt::CaseSensitivity fs_cs = Qt::CaseInsensitive;
 static const QRegularExpression::PatternOption s_regex_cs = QRegularExpression::CaseInsensitiveOption;
 #endif
 
-static std::vector<QRegularExpression> get_patterns(const std::vector<std::shared_ptr<Kleo::ChecksumDefinition>> &checksumDefinitions)
-{
-    std::vector<QRegularExpression> result;
-    for (const auto &cd : checksumDefinitions) {
-        if (!cd) {
-            continue;
-        }
-        const QStringList &patterns = cd->patterns();
-        result.reserve(result.size() + patterns.size());
-        std::transform(patterns.cbegin(), patterns.cend(), std::back_inserter(result), [](const QString &pattern) {
-            return QRegularExpression(QRegularExpression::anchoredPattern(pattern), s_regex_cs);
-        });
-    }
-
-    return result;
-}
+std::vector<QRegularExpression> get_patterns(const std::vector<std::shared_ptr<Kleo::ChecksumDefinition>> &checksumDefinitions);
 
 struct matches_any : std::unary_function<QString, bool> {
     const std::vector<QRegularExpression> m_regexps;
     explicit matches_any(const std::vector<QRegularExpression> &regexps) : m_regexps(regexps) {}
     bool operator()(const QString &s) const
     {
         return std::any_of(m_regexps.cbegin(), m_regexps.cend(),
                            [&s](const QRegularExpression &rx) { return rx.match(s).hasMatch(); });
     }
 };
 
 struct File {
     QString name;
     QByteArray checksum;
     bool binary;
 };
 
-static QString decode(const QString &encoded)
-{
-    QString decoded;
-    decoded.reserve(encoded.size());
-    bool shift = false;
-    for (const QChar ch : encoded)
-        if (shift) {
-            switch (ch.toLatin1()) {
-            case '\\': decoded += QLatin1Char('\\'); break;
-            case 'n':  decoded += QLatin1Char('\n'); break;
-            default:
-                qCDebug(KLEOPATRA_LOG) << "invalid escape sequence" << '\\' << ch << "(interpreted as '" << ch << "')";
-                decoded += ch;
-                break;
-            }
-            shift = false;
-        } else {
-            if (ch == QLatin1Char('\\')) {
-                shift = true;
-            } else {
-                decoded += ch;
-            }
-        }
-    return decoded;
-}
-
-static std::vector<File> parse_sum_file(const QString &fileName)
-{
-    std::vector<File> files;
-    QFile f(fileName);
-    if (!f.open(QIODevice::ReadOnly)) {
-        return {};
-    }
-
-    QTextStream s(&f);
-    static const QRegularExpression rx(QRegularExpression::anchoredPattern(uR"((\\?)([a-f0-9A-F]+) ([ *])([^\\n]+)\\n*)"));
-    while (!s.atEnd()) {
-        const QString line = s.readLine();
-        QRegularExpressionMatch match = rx.match(line);
-        if (!match.hasMatch()) {
-            continue;
-        }
+std::vector<File> parse_sum_file(const QString &fileName);
 
-        Q_ASSERT(!match.capturedView(4).endsWith(QLatin1Char('\n')));
-        const File file = {
-            match.capturedView(1) == QLatin1Char('\\') ? decode(match.captured(4)) : match.captured(4),
-            match.capturedView(2).toLatin1(),
-            match.capturedView(3) == QLatin1Char('*'),
-        };
-        files.push_back(file);
-    }
-
-    return files;
-}
+std::shared_ptr<Kleo::ChecksumDefinition> filename2definition(const QString &fileName,
+        const std::vector<std::shared_ptr<Kleo::ChecksumDefinition>> &checksumDefinitions);
 
-static std::shared_ptr<Kleo::ChecksumDefinition> filename2definition(const QString &fileName,
-        const std::vector<std::shared_ptr<Kleo::ChecksumDefinition>> &checksumDefinitions)
-{
-    auto matchFileName = [&fileName](const std::shared_ptr<Kleo::ChecksumDefinition> &cd) {
-        if (!cd) {
-            return false;
-        }
-
-        const QStringList &patterns = cd->patterns();
-        return std::any_of(patterns.cbegin(), patterns.cend(), [&fileName](const QString &pattern) {
-            const QRegularExpression re(QRegularExpression::anchoredPattern(pattern), s_regex_cs);
-            return re.match(fileName).hasMatch();
-        });
-    };
-
-    auto it = std::find_if(checksumDefinitions.cbegin(), checksumDefinitions.cend(), matchFileName);
-
-    return it != checksumDefinitions.cend() ? *it : std::shared_ptr<Kleo::ChecksumDefinition>{};
-}
+} // namespace ChecksumsUtils
diff --git a/src/crypto/createchecksumscontroller.cpp b/src/crypto/createchecksumscontroller.cpp
index a7bfb23d3..8568581dd 100644
--- a/src/crypto/createchecksumscontroller.cpp
+++ b/src/crypto/createchecksumscontroller.cpp
@@ -1,609 +1,610 @@
 /* -*- mode: c++; c-basic-offset:4 -*-
     crypto/createchecksumscontroller.cpp
 
     This file is part of Kleopatra, the KDE keymanager
     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
 #include <config-kleopatra.h>
 
 #include "createchecksumscontroller.h"
 #include "checksumsutils_p.h"
 
 #include <utils/input.h>
 #include <utils/output.h>
 #include <utils/kleo_assert.h>
 
 #include <Libkleo/Stl_Util>
+#include <Libkleo/ChecksumDefinition>
 #include <Libkleo/Classify>
 
 #include <KLocalizedString>
 #include <QTemporaryFile>
 #include <KConfigGroup>
 #include <KSharedConfig>
 
 #include <QDialog>
 #include <QDialogButtonBox>
 #include <QLabel>
 #include <QListWidget>
 #include <QVBoxLayout>
 
 #include <QPointer>
 #include <QFileInfo>
 #include <QThread>
 #include <QMutex>
 #include <QProgressDialog>
 #include <QDir>
 #include <QProcess>
 
 #include <gpg-error.h>
 
 #include <deque>
 #include <map>
 #include <limits>
 #include <functional>
 
 using namespace Kleo;
 using namespace Kleo::Crypto;
 
 namespace
 {
 
 class ResultDialog : public QDialog
 {
     Q_OBJECT
 public:
     ResultDialog(const QStringList &created, const QStringList &errors, QWidget *parent = nullptr, Qt::WindowFlags f = {})
         : QDialog(parent, f),
           createdLB(created.empty()
                     ? i18nc("@info", "No checksum files have been created.")
                     : i18nc("@info", "These checksum files have been successfully created:"), this),
           createdLW(this),
           errorsLB(errors.empty()
                    ? i18nc("@info", "There were no errors.")
                    : i18nc("@info", "The following errors were encountered:"), this),
           errorsLW(this),
           buttonBox(QDialogButtonBox::Ok, Qt::Horizontal, this),
           vlay(this)
     {
         KDAB_SET_OBJECT_NAME(createdLB);
         KDAB_SET_OBJECT_NAME(createdLW);
         KDAB_SET_OBJECT_NAME(errorsLB);
         KDAB_SET_OBJECT_NAME(errorsLW);
         KDAB_SET_OBJECT_NAME(buttonBox);
         KDAB_SET_OBJECT_NAME(vlay);
 
         createdLW.addItems(created);
         QRect r;
         for (int i = 0; i < created.size(); ++i) {
             r = r.united(createdLW.visualRect(createdLW.model()->index(0, i)));
         }
         createdLW.setMinimumWidth(qMin(1024, r.width() + 4 * createdLW.frameWidth()));
 
         errorsLW.addItems(errors);
 
         vlay.addWidget(&createdLB);
         vlay.addWidget(&createdLW, 1);
         vlay.addWidget(&errorsLB);
         vlay.addWidget(&errorsLW, 1);
         vlay.addWidget(&buttonBox);
 
         if (created.empty()) {
             createdLW.hide();
         }
         if (errors.empty()) {
             errorsLW.hide();
         }
 
         connect(&buttonBox, &QDialogButtonBox::accepted, this, &ResultDialog::accept);
         connect(&buttonBox, &QDialogButtonBox::rejected, this, &ResultDialog::reject);
         readConfig();
     }
     ~ResultDialog() override
     {
         writeConfig();
     }
 
     void readConfig()
     {
         KConfigGroup dialog(KSharedConfig::openStateConfig(), "ResultDialog");
         const QSize size = dialog.readEntry("Size", QSize(600, 400));
         if (size.isValid()) {
             resize(size);
         }
     }
     void writeConfig()
     {
         KConfigGroup dialog(KSharedConfig::openStateConfig(), "ResultDialog");
         dialog.writeEntry("Size", size());
         dialog.sync();
     }
 
 private:
     QLabel createdLB;
     QListWidget createdLW;
     QLabel errorsLB;
     QListWidget errorsLW;
     QDialogButtonBox buttonBox;
     QVBoxLayout vlay;
 };
 
 }
 
 static QStringList fs_sort(QStringList l)
 {
     std::sort(l.begin(), l.end(), [](const QString &lhs, const QString &rhs) {
-                                    return QString::compare(lhs, rhs, fs_cs) < 0;
+                                    return QString::compare(lhs, rhs, ChecksumsUtils::fs_cs) < 0;
                                   });
     return l;
 }
 
 static QStringList fs_intersect(QStringList l1, QStringList l2)
 {
     fs_sort(l1);
     fs_sort(l2);
     QStringList result;
     std::set_intersection(l1.begin(), l1.end(),
                           l2.begin(), l2.end(),
                           std::back_inserter(result),
                           [](const QString &lhs, const QString &rhs) {
-                              return QString::compare(lhs, rhs, fs_cs) < 0;
+                              return QString::compare(lhs, rhs, ChecksumsUtils::fs_cs) < 0;
                           });
     return result;
 }
 
 class CreateChecksumsController::Private : public QThread
 {
     Q_OBJECT
     friend class ::Kleo::Crypto::CreateChecksumsController;
     CreateChecksumsController *const q;
 public:
     explicit Private(CreateChecksumsController *qq);
     ~Private() override;
 
 Q_SIGNALS:
     void progress(int, int, const QString &);
 
 private:
     void slotOperationFinished()
     {
 #ifndef QT_NO_PROGRESSDIALOG
         if (progressDialog) {
             progressDialog->setValue(progressDialog->maximum());
             progressDialog->close();
         }
 #endif // QT_NO_PROGRESSDIALOG
         auto const dlg = new ResultDialog(created, errors);
         dlg->setAttribute(Qt::WA_DeleteOnClose);
         q->bringToForeground(dlg);
         if (!errors.empty())
             q->setLastError(gpg_error(GPG_ERR_GENERAL),
                             errors.join(QLatin1Char('\n')));
         q->emitDoneOrError();
     }
     void slotProgress(int current, int total, const QString &what)
     {
         qCDebug(KLEOPATRA_LOG) << "progress: " << current << "/" << total << ": " << qPrintable(what);
 #ifndef QT_NO_PROGRESSDIALOG
         if (!progressDialog) {
             return;
         }
         progressDialog->setMaximum(total);
         progressDialog->setValue(current);
         progressDialog->setLabelText(what);
 #endif // QT_NO_PROGRESSDIALOG
     }
 
 private:
     void run() override;
 
 private:
 #ifndef QT_NO_PROGRESSDIALOG
     QPointer<QProgressDialog> progressDialog;
 #endif
     mutable QMutex mutex;
     const std::vector< std::shared_ptr<ChecksumDefinition> > checksumDefinitions;
     std::shared_ptr<ChecksumDefinition> checksumDefinition;
     QStringList files;
     QStringList errors, created;
     bool allowAddition;
     volatile bool canceled;
 };
 
 CreateChecksumsController::Private::Private(CreateChecksumsController *qq)
     : q(qq),
 #ifndef QT_NO_PROGRESSDIALOG
       progressDialog(),
 #endif
       mutex(),
       checksumDefinitions(ChecksumDefinition::getChecksumDefinitions()),
       checksumDefinition(ChecksumDefinition::getDefaultChecksumDefinition(checksumDefinitions)),
       files(),
       errors(),
       created(),
       allowAddition(false),
       canceled(false)
 {
     connect(this, SIGNAL(progress(int,int,QString)),
             q, SLOT(slotProgress(int,int,QString)));
     connect(this, &Private::progress,
             q, &Controller::progress);
     connect(this, SIGNAL(finished()),
             q, SLOT(slotOperationFinished()));
 }
 
 CreateChecksumsController::Private::~Private()
 {
     qCDebug(KLEOPATRA_LOG);
 }
 
 CreateChecksumsController::CreateChecksumsController(QObject *p)
     : Controller(p), d(new Private(this))
 {
 
 }
 
 CreateChecksumsController::CreateChecksumsController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *p)
     : Controller(ctx, p), d(new Private(this))
 {
 
 }
 
 CreateChecksumsController::~CreateChecksumsController()
 {
     qCDebug(KLEOPATRA_LOG);
 }
 
 void CreateChecksumsController::setFiles(const QStringList &files)
 {
     kleo_assert(!d->isRunning());
     kleo_assert(!files.empty());
-    const std::vector<QRegularExpression> patterns = get_patterns(d->checksumDefinitions);
-    if (!std::all_of(files.cbegin(), files.cend(), matches_any(patterns)) &&
-            !std::none_of(files.cbegin(), files.cend(), matches_any(patterns))) {
+    const std::vector<QRegularExpression> patterns = ChecksumsUtils::get_patterns(d->checksumDefinitions);
+    if (!std::all_of(files.cbegin(), files.cend(), ChecksumsUtils::matches_any(patterns)) &&
+            !std::none_of(files.cbegin(), files.cend(), ChecksumsUtils::matches_any(patterns))) {
         throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Create Checksums: input files must be either all checksum files or all files to be checksummed, not a mixture of both."));
     }
     const QMutexLocker locker(&d->mutex);
     d->files = files;
 }
 
 void CreateChecksumsController::setAllowAddition(bool allow)
 {
     kleo_assert(!d->isRunning());
     const QMutexLocker locker(&d->mutex);
     d->allowAddition = allow;
 }
 
 bool CreateChecksumsController::allowAddition() const
 {
     const QMutexLocker locker(&d->mutex);
     return d->allowAddition;
 }
 
 void CreateChecksumsController::start()
 {
 
     {
         const QMutexLocker locker(&d->mutex);
 
 #ifndef QT_NO_PROGRESSDIALOG
         d->progressDialog = new QProgressDialog(i18n("Initializing..."), i18n("Cancel"), 0, 0);
         applyWindowID(d->progressDialog);
         d->progressDialog->setAttribute(Qt::WA_DeleteOnClose);
         d->progressDialog->setMinimumDuration(1000);
         d->progressDialog->setWindowTitle(i18nc("@title:window", "Create Checksum Progress"));
         connect(d->progressDialog.data(), &QProgressDialog::canceled, this, &CreateChecksumsController::cancel);
 #endif // QT_NO_PROGRESSDIALOG
 
         d->canceled = false;
         d->errors.clear();
         d->created.clear();
     }
 
     d->start();
 
 }
 
 void CreateChecksumsController::cancel()
 {
     qCDebug(KLEOPATRA_LOG);
     const QMutexLocker locker(&d->mutex);
     d->canceled = true;
 }
 
 namespace
 {
 
 struct Dir {
     QDir dir;
     QString sumFile;
     QStringList inputFiles;
     quint64 totalSize;
     std::shared_ptr<ChecksumDefinition> checksumDefinition;
 };
 
 }
 
 static QStringList remove_checksum_files(QStringList l, const std::vector<QRegularExpression> &rxs)
 {
     QStringList::iterator end = l.end();
     for (const auto &rx : rxs) {
         end = std::remove_if(l.begin(), end,
                              [rx](const QString &str) {
                                 return rx.match(str).hasMatch();
                              });
     }
     l.erase(end, l.end());
     return l;
 }
 
 static quint64 aggregate_size(const QDir &dir, const QStringList &files)
 {
     quint64 n = 0;
     for (const QString &file : files) {
         n += QFileInfo(dir.absoluteFilePath(file)).size();
     }
     return n;
 }
 
 static std::vector<Dir> find_dirs_by_sum_files(const QStringList &files, bool allowAddition,
         const std::function<void(int)> &progress,
         const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions)
 {
 
-    const std::vector<QRegularExpression> patterns = get_patterns(checksumDefinitions);
+    const std::vector<QRegularExpression> patterns = ChecksumsUtils::get_patterns(checksumDefinitions);
 
     std::vector<Dir> dirs;
     dirs.reserve(files.size());
 
     int i = 0;
 
     for (const QString &file : files) {
 
         const QFileInfo fi(file);
         const QDir dir = fi.dir();
         const QStringList entries = remove_checksum_files(dir.entryList(QDir::Files), patterns);
 
         QStringList inputFiles;
         if (allowAddition) {
             inputFiles = entries;
         } else {
-            const std::vector<File> parsed = parse_sum_file(fi.absoluteFilePath());
+            const std::vector<ChecksumsUtils::File> parsed = ChecksumsUtils::parse_sum_file(fi.absoluteFilePath());
             QStringList oldInputFiles;
             oldInputFiles.reserve(parsed.size());
             std::transform(parsed.cbegin(), parsed.cend(), std::back_inserter(oldInputFiles),
-                           std::mem_fn(&File::name));
+                           std::mem_fn(&ChecksumsUtils::File::name));
             inputFiles = fs_intersect(oldInputFiles, entries);
         }
 
         const Dir item = {
             dir,
             fi.fileName(),
             inputFiles,
             aggregate_size(dir, inputFiles),
-            filename2definition(fi.fileName(), checksumDefinitions)
+            ChecksumsUtils::filename2definition(fi.fileName(), checksumDefinitions)
         };
 
         dirs.push_back(item);
 
         if (progress) {
             progress(++i);
         }
 
     }
     return dirs;
 }
 
 namespace
 {
 struct less_dir : std::binary_function<QDir, QDir, bool> {
     bool operator()(const QDir &lhs, const QDir &rhs) const
     {
-        return QString::compare(lhs.absolutePath(), rhs.absolutePath(), fs_cs) < 0;
+        return QString::compare(lhs.absolutePath(), rhs.absolutePath(), ChecksumsUtils::fs_cs) < 0;
     }
 };
 }
 
 static std::vector<Dir> find_dirs_by_input_files(const QStringList &files, const std::shared_ptr<ChecksumDefinition> &checksumDefinition, bool allowAddition,
         const std::function<void(int)> &progress,
         const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions)
 {
     Q_UNUSED(allowAddition)
     if (!checksumDefinition) {
         return std::vector<Dir>();
     }
 
-    const std::vector<QRegularExpression> patterns = get_patterns(checksumDefinitions);
+    const std::vector<QRegularExpression> patterns = ChecksumsUtils::get_patterns(checksumDefinitions);
 
     std::map<QDir, QStringList, less_dir> dirs2files;
 
     // Step 1: sort files by the dir they're contained in:
 
     std::deque<QString> inputs(files.begin(), files.end());
 
     int i = 0;
     while (!inputs.empty()) {
         const QString file = inputs.front();
         inputs.pop_front();
         const QFileInfo fi(file);
         if (fi.isDir()) {
             QDir dir(file);
             dirs2files[ dir ] = remove_checksum_files(dir.entryList(QDir::Files), patterns);
             const auto entryList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
             std::transform(entryList.cbegin(), entryList.cend(),
                            std::inserter(inputs, inputs.begin()),
                            [&dir](const QString &entry) {
                                return dir.absoluteFilePath(entry);
                            });
         } else {
             dirs2files[fi.dir()].push_back(file);
         }
         if (progress) {
             progress(++i);
         }
     }
 
     // Step 2: convert into vector<Dir>:
 
     std::vector<Dir> dirs;
     dirs.reserve(dirs2files.size());
 
     for (auto it = dirs2files.begin(), end = dirs2files.end(); it != end; ++it) {
 
         const QStringList inputFiles = remove_checksum_files(it->second, patterns);
         if (inputFiles.empty()) {
             continue;
         }
 
         const Dir dir = {
             it->first,
             checksumDefinition->outputFileName(),
             inputFiles,
             aggregate_size(it->first, inputFiles),
             checksumDefinition
         };
         dirs.push_back(dir);
 
         if (progress) {
             progress(++i);
         }
 
     }
     return dirs;
 }
 
 static QString process(const Dir &dir, bool *fatal)
 {
     const QString absFilePath = dir.dir.absoluteFilePath(dir.sumFile);
     QTemporaryFile out;
     QProcess p;
     if (!out.open()) {
         return QStringLiteral("Failed to open Temporary file.");
     }
     p.setWorkingDirectory(dir.dir.absolutePath());
     p.setStandardOutputFile(out.fileName());
     const QString program = dir.checksumDefinition->createCommand();
     dir.checksumDefinition->startCreateCommand(&p, dir.inputFiles);
     p.waitForFinished();
     qCDebug(KLEOPATRA_LOG) << "[" << &p << "] Exit code " << p.exitCode();
 
     if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) {
         if (fatal && p.error() == QProcess::FailedToStart) {
             *fatal = true;
         }
         if (p.error() == QProcess::UnknownError)
             return i18n("Error while running %1: %2", program,
                         QString::fromLocal8Bit(p.readAllStandardError().trimmed().constData()));
         else {
             return i18n("Failed to execute %1: %2", program, p.errorString());
         }
     }
 
     QFileInfo fi(absFilePath);
     if (!(fi.exists() && !QFile::remove(absFilePath)) && QFile::copy(out.fileName(), absFilePath)) {
         return QString();
     }
 
     return xi18n("Failed to overwrite <filename>%1</filename>.", dir.sumFile);
 }
 
 namespace
 {
 static QDebug operator<<(QDebug s, const Dir &dir)
 {
     return s << "Dir(" << dir.dir << "->" << dir.sumFile << "<-(" << dir.totalSize << ')' << dir.inputFiles << ")\n";
 }
 }
 
 void CreateChecksumsController::Private::run()
 {
 
     QMutexLocker locker(&mutex);
 
     const QStringList files = this->files;
     const std::vector< std::shared_ptr<ChecksumDefinition> > checksumDefinitions = this->checksumDefinitions;
     const std::shared_ptr<ChecksumDefinition> checksumDefinition = this->checksumDefinition;
     const bool allowAddition = this->allowAddition;
 
     locker.unlock();
 
     QStringList errors;
     QStringList created;
 
     if (!checksumDefinition) {
         errors.push_back(i18n("No checksum programs defined."));
         locker.relock();
         this->errors = errors;
         return;
     } else {
         qCDebug(KLEOPATRA_LOG) << "using checksum-definition" << checksumDefinition->id();
     }
 
     //
     // Step 1: build a list of work to do (no progress):
     //
 
     const QString scanning = i18n("Scanning directories...");
     Q_EMIT progress(0, 0, scanning);
 
-    const bool haveSumFiles = std::all_of(files.cbegin(), files.cend(), matches_any(get_patterns(checksumDefinitions)));
+    const bool haveSumFiles = std::all_of(files.cbegin(), files.cend(), ChecksumsUtils::matches_any(ChecksumsUtils::get_patterns(checksumDefinitions)));
     const auto progressCb = [this, &scanning](int c) { Q_EMIT progress(c, 0, scanning); };
     const std::vector<Dir> dirs = haveSumFiles
                                   ? find_dirs_by_sum_files(files, allowAddition, progressCb, checksumDefinitions)
                                   : find_dirs_by_input_files(files, checksumDefinition, allowAddition, progressCb, checksumDefinitions);
 
     for (const Dir &dir : dirs) {
         qCDebug(KLEOPATRA_LOG) << dir;
     }
 
     if (!canceled) {
 
         Q_EMIT progress(0, 0, i18n("Calculating total size..."));
 
         const quint64 total = kdtools::accumulate_transform(dirs.cbegin(), dirs.cend(),
                                                             std::mem_fn(&Dir::totalSize),
                                                             Q_UINT64_C(0));
 
         if (!canceled) {
 
             //
             // Step 2: perform work (with progress reporting):
             //
 
             // re-scale 'total' to fit into ints (wish QProgressDialog would use quint64...)
             const quint64 factor = total / std::numeric_limits<int>::max() + 1;
 
             quint64 done = 0;
             for (const Dir &dir : dirs) {
                 Q_EMIT progress(done / factor, total / factor,
                                 i18n("Checksumming (%2) in %1", dir.checksumDefinition->label(), dir.dir.path()));
                 bool fatal = false;
                 const QString error = process(dir, &fatal);
                 if (!error.isEmpty()) {
                     errors.push_back(error);
                 } else {
                     created.push_back(dir.dir.absoluteFilePath(dir.sumFile));
                 }
                 done += dir.totalSize;
                 if (fatal || canceled) {
                     break;
                 }
             }
             Q_EMIT progress(done / factor, total / factor, i18n("Done."));
 
         }
     }
 
     locker.relock();
 
     this->errors = errors;
     this->created = created;
 
     // mutex unlocked by QMutexLocker
 
 }
 
 #include "moc_createchecksumscontroller.cpp"
 #include "createchecksumscontroller.moc"
diff --git a/src/crypto/verifychecksumscontroller.cpp b/src/crypto/verifychecksumscontroller.cpp
index e84f307b8..25f8edf3a 100644
--- a/src/crypto/verifychecksumscontroller.cpp
+++ b/src/crypto/verifychecksumscontroller.cpp
@@ -1,586 +1,587 @@
 /* -*- mode: c++; c-basic-offset:4 -*-
     crypto/verifychecksumscontroller.cpp
 
     This file is part of Kleopatra, the KDE keymanager
     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
 
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
 #include <config-kleopatra.h>
 
 #include "verifychecksumscontroller.h"
 #include "checksumsutils_p.h"
 
 #ifndef QT_NO_DIRMODEL
 
 #include <crypto/gui/verifychecksumsdialog.h>
 
 #include <utils/input.h>
 #include <utils/output.h>
 #include <utils/kleo_assert.h>
 
 #include <Libkleo/Stl_Util>
+#include <Libkleo/ChecksumDefinition>
 #include <Libkleo/Classify>
 
 #include <KLocalizedString>
 
 #include <QPointer>
 #include <QFileInfo>
 #include <QThread>
 #include <QMutex>
 #include <QProgressDialog>
 #include <QDir>
 #include <QProcess>
 
 #include <gpg-error.h>
 
 #include <deque>
 #include <limits>
 #include <set>
 
 using namespace Kleo;
 using namespace Kleo::Crypto;
 using namespace Kleo::Crypto::Gui;
 
 static const QLatin1String CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id");
 
 #if 0
 static QStringList fs_sort(QStringList l)
 {
     int (*QString_compare)(const QString &, const QString &, Qt::CaseSensitivity) = &QString::compare;
     std::sort(l.begin(), l.end(),
               [](const QString &lhs, const QString &rhs) {
-                return QString::compare(lhs, rhs, fs_cs) < 0;
+                return QString::compare(lhs, rhs, ChecksumsUtils::fs_cs) < 0;
               });
     return l;
 }
 
 static QStringList fs_intersect(QStringList l1, QStringList l2)
 {
     int (*QString_compare)(const QString &, const QString &, Qt::CaseSensitivity) = &QString::compare;
     fs_sort(l1);
     fs_sort(l2);
     QStringList result;
     std::set_intersection(l1.begin(), l1.end(),
                           l2.begin(), l2.end(),
                           std::back_inserter(result),
                           [](const QString &lhs, const QString &rhs) {
-                            return QString::compare(lhs, rhs, fs_cs) < 0;
+                            return QString::compare(lhs, rhs, ChecksumsUtils::fs_cs) < 0;
                           });
     return result;
 }
 #endif
 
 namespace {
 struct matches_none_of : std::unary_function<QString, bool> {
     const std::vector<QRegularExpression> m_regexps;
     explicit matches_none_of(const std::vector<QRegularExpression> &regexps) : m_regexps(regexps) {}
     bool operator()(const QString &s) const
     {
         return std::none_of(m_regexps.cbegin(), m_regexps.cend(),
                             [&s](const QRegularExpression &rx) { return rx.match(s).hasMatch(); });
     }
 };
 }
 
 class VerifyChecksumsController::Private : public QThread
 {
     Q_OBJECT
     friend class ::Kleo::Crypto::VerifyChecksumsController;
     VerifyChecksumsController *const q;
 public:
     explicit Private(VerifyChecksumsController *qq);
     ~Private() override;
 
 Q_SIGNALS:
     void baseDirectories(const QStringList &);
     void progress(int, int, const QString &);
     void status(const QString &file, Kleo::Crypto::Gui::VerifyChecksumsDialog::Status);
 
 private:
     void slotOperationFinished()
     {
         if (dialog) {
             dialog->setProgress(100, 100);
             dialog->setErrors(errors);
         }
 
         if (!errors.empty())
             q->setLastError(gpg_error(GPG_ERR_GENERAL),
                             errors.join(QLatin1Char('\n')));
         q->emitDoneOrError();
     }
 
 private:
     void run() override;
 
 private:
     QPointer<VerifyChecksumsDialog> dialog;
     mutable QMutex mutex;
     const std::vector< std::shared_ptr<ChecksumDefinition> > checksumDefinitions;
     QStringList files;
     QStringList errors;
     volatile bool canceled;
 };
 
 VerifyChecksumsController::Private::Private(VerifyChecksumsController *qq)
     : q(qq),
       dialog(),
       mutex(),
       checksumDefinitions(ChecksumDefinition::getChecksumDefinitions()),
       files(),
       errors(),
       canceled(false)
 {
     connect(this, &Private::progress,
             q, &Controller::progress);
     connect(this, SIGNAL(finished()),
             q, SLOT(slotOperationFinished()));
 }
 
 VerifyChecksumsController::Private::~Private()
 {
     qCDebug(KLEOPATRA_LOG);
 }
 
 VerifyChecksumsController::VerifyChecksumsController(QObject *p)
     : Controller(p), d(new Private(this))
 {
 
 }
 
 VerifyChecksumsController::VerifyChecksumsController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *p)
     : Controller(ctx, p), d(new Private(this))
 {
 
 }
 
 VerifyChecksumsController::~VerifyChecksumsController()
 {
     qCDebug(KLEOPATRA_LOG);
 }
 
 void VerifyChecksumsController::setFiles(const QStringList &files)
 {
     kleo_assert(!d->isRunning());
     kleo_assert(!files.empty());
     const QMutexLocker locker(&d->mutex);
     d->files = files;
 }
 
 void VerifyChecksumsController::start()
 {
 
     {
         const QMutexLocker locker(&d->mutex);
 
         d->dialog = new VerifyChecksumsDialog;
         d->dialog->setAttribute(Qt::WA_DeleteOnClose);
         d->dialog->setWindowTitle(i18nc("@title:window", "Verify Checksum Results"));
 
         connect(d->dialog.data(), &VerifyChecksumsDialog::canceled,
                 this, &VerifyChecksumsController::cancel);
         connect(d.get(), &Private::baseDirectories,
                 d->dialog.data(), &VerifyChecksumsDialog::setBaseDirectories);
         connect(d.get(), &Private::progress,
                 d->dialog.data(), &VerifyChecksumsDialog::setProgress);
         connect(d.get(), &Private::status,
                 d->dialog.data(), &VerifyChecksumsDialog::setStatus);
 
         d->canceled = false;
         d->errors.clear();
     }
 
     d->start();
 
     d->dialog->show();
 
 }
 
 void VerifyChecksumsController::cancel()
 {
     qCDebug(KLEOPATRA_LOG);
     const QMutexLocker locker(&d->mutex);
     d->canceled = true;
 }
 
 namespace
 {
 
 struct SumFile {
     QDir dir;
     QString sumFile;
     quint64 totalSize;
     std::shared_ptr<ChecksumDefinition> checksumDefinition;
 };
 
 }
 
 static QStringList filter_checksum_files(QStringList l, const std::vector<QRegularExpression> &rxs)
 {
     l.erase(std::remove_if(l.begin(), l.end(),
                            matches_none_of(rxs)),
             l.end());
     return l;
 }
 
 static quint64 aggregate_size(const QDir &dir, const QStringList &files)
 {
     quint64 n = 0;
     for (const QString &file : files) {
         n += QFileInfo(dir.absoluteFilePath(file)).size();
     }
     return n;
 }
 
 namespace
 {
 struct less_dir : std::binary_function<QDir, QDir, bool> {
     bool operator()(const QDir &lhs, const QDir &rhs) const
     {
-        return QString::compare(lhs.absolutePath(), rhs.absolutePath(), fs_cs) < 0;
+        return QString::compare(lhs.absolutePath(), rhs.absolutePath(), ChecksumsUtils::fs_cs) < 0;
     }
 };
 struct less_file : std::binary_function<QString, QString, bool> {
     bool operator()(const QString &lhs, const QString &rhs) const
     {
-        return QString::compare(lhs, rhs, fs_cs) < 0;
+        return QString::compare(lhs, rhs, ChecksumsUtils::fs_cs) < 0;
     }
 };
 struct sumfile_contains_file : std::unary_function<QString, bool> {
     const QDir dir;
     const QString fileName;
     sumfile_contains_file(const QDir &dir_, const QString &fileName_)
         : dir(dir_), fileName(fileName_) {}
     bool operator()(const QString &sumFile) const
     {
-        const std::vector<File> files = parse_sum_file(dir.absoluteFilePath(sumFile));
+        const std::vector<ChecksumsUtils::File> files = ChecksumsUtils::parse_sum_file(dir.absoluteFilePath(sumFile));
         qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files:      found " << files.size()
                                << " files listed in " << qPrintable(dir.absoluteFilePath(sumFile));
-        for (const File &file : files) {
-            const bool isSameFileName = (QString::compare(file.name, fileName, fs_cs) == 0);
+        for (const ChecksumsUtils::File &file : files) {
+            const bool isSameFileName = (QString::compare(file.name, fileName, ChecksumsUtils::fs_cs) == 0);
             qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files:        "
                                    << qPrintable(file.name) << " == "
                                    << qPrintable(fileName)  << " ? "
                                    << isSameFileName;
             if (isSameFileName) {
                 return true;
             }
         }
         return false;
     }
 };
 
 }
 
 // IF is_dir(file)
 //   add all sumfiles \in dir(file)
 //   inputs.prepend( all dirs \in dir(file) )
 // ELSE IF is_sum_file(file)
 //   add
 // ELSE IF \exists sumfile in dir(file) \where sumfile \contains file
 //   add sumfile
 // ELSE
 //   error: no checksum found for "file"
 
 static QStringList find_base_directories(const QStringList &files)
 {
 
     // Step 1: find base dirs:
 
     std::set<QDir, less_dir> dirs;
     for (const QString &file : files) {
         const QFileInfo fi(file);
         const QDir dir = fi.isDir() ? QDir(file) : fi.dir();
         dirs.insert(dir);
     }
 
     // Step 1a: collapse direct child directories
 
     bool changed;
     do {
         changed = false;
         auto it = dirs.begin();
         while (it != dirs.end()) {
             QDir dir = *it;
             if (dir.cdUp() && dirs.count(dir)) {
                 dirs.erase(it++);
                 changed = true;
             } else {
                 ++it;
             }
         }
     } while (changed);
 
     QStringList rv;
     rv.reserve(dirs.size());
     std::transform(dirs.cbegin(), dirs.cend(), std::back_inserter(rv), std::mem_fn(&QDir::absolutePath));
     return rv;
 }
 
 static std::vector<SumFile> find_sums_by_input_files(const QStringList &files, QStringList &errors,
         const std::function<void(int)> &progress,
         const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions)
 {
-    const std::vector<QRegularExpression> patterns = get_patterns(checksumDefinitions);
+    const std::vector<QRegularExpression> patterns = ChecksumsUtils::get_patterns(checksumDefinitions);
 
-    const matches_any is_sum_file(patterns);
+    const ChecksumsUtils::matches_any is_sum_file(patterns);
 
     std::map<QDir, std::set<QString, less_file>, less_dir> dirs2sums;
 
     // Step 1: find the sumfiles we need to check:
 
     std::deque<QString> inputs(files.begin(), files.end());
 
     int i = 0;
     while (!inputs.empty()) {
         const QString file = inputs.front();
         qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: considering " << qPrintable(file);
         inputs.pop_front();
         const QFileInfo fi(file);
         const QString fileName = fi.fileName();
         if (fi.isDir()) {
             qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files:   it's a directory";
             QDir dir(file);
             const QStringList sumfiles = filter_checksum_files(dir.entryList(QDir::Files), patterns);
             qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files:   found " << sumfiles.size()
                                    << " sum files: " << qPrintable(sumfiles.join(QLatin1String(", ")));
             dirs2sums[ dir ].insert(sumfiles.begin(), sumfiles.end());
             const QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
             qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files:   found " << dirs.size()
                                    << " subdirs, prepending";
             std::transform(dirs.cbegin(), dirs.cend(),
                            std::inserter(inputs, inputs.begin()),
                            [&dir](const QString &path) {
                                return dir.absoluteFilePath(path);
                            });
         } else if (is_sum_file(fileName)) {
             qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files:   it's a sum file";
             dirs2sums[fi.dir()].insert(fileName);
         } else {
             qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files:   it's something else; checking whether we'll find a sumfile for it...";
             const QDir dir = fi.dir();
             const QStringList sumfiles = filter_checksum_files(dir.entryList(QDir::Files), patterns);
             qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files:   found " << sumfiles.size()
                                    << " potential sumfiles: " << qPrintable(sumfiles.join(QLatin1String(", ")));
             const auto it = std::find_if(sumfiles.cbegin(), sumfiles.cend(),
                                          sumfile_contains_file(dir, fileName));
             if (it == sumfiles.end()) {
                 errors.push_back(i18n("Cannot find checksums file for file %1", file));
             } else {
                 dirs2sums[dir].insert(*it);
             }
         }
         if (progress) {
             progress(++i);
         }
     }
 
     // Step 2: convert into vector<SumFile>:
 
     std::vector<SumFile> sumfiles;
     sumfiles.reserve(dirs2sums.size());
 
     for (auto it = dirs2sums.begin(), end = dirs2sums.end(); it != end; ++it) {
 
         if (it->second.empty()) {
             continue;
         }
 
         const QDir &dir = it->first;
 
         for (const QString &sumFileName : std::as_const(it->second)) {
 
-            const std::vector<File> summedfiles = parse_sum_file(dir.absoluteFilePath(sumFileName));
+            const std::vector<ChecksumsUtils::File> summedfiles = ChecksumsUtils::parse_sum_file(dir.absoluteFilePath(sumFileName));
             QStringList files;
             files.reserve(summedfiles.size());
             std::transform(summedfiles.cbegin(), summedfiles.cend(),
-                           std::back_inserter(files), std::mem_fn(&File::name));
+                           std::back_inserter(files), std::mem_fn(&ChecksumsUtils::File::name));
             const SumFile sumFile = {
                 it->first,
                 sumFileName,
                 aggregate_size(it->first, files),
-                filename2definition(sumFileName, checksumDefinitions),
+                ChecksumsUtils::filename2definition(sumFileName, checksumDefinitions),
             };
             sumfiles.push_back(sumFile);
 
         }
 
         if (progress) {
             progress(++i);
         }
 
     }
     return sumfiles;
 }
 
 static QStringList c_lang_environment()
 {
-    static const QRegularExpression re(QRegularExpression::anchoredPattern(u"LANG=.*"), s_regex_cs);
+    static const QRegularExpression re(QRegularExpression::anchoredPattern(u"LANG=.*"), ChecksumsUtils::s_regex_cs);
     QStringList env = QProcess::systemEnvironment();
     env.erase(std::remove_if(env.begin(), env.end(),
                              [](const QString &str) {
                                  return re.match(str).hasMatch();
                              }),
               env.end());
     env.push_back(QStringLiteral("LANG=C"));
     return env;
 }
 
 static const struct {
     const char *string;
     VerifyChecksumsDialog::Status status;
 } statusStrings[] = {
     { "OK",     VerifyChecksumsDialog::OK     },
     { "FAILED", VerifyChecksumsDialog::Failed },
 };
 static const size_t numStatusStrings = sizeof statusStrings / sizeof * statusStrings;
 
 static VerifyChecksumsDialog::Status string2status(const QByteArray &str)
 {
     for (unsigned int i = 0; i < numStatusStrings; ++i)
         if (str == statusStrings[i].string) {
             return statusStrings[i].status;
         }
     return VerifyChecksumsDialog::Unknown;
 }
 
 static QString process(const SumFile &sumFile, bool *fatal, const QStringList &env,
                        const std::function<void(const QString &, VerifyChecksumsDialog::Status)> &status)
 {
     QProcess p;
     p.setEnvironment(env);
     p.setWorkingDirectory(sumFile.dir.absolutePath());
     p.setReadChannel(QProcess::StandardOutput);
 
     const QString absFilePath = sumFile.dir.absoluteFilePath(sumFile.sumFile);
 
     const QString program = sumFile.checksumDefinition->verifyCommand();
     sumFile.checksumDefinition->startVerifyCommand(&p, QStringList(absFilePath));
 
     QByteArray remainder; // used for filenames with newlines in them
     while (p.state() != QProcess::NotRunning) {
         p.waitForReadyRead();
         while (p.canReadLine()) {
             const QByteArray line = p.readLine();
             const int colonIdx = line.lastIndexOf(':');
             if (colonIdx < 0) {
                 remainder += line; // no colon -> probably filename with a newline
                 continue;
             }
             const QString file = QFile::decodeName(remainder + line.left(colonIdx));
             remainder.clear();
             const VerifyChecksumsDialog::Status result = string2status(line.mid(colonIdx + 1).trimmed());
             status(sumFile.dir.absoluteFilePath(file), result);
         }
     }
     qCDebug(KLEOPATRA_LOG) << "[" << &p << "] Exit code " << p.exitCode();
 
     if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) {
         if (fatal && p.error() == QProcess::FailedToStart) {
             *fatal = true;
         }
         if (p.error() == QProcess::UnknownError)
             return i18n("Error while running %1: %2", program,
                         QString::fromLocal8Bit(p.readAllStandardError().trimmed().constData()));
         else {
             return i18n("Failed to execute %1: %2", program, p.errorString());
         }
     }
 
     return QString();
 }
 
 namespace
 {
 static QDebug operator<<(QDebug s, const SumFile &sum)
 {
     return s << "SumFile(" << sum.dir << "->" << sum.sumFile << "<-(" << sum.totalSize << ')' << ")\n";
 }
 }
 
 void VerifyChecksumsController::Private::run()
 {
 
     QMutexLocker locker(&mutex);
 
     const QStringList files = this->files;
     const std::vector< std::shared_ptr<ChecksumDefinition> > checksumDefinitions = this->checksumDefinitions;
 
     locker.unlock();
 
     QStringList errors;
 
     //
     // Step 0: find base directories:
     //
 
     Q_EMIT baseDirectories(find_base_directories(files));
 
     //
     // Step 1: build a list of work to do (no progress):
     //
 
     const QString scanning = i18n("Scanning directories...");
     Q_EMIT progress(0, 0, scanning);
 
     const auto progressCb = [this, scanning](int arg) { Q_EMIT progress(arg, 0, scanning); };
     const auto statusCb = [this](const QString &str, VerifyChecksumsDialog::Status st) { Q_EMIT status(str, st); };
 
     const std::vector<SumFile> sumfiles = find_sums_by_input_files(files, errors, progressCb, checksumDefinitions);
 
     for (const SumFile &sumfile : sumfiles) {
         qCDebug(KLEOPATRA_LOG) << sumfile;
     }
 
     if (!canceled) {
 
         Q_EMIT progress(0, 0, i18n("Calculating total size..."));
 
         const quint64 total
             = kdtools::accumulate_transform(sumfiles.cbegin(), sumfiles.cend(),
                                             std::mem_fn(&SumFile::totalSize), Q_UINT64_C(0));
 
         if (!canceled) {
 
             //
             // Step 2: perform work (with progress reporting):
             //
 
             const QStringList env = c_lang_environment();
 
             // re-scale 'total' to fit into ints (wish QProgressDialog would use quint64...)
             const quint64 factor = total / std::numeric_limits<int>::max() + 1;
 
             quint64 done = 0;
             for (const SumFile &sumFile : sumfiles) {
                 Q_EMIT progress(done / factor, total / factor,
                                 i18n("Verifying checksums (%2) in %1", sumFile.checksumDefinition->label(), sumFile.dir.path()));
                 bool fatal = false;
                 const QString error = process(sumFile, &fatal, env, statusCb);
                 if (!error.isEmpty()) {
                     errors.push_back(error);
                 }
                 done += sumFile.totalSize;
                 if (fatal || canceled) {
                     break;
                 }
             }
             Q_EMIT progress(done / factor, total / factor, i18n("Done."));
 
         }
     }
 
     locker.relock();
 
     this->errors = errors;
 
     // mutex unlocked by QMutexLocker
 
 }
 
 #include "moc_verifychecksumscontroller.cpp"
 #include "verifychecksumscontroller.moc"
 
 #endif // QT_NO_DIRMODEL