Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
newaccountmodel.cpp 45.07 KiB
/****************************************************************************
 *    Copyright (C) 2017-2020 Savoir-faire Linux Inc.                       *
 *   Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>             *
 *   Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>           *
 *   Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>       *
 *   Author: Andreas Traczyk <andreas.traczyk@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 "api/newaccountmodel.h"

// new LRC
#include "api/lrc.h"
#include "api/contactmodel.h"
#include "api/conversationmodel.h"
#include "api/peerdiscoverymodel.h"
#include "api/newcallmodel.h"
#include "api/newcodecmodel.h"
#include "api/newdevicemodel.h"
#include "api/behaviorcontroller.h"
#include "authority/storagehelper.h"
#include "callbackshandler.h"
#include "database.h"
#include "vcard.h"

// old LRC
#include "api/profile.h"
#include "qtwrapper/conversions_wrap.hpp"

// Dbus
#include "dbus/configurationmanager.h"

// daemon
#include <account_const.h>

//qt
#include <QtGui/QPixmap>
#include <QtGui/QImage>
#include <QtCore/QBuffer>

#include <atomic>

namespace lrc
{

using namespace api;

class NewAccountModelPimpl: public QObject
{
    Q_OBJECT
public:
    NewAccountModelPimpl(NewAccountModel& linked,
                         Lrc& lrc,
                         const CallbacksHandler& callbackHandler,
                         const BehaviorController& behaviorController,
                         MigrationCb& willMigrateCb,
                         MigrationCb& didMigrateCb);
    ~NewAccountModelPimpl();
    using AccountInfoDbMap = std::map<QString,
                                      std::pair<account::Info, std::shared_ptr<Database>>>;

    NewAccountModel& linked;
    Lrc& lrc;
    const CallbacksHandler& callbacksHandler;
    const BehaviorController& behaviorController;
    AccountInfoDbMap accounts;

    // Synchronization tools
    std::mutex m_mutex_account;
    std::mutex m_mutex_account_removal;
    std::condition_variable m_condVar_account_removal;
    std::atomic_bool username_changed;
    QString new_username;

    /**
     * Add the profile information from an account to the db then add it to accounts.
     * @param accountId
     * @param db an optional migrated database object
     * @note this method get details for an account from the daemon.
     */
    void addToAccounts(const QString& accountId, std::shared_ptr<Database> db = nullptr);

    /**
     * Remove account from accounts list. Emit accountRemoved.
     * @param accountId
     */
    void removeFromAccounts(const QString& accountId);

    /**
     * Sync changes to the accounts list with the lrc.
     */
    void updateAccounts();

public Q_SLOTS:

    /**
     * Emit accountStatusChanged.
     * @param accountId
     * @param status
     */
    void slotAccountStatusChanged(const QString& accountID, const api::account::Status status);

    /**
     * Emit exportOnRingEnded.
     * @param accountId
     * @param status
     * @param pin
     */
    void slotExportOnRingEnded(const QString& accountID, int status, const QString& pin);

    /**
     * @param accountId
     * @param details
     */
    void slotAccountDetailsChanged(const QString& accountID, const MapStringString& details);

    /**
     * @param accountId
     * @param details
     */
    void slotVolatileAccountDetailsChanged(const QString& accountID, const MapStringString& details);

    /**
     * Emit nameRegistrationEnded
     * @param accountId
     * @param status
     * @param name
     */
    void slotNameRegistrationEnded(const QString& accountId, int status, const QString& name);

    /**
     * Emit registeredNameFound
     * @param accountId
     * @param status
     * @param address
     * @param name
     */
    void slotRegisteredNameFound(const QString& accountId, int status, const QString& address, const QString& name);

    /**
     * Emit migrationEnded
     * @param accountId
     * @param ok
     */
    void slotMigrationEnded(const QString& accountId, bool ok);

    /**
     * Emit accountAvatarReceived
     * @param accountId
     * @param userPhoto
     */
    void slotAccountAvatarReceived(const QString& accountId, const QString& userPhoto);
};

NewAccountModel::NewAccountModel(Lrc& lrc,
                                 const CallbacksHandler& callbacksHandler,
                                 const BehaviorController& behaviorController,
                                 MigrationCb& willMigrateCb,
                                 MigrationCb& didMigrateCb)
: QObject()
, pimpl_(std::make_unique<NewAccountModelPimpl>(*this, lrc, callbacksHandler, behaviorController,
                                                willMigrateCb, didMigrateCb))
{
}

NewAccountModel::~NewAccountModel()
{
}

QStringList
NewAccountModel::getAccountList() const
{
    QStringList filteredAccountIds;
    const QStringList accountIds = ConfigurationManager::instance().getAccountList();

    for (auto const& id : accountIds) {
        auto account = pimpl_->accounts.find(id);
        // Do not include accounts flagged for removal
        if (account != pimpl_->accounts.end() && account->second.first.valid)
            filteredAccountIds.push_back(id);
    }

    return filteredAccountIds;
}

void
NewAccountModel::setAccountEnabled(const QString& accountId, bool enabled) const
{
    auto account = pimpl_->accounts.find(accountId);
    if (account == pimpl_->accounts.end()) {
        throw std::out_of_range("NewAccountModel::getAccountConfig, can't find " + accountId.toStdString());
    }
    auto& accountInfo = account->second.first;
    accountInfo.enabled = enabled;
    ConfigurationManager::instance().sendRegister(accountId, enabled);
}

