/* * 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 SEASIDECACHE_H #define SEASIDECACHE_H #include "contactcacheexport.h" #include "cacheconfiguration.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QTCONTACTS_USE_NAMESPACE class CONTACTCACHE_EXPORT SeasideDisplayLabelGroupChangeListener { public: SeasideDisplayLabelGroupChangeListener() {} ~SeasideDisplayLabelGroupChangeListener() {} virtual void displayLabelGroupsUpdated(const QHash > &groups) = 0; }; class CONTACTCACHE_EXPORT SeasideCache : public QObject { Q_OBJECT public: enum FilterType { FilterNone, FilterAll, FilterFavorites, FilterOnline, FilterTypesCount }; enum FetchDataType { FetchNone = 0, FetchAccountUri = (1 << 0), FetchPhoneNumber = (1 << 1), FetchEmailAddress = (1 << 2), FetchOrganization = (1 << 3), FetchTypesMask = (FetchAccountUri | FetchPhoneNumber | FetchEmailAddress | FetchOrganization) }; enum DisplayLabelOrder { FirstNameFirst = CacheConfiguration::FirstNameFirst, LastNameFirst = CacheConfiguration::LastNameFirst }; enum ContactState { ContactAbsent, ContactPartial, ContactRequested, ContactComplete }; enum { // Must be after the highest bit used in QContactStatusFlags::Flag HasValidOnlineAccount = (QContactStatusFlags::IsOnline << 1) }; struct ItemData { virtual ~ItemData() {} virtual void displayLabelOrderChanged(DisplayLabelOrder order) = 0; virtual void updateContact(const QContact &newContact, QContact *oldContact, ContactState state) = 0; virtual void constituentsFetched(const QList &ids) = 0; virtual void mergeCandidatesFetched(const QList &ids) = 0; virtual void aggregationOperationCompleted() = 0; virtual QList constituents() const = 0; }; struct CacheItem; struct ItemListener { ItemListener() : next(0), key(0) {} virtual ~ItemListener() {} virtual void itemUpdated(CacheItem *item) = 0; virtual void itemAboutToBeRemoved(CacheItem *item) = 0; ItemListener *next; void *key; }; struct CacheItem { CacheItem() : itemData(0), iid(0), statusFlags(0), contactState(ContactAbsent), listeners(0) {} CacheItem(const QContact &contact) : contact(contact), itemData(0), iid(internalId(contact)), statusFlags(contact.detail().flagsValue()), contactState(ContactAbsent), listeners(0) {} QContactId apiId() const { return SeasideCache::apiId(contact); } ItemListener *appendListener(ItemListener *listener, void *key) { if (listeners) { ItemListener *existing(listeners); while (existing->next) { existing = existing->next; } existing->next = listener; } else { listeners = listener; } listener->next = 0; listener->key = key; return listener; } bool removeListener(ItemListener *listener) { if (listeners) { ItemListener *existing(listeners); ItemListener **previous = &listeners; while (existing) { if (existing == listener) { *previous = listener->next; return true; } previous = &existing->next; existing = existing->next; } } return false; } ItemListener *listener(void *key) { ItemListener *existing(listeners); while (existing && (existing->key != key) && (existing->next)) { existing = existing->next; } return (existing && (existing->key == key)) ? existing : 0; } QContact contact; ItemData *itemData; quint32 iid; quint64 statusFlags; ContactState contactState; ItemListener *listeners; QString displayLabelGroup; QString displayLabel; }; struct ContactLinkRequest { ContactLinkRequest(const QContactId &id) : contactId(id), constituentsFetched(false) {} ContactLinkRequest(const ContactLinkRequest &req) : contactId(req.contactId), constituentsFetched(req.constituentsFetched) {} QContactId contactId; bool constituentsFetched; }; class ListModel : public QAbstractListModel { public: ListModel(QObject *parent = 0) : QAbstractListModel(parent) {} virtual ~ListModel() {} virtual void sourceAboutToRemoveItems(int begin, int end) = 0; virtual void sourceItemsRemoved() = 0; virtual void sourceAboutToInsertItems(int begin, int end) = 0; virtual void sourceItemsInserted(int begin, int end) = 0; virtual void sourceDataChanged(int begin, int end) = 0; virtual void sourceItemsChanged() = 0; virtual void makePopulated() = 0; virtual void updateDisplayLabelOrder() = 0; virtual void updateSortProperty() = 0; virtual void updateGroupProperty() = 0; }; struct ResolveListener { virtual ~ResolveListener() {} virtual void addressResolved(const QString &first, const QString &second, CacheItem *item) = 0; }; struct ChangeListener { virtual ~ChangeListener() {} virtual void itemUpdated(CacheItem *item) = 0; virtual void itemAboutToBeRemoved(CacheItem *item) = 0; }; static SeasideCache *instance(); static QContactManager *manager(); static QContactId apiId(const QContact &contact); static QContactId apiId(quint32 iid); static bool validId(const QContactId &id); static quint32 internalId(const QContact &contact); static quint32 internalId(const QContactId &id); static void registerModel(ListModel *model, FilterType type, FetchDataType requiredTypes = FetchNone, FetchDataType extraTypes = FetchNone); static void unregisterModel(ListModel *model); static void registerUser(QObject *user); static void unregisterUser(QObject *user); static void registerDisplayLabelGroupChangeListener(SeasideDisplayLabelGroupChangeListener *listener); static void unregisterDisplayLabelGroupChangeListener(SeasideDisplayLabelGroupChangeListener *listener); static void registerChangeListener(ChangeListener *listener); static void unregisterChangeListener(ChangeListener *listener); static void unregisterResolveListener(ResolveListener *listener); static DisplayLabelOrder displayLabelOrder(); static QString sortProperty(); static QString groupProperty(); static int contactId(const QContact &contact); static CacheItem *existingItem(const QContactId &id); static CacheItem *existingItem(quint32 iid); static CacheItem *itemById(const QContactId &id, bool requireComplete = true); static CacheItem *itemById(int id, bool requireComplete = true); static QContactId selfContactId(); static QContact contactById(const QContactId &id); static void ensureCompletion(CacheItem *cacheItem); static void refreshContact(CacheItem *cacheItem); static QString displayLabelGroup(const CacheItem *cacheItem); static QStringList allDisplayLabelGroups(); static QHash > displayLabelGroupMembers(); static CacheItem *itemByPhoneNumber(const QString &number, bool requireComplete = true); static CacheItem *itemByEmailAddress(const QString &address, bool requireComplete = true); static CacheItem *itemByOnlineAccount(const QString &localUid, const QString &remoteUid, bool requireComplete = true); static CacheItem *resolvePhoneNumber(ResolveListener *listener, const QString &number, bool requireComplete = true); static CacheItem *resolveEmailAddress(ResolveListener *listener, const QString &address, bool requireComplete = true); static CacheItem *resolveOnlineAccount(ResolveListener *listener, const QString &localUid, const QString &remoteUid, bool requireComplete = true); static bool saveContact(const QContact &contact); static bool removeContact(const QContact &contact); static void aggregateContacts(const QContact &contact1, const QContact &contact2); static void disaggregateContacts(const QContact &contact1, const QContact &contact2); static bool fetchConstituents(const QContact &contact); static bool fetchMergeCandidates(const QContact &contact); static int importContacts(const QString &path); static QString exportContacts(); static const QList *contacts(FilterType filterType); static bool isPopulated(FilterType filterType); static QString primaryName(const QString &firstName, const QString &lastName); static QString secondaryName(const QString &firstName, const QString &lastName); static void decomposeDisplayLabel(const QString &formattedDisplayLabel, QContactName *nameDetail); static QString generateDisplayLabel(const QContact &contact, DisplayLabelOrder order = FirstNameFirst); static QString generateDisplayLabelFromNonNameDetails(const QContact &contact); static QUrl filteredAvatarUrl(const QContact &contact, const QStringList &metadataFragments = QStringList()); static QString normalizePhoneNumber(const QString &input, bool validate = false); static QString minimizePhoneNumber(const QString &input, bool validate = false); bool event(QEvent *event); // For synchronizeLists() int insertRange(int index, int count, const QList &source, int sourceIndex) { return insertRange(m_syncFilter, index, count, source, sourceIndex); } int removeRange(int index, int count) { removeRange(m_syncFilter, index, count); return 0; } protected: void timerEvent(QTimerEvent *event); void setSortOrder(const QString &property); void startRequest(bool *idleProcessing); private slots: void contactsAvailable(); void contactIdsAvailable(); void relationshipsAvailable(); void requestStateChanged(QContactAbstractRequest::State state); void addressRequestStateChanged(QContactAbstractRequest::State state); void dataChanged(); void contactsAdded(const QList &contactIds); void contactsChanged(const QList &contactIds); void contactsPresenceChanged(const QList &contactIds); void contactsRemoved(const QList &contactIds); void displayLabelGroupsChanged(const QStringList &groups); void displayLabelOrderChanged(CacheConfiguration::DisplayLabelOrder order); void sortPropertyChanged(const QString &sortProperty); void displayStatusChanged(const QString &); private: enum PopulateProgress { Unpopulated, FetchFavorites, FetchMetadata, FetchOnline, Populated }; SeasideCache(); ~SeasideCache(); static void checkForExpiry(); void keepPopulated(quint32 requiredTypes, quint32 extraTypes); void requestUpdate(); void appendContacts(const QList &contacts, FilterType filterType, bool partialFetch, const QSet &queryDetailTypes); void fetchContacts(); void updateContacts(const QList &contactIds, QList *updateList); void applyPendingContactUpdates(); void applyContactUpdates(const QList &contacts, const QSet &queryDetailTypes); void resolveUnknownAddresses(const QString &first, const QString &second, CacheItem *item); bool updateContactIndexing(const QContact &oldContact, const QContact &contact, quint32 iid, const QSet &queryDetailTypes, CacheItem *item); void updateCache(CacheItem *item, const QContact &contact, bool partialFetch, bool initialInsert); void reportItemUpdated(CacheItem *item); void removeRange(FilterType filter, int index, int count); int insertRange(FilterType filter, int index, int count, const QList &queryIds, int queryIndex); void contactDataChanged(quint32 iid); void contactDataChanged(quint32 iid, FilterType filter); void removeContactData(quint32 iid, FilterType filter); void makePopulated(FilterType filter); void addToContactDisplayLabelGroup(quint32 iid, const QString &group, QSet *modifiedGroups = 0); void removeFromContactDisplayLabelGroup(quint32 iid, const QString &group, QSet *modifiedGroups = 0); void notifyDisplayLabelGroupsChanged(const QSet &groups); void updateConstituentAggregations(const QContactId &contactId); void completeContactAggregation(const QContactId &contact1Id, const QContactId &contact2Id); void resolveAddress(ResolveListener *listener, const QString &first, const QString &second, bool requireComplete); CacheItem *itemMatchingPhoneNumber(const QString &number, const QString &normalized, bool requireComplete); int contactIndex(quint32 iid, FilterType filter); static QContactRelationship makeRelationship(const QString &type, const QContactId &id1, const QContactId &id2); static QContactRelationship makeRelationship(const QString &type, const QContact &contact1, const QContact &contact2); QList m_contacts[FilterTypesCount]; QBasicTimer m_expiryTimer; QBasicTimer m_fetchTimer; QHash m_people; QMultiHash m_phoneNumberIds; QHash m_emailAddressIds; QHash, quint32> m_onlineAccountIds; QHash m_contactsToSave; QHash > m_contactDisplayLabelGroups; QList m_contactsToCreate; QHash, QList > > m_contactsToAppend; QList, QList > > m_contactsToUpdate; QList m_contactsToRemove; QList m_changedContacts; QList m_presenceChangedContacts; QSet m_aggregatedContacts; QList m_contactsToFetchConstituents; QList m_contactsToFetchCandidates; QList m_contactsToLinkTo; QList > m_contactPairsToLink; QList m_relationshipsToSave; QList m_relationshipsToRemove; QList m_displayLabelGroupChangeListeners; QList m_changeListeners; QList m_models[FilterTypesCount]; QSet m_users; QHash m_expiredContacts; QContactFetchRequest m_fetchRequest; QContactFetchByIdRequest m_fetchByIdRequest; QContactIdFetchRequest m_contactIdRequest; QContactRelationshipFetchRequest m_relationshipsFetchRequest; QContactRemoveRequest m_removeRequest; QContactSaveRequest m_saveRequest; QContactRelationshipSaveRequest m_relationshipSaveRequest; QContactRelationshipRemoveRequest m_relationshipRemoveRequest; QList m_sortOrder; QList m_onlineSortOrder; FilterType m_syncFilter; int m_populated; int m_cacheIndex; int m_queryIndex; int m_fetchProcessedCount; int m_fetchByIdProcessedCount; DisplayLabelOrder m_displayLabelOrder; QString m_sortProperty; QString m_groupProperty; bool m_keepPopulated; PopulateProgress m_populateProgress; bool m_populating; // true if current m_fetchRequest makes progress quint32 m_fetchTypes; quint32 m_extraFetchTypes; quint32 m_dataTypesFetched; bool m_updatesPending; bool m_refreshRequired; bool m_contactsUpdated; bool m_displayOff; QSet m_constituentIds; QSet m_candidateIds; struct ResolveData { QString first; QString second; QString compare; // only used in m_unknownAddresses bool requireComplete; ResolveListener *listener; }; QHash m_resolveAddresses; QSet m_pendingResolve; // these have active requests already QList m_unknownResolveAddresses; QList m_unknownAddresses; QSet m_resolvedPhoneNumbers; QElapsedTimer m_timer; QElapsedTimer m_fetchPostponed; static SeasideCache *instancePtr; static int contactDisplayLabelGroupCount; static QStringList allContactDisplayLabelGroups; static QTranslator *engEnTranslator; static QTranslator *translator; friend bool operator==(const SeasideCache::ResolveData &lhs, const SeasideCache::ResolveData &rhs); friend uint qHash(const SeasideCache::ResolveData &key, uint seed); }; bool operator==(const SeasideCache::ResolveData &lhs, const SeasideCache::ResolveData &rhs); uint qHash(const SeasideCache::ResolveData &key, uint seed = 0); #endif