diff --git a/autotests/kpeoplevcardtest.cpp b/autotests/kpeoplevcardtest.cpp index f21d370..8c4d192 100644 --- a/autotests/kpeoplevcardtest.cpp +++ b/autotests/kpeoplevcardtest.cpp @@ -1,161 +1,177 @@ /* 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); 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 = 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")); + QString uri; { QSignalSpy spy(m_backend, &KPeople::AllContactsMonitor::contactAdded); QVERIFY(m_source->addContact({ {"vcard", createContact(addr) } })); QVERIFY(spy.wait()); + + uri = spy.constFirst().constFirst().toString(); + } + + QCOMPARE(QDir(KPeopleVCard::contactsVCardWritePath()).count(), 3); //. .. and the potato person + QVERIFY(m_backend->contacts().contains(uri)); + + { + QSignalSpy spy(m_backend, &KPeople::AllContactsMonitor::contactRemoved); + QVERIFY(m_source->deleteContact(uri)); + QVERIFY(spy.wait()); + + uri = spy.constFirst().constFirst().toString(); } + QCOMPARE(QDir(KPeopleVCard::contactsVCardWritePath()).count(), 2); //. .. and the potato person + QVERIFY(!m_backend->contacts().contains(uri)); } 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 b15cd7e..30b6e6a 100644 --- a/src/kpeoplevcard.cpp +++ b/src/kpeoplevcard.cpp @@ -1,264 +1,263 @@ /* 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 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; }; bool VCardDataSource::addContact(const QVariantMap & properties) { 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; } f.write(properties.value("vcard").toByteArray()); return true; } bool VCardDataSource::deleteContact(const QString &uri) { QString path = uri; path.remove("vcard:/"); - return QFile::remove(path); } KPeopleVCard::KPeopleVCard() : KPeople::AllContactsMonitor() , m_fs(new KDirWatch(this)) { QDir().mkpath(*vcardsLocation); 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); + + const int r = m_contactForUri.remove(uri); + if (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"