void
NewAccountModel::setAccountConfig(const QString& accountId,
                                  const account::ConfProperties_t& confProperties) const
{
    auto account = pimpl_->accounts.find(accountId);
    if (account == pimpl_->accounts.end()) {
        throw std::out_of_range("NewAccountModel::save, can't find " + accountId.toStdString());
    }
    auto& accountInfo = account->second.first;
    auto& configurationManager = ConfigurationManager::instance();
    MapStringString details = confProperties.toDetails();
    // Set values from Info. No need to include ID and TYPE. SIP accounts may modify the USERNAME
    // TODO: move these into the ConfProperties_t struct ?
    using namespace DRing::Account;
    qDebug("UPNP_ENABLED: %s\n", details[ConfProperties::UPNP_ENABLED].toStdString().c_str());
    details[ConfProperties::ENABLED]                    = accountInfo.enabled ? QString("true") : QString ("false");
    details[ConfProperties::ALIAS]                      = accountInfo.profileInfo.alias;
    details[ConfProperties::DISPLAYNAME]                = accountInfo.profileInfo.alias;
    details[ConfProperties::TYPE]                       = (accountInfo.profileInfo.type == profile::Type::RING) ? QString(ProtocolNames::RING) : QString(ProtocolNames::SIP);
    if (accountInfo.profileInfo.type == profile::Type::RING) {
        details[ConfProperties::USERNAME] = accountInfo.profileInfo.uri.prepend((accountInfo.profileInfo.type == profile::Type::RING) ? "ring:" : "");
    } else if (accountInfo.profileInfo.type == profile::Type::SIP) {
        VectorMapStringString finalCred;

        MapStringString credentials;
        credentials[ConfProperties::USERNAME] = confProperties.username;
        credentials[ConfProperties::PASSWORD] = confProperties.password;
        credentials[ConfProperties::REALM] = confProperties.realm.isEmpty() ? "*" : confProperties.realm;

        auto credentialsVec = confProperties.credentials;
        credentialsVec[0] = credentials;
        for (auto const &i : credentialsVec) {
            QMap<QString, QString> credMap;
            for (auto const &j : i.toStdMap()) {
                credMap[j.first] = j.second;
            }
            finalCred.append(credMap);
        }

        ConfigurationManager::instance().setCredentials(accountId, finalCred);
        details[ConfProperties::USERNAME] = confProperties.username;
        accountInfo.confProperties.credentials.swap(credentialsVec);
    }
    configurationManager.setAccountDetails(accountId, details);
}

account::ConfProperties_t
NewAccountModel::getAccountConfig(const QString& accountId) const
{
    auto account = pimpl_->accounts.find(accountId);
    if (account == pimpl_->accounts.end()) {
        throw std::out_of_range("NewAccountModel::getAccountConfig, can't find " + accountId.toStdString());
    }
    auto& accountInfo = account->second.first;
    return accountInfo.confProperties;
}

void
NewAccountModel::setAlias(const QString& accountId, const QString& alias)
{
    auto account = pimpl_->accounts.find(accountId);
    if (account == pimpl_->accounts.end()) {
        throw std::out_of_range("NewAccountModel::setAlias, can't find " + accountId.toStdString());
    }
    auto& accountInfo = account->second.first;
    accountInfo.profileInfo.alias = alias;

    authority::storage::createOrUpdateProfile(accountInfo.id, accountInfo.profileInfo);

    emit profileUpdated(accountId);
}

void
NewAccountModel::setAvatar(const QString& accountId, const QString& avatar)
{
    auto account = pimpl_->accounts.find(accountId);
    if (account == pimpl_->accounts.end()) {
        throw std::out_of_range("NewAccountModel::setAvatar, can't find " + accountId.toStdString());
    }
    auto& accountInfo = account->second.first;
    accountInfo.profileInfo.avatar = avatar;

    authority::storage::createOrUpdateProfile(accountInfo.id, accountInfo.profileInfo);

    emit profileUpdated(accountId);
}

bool
NewAccountModel::registerName(const QString& accountId, const QString& password, const QString& username)
{
    return ConfigurationManager::instance().registerName(accountId, password, username);
}

bool
NewAccountModel::exportToFile(const QString& accountId, const QString& path, const QString& password) const
{
    return ConfigurationManager::instance().exportToFile(accountId, path, password);
}

bool
NewAccountModel::exportOnRing(const QString& accountId, const QString& password) const
{
    return ConfigurationManager::instance().exportOnRing(accountId, password);
}

void
NewAccountModel::removeAccount(const QString& accountId) const
{
    ConfigurationManager::instance().removeAccount(accountId);
}

bool
NewAccountModel::changeAccountPassword(const QString& accountId,
                                       const QString& currentPassword,
                                       const QString& newPassword) const
{
    return ConfigurationManager::instance()
    .changeAccountPassword(accountId, currentPassword, newPassword);
}

void
NewAccountModel::flagFreeable(const QString& accountId) const
{
    auto account = pimpl_->accounts.find(accountId);
    if (account == pimpl_->accounts.end())
        throw std::out_of_range("NewAccountModel::flagFreeable, can't find " + accountId.toStdString());

    {
        std::lock_guard<std::mutex> lock(pimpl_->m_mutex_account_removal);
        account->second.first.freeable = true;
    }
    pimpl_->m_condVar_account_removal.notify_all();
}

const account::Info&
NewAccountModel::getAccountInfo(const QString& accountId) const
{
    auto accountInfo = pimpl_->accounts.find(accountId);
    if (accountInfo == pimpl_->accounts.end())
        throw std::out_of_range("NewAccountModel::getAccountInfo, can't find " + accountId.toStdString());
    return accountInfo->second.first;
}

