File indexing completed on 2025-01-05 04:49:19

0001 /*
0002    SPDX-FileCopyrightText: 2016-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "gmximportexportplugininterface.h"
0008 #include "kaddressbook_importexport_gmx_debug.h"
0009 #include <KActionCollection>
0010 #include <KJobWidgets>
0011 #include <KLocalizedString>
0012 #include <KMessageBox>
0013 #include <PimCommon/RenameFileDialog>
0014 #include <QAction>
0015 #include <QFileDialog>
0016 #include <QPointer>
0017 #include <QTemporaryFile>
0018 #include <QTextStream>
0019 #include <QUrl>
0020 #include <importexportengine.h>
0021 #include <kio/filecopyjob.h>
0022 
0023 #include <KAddressBookImportExport/ContactSelectionDialog>
0024 
0025 #define GMX_FILESELECTION_STRING i18n("GMX address book file (*.gmxa)")
0026 const int typeHome = 0;
0027 const int typeWork = 1;
0028 const int typeOther = 2;
0029 
0030 GMXImportExportPluginInterface::GMXImportExportPluginInterface(QObject *parent)
0031     : KAddressBookImportExport::PluginInterface(parent)
0032 {
0033 }
0034 
0035 GMXImportExportPluginInterface::~GMXImportExportPluginInterface() = default;
0036 
0037 void GMXImportExportPluginInterface::createAction(KActionCollection *ac)
0038 {
0039     QAction *action = ac->addAction(QStringLiteral("file_import_gmx"));
0040     action->setText(i18n("Import GMX file..."));
0041     action->setWhatsThis(i18n("Import contacts from a GMX address book file."));
0042     setImportActions(QList<QAction *>() << action);
0043     connect(action, &QAction::triggered, this, &GMXImportExportPluginInterface::slotImportGmx);
0044 
0045     action = ac->addAction(QStringLiteral("file_export_gmx"));
0046     action->setText(i18n("Export GMX file..."));
0047     action->setWhatsThis(i18n("Export contacts to a GMX address book file."));
0048     setExportActions(QList<QAction *>() << action);
0049     connect(action, &QAction::triggered, this, &GMXImportExportPluginInterface::slotExportGmx);
0050 }
0051 
0052 void GMXImportExportPluginInterface::exec()
0053 {
0054     switch (mImportExportAction) {
0055     case Import:
0056         importGMX();
0057         break;
0058     case Export:
0059         exportGMX();
0060         break;
0061     }
0062 }
0063 
0064 void GMXImportExportPluginInterface::slotImportGmx()
0065 {
0066     mImportExportAction = Import;
0067     Q_EMIT emitPluginActivated(this);
0068 }
0069 
0070 void GMXImportExportPluginInterface::slotExportGmx()
0071 {
0072     mImportExportAction = Export;
0073     Q_EMIT emitPluginActivated(this);
0074 }
0075 
0076 void GMXImportExportPluginInterface::exportGMX()
0077 {
0078     QPointer<KAddressBookImportExport::ContactSelectionDialog> dlg =
0079         new KAddressBookImportExport::ContactSelectionDialog(itemSelectionModel(), false, parentWidget());
0080     dlg->setMessageText(i18n("Which contact do you want to export?"));
0081     dlg->setDefaultAddressBook(defaultCollection());
0082     if (!dlg->exec()) {
0083         delete dlg;
0084         return;
0085     }
0086     const KContacts::AddresseeList contacts = dlg->selectedContacts().addressList();
0087     delete dlg;
0088 
0089     if (contacts.isEmpty()) {
0090         KMessageBox::error(nullptr, i18n("You have not selected any contacts to export."));
0091         return;
0092     }
0093 
0094     KAddressBookImportExport::ContactList contactLists;
0095     contactLists.setAddressList(contacts);
0096     QFileDialog::Options options = QFileDialog::DontConfirmOverwrite;
0097     QUrl url = QFileDialog::getSaveFileUrl(parentWidget(),
0098                                            QString(),
0099                                            QUrl::fromLocalFile(QDir::homePath() + QStringLiteral("/addressbook.gmx")),
0100                                            GMX_FILESELECTION_STRING,
0101                                            nullptr,
0102                                            options);
0103     if (url.isEmpty()) {
0104         return;
0105     }
0106 
0107     if (QFileInfo::exists(url.isLocalFile() ? url.toLocalFile() : url.path())) {
0108         if (url.isLocalFile() && QFileInfo::exists(url.toLocalFile())) {
0109             auto dialog = new PimCommon::RenameFileDialog(url, false, parentWidget());
0110             const auto result = static_cast<PimCommon::RenameFileDialog::RenameFileDialogResult>(dialog->exec());
0111             if (result == PimCommon::RenameFileDialog::RENAMEFILE_RENAME) {
0112                 url = dialog->newName();
0113             } else if (result == PimCommon::RenameFileDialog::RENAMEFILE_IGNORE) {
0114                 delete dialog;
0115                 return;
0116             }
0117             delete dialog;
0118         }
0119     }
0120 
0121     if (!url.isLocalFile()) {
0122         QTemporaryFile tmpFile;
0123         if (!tmpFile.open()) {
0124             const QString txt = i18n("<qt>Unable to open file <b>%1</b></qt>", url.url());
0125             KMessageBox::error(parentWidget(), txt);
0126             return;
0127         }
0128 
0129         doExport(&tmpFile, contactLists.addressList());
0130         tmpFile.flush();
0131         auto job = KIO::file_copy(QUrl::fromLocalFile(tmpFile.fileName()), url, -1, KIO::Overwrite);
0132         KJobWidgets::setWindow(job, parentWidget());
0133         job->exec();
0134     } else {
0135         QString fileName = url.toLocalFile();
0136         QFile file(fileName);
0137 
0138         if (!file.open(QIODevice::WriteOnly)) {
0139             const QString txt = i18n("<qt>Unable to open file <b>%1</b>.</qt>", fileName);
0140             KMessageBox::error(parentWidget(), txt);
0141             return;
0142         }
0143 
0144         doExport(&file, contactLists.addressList());
0145         file.close();
0146     }
0147 }
0148 
0149 static bool checkDateTime(const QString &dateStr, QDateTime &dt)
0150 {
0151     if (dateStr.isEmpty()) {
0152         return false;
0153     }
0154 
0155     dt = QDateTime::fromString(dateStr, Qt::ISODate);
0156     if (dt.isValid() && dt.date().year() > 1901) {
0157         return true;
0158     }
0159     dt.setDate(QDate());
0160 
0161     return false;
0162 }
0163 
0164 static const QString dateString(const QDateTime &dt)
0165 {
0166     if (!dt.isValid()) {
0167         return QStringLiteral("1000-01-01 00:00:00");
0168     }
0169     QString d(dt.toString(Qt::ISODate));
0170     d[10] = QLatin1Char(' '); // remove the "T" in the middle of the string
0171     return d;
0172 }
0173 
0174 static const QStringList assignedCategoriesSorted(const KContacts::AddresseeList &list)
0175 {
0176     // Walk through the addressees and return a unique list of up to 31
0177     // categories, alphabetically sorted
0178     QStringList categoryList;
0179     const KContacts::Addressee *addressee = nullptr;
0180     for (KContacts::AddresseeList::ConstIterator addresseeIt = list.begin(); addresseeIt != list.end() && categoryList.count() < 32; ++addresseeIt) {
0181         addressee = &(*addresseeIt);
0182         if (addressee->isEmpty()) {
0183             continue;
0184         }
0185         const QStringList categories = addressee->categories();
0186         for (int i = 0; i < categories.count() && categoryList.count() < 32; ++i) {
0187             if (!categoryList.contains(categories[i])) {
0188                 categoryList.append(categories[i]);
0189             }
0190         }
0191     }
0192     categoryList.sort();
0193     return categoryList;
0194 }
0195 
0196 void GMXImportExportPluginInterface::doExport(QFile *fp, const KContacts::AddresseeList &list) const
0197 {
0198     if (!fp || list.isEmpty()) {
0199         return;
0200     }
0201 
0202     QTextStream t(fp);
0203     t.setEncoding(QStringConverter::Latin1);
0204 
0205     using AddresseeMap = QMap<int, const KContacts::Addressee *>;
0206     AddresseeMap addresseeMap;
0207     const KContacts::Addressee *addressee = nullptr;
0208 
0209     t << "AB_ADDRESSES:\n";
0210     t << "Address_id,Nickname,Firstname,Lastname,Title,Birthday,Comments,"
0211          "Change_date,Status,Address_link_id,Categories\n";
0212 
0213     QStringList categoryMap;
0214     categoryMap.append(assignedCategoriesSorted(list));
0215 
0216     int addresseeId = 0;
0217     const QChar DELIM(QLatin1Char('#'));
0218     for (KContacts::AddresseeList::ConstIterator it = list.begin(); it != list.end(); ++it) {
0219         addressee = &(*it);
0220         if (addressee->isEmpty()) {
0221             continue;
0222         }
0223         addresseeMap[++addresseeId] = addressee;
0224 
0225         // Assign categories as bitfield
0226         const QStringList categories = addressee->categories();
0227         long int category = 0;
0228         if (!categories.isEmpty()) {
0229             const int categoriesCount(categories.count());
0230             for (int i = 0; i < categoriesCount; ++i) {
0231                 const QString cat = categories[i];
0232                 if (categoryMap.contains(cat)) {
0233                     category |= 1 << categoryMap.indexOf(cat, 0);
0234                 }
0235             }
0236         }
0237 
0238         // GMX sorts by nickname by default - don't leave empty
0239         QString nickName = addressee->nickName();
0240         if (nickName.isEmpty()) {
0241             nickName = addressee->formattedName();
0242         }
0243 
0244         t << addresseeId << DELIM // Address_id
0245           << nickName << DELIM // Nickname
0246           << addressee->givenName() << DELIM // Firstname
0247           << addressee->familyName() << DELIM // Lastname
0248           << addressee->prefix() << DELIM // Title - Note: ->title()
0249                                           // refers to the professional title
0250           << dateString(addressee->birthday()) << DELIM // Birthday
0251           << addressee->note() /*.replace('\n',"\r\n")*/ << DELIM // Comments
0252           << dateString(addressee->revision()) << DELIM // Change_date
0253           << "1" << DELIM // Status
0254           << DELIM // Address_link_id
0255           << category << QLatin1Char('\n'); // Categories
0256     }
0257 
0258     t << "####\n";
0259     t << "AB_ADDRESS_RECORDS:\n";
0260     t << "Address_id,Record_id,Street,Country,Zipcode,City,Phone,Fax,Mobile,"
0261          "Mobile_type,Email,Homepage,Position,Comments,Record_type_id,Record_type,"
0262          "Company,Department,Change_date,Preferred,Status\n";
0263 
0264     addresseeId = 1;
0265     while ((addressee = addresseeMap[addresseeId]) != nullptr) {
0266         const KContacts::PhoneNumber::List cellPhones = addressee->phoneNumbers(KContacts::PhoneNumber::Cell);
0267 
0268         const QStringList emails = addressee->emails();
0269 
0270         for (int recId = 0; recId < 3; ++recId) {
0271             KContacts::Address address;
0272             KContacts::PhoneNumber phone;
0273             KContacts::PhoneNumber fax;
0274             KContacts::PhoneNumber cell;
0275 
0276             // address preference flag:
0277             // & 1: preferred email address
0278             // & 4: preferred cell phone
0279             int prefFlag = 0;
0280 
0281             switch (recId) {
0282             // Assign address, phone and cellphone, fax if applicable
0283             case typeHome:
0284                 address = addressee->address(KContacts::Address::Home);
0285                 phone = addressee->phoneNumber(KContacts::PhoneNumber::Home);
0286                 if (!cellPhones.isEmpty()) {
0287                     cell = cellPhones.at(0);
0288                     if (!cell.isEmpty()) {
0289                         prefFlag |= 4;
0290                     }
0291                 }
0292                 break;
0293             case typeWork:
0294                 address = addressee->address(KContacts::Address::Work);
0295                 phone = addressee->phoneNumber(KContacts::PhoneNumber::Work);
0296                 if (cellPhones.count() >= 2) {
0297                     cell = cellPhones.at(1);
0298                 }
0299                 fax = addressee->phoneNumber(KContacts::PhoneNumber::Fax);
0300                 break;
0301             case typeOther:
0302             default:
0303                 if (addressee->addresses(KContacts::Address::Home).count() > 1) {
0304                     address = addressee->addresses(KContacts::Address::Home).at(1);
0305                 }
0306                 if ((address.isEmpty()) && (addressee->addresses(KContacts::Address::Work).count() > 1)) {
0307                     address = addressee->addresses(KContacts::Address::Work).at(1);
0308                 }
0309                 if (address.isEmpty()) {
0310                     address = addressee->address(KContacts::Address::Dom);
0311                 }
0312                 if (address.isEmpty()) {
0313                     address = addressee->address(KContacts::Address::Intl);
0314                 }
0315                 if (address.isEmpty()) {
0316                     address = addressee->address(KContacts::Address::Postal);
0317                 }
0318                 if (address.isEmpty()) {
0319                     address = addressee->address(KContacts::Address::Parcel);
0320                 }
0321 
0322                 if (addressee->phoneNumbers(KContacts::PhoneNumber::Home).count() > 1) {
0323                     phone = addressee->phoneNumbers(KContacts::PhoneNumber::Home).at(1);
0324                 }
0325                 if ((phone.isEmpty()) && (addressee->phoneNumbers(KContacts::PhoneNumber::Work).count() > 1)) {
0326                     phone = addressee->phoneNumbers(KContacts::PhoneNumber::Work).at(1);
0327                 }
0328                 if (phone.isEmpty()) {
0329                     phone = addressee->phoneNumber(KContacts::PhoneNumber::Voice);
0330                 }
0331                 if (phone.isEmpty()) {
0332                     phone = addressee->phoneNumber(KContacts::PhoneNumber::Msg);
0333                 }
0334                 if (phone.isEmpty()) {
0335                     phone = addressee->phoneNumber(KContacts::PhoneNumber::Isdn);
0336                 }
0337                 if (phone.isEmpty()) {
0338                     phone = addressee->phoneNumber(KContacts::PhoneNumber::Car);
0339                 }
0340                 if (phone.isEmpty()) {
0341                     phone = addressee->phoneNumber(KContacts::PhoneNumber::Pager);
0342                 }
0343 
0344                 switch (cellPhones.count()) {
0345                 case 0:
0346                     break;
0347                 case 1:
0348                 case 2:
0349                     if (!address.isEmpty()) {
0350                         cell = cellPhones.at(0);
0351                     }
0352                     break;
0353                 default:
0354                     cell = cellPhones.at(2);
0355                     break;
0356                 }
0357                 break;
0358             }
0359 
0360             QString email;
0361             if (emails.count() > recId) {
0362                 email = emails[recId];
0363                 if (email == addressee->preferredEmail()) {
0364                     prefFlag |= 1;
0365                 }
0366             }
0367 
0368             if (!address.isEmpty() || !phone.isEmpty() || !cell.isEmpty() || !email.isEmpty()) {
0369                 t << addresseeId << DELIM // Address_id
0370                   << recId << DELIM // Record_id
0371                   << address.street() << DELIM // Street
0372                   << address.country() << DELIM // Country
0373                   << address.postalCode() << DELIM // Zipcode
0374                   << address.locality() << DELIM // City
0375                   << phone.number() << DELIM // Phone
0376                   << fax.number() << DELIM // Fax
0377                   << cell.number() << DELIM // Mobile
0378 
0379                   << ((recId == typeWork) ? 0 : 1) << DELIM // Mobile_type
0380 
0381                   << email << DELIM // Email
0382 
0383                   << ((recId == typeWork) ? addressee->url().url().url() : QString()) << DELIM // Homepage
0384 
0385                   << ((recId == typeWork) ? addressee->role() : QString()) << DELIM // Position
0386 
0387                   << ((recId == typeHome) ? addressee->custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-SpousesName")) : QString()) << DELIM // Comments
0388 
0389                   << recId << DELIM // Record_type_id (0,1,2)
0390 
0391                   << DELIM // Record_type
0392 
0393                   << ((recId == typeWork) ? addressee->organization() : QString()) << DELIM // Company
0394 
0395                   << ((recId == typeWork) ? addressee->custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Department")) : QString())
0396                   << DELIM // Department
0397 
0398                   << dateString(addressee->revision()) << DELIM // Change_date
0399 
0400                   << prefFlag << DELIM // Preferred:
0401                                        // ( & 1: preferred email,
0402                                        //   & 4: preferred cell phone )
0403                   << 1 << QLatin1Char('\n'); // Status (should always be "1")
0404             }
0405         }
0406 
0407         ++addresseeId;
0408     }
0409 
0410     t << "####" << QLatin1Char('\n');
0411     t << "AB_CATEGORIES:" << QLatin1Char('\n');
0412     t << "Category_id,Name,Icon_id" << QLatin1Char('\n');
0413 
0414     //  Write Category List (beware: Category_ID 0 is reserved for none
0415     //  Interestingly: The index here is an int sequence and does not
0416     //  correspond to the bit reference used above.
0417     const int categoryCount(categoryMap.size());
0418     for (int i = 0; i < categoryCount; ++i) {
0419         t << (i + 1) << DELIM << categoryMap.at(i) << DELIM << 0 << QLatin1Char('\n');
0420     }
0421     t << "####" << QLatin1Char('\n');
0422 }
0423 
0424 void GMXImportExportPluginInterface::importGMX()
0425 {
0426     const QString fileName = QFileDialog::getOpenFileName(parentWidget(), QString(), QDir::homePath(), GMX_FILESELECTION_STRING);
0427 
0428     if (fileName.isEmpty()) {
0429         return;
0430     }
0431 
0432     QFile file(fileName);
0433     if (!file.open(QIODevice::ReadOnly)) {
0434         const QString msg = i18n("<qt>Unable to open <b>%1</b> for reading.</qt>", fileName);
0435         KMessageBox::error(parentWidget(), msg);
0436         return;
0437     }
0438 
0439     QDateTime dt;
0440     QTextStream gmxStream(&file);
0441     gmxStream.setEncoding(QStringConverter::Latin1);
0442     QString line;
0443     QString line2;
0444     line = gmxStream.readLine();
0445     line2 = gmxStream.readLine();
0446     if (!line.startsWith(QLatin1StringView("AB_ADDRESSES:")) || !line2.startsWith(QLatin1StringView("Address_id"))) {
0447         KMessageBox::error(parentWidget(), i18n("%1 is not a GMX address book file.", fileName));
0448         return;
0449     }
0450 
0451     QStringList itemList;
0452     QMap<QString, QString> categoriesOfAddressee;
0453     using AddresseeMap = QMap<QString, KContacts::Addressee *>;
0454     AddresseeMap addresseeMap;
0455 
0456     // "Address_id,Nickname,Firstname,Lastname,Title,Birthday,Comments,
0457     // Change_date,Status,Address_link_id,Categories"
0458     line = gmxStream.readLine();
0459     while ((line != QLatin1StringView("####")) && !gmxStream.atEnd()) {
0460         // an addressee entry may spread over several lines in the file
0461         while (true) {
0462             itemList = line.split(QLatin1Char('#'), Qt::KeepEmptyParts);
0463             if (itemList.count() >= 11) {
0464                 break;
0465             }
0466             line.append(QLatin1Char('\n'));
0467             line.append(gmxStream.readLine());
0468         }
0469 
0470         // populate the addressee
0471         auto addressee = new KContacts::Addressee;
0472         addressee->setNickName(itemList.at(1));
0473         addressee->setGivenName(itemList.at(2));
0474         addressee->setFamilyName(itemList.at(3));
0475         addressee->setFormattedName(itemList.at(3) + QStringLiteral(", ") + itemList.at(2));
0476         addressee->setPrefix(itemList.at(4));
0477         if (checkDateTime(itemList.at(5), dt)) {
0478             addressee->setBirthday(dt);
0479         }
0480         addressee->setNote(itemList.at(6));
0481         if (checkDateTime(itemList.at(7), dt)) {
0482             addressee->setRevision(dt);
0483         }
0484         // addressee->setStatus( itemList[8] ); Status
0485         // addressee->xxx( itemList[9] ); Address_link_id
0486         categoriesOfAddressee[itemList[0]] = itemList[10];
0487         addresseeMap[itemList[0]] = addressee;
0488 
0489         line = gmxStream.readLine();
0490     }
0491 
0492     // now read the address records
0493     line = gmxStream.readLine();
0494     if (!line.startsWith(QLatin1StringView("AB_ADDRESS_RECORDS:"))) {
0495         qCWarning(KADDRESSBOOK_IMPORTEXPORT_GMX_LOG) << "Could not find address records!";
0496         return;
0497     }
0498     // Address_id,Record_id,Street,Country,Zipcode,City,Phone,Fax,Mobile,
0499     // Mobile_type,Email,Homepage,Position,Comments,Record_type_id,Record_type,
0500     // Company,Department,Change_date,Preferred,Status
0501     line = gmxStream.readLine();
0502     line = gmxStream.readLine();
0503 
0504     while (!line.startsWith(QLatin1StringView("####")) && !gmxStream.atEnd()) {
0505         // an address entry may spread over several lines in the file
0506         while (true) {
0507             itemList = line.split(QLatin1Char('#'), Qt::KeepEmptyParts);
0508             if (itemList.count() >= 21) {
0509                 break;
0510             }
0511             line.append(QLatin1Char('\n'));
0512             line.append(gmxStream.readLine());
0513         }
0514 
0515         KContacts::Addressee *addressee = addresseeMap[itemList[0]];
0516         if (addressee) {
0517             // itemList[1] = Record_id (numbered item, ignore here)
0518             int recordTypeId = itemList[14].toInt();
0519             KContacts::Address::Type addressType;
0520             KContacts::PhoneNumber::Type phoneType;
0521             switch (recordTypeId) {
0522             case typeHome:
0523                 addressType = KContacts::Address::Home;
0524                 phoneType = KContacts::PhoneNumber::Home;
0525                 break;
0526             case typeWork:
0527                 addressType = KContacts::Address::Work;
0528                 phoneType = KContacts::PhoneNumber::Work;
0529                 break;
0530             case typeOther:
0531             default:
0532                 addressType = KContacts::Address::Intl;
0533                 phoneType = KContacts::PhoneNumber::Voice;
0534                 break;
0535             }
0536             KContacts::Address address = addressee->address(addressType);
0537             address.setStreet(itemList[2]);
0538             address.setCountry(itemList[3]);
0539             address.setPostalCode(itemList[4]);
0540             address.setLocality(itemList[5]);
0541             if (!itemList[6].isEmpty()) {
0542                 addressee->insertPhoneNumber(KContacts::PhoneNumber(itemList[6], phoneType));
0543             }
0544             if (!itemList[7].isEmpty()) {
0545                 addressee->insertPhoneNumber(KContacts::PhoneNumber(itemList[7], KContacts::PhoneNumber::Fax));
0546             }
0547             KContacts::PhoneNumber::Type cellType = KContacts::PhoneNumber::Cell;
0548             // itemList[9]=Mobile_type // always 0 or -1(default phone).
0549             // if ( itemList[19].toInt() & 4 ) cellType |= KContacts::PhoneNumber::Pref;
0550             // don't do the above to avoid duplicate mobile numbers
0551             if (!itemList[8].isEmpty()) {
0552                 addressee->insertPhoneNumber(KContacts::PhoneNumber(itemList[8], cellType));
0553             }
0554             KContacts::Email email(itemList[10]);
0555             if (itemList[19].toInt() & 1) {
0556                 email.setPreferred(true);
0557             }
0558             addressee->addEmail(email);
0559             if (!itemList[11].isEmpty()) {
0560                 KContacts::ResourceLocatorUrl url;
0561                 url.setUrl(QUrl(itemList[11]));
0562                 addressee->setUrl(url);
0563             }
0564             if (!itemList[12].isEmpty()) {
0565                 addressee->setRole(itemList[12]);
0566             }
0567             // itemList[13]=Comments
0568             // itemList[14]=Record_type_id (0,1,2) - see above
0569             // itemList[15]=Record_type (name of this additional record entry)
0570             if (!itemList[16].isEmpty()) {
0571                 addressee->setOrganization(itemList[16]); // Company
0572             }
0573             if (!itemList[17].isEmpty()) {
0574                 addressee->insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Department"), itemList[17]); // Department
0575             }
0576             if (checkDateTime(itemList[18], dt)) {
0577                 addressee->setRevision(dt); // Change_date
0578             }
0579             // itemList[19]=Preferred (see above)
0580             // itemList[20]=Status (should always be "1")
0581             addressee->insertAddress(address);
0582         } else {
0583             qCWarning(KADDRESSBOOK_IMPORTEXPORT_GMX_LOG) << "unresolved line:" << line;
0584         }
0585         line = gmxStream.readLine();
0586     }
0587 
0588     // extract the categories from the list of addressees of the file to import
0589     QStringList usedCategoryList;
0590     line = gmxStream.readLine();
0591     line2 = gmxStream.readLine();
0592     if (!line.startsWith(QLatin1StringView("AB_CATEGORIES:")) || !line2.startsWith(QLatin1StringView("Category_id"))) {
0593         qCWarning(KADDRESSBOOK_IMPORTEXPORT_GMX_LOG) << "Could not find category records!";
0594     } else {
0595         while (!line.startsWith(QLatin1StringView("####")) && !gmxStream.atEnd()) {
0596             // a category should not spread over multiple lines, but just in case
0597             while (true) {
0598                 itemList = line.split(QLatin1Char('#'), Qt::KeepEmptyParts);
0599                 if (itemList.count() >= 3) {
0600                     break;
0601                 }
0602                 line.append(QLatin1Char('\n'));
0603                 line.append(gmxStream.readLine());
0604             }
0605             usedCategoryList.append(itemList[1]);
0606             line = gmxStream.readLine();
0607         }
0608     }
0609     KContacts::Addressee::List addresseeList;
0610 
0611     // now add the addresses to addresseeList
0612     for (AddresseeMap::Iterator addresseeIt = addresseeMap.begin(); addresseeIt != addresseeMap.end(); ++addresseeIt) {
0613         KContacts::Addressee *addressee = addresseeIt.value();
0614         // Add categories
0615         // categories is a bitfield with max 31 defined categories
0616         int categories = categoriesOfAddressee[addresseeIt.key()].toInt();
0617         for (int i = 32; i >= 0; --i) {
0618             // convert category index to bitfield value for comparison
0619             int catBit = 1 << i;
0620             if (catBit > categories) {
0621                 continue; // current index unassigned
0622             }
0623             if (catBit & categories && usedCategoryList.count() > i) {
0624                 addressee->insertCategory(usedCategoryList[i]);
0625             }
0626         }
0627         addresseeList.append(*addressee);
0628         delete addressee;
0629     }
0630 
0631     file.close();
0632     KAddressBookImportExport::ContactList contactList;
0633     contactList.setAddressList(addresseeList);
0634 
0635     auto engine = new KAddressBookImportExport::ImportExportEngine(this);
0636     engine->setContactList(contactList);
0637     engine->setDefaultAddressBook(defaultCollection());
0638     engine->importContacts();
0639 }
0640 
0641 bool GMXImportExportPluginInterface::canImportFileType(const QUrl &url)
0642 {
0643     return url.path().endsWith(QLatin1StringView(".gmx"));
0644 }
0645 
0646 #include "moc_gmximportexportplugininterface.cpp"