From 8dfc664daca531cef1cb6ce2cf78bd335c0e87be Mon Sep 17 00:00:00 2001
From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
Date: Wed, 6 Mar 2019 13:26:09 -0500
Subject: [PATCH] database: migrate to per account database

Gitlab: #407
Change-Id: I834cf0d216dfd9e6badab8d7aab951b8875c1bd6
---
 CMakeLists.txt                                |    2 +-
 src/api/call.h                                |    2 +-
 src/api/contact.h                             |    7 +
 src/api/contactmodel.h                        |   14 +-
 src/api/interaction.h                         |   96 +-
 src/api/lrc.h                                 |   14 +-
 src/api/newaccountmodel.h                     |   11 +-
 src/api/newcallmodel.h                        |   21 +-
 src/api/profile.h                             |   10 +-
 src/authority/databasehelper.cpp              |  574 --------
 src/authority/storagehelper.cpp               | 1283 +++++++++++++++++
 .../{databasehelper.h => storagehelper.h}     |  294 ++--
 src/avmodel.cpp                               |    4 +-
 src/callbackshandler.cpp                      |   15 +-
 src/callbackshandler.h                        |    4 +
 src/contactmodel.cpp                          |  124 +-
 src/conversationmodel.cpp                     |  436 +++---
 src/database.cpp                              |  446 +++---
 src/database.h                                |  135 +-
 src/datatransfermodel.cpp                     |   10 -
 src/lrc.cpp                                   |   23 +-
 src/newaccountmodel.cpp                       |  263 ++--
 src/newcallmodel.cpp                          |   53 +-
 src/typedefs.h                                |    4 +-
 src/uri.cpp                                   |   21 +-
 src/uri.h                                     |    3 +
 src/vcard.h                                   |    5 +
 27 files changed, 2420 insertions(+), 1454 deletions(-)
 delete mode 100644 src/authority/databasehelper.cpp
 create mode 100644 src/authority/storagehelper.cpp
 rename src/authority/{databasehelper.h => storagehelper.h} (50%)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0c999a6f..12b2573b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -280,7 +280,7 @@ SET( libringclient_LIB_SRCS
   src/conversationmodel.cpp
   src/database.cpp
   src/authority/daemon.cpp
-  src/authority/databasehelper.cpp
+  src/authority/storagehelper.cpp
   src/lrc.cpp
   src/newaccountmodel.cpp
   src/peerdiscoverymodel.cpp
diff --git a/src/api/call.h b/src/api/call.h
index a91e2734..05714e55 100644
--- a/src/api/call.h
+++ b/src/api/call.h
@@ -131,7 +131,7 @@ struct Info
     std::chrono::steady_clock::time_point startTime;
     Status status = Status::INVALID;
     Type type = Type::INVALID;
-    std::string peer;
+    std::string peerUri;
     bool isOutgoing;
     bool audioMuted = false;
     bool videoMuted = false;
diff --git a/src/api/contact.h b/src/api/contact.h
index 76608576..748df035 100644
--- a/src/api/contact.h
+++ b/src/api/contact.h
@@ -33,6 +33,13 @@ namespace api
 namespace contact
 {
 
+/**
+ * @var profileInfo
+ * @var registeredName
+ * @var isTrusted
+ * @var isPresent
+ * @var isBanned
+ */
 struct Info
 {
     profile::Info profileInfo;
diff --git a/src/api/contactmodel.h b/src/api/contactmodel.h
index 6375dd3f..bffbd752 100644
--- a/src/api/contactmodel.h
+++ b/src/api/contactmodel.h
@@ -57,7 +57,7 @@ public:
     const account::Info& owner;
 
     ContactModel(const account::Info& owner,
-                 Database& database,
+                 Database& db,
                  const CallbacksHandler& callbacksHandler,
                  const BehaviorController& behaviorController);
     ~ContactModel();
@@ -85,18 +85,6 @@ public:
      * @return list of banned contacts uris as string
      */
     const std::list<std::string>& getBannedContacts() const;
-    /**
-     * @param  uri
-     * @param  isAccount
-     * @return empty string if no contact, else the uri in db
-     */
-    const std::string getProfileId(const std::string &ur, bool isAccount = false) const;
-    /**
-     * @deprecated use getProfileId
-     * @param  contactUri
-     * @return empty string if no contact, else the uri in db
-     */
-    const std::string getContactProfileId(const std::string& contactUri) const;
     /**
      * @return all contacts for this account.
      */
diff --git a/src/api/interaction.h b/src/api/interaction.h
index d2a5e9fb..40818d1b 100644
--- a/src/api/interaction.h
+++ b/src/api/interaction.h
@@ -36,8 +36,8 @@ enum class Type {
     TEXT,
     CALL,
     CONTACT,
-    OUTGOING_DATA_TRANSFER,
-    INCOMING_DATA_TRANSFER
+    DATA_TRANSFER,
+    COUNT__
 };
 
 static inline const std::string
@@ -50,11 +50,10 @@ to_string(const Type& type)
         return "CALL";
     case Type::CONTACT:
         return "CONTACT";
-    case Type::OUTGOING_DATA_TRANSFER:
-        return "OUTGOING_DATA_TRANSFER";
-    case Type::INCOMING_DATA_TRANSFER:
-        return "INCOMING_DATA_TRANSFER";
+    case Type::DATA_TRANSFER:
+        return "DATA_TRANSFER";
     case Type::INVALID:
+    case Type::COUNT__:
     default:
         return "INVALID";
     }
@@ -69,24 +68,19 @@ to_type(const std::string& type)
         return interaction::Type::CALL;
     else if (type == "CONTACT")
         return interaction::Type::CONTACT;
-    else if (type == "OUTGOING_DATA_TRANSFER")
-        return interaction::Type::OUTGOING_DATA_TRANSFER;
-    else if (type == "INCOMING_DATA_TRANSFER")
-        return interaction::Type::INCOMING_DATA_TRANSFER;
+    else if (type == "DATA_TRANSFER")
+        return interaction::Type::DATA_TRANSFER;
     else
         return interaction::Type::INVALID;
 }
 
-
 enum class Status {
     INVALID,
     UNKNOWN,
     SENDING,
-    FAILED,
-    SUCCEED,
-    READ,
-    UNREAD,
-    TRANSFER_CREATED, /*[jn] mettre à jour les fonctions de conversion */
+    FAILURE,
+    SUCCESS,
+    TRANSFER_CREATED,
     TRANSFER_ACCEPTED,
     TRANSFER_CANCELED,
     TRANSFER_ERROR,
@@ -95,7 +89,8 @@ enum class Status {
     TRANSFER_AWAITING_PEER,
     TRANSFER_AWAITING_HOST,
     TRANSFER_TIMEOUT_EXPIRED,
-    TRANSFER_FINISHED
+    TRANSFER_FINISHED,
+    COUNT__
 };
 
 static inline const std::string
@@ -106,14 +101,10 @@ to_string(const Status& status)
         return "UNKNOWN";
     case Status::SENDING:
         return "SENDING";
-    case Status::FAILED:
-        return "FAILED";
-    case Status::SUCCEED:
-        return "SUCCEED";
-    case Status::READ:
-        return "READ";
-    case Status::UNREAD:
-        return "UNREAD";
+    case Status::FAILURE:
+        return "FAILURE";
+    case Status::SUCCESS:
+        return "SUCCESS";
     case Status::TRANSFER_CREATED:
         return "TRANSFER_CREATED";
     case Status::TRANSFER_ACCEPTED:
@@ -135,6 +126,7 @@ to_string(const Status& status)
     case Status::TRANSFER_FINISHED:
         return "TRANSFER_FINISHED";
     case Status::INVALID:
+    case Status::COUNT__:
     default:
         return "INVALID";
     }
@@ -144,56 +136,60 @@ static inline Status
 to_status(const std::string& status)
 {
     if (status == "UNKNOWN")
-        return interaction::Status::UNKNOWN;
+        return Status::UNKNOWN;
     else if (status == "SENDING")
-        return interaction::Status::SENDING;
-    else if (status == "FAILED")
-        return interaction::Status::FAILED;
-    else if (status == "SUCCEED")
-        return interaction::Status::SUCCEED;
-    else if (status == "READ")
-        return interaction::Status::READ;
-    else if (status == "UNREAD")
-        return interaction::Status::UNREAD;
+        return Status::SENDING;
+    else if (status == "FAILURE")
+        return Status::FAILURE;
+    else if (status == "SUCCESS")
+        return Status::SUCCESS;
     else if (status == "TRANSFER_CREATED")
-        return interaction::Status::TRANSFER_CREATED;
+        return Status::TRANSFER_CREATED;
     else if (status == "TRANSFER_ACCEPTED")
-        return interaction::Status::TRANSFER_ACCEPTED;
+        return Status::TRANSFER_ACCEPTED;
     else if (status == "TRANSFER_CANCELED")
-        return interaction::Status::TRANSFER_CANCELED;
+        return Status::TRANSFER_CANCELED;
     else if (status == "TRANSFER_ERROR")
-        return interaction::Status::TRANSFER_ERROR;
+        return Status::TRANSFER_ERROR;
     else if (status == "TRANSFER_UNJOINABLE_PEER")
-        return interaction::Status::TRANSFER_UNJOINABLE_PEER;
+        return Status::TRANSFER_UNJOINABLE_PEER;
     else if (status == "TRANSFER_ONGOING")
-        return interaction::Status::TRANSFER_ONGOING;
+        return Status::TRANSFER_ONGOING;
     else if (status == "TRANSFER_AWAITING_HOST")
-        return interaction::Status::TRANSFER_AWAITING_HOST;
+        return Status::TRANSFER_AWAITING_HOST;
     else if (status == "TRANSFER_AWAITING_PEER")
-        return interaction::Status::TRANSFER_AWAITING_PEER;
+        return Status::TRANSFER_AWAITING_PEER;
     else if (status == "TRANSFER_TIMEOUT_EXPIRED")
-        return interaction::Status::TRANSFER_TIMEOUT_EXPIRED;
+        return Status::TRANSFER_TIMEOUT_EXPIRED;
     else if (status == "TRANSFER_FINISHED")
-        return interaction::Status::TRANSFER_FINISHED;
+        return Status::TRANSFER_FINISHED;
     else
-        return interaction::Status::INVALID;
+        return Status::INVALID;
 
 }
 
+/**
+ * @var authorUri
+ * @var body
+ * @var timestamp
+ * @var duration
+ * @var type
+ * @var status
+ * @var isRead
+ */
 struct Info
 {
     std::string authorUri;
     std::string body;
     std::time_t timestamp = 0;
+    std::time_t duration = 0;
     Type type = Type::INVALID;
     Status status = Status::INVALID;
+    bool isRead = false;
 };
 
 static inline bool isOutgoing(const Info& interaction) {
-    return (interaction.status != lrc::api::interaction::Status::READ
-    && interaction.status != lrc::api::interaction::Status::UNREAD
-    && interaction.type != lrc::api::interaction::Type::INCOMING_DATA_TRANSFER)
-    || interaction.type == lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER;
+    return interaction.authorUri.empty();
 }
 
 } // namespace interaction
