From 1698ac16914200729543ca1568fcc6938266346b Mon Sep 17 00:00:00 2001 From: Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> Date: Wed, 3 Sep 2014 18:55:52 +0200 Subject: [PATCH] [ #53076 #53074 ] Store contact information in history log This commit refactor the contact handling code so it can merge multiple contacts into one at any point in time. This allow SFLPhone-KDE to store contacts UID in history, then start to use them before Akonadi or an other backend start. This is the first part of a 3 commit change that will also allow PhoneNumbers to be merged. In turn, this will double the ammount of data used to match calls to contact, solving bug #52259 This commit also remove some underused constants and add comments --- src/account.cpp | 2 +- src/account.h | 6 ++ src/call.cpp | 20 +++++- src/call.h | 3 + src/callmodel.cpp | 15 +++-- src/contact.cpp | 127 +++++++++++++++++++++++++++++++----- src/contact.h | 21 +++++- src/contactmodel.cpp | 25 +++++++ src/contactmodel.h | 3 + src/phonedirectorymodel.cpp | 7 +- src/phonenumber.cpp | 15 +++++ src/phonenumber.h | 2 + src/sflphone_const.h | 24 ------- 13 files changed, 216 insertions(+), 54 deletions(-) diff --git a/src/account.cpp b/src/account.cpp index a1411a64..02e94c6f 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -220,7 +220,7 @@ const QString Account::accountDetail(const QString& param) const } else if (m_hAccountDetails.count() > 0) { if (param == Account::MapField::ENABLED) //If an account is invalid, at least does not try to register it - return REGISTRATION_ENABLED_FALSE; + return Account::RegistrationEnabled::NO; if (param == Account::MapField::Registration::STATUS) //If an account is new, then it is unregistered return Account::State::UNREGISTERED; if (protocol() != Account::Protocol::IAX) //IAX accounts lack some fields, be quiet diff --git a/src/account.h b/src/account.h index 914e4eb3..81f83d12 100644 --- a/src/account.h +++ b/src/account.h @@ -153,6 +153,12 @@ class LIB_EXPORT Account : public QObject { constexpr static const char* REQUEST_TIMEOUT = "Request Timeout" ; }; + class RegistrationEnabled { + public: + constexpr static const char* YES = "true"; + constexpr static const char* NO = "false"; + }; + ~Account(); //Constructors static Account* buildExistingAccountFromId(const QString& _accountId); diff --git a/src/call.cpp b/src/call.cpp index de43fc5e..7ae83a31 100644 --- a/src/call.cpp +++ b/src/call.cpp @@ -379,14 +379,23 @@ Call* Call::buildHistoryCall(const QMap<QString,QString>& hc) accId = QString(Account::ProtocolName::IP2IP); } + //Try to assiciate a contact now, the real contact object is probably not + //loaded yet, but we can get a placeholder for now +// const QString& contactUsed = hc[ Call::HistoryMapFields::CONTACT_USED ]; //TODO + const QString& contactUid = hc[ Call::HistoryMapFields::CONTACT_UID ]; + + Contact* ct = nullptr; + if (!hc[ Call::HistoryMapFields::CONTACT_UID].isEmpty()) + ct = ContactModel::instance()->getPlaceHolder(contactUid.toAscii()); + Account* acc = AccountListModel::instance()->getAccountById(accId); - PhoneNumber* nb = PhoneDirectoryModel::instance()->getNumber(number,acc); + PhoneNumber* nb = PhoneDirectoryModel::instance()->getNumber(number,ct,acc); + Call* call = new Call(Call::State::OVER, callId, (name == "empty")?QString():name, nb, acc ); call->m_pStopTimeStamp = stopTimeStamp ; call->m_History = true; call->setStartTimeStamp(startTimeStamp); - call->m_HistoryState = historyStateFromType(type); call->m_Account = AccountListModel::instance()->getAccountById(accId); @@ -409,7 +418,7 @@ Call* Call::buildHistoryCall(const QMap<QString,QString>& hc) call->m_Direction = Call::Direction::INCOMING ; else if (call->m_HistoryState == Call::LegacyHistoryState::OUTGOING) call->m_Direction = Call::Direction::OUTGOING ; - else //BUG Pick one, even if it is the wrong one + else //Getting there is a bug. Pick one, even if it is the wrong one call->m_Direction = Call::Direction::OUTGOING ; if (missed) call->m_HistoryState = Call::LegacyHistoryState::MISSED; @@ -419,7 +428,12 @@ Call* Call::buildHistoryCall(const QMap<QString,QString>& hc) if (call->peerPhoneNumber()) { call->peerPhoneNumber()->addCall(call); + + //Reload the glow and number colors connect(call->peerPhoneNumber(),SIGNAL(presentChanged(bool)),call,SLOT(updated())); + + //Change the display name and picture + connect(call->peerPhoneNumber(),SIGNAL(rebased(PhoneNumber*)),call,SLOT(updated())); } return call; diff --git a/src/call.h b/src/call.h index 89031270..3a981fba 100644 --- a/src/call.h +++ b/src/call.h @@ -212,6 +212,9 @@ public: constexpr static const char* TIMESTAMP_STOP = "timestamp_stop" ; constexpr static const char* MISSED = "missed" ; constexpr static const char* DIRECTION = "direction" ; + constexpr static const char* CONTACT_USED = "contact_used" ; + constexpr static const char* CONTACT_UID = "contact_uid" ; + constexpr static const char* NUMBER_TYPE = "number_type" ; }; ///"getCallDetails()" fields diff --git a/src/callmodel.cpp b/src/callmodel.cpp index 244770e8..f190c112 100644 --- a/src/callmodel.cpp +++ b/src/callmodel.cpp @@ -285,6 +285,7 @@ Call* CallModel::addCall(Call* call, Call* parentCall) Q_ASSERT(false); } + //Even history call currently need to be tracked in CallModel, this may change InternalStruct* aNewStruct = new InternalStruct; aNewStruct->call_real = call; aNewStruct->conference = false; @@ -297,13 +298,15 @@ Call* CallModel::addCall(Call* call, Call* parentCall) } m_sPrivateCallList_callId[ call->id() ] = aNewStruct; - if (call->lifeCycleState() != Call::LifeCycleState::FINISHED) + //If the call is already finished, there is no point to track it here + if (call->lifeCycleState() != Call::LifeCycleState::FINISHED) { emit callAdded(call,parentCall); - const QModelIndex idx = index(m_lInternalModel.size()-1,0,QModelIndex()); - emit dataChanged(idx, idx); - connect(call,SIGNAL(changed(Call*)),this,SLOT(slotCallChanged(Call*))); - connect(call,SIGNAL(dtmfPlayed(QString)),this,SLOT(slotDTMFPlayed(QString))); - emit layoutChanged(); + const QModelIndex idx = index(m_lInternalModel.size()-1,0,QModelIndex()); + emit dataChanged(idx, idx); + connect(call,SIGNAL(changed(Call*)),this,SLOT(slotCallChanged(Call*))); + connect(call,SIGNAL(dtmfPlayed(QString)),this,SLOT(slotDTMFPlayed(QString))); + emit layoutChanged(); + } return call; } //addCall diff --git a/src/contact.cpp b/src/contact.cpp index dc0d922c..c651ed04 100644 --- a/src/contact.cpp +++ b/src/contact.cpp @@ -46,8 +46,55 @@ public: Contact::PhoneNumbers m_Numbers ; bool m_Active ; AbstractContactBackend* m_pBackend ; + bool m_isPlaceHolder ; + + //Helper code to help handle multiple parents + QList<Contact*> m_lParents; + + //As a single D-Pointer can have multiple parent (when merged), all emit need + //to use a proxy to make sure everybody is notified + void presenceChanged( PhoneNumber* ); + void statusChanged ( bool ); + void changed ( ); + void phoneNumberCountChanged(int,int); + void phoneNumberCountAboutToChange(int,int); }; +void ContactPrivate::changed() +{ + foreach (Contact* c,m_lParents) { + emit c->changed(); + } +} + +void ContactPrivate::presenceChanged( PhoneNumber* n ) +{ + foreach (Contact* c,m_lParents) { + emit c->presenceChanged(n); + } +} + +void ContactPrivate::statusChanged ( bool s ) +{ + foreach (Contact* c,m_lParents) { + emit c->statusChanged(s); + } +} + +void ContactPrivate::phoneNumberCountChanged(int n,int o) +{ + foreach (Contact* c,m_lParents) { + emit c->phoneNumberCountChanged(n,o); + } +} + +void ContactPrivate::phoneNumberCountAboutToChange(int n,int o) +{ + foreach (Contact* c,m_lParents) { + emit c->phoneNumberCountAboutToChange(n,o); + } +} + ContactPrivate::ContactPrivate(Contact* contact, AbstractContactBackend* parent):m_pPhoto(nullptr), m_Numbers(contact),m_DisplayPhoto(nullptr),m_Active(true), m_pBackend(parent?parent:TransitionalContactBackend::instance()) @@ -72,6 +119,8 @@ Contact* Contact::PhoneNumbers::contact() const Contact::Contact(AbstractContactBackend* parent):QObject(parent?parent:TransitionalContactBackend::instance()), d(new ContactPrivate(this,parent)) { + d->m_isPlaceHolder = false; + d->m_lParents << this; } ///Destructor @@ -153,19 +202,19 @@ void Contact::setPhoneNumbers(PhoneNumbers numbers) disconnect(n,SIGNAL(presentChanged(bool)),this,SLOT(slotPresenceChanged())); d->m_Numbers = numbers; if (newCount < oldCount) //Rows need to be removed from models first - emit phoneNumberCountAboutToChange(newCount,oldCount); + d->phoneNumberCountAboutToChange(newCount,oldCount); foreach(PhoneNumber* n, d->m_Numbers) connect(n,SIGNAL(presentChanged(bool)),this,SLOT(slotPresenceChanged())); if (newCount > oldCount) //Need to be updated after the data to prevent invalid memory access - emit phoneNumberCountChanged(newCount,oldCount); - emit changed(); + d->phoneNumberCountChanged(newCount,oldCount); + d->changed(); } ///Set the nickname void Contact::setNickName(const QString& name) { d->m_NickName = name; - emit changed(); + d->changed(); } ///Set the first name @@ -173,7 +222,7 @@ void Contact::setFirstName(const QString& name) { d->m_FirstName = name; setObjectName(formattedName()); - emit changed(); + d->changed(); } ///Set the family name @@ -181,64 +230,64 @@ void Contact::setFamilyName(const QString& name) { d->m_SecondName = name; setObjectName(formattedName()); - emit changed(); + d->changed(); } ///Set the Photo/Avatar void Contact::setPhoto(QPixmap* photo) { d->m_pPhoto = photo; - emit changed(); + d->changed(); } ///Set the formatted name (display name) void Contact::setFormattedName(const QString& name) { d->m_FormattedName = name; - emit changed(); + d->changed(); } ///Set the organisation / business void Contact::setOrganization(const QString& name) { d->m_Organization = name; - emit changed(); + d->changed(); } ///Set the default email void Contact::setPreferredEmail(const QString& name) { d->m_PreferredEmail = name; - emit changed(); + d->changed(); } ///Set UID void Contact::setUid(const QByteArray& id) { d->m_Uid = id; - emit changed(); + d->changed(); } ///Set Group void Contact::setGroup(const QString& name) { d->m_Group = name; - emit changed(); + d->changed(); } ///Set department void Contact::setDepartment(const QString& name) { d->m_Department = name; - emit changed(); + d->changed(); } ///If the contact have been deleted or not yet fully created void Contact::setActive( bool active) { d->m_Active = active; - emit statusChanged(d->m_Active); - emit changed(); + d->statusChanged(d->m_Active); + d->changed(); } ///Return if one of the PhoneNumber is present @@ -295,7 +344,7 @@ time_t Contact::PhoneNumbers::lastUsedTimeStamp() const ///Callback when one of the phone number presence change void Contact::slotPresenceChanged() { - emit changed(); + d->changed(); } ///Save the contact @@ -322,3 +371,49 @@ bool Contact::addPhoneNumber(PhoneNumber* n) { return d->m_pBackend->addPhoneNumber(this,n); } + +///Create a placeholder contact, it will eventually be replaced when the real one is loaded +ContactPlaceHolder::ContactPlaceHolder(const QByteArray& uid) +{ + setUid(uid); + d->m_isPlaceHolder = true; +} + + +bool ContactPlaceHolder::merge(Contact* contact) +{ + ContactPrivate* currentD = d; + replaceDPointer(contact); + currentD->m_lParents.removeAll(this); + if (!currentD->m_lParents.size()) + delete currentD; + return true; +} + +void Contact::replaceDPointer(Contact* c) +{ + this->d = c->d; + d->m_lParents << this; + emit changed(); + emit rebased(c); +} + +bool Contact::operator==(Contact* other) +{ + return this->d == other->d; +} + +bool Contact::operator==(const Contact* other) const +{ + return this->d == other->d; +} + +bool Contact::operator==(Contact& other) +{ + return this->d == other.d; +} + +bool Contact::operator==(const Contact& other) const +{ + return this->d == other.d; +} diff --git a/src/contact.h b/src/contact.h index 3e0fc082..a42a982e 100644 --- a/src/contact.h +++ b/src/contact.h @@ -22,7 +22,6 @@ #include <QtCore/QObject> #include <QtCore/QVariant> -#include <QtCore/QSharedPointer> #include <time.h> //Qt @@ -84,9 +83,10 @@ public: Q_INVOKABLE bool remove() ; Q_INVOKABLE bool addPhoneNumber(PhoneNumber* n); -private: +protected: //The D-Pointer can be shared if a PlaceHolderContact is merged with a real one - QSharedPointer<ContactPrivate> d; + ContactPrivate* d; + void replaceDPointer(Contact* other); public: //Constructors & Destructors @@ -126,6 +126,12 @@ public: void setPhoto ( QPixmap* photo ); void setActive ( bool active ); + //Operator + bool operator==(Contact* other); + bool operator==(const Contact* other) const; + bool operator==(Contact& other); + bool operator==(const Contact& other) const; + private Q_SLOTS: void slotPresenceChanged(); @@ -135,12 +141,21 @@ Q_SIGNALS: void changed ( ); void phoneNumberCountChanged(int,int); void phoneNumberCountAboutToChange(int,int); + void rebased ( Contact* ); protected: //Presence secret methods void updatePresenceInformations(const QString& uri, bool status, const QString& message); }; +class LIB_EXPORT ContactPlaceHolder : public Contact { + Q_OBJECT +public: + ContactPlaceHolder(const QByteArray& uid); + bool merge(Contact* contact); +}; + + Q_DECLARE_METATYPE(Contact*) #endif diff --git a/src/contactmodel.cpp b/src/contactmodel.cpp index e4c0da7b..6d7923b8 100644 --- a/src/contactmodel.cpp +++ b/src/contactmodel.cpp @@ -163,6 +163,25 @@ Contact* ContactModel::getContactByUid(const QByteArray& uid) return m_hContactsByUid[uid]; } +/** + * Create a temporary contact or return the existing one for an UID + * This temporary contact should eventually be merged into the real one + */ +Contact* ContactModel::getPlaceHolder(const QByteArray& uid ) +{ + Contact* ct = m_hContactsByUid[uid]; + + //Do not create a placeholder if the real deal exist + if (ct) { + return ct; + } + + ContactPlaceHolder* ct2 = new ContactPlaceHolder(uid); + + m_hPlaceholders[ct2->uid()] = ct2; + return ct2; +} + ///Return if there is backends bool ContactModel::hasBackends() const { @@ -208,6 +227,12 @@ bool ContactModel::addContact(Contact* c) beginInsertRows(QModelIndex(),m_lContacts.size()-1,m_lContacts.size()); m_lContacts << c; m_hContactsByUid[c->uid()] = c; + + //Deprecate the placeholder + if (m_hPlaceholders.contains(c->uid())) { + m_hPlaceholders[c->uid()]->merge(c); + m_hPlaceholders[c->uid()] = nullptr; + } endInsertRows(); emit layoutChanged(); emit newContactAdded(c); diff --git a/src/contactmodel.h b/src/contactmodel.h index e708f419..01c8c6f8 100644 --- a/src/contactmodel.h +++ b/src/contactmodel.h @@ -70,6 +70,7 @@ public: //Getters Contact* getContactByUid ( const QByteArray& uid ); + Contact* getPlaceHolder(const QByteArray& uid ); bool hasBackends () const; const ContactList contacts() const; virtual const QVector<AbstractContactBackend*> enabledBackends() const; @@ -96,6 +97,7 @@ private: static ContactModel* m_spInstance; QVector<AbstractContactBackend*> m_lBackends; CommonItemBackendModel* m_pBackendModel; + QHash<QByteArray,ContactPlaceHolder*> m_hPlaceholders; //Indexes QHash<QByteArray,Contact*> m_hContactsByUid; @@ -114,4 +116,5 @@ Q_SIGNALS: void newBackendAdded(AbstractContactBackend* backend); }; + #endif diff --git a/src/phonedirectorymodel.cpp b/src/phonedirectorymodel.cpp index db352611..b78118af 100644 --- a/src/phonedirectorymodel.cpp +++ b/src/phonedirectorymodel.cpp @@ -409,6 +409,9 @@ PhoneNumber* PhoneDirectoryModel::getNumber(const QString& uri, Account* account PhoneNumber* PhoneDirectoryModel::getNumber(const QString& uri, Contact* contact, Account* account, const QString& type) { + if (!contact) + return getNumber(uri,account,type); + const QString strippedUri = PhoneNumber::stripUri(uri); //See if the number is already loaded @@ -426,7 +429,9 @@ PhoneNumber* PhoneDirectoryModel::getNumber(const QString& uri, Contact* contact if ((!number->hasType()) && (!type.isEmpty())) { number->setCategory(NumberCategoryModel::instance()->getCategory(type)); } - if (((!contact) || number->contact() == contact) && ((!account) || number->account() == account)) + //Use the operator== check to avoid issues with placeholders + if (((!contact) || (number->contact() && (*number->contact()) == (*contact))) + && ((!account) || number->account() == account)) return number; } } diff --git a/src/phonenumber.cpp b/src/phonenumber.cpp index 1bd1701c..1e6f703f 100644 --- a/src/phonenumber.cpp +++ b/src/phonenumber.cpp @@ -162,6 +162,7 @@ void PhoneNumber::setContact(Contact* contact) PhoneDirectoryModel::instance()->indexNumber(this,d->m_hNames.keys()+QStringList(contact->formattedName())); d->m_PrimaryName_cache = contact->formattedName(); emit primaryNameChanged(d->m_PrimaryName_cache); + connect(contact,SIGNAL(rebased(Contact*)),this,SLOT(contactRebased(Contact*))); } emit changed(); } @@ -443,6 +444,20 @@ void PhoneNumber::accountDestroyed(QObject* o) d->m_pAccount = nullptr; } +/** + * When the PhoneNumber contact is merged with another one, the phone number + * data might be replaced, like the preferred name. + */ +void PhoneNumber::contactRebased(Contact* other) +{ + d->m_PrimaryName_cache = other->formattedName(); + emit primaryNameChanged(d->m_PrimaryName_cache); + emit changed(); + + //It is a "partial" rebase, so the PhoneNumber data stay the same + emit rebased(this); +} + /** * Merge two phone number to share the same data. This avoid having to change * pointers all over the place. The PhoneNumber objects remain intact, the diff --git a/src/phonenumber.h b/src/phonenumber.h index 03678612..d8824b7b 100644 --- a/src/phonenumber.h +++ b/src/phonenumber.h @@ -183,6 +183,7 @@ private: private Q_SLOTS: void accountDestroyed(QObject* o); + void contactRebased(Contact* other); Q_SIGNALS: void callAdded(Call* call); @@ -191,6 +192,7 @@ Q_SIGNALS: void presenceMessageChanged(const QString&); void trackedChanged(bool); void primaryNameChanged(const QString& name); + void rebased(PhoneNumber* other); }; Q_DECLARE_METATYPE(PhoneNumber*) diff --git a/src/sflphone_const.h b/src/sflphone_const.h index ed13598e..3312859d 100644 --- a/src/sflphone_const.h +++ b/src/sflphone_const.h @@ -22,19 +22,6 @@ #ifndef SFLPHONE_CONST_H #define SFLPHONE_CONST_H -#include <QtCore/QString> - -#define ACTION_LABEL_CALL i18n("New call") -#define ACTION_LABEL_PLACE_CALL i18n("Place call") -#define ACTION_LABEL_HANG_UP i18n("Hang up") -#define ACTION_LABEL_HOLD i18n("Hold on") -#define ACTION_LABEL_TRANSFER i18n("Transfer") -#define ACTION_LABEL_RECORD i18n("Record") -#define ACTION_LABEL_ACCEPT i18n("Pick up") -#define ACTION_LABEL_REFUSE i18n("Hang up") -#define ACTION_LABEL_UNHOLD i18n("Hold off") -#define ACTION_LABEL_GIVE_UP_TRANSF i18n("Give up transfer") -#define ACTION_LABEL_MAILBOX i18n("Voicemail") #define ICON_INCOMING ":/images/icons/ring.svg" #define ICON_RINGING ":/images/icons/ring.svg" @@ -63,20 +50,9 @@ #define ICON_HISTORY_MISSED ":/images/icons/missed.svg" #define ICON_SFLPHONE ":/images/icons/sflphone.svg" -/** Maybe to remove **/ -static const QString REGISTRATION_ENABLED_TRUE("true"); -static const QString REGISTRATION_ENABLED_FALSE("false"); // #define ACCOUNT_TYPES_TAB {QString(Account::ProtocolName::SIP), QString(Account::ProtocolName::IAX)} /*********************/ -/** Hooks settings */ -#define HOOKS_ADD_PREFIX "PHONE_NUMBER_HOOK_ADD_PREFIX" -#define HOOKS_ENABLED "PHONE_NUMBER_HOOK_ENABLED" -#define HOOKS_COMMAND "URLHOOK_COMMAND" -#define HOOKS_IAX2_ENABLED "URLHOOK_IAX2_ENABLED" -#define HOOKS_SIP_ENABLED "URLHOOK_SIP_ENABLED" -#define HOOKS_SIP_FIELD "URLHOOK_SIP_FIELD" - /** MIME API */ #define MIME_CALLID "text/sflphone.call.id" #define MIME_CONTACT "text/sflphone.contact" -- GitLab