LCOV - code coverage report
Current view: top level - src/jamidht - conversationrepository.h (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 14 16 87.5 %
Date: 2024-04-19 08:05:40 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2019-2024 Savoir-faire Linux Inc.
       3             :  *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
       4             :  *
       5             :  *  This program is free software; you can redistribute it and/or modify
       6             :  *  it under the terms of the GNU General Public License as published by
       7             :  *  the Free Software Foundation; either version 3 of the License, or
       8             :  *  (at your option) any later version.
       9             :  *
      10             :  *  This program is distributed in the hope that it will be useful,
      11             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :  *  GNU General Public License for more details.
      14             :  *
      15             :  *  You should have received a copy of the GNU General Public License
      16             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      17             :  */
      18             : #pragma once
      19             : 
      20             : #include <optional>
      21             : #include <git2.h>
      22             : #include <memory>
      23             : #include <opendht/default_types.h>
      24             : #include <string>
      25             : #include <vector>
      26             : 
      27             : #include "def.h"
      28             : 
      29             : using GitPackBuilder = std::unique_ptr<git_packbuilder, decltype(&git_packbuilder_free)>;
      30             : using GitRepository = std::unique_ptr<git_repository, decltype(&git_repository_free)>;
      31             : using GitRevWalker = std::unique_ptr<git_revwalk, decltype(&git_revwalk_free)>;
      32             : using GitCommit = std::unique_ptr<git_commit, decltype(&git_commit_free)>;
      33             : using GitAnnotatedCommit
      34             :     = std::unique_ptr<git_annotated_commit, decltype(&git_annotated_commit_free)>;
      35             : using GitIndex = std::unique_ptr<git_index, decltype(&git_index_free)>;
      36             : using GitTree = std::unique_ptr<git_tree, decltype(&git_tree_free)>;
      37             : using GitRemote = std::unique_ptr<git_remote, decltype(&git_remote_free)>;
      38             : using GitReference = std::unique_ptr<git_reference, decltype(&git_reference_free)>;
      39             : using GitSignature = std::unique_ptr<git_signature, decltype(&git_signature_free)>;
      40             : using GitObject = std::unique_ptr<git_object, decltype(&git_object_free)>;
      41             : using GitDiff = std::unique_ptr<git_diff, decltype(&git_diff_free)>;
      42             : using GitDiffStats = std::unique_ptr<git_diff_stats, decltype(&git_diff_stats_free)>;
      43             : using GitIndexConflictIterator
      44             :     = std::unique_ptr<git_index_conflict_iterator, decltype(&git_index_conflict_iterator_free)>;
      45             : 
      46             : namespace jami {
      47             : 
      48             : using DeviceId = dht::PkId;
      49             : 
      50             : constexpr auto EFETCH = 1;
      51             : constexpr auto EINVALIDMODE = 2;
      52             : constexpr auto EVALIDFETCH = 3;
      53             : constexpr auto EUNAUTHORIZED = 4;
      54             : constexpr auto ECOMMIT = 5;
      55             : 
      56             : class JamiAccount;
      57             : 
      58             : struct LogOptions
      59             : {
      60             :     std::string from {};
      61             :     std::string to {};
      62             :     uint64_t nbOfCommits {0};  // maximum number of commits wanted
      63             :     bool skipMerge {false};    // Do not include merge commits in the log. Used by the module to get
      64             :                                // last interaction without potential merges
      65             :     bool includeTo {false};    // If we want or not the "to" commit [from-to] or [from-to)
      66             :     bool fastLog {false};      // Do not parse content, used mostly to count
      67             :     bool logIfNotFound {true}; // Add a warning in the log if commit is not found
      68             : 
      69             :     std::string authorUri {}; // filter commits from author
      70             : };
      71             : 
      72             : struct Filter
      73             : {
      74             :     std::string author;
      75             :     std::string lastId;
      76             :     std::string regexSearch;
      77             :     std::string type;
      78             :     int64_t after {0};
      79             :     int64_t before {0};
      80             :     uint32_t maxResult {0};
      81             :     bool caseSensitive {false};
      82             : };
      83             : 
      84             : struct GitAuthor
      85             : {
      86             :     std::string name {};
      87             :     std::string email {};
      88             : };
      89             : 
      90             : enum class ConversationMode : int { ONE_TO_ONE = 0, ADMIN_INVITES_ONLY, INVITES_ONLY, PUBLIC };
      91             : 
      92             : struct ConversationCommit
      93             : {
      94             :     std::string id {};
      95             :     std::vector<std::string> parents {};
      96             :     GitAuthor author {};
      97             :     std::vector<uint8_t> signed_content {};
      98             :     std::vector<uint8_t> signature {};
      99             :     std::string commit_msg {};
     100             :     std::string linearized_parent {};
     101             :     int64_t timestamp {0};
     102             : };
     103             : 
     104             : enum class MemberRole { ADMIN = 0, MEMBER, INVITED, BANNED, LEFT };
     105             : 
     106             : struct ConversationMember
     107             : {
     108             :     std::string uri;
     109             :     MemberRole role;
     110             : 
     111        2007 :     std::map<std::string, std::string> map() const
     112             :     {
     113        2007 :         std::string rolestr;
     114        2007 :         if (role == MemberRole::ADMIN) {
     115         600 :             rolestr = "admin";
     116        1407 :         } else if (role == MemberRole::MEMBER) {
     117        1357 :             rolestr = "member";
     118          50 :         } else if (role == MemberRole::INVITED) {
     119          43 :             rolestr = "invited";
     120           7 :         } else if (role == MemberRole::BANNED) {
     121           7 :             rolestr = "banned";
     122           0 :         } else if (role == MemberRole::LEFT) {
     123           0 :             rolestr = "left"; // For one to one
     124             :         }
     125             : 
     126        8028 :         return {{"uri", uri}, {"role", rolestr}};
     127        2007 :     }
     128       13911 :     MSGPACK_DEFINE(uri, role)
     129             : };
     130             : 
     131             : enum class CallbackResult { Skip, Break, Ok };
     132             : 
     133             : using PreConditionCb
     134             :     = std::function<CallbackResult(const std::string&, const GitAuthor&, const GitCommit&)>;
     135             : using PostConditionCb
     136             :     = std::function<bool(const std::string&, const GitAuthor&, ConversationCommit&)>;
     137             : 
     138             : /**
     139             :  * This class gives access to the git repository that represents the conversation
     140             :  */
     141             : class LIBJAMI_TESTABLE ConversationRepository
     142             : {
     143             : public:
     144             :     #ifdef LIBJAMI_TESTABLE
     145             :     static bool DISABLE_RESET; // Some tests inject bad files so resetHard() will break the test
     146             :     #endif
     147             :     /**
     148             :      * Creates a new repository, with initial files, where the first commit hash is the conversation id
     149             :      * @param account       The related account
     150             :      * @param mode          The wanted mode
     151             :      * @param otherMember   The other uri
     152             :      * @return  the conversation repository object
     153             :      */
     154             :     static LIBJAMI_TESTABLE std::unique_ptr<ConversationRepository> createConversation(
     155             :         const std::shared_ptr<JamiAccount>& account,
     156             :         ConversationMode mode = ConversationMode::INVITES_ONLY,
     157             :         const std::string& otherMember = "");
     158             : 
     159             :     /**
     160             :      * Clones a conversation on a remote device
     161             :      * @note This will use the socket registered for the conversation with JamiAccount::addGitSocket()
     162             :      * @param account           The account getting the conversation
     163             :      * @param deviceId          Remote device
     164             :      * @param conversationId    Conversation to clone
     165             :      * @param checkCommitCb     Used if commits should be treated
     166             :      */
     167             :     static LIBJAMI_TESTABLE std::unique_ptr<ConversationRepository> cloneConversation(
     168             :         const std::shared_ptr<JamiAccount>& account,
     169             :         const std::string& deviceId,
     170             :         const std::string& conversationId,
     171             :         std::function<void(std::vector<ConversationCommit>)>&& checkCommitCb = {});
     172             : 
     173             :     /**
     174             :      * Open a conversation repository for an account and an id
     175             :      * @param account       The related account
     176             :      * @param id            The conversation id
     177             :      */
     178             :     ConversationRepository(const std::weak_ptr<JamiAccount>& account, const std::string& id);
     179             :     ~ConversationRepository();
     180             : 
     181             :     /**
     182             :      * Write the certificate in /members and commit the change
     183             :      * @param uri    Member to add
     184             :      * @return the commit id if successful
     185             :      */
     186             :     std::string addMember(const std::string& uri);
     187             : 
     188             :     /**
     189             :      * Fetch a remote repository via the given socket
     190             :      * @note This will use the socket registered for the conversation with JamiAccount::addGitSocket()
     191             :      * @note will create a remote identified by the deviceId
     192             :      * @param remoteDeviceId    Remote device id to fetch
     193             :      * @return if the operation was successful
     194             :      */
     195             :     bool fetch(const std::string& remoteDeviceId);
     196             : 
     197             :     /**
     198             :      * Retrieve remote head. Can be useful after a fetch operation
     199             :      * @param remoteDeviceId        The remote name
     200             :      * @param branch                Remote branch to check (default: main)
     201             :      * @return the commit id pointed
     202             :      */
     203             :     std::string remoteHead(const std::string& remoteDeviceId,
     204             :                            const std::string& branch = "main") const;
     205             : 
     206             :     /**
     207             :      * Return the conversation id
     208             :      */
     209             :     const std::string& id() const;
     210             : 
     211             :     /**
     212             :      * Add a new commit to the conversation
     213             :      * @param msg           The commit message of the commit
     214             :      * @param verifyDevice  If we need to validate that certificates are correct (used for testing)
     215             :      * @return <empty> on failure, else the message id
     216             :      */
     217             :     std::string commitMessage(const std::string& msg, bool verifyDevice = true);
     218             : 
     219             :     std::vector<std::string> commitMessages(const std::vector<std::string>& msgs);
     220             : 
     221             :     /**
     222             :      * Amend a commit message
     223             :      * @param id      The commit to amend
     224             :      * @param msg     The commit message of the commit
     225             :      * @return <empty> on failure, else the message id
     226             :      */
     227             :     std::string amend(const std::string& id, const std::string& msg);
     228             : 
     229             :     /**
     230             :      * Get commits depending on the options we pass
     231             :      * @return a list of commits
     232             :      */
     233             :     std::vector<ConversationCommit> log(const LogOptions& options = {}) const;
     234             :     void log(PreConditionCb&& preCondition,
     235             :             std::function<void(ConversationCommit&&)>&& emplaceCb,
     236             :             PostConditionCb&& postCondition,
     237             :             const std::string& from = "",
     238             :             bool logIfNotFound = true) const;
     239             :     std::optional<ConversationCommit> getCommit(const std::string& commitId,
     240             :                                                 bool logIfNotFound = true) const;
     241             : 
     242             :     /**
     243             :      * Get parent via topological + date sort in branch main of a commit
     244             :      * @param commitId      id to choice
     245             :      */
     246             :     std::optional<std::string> linearizedParent(const std::string& commitId) const;
     247             : 
     248             :     /**
     249             :      * Merge another branch into the main branch
     250             :      * @param merge_id      The reference to merge
     251             :      * @param force         Should be false, skip validateDevice() ; used for test purpose
     252             :      * @return a pair containing if the merge was successful and the merge commit id
     253             :      * generated if one (can be a fast forward merge without commit)
     254             :      */
     255             :     std::pair<bool, std::string> merge(const std::string& merge_id, bool force = false);
     256             : 
     257             :     /**
     258             :      * Get the common parent between two branches
     259             :      * @param from  The first branch
     260             :      * @param to    The second branch
     261             :      * @return the common parent
     262             :      */
     263             :     std::string mergeBase(const std::string& from, const std::string& to) const;
     264             : 
     265             :     /**
     266             :      * Get current diff stats between two commits
     267             :      * @param oldId     Old commit
     268             :      * @param newId     Recent commit (empty value will compare to the empty repository)
     269             :      * @note "HEAD" is also accepted as parameter for newId
     270             :      * @return diff stats
     271             :      */
     272             :     std::string diffStats(const std::string& newId, const std::string& oldId = "") const;
     273             : 
     274             :     /**
     275             :      * Get changed files from a git diff
     276             :      * @param diffStats     The stats to analyze
     277             :      * @return get the changed files from a git diff
     278             :      */
     279             :     static std::vector<std::string> changedFiles(std::string_view diffStats);
     280             : 
     281             :     /**
     282             :      * Join a repository
     283             :      * @return commit Id
     284             :      */
     285             :     std::string join();
     286             : 
     287             :     /**
     288             :      * Erase self from repository
     289             :      * @return commit Id
     290             :      */
     291             :     std::string leave();
     292             : 
     293             :     /**
     294             :      * Erase repository
     295             :      */
     296             :     void erase();
     297             : 
     298             :     /**
     299             :      * Get conversation's mode
     300             :      * @return the mode
     301             :      */
     302             :     ConversationMode mode() const;
     303             : 
     304             :     /**
     305             :      * The voting system is divided in two parts. The voting phase where
     306             :      * admins can decide an action (such as kicking someone)
     307             :      * and the resolving phase, when > 50% of the admins voted, we can
     308             :      * considered the vote as finished
     309             :      */
     310             :     /**
     311             :      * Add a vote to kick a device or a user
     312             :      * @param uri       identified of the user/device
     313             :      * @param type      device, members, admins or invited
     314             :      * @return the commit id or empty if failed
     315             :      */
     316             :     std::string voteKick(const std::string& uri, const std::string& type);
     317             :     /**
     318             :      * Add a vote to re-add a user
     319             :      * @param uri       identified of the user
     320             :      * @param type      device, members, admins or invited
     321             :      * @return the commit id or empty if failed
     322             :      */
     323             :     std::string voteUnban(const std::string& uri, const std::string_view type);
     324             :     /**
     325             :      * Validate if a vote is finished
     326             :      * @param uri       identified of the user/device
     327             :      * @param type      device, members, admins or invited
     328             :      * @param voteType  "ban" or "unban"
     329             :      * @return the commit id or empty if failed
     330             :      */
     331             :     std::string resolveVote(const std::string& uri,
     332             :                             const std::string_view type,
     333             :                             const std::string& voteType);
     334             : 
     335             :     /**
     336             :      * Validate a fetch with remote device
     337             :      * @param remotedevice
     338             :      * @return the validated commits and if an error occurs
     339             :      */
     340             :     std::pair<std::vector<ConversationCommit>, bool> validFetch(
     341             :         const std::string& remoteDevice) const;
     342             :     bool validClone(std::function<void(std::vector<ConversationCommit>)>&& checkCommitCb) const;
     343             : 
     344             :     /**
     345             :      * Delete branch with remote
     346             :      * @param remoteDevice
     347             :      */
     348             :     void removeBranchWith(const std::string& remoteDevice);
     349             : 
     350             :     /**
     351             :      * One to one util, get initial members
     352             :      * @return initial members
     353             :      */
     354             :     std::vector<std::string> getInitialMembers() const;
     355             : 
     356             :     /**
     357             :      * Get conversation's members
     358             :      * @return members
     359             :      */
     360             :     std::vector<ConversationMember> members() const;
     361             : 
     362             :     /**
     363             :      * Get conversation's devices
     364             :      * @param ignoreExpired     If we want to ignore expired devices
     365             :      * @return members
     366             :      */
     367             :     std::map<std::string, std::vector<DeviceId>> devices(bool ignoreExpired = true) const;
     368             : 
     369             :     /**
     370             :      * @param filter           If we want to remove one member
     371             :      * @param filteredRoles    If we want to ignore some roles
     372             :      * @return members' uris
     373             :      */
     374             :     std::set<std::string> memberUris(std::string_view filter,
     375             :                                         const std::set<MemberRole>& filteredRoles) const;
     376             : 
     377             :     /**
     378             :      * To use after a merge with member's events, refresh members knowledge
     379             :      */
     380             :     void refreshMembers() const;
     381             : 
     382             :     /**
     383             :      * Because conversations can contains non contacts certificates, this methods
     384             :      * loads certificates in conversations into the cert store
     385             :      * @param blocking      if we need to wait that certificates are pinned
     386             :      */
     387             :     void pinCertificates(bool blocking = false);
     388             :     /**
     389             :      * Retrieve the uri from a deviceId
     390             :      * @note used by swarm manager (peersToSyncWith)
     391             :      * @param deviceId
     392             :      * @return corresponding issuer
     393             :      */
     394             :     std::string uriFromDevice(const std::string& deviceId) const;
     395             : 
     396             :     /**
     397             :      * Change repository's infos
     398             :      * @param map       New infos (supported keys: title, description, avatar)
     399             :      * @return the commit id
     400             :      */
     401             :     std::string updateInfos(const std::map<std::string, std::string>& map);
     402             : 
     403             :     /**
     404             :      * Retrieve current infos (title, description, avatar, mode)
     405             :      * @return infos
     406             :      */
     407             :     std::map<std::string, std::string> infos() const;
     408             :     static std::map<std::string, std::string> infosFromVCard(
     409             :         std::map<std::string, std::string>&& details);
     410             : 
     411             :     /**
     412             :      * Convert ConversationCommit to MapStringString for the client
     413             :      */
     414             :     std::vector<std::map<std::string, std::string>> convCommitsToMap(
     415             :         const std::vector<ConversationCommit>& commits) const;
     416             :     std::optional<std::map<std::string, std::string>> convCommitToMap(
     417             :         const ConversationCommit& commit) const;
     418             : 
     419             :     /**
     420             :      * Get current HEAD hash
     421             :      */
     422             :     std::string getHead() const;
     423             : 
     424             : private:
     425             :     ConversationRepository() = delete;
     426             :     class Impl;
     427             :     std::unique_ptr<Impl> pimpl_;
     428             : };
     429             : 
     430             : } // namespace jami
     431       13911 : MSGPACK_ADD_ENUM(jami::MemberRole);

Generated by: LCOV version 1.14