diff --git a/src/api/lrc.h b/src/api/lrc.h
index 71a8ead8..aec00d77 100644
--- a/src/api/lrc.h
+++ b/src/api/lrc.h
@@ -41,7 +41,15 @@ class AVModel;
 
 class LIB_EXPORT Lrc {
 public:
-    Lrc();
+    /**
+     * Construct an Lrc object and optionally invoke callbacks
+     * to control ui informing the user of a possibly lengthy
+     * migration process.
+     * @param willMigrateCb
+     * @param didMigrateCb
+     */
+    Lrc(MigrationCb willMigrateCb = {},
+        MigrationCb didMigrateCb = {});
     ~Lrc();
     /**
      * get a reference on account model.
@@ -77,6 +85,10 @@ public:
      * Can communicate with the daemon via dbus
      */
     static bool dbusIsValid();
+    /**
+     * Connect to debugMessageReceived signal
+     */
+    void subscribeToDebugReceived();
 
     /**
      * Helper: get call list from daemon
diff --git a/src/api/newaccountmodel.h b/src/api/newaccountmodel.h
index c9c0d47c..d178782e 100644
--- a/src/api/newaccountmodel.h
+++ b/src/api/newaccountmodel.h
@@ -53,12 +53,11 @@ class BehaviorController;
 class LIB_EXPORT NewAccountModel : public QObject {
     Q_OBJECT
 public:
-    using AccountInfoMap = std::map<std::string, account::Info>;
-
     NewAccountModel(Lrc& lrc,
-                    Database& database,
                     const CallbacksHandler& callbackHandler,
-                    const api::BehaviorController& behaviorController);
+                    const api::BehaviorController& behaviorController,
+                    MigrationCb& willMigrateCb,
+                    MigrationCb& didMigrateCb);
 
     ~NewAccountModel();
     /**
@@ -173,13 +172,13 @@ public:
      * Set an account to the first position
      */
     void setTopAccount(const std::string& accountId);
+
     /**
-     * Build the vCard for an account
+     * Get the vCard for an account
      * @param id
      * @return vcard of the account
      */
     std::string accountVCard(const std::string& accountId, bool compressImage = true) const;
-    std::string compressedAvatar(const std::string& img) const;
 
 Q_SIGNALS:
     /**
diff --git a/src/api/newcallmodel.h b/src/api/newcallmodel.h
index b5f0b955..dbf8b549 100644
--- a/src/api/newcallmodel.h
+++ b/src/api/newcallmodel.h
@@ -72,11 +72,12 @@ public:
 
     /**
      * Create a new call with a contact
-     * @param  url of the contact to call
+     * @param  uri of the contact to call
      * @param  isAudioOnly, set to false by default
      * @return the call uid created. Empty string is returned if call couldn't be created.
      */
-    std::string createCall(const std::string& url, bool isAudioOnly = false);
+    std::string createCall(const std::string& uri, bool isAudioOnly = false);
+
     /**
      * Get the call from its call id
      * @param  uid
@@ -84,6 +85,7 @@ public:
      * @throw out_of_range exception if not found
      */
     const call::Info& getCall(const std::string& uid) const;
+
     /**
      * Get the call from the peer uri
      * @param  uri
@@ -92,6 +94,7 @@ public:
      * @throw out_of_range exception if not found
      */
     const call::Info& getCallFromURI(const std::string& uri, bool notOver = false) const;
+
     /**
      * Get conference from a peer uri
      * @param  uri
@@ -99,11 +102,13 @@ public:
      * @throw out_of_range exception if not found
      */
     const call::Info& getConferenceFromURI(const std::string& uri) const;
+
     /**
      * @param  callId to test
      * @return true if callId is presend else false.
      */
     bool hasCall(const std::string& callId) const;
+
     /**
      * Send a text message to a SIP call
      * @param callId
@@ -116,64 +121,76 @@ public:
      * @param callId
      */
     void accept(const std::string& callId) const;
+
     /**
      * Hang up a call
      * @param callId
      */
     void hangUp(const std::string& callId) const;
+
     /**
      * Refuse a call
      * @param callId
      */
     void refuse(const std::string& callId) const;
+
     /**
      * Toggle audio record on a call
      * @param callId
      */
     void toggleAudioRecord(const std::string& callId) const;
+
     /**
      * Play DTMF in a call
      * @param callId
      * @param value to play
      */
     void playDTMF(const std::string& callId, const std::string& value) const;
+
     /**
      * Toggle pause on a call
      * @param callId
      */
     void togglePause(const std::string& callId) const;
+
     /**
      * Toggle a media on a call
      * @param callId
      * @param media {AUDIO, VIDEO}
      */
     void toggleMedia(const std::string& callId, const NewCallModel::Media media) const;
+
     /**
      * Not implemented yet
      */
     void setQuality(const std::string& callId, const double quality) const;
+
     /**
      * Blind transfer. Directly transfer a call to a sip number
      * @param callId: the call to transfer
      * @param to: the sip number (for example: "sip:1412")
      */
     void transfer(const std::string& callId, const std::string& to) const;
+
     /**
      * Perform an attended. Transfer a call to another call
      * @param callIdSrc: the call to transfer
      * @param callIdDest: the destination's call
      */
     void transferToCall(const std::string& callIdSrc, const std::string& callIdDest) const;
+
     /**
      * Create a conference from 2 calls.
      * @param callIdA uid of the call A
      * @param callIdB uid of the call B
      */
     void joinCalls(const std::string& callIdA, const std::string& callIdB) const;
+
     /**
      * Not implemented yet
      */
     void removeParticipant(const std::string& callId, const std::string& participant) const;
+
     /**
      * @param  callId
      * @return a human readable call duration (M:ss)
diff --git a/src/api/profile.h b/src/api/profile.h
index 6a571e52..1a1797e3 100644
--- a/src/api/profile.h
+++ b/src/api/profile.h
@@ -35,7 +35,8 @@ enum class Type {
     RING,
     SIP,
     PENDING,
-    TEMPORARY
+    TEMPORARY,
+    COUNT__
 };
 
 static inline const std::string
@@ -51,6 +52,7 @@ to_string(const Type& type)
     case Type::TEMPORARY:
         return "TEMPORARY";
     case Type::INVALID:
+    case Type::COUNT__:
     default:
         return "INVALID";
     }
@@ -71,6 +73,12 @@ to_type(const std::string& type)
         return Type::INVALID;
 }
 
+/**
+ * @var uri
+ * @var avatar
+ * @var alias
+ * @var type
+ */
 struct Info
 {
     std::string uri = "";
diff --git a/src/authority/databasehelper.cpp b/src/authority/databasehelper.cpp
deleted file mode 100644
index 86adaa2d..00000000
--- a/src/authority/databasehelper.cpp
+++ /dev/null
@@ -1,574 +0,0 @@
-/****************************************************************************
- *    Copyright (C) 2017-2019 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>       *
- *                                                                          *
- *   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 "databasehelper.h"
-#include "api/profile.h"
-#include "api/datatransfer.h"
-#include <account_const.h>
-
-#include <datatransfer_interface.h>
-
-namespace lrc
-{
-
-namespace authority
-{
-
-namespace database
-{
-
-std::string
-getProfileId(Database& db,
-            const std::string& accountId,
-            const std::string& isAccount,
-            const std::string& uri)
-{
-    auto accountProfiles = db.select("profile_id", "profiles_accounts",
-                                     "account_id=:account_id AND is_account=:is_account",
-                                     {{":account_id", accountId},
-                                     {":is_account", isAccount}}).payloads;
-    if (accountProfiles.empty() && (isAccount == "true")) {
-        return "";
-    }
-    if (isAccount == "true") return accountProfiles[0];
-
-    // we may have many contacts profiles for one account id,
-    // and need to check uri in addition to account id
-    auto profiles = db.select("id", "profiles", "uri=:uri", {{":uri", uri}}).payloads;
-
-    if (profiles.empty()) return "";
-    std::sort(accountProfiles.begin(), accountProfiles.end());
-    std::sort(profiles.begin(), profiles.end());
-
-    std::vector<std::string> common;
-    std::set_intersection(accountProfiles.begin(), accountProfiles.end(),
-                          profiles.begin(), profiles.end(),
-                          std::back_inserter(common));
-    //if profile exists but not linked with account id,
-    // update profiles_accounts. Except empty uri for SIP accounts
-    if(common.empty()) {
-        if(!uri.empty()) {
-            db.insertInto("profiles_accounts",
-                         {{":profile_id", "profile_id"}, {":account_id", "account_id"},
-                         {":is_account", "is_account"}},
-                         {{":profile_id", profiles[0]}, {":account_id", accountId},
-                         {":is_account", isAccount}});
-        }
-        return profiles[0];
-    }
-    return  common[0];
-}
-
-std::string
-getOrInsertProfile(Database& db,
-                   const std::string& contactUri,
-                   const std::string& accountId,
-                   bool  isAccount,
-                   const std::string& type,
-                   const std::string& alias,
-                   const std::string& avatar)
-{
-    // Check if profile is already present.
-    const std::string isAccountStr = isAccount ? "true" : "false";
-    auto profileAlreadyExists = getProfileId(db, accountId, isAccountStr, contactUri);
-    if (profileAlreadyExists.empty()) {
-       // Doesn't exists, add profile to the database
-       auto row = db.insertInto("profiles",
-       {{":uri", "uri"}, {":alias", "alias"}, {":photo", "photo"}, {":type", "type"},
-       {":status", "status"}},
-       {{":uri", contactUri}, {":alias", alias}, {":photo", avatar}, {":type", type},
-       {":status", "TRUSTED"}});
-
-        if (row == -1) {
-            qDebug() << "contact not added to the database";
-            return "";
-        }
-        // link profile id to account id
-        auto profiles = db.select("profile_id", "profiles_accounts",
-                                  "profile_id=:profile_id AND \
-                                  account_id=:account_id AND  \
-                                  is_account=:is_account",
-                                  {{":profile_id", std::to_string(row)},
-                                  {":account_id", accountId},
-                                  {":is_account", isAccountStr}})
-                                  .payloads;
-
-       if (profiles.empty()) {
-            db.insertInto("profiles_accounts",
-                          {{":profile_id", "profile_id"},
-                          {":account_id", "account_id"},
-                          {":is_account", "is_account"}},
-                          {{":profile_id", std::to_string(row)},
-                          {":account_id", accountId},
-                          {":is_account", isAccountStr}});
-       }
-
-      return std::to_string(row);
-    } else {
-       // Exists, update and retrieve it.
-       if (!avatar.empty() && !alias.empty()) {
-           db.update("profiles",
-                     "alias=:alias, photo=:photo",
-                     {{":alias", alias}, {":photo", avatar}},
-                     "id=:id", {{":id", profileAlreadyExists}});
-       } else if (!avatar.empty()) {
-           db.update("profiles",
-                     "photo=:photo",
-                     {{":photo", avatar}},
-                     "id=:id", {{":id", profileAlreadyExists}});
-       }
-       return profileAlreadyExists;
-    }
-}
-
-std::vector<std::string>
-getConversationsForProfile(Database& db, const std::string& profileId)
-{
-    return db.select("id",
-                     "conversations",
-                     "participant_id=:participant_id",
-                     {{":participant_id", profileId}}).payloads;
-}
-
-std::vector<std::string>
-getPeerParticipantsForConversation(Database& db, const std::string& profileId, const std::string& conversationId)
-{
-    return db.select("participant_id",
-                     "conversations",
-                     "id=:id AND participant_id!=:participant_id",
-                     {{":id", conversationId}, {":participant_id", profileId}}).payloads;
-}
-
-std::string
-getAvatarForProfileId(Database& db, const std::string& profileId)
-{
-    auto returnFromDb = db.select("photo",
-                                  "profiles",
-                                  "id=:id",
-                                  {{":id", profileId}});
-    if (returnFromDb.nbrOfCols == 1 && returnFromDb.payloads.size() >= 1) {
-      auto payloads = returnFromDb.payloads;
-      return payloads[0];
-    }
-    return "";
-}
-
-std::string
-getAliasForProfileId(Database& db, const std::string& profileId)
-{
-    auto returnFromDb = db.select("alias",
-                                  "profiles",
-                                  "id=:id",
-                                  {{":id", profileId}});
-    if (returnFromDb.nbrOfCols == 1 && returnFromDb.payloads.size() >= 1) {
-      auto payloads = returnFromDb.payloads;
-      return payloads[0];
-    }
-    return "";
-}
-
-bool
-profileCouldBeRemoved(Database& db, const std::string& profileId)
-{
-    auto returnFromDb = db.select("account_id",
-                                  "profiles_accounts",
-                                  "profile_id=:profile_id",
-                                  {{":profile_id", profileId}});
-    if (returnFromDb.nbrOfCols == 1 && returnFromDb.payloads.size() >= 1) {
-        return false;
-    }
-    return true;
-}
-
-void
-setAliasForProfileId(Database& db, const std::string& profileId, const std::string& alias)
-{
-    db.update("profiles",
-              "alias=:alias",
-              {{":alias", alias}},
-              "id=:id",
-              {{":id", profileId}});
-}
-
-void
-setAvatarForProfileId(Database& db, const std::string& profileId, const std::string& avatar)
-{
-    db.update("profiles",
-              "photo=:photo",
-              {{":photo", avatar}},
-              "id=:id",
-              {{":id", profileId}});
-}
-
-api::contact::Info
-buildContactFromProfileId(Database& db, const std::string& profileId)
-{
-    auto returnFromDb = db.select("uri, photo, alias, type",
-                                  "profiles",
-                                  "id=:id",
-                                  {{":id", profileId}});
-    if (returnFromDb.nbrOfCols == 4 && returnFromDb.payloads.size() >= 4) {
-      auto payloads = returnFromDb.payloads;
-
-      api::profile::Info profileInfo = {payloads[0], payloads[1], payloads[2], api::profile::to_type(payloads[3])};
-
-      return {profileInfo, "", true, false};
-    }
-    return api::contact::Info();
-}
-
-std::vector<std::string>
-getConversationsBetween(Database& db, const std::string& accountProfile, const std::string& contactProfile)
-{
-    auto conversationsForAccount = getConversationsForProfile(db, accountProfile);
-    std::sort(conversationsForAccount.begin(), conversationsForAccount.end());
-    auto conversationsForContact = getConversationsForProfile(db, contactProfile);
-    std::sort(conversationsForContact.begin(), conversationsForContact.end());
-    std::vector<std::string> common;
-
-    std::set_intersection(conversationsForAccount.begin(), conversationsForAccount.end(),
-                       conversationsForContact.begin(), conversationsForContact.end(),
-                       std::back_inserter(common));
-    return common;
-}
-
-std::string
-beginConversationsBetween(Database& db, const std::string& accountProfile, const std::string& contactProfile, const std::string& firstMessage)
-{
-    // Add conversation between account and profile
-    auto newConversationsId = db.select("IFNULL(MAX(id), 0) + 1",
-                                        "conversations",
-                                        "1=1",
-                                        {}).payloads[0];
-    db.insertInto("conversations",
-                  {{":id", "id"}, {":participant_id", "participant_id"}},
-                  {{":id", newConversationsId}, {":participant_id", accountProfile}});
-    db.insertInto("conversations",
-                  {{":id", "id"}, {":participant_id", "participant_id"}},
-                  {{":id", newConversationsId}, {":participant_id", contactProfile}});
-    // Add first interaction
-    if (!firstMessage.empty())
-        db.insertInto("interactions",
-                      {{":account_id", "account_id"}, {":author_id", "author_id"},
-                      {":conversation_id", "conversation_id"}, {":timestamp", "timestamp"},
-                      {":body", "body"}, {":type", "type"},
-                      {":status", "status"}},
-                      {{":account_id", accountProfile}, {":author_id", accountProfile},
-                      {":conversation_id", newConversationsId},
-                      {":timestamp", std::to_string(std::time(nullptr))},
-                      {":body", firstMessage}, {":type", "CONTACT"},
-                      {":status", "SUCCEED"}});
-    return newConversationsId;
-}
-
-void
-getHistory(Database& db, api::conversation::Info& conversation)
-{
-    auto accountProfile = getProfileId(db, conversation.accountId, "true");
-    auto interactionsResult = db.select("id, author_id, body, timestamp, type, status",
-                                    "interactions",
-                                    "conversation_id=:conversation_id AND account_id=:account_id",
-                                    {{":conversation_id", conversation.uid}, {":account_id", accountProfile}});
-    if (interactionsResult.nbrOfCols == 6) {
-        auto payloads = interactionsResult.payloads;
-        for (decltype(payloads.size()) i = 0; i < payloads.size(); i += 6) {
-            auto msg = api::interaction::Info({payloads[i + 1], payloads[i + 2],
-                                         std::stoi(payloads[i + 3]),
-                                         api::interaction::to_type(payloads[i + 4]),
-                                         api::interaction::to_status(payloads[i + 5])});
-            conversation.interactions.emplace(std::stoull(payloads[i]), std::move(msg));
-            conversation.lastMessageUid = std::stoull(payloads[i]);
-        }
-    }
-}
-
-int
-addMessageToConversation(Database& db,
-                         const std::string& accountProfile,
-                         const std::string& conversationId,
-                         const api::interaction::Info& msg)
-{
-    return db.insertInto("interactions",
-                          {{":account_id", "account_id"}, {":author_id", "author_id"},
-                          {":conversation_id", "conversation_id"}, {":timestamp", "timestamp"},
-                          {":body", "body"}, {":type", "type"},
-                          {":status", "status"}},
-                          {{":account_id", accountProfile}, {":author_id", msg.authorUri},
-                          {":conversation_id", conversationId},
-                          {":timestamp", std::to_string(msg.timestamp)},
-                          {":body", msg.body}, {":type", to_string(msg.type)},
-                          {":status", to_string(msg.status)}});
-}
-
-int
-addDataTransferToConversation(Database& db,
-                              const std::string& accountProfileId,
-                              const std::string& conversationId,
-                              const api::datatransfer::Info& infoFromDaemon)
-{
-    auto peerProfileId = getProfileId(db, infoFromDaemon.accountId, "false",
-        infoFromDaemon.peerUri);
-
-    return db.insertInto("interactions", {
-            {":account_id", "account_id"},
-            {":author_id", "author_id"},
-            {":conversation_id", "conversation_id"},
-            {":timestamp", "timestamp"},
-            {":body", "body"},
-            {":type", "type"},
-            {":status", "status"}
-        }, {
-            {":account_id", accountProfileId},
-            {":author_id", infoFromDaemon.isOutgoing? accountProfileId : peerProfileId},
-            {":conversation_id", conversationId},
-            {":timestamp", std::to_string(std::time(nullptr))},
-            {":body", infoFromDaemon.path},
-            {":type", infoFromDaemon.isOutgoing ?
-                    "OUTGOING_DATA_TRANSFER" :
-                    "INCOMING_DATA_TRANSFER"},
-            {":status", "TRANSFER_CREATED"}
-        });
-}
-
-int
-addOrUpdateMessage(Database& db,
-                         const std::string& accountProfile,
-                         const std::string& conversationId,
-                         const api::interaction::Info& msg,
-                         const std::string& daemonId)
-{
-    // Check if profile is already present.
-    auto msgAlreadyExists = db.select("id",
-                                      "interactions",
-                                      "daemon_id=:daemon_id",
-                                       {{":daemon_id", daemonId}});
-    if (msgAlreadyExists.payloads.empty()) {
-        return db.insertInto("interactions",
-                              {{":account_id", "account_id"}, {":author_id", "author_id"},
-                              {":conversation_id", "conversation_id"}, {":timestamp", "timestamp"},
-                              {":body", "body"}, {":type", "type"}, {":daemon_id", "daemon_id"},
-                              {":status", "status"}},
-                              {{":account_id", accountProfile}, {":author_id", msg.authorUri},
-                              {":conversation_id", conversationId},
-                              {":timestamp", std::to_string(msg.timestamp)},
-                              {":body", msg.body}, {":type", to_string(msg.type)}, {":daemon_id", daemonId},
-                              {":status", to_string(msg.status)}});
-    } else {
-        // already exists
-        db.update("interactions",
-                  "body=:body",
-                  {{":body", msg.body}},
-                  "daemon_id=:daemon_id",
-                   {{":daemon_id", daemonId}});
-        return std::stoi(msgAlreadyExists.payloads[0]);
-    }
-
-}
-
-void addDaemonMsgId(Database& db,
-                    const std::string& interactionId,
-                    const std::string& daemonId)
-{
-    db.update("interactions", "daemon_id=:daemon_id",
-              {{":daemon_id", daemonId}},
-              "id=:id", {{":id", interactionId}});
-}
-
-std::string getDaemonIdByInteractionId(Database& db, const std::string& id)
-{
-    auto ids = db.select("daemon_id", "interactions", "id=:id", {{":id", id}}).payloads;
-    return ids.empty() ? "" : ids[0];
-}
-
-std::string getInteractionIdByDaemonId(Database& db, const std::string& id)
-{
-    auto ids = db.select("id", "interactions", "daemon_id=:daemon_id", {{":daemon_id", id}}).payloads;
-    return ids.empty() ? "" : ids[0];
-}
-
-void updateInteractionBody(Database& db, unsigned int id,
-                           const std::string& newBody)
-{
-    db.update("interactions", "body=:body",
-              {{":body", newBody}},
-              "id=:id", {{":id", std::to_string(id)}});
-}
-
-void updateInteractionStatus(Database& db, unsigned int id,
-                             api::interaction::Status newStatus)
-{
-    db.update("interactions", "status=:status",
-              {{":status", api::interaction::to_string(newStatus)}},
-              "id=:id", {{":id", std::to_string(id)}});
-}
-
-std::string
-conversationIdFromInteractionId(Database& db, unsigned int interactionId)
-{
-    auto result = db.select("conversation_id",
-                            "interactions",
-                            "id=:interaction_id",
-                            {{":interaction_id", std::to_string(interactionId)}});
-    if (result.nbrOfCols == 1) {
-        auto payloads = result.payloads;
-        return payloads[0];
-    }
-
-    return {};
-}
-
-void clearHistory(Database& db,
-                  const std::string& conversationId)
-{
-    db.deleteFrom("interactions", "conversation_id=:id", {{":id", conversationId}});
-}
-
-void clearInteractionFromConversation(Database& db,
-                                      const std::string& conversationId,
-                                      const uint64_t& interactionId)
-{
-    db.deleteFrom("interactions", "conversation_id=:conv_id AND id=:int_id",
-                 {{":conv_id", conversationId}, {":int_id", std::to_string(interactionId)}});
-}
-
-void clearAllHistoryFor(Database& db, const std::string& accountId)
-{
-    auto profileId = getProfileId(db, accountId, "true");
-
-    if (profileId.empty())
-        return;
-
-    db.deleteFrom("interactions", "account_id=:account_id", {{":account_id", profileId}});
-}
-
-void
-removeContact(Database& db, const std::string& contactUri, const std::string& accountId)
-{
-    // Get profile for contact
-    auto contactId = getProfileId(db, accountId, "false", contactUri);
-    if (contactId.empty()) return; // No profile
-    auto accountProfileId = getProfileId(db, accountId, "true");
-    // Get common conversations
-    auto conversations = getConversationsBetween(db, accountProfileId, contactId);
-    // Remove conversations + interactions
-    for (const auto& conversationId: conversations) {
-        // Remove conversation
-        db.deleteFrom("conversations", "id=:id", {{":id", conversationId}});
-        // clear History
-        db.deleteFrom("interactions", "conversation_id=:id", {{":id", conversationId}});
-    }
-    // Get conversations for this contact.
-    conversations = getConversationsForProfile(db, contactId);
-    if (conversations.empty()) {
-        // Delete profile
-        db.deleteFrom("profiles_accounts",
-        "profile_id=:profile_id AND account_id=:account_id AND is_account=:is_account",
-        {{":profile_id", contactId},
-        {":account_id", accountId},
-        {":is_account", "false"}});
-        if (profileCouldBeRemoved(db, contactId))
-        db.deleteFrom("profiles", "id=:id", {{":id", contactId}});
-    }
-}
-
-void
-removeAccount(Database& db, const std::string& accountId)
-{
-    auto accountProfileId = database::getProfileId(db, accountId, "true");
-    auto conversationsForAccount = getConversationsForProfile(db, accountProfileId);
-    for (const auto& convId: conversationsForAccount) {
-        auto peers = getPeerParticipantsForConversation(db, accountProfileId, convId);
-        db.deleteFrom("conversations", "id=:id", {{":id", convId}});
-        db.deleteFrom("interactions", "conversation_id=:id", {{":id", convId}});
-        for (const auto& peerId: peers) {
-            auto otherConversationsForProfile = getConversationsForProfile(db, peerId);
-            if (otherConversationsForProfile.empty()) {
-                db.deleteFrom("profiles_accounts",
-                "profile_id=:profile_id AND account_id=:account_id AND is_account=:is_account",
-                {{":profile_id", peerId},
-                {":account_id", accountId},
-                {":is_account", "false"}});
-                if (profileCouldBeRemoved(db, peerId)) {
-                    db.deleteFrom("profiles", "id=:id", {{":id", peerId}});
-                }
-            }
-        }
-    }
-    db.deleteFrom("profiles_accounts",
-    "profile_id=:profile_id AND account_id=:account_id AND is_account=:is_account",
-    {{":profile_id", accountProfileId},
-    {":account_id", accountId},
-    {":is_account", "true"}});
-    db.deleteFrom("profiles", "id=:id", {{":id", accountProfileId}});
-}
-
-void
-addContact(Database& db, const std::string& contactUri, const std::string& accountId)
-{
-    // Get profile for contact
-    auto row = getOrInsertProfile(db, contactUri, accountId, false, "", "");
-    if (row.empty()) {
-        qDebug() << "database::addContact, no profile for contact. abort";
-        return;
-    }
-    // Get profile of the account linked
-    auto accountProfileId = getProfileId(db, accountId, "true");
-    // Get if conversation exists
-    auto common = getConversationsBetween(db, accountProfileId, row);
-    if (common.empty()) {
-        // conversations doesn't exists, start it.
-        beginConversationsBetween(db, accountProfileId, row);
-    }
-}
-
-int
-countUnreadFromInteractions(Database& db, const std::string& conversationId)
-{
-    return db.count("status", "interactions", "status=:status AND conversation_id=:id",
-           {{":status", "UNREAD"}, {":id", conversationId}});
-}
-
-void
-deleteObsoleteHistory(Database& db, long int date)
-{
-    db.deleteFrom("interactions", "timestamp<=:date", {{":date", std::to_string(date)}});
-}
-
-uint64_t
-getLastTimestamp(Database& db)
-{
-    auto timestamps = db.select("MAX(timestamp)", "interactions", "1=1", {}).payloads;
-    auto result = std::time(nullptr);
-    try {
-        if (!timestamps.empty() && !timestamps[0].empty()) {
-            result = std::stoull(timestamps[0]);
-        }
-    } catch (const std::out_of_range& e) {
-        qDebug() << "database::getLastTimestamp, stoull throws an out_of_range exception: " << e.what();
-    } catch (const std::invalid_argument& e) {
-        qDebug() << "database::getLastTimestamp, stoull throws an invalid_argument exception: " << e.what();
-    }
-    return result;
-}
-
-} // namespace database
-
-} // namespace authority
-
-} // namespace lrc
diff --git a/src/authority/storagehelper.cpp b/src/authority/storagehelper.cpp
new file mode 100644
index 00000000..2cd21563
--- /dev/null
+++ b/src/authority/storagehelper.cpp
@@ -0,0 +1,1283 @@
+/****************************************************************************
+ *    Copyright (C) 2017-2019 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 "storagehelper.h"
+
+#include "api/profile.h"
+#include "api/datatransfer.h"
+#include "uri.h"
+#include "vcard.h"
+
+#include <account_const.h>
+#include <datatransfer_interface.h>
+
+#include <QImage>
+#include <QBuffer>
+#include <QJsonObject>
+#include <QJsonDocument>
+
+#include <fstream>
+#include <thread>
+#include <cstring>
+
+namespace lrc
+{
+
+namespace authority
+{
+
+namespace storage
+{
+
+QString getPath()
+{
+    QDir dataDir(QStandardPaths::writableLocation(
+        QStandardPaths::AppLocalDataLocation));
+    // Avoid to depends on the client name.
+    dataDir.cdUp();
+    return dataDir.absolutePath() + "/jami/";
+}
+
+std::string
+prepareUri(const std::string& uri, api::profile::Type type)
+{
+    URI uriObject(QString::fromStdString(uri));
+    switch (type) {
+    case api::profile::Type::SIP:
+        return uriObject.format(URI::Section::USER_INFO | URI::Section::HOSTNAME)
+            .toStdString();
+        break;
+    case api::profile::Type::RING:
+        return uriObject.format(URI::Section::USER_INFO)
+            .toStdString();
+        break;
+    case api::profile::Type::INVALID:
+    case api::profile::Type::PENDING:
+    case api::profile::Type::TEMPORARY:
+    case api::profile::Type::COUNT__:
+    default:
+        return uri;
+    }
+}
+
+std::string
+getFormattedCallDuration(const std::time_t duration)
+{
+    if (duration == 0) return {};
+    std::string formattedString;
+    auto minutes = duration / 60;
+    auto seconds = duration % 60;
+    if (minutes > 0) {
+        formattedString += std::to_string(minutes) + ":";
+        if (formattedString.length() == 2) {
+            formattedString = "0" + formattedString;
+        }
+    } else {
+        formattedString += "00:";
+    }
+    if (seconds < 10) formattedString += "0";
+    formattedString += std::to_string(seconds);
+    return formattedString;
+}
+
+std::string
+getCallInteractionString(const std::string& authorUri,
+                         const std::time_t& duration)
+{
+    if (duration < 0) {
+        if (authorUri.empty()) {
+            return "📞 " + QObject::tr("Outgoing call").toStdString();
+        } else {
+            return "📞 " + QObject::tr("Incoming call").toStdString();
+        }
+    } else if (authorUri.empty()) {
+        if (duration) {
+            return "📞 " + QObject::tr("Outgoing call").toStdString()
+                    + " - " + getFormattedCallDuration(duration);
+        } else {
+            return "🕽 " + QObject::tr("Missed outgoing call").toStdString();
+        }
+    } else {
+        if (duration) {
+            return "📞 " + QObject::tr("Incoming call").toStdString()
+                    + " - " + getFormattedCallDuration(duration);
+        } else {
+            return "🕽 " + QObject::tr("Missed incoming call").toStdString();
+        }
+    }
+}
+
+std::string
+getContactInteractionString(const std::string& authorUri,
+                            const api::interaction::Status& status)
+{
+    if (authorUri.empty()) {
+        return QObject::tr("Contact added").toStdString();
+    } else {
+        if (status == api::interaction::Status::UNKNOWN) {
+            return QObject::tr("Invitation received").toStdString();
+        } else if (status == api::interaction::Status::SUCCESS) {
+            return QObject::tr("Invitation accepted").toStdString();
+        }
+    }
+    return {};
+}
+
+namespace vcard
+{
+std::string compressedAvatar(const std::string& image);
+void setProfile(const std::string& accountId,
+                const api::profile::Info& profileInfo,
+                const bool overwrite,
+                const bool isPeer);
+
+std::string
+compressedAvatar(const std::string& image)
+{
+    QImage qimage;
+    const bool ret = qimage.loadFromData(QByteArray::fromBase64(image.c_str()), 0);
+    if (!ret) {
+        qDebug() << "vCard image loading failed";
+        return image;
+    }
+    QByteArray bArray;
+    QBuffer buffer(&bArray);
+    buffer.open(QIODevice::WriteOnly);
+    qimage.scaled({ 128,128 }).save(&buffer, "JPEG", 90);
+    auto b64Img = bArray.toBase64().trimmed();
+    return std::string(b64Img.constData(), b64Img.length());
+}
+
+std::string
+profileToVcard(const api::profile::Info& profileInfo,
+               bool compressImage)
+{
+    using namespace api;
+    bool compressedImage = std::strncmp(profileInfo.avatar.c_str(), "/9j/", 4) == 0;
+    if (compressedImage && !compressImage) {
+        compressImage = false;
+    }
+    std::string vCardStr = vCard::Delimiter::BEGIN_TOKEN;
+    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    vCardStr += vCard::Property::VERSION;
+    vCardStr += ":2.1";
+    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    vCardStr += vCard::Property::FORMATTED_NAME;
+    vCardStr += ":";
+    vCardStr += profileInfo.alias;
+    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    if (profileInfo.type == profile::Type::RING) {
+        vCardStr += vCard::Property::TELEPHONE;
+        vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
+        vCardStr += "other:ring:";
+        vCardStr += profileInfo.uri;
+        vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    } else {
+        vCardStr += vCard::Property::TELEPHONE;
+        vCardStr += ":";
+        vCardStr += profileInfo.uri;
+        vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    }
+    vCardStr += vCard::Property::PHOTO;
+    vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
+    vCardStr += vCard::Property::BASE64;
+    vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
+    if (compressImage) {
+        vCardStr += vCard::Property::TYPE_JPEG;
+        vCardStr += ":";
+        vCardStr += compressedImage ? profileInfo.avatar : compressedAvatar(profileInfo.avatar);
+    } else {
+        vCardStr += compressedImage ? vCard::Property::TYPE_JPEG : vCard::Property::TYPE_PNG;
+        vCardStr += ":";
+        vCardStr += profileInfo.avatar;
+    }
+    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    vCardStr += vCard::Delimiter::END_TOKEN;
+    return vCardStr;
+}
+
+void
+setProfile(const std::string& accountId,
+           const api::profile::Info& profileInfo,
+           const bool isPeer)
+{
+    auto vcard = vcard::profileToVcard(profileInfo);
+    auto accountLocalPath = getPath() + QString::fromStdString(accountId) + "/";
+    QString filePath;
+    if (isPeer) {
+        filePath = accountLocalPath + "profiles/" + QString::fromStdString(profileInfo.uri) + ".vcf";
+    } else {
+        filePath = accountLocalPath + "profile" + ".vcf";
+    }
+    QFile file(filePath);
+    if (!file.open(QIODevice::WriteOnly)) {
+        qWarning() << "Can't open file: " << filePath;
+        return;
+    }
+    QTextStream(&file) << QString::fromStdString(vcard);
+}
+} // namespace vcard
+
+std::vector<std::string>
+getConversationsWithPeer(Database& db, const std::string& participant_uri)
+{
+    return db.select("id",
+                     "conversations",
+                     "participant=:participant",
+                     {{":participant", participant_uri}}).payloads;
+}
+
+std::vector<std::string>
+getPeerParticipantsForConversation(Database& db, const std::string& conversationId)
+{
+    return db.select("participant",
+                     "conversations",
+                     "id=:id",
+                     { {":id", conversationId} }).payloads;
+}
+
+void
+createOrUpdateProfile(const std::string & accountId,
+              const api::profile::Info & profileInfo,
+              const bool isPeer)
+{
+    if (isPeer) {
+        auto contact = storage::buildContactFromProfile(accountId, profileInfo.uri, profileInfo.type);
+        if (!profileInfo.alias.empty()) contact.profileInfo.alias = profileInfo.alias;
+        if (!profileInfo.avatar.empty()) contact.profileInfo.avatar = profileInfo.avatar;
+        vcard::setProfile(accountId, contact.profileInfo, isPeer);
+        return;
+    }
+    vcard::setProfile(accountId, profileInfo, isPeer);
+}
+
+std::string
+getAccountAvatar(const std::string& accountId)
+{
+    auto accountLocalPath = getPath() + QString::fromStdString(accountId) + "/";
+    QString filePath;
+    filePath = accountLocalPath + "profile.vcf";
+    QFile file(filePath);
+    if (!file.open(QIODevice::ReadOnly)) {
+        qWarning() << "Can't open file: " << filePath;
+        return {};
+    }
+    QTextStream in(&file);
+    const auto vCard = lrc::vCard::utils::toHashMap(in.readAll().toUtf8());
+    const auto photo = (vCard.find(vCard::Property::PHOTO_PNG) == vCard.end()) ?
+        vCard[vCard::Property::PHOTO_JPEG] : vCard[vCard::Property::PHOTO_PNG];
+    return photo.toStdString();
+}
+
+api::contact::Info
+buildContactFromProfile(const std::string & accountId,
+                        const std::string& peer_uri,
+                        const api::profile::Type& type)
+{
+    lrc::api::profile::Info profileInfo;
+    profileInfo.uri = peer_uri;
+    profileInfo.type = type;
+    auto accountLocalPath = getPath() + QString::fromStdString(accountId) + "/";
+    QString filePath;
+    filePath = accountLocalPath + "profiles/" + QString::fromStdString(peer_uri) + ".vcf";
+    QFile file(filePath);
+    if (!file.open(QIODevice::ReadOnly)) {
+        qWarning() << "Can't open file: " << filePath;
+        return { profileInfo, "", true, false };
+    }
+    QTextStream in(&file);
+    QByteArray vcard = in.readAll().toUtf8();
+    const auto vCard = lrc::vCard::utils::toHashMap(vcard);
+    const auto alias = vCard[vCard::Property::FORMATTED_NAME];
+    const auto photo = (vCard.find(vCard::Property::PHOTO_PNG) == vCard.end()) ?
+        vCard[vCard::Property::PHOTO_JPEG] : vCard[vCard::Property::PHOTO_PNG];
+
+    profileInfo.avatar = photo.toStdString();
+    profileInfo.alias = alias.toStdString();
+    return { profileInfo, "", true, false };
+}
+
+std::vector<std::string> getAllConversations(Database & db)
+{
+    return db.select("id", "conversations", {}, {}).payloads;
+}
+
+std::vector<std::string>
+getConversationsBetween(Database& db, const std::string& peer1_uri, const std::string& peer2_uri)
+{
+    auto conversationsForPeer1 = getConversationsWithPeer(db, peer1_uri);
+    std::sort(conversationsForPeer1.begin(), conversationsForPeer1.end());
+    auto conversationsForPeer2 = getConversationsWithPeer(db, peer2_uri);
+    std::sort(conversationsForPeer2.begin(), conversationsForPeer2.end());
+    std::vector<std::string> common;
+
+    std::set_intersection(conversationsForPeer1.begin(), conversationsForPeer1.end(),
+                          conversationsForPeer2.begin(), conversationsForPeer2.end(),
+                          std::back_inserter(common));
+    return common;
+}
+
+std::string
+beginConversationWithPeer(Database& db, const std::string& peer_uri, const bool isOutgoing)
+{
+    // Add conversation between account and profile
+    auto newConversationsId = db.select("IFNULL(MAX(id), 0) + 1",
+                                        "conversations",
+                                        "1=1",
+                                        {}).payloads[0];
+    db.insertInto("conversations",
+                  {{":id", "id"}, {":participant", "participant"}},
+                  {{":id", newConversationsId}, {":participant", peer_uri}});
+    api::interaction::Info msg{
+        isOutgoing ? "" : peer_uri,
+        {},
+        std::time(nullptr),
+        0,
+        api::interaction::Type::CONTACT,
+        isOutgoing ? api::interaction::Status::SUCCESS : api::interaction::Status::UNKNOWN,
+        isOutgoing
+    };
+    // Add first interaction
+    addMessageToConversation(db, newConversationsId, msg);
+    return newConversationsId;
+}
+
+void
+getHistory(Database& db, api::conversation::Info& conversation)
+{
+    auto interactionsResult = db.select("id, author, body, timestamp, type, status, is_read, extra_data",
+                                        "interactions",
+                                        "conversation=:conversation",
+                                        {{":conversation", conversation.uid}});
+    auto nCols = 8;
+    if (interactionsResult.nbrOfCols == nCols) {
+        auto payloads = interactionsResult.payloads;
+        for (decltype(payloads.size()) i = 0; i < payloads.size(); i += nCols) {
+            std::string durationString;
+            auto extra_data_str = QString::fromStdString(payloads[i + 7]);
+            if (!extra_data_str.isEmpty()) {
+                auto jsonData = JSONFromString(extra_data_str);
+                durationString = readJSONValue(jsonData, "duration").toStdString();
+            }
+            auto body = payloads[i + 2];
+            auto type = api::interaction::to_type(payloads[i + 4]);
+            std::time_t duration = durationString.empty() ? 0 : std::stoi(durationString);
+            auto status = api::interaction::to_status(payloads[i + 5]);
+            if (type == api::interaction::Type::CALL) {
+                body = getCallInteractionString(payloads[i + 1], duration);
+            } else if(type == api::interaction::Type::CONTACT) {
+                body = getContactInteractionString(payloads[i + 1], status);
+            }
+            auto msg = api::interaction::Info({
+                    payloads[i + 1],
+                    body,
+                    std::stoi(payloads[i + 3]),
+                    duration,
+                    type,
+                    status,
+                    (payloads[i + 6] == "1" ? true : false)
+                });
+            conversation.interactions.emplace(std::stoull(payloads[i]), std::move(msg));
+            conversation.lastMessageUid = std::stoull(payloads[i]);
+        }
+    }
+}
+
+int
+addMessageToConversation(Database& db,
+                         const std::string& conversationId,
+                         const api::interaction::Info& msg)
+{
+    return db.insertInto("interactions", {
+            { ":author", "author" },
+            { ":conversation", "conversation" },
+            { ":timestamp", "timestamp" },
+            { ":body", "body" },
+            { ":type", "type" },
+            { ":status", "status" },
+            { ":is_read", "is_read" }
+        }, {
+            { ":author", msg.authorUri},
+            { ":conversation", conversationId},
+            { ":timestamp", std::to_string(msg.timestamp)},
+            { ":body", msg.body},
+            { ":type", to_string(msg.type)},
+            { ":status", to_string(msg.status)},
+            { ":is_read", msg.isRead ? "1" : "0" }
+        });
+}
+
+int
+addOrUpdateMessage(Database& db,
+                   const std::string& conversationId,
+                   const api::interaction::Info& msg,
+                   const std::string& daemonId)
+{
+    // Check if profile is already present.
+    auto msgAlreadyExists = db.select("id",
+                                      "interactions",
+                                      "author=:author AND daemon_id=:daemon_id",
+                                      { {":author", msg.authorUri},
+                                      { ":daemon_id", daemonId } }).payloads;
+    if (msgAlreadyExists.empty()) {
+        return db.insertInto("interactions", {
+                {":author", "author"},
+                {":conversation", "conversation"},
+                {":timestamp", "timestamp"},
+                {":body", "body"},
+                {":type", "type"},
+                {":status", "status"},
+                {":daemon_id", "daemon_id"}
+            }, {
+                {":author", msg.authorUri.empty() ? "" : msg.authorUri},
+                {":conversation", conversationId},
+                {":timestamp", std::to_string(msg.timestamp)},
+                {msg.body.empty() ? "" : ":body", msg.body},
+                {":type", to_string(msg.type)},
+                {daemonId.empty() ? "" : ":daemon_id", daemonId},
+                {":status", to_string(msg.status)}
+            });
+    } else {
+        // already exists @ id(msgAlreadyExists[0])
+        auto id = msgAlreadyExists[0];
+        std::string extra_data;
+        if (msg.type == api::interaction::Type::CALL) {
+            auto duration = std::max(msg.duration, static_cast<std::time_t>(0));
+            auto extra_data_str = getInteractionExtraDataById(db, id);
+            auto extra_data_JSON = JSONFromString(QString::fromStdString(extra_data_str));
+            writeJSONValue(extra_data_JSON, "duration", QString::number(duration));
+            extra_data = stringFromJSON(extra_data_JSON).toStdString();
+        }
+        db.update("interactions",
+                  { "body=:body, extra_data=:extra_data" },
+                  { {msg.body.empty() ? "" : ":body", msg.body},
+                  { extra_data.empty() ? "" : ":extra_data", extra_data } },
+                  "id=:id", { {":id", id} });
+        return std::stoi(id);
+    }
+
+}
+int
+addDataTransferToConversation(Database& db,
+                              const std::string& conversationId,
+                              const api::datatransfer::Info& infoFromDaemon)
+{
+    return db.insertInto("interactions", {
+            {":author", "author"},
+            {":conversation", "conversation"},
+            {":timestamp", "timestamp"},
+            {":body", "body"},
+            {":type", "type"},
+            {":status", "status"},
+            {":is_read", "is_read"}
+        }, {
+            {":author", infoFromDaemon.isOutgoing ? "" : infoFromDaemon.peerUri},
+            {":conversation", conversationId},
+            {":timestamp", std::to_string(std::time(nullptr))},
+            {":body", infoFromDaemon.path},
+            {":type", infoFromDaemon.isOutgoing ?
+                    "DATA_TRANSFER" :
+                    "DATA_TRANSFER"},
+            {":status", "TRANSFER_CREATED"},
+            {":is_read", "0"}
+        });
+}
+
+void addDaemonMsgId(Database& db,
+                    const std::string& interactionId,
+                    const std::string& daemonId)
+{
+    db.update("interactions",
+              "daemon_id=:daemon_id",
+              {{":daemon_id", daemonId}},
+              "id=:id", {{":id", interactionId}});
+}
+
+std::string getDaemonIdByInteractionId(Database& db, const std::string& id)
+{
+    auto ids = db.select("daemon_id",
+                         "interactions",
+                         "id=:id",
+                         {{":id", id}}).payloads;
+    return ids.empty() ? "" : ids[0];
+}
+
+std::string getInteractionIdByDaemonId(Database& db, const std::string& daemon_id)
+{
+    auto ids = db.select("id",
+                         "interactions",
+                         "daemon_id=:daemon_id",
+                         {{":daemon_id", daemon_id}}).payloads;
+    return ids.empty() ? "" : ids[0];
+}
+
+std::string getInteractionExtraDataById(Database& db, const std::string& id,
+                                        const std::string& key)
+{
+    auto extra_datas = db.select("extra_data",
+                                 "interactions",
+                                 "id=:id",
+                                 { {":id", id} }).payloads;
+    if (key.empty()) {
+        return extra_datas.empty() ? "" : extra_datas[0];
+    }
+    std::string value;
+    auto extra_data_str = QString::fromStdString(extra_datas[0]);
+    if (!extra_data_str.isEmpty()) {
+        value = readJSONValue(JSONFromString(extra_data_str), QString::fromStdString(key))
+            .toStdString();
+    }
+    return value;
+}
+
+void updateInteractionBody(Database& db, unsigned int id,
+                           const std::string& newBody)
+{
+    db.update("interactions", "body=:body",
+              {{":body", newBody}},
+              "id=:id", {{":id", std::to_string(id)}});
+}
+
+void updateInteractionStatus(Database& db, unsigned int id,
+                             api::interaction::Status newStatus)
+{
+    db.update("interactions",
+              { "status=:status" },
+              {{":status", api::interaction::to_string(newStatus)}},
+              "id=:id", {{":id", std::to_string(id)}});
+}
+
+void setInteractionRead(Database& db, unsigned int id)
+{
+    db.update("interactions",
+              { "is_read=:is_read" },
+              { {":is_read", "1"} },
+              "id=:id", { {":id", std::to_string(id)} });
+}
+
+std::string
+conversationIdFromInteractionId(Database& db, unsigned int interactionId)
+{
+    auto result = db.select("conversation",
+                            "interactions",
+                            "id=:id",
+                            {{":id", std::to_string(interactionId)}});
+    if (result.nbrOfCols == 1 && result.payloads.size()) {
+        return result.payloads[0];
+    }
+    return {};
+}
+
+void clearHistory(Database& db,
+                  const std::string& conversationId)
+{
+    db.deleteFrom("interactions",
+                  "conversation=:conversation",
+                  {{":conversation", conversationId}});
+}
+
+void clearInteractionFromConversation(Database& db,
+                                      const std::string& conversationId,
+                                      const uint64_t& interactionId)
+{
+    db.deleteFrom("interactions",
+                  "conversation=:conversation AND id=:id",
+                  {{":conversation", conversationId},
+                  {":id", std::to_string(interactionId)}});
+}
+
+void clearAllHistory(Database& db)
+{
+    db.truncateTable("interactions");
+}
+
+void
+deleteObsoleteHistory(Database& db, long int date)
+{
+    db.deleteFrom("interactions", "timestamp<=:date", { {":date", std::to_string(date)} });
+}
+
+void
+removeContact(Database& db, const std::string& contactUri)
+{
+    // Get common conversations
+    auto conversations = getConversationsWithPeer(db, contactUri);
+    // Remove conversations + interactions
+    for (const auto& conversationId: conversations) {
+        // Remove conversation
+        db.deleteFrom("conversations", "id=:id", {{":id", conversationId}});
+        // clear History
+        db.deleteFrom("interactions", "conversation=:id", {{":id", conversationId}});
+    }
+}
+
+void
+removeAccount(const std::string& accountId)
+{
+    auto accountLocalPath = getPath() + QString::fromStdString(accountId) + "/";
+    QDir dir(accountLocalPath);
+    dir.removeRecursively();
+}
+
+int
+countUnreadFromInteractions(Database& db, const std::string& conversationId)
+{
+    return db.count("is_read",
+                    "interactions",
+                    "is_read=:is_read AND conversation=:id",
+                    {{":is_read", "0"}, {":id", conversationId}});
+}
+
+uint64_t
+getLastTimestamp(Database& db)
+{
+    auto timestamps = db.select("MAX(timestamp)", "interactions", "1=1", {}).payloads;
+    auto result = std::time(nullptr);
+    try {
+        if (!timestamps.empty() && !timestamps[0].empty()) {
+            result = std::stoull(timestamps[0]);
+        }
+    } catch (const std::out_of_range& e) {
+        qDebug() << "storage::getLastTimestamp, stoull throws an out_of_range exception: " << e.what();
+    } catch (const std::invalid_argument& e) {
+        qDebug() << "storage::getLastTimestamp, stoull throws an invalid_argument exception: " << e.what();
+    }
+    return result;
+}
+
+namespace {
+QString stringFromJSON(const QJsonObject& json)
+{
+    QJsonDocument doc(json);
+    return QString::fromLocal8Bit(doc.toJson(QJsonDocument::Compact));
+}
+
+QJsonObject
+JSONFromString(const QString& str)
+{
+    QJsonObject json;
+    QJsonDocument doc = QJsonDocument::fromJson(str.toUtf8());
+
+    if (!doc.isNull()) {
+        if (doc.isObject()) {
+            json = doc.object();
+        } else {
+            qDebug() << "Document is not a JSON object: " << str;
+        }
+    } else {
+        qDebug() << "Invalid JSON: " << str;
+    }
+    return json;
+}
+
+QString JSONStringFromInitList(const std::initializer_list<QPair<QString, QJsonValue>> args)
+{
+    QJsonObject jsonObject(args);
+    return stringFromJSON(jsonObject);
+}
+
+QString
+readJSONValue(const QJsonObject& json, const QString& key)
+{
+    if (!json.isEmpty() && json.contains(key) && json[key].isString()) {
+        if (json[key].isString()) {
+            return json[key].toString();
+        }
+    }
+    return {};
+}
+
+void
+writeJSONValue(QJsonObject& json, const QString& key, const QString& value)
+{
+    json[key] = value;
+}
+}
+
+//================================================================================
+// This section provides migration helpers from ring.db
+// to per-account databases yielding a file structure like:
+//
+// { local_storage } / jami
+// └──{ account_id }
+// ├── config.yml
+// ├── contacts
+// ├── export.gz
+// ├── incomingTrustRequests
+// ├── knownDevicesNames
+// ├── history.db < --conversations and interactions database
+// ├── profile.vcf < --account vcard
+// ├── profiles < --account contact vcards
+// │   │──{ contact_uri }.vcf
+// │   └── ...
+// ├── ring_device.crt
+// └── ring_device.key
+//================================================================================
+namespace migration {
+
+enum class msgFlag {
+    IS_INCOMING,
+    IS_OUTGOING,
+    IS_CONTACT_ADDED,
+    IS_INVITATION_RECEIVED,
+    IS_INVITATION_ACCEPTED,
+    IS_TEXT
+};
+
+std::string profileToVcard(const lrc::api::profile::Info&, const std::string&);
+uint64_t getTimeFromTimeStr(const std::string&) noexcept;
+std::pair<msgFlag, uint64_t> migrateMessageBody(const std::string&,
+                                                const lrc::api::interaction::Type&);
+std::vector<std::string> getPeerParticipantsForConversationId(lrc::Database&,
+                                                              const std::string&,
+                                                              const std::string&);
+void migrateAccountDb(const QString&,
+                      std::shared_ptr<lrc::Database>,
+                      std::shared_ptr<lrc::Database>);
+
+namespace interaction {
+
+static inline api::interaction::Type
+to_type(const std::string& type)
+{
+    if (type == "TEXT")
+        return api::interaction::Type::TEXT;
+    else if (type == "CALL")
+        return api::interaction::Type::CALL;
+    else if (type == "CONTACT")
+        return api::interaction::Type::CONTACT;
+    else if (type == "OUTGOING_DATA_TRANSFER")
+        return api::interaction::Type::DATA_TRANSFER;
+    else if (type == "INCOMING_DATA_TRANSFER")
+        return api::interaction::Type::DATA_TRANSFER;
+    else
+        return api::interaction::Type::INVALID;
+}
+
+static inline std::string
+to_migrated_status_string(const std::string& status)
+{
+    if (status == "FAILED")
+        return "FAILURE";
+    else if (status == "SUCCEED")
+        return "SUCCESS";
+    else if (status == "READ")
+        return "SUCCESS";
+    else if (status == "UNREAD")
+        return "SUCCESS";
+    else
+        return status;
+
+}
+
+} // namespace interaction
+
+std::string
+profileToVcard(const api::profile::Info& profileInfo,
+               const std::string& accountId = {})
+{
+    using namespace api;
+    bool compressedImage = std::strncmp(profileInfo.avatar.c_str(), "/9g=", 4) == 0;;
+    std::string vCardStr = vCard::Delimiter::BEGIN_TOKEN;
+    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    vCardStr += vCard::Property::VERSION;
+    vCardStr += ":2.1";
+    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    if (!accountId.empty()) {
+        vCardStr += vCard::Property::UID;
+        vCardStr += ":";
+        vCardStr += accountId;
+        vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    }
+    vCardStr += vCard::Property::FORMATTED_NAME;
+    vCardStr += ":";
+    vCardStr += profileInfo.alias;
+    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    if (profileInfo.type == profile::Type::RING) {
+        vCardStr += vCard::Property::TELEPHONE;
+        vCardStr += ":";
+        vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
+        vCardStr += "other:ring:";
+        vCardStr += profileInfo.uri;
+        vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    } else {
+        vCardStr += vCard::Property::TELEPHONE;
+        vCardStr += profileInfo.uri;
+        vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    }
+    vCardStr += vCard::Property::PHOTO;
+    vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
+    vCardStr += "ENCODING=BASE64";
+    vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
+    vCardStr += compressedImage ? "TYPE=JPEG:" : "TYPE=PNG:";
+    vCardStr += profileInfo.avatar;
+    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
+    vCardStr += vCard::Delimiter::END_TOKEN;
+    return vCardStr;
+}
+
+uint64_t
+getTimeFromTimeStr(const std::string& str) noexcept
+{
+    uint64_t minutes = 0, seconds = 0;
+    std::size_t delimiterPos = str.find(":");
+    if (delimiterPos != std::string::npos) {
+        try {
+            minutes = std::stoull(str.substr(0, delimiterPos));
+            seconds = std::stoull(str.substr(delimiterPos + 1));
+        } catch (const std::exception&) {
+            return 0;
+        }
+    }
+    return minutes * 60 + seconds;
+}
+
+std::pair<msgFlag, uint64_t>
+migrateMessageBody(const std::string& body, const api::interaction::Type& type)
+{
+    uint64_t duration{ 0 };
+    // check in english and local to determine the direction of the call
+    static QString emo = "Missed outgoing call";
+    static QString lmo = QObject::tr("Missed outgoing call");
+    static QString eo  = "Outgoing call";
+    static QString lo  = QObject::tr("Outgoing call");
+    static QString eca = "Contact added";
+    static QString lca = QObject::tr("Contact added");
+    static QString eir = "Invitation received";
+    static QString lir = QObject::tr("Invitation received");
+    static QString eia = "Invitation accepted";
+    static QString lia = QObject::tr("Invitation accepted");
+    auto qstrBody = QString::fromStdString(body);
+    switch (type) {
+    case api::interaction::Type::CALL:
+        {
+        bool en_missedOut   = qstrBody.contains(emo);
+        bool en_out         = qstrBody.contains(eo);
+        bool loc_missedOut  = qstrBody.contains(lmo);
+        bool loc_out        = qstrBody.contains(lo);
+        bool outgoingCall   = en_missedOut || en_out || loc_missedOut || loc_out;
+        std::size_t dashPos = body.find("-");
+        if (dashPos != std::string::npos) {
+            duration = getTimeFromTimeStr(body.substr(dashPos + 2));
+        }
+        return std::make_pair(msgFlag(outgoingCall),
+                              duration);
+        }
+        break;
+    case api::interaction::Type::CONTACT:
+        if (qstrBody.contains(eca) || qstrBody.contains(lca)) {
+            return std::make_pair(msgFlag::IS_CONTACT_ADDED, 0);
+        } else if (qstrBody.contains(eir) || qstrBody.contains(lir)) {
+            return std::make_pair(msgFlag::IS_INVITATION_RECEIVED, 0);
+        } else if (qstrBody.contains(eia) || qstrBody.contains(lia)) {
+            return std::make_pair(msgFlag::IS_INVITATION_ACCEPTED, 0);
+        }
+        break;
+    case api::interaction::Type::INVALID:
+    case api::interaction::Type::TEXT:
+    case api::interaction::Type::DATA_TRANSFER:
+    case api::interaction::Type::COUNT__:
+    default:
+        return std::make_pair(msgFlag::IS_TEXT, 0);
+    }
+    return std::make_pair(msgFlag::IS_OUTGOING, 0);
+}
+
+std::vector<std::string>
+getPeerParticipantsForConversationId(Database& db, const std::string& profileId, const std::string& conversationId)
+{
+    return db.select("participant_id",
+        "conversations",
+        "id=:id AND participant_id!=:participant_id",
+        { {":id", conversationId}, {":participant_id", profileId} }).payloads;
+}
+
+void
+migrateAccountDb(const QString& accountId,
+                 std::shared_ptr<Database> db,
+                 std::shared_ptr<Database> legacyDb)
+{
+    using namespace lrc::api;
+    using namespace migration;
+
+    auto accountLocalPath = getPath() + accountId + "/";
+
+    using namespace DRing::Account;
+    MapStringString accountDetails = ConfigurationManager::instance().
+        getAccountDetails(accountId.toStdString().c_str());
+    bool isRingAccount = accountDetails[ConfProperties::TYPE] == "RING";
+    std::map<std::string, std::string> profileIdUriMap;
+    std::map<std::string, std::string> convIdPeerUriMap;
+    std::string accountProfileId;
+
+    // 1. profiles_accounts
+    // migrate account's avatar/alias from profiles table to {data_dir}/profile.vcf
+    std::string accountUri;
+    if (isRingAccount) {
+        accountUri = accountDetails[DRing::Account::ConfProperties::USERNAME].contains("ring:") ?
+            accountDetails[DRing::Account::ConfProperties::USERNAME].toStdString().substr(std::string("ring:").size()) :
+            accountDetails[DRing::Account::ConfProperties::USERNAME].toStdString();
+    } else {
+        accountUri = accountDetails[DRing::Account::ConfProperties::USERNAME].toStdString();
+    }
+
+    auto accountProfileIds = legacyDb->select(
+        "profile_id", "profiles_accounts",
+        "account_id=:account_id AND is_account=:is_account",
+        { {":account_id", accountId.toStdString()},
+        {":is_account", "true"} }).payloads;
+    if (accountProfileIds.size() != 1) {
+        return;
+    }
+    accountProfileId = accountProfileIds[0];
+    auto accountProfile = legacyDb->select(
+        "photo, alias",
+        "profiles", "id=:id",
+        { {":id", accountProfileId} }).payloads;
+    profile::Info accountProfileInfo;
+    // if we can not find the uri in the database
+    // (in the case of poorly kept SIP account uris),
+    // than we cannot migrate the conversations and vcard
+    if (!accountProfile.empty()) {
+        accountProfileInfo = { accountUri, accountProfile[0], accountProfile[1],
+            isRingAccount ? profile::Type::RING : profile::Type::SIP };
+    }
+    auto accountVcard = profileToVcard(accountProfileInfo, accountId.toStdString());
+    auto profileFilePath = accountLocalPath + "profile" + ".vcf";
+    QFile file(profileFilePath);
+    if (!file.open(QIODevice::WriteOnly)) {
+        throw std::runtime_error("Can't open file: " + profileFilePath.toStdString());
+    }
+    QTextStream(&file) << QString::fromStdString(accountVcard);
+
+    // 2. profiles
+    // migrate profiles from profiles table to {data_dir}/{uri}.vcf
+    // - for JAMI, the scheme and the hostname is omitted
+    // - for SIP, the uri is must be stripped of prefix and port
+    // e.g. 3d1112ab2bb089370c0744a44bbbb0786418d40b.vcf
+    //      username.vcf or username@hostname.vcf
+
+    // only select non-account profiles
+    auto profileIds = legacyDb->select(
+        "profile_id", "profiles_accounts",
+        "account_id=:account_id AND is_account=:is_account",
+        { {":account_id", accountId.toStdString()},
+        {":is_account", "false"} }).payloads;
+    for (const auto& profileId : profileIds) {
+        auto profile = legacyDb->select(
+            "uri, alias, photo, type", "profiles",
+            "id=:id",
+            { {":id", profileId} }).payloads;
+        if (profile.empty()) {
+            continue;
+        }
+        profile::Info profileInfo{ profile[0], profile[2], profile[1] };
+        auto uri = URI(QString::fromStdString(profile[0]));
+        auto profileUri = uri.userinfo();
+        if (!isRingAccount && uri.hasHostname()) {
+            profileUri += "@" + uri.hostname();
+        }
+        // insert into map for use during the conversations table migration
+        profileIdUriMap.insert(std::make_pair(profileId, profileUri.toStdString()));
+        auto vcard = profileToVcard(profileInfo);
+        // make sure the directory exists
+        QDir dir(accountLocalPath + "profiles");
+        if (!dir.exists())
+            dir.mkpath(".");
+        profileFilePath = accountLocalPath + "profiles/" + profileUri + ".vcf";
+        QFile file(profileFilePath);
+        // if we catch duplicates here, skip the profile because
+        // the previous db structure does not guarantee unique uris
+        if (file.exists()) {
+            qWarning() << "Profile file already exits: " << profileFilePath;
+            continue;
+        }
+        if (!file.open(QIODevice::WriteOnly)) {
+            qWarning() << "Can't open file: " << profileFilePath;
+            continue;
+        }
+        QTextStream(&file) << QString::fromStdString(vcard);
+    }
+
+    // 3. conversations
+    // migrate old conversations table ==> new conversations table
+    // a) participant_id INTEGER becomes participant TEXT (the uri of the participant)
+    //    use the selected non-account profiles
+    auto conversationIds = legacyDb->select(
+        "id", "conversations",
+        "participant_id=:participant_id",
+        { {":participant_id", accountProfileId} }).payloads;
+    if (conversationIds.empty()) {
+        return;
+    }
+    for (auto conversationId : conversationIds) {
+        // only one peer pre-groupchat
+        auto peerProfileId = getPeerParticipantsForConversationId(*legacyDb, accountProfileId, conversationId);
+        if (peerProfileId.empty()) {
+            continue;
+        }
+        auto it = profileIdUriMap.find(peerProfileId.at(0));
+        // we cannot insert in the conversations table without a uri
+        if (it == profileIdUriMap.end()) {
+            continue;
+        }
+        convIdPeerUriMap.insert(std::make_pair(conversationId, it->second));
+        try {
+            db->insertInto("conversations",
+                { {":id", "id"} ,
+                {":participant", "participant"} },
+                { { ":id", conversationId } ,
+                { ":participant", it->second } });
+        } catch (const std::runtime_error& e) {
+            qWarning() << "Couldn't migrate conversation: " << e.what();
+            continue;
+        }
+    }
+
+    // 4. interactions
+    auto allInteractions = legacyDb->select(
+        "account_id, author_id, conversation_id, \
+         timestamp, body, type, status, daemon_id",
+        "interactions",
+        "account_id=:account_id",
+        { {":account_id", accountProfileId} });
+    auto interactionIt = allInteractions.payloads.begin();
+    while (interactionIt != allInteractions.payloads.end()) {
+        auto author_id = *(interactionIt + 1);
+        auto convId = *(interactionIt + 2);
+        auto timestamp = *(interactionIt + 3);
+        auto body = *(interactionIt + 4);
+        auto type = interaction::to_type(*(interactionIt + 5));
+        auto statusStr = *(interactionIt + 6);
+        auto daemonId = *(interactionIt + 7);
+
+        auto it = profileIdUriMap.find(author_id);
+        if (it == profileIdUriMap.end() && author_id != accountProfileId) {
+            std::advance(interactionIt, allInteractions.nbrOfCols);
+            continue;
+        }
+        // migrate body+type ==> msgFlag+duration
+        auto migratedMsg = migrateMessageBody(body, type);
+        auto profileUri = it == profileIdUriMap.end() ? "" : it->second;
+        // clear author uri if outgoing
+        switch (migratedMsg.first) {
+        case msgFlag::IS_OUTGOING:
+        case msgFlag::IS_CONTACT_ADDED:
+            profileUri.clear();
+            break;
+        case msgFlag::IS_INCOMING:
+        case msgFlag::IS_INVITATION_RECEIVED:
+        case msgFlag::IS_INVITATION_ACCEPTED:
+        {
+            // try to set profile uri using the conversation id
+            auto it = convIdPeerUriMap.find(convId);
+            if (it == convIdPeerUriMap.end()) {
+                std::advance(interactionIt, allInteractions.nbrOfCols);
+                continue;
+            }
+            profileUri = it->second;
+            break;
+        }
+        case msgFlag::IS_TEXT:
+        default:
+            break;
+        }
+        // Set all read, call and datatransfer, and contact added
+        // interactions to a read state
+        bool is_read = statusStr != "UNREAD"
+            || type == api::interaction::Type::CALL
+            || type == api::interaction::Type::CONTACT;
+        // migrate status
+        if (migratedMsg.first == msgFlag::IS_INVITATION_RECEIVED) {
+            statusStr = "UNKNOWN";
+        }
+        std::string extra_data = migratedMsg.second == 0 ? "" :
+            JSONStringFromInitList({
+                    qMakePair(QString("duration"),
+                    QJsonValue(QString::number(migratedMsg.second)))
+                })
+            .toStdString();
+        if (accountUri == profileUri)
+            profileUri.clear();
+        auto typeStr = api::interaction::to_string(type);
+        try {
+            db->insertInto("interactions", {
+                {":author", "author"},
+                {":conversation", "conversation"},
+                {":timestamp", "timestamp"},
+                {":body", "body"},
+                {":type", "type"},
+                {":status", "status"},
+                {":is_read", "is_read"},
+                {":daemon_id", "daemon_id"},
+                {":extra_data", "extra_data"}
+            }, {
+                {":author", profileUri},
+                {":conversation", convId},
+                {":timestamp", timestamp},
+                {migratedMsg.first != msgFlag::IS_TEXT ? "" : ":body", body},
+                {":type", api::interaction::to_string(type)},
+                {":status", interaction::to_migrated_status_string(statusStr)},
+                {":is_read", is_read ? "1" : "0" },
+                {daemonId.empty() ? "" : ":daemon_id", daemonId},
+                {extra_data.empty() ? "" : ":extra_data", extra_data }
+            });
+        } catch (const std::runtime_error& e) {
+            qWarning() << e.what();
+        }
+        std::advance(interactionIt, allInteractions.nbrOfCols);
+    }
+    qDebug() << "Done";
+}
+
+} // namespace migration
+
+std::vector<std::shared_ptr<Database>>
+migrateIfNeeded(const QStringList& accountIds,
+                MigrationCb& willMigrateCb,
+                MigrationCb& didMigrateCb)
+{
+    using namespace lrc::api;
+    using namespace migration;
+
+    std::vector<std::shared_ptr<Database>> dbs(accountIds.size());
+
+    if (!accountIds.size()) {
+        qDebug() << "No accounts to migrate";
+        return dbs;
+    }
+
+    auto appPath = getPath();
+
+    // ring -> jami path migration
+    QDir dataDir(appPath);
+    // create data directory if not created yet
+    dataDir.mkpath(appPath);
+    QDir oldDataDir(appPath);
+    oldDataDir.cdUp();
+    oldDataDir = oldDataDir
+        .absolutePath()
+#if defined(_WIN32)
+        +"/Savoir-faire Linux/Ring";
+#elif defined(__APPLE__)
+        +"/ring";
+#else
+        + "/gnome-ring";
+#endif
+    QStringList filesList = oldDataDir.entryList();
+    QString filename;
+    QDir dir;
+    bool success = true;
+    foreach(filename, filesList) {
+        qDebug() << "Migrate " << oldDataDir.absolutePath() << "/" << filename
+                 << " to " << dataDir.absolutePath() + "/" + filename;
+        if (filename != "." && filename != "..") {
+            success &= dir.rename(oldDataDir.absolutePath() + "/" + filename,
+                dataDir.absolutePath() + "/" + filename);
+        }
+    }
+    if (success) {
+        // Remove old directory if the migration is successful.
+#if defined(_WIN32)
+        oldDataDir.cdUp();
+#endif
+        oldDataDir.removeRecursively();
+    }
+
+    // first check if there is any legacy data storage to migrate
+    {
+        if (not QFile(dataDir.absoluteFilePath("ring.db")).exists() &&
+            not QDir(dataDir.absoluteFilePath("text/")).exists() &&
+            not QDir(dataDir.absoluteFilePath("profiles/")).exists() &&
+            not QDir(dataDir.absoluteFilePath("peer_profiles/")).exists()) {
+            qDebug() << "No migration required";
+            return dbs;
+        }
+    }
+
+    // A fairly long migration may now occur
+    std::thread migrateThread(
+        [&appPath, &accountIds, &dbs, &didMigrateCb, &dataDir] {
+            // 1. migrate old lrc -> new lrc if needed
+            // 2. migrate new lrc db version 1 -> db version 1.1 if needed
+            // the destructor of LegacyDatabase will remove 'ring.db' and clean out
+            // old lrc files
+            std::shared_ptr<Database> legacyDb;
+            try {
+                legacyDb = lrc::DatabaseFactory::create<LegacyDatabase>(appPath);
+            } catch (const std::runtime_error& e) {
+                qDebug() << "Exception while attempting to load legacy database: " << e.what();
+                if (didMigrateCb)
+                    didMigrateCb();
+                return;
+            }
+
+            // attempt to make a backup of ring.db
+            {
+                QFile dbFile(dataDir.absoluteFilePath("ring.db"));
+                if (dbFile.open(QIODevice::ReadOnly)) {
+                    dbFile.copy(appPath + "ring.db.bak");
+                }
+            }
+
+            // clean out any potentially failed previous migration attempts thwarted
+            // by client termination before starting the migration process
+            for (auto accountId : accountIds) {
+                QFile(appPath + accountId + "/history.db").remove();
+                QFile(appPath + accountId + "/history.db-journal").remove();
+                QFile(appPath + accountId + "/profile.vcf").remove();
+                QDir(appPath + accountId + "/profiles/").removeRecursively();
+            }
+
+            // 3. migrate db version 1.1 -> per account dbs version 1
+            int index = 0;
+            for (auto accountId : accountIds) {
+                qDebug() << "Migrating account: " << accountId << "...";
+                try {
+                    auto dbName = QString::fromStdString(accountId.toStdString() + "/history");
+                    dbs.at(index) = lrc::DatabaseFactory::create<Database>(dbName, appPath);
+                    auto& db = dbs.at(index++);
+                    migration::migrateAccountDb(accountId, db, legacyDb);
+                } catch (const std::runtime_error& e) {
+                    qWarning().noquote()
+                        << "Could not migrate database for account: "
+                        << accountId << "\n " << e.what();
+                }
+            }
+
+            // done!
+            if (didMigrateCb)
+                didMigrateCb();
+        });
+
+    // if willMigrateCb blocks, it must be unblocked by didMigrateCb
+    if (willMigrateCb)
+        willMigrateCb();
+
+    migrateThread.join();
+
+    return dbs;
+}
+
+} // namespace database
+
+} // namespace authority
+
+} // namespace lrc
diff --git a/src/authority/databasehelper.h b/src/authority/storagehelper.h
similarity index 50%
rename from src/authority/databasehelper.h
rename to src/authority/storagehelper.h
index fec5f20f..90444a1e 100644
--- a/src/authority/databasehelper.h
+++ b/src/authority/storagehelper.h
@@ -1,8 +1,9 @@
 /****************************************************************************
- *    Copyright (C) 2017-2019 Savoir-faire Linux Inc.                             *
+ *    Copyright (C) 2017-2019 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             *
@@ -37,96 +38,114 @@ struct Info;
 namespace authority
 {
 
-namespace database
+namespace storage
 {
 
 /**
- * Get id from database for a given uri
- * @param db
- * @param accountId
- * @param isAccount
- * @param uri
- * @return the id
- * @note "" if no id
- */
-std::string getProfileId(Database& db,
-            const std::string& accountId,
-            const std::string& isAccount,
-            const std::string& uri="");
-
- /**
- * Get id for a profile. If the profile doesn't exist, create it.
- * @param db
- * @param contactUri
- * @param accountId
- * @param isAccount
- * @param alias
- * @param avatar
- * @return the id
+ * Get the base path for the application's local storage
+ * @return local storage path
+ */
+QString getPath();
+
+/**
+ * Get a formatted for local storage
+ * @param uri that may have a scheme prefixed
+ * @param type of account for which to transform the uri
+ * @return formatted uri
+ */
+std::string prepareUri(const std::string& uri, api::profile::Type type);
+
+/**
+ * Get a formatted string for a call interaction's body
+ * @param author_uri
+ * @param duration of the call
+ * @return the formatted and translated call message string
+ */
+std::string
+getCallInteractionString(const std::string& authorUri,
+                         const std::time_t& duration);
+
+/**
+ * Get a formatted string for a contact interaction's body
+ * @param author_uri
+ * @param status
+ * @return the formatted and translated call message string
+ */
+std::string
+getContactInteractionString(const std::string& authorUri,
+                            const api::interaction::Status& status);
+
+namespace vcard
+{
+
+/**
+ * Build the vCard for a profile
+ * @param profileInfo
+ * @param compressImage
+ * @return vcard string of the profile
+ */
+std::string profileToVcard(const api::profile::Info& profileInfo,
+                           bool compressImage = false);
+
+} // namespace vcard
+
+/**
+ * @param  duration
+ * @return a human readable call duration (M:ss)
  */
- std::string getOrInsertProfile(Database& db,
-                                const std::string& contactUri,
-                                const std::string& accountId,
-                                bool  isAccount,
-                                const std::string& type,
-                                const std::string& alias = "",
-                                const std::string& avatar = "");
+std::string getFormattedCallDuration(const std::time_t duration);
 
 /**
- * Get conversations for a given profile.
+ * Get all conversations with a given participant's URI
  * @param db
- * @param profileId
+ * @param participant_uri
  */
-std::vector<std::string> getConversationsForProfile(Database& db,
-                                                    const std::string& profileId);
+std::vector<std::string> getConversationsWithPeer(Database& db,
+                                                  const std::string& participant_uri);
 
 /**
- * Get peer participant for a conversation linked to a profile.
+ * Get all peer participant(s) URIs for a given conversation id
  * @param db
- * @param profileId
  * @param conversationId
- * @note we don't verify if profileId is in the conversation
  */
 std::vector<std::string> getPeerParticipantsForConversation(Database& db,
-                                                            const std::string& profileId,
                                                             const std::string& conversationId);
 
 /**
- * @param  db
- * @param  profileId
- * @return the avatar in the database for a profile
- */
-std::string getAvatarForProfileId(Database& db, const std::string& profileId);
-
-/**
- * Check if the profile could be removed
- * @param  db
- * @param  profileId
+ * Creates or updates a contact or account vCard file with profile data.
+ * @param  accountId
+ * @param  profileInfo the contact info containing peer profile information
+ * @param  isPeer indicates that a the profileInfo is that of a peer
  */
-bool profileCouldBeRemoved(Database& db, const std::string& profileId);
+void createOrUpdateProfile(const std::string& accountId,
+                           const api::profile::Info& profileInfo,
+                           const bool isPeer = false);
 
 /**
- * @param  db
- * @param  profileId
- * @param  avatar
+ * Gets the account's avatar from the profile.vcf file
+ * @param  accountId
+ * @return the account's base64 avatar
  */
-void setAvatarForProfileId(Database& db, const std::string& profileId, const std::string& avatar);
+std::string
+getAccountAvatar(const std::string& accountId);
 
 /**
- * @param  db
- * @param  profileId
- * @return the alias in the database for a profile
+ * Build a contact info struct from a vCard
+ * @param  accountId
+ * @param  peer_uri
+ * @param  type of contact to build
+ * @return the contact info containing peer profile information
  */
-std::string getAliasForProfileId(Database& db, const std::string& profileId);
+api::contact::Info buildContactFromProfile(const std::string & accountId,
+                                           const std::string& peer_uri,
+                                           const api::profile::Type& type);
 
 /**
- * @param  db
- * @param  profileId
- * @param  alias
+ * Get all conversations for an account in the database.
+ * @param db
+ * @return conversations id for all conversations
  */
-void setAliasForProfileId(Database& db, const std::string& profileId, const std::string& alias);
-
-api::contact::Info buildContactFromProfileId(Database& db, const std::string& profileId);
+std::vector<std::string> getAllConversations(Database& db);
 
 /**
  * Get conversations shared between an account and a contact.
@@ -143,15 +162,13 @@ std::vector<std::string> getConversationsBetween(Database& db,
  * Start a conversation between account and contact. Creates an entry in the conversations table
  * and an entry in the interactions table.
  * @param db
- * @param accountProfile the id of the account in the database
- * @param contactProfile the id of the contact in the database
- * @param firstMessage the body of the first message
+ * @param peer_uri the URI of the peer
+ * @param isOutgoing
  * @return conversation_id of the new conversation.
  */
-std::string beginConversationsBetween(Database& db,
-                                      const std::string& accountProfile,
-                                      const std::string& contactProfile,
-                                      const std::string& firstMessage = "");
+std::string beginConversationWithPeer(Database& db,
+                                      const std::string& peer_uri,
+                                      const bool isOutgoing = true);
 
 /**
  * Return interactions from a conversation
@@ -163,31 +180,38 @@ void getHistory(Database& db, api::conversation::Info& conversation);
 /**
  * Add an entry into interactions linked to a conversation.
  * @param  db
- * @param  accountProfile
  * @param  conversationId
  * @param  msg
  * @return the id of the inserted interaction
  */
 int addMessageToConversation(Database& db,
-                              const std::string& accountProfile,
-                              const std::string& conversationId,
-                              const api::interaction::Info& msg);
+                             const std::string& conversationId,
+                             const api::interaction::Info& msg);
 
 /**
 * Add or update an entry into interactions linked to a conversation.
 * @param  db
-* @param  accountProfile
 * @param  conversationId
 * @param  msg
 * @param  daemonId
 * @return the id of the inserted interaction
 */
 int addOrUpdateMessage(Database& db,
-                       const std::string& accountProfile,
                        const std::string& conversationId,
                        const api::interaction::Info& msg,
                        const std::string& daemonId);
 
+/**
+* Add a data transfer entry into interactions linked to a conversation.
+* @param  db
+* @param  conversationId
+* @param  daemonId
+* @return the id of the inserted interaction
+*/
+int addDataTransferToConversation(Database& db,
+                                  const std::string& conversationId,
+                                  const api::datatransfer::Info& infoFromDaemon);
+
 /**
  * Change the daemon_id column for an interaction
  * @param db
@@ -206,11 +230,22 @@ void addDaemonMsgId(Database& db,
 std::string getDaemonIdByInteractionId(Database& db, const std::string& id);
 
 /**
+ * Obtain the id of an interaction of a given daemon_id
  * @param  db
  * @param  id
  * @return the interaction id for a daemon id else an empty string
  */
-std::string getInteractionIdByDaemonId(Database& db, const std::string& id);
+std::string getInteractionIdByDaemonId(Database& db, const std::string& daemon_id);
+
+/**
+ * Obtain the extra_data column of an interaction of a given id
+ * @note if a key is provided and exists, the value will be returned
+ * @param db
+ * @param id
+ * @param key
+ */
+std::string getInteractionExtraDataById(Database& db, const std::string& id,
+                                        const std::string& key = {});
 
 /**
  * Change the body of an interaction
@@ -226,10 +261,18 @@ void updateInteractionBody(Database& db, unsigned int id,
  * @param db
  * @param id
  * @param newStatus
+ * @param isRead
  */
 void updateInteractionStatus(Database& db, unsigned int id,
                              api::interaction::Status newStatus);
 
+/**
+ * Set interaction to the read state
+ * @param db
+ * @param id
+ */
+void setInteractionRead(Database& db, unsigned int id);
+
 /**
  * Clear history but not the conversation started interaction
  * @param  db
@@ -249,53 +292,45 @@ void clearInteractionFromConversation(Database& db,
                                       const uint64_t& interactionId);
 
 /**
- * Clear all history stored in the database for the account uri
+ * Clear all history stored in the interactions table of the database
  * @param  db
- * @param accountId
  */
-void clearAllHistoryFor(Database& db, const std::string& accountId);
+void clearAllHistory(Database& db);
 
 /**
- * delete obsolete histori from the database
+ * delete obsolete history from the database
  * @param db
  * @param date in second since epoch. Below this date, interactions will be deleted
  */
 void deleteObsoleteHistory(Database& db, long int date);
 
 /**
- * Remove a conversation between an account and a contact. Remove corresponding entries in
- * the conversations table and profiles if the profile is not present in conversations.
+ * Remove all conversation with a contact. Remove corresponding entries in
+ * the conversations table.
  * @param db
  * @param contactUri
- * @param accountId
  */
-void removeContact(Database& db, const std::string& contactUri, const std::string& accountId);
+void removeContact(Database& db, const std::string& contactUri);
 
 /**
- * Remove from conversations and profiles linked to an account.
- * @param db
+ * Ensure that all files located in
+ * {local_storage}/jami/{accountId} are removed
  * @param accountId
  */
-void removeAccount(Database& db, const std::string& accountId);
+void removeAccount(const std::string& accountId);
 
 /**
- * insert into profiles and conversations.
+ * count number of 'UNREAD' from 'interactions' table.
  * @param db
- * @param contactUri
- * @param accountId
+ * @param conversationId
  */
-void addContact(Database& db, const std::string& contactUri, const std::string& accountId);
+int countUnreadFromInteractions(Database& db, const std::string& conversationId);
 
 /**
- * count number of 'UNREAD' from 'interactions' table.
+ * Retrieve an interaction's conversation id
+ * @param db
+ * @param conversationId
  */
-int countUnreadFromInteractions(Database& db, const std::string& conversationId);
-
-int addDataTransferToConversation(Database& db,
-                                  const std::string& accountProfileId,
-                                  const std::string& conversationId,
-                                  const api::datatransfer::Info& infoFromDaemon);
-
 std::string conversationIdFromInteractionId(Database& db, unsigned int interactionId);
 
 /**
@@ -305,7 +340,62 @@ std::string conversationIdFromInteractionId(Database& db, unsigned int interacti
  */
 uint64_t getLastTimestamp(Database& db);
 
-} // namespace database
+/**
+ * JSON parsing functions intended for use with the
+ * extra_data columns(conversations and interactions)
+ */
+namespace {
+/**
+ * Build a string from a QJsonObject
+ * @param  json
+ * @return a JSON as a QString
+ */
+QString stringFromJSON(const QJsonObject& json);
+
+/**
+ * Build a QJsonObject from a QString
+ * @param  str
+ * @return a JSON object
+ */
+QJsonObject JSONFromString(const QString& str);
+
+/**
+ * Build a string from an initializer list of key/value pairs
+ * @param  args
+ * @return a JSON as a QString
+ */
+QString JSONStringFromInitList(const std::initializer_list<QPair<QString, QJsonValue> > args);
+
+/**
+ * Get the value at a key from a JSON object
+ * @param  json
+ * @param  key
+ * @return the value as a QString
+ */
+QString readJSONValue(const QJsonObject& json, const QString& key);
+
+/**
+ * Store a value at a key in a JSON object
+ * @param  json
+ * @param  key
+ * @param  value
+ */
+void writeJSONValue(QJsonObject& json, const QString& key, const QString& value);
+}
+
+/**
+ * Retrieve a list of account database via a migration
+ * procedure from the legacy "ring.db", if it exists
+ * @param accountIds of the accounts to attempt migration upon
+ * @param willMigrateCb to invoke when migration will occur
+ * @param didMigrateCb to invoke when migration has completed
+ */
+std::vector<std::shared_ptr<Database>>
+migrateIfNeeded(const QStringList& accountIds,
+                MigrationCb& willMigrateCb,
+                MigrationCb& didMigrateCb);
+
+} // namespace storage
 
 } // namespace authority
 
