File indexing completed on 2024-05-12 05:21:26

0001 /*
0002     SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez <aleixpol@kde.org>
0003     SPDX-FileCopyrightText: 2015 Martin Klapetek <mklapetek@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "kpeoplevcard.h"
0009 #include <KContacts/Picture>
0010 #include <KContacts/VCardConverter>
0011 #include <KLocalizedString>
0012 #include <QDebug>
0013 #include <QDir>
0014 #include <QImage>
0015 #include <QStandardPaths>
0016 
0017 #include <KFileUtils>
0018 #include <KPluginFactory>
0019 
0020 using namespace KPeople;
0021 
0022 #ifdef Q_OS_WIN
0023 Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsLocation,
0024             (QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QString::fromLatin1("/Contacts")))
0025 #else
0026 Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsLocation,
0027             (QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString::fromLatin1("/kpeoplevcard")))
0028 #endif
0029 
0030 #ifdef Q_OS_WIN
0031 Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsWriteLocation,
0032             (QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QString::fromLatin1("/Contacts/own")))
0033 #else
0034 Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsWriteLocation,
0035             (QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString::fromLatin1("/kpeoplevcard/own/")))
0036 #endif
0037 
0038 class VCardContact : public AbstractEditableContact
0039 {
0040 public:
0041     VCardContact()
0042     {
0043     }
0044     VCardContact(const KContacts::Addressee &addr, const QUrl &location)
0045         : m_addressee(addr)
0046         , m_location(location)
0047     {
0048     }
0049     void setAddressee(const KContacts::Addressee &addr)
0050     {
0051         m_addressee = addr;
0052     }
0053     void setUrl(const QUrl &url)
0054     {
0055         m_location = url;
0056     }
0057 
0058     QVariant customProperty(const QString &key) const override
0059     {
0060         QVariant ret;
0061         if (key == NameProperty) {
0062             const QString name = m_addressee.realName();
0063             if (!name.isEmpty()) {
0064                 return name;
0065             }
0066 
0067             // If both first and last name are set combine them to a full name
0068             if (!m_addressee.givenName().isEmpty() && !m_addressee.familyName().isEmpty())
0069                 return i18ndc("kpeoplevcard", "given-name family-name", "%1 %2", m_addressee.givenName(), m_addressee.familyName());
0070 
0071             // If only one of them is set just return what we know
0072             if (!m_addressee.givenName().isEmpty())
0073                 return m_addressee.givenName();
0074             if (!m_addressee.familyName().isEmpty())
0075                 return m_addressee.familyName();
0076 
0077             // Fall back to other identifiers
0078             if (!m_addressee.preferredEmail().isEmpty()) {
0079                 return m_addressee.preferredEmail();
0080             }
0081             if (!m_addressee.phoneNumbers().isEmpty()) {
0082                 return m_addressee.phoneNumbers().at(0).number();
0083             }
0084             return QVariant();
0085         } else if (key == EmailProperty)
0086             return m_addressee.preferredEmail();
0087         else if (key == AllEmailsProperty)
0088             return m_addressee.emails();
0089         else if (key == PictureProperty)
0090             return m_addressee.photo().data();
0091         else if (key == AllPhoneNumbersProperty) {
0092             const auto phoneNumbers = m_addressee.phoneNumbers();
0093             QVariantList numbers;
0094             for (const KContacts::PhoneNumber &phoneNumber : phoneNumbers) {
0095                 // convert from KContacts specific format to QString
0096                 numbers << phoneNumber.number();
0097             }
0098             return numbers;
0099         } else if (key == PhoneNumberProperty) {
0100             return m_addressee.phoneNumbers().isEmpty() ? QVariant() : m_addressee.phoneNumbers().at(0).number();
0101         } else if (key == VCardProperty) {
0102             KContacts::VCardConverter converter;
0103             return converter.createVCard(m_addressee);
0104         }
0105 
0106         return ret;
0107     }
0108 
0109     bool setCustomProperty(const QString &key, const QVariant &value) override
0110     {
0111         if (key == VCardProperty) {
0112             QFile f(m_location.toLocalFile());
0113             if (!f.open(QIODevice::WriteOnly))
0114                 return false;
0115             f.write(value.toByteArray());
0116             return true;
0117         }
0118         return false;
0119     }
0120 
0121     static QString createUri(const QString &path)
0122     {
0123         return QStringLiteral("vcard:/") + path;
0124     }
0125 
0126 private:
0127     KContacts::Addressee m_addressee;
0128     QUrl m_location;
0129 };
0130 
0131 bool VCardDataSource::addContact(const QVariantMap &properties)
0132 {
0133     if (!properties.contains("vcard"))
0134         return false;
0135 
0136     if (!QDir().mkpath(*vcardsWriteLocation))
0137         return false;
0138 
0139     QFile f(*vcardsWriteLocation + KFileUtils::suggestName(QUrl::fromLocalFile(*vcardsWriteLocation), QStringLiteral("contact.vcard")));
0140     if (!f.open(QFile::WriteOnly)) {
0141         qWarning() << "could not open file to write" << f.fileName();
0142         return false;
0143     }
0144 
0145     f.write(properties.value("vcard").toByteArray());
0146     return true;
0147 }
0148 
0149 bool VCardDataSource::deleteContact(const QString &uri)
0150 {
0151     if (!uri.startsWith("vcard:/"))
0152         return false;
0153 
0154     QString path = uri;
0155     path.remove("vcard:/");
0156 
0157     if (!path.startsWith(*vcardsLocation))
0158         return false;
0159 
0160     return QFile::remove(path);
0161 }
0162 
0163 KPeopleVCard::KPeopleVCard()
0164     : KPeople::AllContactsMonitor()
0165     , m_fs(new KDirWatch(this))
0166 {
0167     QDir().mkpath(*vcardsLocation);
0168 
0169     processDirectory(QFileInfo(*vcardsLocation));
0170 
0171     emitInitialFetchComplete(true);
0172 
0173     connect(m_fs, &KDirWatch::dirty, this, [this](const QString &path) {
0174         const QFileInfo fi(path);
0175         if (fi.isFile())
0176             processVCard(path);
0177         else
0178             processDirectory(fi);
0179     });
0180     connect(m_fs, &KDirWatch::created, this, [this](const QString &path) {
0181         const QFileInfo fi(path);
0182         if (fi.isFile())
0183             processVCard(path);
0184         else
0185             processDirectory(fi);
0186     });
0187     connect(m_fs, &KDirWatch::deleted, this, &KPeopleVCard::deleteVCard);
0188 }
0189 
0190 KPeopleVCard::~KPeopleVCard()
0191 {
0192 }
0193 
0194 QMap<QString, AbstractContact::Ptr> KPeopleVCard::contacts()
0195 {
0196     return m_contactForUri;
0197 }
0198 
0199 void KPeopleVCard::processDirectory(const QFileInfo &fi)
0200 {
0201     const QDir dir(fi.absoluteFilePath());
0202     {
0203         const auto subdirs = dir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); // includes '.', ie. vcards from no subdir
0204 
0205         for (const auto &subdir : subdirs) {
0206             processDirectory(subdir);
0207         }
0208     }
0209 
0210     {
0211         const QFileInfoList subdirVcards = dir.entryInfoList({"*.vcard", "*.vcf"});
0212         for (const QFileInfo &vcardFile : subdirVcards) {
0213             processVCard(vcardFile.absoluteFilePath());
0214         }
0215     }
0216     m_fs->addDir(dir.absolutePath(), KDirWatch::WatchDirOnly | KDirWatch::WatchSubDirs | KDirWatch::WatchFiles);
0217 }
0218 
0219 void KPeopleVCard::processVCard(const QString &path)
0220 {
0221     m_fs->addFile(path);
0222 
0223     QFile f(path);
0224     bool b = f.open(QIODevice::ReadOnly);
0225     if (!b) {
0226         qWarning() << "error: couldn't open:" << path;
0227         return;
0228     }
0229 
0230     KContacts::VCardConverter conv;
0231     const KContacts::Addressee addressee = conv.parseVCard(f.readAll());
0232 
0233     QString uri = VCardContact::createUri(path);
0234     auto it = m_contactForUri.find(uri);
0235     if (it != m_contactForUri.end()) {
0236         static_cast<VCardContact *>(it->data())->setAddressee(addressee);
0237         static_cast<VCardContact *>(it->data())->setUrl(QUrl::fromLocalFile(path));
0238         Q_EMIT contactChanged(uri, *it);
0239     } else {
0240         KPeople::AbstractContact::Ptr contact(new VCardContact(addressee, QUrl::fromLocalFile(path)));
0241         m_contactForUri.insert(uri, contact);
0242         Q_EMIT contactAdded(uri, contact);
0243     }
0244 }
0245 
0246 void KPeopleVCard::deleteVCard(const QString &path)
0247 {
0248     if (QFile::exists(path))
0249         return;
0250     QString uri = VCardContact::createUri(path);
0251 
0252     const int r = m_contactForUri.remove(uri);
0253     if (r)
0254         Q_EMIT contactRemoved(uri);
0255 }
0256 
0257 QString KPeopleVCard::contactsVCardPath()
0258 {
0259     return *vcardsLocation;
0260 }
0261 
0262 QString KPeopleVCard::contactsVCardWritePath()
0263 {
0264     return *vcardsWriteLocation;
0265 }
0266 
0267 VCardDataSource::VCardDataSource(QObject *parent, const QVariantList &args)
0268     : BasePersonsDataSourceV2(parent)
0269 {
0270     Q_UNUSED(args);
0271 }
0272 
0273 VCardDataSource::~VCardDataSource()
0274 {
0275 }
0276 
0277 QString VCardDataSource::sourcePluginId() const
0278 {
0279     return QStringLiteral("vcard");
0280 }
0281 
0282 AllContactsMonitor *VCardDataSource::createAllContactsMonitor()
0283 {
0284     return new KPeopleVCard();
0285 }
0286 
0287 K_PLUGIN_CLASS_WITH_JSON(VCardDataSource, "kpeoplevcard.json")
0288 
0289 #include "kpeoplevcard.moc"
0290 
0291 #include "moc_kpeoplevcard.cpp"