File indexing completed on 2024-04-28 04:21:08

0001 // SPDX-FileCopyrightText: 2020-2024 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0002 // SPDX-FileCopyrightText: 2022 Tobias Leupold <tl@stonemx.de>
0003 //
0004 // SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006 #include "Logging.h"
0007 #include "ThumbnailCacheConverter.h"
0008 
0009 #include <kpabase/SettingsData.h>
0010 #include <kpabase/version.h>
0011 #include <kpathumbnails/ThumbnailCache.h>
0012 
0013 #include <KAboutData>
0014 #include <KConfigGroup>
0015 #include <KLocalizedString>
0016 #include <KSharedConfig>
0017 #include <QCommandLineOption>
0018 #include <QCommandLineParser>
0019 #include <QCoreApplication>
0020 #include <QDebug>
0021 #include <QDir>
0022 #include <QLocale>
0023 #include <QLoggingCategory>
0024 #include <QTextStream>
0025 #include <QTimer>
0026 
0027 using namespace KPAThumbnailTool;
0028 
0029 void checkConflictingOptions(const QCommandLineParser &parser, const QCommandLineOption &opt1, const QCommandLineOption &opt2, QTextStream &err)
0030 {
0031     if (parser.isSet(opt1) && parser.isSet(opt2)) {
0032         err << i18nc("@info:shell", "Conflicting commandline options: %1 and %2!\n", opt1.names().constFirst(), opt2.names().constFirst());
0033         exit(1);
0034     }
0035 }
0036 
0037 int main(int argc, char **argv)
0038 {
0039     KLocalizedString::setApplicationDomain("kphotoalbum");
0040     QCoreApplication app(argc, argv);
0041 
0042     KAboutData aboutData(
0043         QStringLiteral("kpa-thumbnailtool"), // component name
0044         i18n("KPhotoAlbum Thumbnail Tool"), // display name
0045         QStringLiteral(KPA_VERSION),
0046         i18n("Tool for inspecting and editing the KPhotoAlbum thumbnail cache"), // short description
0047         KAboutLicense::GPL,
0048         i18n("Copyright (C) 2020-2024 The KPhotoAlbum Development Team"), // copyright statement
0049         QString(), // other text
0050         QStringLiteral("https://www.kphotoalbum.org") // homepage
0051     );
0052     aboutData.setOrganizationDomain("kde.org");
0053     // maintainer is expected to be the first entry
0054     // Note: I like to sort by name, grouped by active/inactive;
0055     //       Jesper gets ranked with the active authors for obvious reasons
0056     aboutData.addAuthor(i18n("Johannes Zarl-Zierl"), i18n("Development, Maintainer"), QStringLiteral("johannes@zarl-zierl.at"));
0057     aboutData.addAuthor(i18n("Robert Krawitz"), i18n("Development, Optimization"), QStringLiteral("rlk@alum.mit.edu"));
0058     aboutData.addAuthor(i18n("Tobias Leupold"), i18n("Development, Releases, Website"), QStringLiteral("tl@stonemx.de"));
0059     aboutData.addAuthor(i18n("Jesper K. Pedersen"), i18n("Former Maintainer, Project Creator"), QStringLiteral("blackie@kde.org"));
0060     // not currently active:
0061     aboutData.addAuthor(i18n("Hassan Ibraheem"), QString(), QStringLiteral("hasan.ibraheem@gmail.com"));
0062     aboutData.addAuthor(i18n("Jan Kundr&aacute;t"), QString(), QStringLiteral("jkt@gentoo.org"));
0063     aboutData.addAuthor(i18n("Andreas Neustifter"), QString(), QStringLiteral("andreas.neustifter@gmail.com"));
0064     aboutData.addAuthor(i18n("Tuomas Suutari"), QString(), QStringLiteral("thsuut@utu.fi"));
0065     aboutData.addAuthor(i18n("Miika Turkia"), QString(), QStringLiteral("miika.turkia@gmail.com"));
0066     aboutData.addAuthor(i18n("Henner Zeller"), QString(), QStringLiteral("h.zeller@acm.org"));
0067 
0068     // initialize the commandline parser
0069     QCommandLineParser parser;
0070     parser.addPositionalArgument(QString::fromUtf8("imageDir"), i18nc("@info:shell", "The folder containing the .thumbnail folder."));
0071     QCommandLineOption infoOption { QString::fromUtf8("info"), i18nc("@info:shell", "Print information about thumbnail cache.") };
0072     parser.addOption(infoOption);
0073     QCommandLineOption convertV5ToV4Option { QString::fromUtf8("convertV5ToV4"), i18nc("@info:shell", "Convert thumbnailindex to format suitable for KPhotoAlbum >= 4.3.") };
0074     parser.addOption(convertV5ToV4Option);
0075     QCommandLineOption verifyOption { QString::fromUtf8("check-thumbnail-dimensions"), i18nc("@info:shell", "Check thumbnail cache for consistency of thumbnail dimensions.") };
0076     parser.addOption(verifyOption);
0077     QCommandLineOption fixOption { QString::fromUtf8("remove-broken"), i18nc("@info:shell", "Fix inconsistent thumbnails by removing them from the cache (requires --check-thumbnail-dimensions).") };
0078     parser.addOption(fixOption);
0079     QCommandLineOption vacuumOption { QString::fromUtf8("vacuum"), i18nc("@info:shell", "Remove unreferenced thumbnails from the thumbnail data files.") };
0080     parser.addOption(vacuumOption);
0081     QCommandLineOption quietOption { QString::fromUtf8("quiet"), i18nc("@info:shell", "Be less verbose.") };
0082     parser.addOption(quietOption);
0083 
0084     KAboutData::setApplicationData(aboutData);
0085     aboutData.setupCommandLine(&parser);
0086 
0087     parser.process(app);
0088     aboutData.processCommandLine(&parser);
0089     QTextStream console { stdout };
0090     QTextStream err { stderr };
0091 
0092     checkConflictingOptions(parser, convertV5ToV4Option, infoOption, err);
0093     checkConflictingOptions(parser, convertV5ToV4Option, verifyOption, err);
0094 
0095     const auto args = parser.positionalArguments();
0096     if (args.empty()) {
0097         err << i18nc("@info:shell", "Missing argument!\n");
0098         return 1;
0099     }
0100     const auto imageDir = QDir { args.first() };
0101     if (!imageDir.exists()) {
0102         err << i18nc("@info:shell", "%1 is not a folder!\n", args.first());
0103         return 1;
0104     }
0105     if (parser.isSet(convertV5ToV4Option)) {
0106         const QString indexFile = imageDir.absoluteFilePath(QString::fromUtf8(".thumbnails/thumbnailindex"));
0107         return convertV5ToV4Cache(indexFile, err);
0108     }
0109 
0110     int returnValue = 0;
0111     DB::DummyUIDelegate uiDelegate;
0112     Settings::SettingsData::setup(imageDir.path(), uiDelegate);
0113     const auto thumbnailDir = imageDir.absoluteFilePath(ImageManager::defaultThumbnailDirectory());
0114     ImageManager::ThumbnailCache cache { thumbnailDir };
0115     if (parser.isSet(infoOption)) {
0116         console << i18nc("@info:shell", "Thumbnail cache folder: %1\n", thumbnailDir);
0117         console << i18nc("@info:shell", "Thumbnail index file version: %1\n", cache.actualFileVersion());
0118         console << i18nc("@info:shell", "Maximum supported thumbnailindex file version: %1\n", cache.preferredFileVersion());
0119         console << i18nc("@info:shell", "Thumbnail storage dimensions: %1 pixels\n", cache.thumbnailSize());
0120         if (cache.actualFileVersion() < 5 && !parser.isSet(quietOption)) {
0121             console << i18nc("@info:shell", "Note: Thumbnail storage dimensions are defined in the configuration file prior to v5.\n");
0122         }
0123         console << i18nc("@info:shell", "Number of thumbnails: %1\n", cache.size());
0124         console.flush();
0125     }
0126     if (parser.isSet(verifyOption)) {
0127         const auto incorrectDimensions = cache.findIncorrectlySizedThumbnails();
0128         if (incorrectDimensions.isEmpty()) {
0129             console << i18nc("@info:shell", "No inconsistencies found.\n");
0130         } else {
0131             returnValue = 1;
0132             if (!parser.isSet(quietOption)) {
0133                 console << i18nc("@info:shell This line is printed before a list of file names.", "The following thumbnails appear to have incorrect sizes:\n");
0134                 for (const auto &filename : incorrectDimensions) {
0135                     console << filename.absolute() << "\n";
0136                 }
0137             }
0138             if (parser.isSet(fixOption)) {
0139                 cache.removeThumbnails(incorrectDimensions);
0140                 cache.save();
0141                 console << i18ncp("@info:shell",
0142                                   "Removed 1 inconsistent thumbnail from the database.\n",
0143                                   "Removed %1 inconsistent thumbnails from the database.\n", incorrectDimensions.size());
0144             }
0145         }
0146         console.flush();
0147     }
0148     if (parser.isSet(vacuumOption)) {
0149         cache.vacuum();
0150     }
0151 
0152     // immediately quit the event loop:
0153     QTimer::singleShot(0, &app, [&app, returnValue]() { app.exit(returnValue); });
0154     return QCoreApplication::exec();
0155 }
0156 // vi:expandtab:tabstop=4 shiftwidth=4: