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

0001 /*
0002     Copyright (C) 2013  Jonathan Marten <jjm@keelhaul.me.uk>
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 "infocommand.h"
0020 
0021 #include <Akonadi/ItemFetchJob>
0022 #include <Akonadi/ItemFetchScope>
0023 #include <Akonadi/CollectionStatisticsJob>
0024 #include <Akonadi/CollectionStatistics>
0025 
0026 #include <Akonadi/CollectionPathResolver>   // just for error code
0027 
0028 #include <qdatetime.h>
0029 
0030 #include <iostream>
0031 
0032 #include "commandfactory.h"
0033 #include "collectionresolvejob.h"
0034 #include "collectionpathjob.h"
0035 
0036 using namespace Akonadi;
0037 
0038 DEFINE_COMMAND("info", InfoCommand, I18N_NOOP("Show full information for a collection or item"));
0039 
0040 InfoCommand::InfoCommand(QObject *parent)
0041     : AbstractCommand(parent),
0042       mInfoCollection(nullptr),
0043       mInfoItem(nullptr),
0044       mInfoStatistics(nullptr)
0045 {
0046 }
0047 
0048 InfoCommand::~InfoCommand()
0049 {
0050     delete mInfoItem;
0051     delete mInfoCollection;
0052     delete mInfoStatistics;
0053 }
0054 
0055 void InfoCommand::setupCommandOptions(QCommandLineParser *parser)
0056 {
0057     addOptionsOption(parser);
0058     addCollectionItemOptions(parser);
0059 
0060     parser->addPositionalArgument("entity", i18nc("@info:shell", "Collections or items to display"), i18n("entity..."));
0061 }
0062 
0063 int InfoCommand::initCommand(QCommandLineParser *parser)
0064 {
0065     const QStringList args = parser->positionalArguments();
0066     if (!checkArgCount(args, 1, i18nc("@info:shell", "No collections or items specified"))) return InvalidUsage;
0067 
0068     if (!getCommonOptions(parser)) return InvalidUsage;
0069 
0070     initProcessLoop(args);
0071     return NoError;
0072 }
0073 
0074 void InfoCommand::start()
0075 {
0076     startProcessLoop("infoForNext");
0077 }
0078 
0079 void InfoCommand::infoForNext()
0080 {
0081     if (wantItem()) {                   // user forced as an item
0082         fetchItems();                   // do this immediately
0083     } else
0084     {
0085         if (!getResolveJob(currentArg()))
0086         {
0087             processNext();
0088             return;
0089         }
0090 
0091         // User specified that the input is a collection, or
0092         // didn't specify at all what sort of entity it is.
0093         // First try to resolve it as a collection.
0094         CollectionResolveJob *res = resolveJob();
0095         connect(res, &KJob::result, this, &InfoCommand::onBaseFetched);
0096         res->start();
0097     }
0098 }
0099 
0100 void InfoCommand::onBaseFetched(KJob *job)
0101 {
0102     CollectionResolveJob *res = resolveJob();
0103     Q_ASSERT(job == res);
0104 
0105     if (job->error() != 0) {
0106         if (job->error() == CollectionPathResolver::Unknown) {
0107             // failed to resolve as collection
0108             if (!wantCollection()) {            // not forced as a collection
0109                 fetchItems();               // try it as an item
0110                 return;
0111             }
0112         }
0113 
0114         emit error(job->errorString());
0115         processNext();
0116         return;
0117     }
0118 
0119     if (res->collection() == Collection::root()) {
0120         emit error(i18nc("@info:shell", "No information available for collection root"));
0121         processNext();
0122         return;
0123     }
0124 
0125     fetchStatistics();
0126 }
0127 
0128 void InfoCommand::fetchStatistics()
0129 {
0130     Q_ASSERT(resolveJob()->collection().isValid());
0131     CollectionStatisticsJob *job = new CollectionStatisticsJob(resolveJob()->collection(), this);
0132     connect(job, &KJob::result, this, &InfoCommand::onStatisticsFetched);
0133 }
0134 
0135 void InfoCommand::onStatisticsFetched(KJob *job)
0136 {
0137     if (!checkJobResult(job)) return;
0138     CollectionStatisticsJob *statsJob = qobject_cast<CollectionStatisticsJob *>(job);
0139     Q_ASSERT(statsJob != nullptr);
0140     mInfoStatistics = new CollectionStatistics(statsJob->statistics());
0141 
0142     mInfoCollection = new Collection(resolveJob()->collection());
0143     fetchParentPath(mInfoCollection->parentCollection());
0144 }
0145 
0146 void InfoCommand::fetchItems()
0147 {
0148     Item item = CollectionResolveJob::parseItem(currentArg());
0149     if (!item.isValid()) {
0150         emit error(i18nc("@info:shell", "Invalid item/collection syntax '%1'", currentArg()));
0151         processNext();
0152         return;
0153     }
0154 
0155     ItemFetchJob *job = new ItemFetchJob(item, this);
0156     job->fetchScope().setFetchModificationTime(true);
0157     job->fetchScope().setFetchTags(true);
0158     job->fetchScope().fetchAllAttributes(true);
0159 
0160     // Need this so that parentCollection() will be valid.
0161     job->fetchScope().setAncestorRetrieval(ItemFetchScope::Parent);
0162 
0163     // Not actually going to use the payload here, but if we don't set it
0164     // to be fetched then hasPayload() will not return a meaningful result.
0165     job->fetchScope().fetchFullPayload(true);
0166 
0167     connect(job, &KJob::result, this, &InfoCommand::onItemsFetched);
0168 }
0169 
0170 void InfoCommand::onItemsFetched(KJob *job)
0171 {
0172     if (!checkJobResult(job)) return;
0173     ItemFetchJob *fetchJob = qobject_cast<ItemFetchJob *>(job);
0174     Q_ASSERT(fetchJob != nullptr);
0175     Item::List items = fetchJob->items();
0176     if (items.count() < 1) {
0177         emit error(i18nc("@info:shell", "Cannot find '%1' as a collection or item", currentArg()));
0178         processNext();
0179         return;
0180     }
0181 
0182     mInfoItem = new Item(items.first());
0183     fetchParentPath(mInfoItem->parentCollection());
0184 }
0185 
0186 void InfoCommand::fetchParentPath(const Akonadi::Collection &collection)
0187 {
0188     Q_ASSERT(mInfoCollection != nullptr || mInfoItem != nullptr);
0189 
0190     CollectionPathJob *job = new CollectionPathJob(collection);
0191     connect(job, &KJob::result, this, &InfoCommand::onParentPathFetched);
0192     job->start();
0193 }
0194 
0195 static void writeInfo(const QString &tag, const QString &data)
0196 {
0197     std::cout << qPrintable(QString(tag + ":").leftJustified(10));
0198     std::cout << "  ";
0199     std::cout << qPrintable(data);
0200     std::cout << std::endl;
0201 }
0202 
0203 static void writeInfo(const QString &tag, quint64 data)
0204 {
0205     writeInfo(tag, QString::number(data));
0206 }
0207 
0208 void InfoCommand::onParentPathFetched(KJob *job)
0209 {
0210     if (!checkJobResult(job)) return;
0211     CollectionPathJob *pathJob = qobject_cast<CollectionPathJob *>(job);
0212     Q_ASSERT(pathJob != nullptr);
0213     const QString parentString = pathJob->formattedCollectionPath();
0214 
0215     // Finally we have fetched all of the information to display.
0216 
0217     if (mInfoCollection != nullptr) {           // for a collection
0218         Q_ASSERT(mInfoCollection->isValid());
0219 
0220         writeInfo(i18nc("@info:shell", "ID"), QString::number(mInfoCollection->id()));
0221         writeInfo(i18nc("@info:shell", "URL"), mInfoCollection->url().toDisplayString());
0222         writeInfo(i18nc("@info:shell", "Parent"), parentString);
0223         writeInfo(i18nc("@info:shell", "Type"), i18nc("@info:shell entity type", "Collection"));
0224         writeInfo(i18nc("@info:shell", "Name"), mInfoCollection->name());
0225         writeInfo(i18nc("@info:shell", "Owner"), mInfoCollection->resource());
0226         writeInfo(i18nc("@info:shell", "MIME"), mInfoCollection->contentMimeTypes().join(" "));
0227         writeInfo(i18nc("@info:shell", "Remote ID"), mInfoCollection->remoteId());
0228 
0229         QStringList rightsList;
0230         Collection::Rights rights = mInfoCollection->rights();
0231         if (rights & Collection::ReadOnly) {
0232             rightsList << i18nc("@info:shell", "ReadOnly");
0233         }
0234         if (rights & Collection::CanChangeItem) {
0235             rightsList << i18nc("@info:shell", "ChangeItem");
0236         }
0237         if (rights & Collection::CanCreateItem) {
0238             rightsList << i18nc("@info:shell", "CreateItem");
0239         }
0240         if (rights & Collection::CanDeleteItem) {
0241             rightsList << i18nc("@info:shell", "DeleteItem");
0242         }
0243         if (rights & Collection::CanChangeCollection) {
0244             rightsList << i18nc("@info:shell", "ChangeColl");
0245         }
0246         if (rights & Collection::CanCreateCollection) {
0247             rightsList << i18nc("@info:shell", "CreateColl");
0248         }
0249         if (rights & Collection::CanDeleteCollection) {
0250             rightsList << i18nc("@info:shell", "DeleteColl");
0251         }
0252         if (rights & Collection::CanLinkItem) {
0253             rightsList << i18nc("@info:shell", "LinkItem");
0254         }
0255         if (rights & Collection::CanUnlinkItem) {
0256             rightsList << i18nc("@info:shell", "UnlinkItem");
0257         }
0258         writeInfo(i18nc("@info:shell", "Rights"), rightsList.join(" "));
0259 
0260         Q_ASSERT(mInfoStatistics != nullptr);
0261         writeInfo(i18nc("@info:shell", "Count"), QLocale::system().toString(mInfoStatistics->count()));
0262         writeInfo(i18nc("@info:shell", "Unread"), QLocale::system().toString(mInfoStatistics->unreadCount()));
0263         const QString size = QLocale::system().formattedDataSize(mInfoStatistics->size());
0264         writeInfo(i18nc("@info:shell", "Size"), size);
0265     } else if (mInfoItem != nullptr) {          // for an item
0266         writeInfo(i18nc("@info:shell", "ID"), QString::number(mInfoItem->id()));
0267         writeInfo(i18nc("@info:shell", "URL"), mInfoItem->url().toDisplayString());
0268         writeInfo(i18nc("@info:shell", "Parent"), parentString);
0269         writeInfo(i18nc("@info:shell", "Type"), i18nc("@info:shell entity type", "Item"));
0270         writeInfo(i18nc("@info:shell", "MIME"), mInfoItem->mimeType());
0271         // from kdepim/akonadiconsole/browserwidget.cpp BrowserWidget::setItem()
0272         writeInfo(i18nc("@info:shell", "Modified"), (mInfoItem->modificationTime().toString() + " UTC"));
0273         writeInfo(i18nc("@info:shell", "Revision"), mInfoItem->revision());
0274         writeInfo(i18nc("@info:shell", "Remote ID"), mInfoItem->remoteId());
0275         writeInfo(i18nc("@info:shell", "Payload"), (mInfoItem->hasPayload() ? i18nc("@info:shell", "yes") : i18nc("@info:shell", "no")));
0276 
0277         Item::Flags flags = mInfoItem->flags();
0278         QStringList flagDisp;
0279         foreach (const QByteArray &flag, flags) {
0280             flagDisp << flag;
0281         }
0282         if (flagDisp.isEmpty()) {
0283             flagDisp << i18nc("@info:shell", "(none)");
0284         }
0285         writeInfo(i18nc("@info:shell", "Flags"), flagDisp.join(" "));
0286 
0287         Tag::List tags = mInfoItem->tags();
0288         QStringList tagDisp;
0289         foreach (const Akonadi::Tag &tag, tags) {
0290             tagDisp << tag.url().url();
0291         }
0292         if (tagDisp.isEmpty()) {
0293             tagDisp << i18nc("@info:shell", "(none)");
0294         }
0295         writeInfo(i18nc("@info:shell", "Tags"), tagDisp.join(" "));
0296 
0297         const QString size = QLocale::system().formattedDataSize(mInfoItem->size());
0298         writeInfo(i18nc("@info:shell", "Size"), size);
0299     } else {                      // neither collection nor item?
0300         // should never happen
0301         writeInfo(i18nc("@info:shell", "Type"), i18nc("@info:shell entity type", "Unknown"));
0302     }
0303 
0304     if (!isProcessLoopFinished()) {         // not the last item
0305         std::cout << "\n";              // blank line to separate
0306     }
0307 
0308     processNext();
0309 }