Skip to content
Snippets Groups Projects
Select Git revision
  • 6a419d8c00b41411d3a5d00a5e84279992c1f859
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • release/201811
  • release/201808
  • releases/beta1
  • packaging
  • native
  • release-0.2.x
  • 1.0.0
  • 0.2.0
  • 0.1.1
  • 0.1.0
25 results

instantmessagingwidget.cpp

Blame
    • Nicolas Jager's avatar
      1ddb38d5
      ui: update callwidget buttons · 1ddb38d5
      Nicolas Jager authored
      - buttons are .css and .ui based.
      
      - .svg used as icons are set in .ui.
      
      - .svg updated, filled circles removed.
      
      - backgrounds and borders are set in .css.
      
      - redundant and no-more-used code in callwidget.cpp/instantmessagingwidget.cpp removed.
      
      Change-Id: I9c52801a49d503c3c6a7dc8df9ae02ff90bb6a67
      Tuleap: #148
      1ddb38d5
      History
      ui: update callwidget buttons
      Nicolas Jager authored
      - buttons are .css and .ui based.
      
      - .svg used as icons are set in .ui.
      
      - .svg updated, filled circles removed.
      
      - backgrounds and borders are set in .css.
      
      - redundant and no-more-used code in callwidget.cpp/instantmessagingwidget.cpp removed.
      
      Change-Id: I9c52801a49d503c3c6a7dc8df9ae02ff90bb6a67
      Tuleap: #148
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    databasehelper.cpp 23.00 KiB
    /****************************************************************************
     *    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