diff --git a/src/seasidecache.cpp b/src/seasidecache.cpp index 1e3aaeb..4a62f7a 100644 --- a/src/seasidecache.cpp +++ b/src/seasidecache.cpp @@ -106,6 +106,8 @@ QString managerName() : QString(); } +Q_GLOBAL_STATIC_WITH_ARGS(QContactManager, manager, (managerName())) + typedef QList DetailList; template @@ -302,6 +304,11 @@ QList internalIds(const QList &ids) SeasideCache *SeasideCache::instancePtr = 0; QStringList SeasideCache::allContactNameGroups = getAllContactNameGroups(); +QContactManager* SeasideCache::manager() +{ + return ::manager(); +} + SeasideCache* SeasideCache::instance() { return instancePtr; @@ -355,7 +362,7 @@ quint32 SeasideCache::internalId(QContactLocalId id) #endif SeasideCache::SeasideCache() - : m_manager(managerName()) + : m_syncFilter(FilterNone) #ifdef HAS_MLITE , m_displayLabelOrderConf(QLatin1String("/org/nemomobile/contacts/display_label_order")) , m_sortPropertyConf(QLatin1String("/org/nemomobile/contacts/sort_property")) @@ -366,7 +373,6 @@ SeasideCache::SeasideCache() , m_queryIndex(0) , m_fetchProcessedCount(0) , m_fetchByIdProcessedCount(0) - , m_syncFilter(FilterNone) , m_displayLabelOrder(FirstNameFirst) , m_sortProperty(QString::fromLatin1("firstName")) , m_groupProperty(QString::fromLatin1("firstName")) @@ -402,21 +408,23 @@ SeasideCache::SeasideCache() m_groupProperty = groupPropertyConf.toString(); #endif + QContactManager *mgr(manager()); + #ifdef USING_QTPIM - connect(&m_manager, SIGNAL(dataChanged()), this, SLOT(updateContacts())); - connect(&m_manager, SIGNAL(contactsAdded(QList)), + connect(mgr, SIGNAL(dataChanged()), this, SLOT(updateContacts())); + connect(mgr, SIGNAL(contactsAdded(QList)), this, SLOT(contactsAdded(QList))); - connect(&m_manager, SIGNAL(contactsChanged(QList)), + connect(mgr, SIGNAL(contactsChanged(QList)), this, SLOT(contactsChanged(QList))); - connect(&m_manager, SIGNAL(contactsRemoved(QList)), + connect(mgr, SIGNAL(contactsRemoved(QList)), this, SLOT(contactsRemoved(QList))); #else - connect(&m_manager, SIGNAL(dataChanged()), this, SLOT(updateContacts())); - connect(&m_manager, SIGNAL(contactsAdded(QList)), + connect(mgr, SIGNAL(dataChanged()), this, SLOT(updateContacts())); + connect(mgr, SIGNAL(contactsAdded(QList)), this, SLOT(contactsAdded(QList))); - connect(&m_manager, SIGNAL(contactsChanged(QList)), + connect(mgr, SIGNAL(contactsChanged(QList)), this, SLOT(contactsChanged(QList))); - connect(&m_manager, SIGNAL(contactsRemoved(QList)), + connect(mgr, SIGNAL(contactsRemoved(QList)), this, SLOT(contactsRemoved(QList))); #endif @@ -442,14 +450,14 @@ SeasideCache::SeasideCache() connect(&m_relationshipRemoveRequest, SIGNAL(stateChanged(QContactAbstractRequest::State)), this, SLOT(requestStateChanged(QContactAbstractRequest::State))); - m_fetchRequest.setManager(&m_manager); - m_fetchByIdRequest.setManager(&m_manager); - m_contactIdRequest.setManager(&m_manager); - m_relationshipsFetchRequest.setManager(&m_manager); - m_removeRequest.setManager(&m_manager); - m_saveRequest.setManager(&m_manager); - m_relationshipSaveRequest.setManager(&m_manager); - m_relationshipRemoveRequest.setManager(&m_manager); + m_fetchRequest.setManager(mgr); + m_fetchByIdRequest.setManager(mgr); + m_contactIdRequest.setManager(mgr); + m_relationshipsFetchRequest.setManager(mgr); + m_removeRequest.setManager(mgr); + m_saveRequest.setManager(mgr); + m_relationshipSaveRequest.setManager(mgr); + m_relationshipRemoveRequest.setManager(mgr); setSortOrder(m_sortProperty); } @@ -815,7 +823,7 @@ SeasideCache::CacheItem *SeasideCache::resolveOnlineAccount(ResolveListener *lis SeasideCache::ContactIdType SeasideCache::selfContactId() { - return instancePtr->m_manager.selfContactId(); + return manager()->selfContactId(); } void SeasideCache::requestUpdate() @@ -1934,7 +1942,7 @@ int SeasideCache::insertRange(FilterType filter, int index, int count, const QLi QList &cacheIds = m_contacts[filter]; QList &models = m_models[filter]; - const quint32 selfId = internalId(m_manager.selfContactId()); + const quint32 selfId = internalId(manager()->selfContactId()); int end = index + count - 1; for (int i = 0; i < models.count(); ++i) @@ -2435,7 +2443,7 @@ QString SeasideCache::exportContacts() QList contactsToFetch; contactsToFetch.reserve(instancePtr->m_people.count()); - const quint32 selfId = internalId(instancePtr->m_manager.selfContactId()); + const quint32 selfId = internalId(manager()->selfContactId()); typedef QHash::iterator iterator; for (iterator it = instancePtr->m_people.begin(); it != instancePtr->m_people.end(); ++it) { @@ -2449,7 +2457,7 @@ QString SeasideCache::exportContacts() } if (!contactsToFetch.isEmpty()) { - QList fetchedContacts = instancePtr->m_manager.contacts(contactsToFetch); + QList fetchedContacts = manager()->contacts(contactsToFetch); contacts.append(fetchedContacts); } diff --git a/src/seasidecache.h b/src/seasidecache.h index 6d643a9..6c7c763 100644 --- a/src/seasidecache.h +++ b/src/seasidecache.h @@ -256,6 +256,7 @@ class CONTACTCACHE_EXPORT SeasideCache : public QObject }; static SeasideCache *instance(); + static QContactManager *manager(); static ContactIdType apiId(const QContact &contact); static ContactIdType apiId(quint32 iid); @@ -446,7 +447,6 @@ private slots: QList m_models[FilterTypesCount]; QSet m_users; QHash m_expiredContacts; - QContactManager m_manager; QContactFetchRequest m_fetchRequest; QContactFetchByIdRequest m_fetchByIdRequest; #ifdef USING_QTPIM @@ -461,6 +461,7 @@ private slots: QContactRelationshipRemoveRequest m_relationshipRemoveRequest; QList m_sortOrder; QList m_onlineSortOrder; + FilterType m_syncFilter; #ifdef HAS_MLITE MGConfItem m_displayLabelOrderConf; MGConfItem m_sortPropertyConf; @@ -471,7 +472,6 @@ private slots: int m_queryIndex; int m_fetchProcessedCount; int m_fetchByIdProcessedCount; - FilterType m_syncFilter; DisplayLabelOrder m_displayLabelOrder; QString m_sortProperty; QString m_groupProperty; diff --git a/src/seasideimport.cpp b/src/seasideimport.cpp new file mode 100644 index 0000000..cccfc1a --- /dev/null +++ b/src/seasideimport.cpp @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2013 Jolla Mobile + * + * You may use this file under the terms of the BSD license as follows: + * + * "Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Nemo Mobile nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + */ + +#include "seasideimport.h" + +#include "seasidecache.h" +#include "seasidephotohandler.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USING_QTPIM +#include +#include +#else +#include +#endif + +#include +#include +#include +#include + +#include +#include + +namespace { + +QContactFetchHint basicFetchHint() +{ + QContactFetchHint fetchHint; + + fetchHint.setOptimizationHints(QContactFetchHint::NoRelationships | + QContactFetchHint::NoActionPreferences | + QContactFetchHint::NoBinaryBlobs); + + return fetchHint; +} + +QContactFilter localContactFilter() +{ + // Contacts that are local to the device have sync target 'local' or 'was_local' + QContactDetailFilter filterLocal, filterWasLocal; +#ifdef USING_QTPIM + filterLocal.setDetailType(QContactSyncTarget::Type, QContactSyncTarget::FieldSyncTarget); + filterWasLocal.setDetailType(QContactSyncTarget::Type, QContactSyncTarget::FieldSyncTarget); +#else + filterLocal.setDetailDefinitionName(QContactSyncTarget::DefinitionName, QContactSyncTarget::FieldSyncTarget); + filterWasLocal.setDetailDefinitionName(QContactSyncTarget::DefinitionName, QContactSyncTarget::FieldSyncTarget); +#endif + filterLocal.setValue(QString::fromLatin1("local")); + filterWasLocal.setValue(QString::fromLatin1("was_local")); + + return filterLocal | filterWasLocal; +} + +QString contactNameString(const QContact &contact) +{ + QStringList details; + QContactName name(contact.detail()); + details.append(name.prefix()); + details.append(name.firstName()); + details.append(name.middleName()); + details.append(name.lastName()); + details.append(name.suffix()); + return details.join(QChar::fromLatin1('|')); +} + + +template +QVariant detailValue(const T &detail, F field) +{ +#ifdef USING_QTPIM + return detail.value(field); +#else + return detail.variantValue(field); +#endif +} + +#ifdef USING_QTPIM +typedef QMap DetailMap; +#else +typedef QVariantMap DetailMap; +#endif + +DetailMap detailValues(const QContactDetail &detail) +{ +#ifdef USING_QTPIM + DetailMap rv(detail.values()); +#else + DetailMap rv(detail.variantValues()); +#endif + return rv; +} + +static bool variantEqual(const QVariant &lhs, const QVariant &rhs) +{ +#ifdef USING_QTPIM + // Work around incorrect result from QVariant::operator== when variants contain QList + static const int QListIntType = QMetaType::type("QList"); + + const int lhsType = lhs.userType(); + if (lhsType != rhs.userType()) { + return false; + } + + if (lhsType == QListIntType) { + return (lhs.value >() == rhs.value >()); + } +#endif + return (lhs == rhs); +} + +static bool detailValuesSuperset(const QContactDetail &lhs, const QContactDetail &rhs) +{ + // True if all values in rhs are present in lhs + const DetailMap lhsValues(detailValues(lhs)); + const DetailMap rhsValues(detailValues(rhs)); + + if (lhsValues.count() < rhsValues.count()) { + return false; + } + + foreach (const DetailMap::key_type &key, rhsValues.keys()) { + if (!variantEqual(lhsValues[key], rhsValues[key])) { + return false; + } + } + + return true; +} + +static void fixupDetail(QContactDetail &) +{ +} + +#ifdef USING_QTPIM +// Fixup QContactUrl because importer produces incorrectly typed URL field +static void fixupDetail(QContactUrl &url) +{ + QVariant urlField = url.value(QContactUrl::FieldUrl); + if (!urlField.isNull()) { + QString urlString = urlField.toString(); + if (!urlString.isEmpty()) { + url.setValue(QContactUrl::FieldUrl, QUrl(urlString)); + } else { + url.setValue(QContactUrl::FieldUrl, QVariant()); + } + } +} + +// Fixup QContactOrganization because importer produces invalid department +static void fixupDetail(QContactOrganization &org) +{ + QVariant deptField = org.value(QContactOrganization::FieldDepartment); + if (!deptField.isNull()) { + QStringList deptList = deptField.toStringList(); + + // Remove any empty elements from the list + QStringList::iterator it = deptList.begin(); + while (it != deptList.end()) { + if ((*it).isEmpty()) { + it = deptList.erase(it); + } else { + ++it; + } + } + + if (!deptList.isEmpty()) { + org.setValue(QContactOrganization::FieldDepartment, deptList); + } else { + org.setValue(QContactOrganization::FieldDepartment, QVariant()); + } + } +} +#endif + +template +bool updateExistingDetails(QContact *updateContact, const QContact &importedContact, bool singular = false) +{ + bool rv = false; + + QList existingDetails(updateContact->details()); + if (singular && !existingDetails.isEmpty()) + return rv; + + foreach (T detail, importedContact.details()) { + // Make any corrections to the input + fixupDetail(detail); + + // See if the contact already has a detail which is a superset of this one + bool found = false; + foreach (const T &existing, existingDetails) { + if (detailValuesSuperset(existing, detail)) { + found = true; + break; + } + } + if (!found) { + updateContact->saveDetail(&detail); + rv = true; + } + } + + return rv; +} + +bool mergeIntoExistingContact(QContact *updateContact, const QContact &importedContact) +{ + bool rv = false; + + // Update the existing contact with any details in the new import + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact, true); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); + rv |= updateExistingDetails(updateContact, importedContact); +#ifdef USING_QTPIM + rv |= updateExistingDetails(updateContact, importedContact); +#endif + + return rv; +} + +bool updateExistingContact(QContact *updateContact, const QContact &contact) +{ + // Replace the imported contact with the existing version + QContact importedContact(*updateContact); + *updateContact = contact; + + return mergeIntoExistingContact(updateContact, importedContact); +} + +} + +QList SeasideImport::buildImportContacts(const QList &details, int *newCount, int *updatedCount) +{ + if (newCount) + *newCount = 0; + if (updatedCount) + *updatedCount = 0; + + // Read the contacts from the import details + SeasidePhotoHandler photoHandler; + QVersitContactImporter importer; + importer.setPropertyHandler(&photoHandler); + importer.importDocuments(details); + + QList importedContacts(importer.contacts()); + + QHash::iterator> importGuids; + QHash::iterator> importNames; + QHash::iterator> importLabels; + + // Merge any duplicates in the import list + QList::iterator it = importedContacts.begin(); + while (it != importedContacts.end()) { + QContact &contact(*it); + + const QString guid = contact.detail().guid(); + const bool emptyName = contact.detail().isEmpty(); + const QString name = contactNameString(contact); + const QString label = contact.detail().label(); + + QContact *previous = 0; + QHash::iterator>::const_iterator git = importGuids.find(guid); + if (git != importGuids.end()) { + QContact &contact(*(git.value())); + previous = &contact; + } else { + QHash::iterator>::const_iterator nit = importNames.find(name); + if (nit != importNames.end()) { + QContact &contact(*(nit.value())); + previous = &contact; + } else { + // Only if name is empty, use displayLabel - probably SIM import + if (emptyName) { + QHash::iterator>::const_iterator lit = importLabels.find(label); + if (lit != importLabels.end()) { + QContact &contact(*(lit.value())); + previous = &contact; + } + } + } + } + + if (previous) { + // Combine these duplicate contacts + mergeIntoExistingContact(previous, contact); + it = importedContacts.erase(it); + } else { + if (!guid.isEmpty()) { + importGuids.insert(guid, it); + } + if (!emptyName) { + importNames.insert(name, it); + } else if (!label.isEmpty()) { + importLabels.insert(label, it); + + // Modify this contact to have the label as a nickname + QContactNickname nickname; + nickname.setNickname(label); + contact.saveDetail(&nickname); + } + + ++it; + } + } + + // Find all names and GUIDs for local contacts that might match these contacts + QContactFetchHint fetchHint(basicFetchHint()); +#ifdef USING_QTPIM + fetchHint.setDetailTypesHint(QList() << QContactName::Type << QContactNickname::Type << QContactGuid::Type); +#else + fetchHint.setDetailDefinitionsHint(QStringList() << QContactName::DefinitionName << QContactNickname::DefinitionName << QContactGuid::DefinitionName); +#endif + + QHash existingGuids; + QHash existingNames; + QHash existingNicknames; + + QContactManager *mgr(SeasideCache::manager()); + + foreach (const QContact &contact, mgr->contacts(localContactFilter(), QList(), fetchHint)) { + const QString guid = contact.detail().guid(); + const QString name = contactNameString(contact); + + if (!guid.isEmpty()) { + existingGuids.insert(guid, contact.id()); + } + if (!name.isEmpty()) { + existingNames.insert(name, contact.id()); + } + foreach (const QContactNickname &nick, contact.details()) { + existingNicknames.insert(nick.nickname(), contact.id()); + } + } + + // Find any imported contacts that match contacts we already have + QMap::iterator> existingIds; + QList::iterator> duplicates; + + QList::iterator end = importedContacts.end(); + for (it = importedContacts.begin(); it != end; ++it) { + const QString guid = (*it).detail().guid(); + + QContactId existingId; + + bool existing = true; + QHash::const_iterator git = existingGuids.find(guid); + if (git != existingGuids.end()) { + existingId = *git; + } else { + const bool emptyName = (*it).detail().isEmpty(); + if (!emptyName) { + const QString name = contactNameString(*it); + + QHash::const_iterator nit = existingNames.find(name); + if (nit != existingNames.end()) { + existingId = *nit; + } else { + existing = false; + } + } else { + const QString label = (*it).detail().label(); + if (!label.isEmpty()) { + QHash::const_iterator nit = existingNicknames.find(label); + if (nit != existingNicknames.end()) { + existingId = *nit; + } else { + existing = false; + } + } else { + existing = false; + } + } + } + + if (existing) { + QMap::iterator>::iterator eit = existingIds.find(existingId); + if (eit == existingIds.end()) { + existingIds.insert(existingId, it); + } else { + // Combine these contacts with matching names + QList::iterator cit(*eit); + mergeIntoExistingContact(&*cit, *it); + + duplicates.append(it); + } + } + } + + // Remove any duplicates we identified + while (!duplicates.isEmpty()) + importedContacts.erase(duplicates.takeLast()); + + int existingCount(existingIds.count()); + if (existingCount > 0) { + // Retrieve all the contacts that we have matches for +#ifdef USING_QTPIM + QContactIdFilter idFilter; + idFilter.setIds(existingIds.keys()); +#else + QContactLocalIdFilter idFilter; + QList localIds; + foreach (const QContactId &id, existingIds.keys()) { + localids.append(id.toLocal()); + } +#endif + + QSet modifiedContacts; + QSet unmodifiedContacts; + + foreach (const QContact &contact, mgr->contacts(idFilter & localContactFilter(), QList(), basicFetchHint())) { + QMap::iterator>::const_iterator it = existingIds.find(contact.id()); + if (it != existingIds.end()) { + // Update the existing version of the contact with any new details + QList::iterator cit(*it); + QContact &importContact(*cit); + bool modified = updateExistingContact(&importContact, contact); + if (modified) { + modifiedContacts.insert(importContact.id()); + } else { + unmodifiedContacts.insert(importContact.id()); + } + } else { + qWarning() << "unable to update existing contact:" << contact.id(); + } + } + + if (!unmodifiedContacts.isEmpty()) { + QList::iterator it = importedContacts.begin(); + while (it != importedContacts.end()) { + const QContact &importContact(*it); + const QContactId contactId(importContact.id()); + + if (unmodifiedContacts.contains(contactId) && !modifiedContacts.contains(contactId)) { + // This contact was not modified by import - don't update it + it = importedContacts.erase(it); + --existingCount; + } else { + ++it; + } + } + } + } + + if (updatedCount) + *updatedCount = existingCount; + if (newCount) + *newCount = importedContacts.count() - existingCount; + + return importedContacts; +} + diff --git a/src/seasideimport.h b/src/seasideimport.h new file mode 100644 index 0000000..a241332 --- /dev/null +++ b/src/seasideimport.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013 Jolla Mobile + * + * You may use this file under the terms of the BSD license as follows: + * + * "Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Nemo Mobile nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + */ + +#ifndef SEASIDEIMPORT_H +#define SEASIDEIMPORT_H + +#include "contactcacheexport.h" + +#include +#include + +#ifdef USING_QTPIM +QTCONTACTS_USE_NAMESPACE +QTVERSIT_USE_NAMESPACE +#else +QTM_USE_NAMESPACE +#endif + +class CONTACTCACHE_EXPORT SeasideImport +{ + SeasideImport(); + ~SeasideImport(); + +public: + static QList buildImportContacts(const QList &details, int *newCounti = 0, int *updatedCount = 0); +}; + +#endif diff --git a/src/src.pro b/src/src.pro index 1318217..0816bf8 100644 --- a/src/src.pro +++ b/src/src.pro @@ -38,11 +38,13 @@ DEFINES += CONTACTCACHE_BUILD SOURCES += \ $$PWD/seasidecache.cpp \ + $$PWD/seasideimport.cpp \ $$PWD/seasidephotohandler.cpp HEADERS += \ $$PWD/contactcacheexport.h \ $$PWD/seasidecache.h \ + $$PWD/seasideimport.h \ $$PWD/synchronizelists.h \ $$PWD/seasidenamegrouper.h \ $$PWD/seasidephotohandler.h @@ -50,6 +52,7 @@ HEADERS += \ headers.files = \ $$PWD/contactcacheexport.h \ $$PWD/seasidecache.h \ + $$PWD/seasideimport.h \ $$PWD/synchronizelists.h \ $$PWD/seasidenamegrouper.h \ $$PWD/seasidephotohandler.h