LCOV - code coverage report
Current view: top level - src/jamidht - yrs_document.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 91.1 % 79 72
Test Date: 2026-08-23 08:52:56 Functions: 100.0 % 8 8

            Line data    Source code
       1              : /*
       2              :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3              :  *
       4              :  *  This program is free software: you can redistribute it and/or modify
       5              :  *  it under the terms of the GNU General Public License as published by
       6              :  *  the Free Software Foundation, either version 3 of the License, or
       7              :  *  (at your option) any later version.
       8              :  *
       9              :  *  This program is distributed in the hope that it will be useful,
      10              :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11              :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12              :  *  GNU General Public License for more details.
      13              :  *
      14              :  *  You should have received a copy of the GNU General Public License
      15              :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16              :  */
      17              : #include "yrs_document.h"
      18              : 
      19              : #include <atomic>
      20              : #include <mutex>
      21              : 
      22              : extern "C" {
      23              : #include <libyrs.h>
      24              : }
      25              : 
      26              : namespace jami {
      27              : 
      28              : struct YrsDocument::Impl
      29              : {
      30              :     YDoc* doc {nullptr};
      31              :     std::mutex mutex;
      32              :     // Raised by the update observer whenever the document actually moves. It is
      33              :     // written from inside ytransaction_commit, with mutex already held by the
      34              :     // caller, so it must be lock-free and must never read the document back.
      35              :     std::atomic_bool changed {false};
      36              :     YSubscription* subscription {nullptr};
      37              : };
      38              : 
      39           33 : YrsDocument::YrsDocument(uint64_t clientId)
      40           33 :     : pimpl_(std::make_unique<Impl>())
      41              : {
      42           33 :     YOptions options = yoptions();
      43           33 :     options.id = clientId;
      44              :     // UTF-16 offsets, matching what the Qt, Android and iOS text editors index
      45              :     // with. The daemon never uses an offset itself, but the flag is part of the
      46              :     // document's identity and every replica must agree on it.
      47           33 :     options.flags = Y_OFFSET_UTF16;
      48           33 :     pimpl_->doc = ydoc_new_with_options(options);
      49              :     // yoptions() hands over ownership of the strings it allocates (guid, and
      50              :     // collection_id when set), and ydoc_new_with_options() copies them rather
      51              :     // than adopting them. Without this, every document ever opened leaks its
      52              :     // guid for the lifetime of the daemon.
      53           33 :     if (options.guid)
      54           33 :         ystring_destroy(const_cast<char*>(options.guid));
      55           33 :     if (options.collection_id)
      56            0 :         ystring_destroy(const_cast<char*>(options.collection_id));
      57              :     // yrs only fires this when an update brings something the replica did not
      58              :     // already hold, which makes it the one honest answer to "did that change
      59              :     // anything". It says nothing about what changed, so the daemon stays as
      60              :     // blind to the document's shape as it was without it.
      61           33 :     pimpl_->subscription = ydoc_observe_updates_v1(pimpl_->doc, pimpl_.get(), [](void* state, uint32_t, const char*) {
      62           13 :         static_cast<Impl*>(state)->changed.store(true, std::memory_order_relaxed);
      63           13 :     });
      64           33 : }
      65              : 
      66           33 : YrsDocument::~YrsDocument()
      67              : {
      68           33 :     if (pimpl_->subscription)
      69           33 :         yunobserve(pimpl_->subscription);
      70           33 :     if (pimpl_->doc)
      71           33 :         ydoc_destroy(pimpl_->doc);
      72           33 : }
      73              : 
      74              : bool
      75           74 : YrsDocument::takeChanged()
      76              : {
      77           74 :     return pimpl_->changed.exchange(false, std::memory_order_relaxed);
      78              : }
      79              : 
      80              : bool
      81           18 : YrsDocument::applyUpdate(const Bytes& update)
      82              : {
      83           18 :     if (update.empty())
      84            0 :         return false;
      85           18 :     std::lock_guard<std::mutex> lk(pimpl_->mutex);
      86           18 :     YTransaction* txn = ydoc_write_transaction(pimpl_->doc, 0, nullptr);
      87           18 :     if (!txn)
      88            0 :         return false;
      89              :     // Root types are created on demand by the update itself, so the document
      90              :     // never has to be told which shared types it holds.
      91           18 :     const auto err = ytransaction_apply(txn, reinterpret_cast<const char*>(update.data()), update.size());
      92           18 :     ytransaction_commit(txn);
      93           18 :     return err == 0;
      94           18 : }
      95              : 
      96              : YrsDocument::Bytes
      97           26 : YrsDocument::encodeStateAsUpdate() const
      98              : {
      99           26 :     std::lock_guard<std::mutex> lk(pimpl_->mutex);
     100           26 :     YTransaction* txn = ydoc_read_transaction(pimpl_->doc);
     101           26 :     if (!txn)
     102            0 :         return {};
     103           26 :     uint32_t len = 0;
     104              :     // A null state vector requests the full document state.
     105           26 :     char* data = ytransaction_state_diff_v1(txn, nullptr, 0, &len);
     106           26 :     Bytes update;
     107           26 :     if (data) {
     108           26 :         update.assign(reinterpret_cast<const uint8_t*>(data), reinterpret_cast<const uint8_t*>(data) + len);
     109           26 :         ybinary_destroy(data, len);
     110              :     }
     111           26 :     ytransaction_commit(txn);
     112           26 :     return update;
     113           26 : }
     114              : 
     115              : YrsDocument::Bytes
     116           36 : YrsDocument::encodeStateVector() const
     117              : {
     118           36 :     std::lock_guard<std::mutex> lk(pimpl_->mutex);
     119           36 :     YTransaction* txn = ydoc_read_transaction(pimpl_->doc);
     120           36 :     if (!txn)
     121            0 :         return {};
     122           36 :     uint32_t len = 0;
     123           36 :     char* data = ytransaction_state_vector_v1(txn, &len);
     124           36 :     Bytes sv;
     125           36 :     if (data) {
     126           36 :         sv.assign(reinterpret_cast<const uint8_t*>(data), reinterpret_cast<const uint8_t*>(data) + len);
     127           36 :         ybinary_destroy(data, len);
     128              :     }
     129           36 :     ytransaction_commit(txn);
     130           36 :     return sv;
     131           36 : }
     132              : 
     133              : YrsDocument::Bytes
     134            4 : YrsDocument::encodeDiff(const Bytes& stateVector) const
     135              : {
     136            4 :     if (stateVector.empty())
     137            0 :         return encodeStateAsUpdate();
     138            4 :     std::lock_guard<std::mutex> lk(pimpl_->mutex);
     139            4 :     YTransaction* txn = ydoc_read_transaction(pimpl_->doc);
     140            4 :     if (!txn)
     141            0 :         return {};
     142            4 :     uint32_t len = 0;
     143            4 :     char* data = ytransaction_state_diff_v1(txn,
     144            4 :                                             reinterpret_cast<const char*>(stateVector.data()),
     145            4 :                                             stateVector.size(),
     146              :                                             &len);
     147            4 :     Bytes update;
     148            4 :     if (data) {
     149            4 :         update.assign(reinterpret_cast<const uint8_t*>(data), reinterpret_cast<const uint8_t*>(data) + len);
     150            4 :         ybinary_destroy(data, len);
     151              :     }
     152            4 :     ytransaction_commit(txn);
     153            4 :     return update;
     154            4 : }
     155              : 
     156              : } // namespace jami
        

Generated by: LCOV version 2.0-1