NewAccountModelPimpl::NewAccountModelPimpl(NewAccountModel& linked,
                                           Lrc& lrc,
                                           const CallbacksHandler& callbacksHandler,
                                           const BehaviorController& behaviorController,
                                           MigrationCb& willMigrateCb,
                                           MigrationCb& didMigrateCb)
: linked(linked)
, lrc {lrc}
, behaviorController(behaviorController)
, callbacksHandler(callbacksHandler)
, username_changed(false)
{
    const QStringList accountIds = ConfigurationManager::instance().getAccountList();

    // NOTE: If the daemon is down, but dbus answered, id can contains
    // "Remote peer disconnected", "The name is not activable", etc.
    // So avoid to migrate useless directories.
    for (auto& id : accountIds)
        if (id.indexOf(" ") != -1) {
            qWarning() << "Invalid dbus answer. Daemon not running";
            return;
        }

    auto accountDbs = authority::storage::migrateIfNeeded(accountIds, willMigrateCb, didMigrateCb);
    for (const auto& id : accountIds) {
        addToAccounts(id, accountDbs.at(accountIds.indexOf(id)));
    }

    connect(&callbacksHandler, &CallbacksHandler::accountsChanged, this, &NewAccountModelPimpl::updateAccounts);
    connect(&callbacksHandler, &CallbacksHandler::accountStatusChanged, this, &NewAccountModelPimpl::slotAccountStatusChanged);
    connect(&callbacksHandler, &CallbacksHandler::accountDetailsChanged, this, &NewAccountModelPimpl::slotAccountDetailsChanged);
    connect(&callbacksHandler, &CallbacksHandler::volatileAccountDetailsChanged, this, &NewAccountModelPimpl::slotVolatileAccountDetailsChanged);
    connect(&callbacksHandler, &CallbacksHandler::exportOnRingEnded, this, &NewAccountModelPimpl::slotExportOnRingEnded);
    connect(&callbacksHandler, &CallbacksHandler::nameRegistrationEnded, this, &NewAccountModelPimpl::slotNameRegistrationEnded);
    connect(&callbacksHandler, &CallbacksHandler::registeredNameFound, this, &NewAccountModelPimpl::slotRegisteredNameFound);
    connect(&callbacksHandler, &CallbacksHandler::migrationEnded, this, &NewAccountModelPimpl::slotMigrationEnded);
    connect(&callbacksHandler, &CallbacksHandler::accountAvatarReceived, this, &NewAccountModelPimpl::slotAccountAvatarReceived);
}

NewAccountModelPimpl::~NewAccountModelPimpl()
{
}

void
NewAccountModelPimpl::updateAccounts()
{
    qDebug() << "Syncing lrc accounts list with the daemon";
    ConfigurationManagerInterface& configurationManager = ConfigurationManager::instance();
    QStringList accountIds = configurationManager.getAccountList();

    // Detect removed accounts
    QStringList toBeRemoved;
    for (auto& it : accounts) {
        auto& accountInfo = it.second.first;
        if (!accountIds.contains(accountInfo.id)) {
            qDebug() << QString("detected account removal %1").arg(accountInfo.id);
            toBeRemoved.push_back(accountInfo.id);
        }
    }

    for (auto it = toBeRemoved.begin(); it != toBeRemoved.end(); ++it) {
        removeFromAccounts(*it);
    }

    // Detect new accounts
    for (auto& id : accountIds) {
        auto account = accounts.find(id);
        // NOTE: If the daemon is down, but dbus answered, id can contains
        // "Remote peer disconnected", "The name is not activable", etc.
        // So avoid to create useless directories.
        if (account == accounts.end() && id.indexOf(" ") == -1) {
            qWarning() << QString("detected new account %1").arg(id);
            addToAccounts(id);
            auto updatedAccount = accounts.find(id);
            if (updatedAccount == accounts.end()) {
                return;
            }
            if (updatedAccount->second.first.profileInfo.type == profile::Type::SIP) {
                // NOTE: At this point, a SIP account is ready, but not a Ring
                // account. Indeed, the keys are not generated at this point.
                // See slotAccountStatusChanged for more details.
                emit linked.accountAdded(id);
            }
        }
    }
}

void
NewAccountModelPimpl::slotAccountStatusChanged(const QString& accountID, const api::account::Status status)
{
    if (status == api::account::Status::INVALID) {
        emit linked.invalidAccountDetected(accountID);
        return;
    }
    auto it = accounts.find(accountID);

    // If account is not in the map yet, don't add it, it is updateAccounts's job
    if (it == accounts.end()) {
        return;
    }

    auto& accountInfo = it->second.first;

    if (accountInfo.profileInfo.type != profile::Type::SIP) {
        if (status != api::account::Status::INITIALIZING
            && accountInfo.status == api::account::Status::INITIALIZING) {
            // Detect when a new account is generated (keys are ready). During
            // the generation, a Ring account got the "INITIALIZING" status.
            // When keys are generated, the status will change.
            accounts.erase(accountID);
            addToAccounts(accountID);
            emit linked.accountAdded(accountID);
        } else if (!accountInfo.profileInfo.uri.isEmpty()) {
            accountInfo.status = status;
            emit linked.accountStatusChanged(accountID);
        }
    } else {
        accountInfo.status = status;
        emit linked.accountStatusChanged(accountID);
    }
}

void
NewAccountModelPimpl::slotAccountDetailsChanged(const QString& accountId, const MapStringString& details)
{
    auto account = accounts.find(accountId);
    if (account == accounts.end()) {
        throw std::out_of_range("NewAccountModelPimpl::slotAccountDetailsChanged, can't find " + accountId.toStdString());
    }
    auto& accountInfo = account->second.first;
    accountInfo.fromDetails(details);
    if (username_changed) {
        username_changed = false;
        accountInfo.registeredName = new_username;
        emit linked.profileUpdated(accountId);
    }
    emit linked.accountStatusChanged(accountId);
}