diff --git a/src/avmodel.cpp b/src/avmodel.cpp
index cb42fab8..f30a727c 100644
--- a/src/avmodel.cpp
+++ b/src/avmodel.cpp
@@ -42,7 +42,7 @@
 #include "dbus/callmanager.h"
 #include "dbus/configurationmanager.h"
 #include "dbus/videomanager.h"
-#include "database.h"
+#include "authority/storagehelper.h"
 
 namespace lrc
 {
@@ -663,7 +663,7 @@ AVModelPimpl::getRecordingPath() const
 #if defined(_WIN32) || defined(__APPLE__)
     const QDir dir = QString::fromStdString(linked_.getRecordPath()) + "/" + recorderSavesSubdir.c_str();
 #else
-    const QDir dir = lrc::Database::getPath() + "/" + recorderSavesSubdir.c_str();
+    const QDir dir = authority::storage::getPath() + "/" + recorderSavesSubdir.c_str();
 #endif
 
     dir.mkpath(".");
diff --git a/src/callbackshandler.cpp b/src/callbackshandler.cpp
index 60c169d0..fbda0d06 100644
--- a/src/callbackshandler.cpp
+++ b/src/callbackshandler.cpp
@@ -187,12 +187,6 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
             &CallbacksHandler::slotMigrationEnded,
             Qt::QueuedConnection);
 
