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 }