void
NewAccountModelPimpl::slotVolatileAccountDetailsChanged(const QString& accountId, const MapStringString& details)
{
    auto account = accounts.find(accountId);
    if (account == accounts.end()) {
        qWarning() << "NewAccountModelPimpl::slotVolatileAccountDetailsChanged, can't find " << accountId;
        return;
    }
    auto& accountInfo = account->second.first;

    auto new_usernameIt = details.find(DRing::Account::VolatileProperties::REGISTERED_NAME);
    if (new_usernameIt == details.end())
        return;
    accountInfo.registeredName = new_usernameIt.value();
    emit linked.profileUpdated(accountId);
}

void
NewAccountModelPimpl::slotExportOnRingEnded(const QString& accountID, int status, const QString& pin)
{
    account::ExportOnRingStatus convertedStatus = account::ExportOnRingStatus::INVALID;
    switch (status) {
    case 0:
        convertedStatus = account::ExportOnRingStatus::SUCCESS;
        break;
    case 1:
        convertedStatus = account::ExportOnRingStatus::WRONG_PASSWORD;
        break;
    case 2:
        convertedStatus = account::ExportOnRingStatus::NETWORK_ERROR;
        break;
    default:
        break;
    }
    emit linked.exportOnRingEnded(accountID, convertedStatus, pin);
}

void
NewAccountModelPimpl::slotNameRegistrationEnded(const QString& accountId, int status, const QString& name)
{
    account::RegisterNameStatus convertedStatus = account::RegisterNameStatus::INVALID;
    switch (status)
    {
    case 0: {
        convertedStatus = account::RegisterNameStatus::SUCCESS;
        auto account = accounts.find(accountId);
        if (account != accounts.end() && account->second.first.registeredName.isEmpty()) {
            auto conf = linked.getAccountConfig(accountId);
            username_changed = true;
            new_username = name;
            linked.setAccountConfig(accountId, conf);
        }
        break;
      }
    case 1:
        convertedStatus = account::RegisterNameStatus::WRONG_PASSWORD;
        break;
    case 2:
        convertedStatus = account::RegisterNameStatus::INVALID_NAME;
        break;
    case 3:
        convertedStatus = account::RegisterNameStatus::ALREADY_TAKEN;
        break;
    case 4:
        convertedStatus = account::RegisterNameStatus::NETWORK_ERROR;
        break;
    default:
        break;
    }
    emit linked.nameRegistrationEnded(accountId, convertedStatus, name);
}

void
NewAccountModelPimpl::slotRegisteredNameFound(const QString& accountId, int status, const QString& address, const QString& name)
{
    account::LookupStatus convertedStatus = account::LookupStatus::INVALID;
    switch (status)
    {
    case 0:
        convertedStatus = account::LookupStatus::SUCCESS;
        break;
    case 1:
        convertedStatus = account::LookupStatus::INVALID_NAME;
        break;
    case 2:
        convertedStatus = account::LookupStatus::NOT_FOUND;
        break;
    case 3:
        convertedStatus = account::LookupStatus::ERROR;
        break;
    default:
        break;
    }
    emit linked.registeredNameFound(accountId, convertedStatus, address, name);
}

void
NewAccountModelPimpl::slotMigrationEnded(const QString& accountId, bool ok)
{
    if (ok) {
        auto it = accounts.find(accountId);
        if (it == accounts.end()) {
            addToAccounts(accountId);
            return;
        }
        auto& accountInfo = it->second.first;
        MapStringString details = ConfigurationManager::instance().getAccountDetails(accountId);
        accountInfo.fromDetails(details);
        MapStringString volatileDetails = ConfigurationManager::instance().getVolatileAccountDetails(accountId);
        QString daemonStatus = volatileDetails[DRing::Account::ConfProperties::Registration::STATUS];
        accountInfo.status = lrc::api::account::to_status(daemonStatus);
    }
    emit linked.migrationEnded(accountId, ok);
}

void
NewAccountModelPimpl::slotAccountAvatarReceived(const QString& accountId, const QString& userPhoto)
{
    linked.setAvatar(accountId, userPhoto);
}