-    connect(&ConfigurationManager::instance(),
-            &ConfigurationManagerInterface::debugMessageReceived,
-            this,
-            &CallbacksHandler::slotDebugMessageReceived,
-            Qt::QueuedConnection);
-
     connect(&VideoManager::instance(),
             &VideoManagerInterface::startedDecoding,
             this,
@@ -220,7 +214,16 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
 
 CallbacksHandler::~CallbacksHandler()
 {
+}
 
+void
+CallbacksHandler::subscribeToDebugReceived()
+{
+    connect(&ConfigurationManager::instance(),
+            &ConfigurationManagerInterface::debugMessageReceived,
+            this,
+            &CallbacksHandler::slotDebugMessageReceived,
+            Qt::QueuedConnection);
 }
 
 void
diff --git a/src/callbackshandler.h b/src/callbackshandler.h
index bf096fc1..4f38a306 100644
--- a/src/callbackshandler.h
+++ b/src/callbackshandler.h
@@ -51,6 +51,10 @@ public:
     CallbacksHandler(const api::Lrc& parent);
     ~CallbacksHandler();
 
+    // This connection relies on the behavior controller
+    // and needs to be made after the lrc object is constructed
+    void subscribeToDebugReceived();
+
 Q_SIGNALS:
     /**
      * Connect this signal to get incoming text interaction from the DHT.
diff --git a/src/contactmodel.cpp b/src/contactmodel.cpp
index 71578a43..3116b525 100644
--- a/src/contactmodel.cpp
+++ b/src/contactmodel.cpp
@@ -1,10 +1,11 @@
 /****************************************************************************
- *    Copyright (C) 2017-2019 Savoir-faire Linux Inc.                             *
+ *    Copyright (C) 2017-2019 Savoir-faire Linux Inc.                       *
  *   Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>             *
  *   Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>           *
  *   Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>       *
  *   Author: Hugo Lefeuvre <hugo.lefeuvre@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             *
@@ -20,6 +21,8 @@
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
  ***************************************************************************/
 
+#include "api/contactmodel.h"
+
 // Std
 #include <algorithm>
 #include <mutex>
@@ -28,7 +31,6 @@
 #include <account_const.h>
 
 // LRC
-#include "api/contactmodel.h"
 #include "api/account.h"
 #include "api/contact.h"
 #include "api/interaction.h"
@@ -41,7 +43,7 @@
 #include "vcard.h"
 
 #include "authority/daemon.h"
-#include "authority/databasehelper.h"
+#include "authority/storagehelper.h"
 
 // Dbus
 #include "dbus/configurationmanager.h"
@@ -64,16 +66,16 @@ public:
     ~ContactModelPimpl();
 
     /**
-     * Fills with contacts based on database's requests
+     * Fills the contacts based on database's conversations
      * @return if the method succeeds
      */
-    bool fillsWithSIPContacts();
+    bool fillWithSIPContacts();
 
     /**
-     * Fills with contacts based on daemon's requests
+     * Fills the contacts based on database's conversations
      * @return if the method succeeds
      */
-    bool fillsWithRINGContacts();
+    bool fillWithJamiContacts();
 
     /**
      * Add a contact::Info to contacts.
@@ -174,10 +176,12 @@ public Q_SLOTS:
 
 using namespace authority;
 
-ContactModel::ContactModel(const account::Info& owner, Database& database, const CallbacksHandler& callbacksHandler, const BehaviorController& behaviorController)
-: QObject()
-, owner(owner)
-, pimpl_(std::make_unique<ContactModelPimpl>(*this, database, callbacksHandler, behaviorController))
+ContactModel::ContactModel(const account::Info& owner,
+                           Database& db,
+                           const CallbacksHandler& callbacksHandler,
+                           const BehaviorController& behaviorController)
+: owner(owner)
+, pimpl_(std::make_unique<ContactModelPimpl>(*this, db, callbacksHandler, behaviorController))
 {
 }
 
@@ -226,9 +230,9 @@ ContactModel::addContact(contact::Info contactInfo)
     }
 
     if ((owner.profileInfo.type != profile.type) and
-        (profile.type == profile::Type::RING or profile.type == profile::Type::SIP)) {
-            qDebug() << "ContactModel::addContact, types invalids.";
-            return;
+       (profile.type == profile::Type::RING or profile.type == profile::Type::SIP)) {
+        qDebug() << "ContactModel::addContact, types invalids.";
+        return;
     }
 
     MapStringString details = ConfigurationManager::instance().getContactDetails(
@@ -258,13 +262,13 @@ ContactModel::addContact(contact::Info contactInfo)
     case profile::Type::SIP:
         break;
     case profile::Type::INVALID:
+    case profile::Type::COUNT__:
     default:
         qDebug() << "ContactModel::addContact, cannot add contact with invalid type.";
         return;
     }
 
-    database::getOrInsertProfile(pimpl_->db, profile.uri, owner.id, false,
-    to_string(owner.profileInfo.type),profile.alias, profile.avatar);
+    storage::createOrUpdateProfile(owner.id, profile, true);
 
     {
         std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
@@ -299,13 +303,13 @@ ContactModel::removeContact(const std::string& contactUri, bool banned)
                 return;
             }
             pimpl_->contacts.erase(contactUri);
-            database::removeContact(pimpl_->db, contactUri, owner.id);
+            storage::removeContact(pimpl_->db, contactUri);
             emitContactRemoved = true;
         }
         else if (owner.profileInfo.type == profile::Type::SIP) {
             // Remove contact from db
             pimpl_->contacts.erase(contactUri);
-            database::removeContact(pimpl_->db, contactUri, owner.id);
+            storage::removeContact(pimpl_->db, contactUri);
             emitContactRemoved = true;
         }
     }
@@ -336,18 +340,6 @@ ContactModel::getBannedContacts() const
     return pimpl_->bannedContacts;
 }
 
-const std::string
-ContactModel::getProfileId(const std::string& uri, bool isAccount) const
-{
-    return database::getProfileId(pimpl_->db, pimpl_->linked.owner.id, isAccount ? "true" : "false", uri);
-}
-
-const std::string
-ContactModel::getContactProfileId(const std::string& contactUri) const
-{
-    return getProfileId(contactUri, false);
-}
-
 void
 ContactModel::searchContact(const std::string& query)
 {
@@ -442,7 +434,6 @@ ContactModel::sendDhtMessage(const std::string& contactUri, const std::string& b
     return msgId;
 }
 
-
 ContactModelPimpl::ContactModelPimpl(const ContactModel& linked,
                                      Database& db,
                                      const CallbacksHandler& callbacksHandler,
@@ -454,9 +445,9 @@ ContactModelPimpl::ContactModelPimpl(const ContactModel& linked,
 {
     // Init contacts map
     if (linked.owner.profileInfo.type == profile::Type::SIP)
-        fillsWithSIPContacts();
+        fillWithSIPContacts();
     else
-        fillsWithRINGContacts();
+        fillWithJamiContacts();
 
     // connect the signals
     connect(&callbacksHandler, &CallbacksHandler::newBuddySubscription,
@@ -498,15 +489,16 @@ ContactModelPimpl::~ContactModelPimpl()
 }
 
 bool
-ContactModelPimpl::fillsWithSIPContacts()
+ContactModelPimpl::fillWithSIPContacts()
 {
-    auto accountProfileId = database::getProfileId(db, linked.owner.id, "true", linked.owner.profileInfo.uri);
-    auto conversationsForAccount = database::getConversationsForProfile(db, accountProfileId);
-    for (const auto& c : conversationsForAccount) {
-        auto otherParticipants = database::getPeerParticipantsForConversation(db, accountProfileId, c);
+    auto conversationsForAccount = storage::getAllConversations(db);
+    for (const auto& convId : conversationsForAccount) {
+        auto otherParticipants = storage::getPeerParticipantsForConversation(db, convId);
         for (const auto& participant: otherParticipants) {
             // for each conversations get the other profile id
-            auto contactInfo = database::buildContactFromProfileId(db, participant);
+            auto contactInfo = storage::buildContactFromProfile(linked.owner.id,
+                                                                participant,
+                                                                profile::Type::SIP);
             {
                 std::lock_guard<std::mutex> lk(contactsMtx_);
                 contacts.emplace(contactInfo.profileInfo.uri, contactInfo);
@@ -518,7 +510,7 @@ ContactModelPimpl::fillsWithSIPContacts()
 }
 
 bool
-ContactModelPimpl::fillsWithRINGContacts() {
+ContactModelPimpl::fillWithJamiContacts() {
 
     // Add contacts from daemon
     const VectorMapStringString& contacts_vector = ConfigurationManager::instance().getContacts(linked.owner.id.c_str());
@@ -536,18 +528,19 @@ ContactModelPimpl::fillsWithRINGContacts() {
 
         auto contactUri = tr_info[DRing::Account::TrustRequest::FROM];
 
+        auto contactInfo = storage::buildContactFromProfile(linked.owner.id,
+                                                            contactUri.toStdString(),
+                                                            profile::Type::PENDING);
+
         const auto vCard = lrc::vCard::utils::toHashMap(payload);
         const auto alias = vCard["FN"];
-        const auto photo = (vCard.find("PHOTO;ENCODING=BASE64;TYPE=PNG") == vCard.end()) ?
-        vCard["PHOTO;ENCODING=BASE64;TYPE=JPEG"] : vCard["PHOTO;ENCODING=BASE64;TYPE=PNG"];
-
-        lrc::api::profile::Info profileInfo;
-        profileInfo.uri = contactUri.toStdString();
-        profileInfo.avatar = photo.toStdString();
-        profileInfo.alias = alias.toStdString();
-        profileInfo.type = profile::Type::PENDING;
-        contact::Info contactInfo;
-        contactInfo.profileInfo = profileInfo;
+        const auto photo = (vCard.find("PHOTO;ENCODING=BASE64;TYPE=PNG") != vCard.end()) ?
+            vCard["PHOTO;ENCODING=BASE64;TYPE=PNG"] :
+            vCard["PHOTO;ENCODING=BASE64;TYPE=JPEG"];
+
+        contactInfo.profileInfo.type = profile::Type::PENDING;
+        if (!alias.isEmpty()) contactInfo.profileInfo.alias = alias.constData();
+        if (!photo.isEmpty()) contactInfo.profileInfo.avatar = photo.constData();
         contactInfo.registeredName = "";
         contactInfo.isBanned = false;
 
@@ -556,13 +549,15 @@ ContactModelPimpl::fillsWithRINGContacts() {
             contacts.emplace(contactUri.toStdString(), contactInfo);
         }
 
-        database::getOrInsertProfile(db, contactUri.toStdString(), linked.owner.id, false,
-        profile::to_string(profile::Type::RING), alias.toStdString(), photo.toStdString());
+        // create profile vcard for contact
+        storage::createOrUpdateProfile(linked.owner.id, contactInfo.profileInfo, true);
     }
 
     // Update presence
     // TODO fix this map. This is dumb for now. The map contains values as keys, and empty values.
-    const VectorMapStringString& subscriptions {PresenceManager::instance().getSubscriptions(linked.owner.id.c_str())};
+    const VectorMapStringString& subscriptions {
+        PresenceManager::instance().getSubscriptions(linked.owner.id.c_str())
+    };
     for (const auto& subscription : subscriptions) {
         auto first = true;
         std::string uri = "";
@@ -678,7 +673,7 @@ ContactModelPimpl::slotContactRemoved(const std::string& accountId, const std::s
                     bannedContacts.erase(it);
                 }
             }
-            database::removeContact(db, contactUri, accountId);
+            storage::removeContact(db, contactUri);
             contacts.erase(contactUri);
         }
     }
@@ -693,20 +688,20 @@ ContactModelPimpl::slotContactRemoved(const std::string& accountId, const std::s
 }
 
 void
-ContactModelPimpl::addToContacts(const std::string& contactId, const profile::Type& type, bool banned)
+ContactModelPimpl::addToContacts(const std::string& contactUri, const profile::Type& type, bool banned)
 {
-    auto profileId = database::getOrInsertProfile(db, contactId, linked.owner.id,
-    false, to_string(linked.owner.profileInfo.type),"", "");
+    // create a vcard if necessary
+    profile::Info profileInfo{ contactUri, {}, {}, linked.owner.profileInfo.type };
+    storage::createOrUpdateProfile(linked.owner.id, profileInfo, true);
 
-    auto contactInfo = database::buildContactFromProfileId(db, profileId);
+    auto contactInfo = storage::buildContactFromProfile(linked.owner.id, contactUri, type);
     contactInfo.isBanned = banned;
-    contactInfo.profileInfo.type = type; // PENDING should not be stored in the database
 
     // lookup address in case of RING contact
     if (type == profile::Type::RING) {
         ConfigurationManager::instance().lookupAddress(QString::fromStdString(linked.owner.id),
-                                                       "", QString::fromStdString(contactId));
-        PresenceManager::instance().subscribeBuddy(linked.owner.id.c_str(), contactId.c_str(), !banned);
+                                                       "", QString::fromStdString(contactUri));
+        PresenceManager::instance().subscribeBuddy(linked.owner.id.c_str(), contactUri.c_str(), !banned);
     }
 
     contactInfo.profileInfo.type = type; // Because PENDING should not be stored in the database
@@ -719,7 +714,7 @@ ContactModelPimpl::addToContacts(const std::string& contactId, const profile::Ty
         contacts.emplace_hint(iter, contactInfo.profileInfo.uri, contactInfo);
 
     if (banned) {
-        bannedContacts.emplace_back(contactId);
+        bannedContacts.emplace_back(contactUri);
     }
 }
 
@@ -760,7 +755,7 @@ ContactModelPimpl::slotRegisteredNameFound(const std::string& accountId,
             updateTemporaryMessage(tr("Invalid ID").toStdString(), registeredName);
             break;
         case 2 /* NOT FOUND */:
-            updateTemporaryMessage(tr("Not found").toStdString(), registeredName);
+            updateTemporaryMessage(tr("Registered name not found").toStdString(), registeredName);
             break;
         case 3 /* ERROR */:
             updateTemporaryMessage(tr("Couldn't lookup…").toStdString(), registeredName);
@@ -792,8 +787,7 @@ ContactModelPimpl::slotIncomingContactRequest(const std::string& accountId,
             auto contactInfo = contact::Info {profileInfo, "", false, false, false};
             contacts.emplace(contactUri, contactInfo);
             emitTrust = true;
-            database::getOrInsertProfile(db, contactUri, accountId, false,
-            profile::to_string(profile::Type::RING), alias.toStdString(), photo.toStdString());
+            storage::createOrUpdateProfile(accountId, profileInfo, true);
         }
     }
 
diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp
index 1f2de475..918e1b71 100644
--- a/src/conversationmodel.cpp
+++ b/src/conversationmodel.cpp
@@ -45,7 +45,7 @@
 #include "api/datatransfer.h"
 #include "api/datatransfermodel.h"
 #include "callbackshandler.h"
-#include "authority/databasehelper.h"
+#include "authority/storagehelper.h"
 
 #include "uri.h"
 
@@ -105,19 +105,19 @@ public:
     /**
      * Add call interaction for conversation with callId
      * @param callId
-     * @param body
+     * @param duration
      */
-    void addOrUpdateCallMessage(const std::string& callId, const std::string& body);
+    void addOrUpdateCallMessage(const std::string& callId,
+                                const std::string& from = {},
+                                const std::time_t& duration = -1);
     /**
      * Add a new message from a peer in the database
-     * @param from the peer uri
+     * @param from the author uri
      * @param body the content of the message
-     * @param authorProfileId override the author of the message (if empty it's from)
      * @param timestamp the timestamp of the message
      */
     void addIncomingMessage(const std::string& from,
                             const std::string& body,
-                            const std::string& authorProfileId="",
                             const uint64_t& timestamp = 0);
     /**
      * Change the status of an interaction. Listen from callbacksHandler
@@ -163,7 +163,6 @@ public:
     Lrc& lrc;
     Database& db;
     const CallbacksHandler& callbacksHandler;
-    const std::string accountProfileId;
     const BehaviorController& behaviorController;
 
     ConversationModel::ConversationQueue conversations; ///< non-filtered conversations
@@ -331,6 +330,7 @@ ConversationModel::allFilteredConversations() const
                 if (pimpl_->typeFilter != profile::Type::PENDING) {
                     // Remove pending contacts and get the temporary item if filter is not empty
                     switch (contactInfo.profileInfo.type) {
+                    case profile::Type::COUNT__:
                     case profile::Type::INVALID:
                     case profile::Type::PENDING:
                         return false;
@@ -391,7 +391,6 @@ ConversationModel::filteredConversation(const unsigned int row) const
         return conversation::Info();
 
     auto conversationInfo = conversations.at(row);
-    conversationInfo.unreadMessages = pimpl_->getNumberOfUnreadMessagesFor(conversationInfo.uid);
 
     return conversationInfo;
 }
@@ -502,7 +501,7 @@ ConversationModel::deleteObsoleteHistory(int days)
     auto currentTime = static_cast<long int>(std::time(nullptr)); // since epoch, in seconds...
     auto date = currentTime - (days * 86400);
 
-    database::deleteObsoleteHistory(pimpl_->db, date);
+    storage::deleteObsoleteHistory(pimpl_->db, date);
 }
 
 void
@@ -547,14 +546,13 @@ ConversationModelPimpl::placeCall(const std::string& uid, bool isAudioOnly)
     }
 
     auto convId = uid;
-    auto accountId = accountProfileId;
 
     auto participant = conversation.participants.front();
     bool isTemporary = participant.empty();
     auto contactInfo = linked.owner.contactModel->getContact(participant);
-    auto url = contactInfo.profileInfo.uri;
+    auto uri = contactInfo.profileInfo.uri;
 
-    if (url.empty())
+    if (uri.empty())
         return; // Incorrect item
 
     // Don't call banned contact
@@ -564,11 +562,11 @@ ConversationModelPimpl::placeCall(const std::string& uid, bool isAudioOnly)
     }
 
     if (linked.owner.profileInfo.type != profile::Type::SIP) {
-        url = "ring:" + url; // Add the ring: before or it will fail.
+        uri = "ring:" + uri; // Add the ring: before or it will fail.
     }
 
     auto cb = std::function<void(std::string)>(
-        [this, isTemporary, url, isAudioOnly, &conversation](std::string convId) {
+        [this, isTemporary, uri, isAudioOnly, &conversation](std::string convId) {
             int contactIndex;
             if (isTemporary && (contactIndex = indexOfContact(convId)) < 0) {
                 qDebug() << "Can't place call: Other participant is not a contact (removed while placing call ?)";
@@ -578,7 +576,7 @@ ConversationModelPimpl::placeCall(const std::string& uid, bool isAudioOnly)
             auto& newConv = isTemporary ? conversations.at(contactIndex) : conversation;
             convId = newConv.uid;
 
-            newConv.callId = linked.owner.callModel->createCall(url, isAudioOnly);
+            newConv.callId = linked.owner.callModel->createCall(uri, isAudioOnly);
             if (newConv.callId.empty()) {
                 qDebug() << "Can't place call (daemon side failure ?)";
                 return;
@@ -636,7 +634,6 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body)
     }
 
     auto convId = uid;
-    auto accountId = pimpl_->accountProfileId;
     bool isTemporary = conversation.participants.front() == "";
 
     /* Make a copy of participants list: if current conversation is temporary,
@@ -644,7 +641,7 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body)
     const auto participants = conversation.participants;
 
     auto cb = std::function<void(std::string)>(
-        [this, accountId, isTemporary, body, &conversation](std::string convId) {
+        [this, isTemporary, body, &conversation](std::string convId) {
             /* Now we should be able to retrieve the final conversation, in case the previous
                one was temporary */
                // FIXME potential race condition between index check and at() call
@@ -682,14 +679,20 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body)
             }
 
             // Add interaction to database
-            auto msg = interaction::Info{ accountId, body, std::time(nullptr),
-                                          interaction::Type::TEXT, status };
-            int msgId = database::addMessageToConversation(pimpl_->db, accountId, convId, msg);
+            interaction::Info msg {
+                {},
+                body, std::time(nullptr),
+                0,
+                interaction::Type::TEXT,
+                status,
+                true
+            };
+            int msgId = storage::addMessageToConversation(pimpl_->db, convId, msg);
 
             // Update conversation
             if (status == interaction::Status::SENDING) {
                 // Because the daemon already give an id for the message, we need to store it.
-                database::addDaemonMsgId(pimpl_->db, std::to_string(msgId), std::to_string(daemonMsgId));
+                storage::addDaemonMsgId(pimpl_->db, std::to_string(msgId), std::to_string(daemonMsgId));
             }
 
             bool ret = false;
@@ -810,13 +813,13 @@ ConversationModel::clearHistory(const std::string& uid)
 
     auto& conversation = pimpl_->conversations.at(conversationIdx);
     // Remove all TEXT interactions from database
-    database::clearHistory(pimpl_->db, uid);
+    storage::clearHistory(pimpl_->db, uid);
     // Update conversation
     {
         std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[uid]);
         conversation.interactions.clear();
     }
-    database::getHistory(pimpl_->db, conversation); // will contains "Conversation started"
+    storage::getHistory(pimpl_->db, conversation); // will contains "Conversation started"
     pimpl_->sortConversations();
     emit modelSorted();
     emit conversationCleared(uid);
