diff --git a/autotests/kpeoplevcardtest.cpp b/autotests/kpeoplevcardtest.cpp index f83f8aa..f21d370 100644 --- a/autotests/kpeoplevcardtest.cpp +++ b/autotests/kpeoplevcardtest.cpp @@ -1,144 +1,161 @@ /* Copyright (C) 2015 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include <../src/kpeoplevcard.h> class KPeopleVCardTest : public QObject { Q_OBJECT public: KPeopleVCardTest() + : m_source(new VCardDataSource(this, {})) {} + QByteArray createContact(const KContacts::Addressee& addressee) + { + KContacts::VCardConverter conv; + return conv.exportVCard(addressee, KContacts::VCardConverter::v3_0); + } + void writeContact(const KContacts::Addressee& addressee, const QString& path) { QFile f(path); bool b = f.open(QIODevice::WriteOnly); Q_ASSERT(b); - KContacts::VCardConverter conv; - f.write(conv.exportVCard(addressee, KContacts::VCardConverter::v3_0)); + f.write(createContact(addressee)); } private Q_SLOTS: void initTestCase() { QStandardPaths::setTestModeEnabled(true); m_vcardsDir = QDir(KPeopleVCard::contactsVCardPath()); QDir().temp().mkpath(KPeopleVCard::contactsVCardPath()); QVERIFY(m_vcardsDir.exists()); foreach(const QFileInfo & entry, m_vcardsDir.entryInfoList(QDir::Files)) { QFile::remove(entry.absoluteFilePath()); } + QDir(KPeopleVCard::contactsVCardWritePath()).removeRecursively(); QDir().temp().mkpath(KPeopleVCard::contactsVCardPath() + "/subdir"); - QCOMPARE(m_vcardsDir.count(), uint(3)); //. and .. and subdir/ - m_backend = new KPeopleVCard; - m_backend->setParent(this); + m_backend = dynamic_cast(m_source->createAllContactsMonitor()); } void emptyTest() { QVERIFY(m_backend->contacts().isEmpty()); } void crudTest() { // ENSURE EMPTY QVERIFY(m_backend->contacts().isEmpty()); const QString name = QStringLiteral("aaa"), name2 = QStringLiteral("bbb"); KContacts::Addressee addr; addr.setName(name); const QString path = m_vcardsDir.absoluteFilePath("X"); const QString pathInSubdir = m_vcardsDir.absoluteFilePath("subdir/a"); // CREATE { QSignalSpy spy(m_backend, &KPeople::AllContactsMonitor::contactAdded); writeContact(addr, path); QVERIFY(spy.wait()); // write the same contact into a subdir writeContact(addr, pathInSubdir); QVERIFY(spy.wait()); } // READ { KPeople::AbstractContact::Ptr ptr = m_backend->contacts().first(); QCOMPARE(ptr->customProperty(KPeople::AbstractContact::NameProperty).toString(), name); // the contact in subdir ptr = m_backend->contacts().last(); QCOMPARE(ptr->customProperty(KPeople::AbstractContact::NameProperty).toString(), name); } // UPDATE { addr.setName(name2); QSignalSpy spy(m_backend, &KPeople::AllContactsMonitor::contactChanged); writeContact(addr, path); QVERIFY(spy.wait()); writeContact(addr, pathInSubdir); QVERIFY(spy.wait()); } // READ { KPeople::AbstractContact::Ptr ptr = m_backend->contacts().first(); QCOMPARE(ptr->customProperty(KPeople::AbstractContact::NameProperty).toString(), name2); ptr = m_backend->contacts().last(); QCOMPARE(ptr->customProperty(KPeople::AbstractContact::NameProperty).toString(), name2); } // REMOVE { QSignalSpy spy(m_backend, &KPeople::AllContactsMonitor::contactRemoved); QFile::remove(path); QVERIFY(spy.wait()); QFile::remove(pathInSubdir); QVERIFY(spy.wait()); } // ENSURE EMPTY QVERIFY(m_backend->contacts().isEmpty()); } + void editableInterface() { + KContacts::Addressee addr; + addr.setName(QStringLiteral("Potato Person")); + + { + QSignalSpy spy(m_backend, &KPeople::AllContactsMonitor::contactAdded); + QVERIFY(m_source->addContact({ {"vcard", createContact(addr) } })); + QVERIFY(spy.wait()); + } + } + private: KPeopleVCard* m_backend; + VCardDataSource* m_source; QDir m_vcardsDir; }; QTEST_GUILESS_MAIN(KPeopleVCardTest) #include "kpeoplevcardtest.moc" diff --git a/src/kpeoplevcard.cpp b/src/kpeoplevcard.cpp index 8aea25b..d70eef5 100644 --- a/src/kpeoplevcard.cpp +++ b/src/kpeoplevcard.cpp @@ -1,245 +1,256 @@ /* Copyright (C) 2015 Aleix Pol Gonzalez Copyright (C) 2015 Martin Klapetek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "kpeoplevcard.h" #include #include #include #include #include #include -#include -#include #include #include #include #include using namespace KPeople; Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsLocation, (QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + ("/kpeoplevcard"))) Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsWriteLocation, (QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + ("/kpeoplevcard/own/"))) class VCardContact : public AbstractEditableContact { public: VCardContact() {} VCardContact(const KContacts::Addressee& addr, const QUrl &location) : m_addressee(addr), m_location(location) {} void setAddressee(const KContacts::Addressee& addr) { m_addressee = addr; } void setUrl(const QUrl &url) { m_location = url; } QVariant customProperty(const QString & key) const override { QVariant ret; if (key == NameProperty) { const QString name = m_addressee.realName(); if (!name.isEmpty()) { return name; } // If both first and last name are set combine them to a full name if (!m_addressee.givenName().isEmpty() && !m_addressee.familyName().isEmpty()) return i18nc("given-name family-name", "%1 %2", m_addressee.givenName(), m_addressee.familyName()); // If only one of them is set just return what we know if (!m_addressee.givenName().isEmpty()) return m_addressee.givenName(); if (!m_addressee.familyName().isEmpty()) return m_addressee.familyName(); // Fall back to other identifiers if (!m_addressee.preferredEmail().isEmpty()) { return m_addressee.preferredEmail(); } if (!m_addressee.phoneNumbers().isEmpty()) { return m_addressee.phoneNumbers().at(0).number(); } return QVariant(); } else if (key == EmailProperty) return m_addressee.preferredEmail(); else if (key == AllEmailsProperty) return m_addressee.emails(); else if (key == PictureProperty) return m_addressee.photo().data(); else if (key == AllPhoneNumbersProperty) { const auto phoneNumbers = m_addressee.phoneNumbers(); QVariantList numbers; for (const KContacts::PhoneNumber &phoneNumber : phoneNumbers) { // convert from KContacts specific format to QString numbers << phoneNumber.number(); } return numbers; } else if (key == PhoneNumberProperty) { return m_addressee.phoneNumbers().isEmpty() ? QVariant() : m_addressee.phoneNumbers().at(0).number(); } else if (key == VCardProperty) { KContacts::VCardConverter converter; return converter.createVCard(m_addressee); } return ret; } bool setCustomProperty(const QString & key, const QVariant & value) override { if (key == VCardProperty) { QFile f(m_location.toLocalFile()); if (!f.open(QIODevice::WriteOnly)) return false; f.write(value.toByteArray()); return true; } return false; } static QString createUri(const QString& path) { return QStringLiteral("vcard:/") + path; } private: KContacts::Addressee m_addressee; QUrl m_location; }; -class VCardDataSource : public KPeople::BasePersonsDataSourceV2 +bool VCardDataSource::addContact(const QVariantMap & properties) { -public: - VCardDataSource(QObject *parent, const QVariantList &data); - ~VCardDataSource() override; - QString sourcePluginId() const override; - - KPeople::AllContactsMonitor* createAllContactsMonitor() override; - bool addContact(const QVariantMap & properties) override { - if (!properties.contains("vcard")) - return false; - - if (!QDir().mkpath(*vcardsWriteLocation)) - return false; - - QFile f(*vcardsWriteLocation + KFileUtils::suggestName(QUrl::fromLocalFile(*vcardsWriteLocation), QStringLiteral("contact.vcard"))); - if (!f.open(QFile::WriteOnly)) { - qWarning() << "could not open file to write" << f.fileName(); - return false; - } + if (!properties.contains("vcard")) + return false; + + if (!QDir().mkpath(*vcardsWriteLocation)) + return false; - f.write(properties.value("vcard").toByteArray()); - return true; + QFile f(*vcardsWriteLocation + KFileUtils::suggestName(QUrl::fromLocalFile(*vcardsWriteLocation), QStringLiteral("contact.vcard"))); + if (!f.open(QFile::WriteOnly)) { + qWarning() << "could not open file to write" << f.fileName(); + return false; } -}; + + f.write(properties.value("vcard").toByteArray()); + return true; +} KPeopleVCard::KPeopleVCard() : KPeople::AllContactsMonitor() , m_fs(new KDirWatch(this)) { QDir().mkpath(*vcardsLocation); - QDir dir(*vcardsLocation); - const QStringList subdirs = dir.entryList(QDir::AllDirs | QDir::NoDotDot); // includes '.', ie. vcards from no subdir - QStringList entries; - - for (const QString &subdirName : subdirs) { - QDir subdir(dir.absoluteFilePath(subdirName)); - const QFileInfoList subdirVcards = subdir.entryInfoList({"*.vcard", "*.vcf"}); - for (const QFileInfo &vcardFile : subdirVcards) { - entries << vcardFile.absoluteFilePath(); - } - } - - for (const QString& entry : qAsConst(entries)) { - processVCard(entry); - } - - m_fs->addDir(dir.absolutePath(), KDirWatch::WatchDirOnly | KDirWatch::WatchSubDirs); - connect(m_fs, &KDirWatch::dirty, this, [this](const QString& path){ if (QFileInfo(path).isFile()) processVCard(path); }); - connect(m_fs, &KDirWatch::created, this, &KPeopleVCard::processVCard); + processDirectory(QFileInfo(*vcardsLocation)); + + connect(m_fs, &KDirWatch::dirty, this, [this](const QString& path) { + const QFileInfo fi(path); + if (fi.isFile()) + processVCard(path); + }); + connect(m_fs, &KDirWatch::created, this, [this] (const QString &path) { + const QFileInfo fi(path); + if (fi.isFile()) + processVCard(path); + else + processDirectory(fi); + }); connect(m_fs, &KDirWatch::deleted, this, &KPeopleVCard::deleteVCard); } KPeopleVCard::~KPeopleVCard() {} QMap KPeopleVCard::contacts() { return m_contactForUri; } +void KPeopleVCard::processDirectory(const QFileInfo& fi) +{ + static int i = 0; + const QDir dir(fi.absoluteFilePath()); + { + const auto subdirs = dir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); // includes '.', ie. vcards from no subdir + + for (const auto &subdir : subdirs) { + processDirectory(subdir); + } + } + + { + const QFileInfoList subdirVcards = dir.entryInfoList({"*.vcard", "*.vcf"}); + for (const QFileInfo &vcardFile : subdirVcards) { + processVCard(vcardFile.absoluteFilePath()); + } + } + m_fs->addDir(dir.absolutePath(), KDirWatch::WatchDirOnly | KDirWatch::WatchSubDirs | KDirWatch::WatchFiles); +} + void KPeopleVCard::processVCard(const QString &path) { m_fs->addFile(path); QFile f(path); bool b = f.open(QIODevice::ReadOnly); if (!b) { qWarning() << "error: couldn't open:" << path; return; } KContacts::VCardConverter conv; const KContacts::Addressee addressee = conv.parseVCard(f.readAll()); QString uri = VCardContact::createUri(path); auto it = m_contactForUri.find(uri); if (it != m_contactForUri.end()) { static_cast(it->data())->setAddressee(addressee); static_cast(it->data())->setUrl(QUrl::fromLocalFile(path)); Q_EMIT contactChanged(uri, *it); } else { KPeople::AbstractContact::Ptr contact(new VCardContact(addressee, QUrl::fromLocalFile(path))); m_contactForUri.insert(uri, contact); Q_EMIT contactAdded(uri, contact); } } void KPeopleVCard::deleteVCard(const QString &path) { if (QFile::exists(path)) return; QString uri = VCardContact::createUri(path); int r = m_contactForUri.remove(uri); Q_ASSERT(r); Q_EMIT contactRemoved(uri); } QString KPeopleVCard::contactsVCardPath() { return *vcardsLocation; } +QString KPeopleVCard::contactsVCardWritePath() +{ + return *vcardsWriteLocation; +} + VCardDataSource::VCardDataSource(QObject *parent, const QVariantList &args) : BasePersonsDataSourceV2(parent) { Q_UNUSED(args); } VCardDataSource::~VCardDataSource() { } QString VCardDataSource::sourcePluginId() const { return QStringLiteral("vcard"); } AllContactsMonitor* VCardDataSource::createAllContactsMonitor() { return new KPeopleVCard(); } K_PLUGIN_FACTORY_WITH_JSON( VCardDataSourceFactory, "kpeoplevcard.json", registerPlugin(); ) K_EXPORT_PLUGIN( VCardDataSourceFactory("kpeoplevcard") ) #include "kpeoplevcard.moc" diff --git a/src/kpeoplevcard.h b/src/kpeoplevcard.h index fcd27ba..e048db0 100644 --- a/src/kpeoplevcard.h +++ b/src/kpeoplevcard.h @@ -1,46 +1,64 @@ /* Copyright (C) 2015 Aleix Pol Gonzalez Copyright (C) 2015 Martin Klapetek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KPEOPLEVCARD_H #define KPEOPLEVCARD_H #include #include #include +#include +#include + +class QFileInfo; class Q_DECL_EXPORT KPeopleVCard : public KPeople::AllContactsMonitor { Q_OBJECT public: KPeopleVCard(); ~KPeopleVCard() override; QMap contacts() override; static QString contactsVCardPath(); + static QString contactsVCardWritePath(); private: void processVCard(const QString &path); void deleteVCard(const QString &path); + void processDirectory(const QFileInfo &fi); QMap m_contactForUri; KDirWatch* m_fs; }; + +class VCardDataSource : public KPeople::BasePersonsDataSourceV2 +{ +public: + VCardDataSource(QObject *parent, const QVariantList &data); + ~VCardDataSource() override; + QString sourcePluginId() const override; + + KPeople::AllContactsMonitor* createAllContactsMonitor() override; + bool addContact(const QVariantMap & properties) override; +}; + #endif