void
NewAccountModelPimpl::addToAccounts(const QString& accountId,
                                    std::shared_ptr<Database> db)
{
    if (db == nullptr) {
        try {
            auto appPath = authority::storage::getPath();
            auto dbName = accountId + "/history";
            db = DatabaseFactory::create<Database>(dbName, appPath);
            // create the profiles path if necessary
            QDir profilesDir(appPath + accountId + "/profiles");
            if (!profilesDir.exists()) {
                profilesDir.mkpath(".");
            }
        } catch (const std::runtime_error& e) {
            qWarning() << e.what();
            return;
        }
    }

    auto it = accounts.emplace(accountId, std::make_pair(account::Info(), db));

    if (!it.second) {
        qWarning("failed to add new account: id already present in map");
        return;
    }

    // Init profile
    account::Info& newAccInfo = (it.first)->second.first;
    newAccInfo.id = accountId;
    newAccInfo.profileInfo.avatar = authority::storage::getAccountAvatar(accountId);

    // Fill account::Info struct with details from daemon
    MapStringString details = ConfigurationManager::instance().getAccountDetails(accountId);
    newAccInfo.fromDetails(details);

    // Fill account::Info::confProperties credentials
    VectorMapStringString credGet = ConfigurationManager::instance().getCredentials(accountId);
    VectorMapStringString credToStore;
    for (auto const &i : credGet.toStdVector()) {
        MapStringString credMap;
        for (auto const &j : i.toStdMap()) {
            credMap[j.first] = j.second;
        }
        credToStore.push_back(credMap);
    }

    newAccInfo.confProperties.credentials.swap(credToStore);

    // Init models for this account
    newAccInfo.accountModel = &linked;
    newAccInfo.callModel = std::make_unique<NewCallModel>(newAccInfo, callbacksHandler);
    newAccInfo.contactModel = std::make_unique<ContactModel>(newAccInfo, *db, callbacksHandler, behaviorController);
    newAccInfo.conversationModel = std::make_unique<ConversationModel>(newAccInfo, lrc, *db, callbacksHandler, behaviorController);
    newAccInfo.peerDiscoveryModel = std::make_unique<PeerDiscoveryModel>(callbacksHandler, accountId);
    newAccInfo.deviceModel = std::make_unique<NewDeviceModel>(newAccInfo, callbacksHandler);
    newAccInfo.codecModel = std::make_unique<NewCodecModel>(newAccInfo, callbacksHandler);

    MapStringString volatileDetails = ConfigurationManager::instance().getVolatileAccountDetails(accountId);
    auto daemonStatus = volatileDetails[DRing::Account::ConfProperties::Registration::STATUS];
    newAccInfo.status = lrc::api::account::to_status(daemonStatus);
}

void
NewAccountModelPimpl::removeFromAccounts(const QString& accountId)
{
    /* Update db before waiting for the client to stop using the structs is fine
       as long as we don't free anything */
    auto account = accounts.find(accountId);
    if (account == accounts.end()) {
        return;
    }
    auto& accountInfo = account->second.first;
    /* Inform client about account removal. Do *not* free account structures
       before we are sure that the client stopped using it, otherwise we might
       get into use-after-free troubles. */
    accountInfo.valid = false;
    emit linked.accountRemoved(accountId);

#ifdef CHK_FREEABLE_BEFORE_ERASE_ACCOUNT
    std::unique_lock<std::mutex> lock(m_mutex_account_removal);
    // Wait for client to stop using old account structs
    m_condVar_account_removal.wait(lock, [&](){return accounts[accountId].freeable;});
    lock.unlock();
#endif

    // Now we can free them
    accounts.erase(accountId);
}

