File indexing completed on 2024-05-12 05:12:42

0001 /*
0002     Copyright (C) 2012  Kevin Krammer <krammer@kde.org>
0003 
0004     This program is free software; you can redistribute it and/or modify
0005     it under the terms of the GNU General Public License as published by
0006     the Free Software Foundation; either version 2 of the License, or
0007     (at your option) any later version.
0008 
0009     This program is distributed in the hope that it will be useful,
0010     but WITHOUT ANY WARRANTY; without even the implied warranty of
0011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012     GNU General Public License for more details.
0013 
0014     You should have received a copy of the GNU General Public License along
0015     with this program; if not, write to the Free Software Foundation, Inc.,
0016     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
0017 */
0018 
0019 #include "listcommand.h"
0020 
0021 #include "collectionresolvejob.h"
0022 
0023 #include <Akonadi/CollectionFetchJob>
0024 #include <Akonadi/CollectionFetchScope>
0025 #include <Akonadi/ItemFetchJob>
0026 #include <Akonadi/ItemFetchScope>
0027 
0028 #include <QDateTime>
0029 
0030 #include <iostream>
0031 
0032 #include "commandfactory.h"
0033 
0034 using namespace Akonadi;
0035 
0036 DEFINE_COMMAND("list", ListCommand, I18N_NOOP("List sub-collections and/or items in a specified collection"));
0037 
0038 ListCommand::ListCommand(QObject *parent)
0039     : AbstractCommand(parent)
0040 {
0041 }
0042 
0043 void ListCommand::setupCommandOptions(QCommandLineParser *parser)
0044 {
0045     addOptionsOption(parser);
0046     parser->addOption(QCommandLineOption((QStringList() << "l" << "details"), i18n("List more detailed information")));
0047     parser->addOption(QCommandLineOption((QStringList() << "c" << "collections"), i18n("List only sub-collections")));
0048     parser->addOption(QCommandLineOption((QStringList() << "i" << "items"), i18n("List only contained items")));
0049 
0050     parser->addPositionalArgument("collection", i18nc("@info:shell", "The collection to list: an ID, path or Akonadi URL"));
0051 }
0052 
0053 int ListCommand::initCommand(QCommandLineParser *parser)
0054 {
0055     const QStringList args = parser->positionalArguments();
0056     if (!checkArgCount(args, 1, i18nc("@info:shell", "Missing collection argument"))) return InvalidUsage;
0057 
0058     mListItems = parser->isSet("items");        // selection options specified
0059     mListCollections = parser->isSet("collections");
0060     if (!mListCollections && !mListItems) {     // if none given, then
0061         mListCollections = mListItems = true;       // list both by default
0062     }
0063     mListDetails = parser->isSet("details");        // listing option specified
0064 
0065     const QString collectionArg = args.first();
0066     if (!getResolveJob(collectionArg)) return InvalidUsage;
0067 
0068     return NoError;
0069 }
0070 
0071 void ListCommand::start()
0072 {
0073     connect(resolveJob(), &KJob::result, this, &ListCommand::onBaseFetched);
0074     resolveJob()->start();
0075 }
0076 
0077 static void writeColumn(const QString &data, int width = 0)
0078 {
0079     std::cout << data.leftJustified(width).toLocal8Bit().constData() << "  ";
0080 }
0081 
0082 static void writeColumn(quint64 data, int width = 0)
0083 {
0084     writeColumn(QString::number(data), width);
0085 }
0086 
0087 void ListCommand::onBaseFetched(KJob *job)
0088 {
0089     if (job->error() != 0) {
0090         emit error(job->errorString());
0091         emit finished(RuntimeError);
0092         return;
0093     }
0094 
0095     Q_ASSERT(job == resolveJob());
0096 
0097     if (mListCollections) {
0098         fetchCollections();
0099     } else {
0100         fetchItems();
0101     }
0102 }
0103 
0104 void ListCommand::fetchCollections()
0105 {
0106     CollectionResolveJob *res = resolveJob();
0107     Q_ASSERT(res->collection().isValid());
0108 
0109     CollectionFetchJob *job = new CollectionFetchJob(res->collection(), CollectionFetchJob::FirstLevel, this);
0110     job->fetchScope().setListFilter(CollectionFetchScope::NoFilter);
0111     connect(job, &KJob::result, this, &ListCommand::onCollectionsFetched);
0112 }
0113 
0114 void ListCommand::onCollectionsFetched(KJob *job)
0115 {
0116     if (!checkJobResult(job)) return;
0117     CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *>(job);
0118     Q_ASSERT(fetchJob != nullptr);
0119 
0120     Collection::List collections = fetchJob->collections();
0121     if (collections.isEmpty()) {
0122         if (mListCollections) {                  // message only if collections requested
0123             std::cout << qPrintable(i18nc("@info:shell",
0124                                           "Collection %1 has no sub-collections",
0125                                           resolveJob()->formattedCollectionName())) << std::endl;
0126         }
0127     } else {
0128         // This works because Akonadi::Entity implements operator<
0129         // which compares item IDs numerically
0130         std::sort(collections.begin(), collections.end());
0131 
0132         std::cout << qPrintable(i18ncp("@info:shell output section header 1=count, 2=collection",
0133                                       "Collection %2 has %1 sub-collection:",
0134                                       "Collection %2 has %1 sub-collections:",
0135                                       collections.count(),
0136                                       resolveJob()->formattedCollectionName())) << std::endl;
0137         if (mListDetails) {
0138             std::cout << "  ";
0139             writeColumn(i18nc("@info:shell column header", "ID"), 8);
0140             writeColumn(i18nc("@info:shell column header", "Name"));
0141             std::cout << std::endl;
0142         }
0143 
0144         Q_FOREACH (const Collection &collection, collections) {
0145             std::cout << "  ";
0146             if (mListDetails) {
0147                 writeColumn(collection.id(), 8);
0148                 writeColumn(collection.name());
0149             } else {
0150                 std::cout << qPrintable(collection.name());
0151             }
0152             std::cout << std::endl;
0153         }
0154     }
0155 
0156     if (mListItems) {
0157         fetchItems();
0158     } else {
0159         emit finished(NoError);
0160     }
0161 }
0162 
0163 void ListCommand::fetchItems()
0164 {
0165     Collection coll = resolveJob()->collection();
0166     Q_ASSERT(coll.isValid());
0167 
0168     // only attempt item listing if collection has non-collection content MIME types
0169     QStringList contentMimeTypes = coll.contentMimeTypes();
0170     contentMimeTypes.removeAll(Collection::mimeType());
0171     if (!contentMimeTypes.isEmpty()) {
0172         ItemFetchJob *job = new ItemFetchJob(coll, this);
0173         job->fetchScope().setFetchModificationTime(true);
0174         job->fetchScope().fetchAllAttributes(false);
0175         job->fetchScope().fetchFullPayload(false);
0176         connect(job, &KJob::result, this, &ListCommand::onItemsFetched);
0177     } else {
0178         std::cout << qPrintable(i18nc("@info:shell",
0179                                       "Collection %1 cannot contain items",
0180                                       resolveJob()->formattedCollectionName())) << std::endl;
0181         emit finished(NoError);
0182     }
0183 }
0184 
0185 void ListCommand::onItemsFetched(KJob *job)
0186 {
0187     if (!checkJobResult(job)) return;
0188     ItemFetchJob *fetchJob = qobject_cast<ItemFetchJob *>(job);
0189     Q_ASSERT(fetchJob != nullptr);
0190     Item::List items = fetchJob->items();
0191 
0192     if (items.isEmpty()) {
0193         if (mListItems) {                    // message only if items requested
0194             std::cout << qPrintable(i18nc("@info:shell",
0195                                           "Collection %1 has no items",
0196                                           resolveJob()->formattedCollectionName())) << std::endl;
0197         }
0198     } else {
0199         std::sort(items.begin(), items.end());
0200 
0201         std::cout << qPrintable(i18ncp("@info:shell output section header 1=count, 2=collection",
0202                                       "Collection %2 has %1 item:",
0203                                       "Collection %2 has %1 items:",
0204                                       items.count(),
0205                                       resolveJob()->formattedCollectionName())) << std::endl;
0206         if (mListDetails) {
0207             std::cout << "  ";
0208             writeColumn(i18nc("@info:shell column header", "ID"), 8);
0209             writeColumn(i18nc("@info:shell column header", "MIME type"), 20);
0210             writeColumn(i18nc("@info:shell column header", "Size"), 10);
0211             writeColumn(i18nc("@info:shell column header", "Modification Time"));
0212             std::cout << std::endl;
0213         }
0214 
0215         Q_FOREACH (const Item &item, items) {
0216             std::cout << "  ";
0217             if (mListDetails) {
0218                 writeColumn(item.id(), 8);
0219                 writeColumn(item.mimeType(), 20);
0220                 const QString size = QLocale::system().formattedDataSize(item.size());
0221                 writeColumn(size, 10);
0222                 // from kdepim/akonadiconsole/browserwidget.cpp BrowserWidget::setItem()
0223                 writeColumn((item.modificationTime().toString() + " UTC"));
0224             } else {
0225                 std::cout << item.id();
0226             }
0227             std::cout << std::endl;
0228         }
0229     }
0230 
0231     emit finished(NoError);
0232 }