@@ -836,7 +839,7 @@ ConversationModel::clearInteractionFromConversation(const std::string& convId, c
         try
         {
             auto& conversation = pimpl_->conversations.at(conversationIdx);
-            database::clearInteractionFromConversation(pimpl_->db, convId, interactionId);
+            storage::clearInteractionFromConversation(pimpl_->db, convId, interactionId);
             erased_keys = conversation.interactions.erase(interactionId);
 
             if (conversation.lastMessageUid == interactionId) {
@@ -886,13 +889,14 @@ ConversationModel::retryInteraction(const std::string& convId, const uint64_t& i
                 return;  // Do not retry non outgoing info
 
             if (it->second.type == interaction::Type::TEXT
-            || it->second.type == interaction::Type::OUTGOING_DATA_TRANSFER) {
+            || (it->second.type == interaction::Type::DATA_TRANSFER
+            && interaction::isOutgoing(it->second))) {
                 body = it->second.body;
                 interactionType = it->second.type;
             } else
                 return;
 
-            database::clearInteractionFromConversation(pimpl_->db, convId, interactionId);
+            storage::clearInteractionFromConversation(pimpl_->db, convId, interactionId);
             conversation.interactions.erase(interactionId);
         } catch (const std::out_of_range& e) {
             qDebug() << "can't find interaction from conversation: " << e.what();
@@ -914,14 +918,14 @@ ConversationModel::retryInteraction(const std::string& convId, const uint64_t& i
 void
 ConversationModel::clearAllHistory()
 {
-    database::clearAllHistoryFor(pimpl_->db, owner.id);
+    storage::clearAllHistory(pimpl_->db);
 
     for (auto& conversation : pimpl_->conversations) {
         {
             std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[conversation.uid]);
             conversation.interactions.clear();
         }
-        database::getHistory(pimpl_->db, conversation);
+        storage::getHistory(pimpl_->db, conversation);
     }
     pimpl_->sortConversations();
     emit modelSorted();
@@ -937,21 +941,24 @@ ConversationModel::setInteractionRead(const std::string& convId,
     }
     bool emitUpdated = false;
     interaction::Info itCopy;
-    auto newStatus = interaction::Status::READ;
     {
         std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convId]);
         auto& interactions = pimpl_->conversations[conversationIdx].interactions;
         auto it = interactions.find(interactionId);
         if (it != interactions.end()) {
             emitUpdated = true;
-            if (it->second.status != interaction::Status::UNREAD) return;
-            it->second.status = newStatus;
+            if (it->second.isRead) {
+                return;
+            }
+            it->second.isRead = true;
+            if (pimpl_->conversations[conversationIdx].unreadMessages != 0)
+                pimpl_->conversations[conversationIdx].unreadMessages -= 1;
             itCopy = it->second;
         }
     }
     if (emitUpdated) {
         pimpl_->dirtyConversations = {true, true};
-        database::updateInteractionStatus(pimpl_->db, interactionId, newStatus);
+        storage::setInteractionRead(pimpl_->db, interactionId);
         emit interactionStatusUpdated(convId, interactionId, itCopy);
         emit pimpl_->behaviorController.newReadInteraction(owner.id, convId, interactionId);
     }
@@ -969,15 +976,15 @@ ConversationModel::clearUnreadInteractions(const std::string& convId) {
         auto& interactions = pimpl_->conversations[conversationIdx].interactions;
         std::for_each(interactions.begin(), interactions.end(),
                       [&] (decltype(*interactions.begin())& it) {
-                          if (it.second.type == lrc::api::interaction::Type::TEXT &&
-                              it.second.status == lrc::api::interaction::Status::UNREAD) {
+                          if (!it.second.isRead) {
                               emitUpdated = true;
-                              it.second.status = interaction::Status::READ;
-                              database::updateInteractionStatus(pimpl_->db, it.first, interaction::Status::READ);
+                              it.second.isRead = true;
+                              storage::setInteractionRead(pimpl_->db, it.first);
                           }
                       });
     }
     if (emitUpdated) {
+        pimpl_->conversations[conversationIdx].unreadMessages = 0;
         pimpl_->dirtyConversations = {true, true};
         emit conversationUpdated(convId);
     }
@@ -994,7 +1001,6 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
 , callbacksHandler(callbacksHandler)
 , typeFilter(profile::Type::INVALID)
 , customTypeFilter(profile::Type::INVALID)
-, accountProfileId(database::getProfileId(db, linked.owner.id, "true", linked.owner.profileInfo.uri))
 , behaviorController(behaviorController)
 {
     initConversations();
@@ -1017,7 +1023,6 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
     connect(&callbacksHandler, &CallbacksHandler::accountMessageStatusChanged,
             this, &ConversationModelPimpl::slotUpdateInteractionStatus);
 
-
     // Call related
     connect(&*linked.owner.callModel, &NewCallModel::newIncomingCall,
             this, &ConversationModelPimpl::slotIncomingCall);
@@ -1102,7 +1107,6 @@ ConversationModelPimpl::~ConversationModelPimpl()
     disconnect(&callbacksHandler, &CallbacksHandler::accountMessageStatusChanged,
                this, &ConversationModelPimpl::slotUpdateInteractionStatus);
 
-
     // Call related
     disconnect(&*linked.owner.callModel, &NewCallModel::newIncomingCall,
                this, &ConversationModelPimpl::slotIncomingCall);
@@ -1148,30 +1152,18 @@ ConversationModelPimpl::initConversations()
         return;
 
     // Fill conversations
-    if (accountProfileId.empty()) {
-        // Should not, NewAccountModel must create this profile before.
-        qDebug() << "ConversationModelPimpl::initConversations(), account not in db";
-        return;
-    }
     for (auto const& c : linked.owner.contactModel->getAllContacts())
     {
-        auto contactProfileId = database::getProfileId(db, linked.owner.id, "false",
-        c.second.profileInfo.uri);
-        if (contactProfileId.empty()) {
-            // Should not, ContactModel must create profiles before.
-            qDebug() << "ConversationModelPimpl::initConversations(), contact not in db";
-            continue;
-        }
-        auto common = database::getConversationsBetween(db, accountProfileId, contactProfileId);
-        if (common.empty()) {
+        auto conv = storage::getConversationsWithPeer(db, c.second.profileInfo.uri);
+        if (conv.empty()) {
             // Can't find a conversation with this contact. Start it.
-            auto newConversationsId = database::beginConversationsBetween(db, accountProfileId, contactProfileId);
-            common.emplace_back(std::move(newConversationsId));
+            auto newConversationsId = storage::beginConversationWithPeer(db, c.second.profileInfo.uri, c.second.isTrusted);
+            conv.emplace_back(std::move(newConversationsId));
         }
 
-        addConversationWith(common[0], c.first);
+        addConversationWith(conv[0], c.first);
 
-        auto convIdx = indexOf(common[0]);
+        auto convIdx = indexOf(conv[0]);
 
         // Check if file transfer interactions were left in an incorrect state
         std::lock_guard<std::mutex> lk(interactionsLocks[conversations[convIdx].uid]);
@@ -1183,7 +1175,7 @@ ConversationModelPimpl::initConversations()
                 || interaction.second.status == interaction::Status::TRANSFER_ACCEPTED) {
                 // If a datatransfer was left in a non-terminal status in DB, we switch this status to ERROR
                 // TODO : Improve for DBus clients as daemon and transfer may still be ongoing
-                database::updateInteractionStatus(db, interaction.first, interaction::Status::TRANSFER_ERROR);
+                storage::updateInteractionStatus(db, interaction.first, interaction::Status::TRANSFER_ERROR);
                 interaction.second.status = interaction::Status::TRANSFER_ERROR;
             }
         }
@@ -1196,13 +1188,16 @@ ConversationModelPimpl::initConversations()
     // Load all non treated messages for this account
     QVector<Message> messages = ConfigurationManager::instance().getLastMessages(
         linked.owner.id.c_str(),
-        database::getLastTimestamp(db));
+        storage::getLastTimestamp(db)
+    );
     for (const auto& message : messages) {
         uint64_t timestamp = 0;
         try {
             timestamp = static_cast<uint64_t>(message.received);
         } catch (...) {}
-        addIncomingMessage(message.from.toStdString(), message.payloads["text/plain"].toStdString(), "", timestamp);
+        addIncomingMessage(message.from.toStdString(),
+                           message.payloads["text/plain"].toStdString(),
+                           timestamp);
     }
 }
 
@@ -1264,24 +1259,16 @@ void
 ConversationModelPimpl::slotContactAdded(const std::string& uri)
 {
     auto type = linked.owner.profileInfo.type;
-    std::string interaction = "";
     try {
         auto contact = linked.owner.contactModel->getContact(uri);
         type =  contact.profileInfo.type;
-        interaction = type == profile::Type::PENDING ?
-                QObject::tr("Invitation received").toStdString() :
-                QObject::tr("Contact added").toStdString();
     } catch (...) {}
-    auto contactProfileId = database::getOrInsertProfile(db, uri,
-    linked.owner.id, false, to_string(type));
-    auto conv = database::getConversationsBetween(db, accountProfileId, contactProfileId);
+    profile::Info profileInfo{ uri, {}, {}, type };
+    storage::createOrUpdateProfile(linked.owner.id, profileInfo, true);
+    auto conv = storage::getConversationsWithPeer(db, uri);
     if (conv.empty()) {
         // pass conversation UID through only element
-        conv.emplace_back(
-            database::beginConversationsBetween(db, accountProfileId,
-                contactProfileId, interaction
-            )
-        );
+        conv.emplace_back(storage::beginConversationWithPeer(db, uri));
     }
     // Add the conversation if not already here
     if (indexOf(conv[0]) == -1) {
@@ -1306,37 +1293,36 @@ ConversationModelPimpl::slotPendingContactAccepted(const std::string& uri)
     try {
         type = linked.owner.contactModel->getContact(uri).profileInfo.type;
     } catch (std::out_of_range& e) {}
-    auto contactProfileId = database::getOrInsertProfile(db, uri, linked.owner.id,
-    false, to_string(type));
-    auto conv = database::getConversationsBetween(db, accountProfileId, contactProfileId);
-    if (conv.empty()) {
-        conv.emplace_back(
-            database::beginConversationsBetween(db, accountProfileId,
-                contactProfileId, QObject::tr("Invitation accepted").toStdString()
-            )
-        );
+    profile::Info profileInfo{ uri, {}, {}, type };
+    storage::createOrUpdateProfile(linked.owner.id, profileInfo, true);
+    auto convs = storage::getConversationsWithPeer(db, uri);
+    if (convs.empty()) {
+        convs.emplace_back(storage::beginConversationWithPeer(db, uri));
     } else {
         try {
             auto contact = linked.owner.contactModel->getContact(uri);
-            auto msg = interaction::Info {accountProfileId,
-                                          QObject::tr("Invitation accepted").toStdString(),
-                                          std::time(nullptr), interaction::Type::CONTACT,
-                                          interaction::Status::SUCCEED};
-            auto msgId = database::addMessageToConversation(db, accountProfileId, conv[0], msg);
-            auto convIdx = indexOf(conv[0]);
+            auto interaction = interaction::Info { uri,
+                                                   {},
+                                                   std::time(nullptr),
+                                                   0,
+                                                   interaction::Type::CONTACT,
+                                                   interaction::Status::SUCCESS,
+                                                   true };
+            auto msgId = storage::addMessageToConversation(db, convs[0], interaction);
+            interaction.body = storage::getContactInteractionString(uri, interaction::Status::SUCCESS);
+            auto convIdx = indexOf(convs[0]);
             {
                 std::lock_guard<std::mutex> lk(interactionsLocks[conversations[convIdx].uid]);
-                conversations[convIdx].interactions.emplace(msgId, msg);
+                conversations[convIdx].interactions.emplace(msgId, interaction);
             }
             dirtyConversations = {true, true};
-            emit linked.newInteraction(conv[0], msgId, msg);
+            emit linked.newInteraction(convs[0], msgId, interaction);
         } catch (std::out_of_range& e) {
             qDebug() << "ConversationModelPimpl::slotContactAdded can't find contact";
         }
     }
 }
 
-
 void
 ConversationModelPimpl::slotContactRemoved(const std::string& uri)
 {
@@ -1443,26 +1429,34 @@ ConversationModelPimpl::addConversationWith(const std::string& convId,
     } catch (...) {
         conversation.callId = "";
     }
-    database::getHistory(db, conversation);
-    std::vector<std::function<void(void)>> slotLambdas;
+    storage::getHistory(db, conversation);
+    std::vector<std::function<void(void)>> updateSlots;
     {
         std::lock_guard<std::mutex> lk(interactionsLocks[convId]);
         for (auto& interaction: conversation.interactions) {
-            if (interaction.second.status == interaction::Status::SENDING) {
-                // Get the message status from daemon, else unknown
-                auto id = database::getDaemonIdByInteractionId(db, std::to_string(interaction.first));
-                int status = 0;
-                if (!id.empty()) {
-                    auto msgId = std::stoull(id);
-                    status = ConfigurationManager::instance().getMessageStatus(msgId);
-                }
-                slotLambdas.emplace_back([=]() -> void {
-                    slotUpdateInteractionStatus(linked.owner.id, std::stoull(id), contactUri, status);
-                });
+            if (interaction.second.status != interaction::Status::SENDING) {
+                continue;
+            }
+            // Get the message status from daemon, else unknown
+            auto id = storage::getDaemonIdByInteractionId(db, std::to_string(interaction.first));
+            int status = 0;
+            if (id.empty()) {
+                continue;
+            }
+            try {
+                auto msgId = std::stoull(id);
+                status = ConfigurationManager::instance().getMessageStatus(msgId);
+                updateSlots.emplace_back(
+                    [this, msgId, contactUri, status]() -> void {
+                        auto accId = linked.owner.id;
+                        slotUpdateInteractionStatus(accId, msgId, contactUri, status);
+                    });
+            } catch (const std::exception& e) {
+                qDebug() << "message id was invalid";
             }
         }
     }
-    for (const auto& l: slotLambdas) { l(); }
+    for (const auto& s: updateSlots) { s(); }
 
     conversation.unreadMessages = getNumberOfUnreadMessagesFor(convId);
     conversations.emplace_back(conversation);
@@ -1526,12 +1520,10 @@ ConversationModelPimpl::slotCallStatusChanged(const std::string& callId, int cod
 void
 ConversationModelPimpl::slotCallStarted(const std::string& callId)
 {
+
     try {
         auto call = linked.owner.callModel->getCall(callId);
-        if (call.isOutgoing)
-            addOrUpdateCallMessage(callId, QObject::tr("📞 Outgoing call").toStdString());
-        else
-            addOrUpdateCallMessage(callId, QObject::tr("📞 Incoming call").toStdString());
+        addOrUpdateCallMessage(callId, (!call.isOutgoing ? call.peerUri : ""));
     } catch (std::out_of_range& e) {
         qDebug() << "ConversationModelPimpl::slotCallStarted can't start inexistant call";
     }
@@ -1542,20 +1534,14 @@ ConversationModelPimpl::slotCallEnded(const std::string& callId)
 {
     try {
         auto call = linked.owner.callModel->getCall(callId);
+        // get duration
+        std::time_t duration = 0;
         if (call.startTime.time_since_epoch().count() != 0) {
-            if (call.isOutgoing)
-                addOrUpdateCallMessage(callId, QObject::tr("📞 Outgoing call - ").toStdString()
-                    + linked.owner.callModel->getFormattedCallDuration(callId));
-            else
-                addOrUpdateCallMessage(callId, QObject::tr("📞 Incoming call - ").toStdString()
-                    + linked.owner.callModel->getFormattedCallDuration(callId));
-        } else {
-            if (call.isOutgoing)
-                addOrUpdateCallMessage(callId, QObject::tr("🕽 Missed outgoing call").toStdString());
-            else
-                addOrUpdateCallMessage(callId, QObject::tr("🕽 Missed incoming call").toStdString());
+            auto duration_ns = std::chrono::steady_clock::now() - call.startTime;
+            duration = std::chrono::duration_cast<std::chrono::seconds>(duration_ns).count();
         }
-
+        // add or update call interaction with duration
+        addOrUpdateCallMessage(callId, (!call.isOutgoing ? call.peerUri : ""), duration);
         /* Reset the callId stored in the conversation.
            Do not call selectConversation() since it is already done in slotCallStatusChanged. */
         for (auto& conversation: conversations)
@@ -1569,33 +1555,42 @@ ConversationModelPimpl::slotCallEnded(const std::string& callId)
 }
 
 void
-ConversationModelPimpl::addOrUpdateCallMessage(const std::string& callId, const std::string& body)
+ConversationModelPimpl::addOrUpdateCallMessage(const std::string& callId,
+                                               const std::string& from,
+                                               const std::time_t& duration)
 {
     // Get conversation
-    for (auto& conversation: conversations) {
-        if (conversation.callId == callId) {
-            auto uid = conversation.uid;
-            auto msg = interaction::Info {accountProfileId, body, std::time(nullptr),
-                                         interaction::Type::CALL, interaction::Status::SUCCEED};
-            int msgId = database::addOrUpdateMessage(db, accountProfileId, conversation.uid, msg, callId);
-            auto newInteraction = conversation.interactions.find(msgId) == conversation.interactions.end();
-            if (newInteraction) {
-                conversation.lastMessageUid = msgId;
-                std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
-                conversation.interactions.emplace(msgId, msg);
-            } else {
-                std::lock_guard<std::mutex> lk(interactionsLocks[conversation.uid]);
-                conversation.interactions[msgId] = msg;
-            }
-            dirtyConversations = {true, true};
-            if (newInteraction)
-                emit linked.newInteraction(conversation.uid, msgId, msg);
-            else
-                emit linked.interactionStatusUpdated(conversation.uid, msgId, msg);
-            sortConversations();
-            emit linked.modelSorted();
-        }
+    auto conv_it = std::find_if(conversations.begin(), conversations.end(),
+        [&callId](const conversation::Info& conversation) {
+            return conversation.callId == callId;
+        });
+    if (conv_it == conversations.end()) {
+        return;
     }
+    auto uid = conv_it->uid;
+    std::string uriString = storage::prepareUri(from, linked.owner.profileInfo.type);
+    auto msg = interaction::Info { uriString, {}, std::time(nullptr), duration,
+                                 interaction::Type::CALL, interaction::Status::SUCCESS, true };
+    // update the db
+    int msgId = storage::addOrUpdateMessage(db, conv_it->uid, msg, callId);
+    // now set the formatted call message string in memory only
+    msg.body = storage::getCallInteractionString(uriString, duration);
+    auto newInteraction = conv_it->interactions.find(msgId) == conv_it->interactions.end();
+    if (newInteraction) {
+        conv_it->lastMessageUid = msgId;
+        std::lock_guard<std::mutex> lk(interactionsLocks[conv_it->uid]);
+        conv_it->interactions.emplace(msgId, msg);
+    } else {
+        std::lock_guard<std::mutex> lk(interactionsLocks[conv_it->uid]);
+        conv_it->interactions[msgId] = msg;
+    }
+    dirtyConversations = { true, true };
+    if (newInteraction)
+        emit linked.newInteraction(conv_it->uid, msgId, msg);
+    else
+        emit linked.interactionStatusUpdated(conv_it->uid, msgId, msg);
+    sortConversations();
+    emit linked.modelSorted();
 }
 
 void
@@ -1624,14 +1619,10 @@ ConversationModelPimpl::slotIncomingCallMessage(const std::string& callId, const
         // Show messages in all conversations for conferences.
         for (const auto& conversation: conversations) {
             if (conversation.confId == callId) {
-                if (conversation.participants.empty()) continue;
-                 auto type = linked.owner.profileInfo.type;
-                 try {
-                     type = linked.owner.contactModel->getContact(from).profileInfo.type;
-                 } catch (std::out_of_range& e) {}
-                auto authorProfileId = database::getOrInsertProfile(db, from, linked.owner.id,
-                false, to_string(type));
-                addIncomingMessage(conversation.participants.front(), body, authorProfileId);
+                if (conversation.participants.empty()) {
+                    continue;
+                }
+                addIncomingMessage(from, body);
             }
         }
     } else {
@@ -1643,44 +1634,32 @@ ConversationModelPimpl::slotIncomingCallMessage(const std::string& callId, const
 void
 ConversationModelPimpl::addIncomingMessage(const std::string& from,
                                            const std::string& body,
-                                           const std::string& authorProfileId,
                                            const uint64_t& timestamp)
 {
-    auto type = linked.owner.profileInfo.type;
-    try {
-        type = linked.owner.contactModel->getContact(from).profileInfo.type;
-    } catch (std::out_of_range& e) {}
-    auto contactProfileId = database::getOrInsertProfile(db, from, linked.owner.id,
-    false, to_string(type));
-    auto accountProfileId = database::getProfileId(db, linked.owner.id, "true",
-    linked.owner.profileInfo.uri);
-    auto conv = database::getConversationsBetween(db, accountProfileId, contactProfileId);
-    if (conv.empty()) {
-        conv.emplace_back(database::beginConversationsBetween(
-            db, accountProfileId, contactProfileId,
-            QObject::tr("Invitation received").toStdString()
-        ));
-    }
-    auto authorId = authorProfileId.empty()? contactProfileId: authorProfileId;
-    auto msg = interaction::Info {authorId, body,
-                                  timestamp == 0 ? std::time(nullptr) : static_cast<time_t>(timestamp),
-                                  interaction::Type::TEXT, interaction::Status::UNREAD};
-    auto msgId = database::addMessageToConversation(db, accountProfileId, conv[0], msg);
-    auto conversationIdx = indexOf(conv[0]);
+    auto convIds = storage::getConversationsWithPeer(db, from);
+    if (convIds.empty()) {
+        convIds.emplace_back(storage::beginConversationWithPeer(db, from, false));
+    }
+    auto msg = interaction::Info { from, body,
+                                  timestamp == 0 ? std::time(nullptr) : static_cast<time_t>(timestamp), 0,
+                                  interaction::Type::TEXT, interaction::Status::SUCCESS, false};
+    auto msgId = storage::addMessageToConversation(db, convIds[0], msg);
+    auto conversationIdx = indexOf(convIds[0]);
     // Add the conversation if not already here
     if (conversationIdx == -1) {
-        addConversationWith(conv[0], from);
-        emit linked.newConversation(conv[0]);
+        addConversationWith(convIds[0], from);
+        emit linked.newConversation(convIds[0]);
     } else {
         {
             std::lock_guard<std::mutex> lk(interactionsLocks[conversations[conversationIdx].uid]);
             conversations[conversationIdx].interactions.emplace(msgId, msg);
         }
         conversations[conversationIdx].lastMessageUid = msgId;
+        conversations[conversationIdx].unreadMessages = getNumberOfUnreadMessagesFor(convIds[0]);
     }
     dirtyConversations = {true, true};
-    emit behaviorController.newUnreadInteraction(linked.owner.id, conv[0], msgId, msg);
-    emit linked.newInteraction(conv[0], msgId, msg);
+    emit behaviorController.newUnreadInteraction(linked.owner.id, convIds[0], msgId, msg);
+    emit linked.newInteraction(convIds[0], msgId, msg);
     sortConversations();
     emit linked.modelSorted();
 }
@@ -1699,11 +1678,13 @@ ConversationModelPimpl::slotCallAddedToConference(const std::string& callId, con
 
 void
 ConversationModelPimpl::slotUpdateInteractionStatus(const std::string& accountId,
-                                                    const uint64_t id,
-                                                    const std::string& to,
+                                                    const uint64_t daemon_id,
+                                                    const std::string& peer_uri,
                                                     int status)
 {
-    if (accountId != linked.owner.id) return;
+    if (accountId != linked.owner.id) {
+        return;
+    }
     auto newStatus = interaction::Status::INVALID;
     switch (static_cast<DRing::Account::MessageStates>(status))
     {
@@ -1714,13 +1695,11 @@ ConversationModelPimpl::slotUpdateInteractionStatus(const std::string& accountId
         newStatus = interaction::Status::TRANSFER_CANCELED;
         break;
     case DRing::Account::MessageStates::SENT:
-        newStatus = interaction::Status::SUCCEED;
+    case DRing::Account::MessageStates::READ:
+        newStatus = interaction::Status::SUCCESS;
         break;
     case DRing::Account::MessageStates::FAILURE:
-        newStatus = interaction::Status::FAILED;
-        break;
-    case DRing::Account::MessageStates::READ:
-        newStatus = interaction::Status::READ;
+        newStatus = interaction::Status::FAILURE;
         break;
     case DRing::Account::MessageStates::UNKNOWN:
     default:
@@ -1728,17 +1707,16 @@ ConversationModelPimpl::slotUpdateInteractionStatus(const std::string& accountId
         break;
     }
     // Update database
-    auto interactionId = database::getInteractionIdByDaemonId(db, std::to_string(id));
-    if (interactionId.empty()) return;
+    auto interactionId = storage::getInteractionIdByDaemonId(db, std::to_string(daemon_id));
+    if (interactionId.empty()) {
+        return;
+    }
     auto msgId = std::stoull(interactionId);
-    database::updateInteractionStatus(db, msgId, newStatus);
+    storage::updateInteractionStatus(db, msgId, newStatus);
     // Update conversations
-    auto contactProfileId = database::getProfileId(db, linked.owner.id, "false", to);
-    auto accountProfileId = database::getProfileId(db, linked.owner.id, "true",
-    linked.owner.profileInfo.uri);
-    auto conv = database::getConversationsBetween(db, accountProfileId, contactProfileId);
-    if (!conv.empty()) {
-        auto conversationIdx = indexOf(conv[0]);
+    auto convIds = storage::getConversationsWithPeer(db, peer_uri);
+    if (!convIds.empty()) {
+        auto conversationIdx = indexOf(convIds[0]);
         interaction::Info itCopy;
         bool emitUpdated = false;
         if (conversationIdx != -1) {
@@ -1751,8 +1729,10 @@ ConversationModelPimpl::slotUpdateInteractionStatus(const std::string& accountId
                 itCopy = it->second;
             }
         }
-        if (emitUpdated)
-            emit linked.interactionStatusUpdated(conv[0], msgId, itCopy);
+        if (emitUpdated) {
+            dirtyConversations = { true, true };
+            emit linked.interactionStatusUpdated(convIds[0], msgId, itCopy);
+        }
     }
 }
 
@@ -1760,19 +1740,19 @@ void
 ConversationModelPimpl::slotConferenceRemoved(const std::string& confId)
 {
     // Get conversation
-    for(auto& i : conversations){
-        if (i.confId == confId)
+    for(auto& i : conversations) {
+        if (i.confId == confId) {
             i.confId = "";
+        }
     }
 }
 
 int
 ConversationModelPimpl::getNumberOfUnreadMessagesFor(const std::string& uid)
 {
-    return database::countUnreadFromInteractions(db, uid);
+    return storage::countUnreadFromInteractions(db, uid);
 }
 
-
 void
 ConversationModel::sendFile(const std::string& convUid,
                             const std::string& path,
@@ -1853,7 +1833,7 @@ ConversationModel::cancelTransfer(const std::string& convUid, uint64_t interacti
             it->second.status = interaction::Status::TRANSFER_CANCELED;
 
             // update information in the db
-            database::updateInteractionStatus(pimpl_->db, interactionId, interaction::Status::TRANSFER_CANCELED);
+            storage::updateInteractionStatus(pimpl_->db, interactionId, interaction::Status::TRANSFER_CANCELED);
             emitUpdated = true;
             itCopy = it->second;
         }
@@ -1890,11 +1870,12 @@ ConversationModelPimpl::usefulDataFromDataTransfer(long long dringId, const data
 {
     try {
         interactionId = lrc.getDataTransferModel().getInteractionIdFromDringId(dringId);
-    } catch (...) {
+    } catch (const std::out_of_range& e) {
+        qWarning() << "Couldn't get interaction from daemon Id: " << dringId;
         return false;
     }
 
-    convId = database::conversationIdFromInteractionId(db, interactionId);
+    convId = storage::conversationIdFromInteractionId(db, interactionId);
     return true;
 }
 
@@ -1907,38 +1888,24 @@ ConversationModelPimpl::slotTransferStatusCreated(long long dringId, datatransfe
     const MapStringString accountDetails = ConfigurationManager::instance().getAccountDetails(linked.owner.id.c_str());
     if (accountDetails.empty())
         return;
-    auto type = linked.owner.profileInfo.type;
-    try {
-        type = linked.owner.contactModel->getContact(info.peerUri).profileInfo.type;
-    } catch (std::out_of_range& e) {}
-    auto contactProfileId = database::getOrInsertProfile(db, info.peerUri, info.accountId,
-    false, to_string(type));
-    auto accountProfileId = database::getProfileId(db, info.accountId, "true",
-    linked.owner.profileInfo.uri);
-
     // create a new conversation if needed
-    auto conversation_list = database::getConversationsBetween(db, accountProfileId, contactProfileId);
-    if (conversation_list.empty()) {
-        conversation_list.emplace_back(database::beginConversationsBetween(
-                                           db, accountProfileId, contactProfileId,
-                                           QObject::tr("Invitation received").toStdString()));
+    auto convIds = storage::getConversationsWithPeer(db, info.peerUri);
+    if (convIds.empty()) {
+        convIds.emplace_back(storage::beginConversationWithPeer(db, info.peerUri, false));
     }
 
     // add interaction to the db
-    const auto& convId = conversation_list[0];
-    auto interactionId = database::addDataTransferToConversation(db, accountProfileId, convId, info);
+    const auto& convId = convIds[0];
+    auto interactionId = storage::addDataTransferToConversation(db, convId, info);
 
     // map dringId and interactionId for latter retrivial from client (that only known the interactionId)
     lrc.getDataTransferModel().registerTransferId(dringId, interactionId);
 
-    auto interactioType = info.isOutgoing ?
-        interaction::Type::OUTGOING_DATA_TRANSFER :
-        interaction::Type::INCOMING_DATA_TRANSFER;
-    auto interaction = interaction::Info {info.isOutgoing? accountProfileId : contactProfileId,
-                                          info.isOutgoing? info.path : info.displayName,
-                                          std::time(nullptr),
-                                          interactioType,
-                                          interaction::Status::TRANSFER_CREATED};
+    auto interaction = interaction::Info{ info.isOutgoing ? "" : info.peerUri,
+                                          info.isOutgoing ? info.path : info.displayName,
+                                          std::time(nullptr), 0,
+                                          interaction::Type::DATA_TRANSFER,
+                                          interaction::Status::TRANSFER_CREATED, false};
 
     // prepare interaction Info and emit signal for the client
     auto conversationIdx = indexOf(convId);
@@ -1951,6 +1918,7 @@ ConversationModelPimpl::slotTransferStatusCreated(long long dringId, datatransfe
             conversations[conversationIdx].interactions.emplace(interactionId, interaction);
         }
         conversations[conversationIdx].lastMessageUid = interactionId;
+        conversations[conversationIdx].unreadMessages = getNumberOfUnreadMessagesFor(convId);
     }
     dirtyConversations = {true, true};
     emit behaviorController.newUnreadInteraction(linked.owner.id, convId, interactionId, interaction);
@@ -1974,7 +1942,7 @@ ConversationModelPimpl::slotTransferStatusAwaitingHost(long long dringId, datatr
         return;
 
     auto newStatus = interaction::Status::TRANSFER_AWAITING_HOST;
-    database::updateInteractionStatus(db, interactionId, newStatus);
+    storage::updateInteractionStatus(db, interactionId, newStatus);
 
     auto conversationIdx = indexOf(convId);
     if (conversationIdx != -1) {
@@ -2018,8 +1986,8 @@ void
 ConversationModelPimpl::acceptTransfer(const std::string& convUid, uint64_t interactionId, const std::string& path)
 {
     lrc.getDataTransferModel().accept(interactionId, path, 0);
-    database::updateInteractionBody(db, interactionId, path);
-    database::updateInteractionStatus(db, interactionId, interaction::Status::TRANSFER_ACCEPTED);
+    storage::updateInteractionBody(db, interactionId, path);
+    storage::updateInteractionStatus(db, interactionId, interaction::Status::TRANSFER_ACCEPTED);
 
     // prepare interaction Info and emit signal for the client
     auto conversationIdx = indexOf(convUid);
@@ -2053,7 +2021,7 @@ ConversationModelPimpl::slotTransferStatusOngoing(long long dringId, datatransfe
         return;
 
     auto newStatus = interaction::Status::TRANSFER_ONGOING;
-    database::updateInteractionStatus(db, interactionId, newStatus);
+    storage::updateInteractionStatus(db, interactionId, newStatus);
 
     auto conversationIdx = indexOf(convId);
     if (conversationIdx != -1) {
@@ -2109,7 +2077,7 @@ ConversationModelPimpl::slotTransferStatusFinished(long long dringId, datatransf
         }
         if (emitUpdated) {
             dirtyConversations = {true, true};
-            database::updateInteractionStatus(db, interactionId, newStatus);
+            storage::updateInteractionStatus(db, interactionId, newStatus);
             emit linked.interactionStatusUpdated(convId, interactionId, itCopy);
         }
     }
@@ -2148,7 +2116,7 @@ ConversationModelPimpl::updateTransferStatus(long long dringId, datatransfer::In
         return;
 
     // update information in the db
-    database::updateInteractionStatus(db, interactionId, newStatus);
+    storage::updateInteractionStatus(db, interactionId, newStatus);
 
     // prepare interaction Info and emit signal for the client
     auto conversationIdx = indexOf(convId);
diff --git a/src/database.cpp b/src/database.cpp
index 62088cc5..13a5026f 100644
--- a/src/database.cpp
+++ b/src/database.cpp
@@ -1,9 +1,10 @@
 /****************************************************************************
- *    Copyright (C) 2017-2019 Savoir-faire Linux Inc.                             *
+ *   Copyright (C) 2017-2019 Savoir-faire Linux Inc.                        *
  *   Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>             *
  *   Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>           *
  *   Author: Guillaume Roguez <guillaume.roguez@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             *
@@ -53,160 +54,131 @@ namespace lrc
 
 using namespace api;
 
-Database::Database()
-: QObject()
+Database::Database(const QString& name, const QString& basePath)
+    : QObject()
+    , connectionName_(name)
+    , basePath_(basePath)
+    , version_(DB_VERSION)
 {
     if (not QSqlDatabase::drivers().contains("QSQLITE")) {
         throw std::runtime_error("QSQLITE not supported");
     }
 
-    {
-        QDir dataDir(getPath());
-        // create data directory if not created yet
-        dataDir.mkpath(getPath());
-        QDir oldDataDir(getPath());
-        oldDataDir.cdUp();
-        oldDataDir = oldDataDir
-                    .absolutePath()
-#if defined(_WIN32) || defined(__APPLE__)
-                    + "/ring";
-#else
-                    + "/gnome-ring";
-#endif
-        QStringList filesList = oldDataDir.entryList();
-        QString filename;
-        QDir dir;
-        bool success = true;
-        foreach (filename, filesList) {
-          qDebug() << "Migrate " << oldDataDir.absolutePath() << "/" << filename
-                   << " to " << dataDir.absolutePath() + "/" + filename;
-          if (filename != "." && filename != "..") {
-            success &= dir.rename(oldDataDir.absolutePath() + "/" + filename,
-                                  dataDir.absolutePath() + "/" + filename);
-          }
-        }
-        if (success) {
-            // Remove old directory if the migration is successful.
-            oldDataDir.removeRecursively();
-        }
-    }
+    // initalize the database.
+    db_ = QSqlDatabase::addDatabase("QSQLITE", connectionName_);
+
+    auto databaseFile = QFileInfo(basePath_ + connectionName_ + ".db");
+    QString databaseFileName = databaseFile.fileName();
+    auto absoluteDir = databaseFile.absoluteDir();
 
-    // initialize the database.
-    db_ = QSqlDatabase::addDatabase("QSQLITE");
 #ifdef ENABLE_TEST
-    db_.setDatabaseName(QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).filePath(NAME));
+    databaseFullPath_ = QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).filePath(databaseFileName);
 #else
-    db_.setDatabaseName(QDir(getPath()).filePath(NAME));
+    // make sure the directory exists
+    if (!absoluteDir.exists())
+        absoluteDir.mkpath(".");
+    databaseFullPath_ = absoluteDir.filePath(databaseFileName);
 #endif
+    db_.setDatabaseName(databaseFullPath_);
+}
+
+Database::~Database()
+{
+}
+
+void
+Database::remove()
+{
+    // close db and remove file
+    if (db_.isOpen()) {
+        db_.close();
+    }
+    QFile(databaseFullPath_).remove();
+}
 
+void
+Database::load()
+{
     // open the database.
     if (not db_.open()) {
-        throw std::runtime_error("cannot open database");
+        std::stringstream ss;
+        ss << "cannot open database: " << connectionName_.toStdString();
+        throw std::runtime_error(ss.str());
     }
 
     // if db is empty we create them.
     if (db_.tables().empty()) {
         try {
-            QSqlDatabase::database().transaction();
+            QSqlDatabase::database(connectionName_).transaction();
             createTables();
-            QSqlDatabase::database().commit();
+            QSqlDatabase::database(connectionName_).commit();
         } catch (QueryError& e) {
-            QSqlDatabase::database().rollback();
+            QSqlDatabase::database(connectionName_).rollback();
             throw std::runtime_error("Could not correctly create the database");
         }
-        // NOTE: the migration can take some time.
-        migrateOldFiles();
     } else {
         migrateIfNeeded();
     }
-}
-
-QString
-lrc::Database::getPath()
-{
-    QDir dataDir(QStandardPaths::writableLocation(
-        QStandardPaths::AppLocalDataLocation));
-    // Avoid to depends on the client name.
-    dataDir.cdUp();
-    return dataDir.absolutePath() + "/jami";
-}
-
-Database::~Database()
-{
 
 }
 
 void
 Database::createTables()
 {
-    QSqlQuery query;
-
-    auto tableProfiles = "CREATE TABLE profiles (id INTEGER PRIMARY KEY,  \
-                                                 uri TEXT NOT NULL,       \
-                                                 alias TEXT,              \
-                                                 photo TEXT,              \
-                                                 type TEXT,               \
-                                                 status TEXT)";
-
-    auto tableConversations = "CREATE TABLE conversations (id INTEGER,\
-                                                           participant_id INTEGER, \
-                                                           FOREIGN KEY(participant_id) REFERENCES profiles(id))";
-
-    auto tableInteractions = "CREATE TABLE interactions (id INTEGER PRIMARY KEY,\
-                                                         account_id INTEGER, \
-                                                         author_id INTEGER, \
-                                                         conversation_id INTEGER, \
-                                                         timestamp INTEGER, \
-                                                         body TEXT,     \
-                                                         type TEXT,  \
-                                                         status TEXT, \
-                                                         daemon_id TEXT, \
-                                                         FOREIGN KEY(account_id) REFERENCES profiles(id), \
-                                                         FOREIGN KEY(author_id) REFERENCES profiles(id), \
-                                                         FOREIGN KEY(conversation_id) REFERENCES conversations(id))";
-
-     auto tableProfileAccounts = "CREATE TABLE profiles_accounts (profile_id INTEGER NOT NULL,                    \
-                                                                 account_id TEXT NOT NULL,                        \
-                                                                 is_account TEXT,                                 \
-                                                                 FOREIGN KEY(profile_id) REFERENCES profiles(id))";
-    // add profiles table
-    if (not db_.tables().contains("profiles", Qt::CaseInsensitive)
-        and not query.exec(tableProfiles)) {
-            throw QueryError(query);
-    }
+    QSqlQuery query(db_);
+
+    auto tableConversations  = "CREATE TABLE conversations ( \
+                                    id INTEGER, \
+                                    participant TEXT, \
+                                    extra_data TEXT \
+                                )";
+
+    auto indexConversations = "CREATE INDEX `idx_conversations_uri` ON `conversations` (`participant`)";
+
+    auto tableInteractions   = "CREATE TABLE interactions ( \
+                                    id INTEGER PRIMARY KEY, \
+                                    author TEXT, \
+                                    conversation INTEGER, \
+                                    timestamp INTEGER, \
+                                    body TEXT, \
+                                    type TEXT, \
+                                    status TEXT, \
+                                    is_read INTEGER, \
+                                    daemon_id BIGINT, \
+                                    extra_data TEXT, \
+                                    FOREIGN KEY(conversation) REFERENCES conversations(id) \
+                                )";
+
+    auto indexInteractions = "CREATE INDEX `idx_interactions_uri` ON `interactions` (`author`)";
 
     // add conversations table
-    if (not db_.tables().contains("conversations", Qt::CaseInsensitive)
-        and not query.exec(tableConversations)) {
+    if (!db_.tables().contains("conversations", Qt::CaseInsensitive)) {
+        if (!query.exec(tableConversations) || ! query.exec(indexConversations)) {
             throw QueryError(query);
+        }
     }
 
     // add interactions table
-    if (not db_.tables().contains("interactions", Qt::CaseInsensitive)
-        and not query.exec(tableInteractions)) {
-            throw QueryError(query);
-    }
-
-    // add profiles accounts table
-    if (not db_.tables().contains("profiles_accounts", Qt::CaseInsensitive)
-        and not query.exec(tableProfileAccounts)) {
+    if (!db_.tables().contains("interactions", Qt::CaseInsensitive)) {
+        if (!query.exec(tableInteractions) || !query.exec(indexInteractions)) {
             throw QueryError(query);
+        }
     }
 
-    storeVersion(VERSION);
+    storeVersion(version_);
 }
 
 void
 Database::migrateIfNeeded()
 {
     try {
-        std::string currentVersion = getVersion();
-        if (currentVersion == VERSION) {
+        auto currentVersion = getVersion();
+        if (currentVersion == version_) {
             return;
         }
         QSqlDatabase::database().transaction();
         migrateFromVersion(currentVersion);
-        storeVersion(VERSION);
+        storeVersion(version_);
         QSqlDatabase::database().commit();
     } catch (QueryError& e) {
         QSqlDatabase::database().rollback();
@@ -215,49 +187,33 @@ Database::migrateIfNeeded()
 }
 
 void
-Database::migrateFromVersion(const std::string& currentVersion)
-{
-    if (currentVersion == "1") {
-        migrateSchemaFromVersion1();
-    }
-}
-
-void
-Database::migrateSchemaFromVersion1()
+Database::migrateFromVersion(const QString& currentVersion)
 {
-    QSqlQuery query;
-    auto tableProfileAccounts = "CREATE TABLE profiles_accounts (profile_id INTEGER NOT NULL,                     \
-                                                                 account_id TEXT NOT NULL,                        \
-                                                                 is_account TEXT,                                 \
-                                                                 FOREIGN KEY(profile_id) REFERENCES profiles(id))";
-    // add profiles accounts table
-    if (not db_.tables().contains("profiles_accounts", Qt::CaseInsensitive)
-        and not query.exec(tableProfileAccounts)) {
-            throw QueryError(query);
-    }
-    linkRingProfilesWithAccounts(false);
+    (void)currentVersion;
 }
 
 void
-Database::storeVersion(const std::string& version)
+Database::storeVersion(const QString& version)
 {
-    QSqlQuery query;
+    QSqlQuery query(db_);
 
-    auto storeVersionQuery = std::string("PRAGMA user_version = ") + version;
+    auto storeVersionQuery = "PRAGMA user_version = " + version;
 
-    if (not query.exec(storeVersionQuery.c_str()))
+    if (not query.exec(storeVersionQuery))
         throw QueryError(query);
+
+    qDebug() << "database " << databaseFullPath_ << " version set to:" << version;
 }
 
-std::string
+QString
 Database::getVersion()
 {
-    QSqlQuery query;
-    auto getVersionQuery = std::string("pragma user_version");
-    if (not query.exec(getVersionQuery.c_str()))
+    QSqlQuery query(db_);
+    auto getVersionQuery = "pragma user_version";
+    if (not query.exec(getVersionQuery))
         throw QueryError(query);
     query.first();
-    return  query.value(0).toString().toStdString();
+    return  query.value(0).toString();
 }
 
 int
@@ -265,7 +221,7 @@ Database::insertInto(const std::string& table,                             // "t
                      const std::map<std::string, std::string>& bindCol,    // {{":id", "id"}, {":forename", "colforname"}, {":name", "colname"}}
                      const std::map<std::string, std::string>& bindsSet)   // {{":id", "7"}, {":forename", "alice"}, {":name", "cooper"}}
 {
-    QSqlQuery query;
+    QSqlQuery query(db_);
     std::string columns;
     std::string binds;
 
@@ -303,7 +259,7 @@ Database::update(const std::string& table,                              // "test
                  const std::string& where,                              // "contact=:name AND id=:id
                  const std::map<std::string, std::string>& bindsWhere)  // {{":name", "toto"}, {":id", "65"}}
 {
-    QSqlQuery query;
+    QSqlQuery query(db_);
 
     auto prepareStr = std::string("UPDATE " + table + " SET " + set + " WHERE " + where);
     query.prepare(prepareStr.c_str());
@@ -324,10 +280,11 @@ Database::select(const std::string& select,                            // "id",
                  const std::string& where,                             // "contact=:name AND id=:id
                  const std::map<std::string, std::string>& bindsWhere) // {{":name", "toto"}, {":id", "65"}}
 {
-    QSqlQuery query;
+    QSqlQuery query(db_);
     std::string columnsSelect;
 
-    auto prepareStr = std::string("SELECT " + select + " FROM " + table + " WHERE " + where);
+    auto prepareStr = std::string("SELECT " + select + " FROM " + table +
+                                  (where.empty() ? "" : (" WHERE " + where)));
     query.prepare(prepareStr.c_str());
 
     for (const auto& entry : bindsWhere)
@@ -355,7 +312,7 @@ Database::count(const std::string& count, // "id", "body", ...
                 const std::string& where, // "contact=:name AND id=:id"
                 const std::map<std::string, std::string>& bindsWhere) // {{":name", "toto"}, {":id", "65"}}
 {
-    QSqlQuery query;
+    QSqlQuery query(db_);
     std::string columnsSelect;
     auto prepareStr = std::string("SELECT count(" + count + ") FROM " + table + " WHERE " + where);
     query.prepare(prepareStr.c_str());
@@ -375,7 +332,7 @@ Database::deleteFrom(const std::string& table,                             // "t
                      const std::string& where,                             // "contact=:name AND id=:id
                      const std::map<std::string, std::string>& bindsWhere) // {{":name", "toto"}, {":id", "65"}}
 {
-    QSqlQuery query;
+    QSqlQuery query(db_);
 
     auto prepareStr = std::string("DELETE FROM " + table + " WHERE " + where);
     query.prepare(prepareStr.c_str());
@@ -387,6 +344,18 @@ Database::deleteFrom(const std::string& table,                             // "t
         throw QueryDeleteError(query, table, where, bindsWhere);
 }
 
+void
+Database::truncateTable(const std::string& table)
+{
+    QSqlQuery query(db_);
+
+    auto prepareStr = std::string("TRUNCATE TABLE " + table);
+    query.prepare(prepareStr.c_str());
+
+    if (not query.exec())
+        throw QueryTruncateError(query, table);
+}
+
 Database::QueryError::QueryError(const QSqlQuery& query)
     : std::runtime_error(query.lastError().text().toStdString())
     , query(query)
@@ -484,20 +453,140 @@ Database::QueryDeleteError::details()
     return oss.str();
 }
 
+Database::QueryTruncateError::QueryTruncateError(const QSqlQuery& query,
+    const std::string& table)
+    : QueryError(query)
+    , table(table)
+{}
+
+std::string
+Database::QueryTruncateError::details()
+{
+    std::ostringstream oss;
+    oss << "paramaters sent :";
+    oss << "table = " << table.c_str();
+    return oss.str();
+}
+
+/*****************************************************************************
+ *                                                                           *
+ *                               LegacyDatabase                              *
+ *                                                                           *
+ ****************************************************************************/
+LegacyDatabase::LegacyDatabase(const QString& basePath)
+    : Database("ring", basePath)
+{
+    version_ = LEGACY_DB_VERSION;
+}
+
+LegacyDatabase::~LegacyDatabase()
+{
+    remove();
+    // remove old LRC files
+    QDir(basePath_ + "text/").removeRecursively();
+    QDir(basePath_ + "profiles/").removeRecursively();
+    QDir(basePath_ + "peer_profiles/").removeRecursively();
+}
+
+void
+LegacyDatabase::load()
+{
+    // open the database.
+    if (not db_.open()) {
+        std::stringstream ss;
+        ss << "cannot open database: " << connectionName_.toStdString();
+        throw std::runtime_error(ss.str());
+    }
+
+    // if db is empty we create them.
+    if (db_.tables().empty()) {
+        try {
+            QSqlDatabase::database(connectionName_).transaction();
+            createTables();
+            QSqlDatabase::database(connectionName_).commit();
+        } catch (QueryError& e) {
+            QSqlDatabase::database(connectionName_).rollback();
+            throw std::runtime_error("Could not correctly create the database");
+        }
+        migrateOldFiles();
+    } else {
+        migrateIfNeeded();
+    }
+}
+
+void
+LegacyDatabase::createTables()
+{
+    QSqlQuery query(db_);
+
+    auto tableProfiles = "CREATE TABLE profiles (id INTEGER PRIMARY KEY,  \
+                                                 uri TEXT NOT NULL,       \
+                                                 alias TEXT,              \
+                                                 photo TEXT,              \
+                                                 type TEXT,               \
+                                                 status TEXT)";
+
+    auto tableConversations = "CREATE TABLE conversations (id INTEGER,\
+                                                           participant_id INTEGER, \
+                                                           FOREIGN KEY(participant_id) REFERENCES profiles(id))";
+
+    auto tableInteractions = "CREATE TABLE interactions (id INTEGER PRIMARY KEY,\
+                                                         account_id INTEGER, \
+                                                         author_id INTEGER, \
+                                                         conversation_id INTEGER, \
+                                                         timestamp INTEGER, \
+                                                         body TEXT,     \
+                                                         type TEXT,  \
+                                                         status TEXT, \
+                                                         daemon_id TEXT, \
+                                                         FOREIGN KEY(account_id) REFERENCES profiles(id), \
+                                                         FOREIGN KEY(author_id) REFERENCES profiles(id), \
+                                                         FOREIGN KEY(conversation_id) REFERENCES conversations(id))";
+
+    auto tableProfileAccounts = "CREATE TABLE profiles_accounts (profile_id INTEGER NOT NULL,                    \
+                                                                 account_id TEXT NOT NULL,                        \
+                                                                 is_account TEXT,                                 \
+                                                                 FOREIGN KEY(profile_id) REFERENCES profiles(id))";
+    // add profiles table
+    if (not db_.tables().contains("profiles", Qt::CaseInsensitive)
+        and not query.exec(tableProfiles)) {
+        throw QueryError(query);
+    }
+
+    // add conversations table
+    if (not db_.tables().contains("conversations", Qt::CaseInsensitive)
+        and not query.exec(tableConversations)) {
+        throw QueryError(query);
+    }
+
+    // add interactions table
+    if (not db_.tables().contains("interactions", Qt::CaseInsensitive)
+        and not query.exec(tableInteractions)) {
+        throw QueryError(query);
+    }
+
+    // add profiles accounts table
+    if (not db_.tables().contains("profiles_accounts", Qt::CaseInsensitive)
+        and not query.exec(tableProfileAccounts)) {
+        throw QueryError(query);
+    }
+
+    storeVersion(version_);
+}
+
 void
-Database::migrateOldFiles()
+LegacyDatabase::migrateOldFiles()
 {
     migrateLocalProfiles();
     migratePeerProfiles();
     migrateTextHistory();
     linkRingProfilesWithAccounts(true);
-    // NOTE we don't remove old files for now.
 }
 
 void
-Database::migrateLocalProfiles()
+LegacyDatabase::migrateLocalProfiles()
 {
-    const QDir profilesDir = getPath() + "/profiles/";
+    const QDir profilesDir = basePath_ + "profiles/";
     const QStringList entries = profilesDir.entryList({QStringLiteral("*.vcf")}, QDir::Files);
     foreach (const QString& item , entries) {
         auto filePath = profilesDir.path() + '/' + item;
@@ -514,7 +603,6 @@ Database::migrateLocalProfiles()
         const auto alias = vCard[lrc::vCard::Property::FORMATTED_NAME];
         const auto avatar = vCard["PHOTO;ENCODING=BASE64;TYPE=PNG"];
 
-
         const QStringList accountIds = ConfigurationManager::instance().getAccountList();
         for (auto accountId : accountIds) {
             MapStringString account = ConfigurationManager::instance().
@@ -562,9 +650,9 @@ Database::migrateLocalProfiles()
 }
 
 void
-Database::migratePeerProfiles()
+LegacyDatabase::migratePeerProfiles()
 {
-    const QDir profilesDir = getPath() + "/peer_profiles/";
+    const QDir profilesDir = basePath_ + "peer_profiles/";
 
     const QStringList entries = profilesDir.entryList({QStringLiteral("*.vcf")}, QDir::Files);
 
@@ -601,10 +689,10 @@ Database::migratePeerProfiles()
 }
 
 void
-Database::migrateTextHistory()
+LegacyDatabase::migrateTextHistory()
 {
     // load all text recordings so we can recover CMs that are not in the call history
-    QDir dir(getPath() + "/text/");
+    QDir dir(basePath_ + "text/");
     if (dir.exists()) {
         // get .json files, sorted by time, latest first
         QStringList filters;
@@ -733,29 +821,31 @@ Database::migrateTextHistory()
 }
 
 void
-Database::updateProfileAccountForContact(const std::string& contactURI,
-                                         const std::string& accountId)
+LegacyDatabase::migrateFromVersion(const QString& currentVersion)
 {
-    auto profileIds = select("id", "profiles","uri=:uri",
-                             {{":uri", contactURI}})
-                             .payloads;
-    if (profileIds.empty()) {
-        return;
+    if (currentVersion == "1") {
+        migrateSchemaFromVersion1();
     }
-    auto rows = select("profile_id", "profiles_accounts",
-    "account_id=:account_id AND is_account=:is_account", {{":account_id", accountId},
-    {":is_account", "false"}}).payloads;
-    if (std::find(rows.begin(), rows.end(), profileIds[0]) == rows.end()) {
-        insertInto("profiles_accounts",
-                   {{":profile_id", "profile_id"}, {":account_id", "account_id"},
-                   {":is_account", "is_account"}},
-                   {{":profile_id", profileIds[0]}, {":account_id", accountId},
-                   {":is_account", "false"}});
+}
+
+void
+LegacyDatabase::migrateSchemaFromVersion1()
+{
+    QSqlQuery query(db_);
+    auto tableProfileAccounts = "CREATE TABLE profiles_accounts (profile_id INTEGER NOT NULL,                     \
+                                                                 account_id TEXT NOT NULL,                        \
+                                                                 is_account TEXT,                                 \
+                                                                 FOREIGN KEY(profile_id) REFERENCES profiles(id))";
+    // add profiles accounts table
+    if (not db_.tables().contains("profiles_accounts", Qt::CaseInsensitive)
+        and not query.exec(tableProfileAccounts)) {
+        throw QueryError(query);
     }
+    linkRingProfilesWithAccounts(false);
 }
 
 void
-Database::linkRingProfilesWithAccounts(bool contactsOnly)
+LegacyDatabase::linkRingProfilesWithAccounts(bool contactsOnly)
 {
     const QStringList accountIds =
     ConfigurationManager::instance().getAccountList();
@@ -832,4 +922,26 @@ Database::linkRingProfilesWithAccounts(bool contactsOnly)
     }
 }
 
+void
+LegacyDatabase::updateProfileAccountForContact(const std::string& contactURI,
+    const std::string& accountId)
+{
+    auto profileIds = select("id", "profiles", "uri=:uri",
+        { {":uri", contactURI} })
+        .payloads;
+    if (profileIds.empty()) {
+        return;
+    }
+    auto rows = select("profile_id", "profiles_accounts",
+        "account_id=:account_id AND is_account=:is_account", { {":account_id", accountId},
+        {":is_account", "false"} }).payloads;
+    if (std::find(rows.begin(), rows.end(), profileIds[0]) == rows.end()) {
+        insertInto("profiles_accounts",
+            { {":profile_id", "profile_id"}, {":account_id", "account_id"},
+            {":is_account", "is_account"} },
+            { {":profile_id", profileIds[0]}, {":account_id", accountId},
+            {":is_account", "false"} });
+    }
+}
+
 } // namespace lrc
diff --git a/src/database.h b/src/database.h
index 03e551ce..deddf189 100644
--- a/src/database.h
+++ b/src/database.h
@@ -1,9 +1,10 @@
 /****************************************************************************
- *    Copyright (C) 2017-2019 Savoir-faire Linux Inc.                             *
+ *   Copyright (C) 2017-2019 Savoir-faire Linux Inc.                        *
  *   Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>             *
  *   Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>           *
  *   Author: Guillaume Roguez <guillaume.roguez@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             *
@@ -20,25 +21,27 @@
  ***************************************************************************/
 #pragma once
 
-// Std
-#include <memory>
-#include <string>
-#include <stdexcept>
-
 // Qt
-#include <qobject.h>
+#include <QObject>
 #include <QtCore/QDir>
 #include <QtSql/QSqlQuery>
 #include <QtCore/QStandardPaths>
+#include <QDebug>
+
+// Std
+#include <memory>
+#include <string>
+#include <stdexcept>
+#include <type_traits>
 
 namespace lrc
 {
 
-static constexpr auto VERSION = "1.1";
-static constexpr auto NAME = "ring.db";
+static constexpr auto LEGACY_DB_VERSION = "1.1";
+static constexpr auto DB_VERSION        = "1";
 
 /**
-  *  @brief Class that communicates with the database.
+  *  @brief Base class that communicates with a database.
   *  @note not thread safe.
   */
 class Database : public QObject {
@@ -47,12 +50,16 @@ class Database : public QObject {
 public:
     /**
      * Create a database on the user system.
+     * @param the name for which to construct the db.
      * @exception QueryError database query error.
      */
-    Database();
-
+    Database(const QString& name, const QString& basePath);
     ~Database();
 
+    void remove();
+
+    virtual void load();
+
     /**
      * A structure which contains result(s) returned by a database query.
      */
@@ -151,6 +158,19 @@ public:
         const std::map<std::string, std::string> bindsWhere;
     };
 
+    /**
+     * Exception on database truncate operation.
+     * details() returns more information.
+     */
+    class QueryTruncateError final : public QueryError {
+    public:
+        explicit QueryTruncateError(const QSqlQuery& query,
+            const std::string& table);
+        std::string details() override;
+
+        const std::string table;
+    };
+
     /**
      * Insert value(s) inside a table.
      * @param table where to perfom the action on.
@@ -197,6 +217,14 @@ public:
     void deleteFrom(const std::string& table,
                     const std::string& where,
                     const std::map<std::string, std::string>& bindsWhere);
+    /**
+     * Delete all rows from a table(truncate).
+     * @param table where to perfom the action on.
+     * @exception QueryDeleteError delete query failed.
+     *
+     * @note usually, identifiers between where and bindsWhere, are equals.
+     */
+    void truncateTable(const std::string& table);
     /**
      * Select data from table.
      * @param select column(s) to select.e
@@ -225,27 +253,86 @@ public:
     int count(const std::string& count, const std::string& table,
               const std::string& where, const std::map<std::string, std::string>& bindsWhere);
 
-    static QString getPath();
-private:
-    void createTables();
-    void storeVersion(const std::string& version);
+    QString basePath_;
+
+protected:
+    virtual void createTables();
+
+    /**
+     * Migration helpers.
+     */
+    void migrateIfNeeded();
+    void storeVersion(const QString& version);
+    QString getVersion();
+
+    virtual void migrateFromVersion(const QString& version);
+
+    QString version_;
+    QString connectionName_;
+    QString databaseFullPath_;
+    QSqlDatabase db_;
+};
+
+/**
+  *  @brief A legacy database to help migrate from the single db epoch.
+  *  @note not thread safe.
+  */
+class LegacyDatabase final : public Database {
+    Q_OBJECT
 
+public:
     /**
-     * Migration helpers. Parse JSON for history and VCards and add it into the database.
+     * Create a migratory legacy database.
+     * @exception QueryError database query error.
+     */
+    LegacyDatabase(const QString& basePath);
+    ~LegacyDatabase();
+
+    void load() override;
+
+protected:
+    void createTables() override;
+
+private:
+    /**
+     * Migration helpers from old LRC. Parse JSON for history and VCards and add it into the database.
      */
     void migrateOldFiles();
     void migrateLocalProfiles();
     void migratePeerProfiles();
     void migrateTextHistory();
-    void linkRingProfilesWithAccounts(bool contactsOnly);
-    void migrateIfNeeded();
-    std::string  getVersion();
-    void migrateFromVersion(const std::string& version);
+
+    void migrateFromVersion(const QString& version) override;
+
+    /**
+     * Migration helpers from version 1
+     */
     void migrateSchemaFromVersion1();
+    void linkRingProfilesWithAccounts(bool contactsOnly);
     void updateProfileAccountForContact(const std::string& contactURI,
-                                        const std::string& accountID);
-
-    QSqlDatabase db_;
+        const std::string& accountID);
 };
 
+namespace DatabaseFactory
+{
+template<typename T, class... Args>
+std::enable_if_t<
+    std::is_constructible<T, Args...>::value,
+    std::shared_ptr<Database>
+>
+create(Args&&... args) {
+    auto pdb = std::static_pointer_cast<Database>(
+        std::make_shared<T>(std::forward<Args>(args)...)
+    );
+    // To allow override of the db load method we don't
+    // call it from the constructor.
+    try {
+        pdb->load();
+    } catch (const std::runtime_error& e) {
+        throw std::runtime_error(e);
+    }
+    return pdb;
+}
+} // DatabaseFactory
+
 } // namespace lrc
diff --git a/src/datatransfermodel.cpp b/src/datatransfermodel.cpp
index 41a428d2..0db15909 100644
--- a/src/datatransfermodel.cpp
+++ b/src/datatransfermodel.cpp
@@ -76,7 +76,6 @@ DataTransferModel::Impl::Impl(DataTransferModel& up_link)
     , upLink {up_link}
 {}
 
-
 void
 DataTransferModel::registerTransferId(long long dringId, int interactionId)
 {
@@ -85,7 +84,6 @@ DataTransferModel::registerTransferId(long long dringId, int interactionId)
     pimpl_->lrc2dringIdMap.emplace(interactionId, dringId);
 }
 
-
 DataTransferModel::DataTransferModel()
     : QObject()
     , pimpl_ { std::make_unique<Impl>(*this) }
@@ -113,14 +111,6 @@ DataTransferModel::transferInfo(long long ringId, datatransfer::Info& lrc_info)
 {
     DataTransferInfo infoFromDaemon;
     if (ConfigurationManager::instance().dataTransferInfo(ringId, infoFromDaemon) == 0) {
-#if 0
-        int interactionId;
-        try {
-            interactionId = pimpl_->dring2lrcIdMap.at(ringId);
-        } catch (...) {
-            interactionId = -1;
-        }
-#endif
         //lrc_info.uid = ?
         lrc_info.status = convertDataTransferEvent(DRing::DataTransferEventCode(infoFromDaemon.lastEvent));
         lrc_info.isOutgoing = !(infoFromDaemon.flags & (1 << uint32_t(DRing::DataTransferFlags::direction)));
diff --git a/src/lrc.cpp b/src/lrc.cpp
index f6e44c45..f612b7f8 100644
--- a/src/lrc.cpp
+++ b/src/lrc.cpp
@@ -1,5 +1,5 @@
 /****************************************************************************
- *    Copyright (C) 2017-2019 Savoir-faire Linux Inc.                             *
+ *    Copyright (C) 2017-2019 Savoir-faire Linux Inc.                       *
  *   Author : Nicolas Jäger <nicolas.jager@savoirfairelinux.com>            *
  *   Author : Sébastien Blin <sebastien.blin@savoirfairelinux.com>          *
  *                                                                          *
@@ -30,11 +30,11 @@
 #include "api/datatransfermodel.h"
 #include "api/newaccountmodel.h"
 #include "callbackshandler.h"
-#include "database.h"
 #include "dbus/callmanager.h"
 #include "dbus/configurationmanager.h"
 #include "dbus/instancemanager.h"
 #include "dbus/configurationmanager.h"
+#include "authority/storagehelper.h"
 
 namespace lrc
 {
@@ -45,23 +45,23 @@ class LrcPimpl
 {
 
 public:
-    LrcPimpl(Lrc& linked);
+    LrcPimpl(Lrc& linked, MigrationCb& willMigrateCb, MigrationCb& didMigrateCb);
 
     const Lrc& linked;
     std::unique_ptr<BehaviorController> behaviorController;
     std::unique_ptr<CallbacksHandler> callbackHandler;
-    std::unique_ptr<Database> database;
     std::unique_ptr<NewAccountModel> accountModel;
     std::unique_ptr<DataTransferModel> dataTransferModel;
     std::unique_ptr<AVModel> AVModel_;
+
 };
 
-Lrc::Lrc()
+Lrc::Lrc(MigrationCb willDoMigrationCb, MigrationCb didDoMigrationCb)
 {
     // Ensure Daemon is running/loaded (especially on non-DBus platforms)
     // before instantiating LRC and its members
     InstanceManager::instance();
-    lrcPimpl_ = std::make_unique<LrcPimpl>(*this);
+    lrcPimpl_ = std::make_unique<LrcPimpl>(*this, willDoMigrationCb, didDoMigrationCb);
 }
 
 Lrc::~Lrc()
@@ -124,6 +124,12 @@ Lrc::dbusIsValid()
 #endif
 }
 
+void
+Lrc::subscribeToDebugReceived()
+{
+    lrcPimpl_->callbackHandler->subscribeToDebugReceived();
+}
+
 std::vector<std::string>
 Lrc::activeCalls()
 {
@@ -136,12 +142,11 @@ Lrc::activeCalls()
     return result;
 }
 
-LrcPimpl::LrcPimpl(Lrc& linked)
+LrcPimpl::LrcPimpl(Lrc& linked, MigrationCb& willMigrateCb, MigrationCb& didMigrateCb)
 : linked(linked)
 , behaviorController(std::make_unique<BehaviorController>())
 , callbackHandler(std::make_unique<CallbacksHandler>(linked))
-, database(std::make_unique<Database>())
-, accountModel(std::make_unique<NewAccountModel>(linked, *database, *callbackHandler, *behaviorController))
+, accountModel(std::make_unique<NewAccountModel>(linked, *callbackHandler, *behaviorController, willMigrateCb, didMigrateCb))
 , dataTransferModel {std::make_unique<DataTransferModel>()}
 , AVModel_ {std::make_unique<AVModel>(*callbackHandler)}
 {
diff --git a/src/newaccountmodel.cpp b/src/newaccountmodel.cpp
index 14151bbf..04208dbc 100644
--- a/src/newaccountmodel.cpp
+++ b/src/newaccountmodel.cpp
@@ -1,8 +1,9 @@
 /****************************************************************************
- *    Copyright (C) 2017-2019 Savoir-faire Linux Inc.                             *
+ *    Copyright (C) 2017-2019 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             *
@@ -36,7 +37,7 @@
 #include "api/newcodecmodel.h"
 #include "api/newdevicemodel.h"
 #include "api/behaviorcontroller.h"
-#include "authority/databasehelper.h"
+#include "authority/storagehelper.h"
 #include "callbackshandler.h"
 #include "database.h"
 #include "vcard.h"
@@ -61,17 +62,20 @@ class NewAccountModelPimpl: public QObject
 public:
     NewAccountModelPimpl(NewAccountModel& linked,
                          Lrc& lrc,
-                         Database& database,
                          const CallbacksHandler& callbackHandler,
-                         const BehaviorController& behaviorController);
+                         const BehaviorController& behaviorController,
+                         MigrationCb& willMigrateCb,
+                         MigrationCb& didMigrateCb);
     ~NewAccountModelPimpl();
 
+    using AccountInfoDbMap = std::map<std::string,
+                                      std::pair<account::Info, std::shared_ptr<Database>>>;
+
     NewAccountModel& linked;
     Lrc& lrc;
     const CallbacksHandler& callbacksHandler;
-    Database& database;
-    NewAccountModel::AccountInfoMap accounts;
     const BehaviorController& behaviorController;
+    AccountInfoDbMap accounts;
 
     // Synchronization tools
     std::mutex m_mutex_account;
@@ -83,9 +87,10 @@ public:
     /**
      * 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 std::string& accountId);
+    void addToAccounts(const std::string& accountId, std::shared_ptr<Database> db = nullptr);
 
     /**
      * Remove account from accounts list. Emit accountRemoved.
@@ -147,11 +152,13 @@ public Q_SLOTS:
 };
 
 NewAccountModel::NewAccountModel(Lrc& lrc,
-                                 Database& database,
                                  const CallbacksHandler& callbacksHandler,
-                                 const BehaviorController& behaviorController)
+                                 const BehaviorController& behaviorController,
+                                 MigrationCb& willMigrateCb,
+                                 MigrationCb& didMigrateCb)
 : QObject()
-, pimpl_(std::make_unique<NewAccountModelPimpl>(*this, lrc, database, callbacksHandler, behaviorController))
+, pimpl_(std::make_unique<NewAccountModelPimpl>(*this, lrc, callbacksHandler, behaviorController,
+                                                willMigrateCb, didMigrateCb))
 {
 }
 
@@ -166,9 +173,9 @@ NewAccountModel::getAccountList() const
     const QStringList accountIds = ConfigurationManager::instance().getAccountList();
 
     for (auto const& id : accountIds) {
-        auto accountInfo = pimpl_->accounts.find(id.toStdString());
+        auto account = pimpl_->accounts.find(id.toStdString());
         // Do not include accounts flagged for removal
-        if (accountInfo != pimpl_->accounts.end() && accountInfo->second.valid)
+        if (account != pimpl_->accounts.end() && account->second.first.valid)
             accountsId.emplace_back(id.toStdString());
     }
 
@@ -178,11 +185,12 @@ NewAccountModel::getAccountList() const
 void
 NewAccountModel::setAccountEnabled(const std::string& accountId, bool enabled) const
 {
-    auto accountInfo = pimpl_->accounts.find(accountId);
-    if (accountInfo == pimpl_->accounts.end()) {
+    auto account = pimpl_->accounts.find(accountId);
+    if (account == pimpl_->accounts.end()) {
         throw std::out_of_range("NewAccountModel::getAccountConfig, can't find " + accountId);
     }
-    accountInfo->second.enabled = enabled;
+    auto& accountInfo = account->second.first;
+    accountInfo.enabled = enabled;
     ConfigurationManager::instance().sendRegister(QString::fromStdString(accountId), enabled);
 }
 
@@ -190,11 +198,11 @@ void
 NewAccountModel::setAccountConfig(const std::string& accountId,
                                   const account::ConfProperties_t& confProperties) const
 {
-    auto accountInfoEntry = pimpl_->accounts.find(accountId);
-    if (accountInfoEntry == pimpl_->accounts.end()) {
+    auto account = pimpl_->accounts.find(accountId);
+    if (account == pimpl_->accounts.end()) {
         throw std::out_of_range("NewAccountModel::save, can't find " + accountId);
     }
-    auto& accountInfo = accountInfoEntry->second;
+    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
@@ -223,45 +231,41 @@ NewAccountModel::setAccountConfig(const std::string& accountId,
 account::ConfProperties_t
 NewAccountModel::getAccountConfig(const std::string& accountId) const
 {
-    auto accountInfo = pimpl_->accounts.find(accountId);
-    if (accountInfo == pimpl_->accounts.end()) {
+    auto account = pimpl_->accounts.find(accountId);
+    if (account == pimpl_->accounts.end()) {
         throw std::out_of_range("NewAccountModel::getAccountConfig, can't find " + accountId);
     }
-
-    return accountInfo->second.confProperties;
+    auto& accountInfo = account->second.first;
+    return accountInfo.confProperties;
 }
 
 void
 NewAccountModel::setAlias(const std::string& accountId, const std::string& alias)
 {
-    auto accountInfo = pimpl_->accounts.find(accountId);
-    if (accountInfo == pimpl_->accounts.end()) {
+    auto account = pimpl_->accounts.find(accountId);
+    if (account == pimpl_->accounts.end()) {
         throw std::out_of_range("NewAccountModel::setAlias, can't find " + accountId);
     }
-    accountInfo->second.profileInfo.alias = alias;
-    auto accountProfileId = authority::database::getOrInsertProfile(pimpl_->database,
-    accountInfo->second.profileInfo.uri, accountId, true,
-    to_string(accountInfo->second.profileInfo.type));
-    if (!accountProfileId.empty()) {
-        authority::database::setAliasForProfileId(pimpl_->database, accountProfileId, alias);
-    }
+    auto& accountInfo = account->second.first;
+    accountInfo.profileInfo.alias = alias;
+
+    authority::storage::createOrUpdateProfile(accountInfo.id, accountInfo.profileInfo);
+
     emit profileUpdated(accountId);
 }
 
 void
 NewAccountModel::setAvatar(const std::string& accountId, const std::string& avatar)
 {
-    auto accountInfo = pimpl_->accounts.find(accountId);
-    if (accountInfo == pimpl_->accounts.end()) {
+    auto account = pimpl_->accounts.find(accountId);
+    if (account == pimpl_->accounts.end()) {
         throw std::out_of_range("NewAccountModel::setAvatar, can't find " + accountId);
     }
-    accountInfo->second.profileInfo.avatar = avatar;
-    auto accountProfileId = authority::database::getOrInsertProfile(pimpl_->database,
-    accountInfo->second.profileInfo.uri, accountId, true,
-    to_string(accountInfo->second.profileInfo.type));
-    if (!accountProfileId.empty()) {
-        authority::database::setAvatarForProfileId(pimpl_->database, accountProfileId, avatar);
-    }
+    auto& accountInfo = account->second.first;
+    accountInfo.profileInfo.avatar = avatar;
+
+    authority::storage::createOrUpdateProfile(accountInfo.id, accountInfo.profileInfo);
+
     emit profileUpdated(accountId);
 }
 
@@ -301,13 +305,13 @@ NewAccountModel::changeAccountPassword(const std::string& accountId,
 void
 NewAccountModel::flagFreeable(const std::string& accountId) const
 {
-    auto accountInfo = pimpl_->accounts.find(accountId);
-    if (accountInfo == pimpl_->accounts.end())
+    auto account = pimpl_->accounts.find(accountId);
+    if (account == pimpl_->accounts.end())
         throw std::out_of_range("NewAccountModel::flagFreeable, can't find " + accountId);
 
     {
         std::lock_guard<std::mutex> lock(pimpl_->m_mutex_account_removal);
-        accountInfo->second.freeable = true;
+        account->second.first.freeable = true;
     }
     pimpl_->m_condVar_account_removal.notify_all();
 }
@@ -319,25 +323,26 @@ NewAccountModel::getAccountInfo(const std::string& accountId) const
     if (accountInfo == pimpl_->accounts.end())
         throw std::out_of_range("NewAccountModel::getAccountInfo, can't find " + accountId);
 
-    return accountInfo->second;
+    return accountInfo->second.first;
 }
 
 NewAccountModelPimpl::NewAccountModelPimpl(NewAccountModel& linked,
                                            Lrc& lrc,
-                                           Database& database,
                                            const CallbacksHandler& callbacksHandler,
-                                           const BehaviorController& behaviorController)
+                                           const BehaviorController& behaviorController,
+                                           MigrationCb& willMigrateCb,
+                                           MigrationCb& didMigrateCb)
 : linked(linked)
 , lrc {lrc}
 , behaviorController(behaviorController)
 , callbacksHandler(callbacksHandler)
-, database(database)
 , username_changed(false)
 {
     const QStringList accountIds = ConfigurationManager::instance().getAccountList();
-
-    for (auto& id : accountIds)
-        addToAccounts(id.toStdString());
+    auto accountDbs = authority::storage::migrateIfNeeded(accountIds, willMigrateCb, didMigrateCb);
+    for (const auto& id : accountIds) {
+        addToAccounts(id.toStdString(), accountDbs.at(accountIds.indexOf(id)));
+    }
 
     connect(&callbacksHandler, &CallbacksHandler::accountsChanged, this, &NewAccountModelPimpl::updateAccounts);
     connect(&callbacksHandler, &CallbacksHandler::accountStatusChanged, this, &NewAccountModelPimpl::slotAccountStatusChanged);
@@ -362,7 +367,7 @@ NewAccountModelPimpl::updateAccounts()
     // Detect removed accounts
     std::list<std::string> toBeRemoved;
     for (auto& it : accounts) {
-        auto& accountInfo = it.second;
+        auto& accountInfo = it.second.first;
         if (!accountIds.contains(QString::fromStdString(accountInfo.id))) {
             qDebug("detected account removal %s", accountInfo.id.c_str());
             toBeRemoved.push_back(accountInfo.id);
@@ -375,15 +380,15 @@ NewAccountModelPimpl::updateAccounts()
 
     // Detect new accounts
     for (auto& id : accountIds) {
-        auto accountInfo = accounts.find(id.toStdString());
-        if (accountInfo == accounts.end()) {
+        auto account = accounts.find(id.toStdString());
+        if (account == accounts.end()) {
             qDebug("detected new account %s", id.toStdString().c_str());
             addToAccounts(id.toStdString());
-            auto updatedAccountInfo = accounts.find(id.toStdString());
-            if (updatedAccountInfo == accounts.end()) {
+            auto updatedAccount = accounts.find(id.toStdString());
+            if (updatedAccount == accounts.end()) {
                 return;
             }
-            if (updatedAccountInfo->second.profileInfo.type == profile::Type::SIP) {
+            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.
@@ -407,7 +412,7 @@ NewAccountModelPimpl::slotAccountStatusChanged(const std::string& accountID, con
         return;
     }
 
-    auto& accountInfo = it->second;
+    auto& accountInfo = it->second.first;
 
     if (accountInfo.profileInfo.type != profile::Type::SIP) {
         if (status != api::account::Status::INITIALIZING
@@ -431,14 +436,15 @@ NewAccountModelPimpl::slotAccountStatusChanged(const std::string& accountID, con
 void
 NewAccountModelPimpl::slotAccountDetailsChanged(const std::string& accountId, const std::map<std::string, std::string>& details)
 {
-    auto accountInfo = accounts.find(accountId);
-    if (accountInfo == accounts.end()) {
+    auto account = accounts.find(accountId);
+    if (account == accounts.end()) {
         throw std::out_of_range("NewAccountModelPimpl::slotAccountDetailsChanged, can't find " + accountId);
     }
-    accountInfo->second.fromDetails(convertMap(details));
+    auto& accountInfo = account->second.first;
+    accountInfo.fromDetails(convertMap(details));
     if (username_changed) {
         username_changed = false;
-        accountInfo->second.registeredName = new_username;
+        accountInfo.registeredName = new_username;
         emit linked.profileUpdated(accountId);
     }
     emit linked.accountStatusChanged(accountId);
@@ -472,8 +478,8 @@ NewAccountModelPimpl::slotNameRegistrationEnded(const std::string& accountId, in
     {
     case 0: {
         convertedStatus = account::RegisterNameStatus::SUCCESS;
-        auto accountInfo = accounts.find(accountId);
-        if (accountInfo != accounts.end() && accountInfo->second.registeredName.empty()) {
+        auto account = accounts.find(accountId);
+        if (account != accounts.end() && account->second.first.registeredName.empty()) {
             auto conf = linked.getAccountConfig(accountId);
             username_changed = true;
             new_username = name;
@@ -533,9 +539,26 @@ NewAccountModelPimpl::slotMigrationEnded(const std::string& accountId, bool ok)
 }
 
 void
-NewAccountModelPimpl::addToAccounts(const std::string& accountId)
-{
-    auto it = accounts.emplace(accountId, account::Info());
+NewAccountModelPimpl::addToAccounts(const std::string& accountId,
+                                    std::shared_ptr<Database> db)
+{
+    if (db == nullptr) {
+        try {
+            auto appPath = authority::storage::getPath();
+            auto dbName = QString::fromStdString(accountId + "/history");
+            db = DatabaseFactory::create<Database>(dbName, appPath);
+            // create the profiles path if necessary
+            QDir profilesDir(appPath + QString::fromStdString(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) {
         qDebug("failed to add new account: id already present in map");
@@ -543,45 +566,26 @@ NewAccountModelPimpl::addToAccounts(const std::string& accountId)
     }
 
     // Init profile
-    account::Info& newAcc = (it.first)->second;
-    newAcc.id = accountId;
+    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.c_str());
-    newAcc.fromDetails(details);
-
-    // Add profile to database
-    std::string accountType = newAcc.profileInfo.type == profile::Type::RING ?
-                                   DRing::Account::ProtocolNames::RING :
-                                   DRing::Account::ProtocolNames::SIP;
-    if (accountType == DRing::Account::ProtocolNames::SIP || !newAcc.profileInfo.uri.empty()) {
-        auto accountProfileId = authority::database::getOrInsertProfile(database,
-                                                                    newAcc.profileInfo.uri,
-                                                                    accountId,
-                                                                    true,
-                                                                    accountType,
-                                                                    newAcc.profileInfo.alias,
-                                                                    "");
-
-        // Retrieve avatar from database
-        newAcc.profileInfo.avatar = authority::database::getAvatarForProfileId(database, accountProfileId);
-
-        // Retrieve alias from database
-        newAcc.profileInfo.alias = authority::database::getAliasForProfileId(database, accountProfileId);
-    }
+    newAccInfo.fromDetails(details);
 
     // Init models for this account
-    newAcc.callModel = std::make_unique<NewCallModel>(newAcc, callbacksHandler);
-    newAcc.contactModel = std::make_unique<ContactModel>(newAcc, database, callbacksHandler, behaviorController);
-    newAcc.conversationModel = std::make_unique<ConversationModel>(newAcc, lrc, database, callbacksHandler, behaviorController);
-    newAcc.peerDiscoveryModel = std::make_unique<PeerDiscoveryModel>(callbacksHandler, accountId);
-    newAcc.deviceModel = std::make_unique<NewDeviceModel>(newAcc, callbacksHandler);
-    newAcc.codecModel = std::make_unique<NewCodecModel>(newAcc, callbacksHandler);
-    newAcc.accountModel = &linked;
+    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.c_str());
     std::string daemonStatus = volatileDetails[DRing::Account::ConfProperties::Registration::STATUS].toStdString();
-    newAcc.status = lrc::api::account::to_status(daemonStatus);
+    newAccInfo.status = lrc::api::account::to_status(daemonStatus);
 }
 
 void
@@ -589,16 +593,19 @@ NewAccountModelPimpl::removeFromAccounts(const std::string& accountId)
 {
     /* Update db before waiting for the client to stop using the structs is fine
        as long as we don't free anything */
-    auto accountInfo = accounts.find(accountId);
-    if (accountInfo == accounts.end()) {
+    auto account = accounts.find(accountId);
+    if (account == accounts.end()) {
         return;
     }
-    authority::database::removeAccount(database, accountId);
+    auto& accountInfo = account->second.first;
+    auto& accountDb = *(account->second.second);
+    accountDb.remove();
+    authority::storage::removeAccount(accountId);
 
     /* 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. */
-    accounts[accountId].valid = false;
+    accountInfo.valid = false;
     emit linked.accountRemoved(accountId);
 
 #ifdef CHK_FREEABLE_BEFORE_ERASE_ACCOUNT
@@ -879,58 +886,12 @@ NewAccountModel::setTopAccount(const std::string& accountId)
 std::string
 NewAccountModel::accountVCard(const std::string& accountId, bool compressImage) const
 {
-    auto accountInfo = pimpl_->accounts.find(accountId);
-    if (accountInfo == pimpl_->accounts.end()) {
+    auto account = pimpl_->accounts.find(accountId);
+    if (account == pimpl_->accounts.end()) {
         return {};
     }
-    std::string vCardStr = vCard::Delimiter::BEGIN_TOKEN;
-    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
-    vCardStr += vCard::Property::VERSION;
-    vCardStr += ":2.1";
-    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
-    vCardStr += vCard::Property::UID;
-    vCardStr += ":";
-    vCardStr += accountInfo->second.id;
-    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
-    vCardStr += vCard::Property::FORMATTED_NAME;
-    vCardStr += ":";
-    vCardStr += accountInfo->second.profileInfo.alias;
-    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
-    if (accountInfo->second.profileInfo.type == profile::Type::RING) {
-        vCardStr += vCard::Property::TELEPHONE;
-        vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
-        vCardStr += "other:ring:";
-        vCardStr += accountInfo->second.profileInfo.uri;
-        vCardStr += vCard::Delimiter::END_LINE_TOKEN;
-    } else {
-        vCardStr += vCard::Property::TELEPHONE;
-        vCardStr += accountInfo->second.profileInfo.uri;
-        vCardStr += vCard::Delimiter::END_LINE_TOKEN;
-    }
-    vCardStr += vCard::Property::PHOTO;
-    vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
-    vCardStr += "ENCODING=BASE64";
-    vCardStr += vCard::Delimiter::SEPARATOR_TOKEN;
-    vCardStr += compressImage ? "TYPE=JPEG:" : "TYPE=PNG:";
-    vCardStr += compressImage ? compressedAvatar(accountInfo->second.profileInfo.avatar) : accountInfo->second.profileInfo.avatar;
-    vCardStr += vCard::Delimiter::END_LINE_TOKEN;
-    vCardStr += vCard::Delimiter::END_TOKEN;
-    return vCardStr;
-}
-std::string NewAccountModel::compressedAvatar(const std::string& img) const
-{
-    QImage image;
-    const bool ret = image.loadFromData(QByteArray::fromBase64(img.c_str()), 0);
-    if (!ret) {
-        qDebug() << "vCard image loading failed";
-        return img;
-    }
-    QByteArray bArray;
-    QBuffer buffer(&bArray);
-    buffer.open(QIODevice::WriteOnly);
-    image.scaled({128,128}).save(&buffer, "JPEG", 90);
-    auto b64Img = bArray.toBase64().trimmed();
-    return std::string(b64Img.constData(), b64Img.length());
+    auto& accountInfo = account->second.first;
+    return authority::storage::vcard::profileToVcard(accountInfo.profileInfo, compressImage);
 }
 
 } // namespace lrc
diff --git a/src/newcallmodel.cpp b/src/newcallmodel.cpp
index dc94bf54..a1fbc963 100644
--- a/src/newcallmodel.cpp
+++ b/src/newcallmodel.cpp
@@ -29,6 +29,7 @@
 #include "api/contact.h"
 #include "api/contactmodel.h"
 #include "api/newaccountmodel.h"
+#include "authority/storagehelper.h"
 #include "dbus/callmanager.h"
 #include "vcard.h"
 #include "video/renderer.h"
@@ -183,7 +184,7 @@ NewCallModel::getCallFromURI(const std::string& uri, bool notOver) const
     // peer url = ring:uri or sip number
     auto url = (owner.profileInfo.type != profile::Type::SIP && uri.find("ring:") == std::string::npos) ? "ring:" + uri : uri;
     for (const auto& call: pimpl_->calls) {
-        if (call.second->peer == url) {
+        if (call.second->peerUri == url) {
             if (!notOver || call.second->status != call::Status::ENDED)
                 return *call.second;
         }
@@ -198,7 +199,7 @@ NewCallModel::getConferenceFromURI(const std::string& uri) const
         if (call.second->type == call::Type::CONFERENCE) {
             QStringList callList = CallManager::instance().getParticipantList(call.first.c_str());
             foreach(const auto& callId, callList) {
-                if (pimpl_->calls[callId.toStdString()]->peer == uri) {
+                if (pimpl_->calls[callId.toStdString()]->peerUri == uri) {
                     return *call.second;
                 }
             }
@@ -214,29 +215,29 @@ NewCallModel::getCall(const std::string& uid) const
 }
 
 std::string
-NewCallModel::createCall(const std::string& url, bool isAudioOnly)
+NewCallModel::createCall(const std::string& uri, bool isAudioOnly)
 {
 #ifdef ENABLE_LIBWRAP
     auto callId = isAudioOnly ? CallManager::instance().placeCall(owner.id.c_str(),
-                                                                  url.c_str(),
+                                                                  uri.c_str(),
                                                                   {{"AUDIO_ONLY", "true"}})
-                                  : CallManager::instance().placeCall(owner.id.c_str(), url.c_str());
+                                  : CallManager::instance().placeCall(owner.id.c_str(), uri.c_str());
 #else // dbus
     // do not use auto here (QDBusPendingReply<QString>)
     QString callId = isAudioOnly ? CallManager::instance().placeCallWithDetails(owner.id.c_str(),
-                                                                                url.c_str(),
+                                                                                uri.c_str(),
                                                                                 {{"AUDIO_ONLY", "true"}})
-                                 : CallManager::instance().placeCall(owner.id.c_str(), url.c_str());
+                                 : CallManager::instance().placeCall(owner.id.c_str(), uri.c_str());
 #endif // ENABLE_LIBWRAP
 
     if (callId.isEmpty()) {
-        qDebug() << "no call placed between (account :" << owner.id.c_str() << ", contact :" << url.c_str() << ")";
+        qDebug() << "no call placed between (account: " << owner.id.c_str() << ", contact: " << uri.c_str() << ")";
         return "";
     }
 
     auto callInfo = std::make_shared<call::Info>();
     callInfo->id = callId.toStdString();
-    callInfo->peer = url;
+    callInfo->peerUri = uri;
     callInfo->isOutgoing = true;
     callInfo->status =  call::Status::SEARCHING;
     callInfo->type =  call::Type::DIALOG;
@@ -393,22 +394,8 @@ NewCallModel::getFormattedCallDuration(const std::string& callId) const
     if (startTime.time_since_epoch().count() == 0) return "00:00";
     auto now = std::chrono::steady_clock::now();
     auto d = std::chrono::duration_cast<std::chrono::seconds>(
-             now.time_since_epoch() - startTime.time_since_epoch()).count();
-
-    std::string formattedString;
-    auto minutes = d / 60;
-    auto seconds = d % 60;
-    if (minutes > 0) {
-        formattedString += std::to_string(minutes) + ":";
-        if (formattedString.length() == 2) {
-            formattedString = "0" + formattedString;
-        }
-    } else {
-        formattedString += "00:";
-    }
-    if (seconds < 10) formattedString += "0";
-    formattedString += std::to_string(seconds);
-    return formattedString;
+        now.time_since_epoch() - startTime.time_since_epoch()).count();
+    return authority::storage::getFormattedCallDuration(d);
 }
 
 bool
@@ -428,7 +415,6 @@ NewCallModel::getSIPCallStatusString(const short& statusCode)
     return "";
 }
 
-
 NewCallModelPimpl::NewCallModelPimpl(const NewCallModel& linked, const CallbacksHandler& callbacksHandler)
 : linked(linked)
 , callbacksHandler(callbacksHandler)
@@ -467,9 +453,9 @@ NewCallModelPimpl::initCallFromDaemon()
             callInfo->startTime = now - std::chrono::seconds(diff);
             callInfo->status = call::to_status(details["CALL_STATE"].toStdString());
             auto endId = details["PEER_NUMBER"].indexOf("@");
-            callInfo->peer = details["PEER_NUMBER"].left(endId).toStdString();
+            callInfo->peerUri = details["PEER_NUMBER"].left(endId).toStdString();
             if (linked.owner.profileInfo.type == lrc::api::profile::Type::RING) {
-                callInfo->peer = "ring:" + callInfo->peer;
+                callInfo->peerUri = "ring:" + callInfo->peerUri;
             }
             callInfo->videoMuted = details["VIDEO_MUTED"] == "true";
             callInfo->audioMuted = details["AUDIO_MUTED"] == "true";
@@ -510,7 +496,6 @@ NewCallModelPimpl::initConferencesFromDaemon()
     }
 }
 
-
 void
 NewCallModel::sendSipMessage(const std::string& callId, const std::string& body) const
 {
@@ -536,16 +521,18 @@ NewCallModel::hangupCallsAndConferences()
 void
 NewCallModelPimpl::slotIncomingCall(const std::string& accountId, const std::string& callId, const std::string& fromId)
 {
-    if (linked.owner.id != accountId) return;
+    if (linked.owner.id != accountId) {
+        return;
+    }
 
     // do not use auto here (QDBusPendingReply<MapStringString>)
     MapStringString callDetails = CallManager::instance().getCallDetails(callId.c_str());
 
     auto callInfo = std::make_shared<call::Info>();
     callInfo->id = callId;
-    // peer url = ring:uri or sip number
-    auto url = (linked.owner.profileInfo.type != profile::Type::SIP && fromId.find("ring:") == std::string::npos) ? "ring:" + fromId : fromId;
-    callInfo->peer = url;
+    // peer uri = ring:<jami_id> or sip number
+    auto uri = (linked.owner.profileInfo.type != profile::Type::SIP && fromId.find("ring:") == std::string::npos) ? "ring:" + fromId : fromId;
+    callInfo->peerUri = uri;
     callInfo->isOutgoing = false;
     callInfo->status =  call::Status::INCOMING_RINGING;
     callInfo->type =  call::Type::DIALOG;
diff --git a/src/typedefs.h b/src/typedefs.h
index 7d8656be..ab429508 100644
--- a/src/typedefs.h
+++ b/src/typedefs.h
@@ -194,7 +194,6 @@ private:
 #pragma GCC diagnostic ignored "-Wunused-function"
 #endif
 
-
 #define DECLARE_ENUM_FLAGS(T)\
 DO_PRAGMA(GCC diagnostic push)\
 DO_PRAGMA(GCC diagnostic ignored "-Wunused-function")\
@@ -203,3 +202,6 @@ __attribute__ ((unused)) static FlagPack<T> operator|(const T& first, const T& s
    return p | second; \
 } \
 DO_PRAGMA(GCC diagnostic pop)
+
+#include <functional>
+typedef std::function<void()> MigrationCb;
\ No newline at end of file
diff --git a/src/uri.cpp b/src/uri.cpp
index 4ceae91e..e1d97b95 100644
--- a/src/uri.cpp
+++ b/src/uri.cpp
@@ -200,7 +200,7 @@ QString URI::hostname() const
  */
 bool URI::hasHostname() const
 {
-    return hostname().isEmpty();
+    return !hostname().isEmpty();
 }
 
 /**
@@ -443,6 +443,25 @@ QString URI::userinfo() const
     return pimpl_->m_Userinfo;
 }
 
+void URI::setUserinfo(const QString & userinfo)
+{
+    pimpl_->m_Userinfo = userinfo;
+}
+
+void URI::setHostname(const QString& hostname)
+{
+    pimpl_->m_ExtHostname = hostname;
+}
+
+void URI::setPort(const QString& port)
+{
+    try {
+        pimpl_->m_Port = port.toInt();
+    } catch (...) {
+        qWarning() << "Can't convert port to integer";
+    }
+}
+
 /**
  * Sometime, some metadata can be used to deduce the scheme even if it wasn't
  * originally known.
diff --git a/src/uri.h b/src/uri.h
index 46d5e076..2c146608 100644
--- a/src/uri.h
+++ b/src/uri.h
@@ -174,6 +174,9 @@ public:
     ProtocolHint protocolHint() const;
 
     // Setter
+    void setUserinfo(const QString& userinfo);
+    void setHostname(const QString& hostname);
+    void setPort(const QString& port);
     void setSchemeType(SchemeType t);
 
     // Converter
diff --git a/src/vcard.h b/src/vcard.h
index a5b7c111..84fc64a6 100644
--- a/src/vcard.h
+++ b/src/vcard.h
@@ -64,6 +64,11 @@ struct Property {
    constexpr static const char* TIME_ZONE           = "TZ";
    constexpr static const char* TITLE               = "TITLE";
    constexpr static const char* URL                 = "URL";
+   constexpr static const char* BASE64              = "ENCODING=BASE64";
+   constexpr static const char* TYPE_PNG            = "TYPE=PNG";
+   constexpr static const char* TYPE_JPEG           = "TYPE=JPEG";
+   constexpr static const char* PHOTO_PNG           = "PHOTO;ENCODING=BASE64;TYPE=PNG";
+   constexpr static const char* PHOTO_JPEG          = "PHOTO;ENCODING=BASE64;TYPE=JPEG";
 
    constexpr static const char* X_RINGACCOUNT       = "X-RINGACCOUNTID";
 };
-- 
GitLab