void
account::Info::fromDetails(const MapStringString& details)
{
    using namespace DRing::Account;
    const MapStringString volatileDetails = ConfigurationManager::instance().getVolatileAccountDetails(id);

    // General
    if (details[ConfProperties::TYPE] != "")
        profileInfo.type                                = details[ConfProperties::TYPE] == QString(ProtocolNames::RING) ? profile::Type::RING : profile::Type::SIP;
    registeredName                                      = profileInfo.type == profile::Type::RING ? volatileDetails[VolatileProperties::REGISTERED_NAME] : "";
    profileInfo.alias                                   = details[ConfProperties::DISPLAYNAME];
    enabled                                             = toBool(details[ConfProperties::ENABLED]);
    confProperties.mailbox                              = details[ConfProperties::MAILBOX];
    confProperties.dtmfType                             = details[ConfProperties::DTMF_TYPE];
    confProperties.autoAnswer                           = toBool(details[ConfProperties::AUTOANSWER]);
    confProperties.activeCallLimit                      = toInt(details[ConfProperties::ACTIVE_CALL_LIMIT]);
    confProperties.hostname                             = details[ConfProperties::HOSTNAME];
    profileInfo.uri                                     = (profileInfo.type == profile::Type::RING and details[ConfProperties::USERNAME].contains("ring:"))
                                                          ? QString(details[ConfProperties::USERNAME]).remove(QString("ring:"))
                                                          : details[ConfProperties::USERNAME];
    confProperties.username                             = details[ConfProperties::USERNAME];
    confProperties.routeset                             = details[ConfProperties::ROUTE];
    confProperties.password                             = details[ConfProperties::PASSWORD];
    confProperties.realm                                = details[ConfProperties::REALM];
    confProperties.localInterface                       = details[ConfProperties::LOCAL_INTERFACE];
    confProperties.deviceId                             = details[ConfProperties::RING_DEVICE_ID];
    confProperties.deviceName                           = details[ConfProperties::RING_DEVICE_NAME];
    confProperties.publishedSameAsLocal                 = toBool(details[ConfProperties::PUBLISHED_SAMEAS_LOCAL]);
    confProperties.localPort                            = toInt(details[ConfProperties::LOCAL_PORT]);
    confProperties.publishedPort                        = toInt(details[ConfProperties::PUBLISHED_PORT]);
    confProperties.publishedAddress                     = details[ConfProperties::PUBLISHED_ADDRESS];
    confProperties.userAgent                            = details[ConfProperties::USER_AGENT];
    confProperties.upnpEnabled                          = toBool(details[ConfProperties::UPNP_ENABLED]);
    confProperties.hasCustomUserAgent                   = toBool(details[ConfProperties::HAS_CUSTOM_USER_AGENT]);
    confProperties.allowIncoming                        = toBool(details[ConfProperties::ALLOW_CERT_FROM_HISTORY])
                                                        | toBool(details[ConfProperties::ALLOW_CERT_FROM_CONTACT])
                                                        | toBool(details[ConfProperties::ALLOW_CERT_FROM_TRUSTED]);
    confProperties.archivePassword                      = details[ConfProperties::ARCHIVE_PASSWORD];
    confProperties.archiveHasPassword                   = toBool(details[ConfProperties::ARCHIVE_HAS_PASSWORD]);
    confProperties.archivePath                          = details[ConfProperties::ARCHIVE_PATH];
    confProperties.archivePin                           = details[ConfProperties::ARCHIVE_PIN];
    confProperties.proxyEnabled                         = toBool(details[ConfProperties::PROXY_ENABLED]);
    confProperties.proxyServer                          = details[ConfProperties::PROXY_SERVER];
    confProperties.proxyPushToken                       = details[ConfProperties::PROXY_PUSH_TOKEN];
    confProperties.peerDiscovery                        = toBool(details[ConfProperties::DHT_PEER_DISCOVERY]);
    confProperties.accountDiscovery                     = toBool(details[ConfProperties::ACCOUNT_PEER_DISCOVERY]);
    confProperties.accountPublish                       = toBool(details[ConfProperties::ACCOUNT_PUBLISH]);
    // Audio
    confProperties.Audio.audioPortMax                   = toInt(details[ConfProperties::Audio::PORT_MAX]);
    confProperties.Audio.audioPortMin                   = toInt(details[ConfProperties::Audio::PORT_MIN]);
    // Video
    confProperties.Video.videoEnabled                   = toBool(details[ConfProperties::Video::ENABLED]);
    confProperties.Video.videoPortMax                   = toInt(details[ConfProperties::Video::PORT_MAX]);
    confProperties.Video.videoPortMin                   = toInt(details[ConfProperties::Video::PORT_MIN]);
    // STUN
    confProperties.STUN.server                          = details[ConfProperties::STUN::SERVER];
    confProperties.STUN.enable                          = toBool(details[ConfProperties::STUN::ENABLED]);
    // TURN
    confProperties.TURN.server                          = details[ConfProperties::TURN::SERVER];
    confProperties.TURN.enable                          = toBool(details[ConfProperties::TURN::ENABLED]);
    confProperties.TURN.username                        = details[ConfProperties::TURN::SERVER_UNAME];
    confProperties.TURN.password                        = details[ConfProperties::TURN::SERVER_PWD];
    confProperties.TURN.realm                           = details[ConfProperties::TURN::SERVER_REALM];
    // Presence
    confProperties.Presence.presencePublishSupported    = toBool(details[ConfProperties::Presence::SUPPORT_PUBLISH]);
    confProperties.Presence.presenceSubscribeSupported  = toBool(details[ConfProperties::Presence::SUPPORT_SUBSCRIBE]);
    confProperties.Presence.presenceEnabled             = toBool(details[ConfProperties::Presence::ENABLED]);
    // Ringtone
    confProperties.Ringtone.ringtonePath                = details[ConfProperties::Ringtone::PATH];
    confProperties.Ringtone.ringtoneEnabled             = toBool(details[ConfProperties::Ringtone::ENABLED]);
    // SRTP
    confProperties.SRTP.keyExchange                     = details[ConfProperties::SRTP::KEY_EXCHANGE].isEmpty()? account::KeyExchangeProtocol::NONE : account::KeyExchangeProtocol::SDES;
    confProperties.SRTP.enable                          = toBool(details[ConfProperties::SRTP::ENABLED]);
    confProperties.SRTP.rtpFallback                     = toBool(details[ConfProperties::SRTP::RTP_FALLBACK]);
    // TLS
    confProperties.TLS.listenerPort                     = toInt(details[ConfProperties::TLS::LISTENER_PORT]);
    confProperties.TLS.enable                           = details[ConfProperties::TYPE] == QString(ProtocolNames::RING)? true : toBool(details[ConfProperties::TLS::ENABLED]);
    confProperties.TLS.port                             = toInt(details[ConfProperties::TLS::PORT]);
    confProperties.TLS.certificateListFile              = details[ConfProperties::TLS::CA_LIST_FILE];
    confProperties.TLS.certificateFile                  = details[ConfProperties::TLS::CERTIFICATE_FILE];
    confProperties.TLS.privateKeyFile                   = details[ConfProperties::TLS::PRIVATE_KEY_FILE];
    confProperties.TLS.password                         = details[ConfProperties::TLS::PASSWORD];
    auto method = toStdString(details[ConfProperties::TLS::METHOD]);
    if (method == "TLSv1") {
        confProperties.TLS.method                       = account::TlsMethod::TLSv1;
    } else if (method == "TLSv1.1") {
        confProperties.TLS.method                       = account::TlsMethod::TLSv1_1;
    } else if (method == "TLSv1.2") {
        confProperties.TLS.method                       = account::TlsMethod::TLSv1_2;
    } else {
        confProperties.TLS.method                       = account::TlsMethod::DEFAULT;
    }
    confProperties.TLS.ciphers                          = details[ConfProperties::TLS::CIPHERS];
    confProperties.TLS.serverName                       = details[ConfProperties::TLS::SERVER_NAME];
    confProperties.TLS.verifyServer                     = toBool(details[ConfProperties::TLS::VERIFY_SERVER]);
    confProperties.TLS.verifyClient                     = toBool(details[ConfProperties::TLS::VERIFY_CLIENT]);
    confProperties.TLS.requireClientCertificate         = toBool(details[ConfProperties::TLS::REQUIRE_CLIENT_CERTIFICATE]);
    confProperties.TLS.negotiationTimeoutSec            = toInt(details[ConfProperties::TLS::NEGOTIATION_TIMEOUT_SEC]);
    // DHT
    confProperties.DHT.port                             = toInt(details[ConfProperties::DHT::PORT]);
    confProperties.DHT.PublicInCalls                    = toBool(details[ConfProperties::DHT::PUBLIC_IN_CALLS]);
    confProperties.DHT.AllowFromTrusted                 = toBool(details[ConfProperties::DHT::ALLOW_FROM_TRUSTED]);
    // RingNS
    confProperties.RingNS.uri                           = details[ConfProperties::RingNS::URI];
    confProperties.RingNS.account                       = details[ConfProperties::RingNS::ACCOUNT];
    // Registration
    confProperties.Registration.expire                  = toInt(details[ConfProperties::Registration::EXPIRE]);
    // Jams
    confProperties.managerUri                           = details[ConfProperties::MANAGER_URI];
    confProperties.managerUsername                      = details[ConfProperties::MANAGER_USERNAME];
}

