Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/releaseTest
  • release/releaseWindowsTest
  • release/windowsReleaseTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 1.0.0
  • 0.3.0
  • 0.2.1
  • 0.2.0
  • 0.1.0
25 results

contactmethod.cpp

Blame
    • Emmanuel Lepage Vallée's avatar
      a1f07277
      contactMethod: Prevent duplication of registered name CMs · a1f07277
      Emmanuel Lepage Vallée authored
      
      **WARNING** This makes the setRegisteredName method private and
      thus break the API. That method could not stay public as this
      would potentially corrupt the indexes.
      
      This patch adds the registered names to the global name
      directory. If `::getNumber()` is used with a known registered
      name, no new CM are created.
      
      This patch fixes the "most common" case, but doesn't implement the
      following corner cases:
      
       * If the registered name is already part of the directory, but
         not fully linked to an account and/or RingId.
       * If there is multiple name service
      
      For the multiple name service, the
      `NameDirectory::registeredNameFound` signal will need to use full
      URIs for the names (ring:foo@ns.ring.cx). This is out of this
      patch scope and will have side effects that needs to be addressed
      in each clients.
      
      Change-Id: If06561dc4cb56453cc77092f4c30c633e852b7fd
      Reviewed-by: default avatarNicolas Jäger <nicolas.jager@savoirfairelinux.com>
      a1f07277
      History
      contactMethod: Prevent duplication of registered name CMs
      Emmanuel Lepage Vallée authored
      
      **WARNING** This makes the setRegisteredName method private and
      thus break the API. That method could not stay public as this
      would potentially corrupt the indexes.
      
      This patch adds the registered names to the global name
      directory. If `::getNumber()` is used with a known registered
      name, no new CM are created.
      
      This patch fixes the "most common" case, but doesn't implement the
      following corner cases:
      
       * If the registered name is already part of the directory, but
         not fully linked to an account and/or RingId.
       * If there is multiple name service
      
      For the multiple name service, the
      `NameDirectory::registeredNameFound` signal will need to use full
      URIs for the names (ring:foo@ns.ring.cx). This is out of this
      patch scope and will have side effects that needs to be addressed
      in each clients.
      
      Change-Id: If06561dc4cb56453cc77092f4c30c633e852b7fd
      Reviewed-by: default avatarNicolas Jäger <nicolas.jager@savoirfairelinux.com>
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    contactmethod.cpp 26.50 KiB
    /****************************************************************************
     *   Copyright (C) 2013-2016 by Savoir-faire Linux                          *
     *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
     *                                                                          *
     *   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 General Public License      *
     *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
     ***************************************************************************/
    #include "contactmethod.h"
    
    //Qt
    #include <QtCore/QCryptographicHash>
    #include <QtCore/QDateTime>
    
    //Ring daemon
    #include "dbus/configurationmanager.h"
    
    //Ring
    #include "phonedirectorymodel.h"
    #include "person.h"
    #include "account.h"
    #include "media/recordingmodel.h"
    #include "private/person_p.h"
    #include "private/contactmethod_p.h"
    #include "call.h"
    #include "availableaccountmodel.h"
    #include "dbus/presencemanager.h"
    #include "numbercategorymodel.h"
    #include "private/numbercategorymodel_p.h"
    #include "numbercategory.h"
    #include "certificate.h"
    #include "accountmodel.h"
    #include "certificatemodel.h"
    #include "media/textrecording.h"
    #include "mime.h"
    #include "globalinstances.h"
    #include "interfaces/pixmapmanipulatori.h"
    
    //Private
    #include "private/phonedirectorymodel_p.h"
    #include "private/textrecording_p.h"
    
    void ContactMethodPrivate::callAdded(Call* call)
    {
       foreach (ContactMethod* n, m_lParents)
          emit n->callAdded(call);
    }
    
    void ContactMethodPrivate::changed()
    {
       foreach (ContactMethod* n, m_lParents)
          emit n->changed();
    }
    
    void ContactMethodPrivate::presentChanged(bool s)
    {
       foreach (ContactMethod* n, m_lParents)
          emit n->presentChanged(s);
    }
    
    void ContactMethodPrivate::presenceMessageChanged(const QString& status)
    {
       foreach (ContactMethod* n, m_lParents)
          emit n->presenceMessageChanged(status);
    }
    
    void ContactMethodPrivate::trackedChanged(bool t)
    {
       foreach (ContactMethod* n, m_lParents)
          emit n->trackedChanged(t);
    }
    
    void ContactMethodPrivate::primaryNameChanged(const QString& name)
    {
       foreach (ContactMethod* n, m_lParents)
          emit n->primaryNameChanged(name);
    }
    
    void ContactMethodPrivate::rebased(ContactMethod* other)
    {
       foreach (ContactMethod* n, m_lParents)
          emit n->rebased(other);
    }
    
    void ContactMethodPrivate::registeredNameSet(const QString& name)
    {
       foreach (ContactMethod* n, m_lParents)
          emit n->registeredNameSet(name);
    }
    
    const ContactMethod* ContactMethod::BLANK()
    {
        static auto instance = []{
            auto instance = new ContactMethod(QString(), NumberCategoryModel::other());
            instance->d_ptr->m_Type = ContactMethod::Type::BLANK;
            return instance;
        }();
        return instance;
    }
    
    ContactMethodPrivate::ContactMethodPrivate(const URI& uri, NumberCategory* cat, ContactMethod::Type st, ContactMethod* q) :
       m_Uri(uri),m_pCategory(cat),m_Tracked(false),m_Present(false),
       m_Type(st),m_PopularityIndex(-1),m_pPerson(nullptr),m_pAccount(nullptr),
       m_IsBookmark(false),
       m_Index(-1),m_hasType(false),m_pTextRecording(nullptr), m_pCertificate(nullptr), q_ptr(q)
    {}
    
    ///Constructor
    ContactMethod::ContactMethod(const URI& number, NumberCategory* cat, Type st) : ItemBase(&PhoneDirectoryModel::instance()),
    d_ptr(new ContactMethodPrivate(number,cat,st,this))
    {
       setObjectName(d_ptr->m_Uri);
       d_ptr->m_hasType = cat != NumberCategoryModel::other();
       if (d_ptr->m_hasType) {
          NumberCategoryModel::instance().d_ptr->registerNumber(this);
       }
       d_ptr->m_lParents << this;
    }
    
    ContactMethod::~ContactMethod()
    {
       d_ptr->m_lParents.removeAll(this);
       if (!d_ptr->m_lParents.size())
          delete d_ptr;
    }
    
    ///Return if this number presence is being tracked
    bool ContactMethod::isTracked() const
    {
       //If the number doesn't support it, ignore the flag
       return supportPresence() && d_ptr->m_Tracked;
    }
    
    ///Is this number present
    bool ContactMethod::isPresent() const
    {
       return d_ptr->m_Tracked && d_ptr->m_Present;
    }
    
    ///This number presence status string
    QString ContactMethod::presenceMessage() const
    {
       return d_ptr->m_PresentMessage;
    }
    
    ///Return the number
    URI ContactMethod::uri() const {
       return d_ptr->m_Uri ;
    }
    
    ///This phone number has a type
    bool ContactMethod::hasType() const
    {
       return d_ptr->m_hasType;
    }
    
    ///Protected getter to get the number index
    int ContactMethod::index() const
    {
       return d_ptr->m_Index;
    }
    
    ///Return the phone number type
    NumberCategory* ContactMethod::category() const {
       return d_ptr->m_pCategory ;
    }
    
    ///Return this number associated account, if any
    Account* ContactMethod::account() const
    {
       return d_ptr->m_pAccount;
    }
    
    ///Return this number associated contact, if any
    Person* ContactMethod::contact() const
    {
       return d_ptr->m_pPerson;
    }
    
    ///Return when this number was last used
    time_t ContactMethod::lastUsed() const
    {
       return d_ptr->m_UsageStats.lastUsed();
    }
    
    ///Set this number default account
    void ContactMethod::setAccount(Account* account)
    {
       //Add the statistics
       if (account && !d_ptr->m_pAccount)
           account->usageStats += d_ptr->m_UsageStats;
    
       d_ptr->m_pAccount = account;
    
       //The sha1 is no longer valid
       d_ptr->m_Sha1.clear();
    
       if (d_ptr->m_pAccount)
          connect (d_ptr->m_pAccount,SIGNAL(destroyed(QObject*)),this,SLOT(accountDestroyed(QObject*)));
    
       //Track Ring contacts by default
       if (this->protocolHint() == URI::ProtocolHint::RING || this->protocolHint() == URI::ProtocolHint::RING_USERNAME) {
           setTracked(true);
       }
    
       d_ptr->changed();
    }
    
    ///Set this number contact
    void ContactMethod::setPerson(Person* contact)
    {
       Person* old = d_ptr->m_pPerson;
       if (d_ptr->m_pPerson == contact)
          return;
    
       d_ptr->m_pPerson = contact;
    
       //The sha1 is no longer valid
       d_ptr->m_Sha1.clear();
    
       if (contact)
          contact->d_ptr->registerContactMethod(this);
    
       if (contact && d_ptr->m_Type != ContactMethod::Type::TEMPORARY) {
          contact->d_ptr->registerContactMethod(this);
          PhoneDirectoryModel::instance().d_ptr->indexNumber(this,d_ptr->m_hNames.keys()+QStringList(contact->formattedName()));
          d_ptr->m_PrimaryName_cache = contact->formattedName();
          d_ptr->primaryNameChanged(d_ptr->m_PrimaryName_cache);
          connect(contact,SIGNAL(rebased(Person*)),this,SLOT(contactRebased(Person*)));
       }
       d_ptr->changed();
    
       emit contactChanged(contact, old);
    }
    
    ///Protected setter to set if there is a type
    void ContactMethod::setHasType(bool value)
    {
       d_ptr->m_hasType = value;
    }
    
    ///Protected setter to set the PhoneDirectoryModel index
    void ContactMethod::setIndex(int value)
    {
       d_ptr->m_Index = value;
    }
    
    ///Protected setter to change the popularity index
    void ContactMethod::setPopularityIndex(int value)
    {
       d_ptr->m_PopularityIndex = value;
    }
    
    void ContactMethod::setCategory(NumberCategory* cat)
    {
       if (cat == d_ptr->m_pCategory) return;
       if (d_ptr->m_hasType)
          NumberCategoryModel::instance().d_ptr->unregisterNumber(this);
       d_ptr->m_hasType = cat != NumberCategoryModel::other();
       d_ptr->m_pCategory = cat;
       if (d_ptr->m_hasType)
          NumberCategoryModel::instance().d_ptr->registerNumber(this);
       d_ptr->changed();
    }
    
    void ContactMethod::setBookmarked(bool bookmarked )
    {
       d_ptr->m_IsBookmark = bookmarked;
    }
    
    ///Force an Uid on this number (instead of hash)
    void ContactMethod::setUid(const QString& uri)
    {
       d_ptr->m_Uid = uri;
    }
    
    ///Attempt to change the number type
    bool ContactMethod::setType(ContactMethod::Type t)
    {
       if (d_ptr->m_Type == ContactMethod::Type::BLANK)
          return false;
       if (account() && t == ContactMethod::Type::ACCOUNT) {
          if (account()->supportPresenceSubscribe()) {
             d_ptr->m_Tracked = true; //The daemon will init the tracker itself
             d_ptr->trackedChanged(true);
          }
          d_ptr->m_Type = t;
          return true;
       }
       return false;
    }
    
    ///Update the last time used only if t is more recent than m_LastUsed
    void ContactMethod::setLastUsed(time_t t)
    {
        if (d_ptr->m_UsageStats.setLastUsed(t))
           emit lastUsedChanged(t);
    }
    
    ///Set if this number is tracking presence information
    void ContactMethod::setTracked(bool track)
    {
       if (track != d_ptr->m_Tracked) { //Subscribe only once
          //You can't subscribe without account
          if (track && !d_ptr->m_pAccount) return;
          d_ptr->m_Tracked = track;
          PresenceManager::instance().subscribeBuddy(d_ptr->m_pAccount->id(),
                                                     uri().format(URI::Section::CHEVRONS |
                                                                  URI::Section::SCHEME |
                                                                  URI::Section::USER_INFO |
                                                                  URI::Section::HOSTNAME),
                                                     track);
          d_ptr->changed();
          d_ptr->trackedChanged(track);
       }
    }
    
    /// Set the registered name
    void ContactMethodPrivate::setRegisteredName(const QString& registeredName)
    {
       if (registeredName.isEmpty() || registeredName == m_RegisteredName)
          return;
    
       // If this happens, better create another ContactMethod manually to avoid
       // all the corner cases. Doing this, for example, allows to keep track of
       // the name in the private index of unique names kept by the
       // PhoneDirectoryModel
       if (!m_RegisteredName.isEmpty()) {
          qWarning() << "A registered name is already set for this ContactMethod"
             << m_RegisteredName << registeredName;
          return;
       }
    
       m_RegisteredName = registeredName;
       registeredNameSet(registeredName);
       changed();
    }
    
    ///Allow phonedirectorymodel to change presence status
    void ContactMethod::setPresent(bool present)
    {
       if (d_ptr->m_Present != present) {
          d_ptr->m_Present = present;
          d_ptr->presentChanged(present);
       }
    }
    
    void ContactMethod::setPresenceMessage(const QString& message)
    {
       if (d_ptr->m_PresentMessage != message) {
          d_ptr->m_PresentMessage = message;
          d_ptr->presenceMessageChanged(message);
       }
    }
    
    ///Return the current type of the number
    ContactMethod::Type ContactMethod::type() const
    {
       return d_ptr->m_Type;
    }
    
    ///Return the number of calls from this number
    int ContactMethod::callCount() const
    {
       return d_ptr->m_UsageStats.totalCount();
    }
    
    uint ContactMethod::weekCount() const
    {
       return d_ptr->m_UsageStats.lastWeekCount();
    }
    
    uint ContactMethod::trimCount() const
    {
       return d_ptr->m_UsageStats.lastTrimCount();
    }
    
    bool ContactMethod::haveCalled() const
    {
       return d_ptr->m_UsageStats.haveCalled();
    }
    
    ///Best bet for this person real name
    QString ContactMethod::primaryName() const
    {
       //Compute the primary name
       if (d_ptr->m_PrimaryName_cache.isEmpty()) {
          QString ret;
          if (d_ptr->m_hNames.size() == 1)
             ret =  d_ptr->m_hNames.constBegin().key();
          else {
             QString toReturn = uri();
             QPair<int, time_t> max = {0, 0};
    
             for (QHash<QString,QPair<int, time_t>>::const_iterator i = d_ptr->m_hNames.begin(); i != d_ptr->m_hNames.end(); ++i) {
                 if (this->protocolHint() == URI::ProtocolHint::RING &&
                         i.value().second > max.second) {
                     max.second = i.value().second;
                     toReturn = i.key  ();
                 }
                 else if (i.value().first > max.first) {
                     max.first = i.value().first;
                     toReturn = i.key  ();
                 }
             }
             ret = toReturn;
          }
          const_cast<ContactMethod*>(this)->d_ptr->m_PrimaryName_cache = ret;
          const_cast<ContactMethod*>(this)->d_ptr->primaryNameChanged(d_ptr->m_PrimaryName_cache);
       }
       //Fallback: Use the URI
       if (d_ptr->m_PrimaryName_cache.isEmpty()) {
          return uri();
       }
    
       //Return the cached primaryname
       return d_ptr->m_PrimaryName_cache;
    }
    
    /// Returns the registered username otherwise returns an empty QString
    QString ContactMethod::registeredName() const
    {
       return d_ptr->m_RegisteredName.isEmpty()? QString() : d_ptr->m_RegisteredName;
    }
    
    /// Best bet for this contact identity
    /// returns the registered name if present, otherwise returns the uri
    QString ContactMethod::getBestId() const
    {
       return d_ptr->m_RegisteredName.isEmpty() ? d_ptr->m_Uri : d_ptr->m_RegisteredName;
    }
    
    ///Is this number bookmarked
    bool ContactMethod::isBookmarked() const
    {
       return d_ptr->m_IsBookmark;
    }
    
    ///If this number could (theoretically) support presence status
    bool ContactMethod::supportPresence() const
    {
       //Without an account, presence is impossible
       if (!d_ptr->m_pAccount)
          return false;
       //The account also have to support it
       if (!d_ptr->m_pAccount->supportPresenceSubscribe())
           return false;
    
       //In the end, it all come down to this, is the number tracked
       return true;
    }
    
    ///Proxy accessor to the category icon
    QVariant ContactMethod::icon() const
    {
       return category()->icon(isTracked(),isPresent());
    }
    
    ///The number of seconds spent with the URI (from history)
    int ContactMethod::totalSpentTime() const
    {
        return d_ptr->m_UsageStats.totalSeconds();
    }
    
    ///Return this number unique identifier (hash)
    QString ContactMethod::uid() const
    {
       return d_ptr->m_Uid.isEmpty()?toHash():d_ptr->m_Uid;
    }
    
    ///Return the URI protocol hint
    URI::ProtocolHint ContactMethod::protocolHint() const
    {
       return d_ptr->m_Uri.protocolHint();
    }
    
    ///Create a SHA1 hash identifying this contact method
    QByteArray ContactMethod::sha1() const
    {
       if (d_ptr->m_Sha1.isEmpty()) {
          QCryptographicHash hash(QCryptographicHash::Sha1);
          hash.addData(toHash().toLatin1());
    
          //Create a reproducible key for this file
          d_ptr->m_Sha1 = hash.result().toHex();
       }
       return d_ptr->m_Sha1;
    }
    
    ///Return all calls from this number
    QList<Call*> ContactMethod::calls() const
    {
       return d_ptr->m_lCalls;
    }
    
    ///Return the phonenumber position in the popularity index
    int ContactMethod::popularityIndex() const
    {
       return d_ptr->m_PopularityIndex;
    }
    
    QHash<QString,QPair<int, time_t>> ContactMethod::alternativeNames() const
    {
       return d_ptr->m_hNames;
    }
    
    QVariant ContactMethod::roleData(int role) const
    {
       QVariant cat;
    
       auto lastCall = d_ptr->m_lCalls.isEmpty() ? nullptr : d_ptr->m_lCalls.last();
    
       switch (role) {
          case static_cast<int>(Ring::Role::Name):
          case static_cast<int>(Call::Role::Name):
             /* the formatted name could be empty either because the name of the contact is actually
              * empty, or because the PersonPlaceHolder was never matched to a Person, in either case
              * we try to display the registered name. In the last resort we display the URI. */
             if (contact() and !contact()->formattedName().isEmpty())
                cat = contact()->formattedName();
             else {
                if (not d_ptr->m_RegisteredName.isEmpty())
                    cat = d_ptr->m_RegisteredName;
                else
                    cat = primaryName();
             }
             break;
          case Qt::ToolTipRole:
             cat = presenceMessage();
             break;
          case Qt::DisplayRole:
          case Qt::EditRole:
          case static_cast<int>(Role::Uri):
          case static_cast<int>(Ring::Role::Number):
          case static_cast<int>(Call::Role::Number):
             cat = uri();
             break;
          case Qt::DecorationRole:
             return GlobalInstances::pixmapManipulator().decorationRole(this);
          case static_cast<int>(Call::Role::Direction):
             cat = cat = !lastCall ? QVariant() : QVariant::fromValue(lastCall->direction());
             break;
          case static_cast<int>(Ring::Role::LastUsed):
          case static_cast<int>(Call::Role::Date):
             cat = d_ptr->m_UsageStats.lastUsed() <= 0 ? QVariant() : QDateTime::fromTime_t(d_ptr->m_UsageStats.lastUsed());
             break;
          case static_cast<int>(Ring::Role::Length):
          case static_cast<int>(Call::Role::Length):
             cat = cat = !lastCall ? QVariant() : lastCall->length();
             break;
          case static_cast<int>(Ring::Role::FormattedLastUsed):
          case static_cast<int>(Call::Role::FormattedDate):
          case static_cast<int>(Call::Role::FuzzyDate):
             cat = HistoryTimeCategoryModel::timeToHistoryCategory(d_ptr->m_UsageStats.lastUsed());
             break;
          case static_cast<int>(Ring::Role::IndexedLastUsed):
             return QVariant(static_cast<int>(HistoryTimeCategoryModel::timeToHistoryConst(d_ptr->m_UsageStats.lastUsed())));
          case static_cast<int>(Call::Role::HasAVRecording):
             cat = cat = !lastCall ? QVariant() : lastCall->isAVRecording();
             break;
          case static_cast<int>(Call::Role::ContactMethod):
          case static_cast<int>(Ring::Role::Object):
          case static_cast<int>(Role::Object):
             cat = QVariant::fromValue(const_cast<ContactMethod*>(this));
             break;
          case static_cast<int>(Ring::Role::ObjectType):
             cat = QVariant::fromValue(Ring::ObjectType::ContactMethod);
             break;
          case static_cast<int>(Call::Role::IsBookmark):
             cat = false;
             break;
          case static_cast<int>(Call::Role::Filter):
             cat = uri()+primaryName();
             break;
          case static_cast<int>(Ring::Role::IsPresent):
          case static_cast<int>(Call::Role::IsPresent):
             cat = isPresent();
             break;
          case static_cast<int>(Call::Role::Photo):
             if (contact())
                cat = contact()->photo();
             break;
          case static_cast<int>(Role::CategoryIcon):
             if (category())
                cat = d_ptr->m_pCategory->icon(isTracked(), isPresent());
             break;
          case static_cast<int>(Call::Role::LifeCycleState):
             return QVariant::fromValue(Call::LifeCycleState::FINISHED);
          case static_cast<int>(Ring::Role::UnreadTextMessageCount):
             if (auto rec = textRecording())
                cat = rec->unreadInstantTextMessagingModel()->rowCount();
             else
                cat = 0;
             break;
       }
       return cat;
    }
    
    QMimeData* ContactMethod::mimePayload() const
    {
       return RingMimes::payload(nullptr, this, nullptr);
    }
    
    ///Add a call to the call list, notify listener
    void ContactMethod::addCall(Call* call)
    {
       if (!call) return;
    
       d_ptr->m_Type = ContactMethod::Type::USED;
       d_ptr->m_lCalls << call;
    
       // call setLastUsed first so that we emit lastUsedChanged()
       auto time = call->startTimeStamp();
       setLastUsed(time);
    
       //Update the contact method statistics
       d_ptr->m_UsageStats.update(call->startTimeStamp(), call->stopTimeStamp());
       if (d_ptr->m_pAccount)
          d_ptr->m_pAccount->usageStats.update(call->startTimeStamp(), call->stopTimeStamp());
    
       if (call->direction() == Call::Direction::OUTGOING) {
          d_ptr->m_UsageStats.setHaveCalled();
          if (d_ptr->m_pAccount)
             d_ptr->m_pAccount->usageStats.setHaveCalled();
       }
    
       if (d_ptr->m_pAccount)
           d_ptr->m_pAccount->usageStats.setLastUsed(time);
    
       d_ptr->callAdded(call);
       d_ptr->changed();
    }
    
    ///Generate an unique representation of this number
    QString ContactMethod::toHash() const
    {
       QString uristr;
    
       switch(uri().protocolHint()) {
          case URI::ProtocolHint::RING         :
             //There is no point in keeping the full URI, a Ring hash is unique
             uristr = uri().userinfo();
             break;
          case URI::ProtocolHint::RING_USERNAME:
          case URI::ProtocolHint::SIP_OTHER:
          case URI::ProtocolHint::IP       :
          case URI::ProtocolHint::SIP_HOST :
             //Some URI have port number in them. They have to be stripped prior to the hash creation
             uristr = uri().format(
                URI::Section::CHEVRONS  |
                URI::Section::SCHEME    |
                URI::Section::USER_INFO |
                URI::Section::HOSTNAME
             );
             break;
       }
    
       return QString("%1///%2///%3")
          .arg(
             uristr
          )
          .arg(
             account()?account()->id():QString()
          )
          .arg(
             contact()?contact()->uid():QString()
          );
    }
    
    ///Increment name counter and update indexes
    void ContactMethod::incrementAlternativeName(const QString& name, const time_t lastUsed)
    {
       const bool needReIndexing = !d_ptr->m_hNames[name].first;
       if (d_ptr->m_hNames[name].second < lastUsed)
          d_ptr->m_hNames[name].second = lastUsed;
       d_ptr->m_hNames[name].first++;
       if (needReIndexing && d_ptr->m_Type != ContactMethod::Type::TEMPORARY) {
          PhoneDirectoryModel::instance().d_ptr->indexNumber(this,d_ptr->m_hNames.keys()+(d_ptr->m_pPerson?(QStringList(d_ptr->m_pPerson->formattedName())):QStringList()));
          //Invalid m_PrimaryName_cache
          if (!d_ptr->m_pPerson)
             d_ptr->m_PrimaryName_cache.clear();
       }
    
       emit changed();
    }
    
    void ContactMethod::accountDestroyed(QObject* o)
    {
       if (o == d_ptr->m_pAccount)
          d_ptr->m_pAccount = nullptr;
    }
    
    /**
     * When the ContactMethod contact is merged with another one, the phone number
     * data might be replaced, like the preferred name.
     */
    void ContactMethod::contactRebased(Person* other)
    {
       d_ptr->m_PrimaryName_cache = other->formattedName();
       d_ptr->primaryNameChanged(d_ptr->m_PrimaryName_cache);
       setPerson(other);
    
       d_ptr->changed();
       d_ptr->rebased(this);
    }
    
    /**
     * Merge two phone number to share the same data. This avoid having to change
     * pointers all over the place. The ContactMethod objects remain intact, the
     * PhoneDirectoryModel will replace the old references, but existing ones will
     * keep working.
     */
    bool ContactMethod::merge(ContactMethod* other)
    {
    
       if ((!other) || other == this || other->d_ptr == d_ptr)
          return false;
    
       //This is invalid, those are different numbers
       if (account() && other->account() && account() != other->account())
          return false;
    
       //TODO Check if the merge is valid
    
       //TODO Merge the alternative names
    
       //TODO Handle presence
    
       const QString oldName = primaryName();
    
       ContactMethodPrivate* currentD = d_ptr;
    
       //Replace the D-Pointer
       this->d_ptr= other->d_ptr;
       d_ptr->m_lParents << this;
    
       //In case the URI is different, take the longest and most precise
       //TODO keep a log of all URI used
       if (currentD->m_Uri.size() > other->d_ptr->m_Uri.size()) {
          other->d_ptr->m_lOtherURIs << other->d_ptr->m_Uri;
          other->d_ptr->m_Uri = currentD->m_Uri;
       }
       else
          other->d_ptr->m_lOtherURIs << currentD->m_Uri;
    
       emit changed();
       emit rebased(other);
    
       if (oldName != primaryName())
          d_ptr->primaryNameChanged(primaryName());
    
       currentD->m_lParents.removeAll(this);
       if (!currentD->m_lParents.size())
          delete currentD;
       return true;
    }
    
    bool ContactMethod::operator==(ContactMethod* other)
    {
       return other && this->d_ptr== other->d_ptr;
    }
    
    bool ContactMethod::operator==(const ContactMethod* other) const
    {
       return other && this->d_ptr== other->d_ptr;
    }
    
    bool ContactMethod::operator==(ContactMethod& other)
    {
       return this->d_ptr== other.d_ptr;
    }
    
    bool ContactMethod::operator==(const ContactMethod& other) const
    {
       return this->d_ptr== other.d_ptr;
    }
    
    Media::TextRecording* ContactMethod::textRecording() const
    {
        if (!d_ptr->m_pTextRecording) {
            d_ptr->m_pTextRecording = Media::RecordingModel::instance().createTextRecording(this);
        }
    
        return d_ptr->m_pTextRecording;
    }
    
    bool ContactMethod::isReachable() const
    {
       auto& m = AccountModel::instance();
    
       const bool hasSip   = m.isSipSupported  ();
       const bool hasIP2IP = m.isIP2IPSupported();
       const bool hasRing  = m.isRingSupported ();
    
       switch (protocolHint()) {
          case URI::ProtocolHint::SIP_HOST :
          case URI::ProtocolHint::IP       :
             if (hasIP2IP)
                return true;
             //no break
             [[clang::fallthrough]];
          case URI::ProtocolHint::SIP_OTHER:
             if (hasSip)
                return true;
             break;
          case URI::ProtocolHint::RING:
          case URI::ProtocolHint::RING_USERNAME:
             if (hasRing)
                return true;
             break;
       }
    
       return false;
    }
    
    Certificate* ContactMethod::certificate() const
    {
        if (!d_ptr->m_pCertificate && protocolHint() == URI::ProtocolHint::RING)
            d_ptr->m_pCertificate = CertificateModel::instance().getCertificateFromId(uri().userinfo(), account());
    
        if (d_ptr->m_pCertificate && !d_ptr->m_pCertificate->contactMethod())
            d_ptr->m_pCertificate->setContactMethod(const_cast<ContactMethod*>(this));
    
        return d_ptr->m_pCertificate;
    }
    
    void ContactMethodPrivate::setCertificate(Certificate* certificate)
    {
        m_pCertificate = certificate;
        if (!certificate->contactMethod())
          certificate->setContactMethod(q_ptr);
    }
    
    void ContactMethodPrivate::setTextRecording(Media::TextRecording* r)
    {
       m_pTextRecording = r;
    }
    
    bool ContactMethod::sendOfflineTextMessage(const QMap<QString,QString>& payloads)
    {
        auto selectedAccount = account() ? account() : AvailableAccountModel::currentDefaultAccount(this);
    
        if (!selectedAccount) {
            qDebug() << "No account available for this contactmethod!";
            return false;
        }
       auto txtRecording = textRecording();
       auto id = ConfigurationManager::instance().sendTextMessage(selectedAccount->id()
                                                        ,uri().format(URI::Section::SCHEME|URI::Section::USER_INFO|URI::Section::HOSTNAME)
                                                        ,payloads);
       txtRecording->d_ptr->insertNewMessage(payloads, this, Media::Media::Direction::OUT, id);
       return true;
    }
    
    
    /************************************************************************************
     *                                                                                  *
     *                             Temporary phone number                               *
     *                                                                                  *
     ***********************************************************************************/
    
    ///Constructor
    TemporaryContactMethod::TemporaryContactMethod(const ContactMethod* number) :
       ContactMethod(QString(),NumberCategoryModel::other(),ContactMethod::Type::TEMPORARY),d_ptr(nullptr)
    {
       if (number) {
          setPerson(number->contact());
          setAccount(number->account());
       }
    }
    
    void TemporaryContactMethod::setUri(const URI& uri)
    {
       ContactMethod::d_ptr->m_Uri = uri;
    
       //The sha1 is no longer valid
       ContactMethod::d_ptr->m_Sha1.clear();
       ContactMethod::d_ptr->changed();
    }
    
    QVariant TemporaryContactMethod::icon() const
    {
       return QVariant(); //TODO use the pixmapmanipulator to get a better icon
    }
    
    Q_DECLARE_METATYPE(QList<Call*>)