Skip to content
Snippets Groups Projects
Select Git revision
  • 2808fcb6479c32acc29a309fa0dc6cefca7052a9
  • 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
26 results

accountmodel.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    accountmodel.cpp 42.21 KiB
    /****************************************************************************
     *   Copyright (C) 2009-2016 by Savoir-faire Linux                          *
     *   Author : Jérémy Quentin <jeremy.quentin@savoirfairelinux.com>          *
     *            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/>.  *
     ***************************************************************************/
    //Parent
    #include "accountmodel.h"
    
    //Std
    #include <atomic>
    
    //Qt
    #include <QtCore/QObject>
    #include <QtCore/QCoreApplication>
    #include <QtCore/QItemSelectionModel>
    #include <QtCore/QMimeData>
    #include <QtCore/QDir>
    
    //Ring daemon
    #include <account_const.h>
    
    //Ring library
    #include "account.h"
    #include "mime.h"
    #include "profilemodel.h"
    #include "protocolmodel.h"
    #include "trustrequest.h"
    #include "pendingtrustrequestmodel.h"
    #include "private/account_p.h"
    #include "private/accountmodel_p.h"
    #include "accountstatusmodel.h"
    #include "dbus/configurationmanager.h"
    #include "dbus/callmanager.h"
    #include "dbus/instancemanager.h"
    #include "codecmodel.h"
    #include "private/pendingtrustrequestmodel_p.h"
    
    QHash<QByteArray,AccountPlaceHolder*> AccountModelPrivate::m_hsPlaceHolder;
    
    AccountModelPrivate::AccountModelPrivate(AccountModel* parent) : QObject(parent),q_ptr(parent),
    m_pIP2IP(nullptr),m_pProtocolModel(nullptr),m_pSelectionModel(nullptr),m_lMimes({RingMimes::ACCOUNT}),
    m_lSupportedProtocols {{
       /* SIP  */ false,
       /* IAX  */ false,
       /* RING */ false,
    }}
    {}
    
    ///Constructors
    AccountModel::AccountModel()
        : QAbstractListModel(QCoreApplication::instance())
        , d_ptr(new AccountModelPrivate(this))
    {}
    
    void AccountModelPrivate::init()
    {
        InstanceManager::instance(); // Make sure the daemon is running before calling updateAccounts()
        q_ptr->updateAccounts();
    
        CallManagerInterface& callManager = CallManager::instance();
        ConfigurationManagerInterface& configurationManager = ConfigurationManager::instance();
    
        connect(&configurationManager, &ConfigurationManagerInterface::registrationStateChanged,this ,
                &AccountModelPrivate::slotDaemonAccountChanged);
        connect(&configurationManager, SIGNAL(accountsChanged())                               ,q_ptr,
                SLOT(updateAccounts())                  );
        connect(&callManager         , SIGNAL(voiceMailNotify(QString,int))                    ,this ,
                SLOT(slotVoiceMailNotify(QString,int))  );
        connect(&configurationManager, SIGNAL(volatileAccountDetailsChanged(QString,MapStringString)),this,
                SLOT(slotVolatileAccountDetailsChange(QString,MapStringString)));
        connect(&configurationManager, SIGNAL(mediaParametersChanged(QString))                 ,this ,
                SLOT(slotMediaParametersChanged(QString)));
        connect(&configurationManager, &ConfigurationManagerInterface::incomingTrustRequest, this,
                &AccountModelPrivate::slotIncomingTrustRequest);
    }
    
    ///Destructor
    AccountModel::~AccountModel()
    {
       while(d_ptr->m_lAccounts.size()) {
          Account* a = d_ptr->m_lAccounts[0];
          d_ptr->m_lAccounts.remove(0);
          delete a;
       }
       for(Account* a : d_ptr->m_pRemovedAccounts) {
          delete a;
       }
       delete d_ptr;
    }
    
    #define CAST(item) static_cast<int>(item)
    QHash<int,QByteArray> AccountModel::roleNames() const
    {
       static QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
       static bool initRoles = false;
       if (!initRoles) {
          initRoles = true;
          roles.insert(CAST(Account::Role::Alias                       ) ,QByteArray("alias"                         ));
          roles.insert(CAST(Account::Role::Proto                       ) ,QByteArray("protocol"                      ));
          roles.insert(CAST(Account::Role::Hostname                    ) ,QByteArray("hostname"                      ));
          roles.insert(CAST(Account::Role::Username                    ) ,QByteArray("username"                      ));
          roles.insert(CAST(Account::Role::Mailbox                     ) ,QByteArray("mailbox"                       ));
          roles.insert(CAST(Account::Role::Proxy                       ) ,QByteArray("proxy"                         ));
          roles.insert(CAST(Account::Role::TlsPassword                 ) ,QByteArray("tlsPassword"                   ));
          roles.insert(CAST(Account::Role::TlsCaListCertificate        ) ,QByteArray("tlsCaListCertificate"          ));
          roles.insert(CAST(Account::Role::TlsCertificate              ) ,QByteArray("tlsCertificate"                ));
          roles.insert(CAST(Account::Role::TlsServerName               ) ,QByteArray("tlsServerName"                 ));
          roles.insert(CAST(Account::Role::SipStunServer               ) ,QByteArray("sipStunServer"                 ));
          roles.insert(CAST(Account::Role::PublishedAddress            ) ,QByteArray("publishedAddress"              ));
          roles.insert(CAST(Account::Role::RingtonePath                ) ,QByteArray("ringtonePath"                  ));
          roles.insert(CAST(Account::Role::RegistrationExpire          ) ,QByteArray("registrationExpire"            ));
          roles.insert(CAST(Account::Role::TlsNegotiationTimeoutSec    ) ,QByteArray("tlsNegotiationTimeoutSec"      ));
          roles.insert(CAST(Account::Role::TlsNegotiationTimeoutMsec   ) ,QByteArray("tlsNegotiationTimeoutMsec"     ));
          roles.insert(CAST(Account::Role::LocalPort                   ) ,QByteArray("localPort"                     ));
          roles.insert(CAST(Account::Role::BootstrapPort               ) ,QByteArray("bootstrapPort"                 ));
          roles.insert(CAST(Account::Role::PublishedPort               ) ,QByteArray("publishedPort"                 ));
          roles.insert(CAST(Account::Role::Enabled                     ) ,QByteArray("enabled"                       ));
          roles.insert(CAST(Account::Role::AutoAnswer                  ) ,QByteArray("autoAnswer"                    ));
          roles.insert(CAST(Account::Role::TlsVerifyServer             ) ,QByteArray("tlsVerifyServer"               ));
          roles.insert(CAST(Account::Role::TlsVerifyClient             ) ,QByteArray("tlsVerifyClient"               ));
          roles.insert(CAST(Account::Role::TlsRequireClientCertificate ) ,QByteArray("tlsRequireClientCertificate"   ));
          roles.insert(CAST(Account::Role::TlsEnabled                  ) ,QByteArray("tlsEnabled"                    ));
          roles.insert(CAST(Account::Role::DisplaySasOnce              ) ,QByteArray("displaySasOnce"                ));
          roles.insert(CAST(Account::Role::SrtpRtpFallback             ) ,QByteArray("srtpRtpFallback"               ));
          roles.insert(CAST(Account::Role::ZrtpDisplaySas              ) ,QByteArray("zrtpDisplaySas"                ));
          roles.insert(CAST(Account::Role::ZrtpNotSuppWarning          ) ,QByteArray("zrtpNotSuppWarning"            ));
          roles.insert(CAST(Account::Role::ZrtpHelloHash               ) ,QByteArray("zrtpHelloHash"                 ));
          roles.insert(CAST(Account::Role::SipStunEnabled              ) ,QByteArray("sipStunEnabled"                ));
          roles.insert(CAST(Account::Role::PublishedSameAsLocal        ) ,QByteArray("publishedSameAsLocal"          ));
          roles.insert(CAST(Account::Role::RingtoneEnabled             ) ,QByteArray("ringtoneEnabled"               ));
          roles.insert(CAST(Account::Role::dTMFType                    ) ,QByteArray("dTMFType"                      ));
          roles.insert(CAST(Account::Role::Id                          ) ,QByteArray("id"                            ));
          roles.insert(CAST(Account::Role::Object                      ) ,QByteArray("object"                        ));
          roles.insert(CAST(Account::Role::TypeName                    ) ,QByteArray("typeName"                      ));
          roles.insert(CAST(Account::Role::PresenceStatus              ) ,QByteArray("presenceStatus"                ));
          roles.insert(CAST(Account::Role::PresenceMessage             ) ,QByteArray("presenceMessage"               ));
          roles.insert(CAST(Account::Role::UsedForOutgogingCall        ) ,QByteArray("usedForOutgogingCall"          ));
          roles.insert(CAST(Account::Role::TotalCallCount              ) ,QByteArray("totalCallCount"                ));
          roles.insert(CAST(Account::Role::WeekCallCount               ) ,QByteArray("weekCallCount"                 ));
          roles.insert(CAST(Account::Role::TrimesterCallCount          ) ,QByteArray("trimesterCallCount"            ));
          roles.insert(CAST(Account::Role::LastUsed                    ) ,QByteArray("lastUsed"                      ));
          roles.insert(CAST(Account::Role::UserAgent                   ) ,QByteArray("userAgent"                     ));
          roles.insert(CAST(Account::Role::Password                    ) ,QByteArray("password"                      ));
          roles.insert(CAST(Account::Role::SupportPresencePublish      ) ,QByteArray("supportPresencePublish"        ));
          roles.insert(CAST(Account::Role::SupportPresenceSubscribe    ) ,QByteArray("supportPresenceSubscribe"      ));
          roles.insert(CAST(Account::Role::PresenceEnabled             ) ,QByteArray("presenceEnabled"               ));
          roles.insert(CAST(Account::Role::IsVideoEnabled              ) ,QByteArray("isVideoEnabled"                ));
          roles.insert(CAST(Account::Role::VideoPortMax                ) ,QByteArray("videoPortMax"                  ));
          roles.insert(CAST(Account::Role::VideoPortMin                ) ,QByteArray("videoPortMin"                  ));
          roles.insert(CAST(Account::Role::AudioPortMin                ) ,QByteArray("audioPortMin"                  ));
          roles.insert(CAST(Account::Role::AudioPortMax                ) ,QByteArray("audioPortMax"                  ));
          roles.insert(CAST(Account::Role::IsUpnpEnabled               ) ,QByteArray("upnpEnabled"                   ));
          roles.insert(CAST(Account::Role::HasCustomUserAgent          ) ,QByteArray("hasCustomUserAgent"            ));
          roles.insert(CAST(Account::Role::LastTransportErrorCode      ) ,QByteArray("lastTransportErrorCode"        ));
          roles.insert(CAST(Account::Role::LastTransportErrorMessage   ) ,QByteArray("lastTransportErrorMessage"     ));
          roles.insert(CAST(Account::Role::UserAgent                   ) ,QByteArray("userAgent"                     ));
          roles.insert(CAST(Account::Role::UseDefaultPort              ) ,QByteArray("useDefaultPort"                ));
          roles.insert(CAST(Account::Role::TurnServer                  ) ,QByteArray("turnServer"                    ));
          roles.insert(CAST(Account::Role::HasProxy                    ) ,QByteArray("hasProxy"                      ));
          roles.insert(CAST(Account::Role::DisplayName                 ) ,QByteArray("displayName"                   ));
          roles.insert(CAST(Account::Role::SrtpEnabled                 ) ,QByteArray("srtpEnabled"                   ));
          roles.insert(CAST(Account::Role::HasCustomBootstrap          ) ,QByteArray("hasCustomBootstrap"            ));
          roles.insert(CAST(Account::Role::CredentialModel             ) ,QByteArray("credentialModel"               ));
          roles.insert(CAST(Account::Role::CodecModel                  ) ,QByteArray("codecModel"                    ));
          roles.insert(CAST(Account::Role::KeyExchangeModel            ) ,QByteArray("keyExchangeModel"              ));
          roles.insert(CAST(Account::Role::CipherModel                 ) ,QByteArray("cipherModel"                   ));
          roles.insert(CAST(Account::Role::StatusModel                 ) ,QByteArray("statusModel"                   ));
          roles.insert(CAST(Account::Role::SecurityEvaluationModel     ) ,QByteArray("securityEvaluationModel"       ));
          roles.insert(CAST(Account::Role::TlsMethodModel              ) ,QByteArray("tlsMethodModel"                ));
          roles.insert(CAST(Account::Role::ProtocolModel               ) ,QByteArray("protocolModel"                 ));
          roles.insert(CAST(Account::Role::BootstrapModel              ) ,QByteArray("bootstrapModel"                ));
          roles.insert(CAST(Account::Role::NetworkInterfaceModel       ) ,QByteArray("networkInterfaceModel"         ));
          roles.insert(CAST(Account::Role::KnownCertificateModel       ) ,QByteArray("knownCertificateModel"         ));
          roles.insert(CAST(Account::Role::BannedCertificatesModel     ) ,QByteArray("bannedCertificatesModel"       ));
          roles.insert(CAST(Account::Role::AllowedCertificatesModel    ) ,QByteArray("allowedCertificatesModel"      ));
          roles.insert(CAST(Account::Role::AllowIncomingFromHistory    ) ,QByteArray("allowIncomingFromHistory"      ));
          roles.insert(CAST(Account::Role::AllowIncomingFromContact    ) ,QByteArray("allowIncomingFromContact"      ));
          roles.insert(CAST(Account::Role::AllowIncomingFromUnknown    ) ,QByteArray("allowIncomingFromUnknown"      ));
          roles.insert(CAST(Account::Role::ActiveCallLimit             ) ,QByteArray("activeCallLimit"               ));
          roles.insert(CAST(Account::Role::HasActiveCallLimit          ) ,QByteArray("hasActiveCallLimit"            ));
          roles.insert(CAST(Account::Role::SecurityLevel               ) ,QByteArray("securityLevel"                 ));
          roles.insert(CAST(Account::Role::SecurityLevelIcon           ) ,QByteArray("securityLevelIcon"             ));
          roles.insert(CAST(Account::Role::TurnServerUsername          ) ,QByteArray("turnServerUsername"            ));
          roles.insert(CAST(Account::Role::TurnServerPassword          ) ,QByteArray("turnServerPassword"            ));
          roles.insert(CAST(Account::Role::TurnServerRealm             ) ,QByteArray("turnServerRealm"               ));
          roles.insert(CAST(Account::Role::TurnServerEnabled           ) ,QByteArray("turnEnabled"                   ));
          roles.insert(CAST(Account::Role::TlsPrivateKey               ) ,QByteArray("tlsPrivateKey"                 ));
    
       }
       return roles;
    }
    #undef CAST
    
    ///Get the IP2IP account
    Account* AccountModel::ip2ip() const
    {
       if (!d_ptr->m_pIP2IP) {
          foreach(Account* a, d_ptr->m_lAccounts) {
             if (a->id() == DRing::Account::ProtocolNames::IP2IP)
                d_ptr->m_pIP2IP = a;
          }
       }
       return d_ptr->m_pIP2IP;
    }
    
    ///Singleton
    AccountModel& AccountModel::instance()
    {
        static auto instance = new AccountModel;
    
        // Upload account configuration only once in re-entrant way
        static std::atomic_flag init_flag {ATOMIC_FLAG_INIT};
        if (not init_flag.test_and_set())
            instance->d_ptr->init();
    
        return *instance;
    }
    
    QItemSelectionModel* AccountModel::selectionModel() const
    {
       if (!d_ptr->m_pSelectionModel)
          d_ptr->m_pSelectionModel = new QItemSelectionModel(const_cast<AccountModel*>(this));
    
       return d_ptr->m_pSelectionModel;
    }
    
    /**
     * The client have a different point of view when it come to the account
     * state. All the different errors are also handled elsewhere
     */
    Account::RegistrationState AccountModelPrivate::fromDaemonName(const QString& st)
    {
       if     ( st == DRing::Account::States::REGISTERED
            ||  st == DRing::Account::States::READY                    )
          return Account::RegistrationState::READY;
    
       else if( st == DRing::Account::States::UNREGISTERED             )
          return Account::RegistrationState::UNREGISTERED;
    
       else if( st == DRing::Account::States::TRYING                   )
          return Account::RegistrationState::TRYING;
    
       else if( st == DRing::Account::States::ERROR
            ||  st == DRing::Account::States::ERROR_GENERIC
            ||  st == DRing::Account::States::ERROR_AUTH
            ||  st == DRing::Account::States::ERROR_NETWORK
            ||  st == DRing::Account::States::ERROR_HOST
            ||  st == DRing::Account::States::ERROR_CONF_STUN
            ||  st == DRing::Account::States::ERROR_EXIST_STUN
            ||  st == DRing::Account::States::ERROR_SERVICE_UNAVAILABLE
            ||  st == DRing::Account::States::ERROR_NOT_ACCEPTABLE
            ||  st == DRing::Account::States::REQUEST_TIMEOUT          )
          return Account::RegistrationState::ERROR;
    
       else {
          qWarning() << "Unknown registration state" << st;
          return Account::RegistrationState::ERROR;
       }
    
    }
    
    ///Account status changed
    void AccountModelPrivate::slotDaemonAccountChanged(const QString& account, const QString& registration_state, unsigned code, const QString& status)
    {
       Q_UNUSED(registration_state);
       Account* a = q_ptr->getById(account.toLatin1());
    
       //TODO move this to AccountStatusModel
       if (!a || (a && a->d_ptr->m_LastSipRegistrationStatus != status )) {
          if (status != "OK") //Do not pollute the log
             qDebug() << "Account" << account << "status changed to" << status;
       }
    
       if (a)
          a->d_ptr->m_LastSipRegistrationStatus = status;
    
       ConfigurationManagerInterface& configurationManager = ConfigurationManager::instance();
    
       //The account may have been deleted by the user, but 'apply' have not been pressed
       if (!a) {
          const QStringList accountIds = configurationManager.getAccountList();
          for (int i = 0; i < accountIds.size(); ++i) {
             if ((!q_ptr->getById(accountIds[i].toLatin1())) && m_lDeletedAccounts.indexOf(accountIds[i]) == -1) {
                Account* acc = AccountPrivate::buildExistingAccountFromId(accountIds[i].toLatin1());
                insertAccount(acc,i);
                connect(acc, &Account::changed                , this, &AccountModelPrivate::slotAccountChanged                );
                connect(acc, &Account::presenceEnabledChanged , this, &AccountModelPrivate::slotAccountPresenceEnabledChanged );
                connect(acc, &Account::enabled                , this, &AccountModelPrivate::slotSupportedProtocolsChanged     );
                emit q_ptr->dataChanged(q_ptr->index(i,0),q_ptr->index(q_ptr->size()-1));
                emit q_ptr->layoutChanged();
    
                if (acc->id() != DRing::Account::ProtocolNames::IP2IP)
                   enableProtocol(acc->protocol());
    
             }
          }
          foreach (Account* acc, m_lAccounts) {
             const int idx =accountIds.indexOf(acc->id());
             if (idx == -1 && (acc->editState() == Account::EditState::READY || acc->editState() == Account::EditState::REMOVED)) {
                m_lAccounts.remove(idx);
                emit q_ptr->dataChanged(q_ptr->index(idx - 1, 0), q_ptr->index(m_lAccounts.size()-1, 0));
                emit q_ptr->layoutChanged();
             }
          }
       }
       else {
          const bool isRegistered = a->registrationState() == Account::RegistrationState::READY;
          a->d_ptr->updateState();
          const QModelIndex idx = a->index();
          emit q_ptr->dataChanged(idx, idx);
          const bool regStateChanged = isRegistered != (a->registrationState() == Account::RegistrationState::READY);
    
          //Handle some important events directly
          if (regStateChanged && (code == 502 || code == 503)) {
             emit q_ptr->badGateway();
          }
          else if (regStateChanged)
             emit q_ptr->registrationChanged(a,a->registrationState() == Account::RegistrationState::READY);
    
          //Send the messages to AccountStatusModel for processing
          a->statusModel()->addSipRegistrationEvent(status,code);
    
          //Make sure volatile details get reloaded
          //TODO eventually remove this call and trust the signal
          slotVolatileAccountDetailsChange(account,configurationManager.getVolatileAccountDetails(account));
    
          emit q_ptr->accountStateChanged(a,a->registrationState());
       }
    
    }
    
    void AccountModelPrivate::slotSupportedProtocolsChanged()
    {
        emit q_ptr->supportedProtocolsChanged();
    }
    
    ///Tell the model something changed
    void AccountModelPrivate::slotAccountChanged(Account* a)
    {
       int idx = m_lAccounts.indexOf(a);
       if (idx != -1) {
          emit q_ptr->dataChanged(q_ptr->index(idx, 0), q_ptr->index(idx, 0));
       }
    }
    
    
    /*****************************************************************************
     *                                                                           *
     *                                  Mutator                                  *
     *                                                                           *
     ****************************************************************************/
    
    
    ///When a new voice mail is available
    void AccountModelPrivate::slotVoiceMailNotify(const QString &accountID, int count)
    {
       Account* a = q_ptr->getById(accountID.toLatin1());
       if (a) {
          a->setVoiceMailCount(count);
          emit q_ptr->voiceMailNotify(a,count);
       }
    }
    
    ///Propagate account presence state
    void AccountModelPrivate::slotAccountPresenceEnabledChanged(bool state)
    {
       Q_UNUSED(state)
       emit q_ptr->presenceEnabledChanged(q_ptr->isPresenceEnabled());
    }
    
    ///Emitted when some runtime details changes
    void AccountModelPrivate::slotVolatileAccountDetailsChange(const QString& accountId, const MapStringString& details)
    {
       Account* a = q_ptr->getById(accountId.toLatin1());
       if (a) {
          const int     transportCode = details[DRing::Account::VolatileProperties::Transport::STATE_CODE].toInt();
          const QString transportDesc = details[DRing::Account::VolatileProperties::Transport::STATE_DESC];
          const QString status        = details[DRing::Account::VolatileProperties::Registration::STATUS];
    
          a->statusModel()->addTransportEvent(transportDesc,transportCode);
    
          a->d_ptr->m_LastTransportCode    = transportCode;
          a->d_ptr->m_LastTransportMessage = transportDesc;
    
          const Account::RegistrationState state = fromDaemonName(a->d_ptr->accountDetail(DRing::Account::ConfProperties::Registration::STATUS));
          a->d_ptr->m_RegistrationState = state;
       }
    }
    
    ///When a Ring-DHT trust request arrive
    void AccountModelPrivate::slotIncomingTrustRequest(const QString& accountId, const QString& hash, const QByteArray& payload, time_t time)
    {
       Q_UNUSED(payload);
       qDebug() << "INCOMING REQUEST" << accountId << hash << time;
       Account* a = q_ptr->getById(accountId.toLatin1());
    
       if (!a) {
          qWarning() << "Incoming trust request for unknown account" << accountId;
          return;
       }
    
       TrustRequest* r = new TrustRequest(a, hash, time);
       a->pendingTrustRequestModel()->d_ptr->addRequest(r);
    }
    
    ///Update accounts
    void AccountModel::update()
    {
       ConfigurationManagerInterface & configurationManager = ConfigurationManager::instance();
       QList<Account*> tmp;
       for (int i = 0; i < d_ptr->m_lAccounts.size(); i++)
          tmp << d_ptr->m_lAccounts[i];
    
       for (int i = 0; i < tmp.size(); i++) {
          Account* current = tmp[i];
          if (!current->isNew() && (current->editState() != Account::EditState::NEW
             && current->editState() != Account::EditState::MODIFIED_COMPLETE
             && current->editState() != Account::EditState::MODIFIED_INCOMPLETE
             && current->editState() != Account::EditState::OUTDATED))
             remove(current);
       }
       //ask for the list of accounts ids to the configurationManager
       const QStringList accountIds = configurationManager.getAccountList();
       for (int i = 0; i < accountIds.size(); ++i) {
          if (d_ptr->m_lDeletedAccounts.indexOf(accountIds[i]) == -1) {
             Account* a = AccountPrivate::buildExistingAccountFromId(accountIds[i].toLatin1());
             d_ptr->insertAccount(a,i);
             emit dataChanged(index(i,0),index(size()-1,0));
             connect(a,SIGNAL(changed(Account*)),d_ptr,SLOT(slotAccountChanged(Account*)));
             //connect(a,SIGNAL(propertyChanged(Account*,QString,QString,QString)),d_ptr,SLOT(slotAccountChanged(Account*)));
             connect(a,SIGNAL(presenceEnabledChanged(bool)),d_ptr,SLOT(slotAccountPresenceEnabledChanged(bool)));
             emit layoutChanged();
    
             if (a->id() != DRing::Account::ProtocolNames::IP2IP)
                d_ptr->enableProtocol(a->protocol());
          }
       }
    } //update
    
    ///Update accounts
    void AccountModel::updateAccounts()
    {
       qDebug() << "Updating all accounts";
       ConfigurationManagerInterface& configurationManager = ConfigurationManager::instance();
       QStringList accountIds = configurationManager.getAccountList();
       //m_lAccounts.clear();
       for (int i = 0; i < accountIds.size(); ++i) {
          Account* acc = getById(accountIds[i].toLatin1());
          if (!acc) {
             Account* a = AccountPrivate::buildExistingAccountFromId(accountIds[i].toLatin1());
             d_ptr->insertAccount(a,d_ptr->m_lAccounts.size());
             connect(a,SIGNAL(changed(Account*)),d_ptr,SLOT(slotAccountChanged(Account*)));
             //connect(a,SIGNAL(propertyChanged(Account*,QString,QString,QString)),d_ptr,SLOT(slotAccountChanged(Account*)));
             connect(a,SIGNAL(presenceEnabledChanged(bool)),d_ptr,SLOT(slotAccountPresenceEnabledChanged(bool)));
             emit dataChanged(index(size()-1,0),index(size()-1,0));
    
             if (a->id() != DRing::Account::ProtocolNames::IP2IP)
                d_ptr->enableProtocol(a->protocol());
    
             emit accountAdded(a);
          }
          else {
             acc->performAction(Account::EditAction::RELOAD);
          }
       }
       emit accountListUpdated();
    } //updateAccounts
    
    ///Save accounts details and reload it
    void AccountModel::save()
    {
       ConfigurationManagerInterface& configurationManager = ConfigurationManager::instance();
       const QStringList accountIds = QStringList(configurationManager.getAccountList());
    
       //create or update each account from accountList
       for (int i = 0; i < size(); i++) {
          Account* current = (*this)[i];
          current->performAction(Account::EditAction::SAVE);
       }
    
       //remove accounts that are in the configurationManager but not in the client
       for (int i = 0; i < accountIds.size(); i++) {
          if(!getById(accountIds[i].toLatin1())) {
             configurationManager.removeAccount(accountIds[i]);
          }
       }
    
       //Set account order
       QString order;
       for( int i = 0 ; i < size() ; i++)
          order += d_ptr->m_lAccounts[i]->id() + '/';
       configurationManager.setAccountsOrder(order);
       d_ptr->m_lDeletedAccounts.clear();
    }
    
    ///Move account up
    bool AccountModel::moveUp()
    {
       if (d_ptr->m_pSelectionModel) {
          const QModelIndex& idx = d_ptr->m_pSelectionModel->currentIndex();
    
          if (!idx.isValid())
             return false;
    
          if (dropMimeData(mimeData({idx}), Qt::MoveAction, idx.row()-1, idx.column(),idx.parent())) {
             return true;
          }
       }
       return false;
    }
    
    ///Move account down
    bool AccountModel::moveDown()
    {
       if (d_ptr->m_pSelectionModel) {
          const QModelIndex& idx = d_ptr->m_pSelectionModel->currentIndex();
    
          if (!idx.isValid())
             return false;
    
          if (dropMimeData(mimeData({idx}), Qt::MoveAction, idx.row()+1, idx.column(),idx.parent())) {
             return true;
          }
       }
       return false;
    }
    
    ///Try to register all enabled accounts
    void AccountModel::registerAllAccounts()
    {
       ConfigurationManagerInterface& configurationManager = ConfigurationManager::instance();
       configurationManager.registerAllAccounts();
    }
    
    ///Cancel all modifications
    void AccountModel::cancel() {
       foreach (Account* a, d_ptr->m_lAccounts) {
          // Account::EditState::NEW is only for new and unmodified accounts
          if (a->isNew())
             remove(a);
          else {
             switch(a->editState()) {
                case Account::EditState::NEW                :
                   remove(a);
                case Account::EditState::MODIFIED_INCOMPLETE:
                case Account::EditState::MODIFIED_COMPLETE  :
                   a << Account::EditAction::CANCEL;
                   break;
                case Account::EditState::OUTDATED           :
                   a << Account::EditAction::RELOAD;
                   break;
                case Account::EditState::READY              :
                case Account::EditState::REMOVED            :
                case Account::EditState::EDITING            :
                case Account::EditState::COUNT__            :
                   break;
             }
          }
       }
       d_ptr->m_lDeletedAccounts.clear();
    }
    
    
    void AccountModelPrivate::enableProtocol(Account::Protocol proto)
    {
       const bool cache = m_lSupportedProtocols[proto];
    
       //Set the supported protocol bits, for now, this intentionally ignore account states
       m_lSupportedProtocols.setAt(proto, true);
    
       if (!cache) {
          emit q_ptr->supportedProtocolsChanged();
       }
    }
    
    AccountModel::EditState AccountModelPrivate::convertAccountEditState(const Account::EditState s)
    {
       AccountModel::EditState ams = AccountModel::EditState::INVALID;
    
       switch (s) {
          case Account::EditState::READY              :
          case Account::EditState::OUTDATED           :
          case Account::EditState::EDITING            :
          case Account::EditState::COUNT__            :
             ams = AccountModel::EditState::SAVED;
             break;
          case Account::EditState::MODIFIED_INCOMPLETE:
             ams = AccountModel::EditState::INVALID;
             break;
          case Account::EditState::NEW                :
          case Account::EditState::REMOVED            :
          case Account::EditState::MODIFIED_COMPLETE  :
             ams = AccountModel::EditState::UNSAVED;
             break;
       }
    
       return ams;
    }
    
    ///Check if the AccountModel need/can be saved as a whole
    AccountModel::EditState AccountModel::editState() const
    {
       typedef AccountModel::EditState  ES ;
       typedef const Account::EditState AES;
    
       static ES s_CurrentState = ES::INVALID;
    
       //This class is a singleton, so using static variables is ok
       static Matrix1D<ES,int> estates = {
          { ES::SAVED   , 0},
          { ES::UNSAVED , 0},
          { ES::INVALID , 0},
       };
    
       auto genState = [this]( const Account* a, AES s, AES p ) {
          Q_UNUSED(a)
    
          const ES newState = d_ptr->convertAccountEditState(s);
          const ES oldState = d_ptr->convertAccountEditState(p);
    
          if (newState != oldState)
             estates.setAt(oldState,estates[oldState]-1);
    
          estates.setAt(newState,estates[newState]+1);
    
          const ES oldGlobalState = s_CurrentState;
    
          s_CurrentState = estates[ES::INVALID] ? ES::INVALID:
                           estates[ES::UNSAVED] ? ES::UNSAVED:
                                                  ES::SAVED  ;
    
          if (s_CurrentState != oldGlobalState)
             emit editStateChanged(s_CurrentState, oldGlobalState);
    
       };
    
       static bool isInit = false;
       if (!isInit) {
          isInit = true;
    
          for (const Account* a : d_ptr->m_lAccounts) {
             genState(a,a->editState(),a->editState());
          }
    
          connect(this, &AccountModel::accountEditStateChanged, genState);
       }
    
    
       return s_CurrentState;
    }
    
    ///Called when codec bitrate changes
    void AccountModelPrivate::slotMediaParametersChanged(const QString& accountId)
    {
       Account* a = q_ptr->getById(accountId.toLatin1());
       if (a) {
          if (auto codecModel = a->codecModel()) {
             qDebug() << "reloading codecs";
             codecModel << CodecModel::EditAction::RELOAD;
          }
       }
    }
    
    /*****************************************************************************
     *                                                                           *
     *                                  Getters                                  *
     *                                                                           *
     ****************************************************************************/
    
    /**
     * Get an account by its ID
     *
     * @note This method have O(N) complexity, but the average account count is low
     *
     * @param id The account identifier
     * @param usePlaceHolder Return a placeholder for a future account instead of nullptr
     * @return an account if it exist, a placeholder if usePlaceHolder==true or nullptr
     */
    Account* AccountModel::getById(const QByteArray& id, bool usePlaceHolder) const
    {
       if (id.isEmpty())
           return nullptr;
       //This function use a loop as the expected size is < 5
       for (int i = 0; i < d_ptr->m_lAccounts.size(); i++) {
          Account* acc = d_ptr->m_lAccounts[i];
          if (acc && !acc->isNew() && acc->id() == id)
             return acc;
       }
    
       //The account doesn't exist (yet)
       if (usePlaceHolder) {
          AccountPlaceHolder* ph =  d_ptr->m_hsPlaceHolder[id];
          if (!ph) {
             ph = new AccountPlaceHolder(id);
             d_ptr->m_hsPlaceHolder[id] = ph;
          }
          return ph;
       }
    
       return nullptr;
    }
    
    ///Get the account size
    int AccountModel::size() const
    {
       return d_ptr->m_lAccounts.size();
    }
    
    ///Get data from the model
    QVariant AccountModel::data ( const QModelIndex& idx, int role) const
    {
       if (!idx.isValid() || idx.row() < 0 || idx.row() >= rowCount())
          return QVariant();
    
       return d_ptr->m_lAccounts[idx.row()]->roleData(role);
    } //data
    
    ///Flags for "idx"
    Qt::ItemFlags AccountModel::flags(const QModelIndex& idx) const
    {
       if (idx.column() == 0)
          return QAbstractItemModel::flags(idx) | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
       return QAbstractItemModel::flags(idx);
    }
    
    ///Number of account
    int AccountModel::rowCount(const QModelIndex& parentIdx) const
    {
       return parentIdx.isValid() ? 0 : d_ptr->m_lAccounts.size();
    }
    
    Account* AccountModel::getAccountByModelIndex(const QModelIndex& item) const
    {
       if (!item.isValid())
          return nullptr;
       return d_ptr->m_lAccounts[item.row()];
    }
    
    ///Generate an unique suffix to prevent multiple account from sharing alias
    QString AccountModel::getSimilarAliasIndex(const QString& alias)
    {
        auto& self = instance();
    
        int count = 0;
        foreach (Account* a, self.d_ptr->m_lAccounts) {
            if (a->alias().left(alias.size()) == alias)
                count++;
        }
        bool found = true;
        do {
            found = false;
            foreach (Account* a, self.d_ptr->m_lAccounts) {
                if (a->alias() == alias+QString(" (%1)").arg(count)) {
                    count++;
                    found = false;
                    break;
                }
            }
        } while(found);
        if (count)
            return QString(" (%1)").arg(count);
        return QString();
    }
    
    QList<Account*> AccountModel::getAccountsByProtocol( const Account::Protocol protocol ) const
    {
       switch(protocol) {
          case Account::Protocol::SIP:
             return d_ptr->m_lSipAccounts;
          case Account::Protocol::IAX:
             return d_ptr->m_lIAXAccounts;
          case Account::Protocol::RING:
             return d_ptr->m_lRingAccounts;
          case Account::Protocol::COUNT__:
             break;
       }
    
       return {};
    }
    
    bool AccountModel::isPresenceEnabled() const
    {
       foreach(Account* a, d_ptr->m_lAccounts) {
          if (a->presenceEnabled())
             return true;
       }
       return false;
    }
    
    bool AccountModel::isPresencePublishSupported() const
    {
       foreach(Account* a,d_ptr->m_lAccounts) {
          if (a->supportPresencePublish())
             return true;
       }
       return false;
    }
    
    bool AccountModel::isPresenceSubscribeSupported() const
    {
       foreach(Account* a,d_ptr->m_lAccounts) {
          if (a->supportPresenceSubscribe())
             return true;
       }
       return false;
    }
    
    bool AccountModel::isSipSupported() const
    {
       return d_ptr->m_lSupportedProtocols[Account::Protocol::SIP];
    }
    
    bool AccountModel::isIAXSupported() const
    {
       return d_ptr->m_lSupportedProtocols[Account::Protocol::IAX];
    }
    
    bool AccountModel::isIP2IPSupported() const
    {
        if (auto a = ip2ip())
            return a->isEnabled();
        return false;
    }
    
    bool AccountModel::isRingSupported() const
    {
       return d_ptr->m_lSupportedProtocols[Account::Protocol::RING];
    }
    
    
    ProtocolModel* AccountModel::protocolModel() const
    {
       if (!d_ptr->m_pProtocolModel)
          d_ptr->m_pProtocolModel = new ProtocolModel();
       return d_ptr->m_pProtocolModel;
    }
    
    
    /*****************************************************************************
     *                                                                           *
     *                                  Setters                                  *
     *                                                                           *
     ****************************************************************************/
    
    ///Have a single place where m_lAccounts receive inserts
    void AccountModelPrivate::insertAccount(Account* a, int idx)
    {
       q_ptr->beginInsertRows(QModelIndex(), idx, idx);
       m_lAccounts.insert(idx,a);
       q_ptr->endInsertRows();
    
       connect(a,&Account::editStateChanged, [a,this](const Account::EditState state, const Account::EditState previous) {
          emit q_ptr->accountEditStateChanged(a, state, previous);
       });
    
       switch(a->protocol()) {
          case Account::Protocol::SIP:
             m_lSipAccounts  << a;
             break;
          case Account::Protocol::IAX:
             m_lIAXAccounts  << a;
             break;
          case Account::Protocol::RING:
             m_lRingAccounts << a;
             break;
          case Account::Protocol::COUNT__:
             break;
       }
    }
    
    Account* AccountModel::add(const QString& alias, const Account::Protocol proto)
    {
       Account* a = AccountPrivate::buildNewAccountFromAlias(proto,alias);
       connect(a,SIGNAL(changed(Account*)),d_ptr,SLOT(slotAccountChanged(Account*)));
       d_ptr->insertAccount(a,d_ptr->m_lAccounts.size());
       connect(a,SIGNAL(presenceEnabledChanged(bool)),d_ptr,SLOT(slotAccountPresenceEnabledChanged(bool)));
       //connect(a,SIGNAL(propertyChanged(Account*,QString,QString,QString)),d_ptr,SLOT(slotAccountChanged(Account*)));
    
       emit dataChanged(index(d_ptr->m_lAccounts.size()-1,0), index(d_ptr->m_lAccounts.size()-1,0));
    
       if (d_ptr->m_pSelectionModel) {
          d_ptr->m_pSelectionModel->setCurrentIndex(index(d_ptr->m_lAccounts.size()-1,0), QItemSelectionModel::ClearAndSelect);
       }
    
       if (a->isNew() || a->id() != DRing::Account::ProtocolNames::IP2IP)
          d_ptr->enableProtocol(proto);
    
    // Override ringtone path
    #if defined(Q_OS_OSX)
        QDir ringtonesDir(QCoreApplication::applicationDirPath());
        ringtonesDir.cdUp();
        ringtonesDir.cd("Resources/ringtones/");
        a->setRingtonePath(ringtonesDir.path()+"/default.wav");
    #endif
    
       emit accountAdded(a);
    
       editState();
    
       return a;
    }
    
    Account* AccountModel::add(const QString& alias, const QModelIndex& idx)
    {
       return add(alias, qvariant_cast<Account::Protocol>(idx.data((int)ProtocolModel::Role::Protocol)));
    }
    
    ///Remove an account
    void AccountModel::remove(Account* account)
    {
       if (not account) return;
       qDebug() << "Removing" << account->alias() << account->id();
       const int aindex = d_ptr->m_lAccounts.indexOf(account);
       beginRemoveRows(QModelIndex(),aindex,aindex);
       d_ptr->m_lAccounts.remove(aindex);
       d_ptr->m_lDeletedAccounts << account->id();
       endRemoveRows();
       emit accountRemoved(account);
       //delete account;
       d_ptr->m_pRemovedAccounts << account;
    }
    
    void AccountModel::remove(const QModelIndex& idx )
    {
       remove(getAccountByModelIndex(idx));
    }
    
    ///Set model data
    bool AccountModel::setData(const QModelIndex& idx, const QVariant& value, int role)
    {
       if (idx.isValid() && idx.column() == 0 && role == Qt::CheckStateRole) {
          const bool prevEnabled = d_ptr->m_lAccounts[idx.row()]->isEnabled();
          d_ptr->m_lAccounts[idx.row()]->setEnabled(value.toBool());
          emit dataChanged(idx, idx);
          if (prevEnabled != value.toBool())
             emit accountEnabledChanged(d_ptr->m_lAccounts[idx.row()]);
          emit dataChanged(idx, idx);
          return true;
       }
       else if ( role == Qt::EditRole ) {
          if (value.toString() != data(idx,Qt::EditRole)) {
             d_ptr->m_lAccounts[idx.row()]->setAlias(value.toString());
             emit dataChanged(idx, idx);
          }
       }
       return false;
    }
    
    
    /*****************************************************************************
     *                                                                           *
     *                                 Operator                                  *
     *                                                                           *
     ****************************************************************************/
    
    ///Get the account from its index
    const Account* AccountModel::operator[] (int i) const
    {
       return d_ptr->m_lAccounts[i];
    }
    
    ///Get the account from its index
    Account* AccountModel::operator[] (int i)
    {
       return d_ptr->m_lAccounts[i];
    }
    
    ///Get accoutn by id
    Account* AccountModel::operator[] (const QByteArray& i) {
       return getById(i);
    }
    
    void AccountModel::add(Account* acc)
    {
       d_ptr->insertAccount(acc,d_ptr->m_lAccounts.size());
    }
    
    
    /*****************************************************************************
     *                                                                           *
     *                              Drag and drop                                *
     *                                                                           *
     ****************************************************************************/
    
    
    QStringList AccountModel::mimeTypes() const
    {
       return d_ptr->m_lMimes;
    }
    
    bool AccountModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
    {
       Q_UNUSED(action)
       if(parent.isValid() || column > 0) {
          qDebug() << "column invalid";
          return false;
       }
    
       if (data->hasFormat(RingMimes::ACCOUNT)) {
          int destinationRow = -1;
    
          if(row < 0) {
             //drop on top
             destinationRow = d_ptr->m_lAccounts.size() - 1;
          }
          else if(row >= d_ptr->m_lAccounts.size()) {
             destinationRow = 0;
          }
          else {
             destinationRow = row;
          }
    
          Account* dest = getById(data->data(RingMimes::ACCOUNT));
          if (!dest)
             return false;
    
          const QModelIndex accIdx = dest->index();
    
          beginRemoveRows(QModelIndex(), accIdx.row(), accIdx.row());
          Account* acc = d_ptr->m_lAccounts[accIdx.row()];
          d_ptr->m_lAccounts.removeAt(accIdx.row());
          endRemoveRows();
    
          d_ptr->insertAccount(acc,destinationRow);
    
          d_ptr->m_pSelectionModel->setCurrentIndex(index(destinationRow), QItemSelectionModel::ClearAndSelect);
    
          return true;
       }
    
       return false;
    }
    
    QMimeData* AccountModel::mimeData(const QModelIndexList& indexes) const
    {
       QMimeData* mMimeData = new QMimeData();
    
       for (const QModelIndex& index : indexes) {
          if (index.isValid()) {
             mMimeData->setData(RingMimes::ACCOUNT, index.data((int)Account::Role::Id).toByteArray());
          }
       }
    
       return mMimeData;
    }
    
    Qt::DropActions AccountModel::supportedDragActions() const
    {
       return Qt::MoveAction | Qt::TargetMoveAction;
    }
    
    Qt::DropActions AccountModel::supportedDropActions() const
    {
       return Qt::MoveAction | Qt::TargetMoveAction;
    }
    
    #include <accountmodel.moc>