MapStringString
account::ConfProperties_t::toDetails() const
{
    using namespace DRing::Account;
    MapStringString details;
    // General
    details[ConfProperties::MAILBOX]                    = this->mailbox;
    details[ConfProperties::DTMF_TYPE]                  = this->dtmfType;
    details[ConfProperties::AUTOANSWER]                 = toQString(this->autoAnswer);
    details[ConfProperties::ACTIVE_CALL_LIMIT]          = toQString(this->activeCallLimit);
    details[ConfProperties::HOSTNAME]                   = this->hostname;
    details[ConfProperties::ROUTE]                      = this->routeset;
    details[ConfProperties::PASSWORD]                   = this->password;
    details[ConfProperties::REALM]                      = this->realm;
    details[ConfProperties::RING_DEVICE_ID]             = this->deviceId;
    details[ConfProperties::RING_DEVICE_NAME]           = this->deviceName;
    details[ConfProperties::LOCAL_INTERFACE]            = this->localInterface;
    details[ConfProperties::PUBLISHED_SAMEAS_LOCAL]     = toQString(this->publishedSameAsLocal);
    details[ConfProperties::LOCAL_PORT]                 = toQString(this->localPort);
    details[ConfProperties::PUBLISHED_PORT]             = toQString(this->publishedPort);
    details[ConfProperties::PUBLISHED_ADDRESS]          = this->publishedAddress;
    details[ConfProperties::USER_AGENT]                 = this->userAgent;
    details[ConfProperties::UPNP_ENABLED]               = toQString(this->upnpEnabled);
    details[ConfProperties::HAS_CUSTOM_USER_AGENT]      = toQString(this->hasCustomUserAgent);
    details[ConfProperties::ALLOW_CERT_FROM_HISTORY]    = toQString(this->allowIncoming);
    details[ConfProperties::ALLOW_CERT_FROM_CONTACT]    = toQString(this->allowIncoming);
    details[ConfProperties::ALLOW_CERT_FROM_TRUSTED]    = toQString(this->allowIncoming);
    details[ConfProperties::ARCHIVE_PASSWORD]           = this->archivePassword;
    details[ConfProperties::ARCHIVE_HAS_PASSWORD]       = toQString(this->archiveHasPassword);
    details[ConfProperties::ARCHIVE_PATH]               = this->archivePath;
    details[ConfProperties::ARCHIVE_PIN]                = this->archivePin;
    // ConfProperties::DEVICE_NAME name is set with NewDeviceModel interface
    details[ConfProperties::PROXY_ENABLED]              = toQString(this->proxyEnabled);
    details[ConfProperties::PROXY_SERVER]               = this->proxyServer;
    details[ConfProperties::PROXY_PUSH_TOKEN]           = this->proxyPushToken;
    details[ConfProperties::DHT_PEER_DISCOVERY]         = toQString(this->peerDiscovery);
    details[ConfProperties::ACCOUNT_PEER_DISCOVERY]     = toQString(this->accountDiscovery);
    details[ConfProperties::ACCOUNT_PUBLISH]            = toQString(this->accountPublish);
    // Audio
    details[ConfProperties::Audio::PORT_MAX]            = toQString(this->Audio.audioPortMax);
    details[ConfProperties::Audio::PORT_MIN]            = toQString(this->Audio.audioPortMin);
    // Video
    details[ConfProperties::Video::ENABLED]             = toQString(this->Video.videoEnabled);
    details[ConfProperties::Video::PORT_MAX]            = toQString(this->Video.videoPortMax);
    details[ConfProperties::Video::PORT_MIN]            = toQString(this->Video.videoPortMin);
    // STUN
    details[ConfProperties::STUN::SERVER]               = this->STUN.server;
    details[ConfProperties::STUN::ENABLED]              = toQString(this->STUN.enable);
    // TURN
    details[ConfProperties::TURN::SERVER]               = this->TURN.server;
    details[ConfProperties::TURN::ENABLED]              = toQString(this->TURN.enable);
    details[ConfProperties::TURN::SERVER_UNAME]         = this->TURN.username;
    details[ConfProperties::TURN::SERVER_PWD]           = this->TURN.password;
    details[ConfProperties::TURN::SERVER_REALM]         = this->TURN.realm;
    // Presence
    details[ConfProperties::Presence::SUPPORT_PUBLISH]  = toQString(this->Presence.presencePublishSupported);
    details[ConfProperties::Presence::SUPPORT_SUBSCRIBE] = toQString(this->Presence.presenceSubscribeSupported);
    details[ConfProperties::Presence::ENABLED]          = toQString(this->Presence.presenceEnabled);
    // Ringtone
    details[ConfProperties::Ringtone::PATH]             = this->Ringtone.ringtonePath;
    details[ConfProperties::Ringtone::ENABLED]          = toQString(this->Ringtone.ringtoneEnabled);
    // SRTP
    details[ConfProperties::SRTP::KEY_EXCHANGE]         = this->SRTP.keyExchange == account::KeyExchangeProtocol::NONE? "" : "sdes";
    details[ConfProperties::SRTP::ENABLED]              = toQString(this->SRTP.enable);
    details[ConfProperties::SRTP::RTP_FALLBACK]         = toQString(this->SRTP.rtpFallback);
    // TLS
    details[ConfProperties::TLS::LISTENER_PORT]         = toQString(this->TLS.listenerPort);
    details[ConfProperties::TLS::ENABLED]               = toQString(this->TLS.enable);
    details[ConfProperties::TLS::PORT]                  = toQString(this->TLS.port);
    details[ConfProperties::TLS::CA_LIST_FILE]          = this->TLS.certificateListFile;
    details[ConfProperties::TLS::CERTIFICATE_FILE]      = this->TLS.certificateFile;
    details[ConfProperties::TLS::PRIVATE_KEY_FILE]      = this->TLS.privateKeyFile;
    details[ConfProperties::TLS::PASSWORD]              = this->TLS.password;
    switch (this->TLS.method) {
    case account::TlsMethod::TLSv1:
        details[ConfProperties::TLS::METHOD]            = "TLSv1";
        break;
    case account::TlsMethod::TLSv1_1:
        details[ConfProperties::TLS::METHOD]            = "TLSv1.1";
        break;
    case account::TlsMethod::TLSv1_2:
        details[ConfProperties::TLS::METHOD]            = "TLSv1.2";
        break;
    case account::TlsMethod::DEFAULT:
    default:
        details[ConfProperties::TLS::METHOD]            = "Default";
        break;
    }
    details[ConfProperties::TLS::CIPHERS]               = this->TLS.ciphers;
    details[ConfProperties::TLS::SERVER_NAME]           = this->TLS.serverName;
    details[ConfProperties::TLS::VERIFY_SERVER]         = toQString(this->TLS.verifyServer);
    details[ConfProperties::TLS::VERIFY_CLIENT]         = toQString(this->TLS.verifyClient);
    details[ConfProperties::TLS::REQUIRE_CLIENT_CERTIFICATE] = toQString(this->TLS.requireClientCertificate);
    details[ConfProperties::TLS::NEGOTIATION_TIMEOUT_SEC] = toQString(this->TLS.negotiationTimeoutSec);
    // DHT
    details[ConfProperties::DHT::PORT]                  = toQString(this->DHT.port);
    details[ConfProperties::DHT::PUBLIC_IN_CALLS]       = toQString(this->DHT.PublicInCalls);
    details[ConfProperties::DHT::ALLOW_FROM_TRUSTED]    = toQString(this->DHT.AllowFromTrusted);
    // RingNS
    details[ConfProperties::RingNS::URI]                = this->RingNS.uri;
    details[ConfProperties::RingNS::ACCOUNT]            = this->RingNS.account;
    // Registration
    details[ConfProperties::Registration::EXPIRE]       = toQString(this->Registration.expire);
    // Manager
    details[ConfProperties::MANAGER_URI]                = this->managerUri;
    details[ConfProperties::MANAGER_USERNAME]           = this->managerUsername;

    return details;
}

