diff --git a/autotests/persondatatests.cpp b/autotests/persondatatests.cpp index b0bac49..bc151e0 100644 --- a/autotests/persondatatests.cpp +++ b/autotests/persondatatests.cpp @@ -1,122 +1,122 @@ /* * Copyright (C) 2013 David Edmundson * * 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 "persondatatests.h" #include #include #include //private includes #include "personmanager_p.h" -#include "personpluginmanager_p.h" //public kpeople includes #include +#include #include "fakecontactsource.h" QTEST_GUILESS_MAIN(PersonDataTests) using namespace KPeople; //this tests PersonData but also implicitly tests the private classes // - BasePersonsDataSource // - DefaultContactMonitor // - MetaContact void PersonDataTests::initTestCase() { QVERIFY(m_database.open()); // Called before the first testfunction is executed PersonManager::instance(m_database.fileName()); PersonManager::instance()->mergeContacts(QStringList() << QStringLiteral("fakesource://contact2") << QStringLiteral("fakesource://contact3")); m_source = new FakeContactSource(nullptr); //don't own. PersonPluginManager removes it on destruction QHash sources; sources[QStringLiteral("fakesource")] = m_source; PersonPluginManager::setDataSourcePlugins(sources); } void PersonDataTests::cleanupTestCase() { // Called after the last testfunction was executed m_database.close(); } void PersonDataTests::init() { // Called before each testfunction is executed } void PersonDataTests::cleanup() { // Called after every testfunction } void PersonDataTests::loadContact() { QString personUri = QStringLiteral("fakesource://contact1"); PersonData person(personUri); //in this case we know the datasource is synchronous, but we should extend the test to cope with it not being async. QCOMPARE(person.contactUris().size(), 1); QCOMPARE(person.name(), QStringLiteral("Contact 1")); QCOMPARE(person.allEmails(), QStringList(QStringLiteral("contact1@example.com"))); QCOMPARE(person.personUri(), personUri); } void PersonDataTests::loadPerson() { //loading contact 2 which is already merged should return person1 //which is both contact 2 and 3 PersonData person(QStringLiteral("fakesource://contact2")); QCOMPARE(person.contactUris().size(), 2); QCOMPARE(person.name(), QStringLiteral("Contact 2")); QCOMPARE(person.allEmails().size(), 2); QCOMPARE(person.personUri(), QStringLiteral("kpeople://1")); //convert to set as order is not important QCOMPARE(person.allEmails().toSet(), QSet() << QStringLiteral("contact2@example.com") << QStringLiteral("contact3@example.com")); } void PersonDataTests::contactChanged() { PersonData person(QStringLiteral("fakesource://contact1")); QCOMPARE(person.allEmails().at(0), QStringLiteral("contact1@example.com")); QSignalSpy spy(&person, SIGNAL(dataChanged())); m_source->changeProperty(AbstractContact::EmailProperty, QStringLiteral("newaddress@yahoo.com")); QCOMPARE(spy.count(), 1); QCOMPARE(person.allEmails().at(0), QStringLiteral("newaddress@yahoo.com")); } void PersonDataTests::nullPerson() { PersonData person(QStringLiteral("fakesource://unexisting")); QCOMPARE(QString(), person.name()); QVERIFY(!person.isValid()); PersonData invalidPerson(QStringLiteral()); QVERIFY(!invalidPerson.isValid()); } diff --git a/autotests/personsmodeltest.cpp b/autotests/personsmodeltest.cpp index 2432bc4..8cf320b 100644 --- a/autotests/personsmodeltest.cpp +++ b/autotests/personsmodeltest.cpp @@ -1,188 +1,188 @@ /* * Copyright (C) 2013 David Edmundson * * 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 "personsmodeltest.h" #include #include #include #include //private includes #include "personmanager_p.h" -#include "personpluginmanager_p.h" //public kpeople includes #include +#include #include "fakecontactsource.h" QTEST_GUILESS_MAIN(PersonsModelTest) using namespace KPeople; void PersonsModelTest::initTestCase() { QVERIFY(m_database.open()); // Called before the first testfunction is executed PersonManager::instance(m_database.fileName()); m_source = new FakeContactSource(nullptr); //don't own. PersonPluginManager removes it on destruction QHash sources; sources[QStringLiteral("fakesource")] = m_source; PersonPluginManager::setDataSourcePlugins(sources); m_model = new KPeople::PersonsModel(this); QSignalSpy modelInit(m_model, SIGNAL(modelInitialized(bool))); QTRY_COMPARE(modelInit.count(), 1); QCOMPARE(modelInit.first().at(0).toBool(), true); } void PersonsModelTest::cleanupTestCase() { // Called after the last testfunction was executed m_database.close(); } void PersonsModelTest::loadModel() { QCOMPARE(m_model->rowCount(), 4); QCOMPARE(m_model->data(m_model->index(0)).toString(), QStringLiteral("Contact 1")); QCOMPARE(m_model->data(m_model->index(1)).toString(), QStringLiteral("Contact 2")); QCOMPARE(m_model->data(m_model->index(2)).toString(), QStringLiteral("Contact 3")); QCOMPARE(m_model->data(m_model->index(3)).toString(), QStringLiteral("Contact 4")); m_source->changeProperty(AbstractContact::NameProperty, QStringLiteral("Contact A")); QCOMPARE(m_model->rowCount(), 4); QCOMPARE(m_model->data(m_model->index(0)).toString(), QStringLiteral("Contact A")); QCOMPARE(m_model->data(m_model->index(1)).toString(), QStringLiteral("Contact 2")); QCOMPARE(m_model->data(m_model->index(2)).toString(), QStringLiteral("Contact 3")); QCOMPARE(m_model->data(m_model->index(3)).toString(), QStringLiteral("Contact 4")); } void PersonsModelTest::mergeContacts() { QStringList uris{QStringLiteral("fakesource://contact1"), QStringLiteral("fakesource://contact2")}; QSignalSpy modelRowsInsert(m_model, SIGNAL(rowsInserted(QModelIndex,int,int))); QCOMPARE(m_model->rowCount(), 4); QString newUri = KPeople::mergeContacts(uris); QCOMPARE(newUri, QStringLiteral("kpeople://1")); // TODO: replace with actual model signals spying QTRY_COMPARE(m_model->rowCount(), 3); QCOMPARE(m_model->rowCount(m_model->indexForPersonUri(newUri)), 2); // There needs to be 2 rows inserted - one for the new Person // and one for the new contact added to it (the other contact // is already a child of the person; merging just takes all // contacts from one person and adds them to the other) QCOMPARE(modelRowsInsert.count(), 2); // The first inserted Person must have invalid parent index QCOMPARE(modelRowsInsert.first().at(0).toModelIndex(), QModelIndex()); // Second inserted row, the Contact, must have the Person index as parent QCOMPARE(modelRowsInsert.at(1).at(0).toModelIndex(), m_model->indexForPersonUri(newUri)); modelRowsInsert.clear(); QStringList uris2{QStringLiteral("fakesource://contact3"), newUri}; QString newUri2 = KPeople::mergeContacts(uris2); QCOMPARE(newUri2, QStringLiteral("kpeople://1")); QTest::qWait(2000); QCOMPARE(m_model->rowCount(), 2); QCOMPARE(m_model->rowCount(m_model->indexForPersonUri(newUri2)), 3); QCOMPARE(modelRowsInsert.count(), 1); QCOMPARE(modelRowsInsert.first().at(0).toModelIndex(), m_model->indexForPersonUri(newUri)); } void PersonsModelTest::gettersTests() { // Find the index for "kpeople://1" using the QAIModel method QModelIndexList indexList = m_model->match(m_model->index(0,0,QModelIndex()), KPeople::PersonsModel::PersonUriRole, QVariant(QStringLiteral("kpeople://1")), 1); QModelIndex personIndex = indexList.first(); // Now get the index using our method QModelIndex indexForPerson = m_model->indexForPersonUri(QStringLiteral("kpeople://1")); // Now compare QCOMPARE(personIndex, indexForPerson); // TODO: also test the get() method? } void PersonsModelTest::unmergeContacts() { QModelIndex personIndex = m_model->indexForPersonUri(QStringLiteral("kpeople://1")); QSignalSpy modelRowsInsert(m_model, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy modelRowsRemove(m_model, SIGNAL(rowsRemoved(QModelIndex,int,int))); QCOMPARE(m_model->rowCount(), 2); QCOMPARE(m_model->rowCount(personIndex), 3); KPeople::unmergeContact(QStringLiteral("fakesource://contact3")); QTest::qWait(2000); QCOMPARE(m_model->rowCount(), 3); QCOMPARE(m_model->rowCount(personIndex), 2); // The unmerged Contact is turned into new Person (the fake Person where Person == Contact) // There must be 1 insertion and the parent must be invalid index QCOMPARE(modelRowsInsert.count(), 1); QCOMPARE(modelRowsInsert.first().at(0).toModelIndex(), QModelIndex()); // Similarly, there must be one row removed and the parent must be // the old Person index QCOMPARE(modelRowsRemove.count(), 1); QCOMPARE(modelRowsRemove.first().at(0).toModelIndex(), personIndex); modelRowsInsert.clear(); modelRowsRemove.clear(); KPeople::unmergeContact(QStringLiteral("kpeople://1")); QTest::qWait(2000); QCOMPARE(m_model->rowCount(), 4); // Check that the person is gone from the model QCOMPARE(m_model->indexForPersonUri(QStringLiteral("kpeople://1")), QModelIndex()); QCOMPARE(modelRowsInsert.count(), 2); QCOMPARE(modelRowsInsert.first().at(0).toModelIndex(), QModelIndex()); QCOMPARE(modelRowsInsert.at(1).at(0).toModelIndex(), QModelIndex()); // There must be exactly 3 rows removed when unmerging a Person // with 2 contacts - first the subcontacts are removed and then // the parent Person itself QCOMPARE(modelRowsRemove.count(), 3); // The first two Contacts must have parent the Person index // and both should have 0 0 as the "first last" args of removeRows QCOMPARE(modelRowsRemove.first().at(0).toModelIndex(), personIndex); QCOMPARE(modelRowsRemove.first().at(1).toInt(), 0); QCOMPARE(modelRowsRemove.first().at(2).toInt(), 0); QCOMPARE(modelRowsRemove.at(1).at(0).toModelIndex(), personIndex); QCOMPARE(modelRowsRemove.at(1).at(1).toInt(), 0); QCOMPARE(modelRowsRemove.at(1).at(2).toInt(), 0); // The parent Person should have just invalid index as parent // (and we don't care about the position) QCOMPARE(modelRowsRemove.at(2).at(0).toModelIndex(), QModelIndex()); } diff --git a/autotests/personsproxymodeltest.cpp b/autotests/personsproxymodeltest.cpp index 5ab7f98..3ebb5ba 100644 --- a/autotests/personsproxymodeltest.cpp +++ b/autotests/personsproxymodeltest.cpp @@ -1,79 +1,79 @@ /* * Copyright (C) 2015 Aleix Pol i 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 "personsproxymodeltest.h" #include #include #include #include //private includes #include "personmanager_p.h" -#include "personpluginmanager_p.h" //public kpeople includes #include +#include #include #include "fakecontactsource.h" QTEST_GUILESS_MAIN(PersonsProxyModelTest) using namespace KPeople; void PersonsProxyModelTest::initTestCase() { QVERIFY(m_database.open()); // Called before the first testfunction is executed PersonManager::instance(m_database.fileName()); m_source = new FakeContactSource(nullptr); //don't own. PersonPluginManager removes it on destruction QHash sources; sources[QStringLiteral("fakesource")] = m_source; PersonPluginManager::setDataSourcePlugins(sources); m_model = new KPeople::PersonsModel(this); QSignalSpy modelInit(m_model, SIGNAL(modelInitialized(bool))); QTRY_COMPARE(modelInit.count(), 1); QCOMPARE(modelInit.first().at(0).toBool(), true); } void PersonsProxyModelTest::cleanupTestCase() { // Called after the last testfunction was executed m_database.close(); } void PersonsProxyModelTest::testFiltering() { PersonsSortFilterProxyModel proxy; proxy.setSourceModel(m_model); QCOMPARE(proxy.rowCount(), 4); proxy.setRequiredProperties(QStringList() << AbstractContact::PhoneNumberProperty); QCOMPARE(proxy.rowCount(), 2); proxy.setRequiredProperties(QStringList() << AbstractContact::PhoneNumberProperty << KPeople::AbstractContact::PresenceProperty); QCOMPARE(proxy.rowCount(), 3); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 092bf76..727aedb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,117 +1,118 @@ include_directories (${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) add_subdirectory(declarative) add_subdirectory(plugins) add_subdirectory(widgets) add_subdirectory(backends) ecm_qt_declare_logging_category(KF5People_debug_SRCS HEADER kpeople_debug.h IDENTIFIER KPEOPLE_LOG CATEGORY_NAME kf5.kpeople) add_library (KF5People ${KF5People_debug_SRCS} global.cpp metacontact.cpp persondata.cpp matchessolver.cpp match.cpp duplicatesfinder.cpp personsmodel.cpp personpluginmanager.cpp personmanager.cpp personssortfilterproxymodel.cpp ) add_library(KF5::People ALIAS KF5People) target_link_libraries (KF5People PUBLIC Qt5::Gui PRIVATE Qt5::Sql Qt5::DBus KF5::I18n KF5::CoreAddons KF5::PeopleBackend KF5::Service ) set_target_properties (KF5People PROPERTIES VERSION ${KPEOPLE_VERSION_STRING} SOVERSION ${KPEOPLE_SOVERSION} EXPORT_NAME People) target_include_directories (KF5People PUBLIC "$" INTERFACE "$" ) if (MSVC) set_target_properties (KF5People PROPERTIES OUTPUT_NAME libkpeople ) endif (MSVC) # Install: install (TARGETS KF5People EXPORT KPeopleTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) ecm_generate_headers(KPeople_CamelCase_HEADERS HEADER_NAMES PersonData + PersonPluginManager PersonsModel Global REQUIRED_HEADERS KPeople_HEADERS PREFIX KPeople ) install (FILES ${KPeople_CamelCase_HEADERS} DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KPeople/KPeople COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kpeople/kpeople_export.h ${KPeople_HEADERS} DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KPeople/kpeople COMPONENT Devel ) generate_export_header(KF5People EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/kpeople/kpeople_export.h BASE_NAME KPeople) if(BUILD_QCH) ecm_add_qch( KF5People_QCH NAME KPeople BASE_NAME KF5People VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KPeople_HEADERS} ${KPeopleWidgets_QCH_SOURCES} ${KPeopleBackend_QCH_SOURCES} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS Qt5Gui_QCH Qt5Widgets_QCH BLANK_MACROS KPEOPLE_EXPORT KPEOPLE_DEPRECATED KPEOPLE_DEPRECATED_EXPORT KPEOPLEWIDGETS_EXPORT KPEOPLEWIDGETS_DEPRECATED KPEOPLEWIDGETS_DEPRECATED_EXPORT KPEOPLEBACKEND_EXPORT KPEOPLEBACKEND_DEPRECATED KPEOPLEBACKEND_DEPRECATED_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() install (FILES kpeople_data_source.desktop kpeople_plugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) ecm_generate_pri_file(BASE_NAME KPeople LIB_NAME KF5ConfigCore DEPS "gui" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KPeople) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/global.cpp b/src/global.cpp index b3595ca..227958b 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -1,94 +1,94 @@ /* Copyright (C) 2013 David Edmundson 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 "global.h" #include "personmanager_p.h" -#include "personpluginmanager_p.h" +#include "personpluginmanager.h" #include "backends/abstractcontact.h" //these namespace members expose the useful bits of PersonManager //global.h should be included from every exported header file so namespace members are always visible QString KPeople::mergeContacts(const QStringList &uris) { return PersonManager::instance()->mergeContacts(uris); } bool KPeople::unmergeContact(const QString &uri) { return PersonManager::instance()->unmergeContact(uri); } QString KPeople::iconNameForPresenceString(const QString &presenceName) { if (presenceName == QLatin1String("available")) { return QStringLiteral("user-online"); } if (presenceName == QLatin1String("away")) { return QStringLiteral("user-away"); } if (presenceName == QLatin1String("busy") || presenceName == QLatin1String("dnd")) { return QStringLiteral("user-busy"); } if (presenceName == QLatin1String("xa")) { return QStringLiteral("user-away-extended"); } if (presenceName == QLatin1String("hidden")) { return QStringLiteral("user-invisible"); } return QStringLiteral("user-offline"); } int KPeople::presenceSortPriority(const QString &presenceName) { if (presenceName == QLatin1String("available")) { return 0; } if (presenceName == QLatin1String("busy") || presenceName == QLatin1String("dnd")) { return 1; } if (presenceName == QLatin1String("hidden")) { return 2; } if (presenceName == QLatin1String("away")) { return 3; } if (presenceName == QLatin1String("xa")) { return 4; } if (presenceName == QLatin1String("unknown")) { return 5; } if (presenceName == QLatin1String("offline")) { return 6; } return 7; } diff --git a/src/persondata.cpp b/src/persondata.cpp index f90e263..a054249 100644 --- a/src/persondata.cpp +++ b/src/persondata.cpp @@ -1,209 +1,209 @@ /* Copyright (C) 2013 David Edmundson (davidedmundson@kde.org) 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 "persondata.h" #include "metacontact_p.h" #include "personmanager_p.h" -#include "personpluginmanager_p.h" +#include "personpluginmanager.h" #include "backends/basepersonsdatasource.h" #include "backends/contactmonitor.h" #include "backends/abstractcontact.h" #include #include "kpeople_debug.h" #include namespace KPeople { class PersonDataPrivate { public: QStringList contactUris; MetaContact metaContact; QList watchers; }; } using namespace KPeople; KPeople::PersonData::PersonData(const QString &id, QObject *parent): QObject(parent), d_ptr(new PersonDataPrivate) { Q_D(PersonData); if (id.isEmpty()) { return; } QString personUri; //query DB if (id.startsWith(QLatin1String("kpeople://"))) { personUri = id; } else { personUri = PersonManager::instance()->personUriForContact(id); } if (personUri.isEmpty()) { d->contactUris = QStringList() << id; } else { d->contactUris = PersonManager::instance()->contactsForPersonUri(personUri); } QMap contacts; Q_FOREACH (const QString &contactUri, d->contactUris) { //load the correct data source for this contact ID const QString sourceId = contactUri.left(contactUri.indexOf(QStringLiteral("://"))); Q_ASSERT(!sourceId.isEmpty()); BasePersonsDataSource *dataSource = PersonPluginManager::dataSource(sourceId); if (dataSource) { ContactMonitorPtr cw = dataSource->contactMonitor(contactUri); d->watchers << cw; //if the data source already has the contact set it already //if not it will be loaded when the contactChanged signal is emitted if (cw->contact()) { contacts[contactUri] = cw->contact(); } connect(cw.data(), SIGNAL(contactChanged()), SLOT(onContactChanged())); } else qCWarning(KPEOPLE_LOG) << "error: creating PersonData for unknown contact" << contactUri << id; } if (personUri.isEmpty() && contacts.size() == 1) { d->metaContact = MetaContact(id, contacts.first()); } else { d->metaContact = MetaContact(personUri, contacts); } } PersonData::~PersonData() { delete d_ptr; } bool PersonData::isValid() const { Q_D(const PersonData); return !d->metaContact.id().isEmpty(); } QString PersonData::personUri() const { Q_D(const PersonData); return d->metaContact.id(); } QStringList PersonData::contactUris() const { Q_D(const PersonData); return d->metaContact.contactUris(); } void PersonData::onContactChanged() { Q_D(PersonData); ContactMonitor *watcher = qobject_cast(sender()); if (d->metaContact.contactUris().contains(watcher->contactUri())) { #ifdef __GNUC__ #warning probably not needed anymore #endif d->metaContact.updateContact(watcher->contactUri(), watcher->contact()); } else { d->metaContact.insertContact(watcher->contactUri(), watcher->contact()); } Q_EMIT dataChanged(); } QPixmap PersonData::photo() const { QPixmap avatar; QVariant pic = contactCustomProperty(AbstractContact::PictureProperty); if (pic.canConvert()) { avatar = QPixmap::fromImage(pic.value()); } else if (pic.canConvert()) { avatar = QPixmap(pic.toUrl().toLocalFile()); } if (avatar.isNull()) { static QString defaultAvatar = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf5/kpeople/dummy_avatar.png")); avatar = QPixmap(defaultAvatar); } return avatar; } QVariant PersonData::contactCustomProperty(const QString &key) const { Q_D(const PersonData); return d->metaContact.personAddressee()->customProperty(key); } QString PersonData::presenceIconName() const { QString contactPresence = contactCustomProperty(QStringLiteral("telepathy-presence")).toString(); return KPeople::iconNameForPresenceString(contactPresence); } QString PersonData::name() const { return contactCustomProperty(AbstractContact::NameProperty).toString(); } QString PersonData::presence() const { return contactCustomProperty(AbstractContact::PresenceProperty).toString(); } QUrl PersonData::pictureUrl() const { return contactCustomProperty(AbstractContact::PictureProperty).toUrl(); } QString PersonData::email() const { return contactCustomProperty(AbstractContact::EmailProperty).toString(); } QStringList PersonData::groups() const { // We might want to cache it eventually? QVariantList groups = contactCustomProperty(AbstractContact::GroupsProperty).toList(); QStringList ret; Q_FOREACH (const QVariant &g, groups) { Q_ASSERT(g.canConvert()); ret += g.toString(); } ret.removeDuplicates(); return ret; } QStringList PersonData::allEmails() const { QVariantList emails = contactCustomProperty(AbstractContact::AllEmailsProperty).toList(); QStringList ret; Q_FOREACH (const QVariant &e, emails) { Q_ASSERT(e.canConvert()); ret += e.toString(); } ret.removeDuplicates(); return ret; } diff --git a/src/personpluginmanager.cpp b/src/personpluginmanager.cpp index c73ca95..1cf893c 100644 --- a/src/personpluginmanager.cpp +++ b/src/personpluginmanager.cpp @@ -1,117 +1,137 @@ /* Copyright (C) 2013 David Edmundson 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 "personpluginmanager_p.h" +#include "personpluginmanager.h" #include "backends/basepersonsdatasource.h" #include #include #include #include #include #include +#include #include "kpeople_debug.h" using namespace KPeople; class PersonPluginManagerPrivate { public: PersonPluginManagerPrivate(); ~PersonPluginManagerPrivate(); QHash dataSourcePlugins; void loadDataSourcePlugins(); + bool m_autoloadDataSourcePlugins; bool m_loadedDataSourcePlugins; QMutex m_mutex; }; Q_GLOBAL_STATIC(PersonPluginManagerPrivate, s_instance) -PersonPluginManagerPrivate::PersonPluginManagerPrivate(): - m_loadedDataSourcePlugins(false) +PersonPluginManagerPrivate::PersonPluginManagerPrivate() + : m_autoloadDataSourcePlugins(true) + , m_loadedDataSourcePlugins(false) { } PersonPluginManagerPrivate::~PersonPluginManagerPrivate() { qDeleteAll(dataSourcePlugins); } void PersonPluginManagerPrivate::loadDataSourcePlugins() { QVector pluginList = KPluginLoader::findPlugins(QStringLiteral("kpeople/datasource")); Q_FOREACH (const KPluginMetaData &service, pluginList) { KPluginLoader loader(service.fileName()); KPluginFactory *factory = loader.factory(); - BasePersonsDataSource *dataSource = qobject_cast(factory->create()); + BasePersonsDataSource *dataSource = factory->create(); if (dataSource) { - dataSourcePlugins[dataSource->sourcePluginId()] = dataSource; + const QString pluginId = dataSource->sourcePluginId(); + if (!dataSourcePlugins.contains(pluginId)) { + dataSourcePlugins[pluginId] = dataSource; + } else { + dataSource->deleteLater(); + qCDebug(KPEOPLE_LOG) << "Plugin" << pluginId << "was already loaded manually, ignoring..."; + } } else { qCWarning(KPEOPLE_LOG) << "Failed to create data source " << service.name() << service.fileName(); } } //TODO: Remove as soon as KTp sources are released with the new plugin system KService::List servicesList = KServiceTypeTrader::self()->query(QStringLiteral("KPeople/DataSource")); Q_FOREACH (const KService::Ptr &service, servicesList) { BasePersonsDataSource *dataSource = service->createInstance(nullptr); if (dataSource) { dataSourcePlugins[dataSource->sourcePluginId()] = dataSource; } else { qCWarning(KPEOPLE_LOG) << "Failed to create data source " << service->name() << service->path(); } } m_loadedDataSourcePlugins = true; } +void PersonPluginManager::setAutoloadDataSourcePlugins(bool autoloadDataSourcePlugins) +{ + s_instance->m_autoloadDataSourcePlugins = autoloadDataSourcePlugins; +} + +void PersonPluginManager::addDataSource(const QString &sourceId, BasePersonsDataSource *source) +{ + QMutexLocker(&s_instance->m_mutex); + if (s_instance->dataSourcePlugins.contains(sourceId)) { + qCWarning(KPEOPLE_LOG) << "Attempting to load data source that is already loaded, overriding!"; + s_instance->dataSourcePlugins[sourceId]->deleteLater(); + } + s_instance->dataSourcePlugins.insert(sourceId, source); +} + void PersonPluginManager::setDataSourcePlugins(const QHash &dataSources) { - s_instance->m_mutex.lock(); + QMutexLocker(&s_instance->m_mutex); qDeleteAll(s_instance->dataSourcePlugins); s_instance->dataSourcePlugins = dataSources; s_instance->m_loadedDataSourcePlugins = true; - s_instance->m_mutex.unlock(); } QList PersonPluginManager::dataSourcePlugins() { - s_instance->m_mutex.lock(); - if (!s_instance->m_loadedDataSourcePlugins) { + QMutexLocker(&s_instance->m_mutex); + if (!s_instance->m_loadedDataSourcePlugins && s_instance->m_autoloadDataSourcePlugins) { s_instance->loadDataSourcePlugins(); } - s_instance->m_mutex.unlock(); return s_instance->dataSourcePlugins.values(); } BasePersonsDataSource *PersonPluginManager::dataSource(const QString &sourceId) { - s_instance->m_mutex.lock(); - if (!s_instance->m_loadedDataSourcePlugins) { + QMutexLocker(&s_instance->m_mutex); + if (!s_instance->m_loadedDataSourcePlugins && s_instance->m_autoloadDataSourcePlugins) { s_instance->loadDataSourcePlugins(); } - s_instance->m_mutex.unlock(); - return s_instance->dataSourcePlugins.value(sourceId); } diff --git a/src/personpluginmanager_p.h b/src/personpluginmanager.h similarity index 60% rename from src/personpluginmanager_p.h rename to src/personpluginmanager.h index 87a4187..d4e8c79 100644 --- a/src/personpluginmanager_p.h +++ b/src/personpluginmanager.h @@ -1,49 +1,75 @@ /* Copyright (C) 2013 David Edmundson + Copyright (C) 2018 Igor Poboiko 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 PERSON_PLUGIN_MANAGER_H #define PERSON_PLUGIN_MANAGER_H #include #include #include namespace KPeople { class AbstractPersonAction; class BasePersonsDataSource; +/** + This class allows applications to manage DataSource plugins + + It can be useful if an application wants to use custom DataSource, + without exposing it to other KPeople-based applications + (i.e. without installing a system-wide plugin) + + Another use-case is that it allows to pass custom arguments to DataSources + (i.e. an ItemModel which will be used as a source of data) + + @since 5.51 + */ class KPEOPLE_EXPORT PersonPluginManager { public: + /** + * Use this if you explicitly don't want KPeople to autoload all the + * available data source plugins via KPluginLoader. + * + * The default behavior is to autoload them + */ + static void setAutoloadDataSourcePlugins(bool autoloadDataSourcePlugins); static QList dataSourcePlugins(); + /** + * Adds custom data source. If DataSource with such @p sourceId was already loaded, we override it + * + * Takes ownership of the @p source + */ + static void addDataSource(const QString &sourceId, BasePersonsDataSource *source); static BasePersonsDataSource *dataSource(const QString &sourceId); static QList actions(); /** * Instead of loading datasources from plugins, set sources manually * This is for unit tests only */ static void setDataSourcePlugins(const QHash &dataSources); }; } #endif // PERSON_PLUGIN_MANAGER_H diff --git a/src/personsmodel.cpp b/src/personsmodel.cpp index 168331d..3b53bf9 100644 --- a/src/personsmodel.cpp +++ b/src/personsmodel.cpp @@ -1,497 +1,497 @@ /* Persons Model Copyright (C) 2012 Martin Klapetek Copyright (C) 2012 Aleix Pol Gonzalez Copyright (C) 2013 David Edmundson 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 "personsmodel.h" -#include "personpluginmanager_p.h" +#include "personpluginmanager.h" #include "metacontact_p.h" #include "backends/basepersonsdatasource.h" #include "personmanager_p.h" #include "backends/abstractcontact.h" #include #include #include #include #include "kpeople_debug.h" namespace KPeople { class PersonsModelPrivate : public QObject { Q_OBJECT public: PersonsModelPrivate(PersonsModel *q) : q(q) , genericAvatarImagePath(QStandardPaths::locate(QStandardPaths::QStandardPaths::GenericDataLocation, QStringLiteral("kf5/kpeople/dummy_avatar.png"))) , initialFetchesDoneCount(0) , isInitialized(false) , hasError(false) { } PersonsModel *const q; //NOTE This is the opposite way round to the return value from contactMapping() for easier lookups QHash contactToPersons; //hash of person objects indexed by ID QHash personIndex; //a list so we have an order in the model QVector metacontacts; QString genericAvatarImagePath; QVector m_sourceMonitors; int initialFetchesDoneCount; bool isInitialized; bool hasError; //methods that manipulate the model void addPerson(const MetaContact &mc); void removePerson(const QString &id); void personChanged(const QString &personUri); QString personUriForContact(const QString &contactUri) const; QVariant dataForContact(const QString &personUri, const AbstractContact::Ptr &contact, int role) const; // SLOTS void onContactsFetched(); //update when a resource signals a contact has changed void onContactAdded(const QString &contactUri, const AbstractContact::Ptr &contact); void onContactChanged(const QString &contactUri, const AbstractContact::Ptr &contact); void onContactRemoved(const QString &contactUri); //update on metadata changes void onAddContactToPerson(const QString &contactUri, const QString &newPersonUri); void onRemoveContactsFromPerson(const QString &contactUri); public Q_SLOTS: void onMonitorInitialFetchComplete(bool success = true); }; } using namespace KPeople; PersonsModel::PersonsModel(QObject *parent): QAbstractItemModel(parent), d_ptr(new PersonsModelPrivate(this)) { Q_D(PersonsModel); Q_FOREACH (BasePersonsDataSource *dataSource, PersonPluginManager::dataSourcePlugins()) { const AllContactsMonitorPtr monitor = dataSource->allContactsMonitor(); if (monitor->isInitialFetchComplete()) { QMetaObject::invokeMethod(d, "onMonitorInitialFetchComplete", Qt::QueuedConnection, Q_ARG(bool, monitor->initialFetchSuccess())); } else { connect(monitor.data(), &AllContactsMonitor::initialFetchComplete, d, &PersonsModelPrivate::onMonitorInitialFetchComplete); } d->m_sourceMonitors << monitor; } d->onContactsFetched(); connect(PersonManager::instance(), &PersonManager::contactAddedToPerson, d, &PersonsModelPrivate::onAddContactToPerson); connect(PersonManager::instance(), &PersonManager::contactRemovedFromPerson, d, &PersonsModelPrivate::onRemoveContactsFromPerson); } PersonsModel::~PersonsModel() { } QHash PersonsModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); roles.insert(PersonUriRole, "personUri"); roles.insert(PersonVCardRole, "personVCard"); roles.insert(ContactsVCardRole, "contactsVCard"); return roles; } QVariant PersonsModel::data(const QModelIndex &index, int role) const { Q_D(const PersonsModel); //optimization - if we don't cover this role, ignore it if (role < Qt::UserRole && role != Qt::DisplayRole && role != Qt::DecorationRole) { return QVariant(); } if (index.row() < 0 || index.row() >= rowCount(index.parent())) { return QVariant(); } if (index.parent().isValid()) { if (role == ContactsVCardRole) { return QVariant::fromValue(AbstractContact::List()); } const MetaContact &mc = d->metacontacts.at(index.parent().row()); return d->dataForContact(mc.id(), mc.contacts().at(index.row()), role); } else { const MetaContact &mc = d->metacontacts.at(index.row()); return d->dataForContact(mc.id(), mc.personAddressee(), role); } } QVariant PersonsModelPrivate::dataForContact(const QString &personUri, const AbstractContact::Ptr &person, int role) const { switch (role) { case PersonsModel::FormattedNameRole: return person->customProperty(AbstractContact::NameProperty); case PersonsModel::PhotoRole: { QVariant pic = person->customProperty(AbstractContact::PictureProperty); if (pic.canConvert()) { QImage avatar = pic.value(); if (!avatar.isNull()) { return avatar; } } else if (pic.canConvert()) { QPixmap avatar = pic.value(); if (!avatar.isNull()) { return avatar; } } else if (pic.canConvert() && pic.toUrl().isLocalFile()) { QPixmap avatar = QPixmap(pic.toUrl().toLocalFile()); if (!avatar.isNull()) { return avatar; } } // If none of the above were valid images, // return the generic one return QPixmap(genericAvatarImagePath); } case PersonsModel::PersonUriRole: return personUri; case PersonsModel::PersonVCardRole: return QVariant::fromValue(person); case PersonsModel::ContactsVCardRole: return QVariant::fromValue(metacontacts[personIndex[personUri].row()].contacts()); case PersonsModel::GroupsRole: return person->customProperty(QStringLiteral("all-groups")); } return QVariant(); } int PersonsModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } int PersonsModel::rowCount(const QModelIndex &parent) const { Q_D(const PersonsModel); if (!parent.isValid()) { return d->metacontacts.size(); } if (parent.isValid() && !parent.parent().isValid()) { return d->metacontacts.at(parent.row()).contacts().count(); } return 0; } bool PersonsModel::isInitialized() const { Q_D(const PersonsModel); return d->isInitialized; } QModelIndex PersonsModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column < 0 || row >= rowCount(parent)) { return QModelIndex(); } //top level items have internalId -1. Anything >=0 is the row of the top level item if (!parent.isValid()) { return createIndex(row, column, -1); } return createIndex(row, column, parent.row()); } QModelIndex PersonsModel::parent(const QModelIndex &childIndex) const { if (childIndex.internalId() == -1 || !childIndex.isValid()) { return QModelIndex(); } return index(childIndex.internalId(), 0, QModelIndex()); } void PersonsModelPrivate::onMonitorInitialFetchComplete(bool success) { initialFetchesDoneCount++; if (!success) { hasError = true; } Q_ASSERT(initialFetchesDoneCount <= m_sourceMonitors.count()); if (initialFetchesDoneCount == m_sourceMonitors.count()) { isInitialized = true; Q_EMIT q->modelInitialized(!hasError); } } void PersonsModelPrivate::onContactsFetched() { QMap addresseeMap; //fetch all already loaded contacts from plugins Q_FOREACH (const AllContactsMonitorPtr &contactWatcher, m_sourceMonitors) { addresseeMap.unite(contactWatcher->contacts()); } //add metacontacts const QMultiHash contactMapping = PersonManager::instance()->allPersons(); Q_FOREACH (const QString &key, contactMapping.uniqueKeys()) { QMap contacts; Q_FOREACH (const QString &contact, contactMapping.values(key)) { contactToPersons[contact] = key; AbstractContact::Ptr ptr = addresseeMap.take(contact); if (ptr) { contacts[contact] = ptr; } } if (!contacts.isEmpty()) { addPerson(MetaContact(key, contacts)); } } //add remaining contacts QMap::const_iterator i; for (i = addresseeMap.constBegin(); i != addresseeMap.constEnd(); ++i) { addPerson(MetaContact(i.key(), i.value())); } Q_FOREACH (const AllContactsMonitorPtr monitor, m_sourceMonitors) { connect(monitor.data(), &AllContactsMonitor::contactAdded, this, &PersonsModelPrivate::onContactAdded); connect(monitor.data(), &AllContactsMonitor::contactChanged, this, &PersonsModelPrivate::onContactChanged); connect(monitor.data(), &AllContactsMonitor::contactRemoved, this, &PersonsModelPrivate::onContactRemoved); } } void PersonsModelPrivate::onContactAdded(const QString &contactUri, const AbstractContact::Ptr &contact) { const QString &personUri = personUriForContact(contactUri); QHash::const_iterator pidx = personIndex.constFind(personUri); if (pidx != personIndex.constEnd()) { int personRow = pidx->row(); MetaContact &mc = metacontacts[personRow]; //if the MC object already contains this object, we want to update the row, not do an insert if (mc.contactUris().contains(contactUri)) { qCWarning(KPEOPLE_LOG) << "Source emitted contactAdded for a contact we already know about " << contactUri; onContactChanged(contactUri, contact); } else { int newContactPos = mc.contacts().size(); q->beginInsertRows(q->index(personRow), newContactPos, newContactPos); mc.insertContact(contactUri, contact); q->endInsertRows(); personChanged(personUri); } } else { //new contact -> new person QMap map; map[contactUri] = contact; addPerson(MetaContact(personUri, map)); } } void PersonsModelPrivate::onContactChanged(const QString &contactUri, const AbstractContact::Ptr &contact) { const QString &personUri = personUriForContact(contactUri); int personRow = personIndex[personUri].row(); int contactRow = metacontacts[personRow].updateContact(contactUri, contact); const QModelIndex contactIndex = q->index(contactRow, 0, q->index(personRow)); Q_EMIT q->dataChanged(contactIndex, contactIndex); personChanged(personUri); } void PersonsModelPrivate::onContactRemoved(const QString &contactUri) { const QString &personUri = personUriForContact(contactUri); int personRow = personIndex[personUri].row(); MetaContact &mc = metacontacts[personRow]; int contactPosition = mc.contactUris().indexOf(contactUri); q->beginRemoveRows(q->index(personRow, 0), contactPosition, contactPosition); mc.removeContact(contactUri); q->endRemoveRows(); //if MC object is now invalid remove the person from the list if (!mc.isValid()) { removePerson(personUri); } personChanged(personUri); } void PersonsModelPrivate::onAddContactToPerson(const QString &contactUri, const QString &newPersonUri) { const QString oldPersonUri = personUriForContact(contactUri); contactToPersons.insert(contactUri, newPersonUri); int oldPersonRow = personIndex[oldPersonUri].row(); if (oldPersonRow < 0) { return; } MetaContact &oldMc = metacontacts[oldPersonRow]; //get contact already in the model, remove it from the previous contact int contactPosition = oldMc.contactUris().indexOf(contactUri); const AbstractContact::Ptr contact = oldMc.contacts().at(contactPosition); q->beginRemoveRows(q->index(oldPersonRow), contactPosition, contactPosition); oldMc.removeContact(contactUri); q->endRemoveRows(); if (!oldMc.isValid()) { removePerson(oldPersonUri); } else { personChanged(oldPersonUri); } //if the new person is already in the model, add the contact to it QHash::const_iterator pidx = personIndex.constFind(newPersonUri); if (pidx != personIndex.constEnd()) { int newPersonRow = pidx->row(); MetaContact &newMc = metacontacts[newPersonRow]; int newContactPos = newMc.contacts().size(); q->beginInsertRows(q->index(newPersonRow), newContactPos, newContactPos); newMc.insertContact(contactUri, contact); q->endInsertRows(); personChanged(newPersonUri); } else { //if the person is not in the model, create a new person and insert it QMap contacts; contacts[contactUri] = contact; addPerson(MetaContact(newPersonUri, contacts)); } } void PersonsModelPrivate::onRemoveContactsFromPerson(const QString &contactUri) { const QString personUri = personUriForContact(contactUri); int personRow = personIndex[personUri].row(); MetaContact &mc = metacontacts[personRow]; const AbstractContact::Ptr &contact = mc.contact(contactUri); const int index = mc.contactUris().indexOf(contactUri); q->beginRemoveRows(personIndex[personUri], index, index); mc.removeContact(contactUri); q->endRemoveRows(); contactToPersons.remove(contactUri); //if we don't want the person object anymore if (!mc.isValid()) { removePerson(personUri); } else { personChanged(personUri); } //now re-insert as a new contact //we know it's not part of a metacontact anymore so reinsert as a contact addPerson(MetaContact(contactUri, contact)); } void PersonsModelPrivate::addPerson(const KPeople::MetaContact &mc) { const QString &id = mc.id(); int row = metacontacts.size(); q->beginInsertRows(QModelIndex(), row, row); metacontacts.append(mc); personIndex[id] = q->index(row); q->endInsertRows(); } void PersonsModelPrivate::removePerson(const QString &id) { QPersistentModelIndex index = personIndex.value(id); if (!index.isValid()) { //item not found return; } q->beginRemoveRows(QModelIndex(), index.row(), index.row()); personIndex.remove(id); metacontacts.removeAt(index.row()); q->endRemoveRows(); } void PersonsModelPrivate::personChanged(const QString &personUri) { int row = personIndex[personUri].row(); if (row >= 0) { const QModelIndex personIndex = q->index(row); Q_EMIT q->dataChanged(personIndex, personIndex); } } QString PersonsModelPrivate::personUriForContact(const QString &contactUri) const { QHash::const_iterator it = contactToPersons.constFind(contactUri); if (it != contactToPersons.constEnd()) { return *it; } else { return contactUri; } } QModelIndex PersonsModel::indexForPersonUri(const QString &personUri) const { Q_D(const PersonsModel); return d->personIndex.value(personUri); } QVariant PersonsModel::get(int row, int role) { return index(row, 0).data(role); } QVariant PersonsModel::contactCustomProperty(const QModelIndex &index, const QString &key) const { Q_D(const PersonsModel); if (index.parent().isValid()) { const MetaContact &mc = d->metacontacts.at(index.parent().row()); return mc.contacts().at(index.row())->customProperty(key); } else { const MetaContact &mc = d->metacontacts.at(index.row()); return mc.personAddressee()->customProperty(key); } } #include "personsmodel.moc"