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

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 "groupcommand.h"
0020 
0021 #include "collectionresolvejob.h"
0022 
0023 #include <Akonadi/ItemFetchJob>
0024 #include <Akonadi/ItemFetchScope>
0025 #include <Akonadi/ItemModifyJob>
0026 #include <Akonadi/ContactSearchJob>
0027 
0028 #include <KContacts/Addressee>
0029 
0030 #include <KCodecs/KEmailAddress>
0031 
0032 #include <iostream>
0033 
0034 #include "commandfactory.h"
0035 #include "errorreporter.h"
0036 
0037 using namespace Akonadi;
0038 
0039 DEFINE_COMMAND("group", GroupCommand, I18N_NOOP("Expand or modify a contact group"));
0040 
0041 GroupCommand::GroupCommand(QObject *parent)
0042     : AbstractCommand(parent),
0043       mGroupItem(nullptr),
0044       mBriefMode(false),
0045       mOperationMode(ModeExpand)
0046 {
0047 }
0048 
0049 GroupCommand::~GroupCommand()
0050 {
0051     delete mGroupItem;
0052 }
0053 
0054 void GroupCommand::setupCommandOptions(QCommandLineParser *parser)
0055 {
0056     addOptionsOption(parser);
0057     parser->addOption(QCommandLineOption((QStringList() << "e" << "expand"), i18n("Show the expanded contact group (the default operation)")));
0058     parser->addOption(QCommandLineOption((QStringList() << "a" << "add"), i18n("Add a contact to the group")));
0059     parser->addOption(QCommandLineOption((QStringList() << "d" << "delete"), i18n("Delete a contact from the group")));
0060     parser->addOption(QCommandLineOption((QStringList() << "C" << "clean"), i18n("Remove unknown item references from the group")));
0061     parser->addOption(QCommandLineOption((QStringList() << "c" << "comment"), i18n("Email comment (name) for an added item"), i18n("name")));
0062     parser->addOption(QCommandLineOption((QStringList() << "b" << "brief"), i18n("Brief output (for 'expand', email addresses only)")));
0063     addDryRunOption(parser);
0064 
0065     parser->addPositionalArgument("item", i18nc("@info:shell", "The contact group item, an ID or Akonadi URL"));
0066     parser->addPositionalArgument("args", i18nc("@info:shell", "Arguments for the operation"), i18n("args..."));
0067 }
0068 
0069 int GroupCommand::initCommand(QCommandLineParser *parser)
0070 {
0071     mItemArgs = parser->positionalArguments();
0072     if (!checkArgCount(mItemArgs, 1, i18nc("@info:shell", "Missing group argument"))) return InvalidUsage;
0073 
0074     if (!getCommonOptions(parser)) return InvalidUsage;
0075 
0076     int modeCount = 0;
0077     if (parser->isSet("expand")) {
0078         ++modeCount;
0079     }
0080     if (parser->isSet("add")) {
0081         ++modeCount;
0082     }
0083     if (parser->isSet("delete")) {
0084         ++modeCount;
0085     }
0086     if (parser->isSet("clean")) {
0087         ++modeCount;
0088     }
0089     if (modeCount > 1) {
0090         emitErrorSeeHelp(i18nc("@info:shell", "Only one of the 'expand', 'add', 'delete' or 'clean' options may be specified"));
0091         return (InvalidUsage);
0092     }
0093 
0094     if (parser->isSet("expand")) {          // see if "Expand" mode
0095         // expand GROUP
0096         mOperationMode = ModeExpand;
0097     } else if (parser->isSet("add")) {          // see if "Add" mode
0098         // add GROUP EMAIL|ID...
0099         // add GROUP -c NAME EMAIL|ID
0100         if (mItemArgs.count()<2) {              // missing GROUP was checked above
0101             emitErrorSeeHelp(i18nc("@info:shell", "No items specified to add"));
0102             return (InvalidUsage);
0103         }
0104 
0105         mNameArg = parser->value("comment");
0106         if (!mNameArg.isEmpty()) {
0107             // if the "comment" option is specified,
0108             // then only one EMAIL|ID argument may be given.
0109             if (mItemArgs.count()>2) {
0110                 emitErrorSeeHelp(i18nc("@info:shell", "Only one item may be specified to add with 'comment'"));
0111                 return (InvalidUsage);
0112             }
0113         }
0114 
0115         mOperationMode = ModeAdd;
0116     } else if (parser->isSet("delete")) {       // see if "Delete" mode
0117         // delete GROUP EMAIL|ID...
0118         if (mItemArgs.count()<2) {          // missing GROUP was checked above
0119             emitErrorSeeHelp(i18nc("@info:shell", "No items specified to delete"));
0120             return (InvalidUsage);
0121         }
0122 
0123         mOperationMode = ModeDelete;
0124     } else if (parser->isSet("clean")) {        // see if "Clean" mode
0125         // clean GROUP
0126         mOperationMode = ModeClean;
0127     } else {                        // no mode option specified
0128         mOperationMode = ModeExpand;
0129     }
0130 
0131     if (mOperationMode != ModeAdd) {
0132         if (parser->isSet("comment")) {
0133             emitErrorSeeHelp(i18nc("@info:shell", "The 'comment' option is only allowed with 'add'"));
0134             return (InvalidUsage);
0135         }
0136     }
0137 
0138     mBriefMode = parser->isSet("brief");        // brief/quiet output
0139 
0140     mGroupArg = mItemArgs.takeFirst();          // contact group collection
0141 
0142     Akonadi::Item item = CollectionResolveJob::parseItem(mGroupArg, true);
0143     if (!item.isValid()) {
0144         return (InvalidUsage);
0145     }
0146 
0147     return (NoError);
0148 }
0149 
0150 void GroupCommand::start()
0151 {
0152     if (mOperationMode == ModeDelete || mOperationMode == ModeClean) {
0153         if (!isDryRun()) {              // allow if not doing anything
0154             if (!allowDangerousOperation()) {
0155                 emit finished(RuntimeError);
0156                 return;
0157             }
0158         }
0159     }
0160 
0161     fetchItems();
0162 }
0163 
0164 void GroupCommand::fetchItems()
0165 {
0166     Item item = CollectionResolveJob::parseItem(mGroupArg);
0167     Q_ASSERT(item.isValid());
0168 
0169     ItemFetchJob *job = new ItemFetchJob(item, this);
0170     job->fetchScope().fetchFullPayload(true);
0171     connect(job, &KJob::result, this, &GroupCommand::onItemsFetched);
0172 }
0173 
0174 void GroupCommand::onItemsFetched(KJob *job)
0175 {
0176     if (!checkJobResult(job)) return;
0177     ItemFetchJob *fetchJob = qobject_cast<ItemFetchJob *>(job);
0178     Q_ASSERT(fetchJob != nullptr);
0179 
0180     Item::List items = fetchJob->items();
0181     if (items.count() < 1) {
0182         emit error(i18nc("@info:shell", "Cannot find '%1' as an item", mGroupArg));
0183         emit finished(RuntimeError);
0184         return;
0185     }
0186 
0187     mGroupItem = new Item(items.first());
0188     if (mGroupItem->mimeType() != KContacts::ContactGroup::mimeType()) {
0189         emit error(i18nc("@info:shell", "Item '%1' is not a contact group", mGroupArg));
0190         emit finished(RuntimeError);
0191         return;
0192     }
0193 
0194     if (!mGroupItem->hasPayload<KContacts::ContactGroup>()) { // should never happen?
0195         emit error(i18nc("@info:shell", "Item '%1' has no contact group payload", mGroupArg));
0196         emit finished(RuntimeError);
0197         return;
0198     }
0199 
0200     KContacts::ContactGroup group = mGroupItem->payload<KContacts::ContactGroup>();
0201 
0202     AbstractCommand::Errors status = RuntimeError;
0203     switch (mOperationMode) {             // perform the requested operation
0204     case ModeExpand:
0205         status = showExpandedGroup(group);
0206         break;
0207 
0208     case ModeAdd:
0209         status = addGroupItems(group);
0210         break;
0211 
0212     case ModeDelete:
0213         status = deleteGroupItems(group);
0214         break;
0215 
0216     case ModeClean:
0217         status = cleanGroupItems(group);
0218         break;
0219     }
0220 
0221     if (mOperationMode != ModeExpand) {         // need to write back?
0222         if (status == NoError) {            // only if there were no errors
0223             if (!isDryRun()) {
0224                 mGroupItem->setPayload<KContacts::ContactGroup>(group);
0225                 Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(*mGroupItem);
0226                 modifyJob->exec();
0227                 if (modifyJob->error() != 0) {
0228                     emit error(modifyJob->errorString());
0229                     status = RuntimeError;
0230                 }
0231             }
0232         } else {
0233             emit error(i18nc("@info:shell", "Errors occurred, group not updated"));
0234         }
0235     }
0236 
0237     emit finished(status);
0238 }
0239 
0240 static void writeColumn(const QString &data, int width = 0)
0241 {
0242     std::cout << qPrintable(data.leftJustified(width)) << "  ";
0243 }
0244 
0245 static void writeColumn(quint64 data, int width = 0)
0246 {
0247     writeColumn(QString::number(data), width);
0248 }
0249 
0250 void GroupCommand::displayContactData(const KContacts::ContactGroup::Data &data)
0251 {
0252     if (mBriefMode) {
0253         return;
0254     }
0255 
0256     writeColumn("  D", 5);
0257     writeColumn("", 8);
0258     writeColumn(data.email(), 30);
0259     writeColumn(data.name());
0260     std::cout << std::endl;
0261 }
0262 
0263 void GroupCommand::displayContactReference(Akonadi::Item::Id id)
0264 {
0265     if (mBriefMode) {
0266         return;
0267     }
0268 
0269     writeColumn("  R", 5);
0270     writeColumn(id, 8);
0271 
0272     Akonadi::Item item(id);
0273     Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item);
0274     job->fetchScope().fetchFullPayload();
0275 
0276     if (!job->exec() || job->count() == 0) {
0277         writeColumn(i18nc("@info:shell", "(unknown referenced item)"));
0278     } else {
0279         const Akonadi::Item::List fetchedItems = job->items();
0280         Q_ASSERT(fetchedItems.count() > 0);
0281         const Akonadi::Item fetchedItem = fetchedItems.first();
0282 
0283         if (!fetchedItem.hasPayload<KContacts::Addressee>()) {
0284             writeColumn(i18nc("@info:shell", "(item has no Addressee payload)"));
0285         } else {
0286             KContacts::Addressee addr = fetchedItem.payload<KContacts::Addressee>();
0287             writeColumn(addr.preferredEmail(), 30);
0288             writeColumn(addr.formattedName());
0289         }
0290     }
0291 
0292     std::cout << std::endl;
0293 }
0294 
0295 void GroupCommand::displayContactReference(const Akonadi::Item &item, const QString &email)
0296 {
0297     if (mBriefMode) {
0298         return;
0299     }
0300 
0301     writeColumn("  R", 5);
0302     writeColumn(item.id(), 8);
0303 
0304     if (!item.hasPayload<KContacts::Addressee>()) {
0305         writeColumn(i18nc("@info:shell", "(item has no Addressee payload)"));
0306     } else {
0307         KContacts::Addressee addr = item.payload<KContacts::Addressee>();
0308         writeColumn(!email.isEmpty() ? email : addr.preferredEmail(), 30);
0309         writeColumn(addr.formattedName());
0310     }
0311 
0312     std::cout << std::endl;
0313 }
0314 
0315 void GroupCommand::displayReferenceError(Akonadi::Item::Id id)
0316 {
0317     if (mBriefMode) {
0318         return;
0319     }
0320 
0321     writeColumn("  E", 5);
0322     writeColumn(id, 8);
0323     if (id == -1) {
0324         std::cout << qPrintable(i18nc("@item:shell", "(invalid referenced item)"));
0325     } else {
0326         std::cout << qPrintable(i18nc("@item:shell", "(unknown referenced item)"));
0327     }
0328     std::cout << std::endl;
0329 }
0330 
0331 bool GroupCommand::removeReferenceById(KContacts::ContactGroup &group, const QString &id, bool verbose)
0332 {
0333     bool somethingFound = false;
0334 
0335     // Remove any existing reference with the same ID from the group.
0336     for (int i = 0; i < group.contactReferenceCount();) {
0337         KContacts::ContactGroup::ContactReference existingRef = group.contactReference(i);
0338         if (existingRef.uid() == id) {
0339             group.remove(existingRef);
0340             somethingFound = true;
0341             if (verbose) {
0342                 displayContactReference(id.toUInt());
0343             }
0344         } else {
0345             ++i;
0346         }
0347     }
0348 
0349     return (somethingFound);
0350 }
0351 
0352 bool GroupCommand::removeDataByEmail(KContacts::ContactGroup &group, const QString &email, bool verbose)
0353 {
0354     bool somethingFound = false;
0355 
0356     // Remove any existing data with the same email address from the group.
0357     for (int i = 0; i < group.dataCount();) {
0358         const KContacts::ContactGroup::Data data = group.data(i);
0359         if (QString::compare(data.email(), email, Qt::CaseInsensitive) == 0) {
0360             group.remove(data);
0361             somethingFound = true;
0362             if (verbose) {
0363                 displayContactData(data);
0364             }
0365         } else {
0366             ++i;
0367         }
0368     }
0369 
0370     return (somethingFound);
0371 }
0372 
0373 AbstractCommand::Errors GroupCommand::showExpandedGroup(const KContacts::ContactGroup &group)
0374 {
0375     if (!mBriefMode) {
0376         std::cout << qPrintable(i18nc("@info:shell section header 1=item 2=groupref count 3=ref count 4=data count 5=name",
0377                                       "Group %1 \"%5\" has %2 groups, %3 references and %4 data items:",
0378                                       QString::number(mGroupItem->id()),
0379                                       group.contactGroupReferenceCount(),
0380                                       group.contactReferenceCount(),
0381                                       group.dataCount(),
0382                                       group.name()));
0383         std::cout << std::endl;
0384 
0385         std::cout << " ";
0386         writeColumn(i18nc("@info:shell column header", "Type"), 4);
0387         writeColumn(i18nc("@info:shell column header", "ID"), 8);
0388         writeColumn(i18nc("@info:shell column header", "Email"), 30);
0389         writeColumn(i18nc("@info:shell column header", "Name"));
0390         std::cout << std::endl;
0391     }
0392 
0393     int c = group.contactGroupReferenceCount();
0394     if (!mBriefMode) {
0395         for (int i = 0; i < c; ++i) {
0396             writeColumn("  G", 5);
0397             writeColumn(group.contactGroupReference(i).uid(), 8);
0398             std::cout << std::endl;
0399         }
0400     }
0401 
0402     c = group.contactReferenceCount();
0403     QList<Item::Id> fetchIds;
0404     for (int i = 0; i < c; ++i) {
0405         Item::Id id = group.contactReference(i).uid().toInt();
0406         fetchIds.append(id);
0407     }
0408 
0409     if (!fetchIds.isEmpty()) {
0410         ItemFetchJob *itemJob = new ItemFetchJob(fetchIds, this);
0411         itemJob->fetchScope().setFetchModificationTime(false);
0412         itemJob->fetchScope().fetchAllAttributes(false);
0413         itemJob->fetchScope().fetchFullPayload(true);
0414 
0415         itemJob->exec();
0416         if (itemJob->error() != 0) {
0417             std::cout << std::endl;
0418             emit error(itemJob->errorString());
0419         }
0420 
0421         Item::List fetchedItems = itemJob->items();
0422         if (fetchedItems.isEmpty()) {
0423             emit error(i18nc("@info:shell", "No items could be fetched"));
0424         }
0425 
0426         for (Item::List::const_iterator it = fetchedItems.constBegin();
0427                 it != fetchedItems.constEnd(); ++it) {
0428             const Item item = (*it);
0429             fetchIds.removeAll(item.id());            // note that we've fetched this
0430 
0431             QString email;
0432             if (item.hasPayload<KContacts::Addressee>()) {
0433                 KContacts::Addressee addr = item.payload<KContacts::Addressee>();
0434                 email = addr.preferredEmail();
0435 
0436                 // Retrieve the original preferred email from the contact group reference.
0437                 // If there is one, display that;  if not, the contact's preferred email.
0438                 for (int i = 0; i < c; ++i) {
0439                     Item::Id id = group.contactReference(i).uid().toInt();
0440                     if (id == item.id()) {
0441                         const QString prefEmail = group.contactReference(i).preferredEmail();
0442                         if (!prefEmail.isEmpty()) {
0443                             email = prefEmail;
0444                         }
0445                         break;
0446                     }
0447                 }
0448             }
0449 
0450             if (mBriefMode) {                 // only show email
0451                 std::cout << qPrintable(email);
0452                 std::cout << std::endl;
0453             } else {
0454                 displayContactReference(item, email);
0455             }
0456         }
0457 
0458         foreach (const Item::Id id, fetchIds) {     // error for any that remain
0459             displayReferenceError(id);
0460         }
0461     }
0462 
0463     c = group.dataCount();
0464     for (int i = 0; i < c; ++i) {
0465         const KContacts::ContactGroup::Data data = group.data(i);
0466         if (mBriefMode) {               // only show email
0467             std::cout << qPrintable(data.email());
0468             std::cout << std::endl;
0469         } else {
0470             displayContactData(data);    // show full information
0471         }
0472     }
0473 
0474     return (!fetchIds.isEmpty() ? RuntimeError : NoError);
0475 }
0476 
0477 AbstractCommand::Errors GroupCommand::addGroupItems(KContacts::ContactGroup &group)
0478 {
0479     Q_ASSERT(!mItemArgs.isEmpty());
0480 
0481     if (!mBriefMode) {
0482         std::cout << qPrintable(i18nc("@info:shell section header 1=item 2=groupref count 3=ref count 4=data count 5=name",
0483                                       "Adding to group %1 \"%2\":",
0484                                       QString::number(mGroupItem->id()), group.name()));
0485         std::cout << std::endl;
0486     }
0487 
0488     bool hadError = false;                // not yet, anyway
0489     foreach (const QString &arg, mItemArgs) {
0490         // Look to see whether the argument is an email address
0491         if (KEmailAddress::isValidSimpleAddress(arg)) {
0492             const QString email = arg.toLower();      // email addresses are case-insensitive
0493 
0494             Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob();
0495             job->setQuery(Akonadi::ContactSearchJob::Email, email);
0496             if (!job->exec()) {
0497                 ErrorReporter::error(i18nc("@info:shell", "Cannot search for email '%1', %2",
0498                                            email, job->errorString()));
0499                 hadError = true;
0500                 continue;
0501             }
0502 
0503             const Item::List resultItems = job->items();
0504             if (resultItems.count() > 0) {
0505                 // If the query returned a known Akonadi contact, add that
0506                 // (or the first, if there are more than one) as a reference.
0507 
0508                 if (resultItems.count() > 1) {
0509                     ErrorReporter::warning(i18nc("@info:shell", "Multiple contacts found for '%1', using the first one", email));
0510                 }
0511 
0512                 Akonadi::Item item = resultItems.first();
0513                 Q_ASSERT(item.hasPayload<KContacts::Addressee>());
0514                 KContacts::ContactGroup::ContactReference ref(QString::number(item.id()));
0515 
0516                 // Not really equivalent to "only add the new reference if it
0517                 // doesn't exist already", because e.g. the old reference may have
0518                 // a preferred email set whereas the new one may be different.
0519 
0520                 removeReferenceById(group, ref.uid());      // remove any existing
0521                 group.append(ref);              // then add new reference
0522                 displayContactReference(item);          // report what was added
0523             } else {
0524                 // If no Akonadi contact matching the specified email could be found,
0525                 // add the email (with the comment, if specified) as contact data.
0526 
0527                 removeDataByEmail(group, email);        // remove existing with that email
0528                 QString name = (mNameArg.isEmpty() ? arg : mNameArg);
0529                 KContacts::ContactGroup::Data data(name, email);
0530                 group.append(data);             // add new contact data
0531                 displayContactData(data);           // report what was added
0532             }
0533         } else {
0534             // Not an email address, see if an Akonadi URL or numeric item ID
0535             Item item = CollectionResolveJob::parseItem(arg, true);
0536             if (!item.isValid()) {
0537                 hadError = true;
0538                 continue;
0539             }
0540 
0541             Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item);
0542             job->fetchScope().fetchFullPayload();
0543             if (!job->exec()) {
0544                 ErrorReporter::error(i18nc("@info:shell", "Cannot fetch requested item %1, %2",
0545                                            QString::number(item.id()), job->errorString()));
0546                 hadError = true;
0547                 continue;
0548             }
0549 
0550             const Akonadi::Item::List fetchedItems = job->items();
0551             Q_ASSERT(fetchedItems.count() > 0);
0552             const Akonadi::Item fetchedItem = fetchedItems.first();
0553 
0554             if (!fetchedItem.hasPayload<KContacts::Addressee>()) {
0555                 ErrorReporter::error(i18nc("@info:shell", "Item %1 is not a contact item",
0556                                            QString::number(item.id())));
0557                 hadError = true;
0558                 continue;
0559             }
0560 
0561             KContacts::ContactGroup::ContactReference ref(QString::number(fetchedItem.id()));
0562 
0563             removeReferenceById(group, ref.uid());        // remove any existing
0564             group.append(ref);                // then add new reference
0565             displayContactReference(fetchedItem);     // report what was added
0566         }
0567     }
0568 
0569     if (!mBriefMode) {
0570         std::cout << qPrintable(i18nc("@info:shell section header 1=item 2=groupref count 3=ref count 4=data count 5=name",
0571                                       "Group %1 \"%5\" now has %2 groups, %3 references and %4 data items",
0572                                       QString::number(mGroupItem->id()),
0573                                       group.contactGroupReferenceCount(),
0574                                       group.contactReferenceCount(),
0575                                       group.dataCount(),
0576                                       group.name()));
0577         std::cout << std::endl;
0578     }
0579 
0580     return (hadError ? RuntimeError : NoError);
0581 }
0582 
0583 AbstractCommand::Errors GroupCommand::deleteGroupItems(KContacts::ContactGroup &group)
0584 {
0585     Q_ASSERT(!mItemArgs.isEmpty());
0586 
0587     if (!mBriefMode) {
0588         std::cout << qPrintable(i18nc("@info:shell section header 1=item 2=groupref count 3=ref count 4=data count 5=name",
0589                                       "Removing from group %1 \"%2\":",
0590                                       QString::number(mGroupItem->id()), group.name()));
0591         std::cout << std::endl;
0592     }
0593 
0594     bool hadError = false;                // not yet, anyway
0595     foreach (const QString &arg, mItemArgs) {
0596         // Look to see whether the argument is an email address
0597         if (KEmailAddress::isValidSimpleAddress(arg)) {
0598             bool somethingFound = false;
0599 
0600             // An email address is specified.  First remove any existing
0601             // data items having that email address.
0602 
0603             if (removeDataByEmail(group, arg, true)) {
0604                 somethingFound = true;              // note that did something
0605             }
0606 
0607             // Then remove any references to any item containing that email address.
0608             Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob();
0609             job->setQuery(Akonadi::ContactSearchJob::Email, arg);
0610             if (!job->exec()) {
0611                 ErrorReporter::error(i18nc("@info:shell", "Cannot search for email '%1', %2",
0612                                            arg, job->errorString()));
0613                 hadError = true;
0614                 continue;
0615             }
0616 
0617             const Item::List resultItems = job->items();
0618             for (int itemIndex = 0; itemIndex < resultItems.count(); ++itemIndex) {
0619                 const Akonadi::Item item = resultItems[itemIndex];
0620                 if (removeReferenceById(group, QString::number(item.id()), true)) {
0621                     somethingFound = true;            // note that did something
0622                 }
0623             }
0624 
0625             if (!somethingFound) {            // nothing found to remove
0626                 ErrorReporter::warning(i18nc("@info:shell", "Nothing to remove for email '%1'", arg));
0627                 hadError = true;
0628                 continue;
0629             }
0630         } else {
0631             // Not an email address, see if an Akonadi URL or numeric item ID
0632             Item item = CollectionResolveJob::parseItem(arg, true);
0633             if (!item.isValid()) {
0634                 hadError = true;
0635                 continue;
0636             }
0637 
0638             // Remove any references to that Akonadi ID
0639             const QString itemId = QString::number(item.id());
0640             for (int i = 0; i < group.contactReferenceCount();) {
0641                 KContacts::ContactGroup::ContactReference existingRef = group.contactReference(i);
0642                 if (existingRef.uid() == itemId) {
0643                     displayContactReference(item.id());
0644                     group.remove(existingRef);
0645                 } else {
0646                     ++i;
0647                 }
0648             }
0649         }
0650     }
0651 
0652     if (!mBriefMode) {
0653         std::cout << qPrintable(i18nc("@info:shell section header 1=item 2=groupref count 3=ref count 4=data count 5=name",
0654                                       "Group %1 \"%5\" now has %2 groups, %3 references and %4 data items",
0655                                       QString::number(mGroupItem->id()),
0656                                       group.contactGroupReferenceCount(),
0657                                       group.contactReferenceCount(),
0658                                       group.dataCount(),
0659                                       group.name()));
0660         std::cout << std::endl;
0661     }
0662 
0663     return (hadError ? RuntimeError : NoError);
0664 }
0665 
0666 AbstractCommand::Errors GroupCommand::cleanGroupItems(KContacts::ContactGroup &group)
0667 {
0668     if (!mBriefMode) {
0669         std::cout << qPrintable(i18nc("@info:shell section header 1=item 2=groupref count 3=ref count 4=data count 5=name",
0670                                       "Cleaning references from group %1 \"%2\":",
0671                                       QString::number(mGroupItem->id()), group.name()));
0672         std::cout << std::endl;
0673     }
0674 
0675     // Remove any reference items with an unknown or invalid ID from the group.
0676     for (int i = 0; i < group.contactReferenceCount();) {
0677         KContacts::ContactGroup::ContactReference ref = group.contactReference(i);
0678 
0679         bool doDelete = false;
0680         qint64 id = ref.uid().toLong();
0681         if (id == -1) {
0682             doDelete = true;    // invalid, always remove this
0683         } else {
0684             Akonadi::Item item(id);
0685             Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item);
0686             // no need for payload
0687             if (!job->exec() || job->count() == 0) {  // if fetch failed or nothing returned
0688                 doDelete = true;
0689             }
0690         }
0691 
0692         if (doDelete) {                 // reference is to be deleted
0693             group.remove(ref);                // remove it from group
0694             displayReferenceError(id);
0695         } else {
0696             ++i;
0697         }
0698     }
0699 
0700     if (!mBriefMode) {
0701         std::cout << qPrintable(i18nc("@info:shell section header 1=item 2=groupref count 3=ref count 4=data count 5=name",
0702                                       "Group %1 \"%5\" now has %2 groups, %3 references and %4 data items",
0703                                       QString::number(mGroupItem->id()),
0704                                       group.contactGroupReferenceCount(),
0705                                       group.contactReferenceCount(),
0706                                       group.dataCount(),
0707                                       group.name()));
0708         std::cout << std::endl;
0709     }
0710 
0711     return (NoError);
0712 }