QString
NewAccountModel::createNewAccount(profile::Type type,
                                  const QString& displayName,
                                  const QString& archivePath,
                                  const QString& password,
                                  const QString& pin,
                                  const QString& uri)
{

    MapStringString details = type == profile::Type::SIP?
                              ConfigurationManager::instance().getAccountTemplate("SIP") :
                              ConfigurationManager::instance().getAccountTemplate("RING");
    using namespace DRing::Account;
    details[ConfProperties::TYPE] = type == profile::Type::SIP? "SIP" : "RING";
    details[ConfProperties::DISPLAYNAME] = displayName;
    details[ConfProperties::ALIAS] = displayName;
    details[ConfProperties::UPNP_ENABLED] = "true";
    details[ConfProperties::ARCHIVE_PASSWORD] = password;
    details[ConfProperties::ARCHIVE_PIN] = pin;
    details[ConfProperties::ARCHIVE_PATH] = archivePath;
    if (type == profile::Type::SIP) {
        details[ConfProperties::USERNAME] = uri;
    }

    QString accountId = ConfigurationManager::instance().addAccount(details);
    return accountId;
}

QString
NewAccountModel::connectToAccountManager(const QString& username,
                                         const QString& password,
                                         const QString& serverUri)
{
    MapStringString details = ConfigurationManager::instance().getAccountTemplate("RING");
    using namespace DRing::Account;
    details[ConfProperties::TYPE] = "RING";
    details[ConfProperties::MANAGER_URI] = serverUri;
    details[ConfProperties::MANAGER_USERNAME] = username;
    details[ConfProperties::ARCHIVE_PASSWORD] = password;

    QString accountId = ConfigurationManager::instance().addAccount(details);
    return accountId;
}

void
NewAccountModel::setTopAccount(const QString& accountId)
{
    bool found = false;
    QString order = {};

    const QStringList accountIds = ConfigurationManager::instance().getAccountList();
    for (auto& id : accountIds)
    {
        if (id == accountId) {
            found = true;
        } else {
            order += id + "/";
        }
    }
    if (found) {
        order = accountId + "/" + order;
    }
    ConfigurationManager::instance().setAccountsOrder(order);
}

QString
NewAccountModel::accountVCard(const QString& accountId, bool compressImage) const
{
    auto account = pimpl_->accounts.find(accountId);
    if (account == pimpl_->accounts.end()) {
        return {};
    }
    auto& accountInfo = account->second.first;
    return authority::storage::vcard::profileToVcard(accountInfo.profileInfo, compressImage);
}

} // namespace lrc

#include "api/moc_newaccountmodel.cpp"
#include "newaccountmodel.moc"