code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "advance.h"
#include "remote_copy.h"
#include "remote_win.h"
#include "proto/proto-helpers.h"
#include "model/cluster.h"
#include "model/diff/modify/add_blocks.h"
#include "model/diff/modify/remove_blocks.h"
#include "model/misc/orphaned_blocks.h"

using namespace syncspirit::model;
using namespace syncspirit::model::diff::advance;

static std::string_view stringify(advance_action_t action) {
    if (action == advance_action_t::remote_copy) {
        return "remote_copy";
    } else if (action == advance_action_t::local_update) {
        return "local_update";
    } else if (action == advance_action_t::resolve_remote_win) {
        return "resolve_remote_win";
    }
    return "ignore";
}

auto advance_t::create(advance_action_t action, const model::file_info_t &source, sequencer_t &sequencer) noexcept
    -> cluster_diff_ptr_t {
    auto &cluster = *source.get_folder_info()->get_folder()->get_cluster();
    auto proto_file = source.as_proto(true);
    auto peer_folder_info = source.get_folder_info();
    auto folder = peer_folder_info->get_folder();
    auto folder_id = folder->get_id();
    auto peer_id = peer_folder_info->get_device()->device_id().get_sha256();

    if (action == advance_action_t::remote_copy) {
        return new remote_copy_t(cluster, sequencer, std::move(proto_file), folder_id, peer_id);
    } else {
        assert(action == advance_action_t::resolve_remote_win);
        return new remote_win_t(cluster, sequencer, std::move(proto_file), folder_id, peer_id);
    }
}

advance_t::advance_t(std::string_view folder_id_, utils::bytes_view_t peer_id_, advance_action_t action_,
                     bool disable_blocks_removal_) noexcept
    : folder_id{folder_id_}, action{action_}, disable_blocks_removal{disable_blocks_removal_} {
    peer_id = utils::bytes_t(peer_id_.begin(), peer_id_.end());
}

void advance_t::initialize(const cluster_t &cluster, sequencer_t &sequencer, proto::FileInfo proto_source_,
                           std::string_view local_file_name_) noexcept {
    proto_source = std::move(proto_source_);
    if (local_file_name_.empty()) {
        local_file_name_ = proto::get_name(proto_source);
    }
    assert(!(proto::get_blocks_size(proto_source) && proto::get_deleted(proto_source)));
    auto folder = cluster.get_folders().by_id(folder_id);
    auto folder_id = folder->get_id();
    auto &self = *cluster.get_device();
    auto self_id = self.device_id().get_sha256();

    auto &local_folder_infos = folder->get_folder_infos();
    auto local_folder_info = local_folder_infos.by_device_id(self_id);
    auto &local_files = local_folder_info->get_file_infos();
    auto local_file = local_files.by_name(local_file_name_);

    auto orphans = orphaned_blocks_t::set_t();
    proto_local = proto_source;
    proto::set_sequence(proto_local, 0);
    proto::set_name(proto_local, local_file_name_);

    if (!local_file) {
        uuid = sequencer.next_uuid();
    } else if (!disable_blocks_removal) {
        assign(uuid, local_file->get_uuid());
        auto orphaned = orphaned_blocks_t();
        orphaned.record(*local_file);
        orphans = orphaned.deduce();
    }

    auto new_blocks = modify::add_blocks_t::blocks_t{};
    auto &blocks_map = cluster.get_blocks();
    if (proto::get_size(proto_local)) {
        auto blocks_size = proto::get_blocks_size(proto_local);
        for (int i = 0; i < blocks_size; ++i) {
            auto &proto_block = proto::get_blocks(proto_local, i);
            auto h = proto::get_hash(proto_block);
            auto strict_hash = block_info_t::make_strict_hash(h);
            auto block = blocks_map.by_hash(strict_hash.get_hash());
            if (!block) {
                new_blocks.push_back(proto_block);
            } else {
                auto it = orphans.find(strict_hash.get_key());
                if (it != orphans.end()) {
                    orphans.erase(it);
                }
            }
        }
    }

    LOG_DEBUG(log, "advance_t ({}), folder = {}, name = {} ( -> {}), blocks = {}, removed blocks = {}, new blocks = {}",
              stringify(action), folder_id, proto::get_name(proto_source), proto::get_name(proto_local),
              proto::get_blocks_size(proto_local), orphans.size(), new_blocks.size());

    auto current = (cluster_diff_t *){};
    if (!new_blocks.empty()) {
        assert(action == advance_action_t::local_update);
        current = assign_child(new modify::add_blocks_t(std::move(new_blocks)));
    }

    if (!orphans.empty()) {
        auto diff = new modify::remove_blocks_t(std::move(orphans));
        if (current) {
            current->assign_sibling(diff);
        } else {
            assign_child(diff);
        }
    }
}

auto advance_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept -> outcome::result<void> {
    auto r = applicator_t::apply_child(cluster, controller);
    if (!r) {
        return r;
    }
    auto my_device = cluster.get_device();
    auto folder = cluster.get_folders().by_id(folder_id);
    auto local_folder = folder->get_folder_infos().by_device(*my_device);
    auto peer_folder = folder->get_folder_infos().by_device_id(peer_id);

    auto prev_file = local_folder->get_file_infos().by_name(proto::get_name(proto_local));
    auto local_file_opt = file_info_t::create(uuid, proto_local, local_folder);
    if (!local_file_opt) {
        return local_file_opt.assume_error();
    }
    auto local_file = std::move(local_file_opt.assume_value());
    if (prev_file) {
        local_folder->get_file_infos().remove(prev_file);
        prev_file->update(*local_file);
        local_file = std::move(prev_file);
    }

    if (proto::get_size(proto_local)) {
        auto &blocks_map = cluster.get_blocks();
        auto blocks_count = proto::get_blocks_size(proto_local);
        for (size_t i = 0; i < blocks_count; ++i) {
            auto &block = proto::get_blocks(proto_local, i);
            auto hash = proto::get_hash(block);
            auto strict_hash = block_info_t::make_strict_hash(hash);
            auto block_info = blocks_map.by_hash(strict_hash.get_hash());
            assert(block_info);
            local_file->assign_block(block_info, i);
            local_file->mark_local_available(i);
        }
    }

    auto sequence = local_folder->get_max_sequence() + 1;
    local_file->mark_local();
    local_file->set_sequence(sequence);
    local_folder->add_strict(local_file);

    LOG_TRACE(log, "advance_t ({}), folder = {}, name = {}, blocks = {}, seq. = {}", stringify(action), folder_id,
              local_file->get_name(), proto::get_blocks_size(proto_local), sequence);

    local_file->notify_update();

    return applicator_t::apply_sibling(cluster, controller);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include "../cluster_diff.h"
#include "model/file_info.h"
#include "model/misc/sequencer.h"
#include "model/misc/resolver.h"
#include "proto/proto-fwd.hpp"

namespace syncspirit::model::diff::advance {

struct SYNCSPIRIT_API advance_t : cluster_diff_t {
    static cluster_diff_ptr_t create(advance_action_t action, const model::file_info_t &source,
                                     sequencer_t &sequencer) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;

    proto::FileInfo proto_source;
    proto::FileInfo proto_local;
    std::string folder_id;
    utils::bytes_t peer_id;
    bu::uuid uuid;
    advance_action_t action;
    bool disable_blocks_removal;

  protected:
    advance_t(std::string_view folder_id, utils::bytes_view_t peer_id, advance_action_t action,
              bool disable_blocks_removal = false) noexcept;
    void initialize(const cluster_t &cluster, sequencer_t &sequencer, proto::FileInfo proto_source,
                    std::string_view local_file_name) noexcept;
};

} // namespace syncspirit::model::diff::advance

repo_name: basiliscos/syncspirit

license: mit


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "local_update.h"
#include "model/cluster.h"
#include "../cluster_visitor.h"
#include "proto/proto-helpers-bep.h"
#include <memory_resource>

using namespace syncspirit::model::diff::advance;

local_update_t::local_update_t(const cluster_t &cluster, sequencer_t &sequencer, proto::FileInfo proto_file_,
                               std::string_view folder_id_) noexcept
    : advance_t(folder_id_, cluster.get_device()->device_id().get_sha256(), advance_action_t::local_update) {

    auto buffer = std::array<std::byte, 256>();
    auto pool = std::pmr::monotonic_buffer_resource(buffer.data(), buffer.size());
    auto allocator = std::pmr::polymorphic_allocator<std::string>(&pool);

    auto &device = *cluster.get_devices().by_sha256(peer_id);
    auto folder = cluster.get_folders().by_id(folder_id);
    auto &self = *cluster.get_device();
    auto self_id = self.device_id().get_sha256();

    auto &folder_infos = folder->get_folder_infos();
    auto local_folder_info = folder_infos.by_device_id(self_id);
    auto &local_files = local_folder_info->get_file_infos();
    auto name = std::pmr::string(proto::get_name(proto_file_), allocator);
    auto local_file = local_files.by_name(name);

    if (auto original_file = get_original(folder_infos, device, proto_file_); original_file) {
        initialize(cluster, sequencer, original_file->as_proto(true), name);
    } else {
        proto::set_modified_by(proto_file_, self.device_id().get_uint());
        initialize(cluster, sequencer, std::move(proto_file_), name);
        auto version = version_ptr_t();
        if (local_file) {
            version = local_file->get_version();
            version->update(device);
        } else {
            version.reset(new version_t(device));
        }
        auto &proto_version = proto::get_version(proto_local);
        version->to_proto(proto_version);
    }
}

auto local_update_t::get_original(const model::folder_infos_map_t &fis, const model::device_t &self,
                                  const proto::FileInfo &local_file) const noexcept -> model::file_info_ptr_t {
    auto r = model::file_info_ptr_t();
    auto name = proto::get_name(local_file);
    auto local_type = proto::get_type(local_file);
    auto local_deleted = proto::get_deleted(local_file);
    auto local_invalid = proto::get_invalid(local_file);
    auto local_perms = proto::get_permissions(local_file);
    auto local_size = proto::get_size(local_file);
    auto local_blocks_sz = proto::get_blocks_size(local_file);

    for (auto &it_fi : fis) {
        auto fi = it_fi.item.get();
        auto peer_file = model::file_info_ptr_t();
        if (fi->get_device() != &self) {
            auto &peer_files = fi->get_file_infos();
            auto candidate = peer_files.by_name(name);
            if (candidate) {
                if (candidate->get_size() == local_size) {
                    auto &peer_blocks = candidate->get_blocks();
                    bool matches = local_type == candidate->get_type() && local_deleted == candidate->is_deleted() &&
                                   local_invalid == candidate->is_invalid() &&
                                   local_perms == candidate->get_permissions() && local_blocks_sz == peer_blocks.size();
                    if (matches) {
                        for (size_t i = 0; i < peer_blocks.size(); ++i) {
                            auto &pb = peer_blocks[i];
                            auto &lb = proto::get_blocks(local_file, i);
                            if (pb->get_hash() != proto::get_hash(lb)) {
                                matches = false;
                                break;
                            }
                        }
                    }
                    if (matches) {
                        peer_file = std::move(candidate);
                    }
                }
            }
        }
        if (peer_file) {
            if (!r) {
                r = std::move(peer_file);
            } else {
                if (model::compare(*peer_file, *r) > 1) {
                    r = std::move(peer_file);
                }
            }
        }
    }
    return r;
}

auto local_update_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto folder = cluster.get_folders().by_id(folder_id);
    if (!folder) {
        LOG_DEBUG(log, "remote_copy_t, folder = {}, name = {}, folder is not available, ignoring", folder_id,
                  proto::get_name(proto_source));
    } else if (folder->is_suspended()) {
        LOG_DEBUG(log, "remote_copy_t, folder = {}, name = {}, folder is suspended, ignoring", folder_id,
                  proto::get_name(proto_source));
    } else {
        return advance_t::apply_impl(cluster, controller);
    }
    return applicator_t::apply_sibling(cluster, controller);
}

auto local_update_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting local_update_t, folder = {}, file = {}", folder_id, proto::get_name(proto_local));
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include "advance.h"
#include "model/folder_info.h"

namespace syncspirit::model::diff::advance {

struct SYNCSPIRIT_API local_update_t final : advance_t {
    using parent_t = advance_t;
    using parent_t::parent_t;

    local_update_t(const cluster_t &cluster, sequencer_t &sequencer, proto::FileInfo proto_file,
                   std::string_view folder_id) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

  private:
    model::file_info_ptr_t get_original(const model::folder_infos_map_t &fis, const model::device_t &self,
                                        const proto::FileInfo &local_file) const noexcept;
};

} // namespace syncspirit::model::diff::advance


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "remote_copy.h"
#include "../cluster_visitor.h"
#include "model/cluster.h"
#include "proto/proto-helpers.h"

using namespace syncspirit::model::diff::advance;

remote_copy_t::remote_copy_t(const cluster_t &cluster, sequencer_t &sequencer, proto::FileInfo proto_file_,
                             std::string_view folder_id_, utils::bytes_view_t peer_id_,
                             bool disable_blocks_removal_) noexcept
    : advance_t(folder_id_, peer_id_, advance_action_t::remote_copy, disable_blocks_removal_) {
    initialize(cluster, sequencer, std::move(proto_file_), {});
}

auto remote_copy_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto folder = cluster.get_folders().by_id(folder_id);
    if (!folder) {
        LOG_DEBUG(log, "remote_copy_t, folder = {}, name = {}, folder is not available, ignoring", folder_id,
                  proto::get_name(proto_source));
    } else if (folder->is_suspended()) {
        LOG_DEBUG(log, "remote_copy_t, folder = {}, name = {}, folder is suspended, ignoring", folder_id,
                  proto::get_name(proto_source));
    } else if (auto peer_folder = folder->get_folder_infos().by_device_id(peer_id); !peer_folder) {
        LOG_DEBUG(log, "remote_copy_t, folder = {}, name = {}, peer folder is not available, ignoring", folder_id,
                  proto::get_name(proto_source));
    } else {
        return advance_t::apply_impl(cluster, controller);
    }
    return applicator_t::apply_sibling(cluster, controller);
}

auto remote_copy_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting remote_copy_t, folder = {}, file = {}", folder_id, proto::get_name(proto_local));
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include "advance.h"

namespace syncspirit::model::diff::advance {

struct SYNCSPIRIT_API remote_copy_t final : advance_t {
    using parent_t = advance_t;
    using parent_t::parent_t;

    remote_copy_t(const cluster_t &cluster, sequencer_t &sequencer, proto::FileInfo proto_file,
                  std::string_view folder_id, utils::bytes_view_t peer_id,
                  bool disable_blocks_removal = false) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;

    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;
};

} // namespace syncspirit::model::diff::advance


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "remote_win.h"
#include "remote_copy.h"
#include "model/cluster.h"
#include "../cluster_visitor.h"
#include "proto/proto-helpers.h"

using namespace syncspirit::model::diff::advance;

remote_win_t::remote_win_t(const cluster_t &cluster, sequencer_t &sequencer, proto::FileInfo proto_file_,
                           std::string_view folder_id_, utils::bytes_view_t peer_id_) noexcept
    : advance_t(folder_id_, peer_id_, advance_action_t::resolve_remote_win) {
    auto &self = *cluster.get_device();
    auto folder = cluster.get_folders().by_id(folder_id);
    auto &local_files = folder->get_folder_infos().by_device(self)->get_file_infos();
    auto local_file = local_files.by_name(proto::get_name(proto_file_));
    auto local_source = local_file->as_proto(true);
    initialize(cluster, sequencer, std::move(local_source), local_file->make_conflicting_name());
    assign_sibling(new remote_copy_t(cluster, sequencer, std::move(proto_file_), folder_id_, peer_id_, true));
}

auto remote_win_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &self = *cluster.get_device();
    auto folder = cluster.get_folders().by_id(folder_id);
    if (!folder) {
        LOG_DEBUG(log, "remote_win_t, folder = {}, name = {}, folder is not available, ignoring", folder_id,
                  proto::get_name(proto_source));
    } else if (folder->is_suspended()) {
        LOG_DEBUG(log, "remote_win_t, folder = {}, name = {}, folder is suspended, ignoring", folder_id,
                  proto::get_name(proto_source));
    } else if (auto peer_folder = folder->get_folder_infos().by_device_id(peer_id); !peer_folder) {
        LOG_DEBUG(log, "remote_win_t, folder = {}, name = {}, peer folder is not available, ignoring", folder_id,
                  proto::get_name(proto_source));
    } else {
        auto &local_files = folder->get_folder_infos().by_device(self)->get_file_infos();
        auto prev_file = local_files.by_name(proto::get_name(proto_source));
        assert(prev_file);
        local_files.remove(prev_file);
        return parent_t::apply_impl(cluster, controller);
    }
    return applicator_t::apply_sibling(cluster, controller);
}

auto remote_win_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting remote_win_t, folder = {}, file = {}", folder_id, proto::get_name(proto_source));
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include "advance.h"

namespace syncspirit::model::diff::advance {

struct SYNCSPIRIT_API remote_win_t final : advance_t {
    using parent_t = advance_t;
    using parent_t::parent_t;

    remote_win_t(const cluster_t &cluster, sequencer_t &sequencer, proto::FileInfo proto_file,
                 std::string_view folder_id, utils::bytes_view_t peer_id) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;
};

} // namespace syncspirit::model::diff::advance


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#include "apply_controller.h"
#include "load/blocks.h"
#include "load/file_infos.h"
#include "load/load_cluster.h"

namespace syncspirit::model::diff {

auto apply_controller_t::apply(const cluster_diff_t &diff, cluster_t &cluster) noexcept -> outcome::result<void> {
    return diff.apply_impl(cluster, *this);
}

auto apply_controller_t::apply(const load::blocks_t &diff, cluster_t &cluster) noexcept -> outcome::result<void> {
    return diff.apply_impl(cluster, *this);
}

auto apply_controller_t::apply(const load::file_infos_t &diff, cluster_t &cluster) noexcept -> outcome::result<void> {
    return diff.apply_impl(cluster, *this);
}

auto apply_controller_t::apply(const load::load_cluster_t &diff, cluster_t &cluster) noexcept -> outcome::result<void> {
    return diff.apply_impl(cluster, *this);
}

} // namespace syncspirit::model::diff


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#pragma once

#include "syncspirit-export.h"
#include "diffs_fwd.h"
#include <boost/outcome.hpp>

namespace syncspirit::model {
struct cluster_t;
}

namespace syncspirit::model::diff {

namespace outcome = boost::outcome_v2;

struct SYNCSPIRIT_API apply_controller_t {
    virtual outcome::result<void> apply(const cluster_diff_t &, cluster_t &cluster) noexcept;
    virtual outcome::result<void> apply(const load::blocks_t &, cluster_t &cluster) noexcept;
    virtual outcome::result<void> apply(const load::file_infos_t &, cluster_t &cluster) noexcept;
    virtual outcome::result<void> apply(const load::load_cluster_t &, cluster_t &cluster) noexcept;
};

} // namespace syncspirit::model::diff


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "block_diff.h"
#include "../cluster.h"

using namespace syncspirit::model::diff;

block_diff_t::block_diff_t(const block_diff_t &source) noexcept
    : file_name(source.file_name), folder_id{source.folder_id}, device_id{source.device_id},
      block_hash{source.block_hash}, block_index{source.block_index} {}

block_diff_t::block_diff_t(const file_info_t &file, size_t block_index_) noexcept
    : file_name{file.get_name()}, block_index{block_index_} {
    auto fi = file.get_folder_info();
    folder_id = fi->get_folder()->get_id();
    device_id = fi->get_device()->device_id().get_sha256();
    block_hash = file.get_blocks()[block_index_]->get_hash();
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include "cluster_diff.h"
#include "utils/bytes.h"

namespace syncspirit::model {

struct file_info_t;

namespace diff {

struct SYNCSPIRIT_API block_diff_t : cluster_diff_t {
    block_diff_t(const block_diff_t &) noexcept;
    block_diff_t(const file_info_t &file, size_t block_index = 0) noexcept;

    std::string file_name;
    std::string folder_id;
    utils::bytes_t device_id;
    utils::bytes_t block_hash;
    size_t block_index;
};

} // namespace diff
} // namespace syncspirit::model


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#include "cluster_diff.h"
#include "apply_controller.h"
#include "model/cluster.h"

using namespace syncspirit::model::diff;

cluster_diff_t::cluster_diff_t() { log = get_log(); }

auto cluster_diff_t::get_log() noexcept -> utils::logger_t { return utils::get_logger("model.diff"); }

cluster_diff_t *cluster_diff_t::assign_sibling(cluster_diff_t *sibling_) noexcept {
    assert(!sibling);
    sibling = sibling_;

    auto n = sibling_;
    while (n->sibling) {
        n = n->sibling.get();
    }
    return n;
}

cluster_diff_t *cluster_diff_t::assign_child(cluster_diff_ptr_t child_) noexcept {
    assert(!child);
    child = std::move(child_);
    return child.get();
}

auto cluster_diff_t::apply(cluster_t &cluster, apply_controller_t &controller) const noexcept -> outcome::result<void> {
    auto r = apply_forward(cluster, controller);
    if (!r) {
        cluster.mark_tainted();
    }
    return r;
}

auto cluster_diff_t::apply_forward(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    return controller.apply(*this, cluster);
}

auto cluster_diff_t::cluster_diff_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto r = outcome::result<void>{outcome::success()};
    if (this->child) {
        r = this->child->apply(cluster, controller);
    }
    if (r && this->sibling) {
        r = this->sibling->apply(cluster, controller);
    }
    return r;
}

auto cluster_diff_t::apply_child(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    return child ? child->apply(cluster, controller) : outcome::success();
}

auto cluster_diff_t::apply_sibling(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    return sibling ? sibling->apply(cluster, controller) : outcome::success();
}

auto cluster_diff_t::visit_next(visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    auto r = outcome::result<void>{outcome::success()};
    if (child) {
        r = child->visit(visitor, custom);
    }
    if (r && sibling) {
        r = sibling->visit(visitor, custom);
    }
    return r;
}

auto cluster_diff_t::visit(visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    return visit_next(visitor, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#pragma once

#include "syncspirit-export.h"
#include "model/misc/arc.hpp"
#include "utils/log.h"
#include <boost/outcome.hpp>

namespace syncspirit::model {
struct cluster_t;
};

namespace syncspirit::model::diff {

namespace outcome = boost::outcome_v2;

struct apply_controller_t;
struct cluster_diff_t;
struct cluster_visitor_t;
using cluster_diff_ptr_t = intrusive_ptr_t<cluster_diff_t>;

struct SYNCSPIRIT_API cluster_diff_t : arc_base_t<cluster_diff_t> {
    using visitor_t = cluster_visitor_t;
    using applicator_t = cluster_diff_t;

    static utils::logger_t get_log() noexcept;

    cluster_diff_t();
    cluster_diff_t(const cluster_diff_t &) = delete;
    cluster_diff_t(cluster_diff_t &&) = delete;
    virtual ~cluster_diff_t() = default;

    outcome::result<void> apply(cluster_t &, apply_controller_t &) const noexcept;
    virtual outcome::result<void> visit(visitor_t &visitor, void *custom) const noexcept;
    outcome::result<void> visit_next(visitor_t &visitor, void *custom) const noexcept;

    cluster_diff_t *assign_sibling(cluster_diff_t *sibling) noexcept;
    cluster_diff_t *assign_child(cluster_diff_ptr_t child) noexcept;

    cluster_diff_ptr_t child;
    cluster_diff_ptr_t sibling;

  protected:
    virtual outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept;
    virtual outcome::result<void> apply_forward(cluster_t &, apply_controller_t &) const noexcept;
    outcome::result<void> apply_child(cluster_t &cluster, apply_controller_t &) const noexcept;
    outcome::result<void> apply_sibling(cluster_t &cluster, apply_controller_t &) const noexcept;

    utils::logger_t log;

    friend struct apply_controller_t;
};

// using cluster_visitor_t = cluster_diff_t::visitor_t;

} // namespace syncspirit::model::diff


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "cluster_diff.h"
#include "cluster_visitor.h"
#include "advance/local_update.h"
#include "advance/remote_copy.h"
#include "advance/remote_win.h"
#include "contact/connect_request.h"
#include "contact/dial_request.h"
#include "contact/ignored_connected.h"
#include "contact/peer_state.h"
#include "contact/relay_connect_request.h"
#include "contact/unknown_connected.h"
#include "contact/update_contact.h"
#include "load/devices.h"
#include "load/ignored_devices.h"
#include "load/load_cluster.h"
#include "load/pending_devices.h"
#include "local/blocks_availability.h"
#include "local/custom.h"
#include "local/io_failure.h"
#include "local/file_availability.h"
#include "local/scan_finish.h"
#include "local/scan_request.h"
#include "local/scan_start.h"
#include "local/synchronization_finish.h"
#include "local/synchronization_start.h"
#include "modify/add_blocks.h"
#include "modify/add_ignored_device.h"
#include "modify/add_pending_device.h"
#include "modify/add_pending_folders.h"
#include "modify/add_remote_folder_infos.h"
#include "modify/append_block.h"
#include "modify/block_ack.h"
#include "modify/block_rej.h"
#include "modify/clone_block.h"
#include "modify/finish_file.h"
#include "modify/lock_file.h"
#include "modify/mark_reachable.h"
#include "modify/remove_blocks.h"
#include "modify/remove_files.h"
#include "modify/remove_folder.h"
#include "modify/remove_folder_infos.h"
#include "modify/remove_ignored_device.h"
#include "modify/remove_peer.h"
#include "modify/remove_pending_device.h"
#include "modify/remove_pending_folders.h"
#include "modify/reset_folder_infos.h"
#include "modify/share_folder.h"
#include "modify/suspend_folder.h"
#include "modify/unshare_folder.h"
#include "modify/update_peer.h"
#include "modify/upsert_folder.h"
#include "modify/upsert_folder_info.h"
#include "peer/cluster_update.h"
#include "peer/rx_tx.h"
#include "peer/update_folder.h"

using namespace syncspirit::model::diff;

auto cluster_visitor_t::operator()(const advance::advance_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const advance::local_update_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    auto &advance_diff = static_cast<const advance::advance_t &>(diff);
    return (*this)(advance_diff, custom);
}

auto cluster_visitor_t::operator()(const advance::remote_copy_t &diff, void *custom) noexcept -> outcome::result<void> {
    auto &advance_diff = static_cast<const advance::advance_t &>(diff);
    return (*this)(advance_diff, custom);
}

auto cluster_visitor_t::operator()(const advance::remote_win_t &diff, void *custom) noexcept -> outcome::result<void> {
    auto &advance_diff = static_cast<const advance::advance_t &>(diff);
    return (*this)(advance_diff, custom);
}

auto cluster_visitor_t::operator()(const contact::connect_request_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const contact::dial_request_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const contact::ignored_connected_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const contact::relay_connect_request_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const contact::peer_state_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const contact::unknown_connected_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const contact::update_contact_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const load::devices_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const load::ignored_devices_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const load::pending_devices_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const load::load_cluster_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const local::custom_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const local::file_availability_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const local::io_failure_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const local::scan_finish_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const local::scan_request_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const local::scan_start_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const local::synchronization_start_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const local::synchronization_finish_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const peer::cluster_update_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const peer::rx_tx_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const peer::update_folder_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::append_block_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const local::blocks_availability_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::block_ack_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::block_rej_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::clone_block_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::upsert_folder_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::add_blocks_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::add_remote_folder_infos_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::add_ignored_device_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::add_pending_device_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::add_pending_folders_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::share_folder_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::suspend_folder_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::unshare_folder_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::update_peer_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::remove_peer_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::remove_blocks_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::remove_files_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}
auto cluster_visitor_t::operator()(const modify::remove_folder_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::remove_folder_infos_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::remove_ignored_device_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::remove_pending_device_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::remove_pending_folders_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::reset_folder_infos_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::finish_file_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::lock_file_t &diff, void *custom) noexcept -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::mark_reachable_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}

auto cluster_visitor_t::operator()(const modify::upsert_folder_info_t &diff, void *custom) noexcept
    -> outcome::result<void> {
    return diff.visit_next(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include "syncspirit-export.h"
#include "diffs_fwd.h"
#include <boost/outcome.hpp>

namespace syncspirit::model::diff {

namespace outcome = boost::outcome_v2;

struct SYNCSPIRIT_API cluster_visitor_t {
    virtual outcome::result<void> operator()(const advance::advance_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const advance::local_update_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const advance::remote_copy_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const advance::remote_win_t &, void *custom) noexcept;

    virtual outcome::result<void> operator()(const contact::connect_request_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const contact::dial_request_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const contact::ignored_connected_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const contact::peer_state_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const contact::relay_connect_request_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const contact::unknown_connected_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const contact::update_contact_t &, void *custom) noexcept;

    virtual outcome::result<void> operator()(const load::devices_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const load::ignored_devices_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const load::pending_devices_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const load::load_cluster_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const local::blocks_availability_t &, void *custom) noexcept;

    virtual outcome::result<void> operator()(const local::custom_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const local::file_availability_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const local::io_failure_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const local::scan_finish_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const local::scan_request_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const local::scan_start_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const local::synchronization_start_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const local::synchronization_finish_t &, void *custom) noexcept;

    virtual outcome::result<void> operator()(const peer::cluster_update_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const peer::rx_tx_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const peer::update_folder_t &, void *custom) noexcept;

    virtual outcome::result<void> operator()(const modify::add_blocks_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::add_ignored_device_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::add_pending_device_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::add_pending_folders_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::add_remote_folder_infos_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::append_block_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::block_ack_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::block_rej_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::clone_block_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::finish_file_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::lock_file_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::mark_reachable_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::remove_blocks_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::remove_files_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::remove_folder_infos_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::remove_folder_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::remove_ignored_device_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::remove_peer_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::remove_pending_device_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::remove_pending_folders_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::reset_folder_infos_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::share_folder_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::suspend_folder_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::unshare_folder_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::update_peer_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::upsert_folder_info_t &, void *custom) noexcept;
    virtual outcome::result<void> operator()(const modify::upsert_folder_t &, void *custom) noexcept;
};

} // namespace syncspirit::model::diff


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#include "connect_request.h"
#include "../cluster_visitor.h"
#include "utils/format.hpp"

using namespace syncspirit::model::diff::contact;

connect_request_t::connect_request_t(tcp::socket sock_, const tcp::endpoint &remote_) noexcept
    : sock{new tcp::socket(std::move(sock_))}, remote{remote_} {
    LOG_DEBUG(log, "connect_request_t, endpoint = {}", remote);
}

auto connect_request_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    return applicator_t::apply_sibling(cluster, controller);
}

auto connect_request_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting connect_request_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#pragma once

#include <boost/asio.hpp>
#include <mutex>
#include "model/diff/cluster_diff.h"
#include "model/cluster.h"

namespace syncspirit::model::diff::contact {

namespace asio = boost::asio;
using tcp = asio::ip::tcp;

struct SYNCSPIRIT_API connect_request_t final : cluster_diff_t {
    using socket_ptr_t = std::unique_ptr<tcp::socket>;
    using mutex_t = std::mutex;

    connect_request_t(tcp::socket sock, const tcp::endpoint &remote) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    mutable socket_ptr_t sock;
    tcp::endpoint remote;
    mutable mutex_t mutex;
};

} // namespace syncspirit::model::diff::contact


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou

#include "dial_request.h"
#include "../cluster_visitor.h"

using namespace syncspirit::model::diff::contact;

dial_request_t::dial_request_t(model::device_t &peer) noexcept {
    peer_id = peer.device_id().get_sha256();
    LOG_DEBUG(log, "dial_request_t, peer = ", peer.device_id().get_short());
}

auto dial_request_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    return applicator_t::apply_sibling(cluster, controller);
}

auto dial_request_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting dial_request_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou

#pragma once

#include <boost/asio.hpp>
#include "model/cluster.h"
#include "model/diff/cluster_diff.h"

namespace syncspirit::model::diff::contact {

namespace asio = boost::asio;
using tcp = asio::ip::tcp;

struct SYNCSPIRIT_API dial_request_t final : cluster_diff_t {
    dial_request_t(model::device_t &peer) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    utils::bytes_t peer_id;
};

} // namespace syncspirit::model::diff::contact


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#include "ignored_connected.h"
#include "model/cluster.h"
#include "../cluster_visitor.h"
#include "model/misc/error_code.h"

using namespace syncspirit::model::diff::contact;

ignored_connected_t::ignored_connected_t(cluster_t &, const model::device_id_t &device_id_,
                                         db::SomeDevice db_device_) noexcept
    : device_id{device_id_}, db_device{std::move(db_device_)} {
    LOG_DEBUG(log, "ignored_connected_t, device = ", device_id.get_short());
}

auto ignored_connected_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &ignored_devices = cluster.get_ignored_devices();
    auto prev = ignored_devices.by_sha256(device_id.get_sha256());
    if (!prev) {
        return make_error_code(error_code_t::no_such_device);
    }
    prev->assign(db_device);
    prev->notify_update();
    return applicator_t::apply_sibling(cluster, controller);
}

auto ignored_connected_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting ignored_connected_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou

#pragma once

#include "../cluster_diff.h"
#include "model/device_id.h"
#include "proto/proto-fwd.hpp"

namespace syncspirit::model::diff::contact {

struct SYNCSPIRIT_API ignored_connected_t final : cluster_diff_t {
    ignored_connected_t(cluster_t &, const model::device_id_t &device_id, db::SomeDevice db_device) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    model::device_id_t device_id;
    db::SomeDevice db_device;
};

} // namespace syncspirit::model::diff::contact


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "peer_state.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"
#include "utils/format.hpp"

using namespace syncspirit::model::diff::contact;

auto peer_state_t::create(cluster_t &cluster, utils::bytes_view_t peer_id_, const r::address_ptr_t &peer_addr_,
                          model::device_state_t state, std::string connection_id_, std::string cert_name_,
                          tcp::endpoint endpoint_, std::string_view client_name_,
                          std::string_view client_version_) noexcept -> cluster_diff_ptr_t {
    auto peer = cluster.get_devices().by_sha256(peer_id_);
    bool update_state = (peer->get_connection_id() == connection_id_) || peer->get_state() < state ||
                        (peer->get_state() == state && peer->get_connection_id() > connection_id_);

    auto diff = cluster_diff_ptr_t{};
    if (update_state) {
        diff = new peer_state_t(cluster, peer_id_, peer_addr_, state, std::move(connection_id_), std::move(cert_name_),
                                std::move(endpoint_), client_name_, client_version_);
    }
    return diff;
}

peer_state_t::peer_state_t(cluster_t &cluster, utils::bytes_view_t peer_id_, const r::address_ptr_t &peer_addr_,
                           model::device_state_t state_, std::string connection_id_, std::string cert_name_,
                           tcp::endpoint endpoint_, std::string_view client_name_,
                           std::string_view client_version_) noexcept
    : peer_addr{peer_addr_}, cert_name{cert_name_}, endpoint{endpoint_}, client_name{client_name_},
      client_version{client_version_}, state{state_}, connection_id{connection_id_}, has_been_online{false} {
    peer_id = peer_id_;
    auto peer = cluster.get_devices().by_sha256(peer_id_);
    assert(peer);
    has_been_online = (state == device_state_t::offline) && (peer->get_state() == device_state_t::online);
    LOG_DEBUG(log, "peer_state_t ({}), device = {}, cert = {}, client ({})", (int)state, peer->device_id().get_short(),
              cert_name, client_name, client_version);
}

auto peer_state_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto peer = cluster.get_devices().by_sha256(peer_id);
    bool update_state = (peer->get_connection_id() == connection_id) || peer->get_state() < state ||
                        (peer->get_state() == state && peer->get_connection_id() > connection_id);
    if (update_state) {
        LOG_DEBUG(log, "peer_state_t, applying {}, device = {}", (int)state, peer->device_id().get_short());
        peer->update_state(state, connection_id);
        if (state == device_state_t::online) {
            peer->update_contact(endpoint, client_name, client_version);
        }
        peer->notify_update();

    } else {
        LOG_DEBUG(log, "peer_state_t, ignored {}, device = {}", (int)state, peer->device_id().get_short());
    }
    return applicator_t::apply_impl(cluster, controller);
}

auto peer_state_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include <rotor/address.hpp>
#include "../cluster_diff.h"
#include "model/device.h"
#include <boost/asio/ip/tcp.hpp>

namespace syncspirit::model::diff::contact {

namespace r = rotor;
using tcp = boost::asio::ip::tcp;

struct SYNCSPIRIT_API peer_state_t final : cluster_diff_t {

    static cluster_diff_ptr_t create(cluster_t &cluster, utils::bytes_view_t peer_id_,
                                     const r::address_ptr_t &peer_addr_, model::device_state_t state,
                                     std::string connection_id_ = {}, std::string cert_name_ = {},
                                     tcp::endpoint endpoint_ = {}, std::string_view client_name_ = {},
                                     std::string_view client_version_ = {}) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *custom) const noexcept override;

    utils::bytes_t peer_id;
    r::address_ptr_t peer_addr;
    std::string cert_name;
    tcp::endpoint endpoint;
    std::string client_name;
    std::string client_version;
    model::device_state_t state;
    std::string connection_id;
    bool has_been_online;

  private:
    peer_state_t(cluster_t &cluster, utils::bytes_view_t peer_id_, const r::address_ptr_t &peer_addr_,
                 model::device_state_t state, std::string connection_id_ = {}, std::string cert_name_ = {},
                 tcp::endpoint endpoint_ = {}, std::string_view client_name_ = {},
                 std::string_view client_version_ = {}) noexcept;
};

} // namespace syncspirit::model::diff::contact


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "relay_connect_request.h"
#include "../cluster_visitor.h"
#include "utils/format.hpp"

using namespace syncspirit::model::diff::contact;

relay_connect_request_t::relay_connect_request_t(model::device_id_t peer_, utils::bytes_t session_key_,
                                                 tcp::endpoint relay_) noexcept
    : peer{std::move(peer_)}, session_key{std::move(session_key_)}, relay{std::move(relay_)} {
    LOG_DEBUG(log, "relay_connect_request_t, device = {}, relay = {}", peer.get_short(), relay);
}

auto relay_connect_request_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    return applicator_t::apply_sibling(cluster, controller);
}

auto relay_connect_request_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting relay_connect_request_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include <boost/asio.hpp>
#include "../cluster_diff.h"
#include "model/cluster.h"
#include "model/device_id.h"

namespace syncspirit::model::diff::contact {

namespace asio = boost::asio;
using tcp = asio::ip::tcp;

struct SYNCSPIRIT_API relay_connect_request_t final : cluster_diff_t {
    relay_connect_request_t(model::device_id_t peer, utils::bytes_t session_key, tcp::endpoint relay) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    model::device_id_t peer;
    utils::bytes_t session_key;
    tcp::endpoint relay;
};

} // namespace syncspirit::model::diff::contact


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#include "unknown_connected.h"
#include "model/cluster.h"
#include "../cluster_visitor.h"
#include "model/misc/error_code.h"

using namespace syncspirit::model::diff::contact;

unknown_connected_t::unknown_connected_t(cluster_t &, const model::device_id_t &device_id_,
                                         db::SomeDevice db_device_) noexcept
    : device_id{device_id_}, db_device{std::move(db_device_)} {
    LOG_DEBUG(log, "unknown_connected_t, device = {}", device_id.get_short());
}

auto unknown_connected_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &pending_devices = cluster.get_pending_devices();
    auto prev = pending_devices.by_sha256(device_id.get_sha256());
    if (!prev) {
        return make_error_code(error_code_t::no_such_device);
    }
    prev->assign(db_device);
    prev->notify_update();
    return applicator_t::apply_sibling(cluster, controller);
}

auto unknown_connected_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting unknown_connected_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou

#pragma once

#include "../cluster_diff.h"
#include "model/pending_device.h"
#include "proto/proto-helpers.h"

namespace syncspirit::model::diff::contact {

struct SYNCSPIRIT_API unknown_connected_t final : cluster_diff_t {
    unknown_connected_t(cluster_t &cluster, const model::device_id_t &device_id, db::SomeDevice db_device) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    model::device_id_t device_id;
    db::SomeDevice db_device;
};

} // namespace syncspirit::model::diff::contact


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "update_contact.h"
#include "model/diff/cluster_visitor.h"
#include "model/cluster.h"
#include <fmt/core.h>
#include <set>

using namespace syncspirit;
using namespace syncspirit::model::diff::contact;

namespace {

struct comparator_t {
    bool operator()(const utils::uri_ptr_t &l, const utils::uri_ptr_t &r) const { return *l < *r; }
};
} // namespace

using set_t = std::set<utils::uri_ptr_t, comparator_t>;

update_contact_t::update_contact_t(const model::cluster_t &cluster, const model::device_id_t &device_,
                                   const utils::uri_container_t &uris_) noexcept
    : device{device_}, self{false} {
    auto &devices = cluster.get_devices();
    known = (bool)devices.by_sha256(device.get_sha256());
    if (known) {
        self = cluster.get_device()->device_id().get_sha256() == device.get_sha256();
    }
    LOG_DEBUG(log, "update_contact_t, device = {}, known = {}", device.get_short(), known);

    set_t set;
    std::copy(begin(uris_), end(uris_), std::inserter(set, set.begin()));
    std::copy(set.begin(), set.end(), std::back_insert_iterator(this->uris));
}

auto update_contact_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    if (known) {
        auto &devices = cluster.get_devices();
        auto device = devices.by_sha256(this->device.get_sha256());
        device->assign_uris(uris);
    }
    return applicator_t::apply_sibling(cluster, controller);
}

auto update_contact_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include "../cluster_diff.h"
#include "model/cluster.h"

namespace syncspirit::model::diff::contact {

struct SYNCSPIRIT_API update_contact_t final : cluster_diff_t {
    using ip_addresses_t = std::vector<std::string_view>;

    update_contact_t(const model::cluster_t &cluster, const model::device_id_t &device,
                     const utils::uri_container_t &uris) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    model::device_id_t device;
    utils::uri_container_t uris;
    bool known;
    bool self;
};

} // namespace syncspirit::model::diff::contact


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

namespace syncspirit::model::diff {

struct cluster_diff_t;

namespace advance {
struct advance_t;
struct local_update_t;
struct remote_copy_t;
struct remote_win_t;
} // namespace advance

namespace contact {
struct connect_request_t;
struct dial_request_t;
struct ignored_connected_t;
struct peer_state_t;
struct relay_connect_request_t;
struct unknown_connected_t;
struct update_contact_t;
} // namespace contact

namespace load {
struct blocks_t;
struct devices_t;
struct file_infos_t;
struct load_cluster_t;
struct ignored_devices_t;
struct pending_devices_t;
} // namespace load

namespace local {
struct blocks_availability_t;
struct custom_t;
struct file_availability_t;
struct io_failure_t;
struct scan_finish_t;
struct scan_request_t;
struct scan_start_t;
struct synchronization_start_t;
struct synchronization_finish_t;
} // namespace local

namespace peer {
struct cluster_update_t;
struct rx_tx_t;
struct update_folder_t;
} // namespace peer

namespace modify {
struct add_blocks_t;
struct add_ignored_device_t;
struct add_pending_device_t;
struct add_pending_folders_t;
struct add_remote_folder_infos_t;
struct append_block_t;
struct block_ack_t;
struct block_rej_t;
struct block_transaction_t;
struct clone_block_t;
struct finish_file_t;
struct generic_remove_t;
struct lock_file_t;
struct mark_reachable_t;
struct remove_blocks_t;
struct remove_files_t;
struct remove_folder_infos_t;
struct remove_folder_t;
struct remove_ignored_device_t;
struct remove_peer_t;
struct remove_pending_device_t;
struct remove_pending_folders_t;
struct reset_folder_infos_t;
struct share_folder_t;
struct suspend_folder_t;
struct unshare_folder_t;
struct update_peer_t;
struct upsert_folder_info_t;
struct upsert_folder_t;
} // namespace modify

} // namespace syncspirit::model::diff


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "blocks.h"
#include "model/cluster.h"
#include "model/diff/apply_controller.h"
#include "model/misc/error_code.h"
#include "proto/proto-helpers-db.h"

using namespace syncspirit::model::diff::load;

auto blocks_t::apply_forward(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    return controller.apply(*this, cluster);
}

auto blocks_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept -> outcome::result<void> {
    auto &blocks_map = cluster.get_blocks();
    for (auto &pair : blocks) {
        auto db_block = db::BlockInfo();
        if (auto left = db::decode(pair.value, db_block); left) {
            return make_error_code(error_code_t::block_deserialization_failure);
        }
        auto block = block_info_t::create(pair.key, db_block);
        if (block.has_error()) {
            return block.assume_error();
        }
        blocks_map.put(std::move(block.value()));
    }
    return applicator_t::apply_sibling(cluster, controller);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include "common.h"

namespace syncspirit::model::diff::load {

struct SYNCSPIRIT_API blocks_t final : cluster_diff_t {
    template <typename T> blocks_t(T &&blocks_) noexcept : blocks{std::forward<T>(blocks_)} {}

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> apply_forward(cluster_t &, apply_controller_t &) const noexcept override;

    container_t blocks;
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include "utils/bytes.h"
#include "../cluster_diff.h"

namespace syncspirit::model::diff::load {

struct pair_t {
    utils::bytes_view_t key;
    utils::bytes_view_t value;
};

using container_t = std::vector<pair_t>;

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "devices.h"
#include "model/cluster.h"
#include "model/misc/error_code.h"
#include "model/diff/cluster_visitor.h"
#include "proto/proto-helpers-db.h"

using namespace syncspirit::model::diff::load;

auto devices_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept -> outcome::result<void> {
    auto &device_map = cluster.get_devices();
    auto &local_device = cluster.get_device();
    for (auto &pair : devices) {
        auto device = device_ptr_t();
        if (pair.key == local_device->get_key()) {
            device = local_device;
        } else {
            auto db_device = db::Device();
            if (auto left = db::decode(pair.value, db_device); left) {
                return make_error_code(error_code_t::device_deserialization_failure);
            }
            auto option = device_t::create(pair.key, db_device);
            if (!option) {
                return option.assume_error();
            }
            device = std::move(option.value());
        }
        device_map.put(device);
    }
    assert(device_map.by_sha256(local_device->device_id().get_sha256()));
    return applicator_t::apply_sibling(cluster, controller);
}

auto devices_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#pragma once

#include "common.h"

namespace syncspirit::model::diff::load {

struct SYNCSPIRIT_API devices_t final : cluster_diff_t {
    template <typename T> devices_t(T &&devices_) noexcept : devices{std::forward<T>(devices_)} {}

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    container_t devices;
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "file_infos.h"
#include "model/cluster.h"
#include "model/diff/apply_controller.h"
#include "proto/proto-helpers-db.h"
#include <unordered_map>

using namespace syncspirit::model::diff::load;

auto file_infos_t::apply_forward(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    return controller.apply(*this, cluster);
}

auto file_infos_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    using folder_info_by_id_t = std::unordered_map<utils::bytes_view_t, folder_info_ptr_t>;
    auto all_fi = folder_info_by_id_t{};
    auto &folders = cluster.get_folders();
    for (auto f : folders) {
        for (auto it : f.item->get_folder_infos()) {
            auto &f = it.item;
            all_fi[f->get_uuid()] = f;
        }
    }
    auto &blocks = cluster.get_blocks();
    for (auto &pair : container) {
        auto key = pair.first;
        auto &db = pair.second;
        auto folder_info_uuid = key.subspan(1, uuid_length);
        auto folder_info = all_fi[folder_info_uuid];
        if (!folder_info) {
            auto name = db::get_name(db);
            LOG_WARN(log, "cannot restore file '{}', missing folder, corrupted db?", name);
            continue;
        }

        auto option = file_info_t::create(key, db, folder_info);
        if (!option) {
            return option.assume_error();
        }
        auto &fi = option.assume_value();

        auto blocks_count = db::get_blocks_size(db);
        auto confirmed_blocks = size_t{0};
        for (size_t i = 0; i < blocks_count; ++i) {
            auto block_hash = db::get_blocks(db, i);
            auto block = blocks.by_hash(block_hash);
            if (block) {
                fi->assign_block(std::move(block), i);
                ++confirmed_blocks;
            } else {
                break;
            }
        }
        if (blocks_count == confirmed_blocks) {
            folder_info->add_relaxed(fi);
        } else {
            LOG_WARN(log, "1+ block(s) are missing for file '{}', discarding the file", fi->get_name());
        }
    }
    return applicator_t::apply_sibling(cluster, controller);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#pragma once

#include "common.h"
#include "proto/proto-fwd.hpp"

namespace syncspirit::model::diff::load {

struct SYNCSPIRIT_API file_infos_t final : cluster_diff_t {
    using item_t = std::pair<utils::bytes_view_t, db::FileInfo>;
    using container_t = std::vector<item_t>;
    inline file_infos_t(container_t &&container_) noexcept : container{std::move(container_)} {}

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> apply_forward(cluster_t &, apply_controller_t &) const noexcept override;

    container_t container;
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "folder_infos.h"
#include "model/misc/error_code.h"
#include "model/cluster.h"
#include "proto/proto-helpers-db.h"

using namespace syncspirit::model::diff::load;

auto folder_infos_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &folders = cluster.get_folders();
    auto &devices = cluster.get_devices();
    for (auto &pair : container) {
        auto &key = pair.key;
        auto decomposed = folder_info_t::decompose_key(key);
        auto device_key = decomposed.device_key();
        auto device = devices.by_key(device_key);
        if (!device) {
            return make_error_code(error_code_t::no_such_device);
        }

        auto folder_key = decomposed.folder_key();
        auto folder = folders.by_key(folder_key);
        if (!folder) {
            return make_error_code(error_code_t::no_such_folder);
        }

        auto db_fi = db::FolderInfo();
        if (auto left = db::decode(pair.value, db_fi); left) {
            return make_error_code(error_code_t::folder_info_deserialization_failure);
        }

        auto &map = folder->get_folder_infos();
        auto option = folder_info_t::create(key, db_fi, device, folder);
        if (!option) {
            return option.assume_error();
        }
        auto &fi = option.assume_value();
        map.put(fi);
    }
    return applicator_t::apply_sibling(cluster, controller);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#pragma once

#include "common.h"

namespace syncspirit::model::diff::load {

struct SYNCSPIRIT_API folder_infos_t final : cluster_diff_t {
    template <typename T> folder_infos_t(T &&container_) noexcept : container{std::forward<T>(container_)} {}

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;

    container_t container;
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "folders.h"
#include "model/cluster.h"
#include "model/misc/error_code.h"
#include "proto/proto-helpers-db.h"

using namespace syncspirit::model::diff::load;

auto folders_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept -> outcome::result<void> {
    auto &map = cluster.get_folders();
    for (auto &pair : folders) {

        auto db_folder = db::Folder();
        if (auto left = db::decode(pair.value, db_folder); left) {
            return make_error_code(error_code_t::folder_deserialization_failure);
        }
        auto option = folder_t::create(pair.key, db_folder);
        if (!option) {
            return option.assume_error();
        }
        auto &folder = option.value();
        map.put(folder);
        folder->assign_cluster(&cluster);
    }
    return applicator_t::apply_sibling(cluster, controller);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2022 Ivan Baidakou

#pragma once

#include "common.h"

namespace syncspirit::model::diff::load {

struct SYNCSPIRIT_API folders_t final : cluster_diff_t {
    template <typename T> folders_t(T &&folders_) noexcept : folders{std::forward<T>(folders_)} {}

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;

    container_t folders;
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#include "model/cluster.h"
#include "ignored_devices.h"
#include "some_devices.hpp"
#include "model/diff/cluster_visitor.h"

using namespace syncspirit::model::diff::load;

auto ignored_devices_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    using device_t = model::ignored_device_t;
    auto &map = cluster.get_ignored_devices();
    auto r = some_devices_t::apply<device_t>(devices, map);
    return r ? applicator_t::apply_sibling(cluster, controller) : r;
}

auto ignored_devices_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2022 Ivan Baidakou

#pragma once

#include "common.h"

namespace syncspirit::model::diff::load {

struct SYNCSPIRIT_API ignored_devices_t final : cluster_diff_t {
    template <typename T> ignored_devices_t(T &&devices_) noexcept : devices{std::forward<T>(devices_)} {}

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    container_t devices;
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "ignored_folders.h"
#include "model/cluster.h"

using namespace syncspirit::model::diff::load;

auto ignored_folders_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &map = cluster.get_ignored_folders();
    for (auto &pair : folders) {
        auto option = ignored_folder_t::create(pair.key, pair.value);
        if (!option) {
            return option.assume_error();
        }
        auto &folder = option.value();
        map.put(folder);
    }
    return applicator_t::apply_sibling(cluster, controller);
};


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2022 Ivan Baidakou

#pragma once

#include "common.h"

namespace syncspirit::model::diff::load {

struct SYNCSPIRIT_API ignored_folders_t final : cluster_diff_t {
    template <typename T> ignored_folders_t(T &&folders_) noexcept : folders{std::forward<T>(folders_)} {}

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;

    container_t folders;
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#include "load_cluster.h"
#include "model/diff/cluster_visitor.h"

using namespace syncspirit::model::diff::load;

load_cluster_t::load_cluster_t(db::transaction_t txn_, std::size_t blocks_count_, std::size_t files_count_) noexcept
    : txn{std::move(txn_)}, blocks_count{blocks_count_}, files_count{files_count_} {}

auto load_cluster_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    return applicator_t::apply_impl(cluster, controller);
}

auto load_cluster_t::apply_forward(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    return controller.apply(*this, cluster);
}

auto load_cluster_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#pragma once
#include "model/diff/cluster_diff.h"
#include "model/diff/apply_controller.h"
#include "db/transaction.h"

namespace syncspirit::model::diff::load {

struct SYNCSPIRIT_API load_cluster_t final : cluster_diff_t {
    using parent_t = cluster_diff_t;

    load_cluster_t(db::transaction_t txn, std::size_t blocks_count, std::size_t files_count) noexcept;

    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;
    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> apply_forward(cluster_t &, apply_controller_t &) const noexcept override;

    db::transaction_t txn;
    std::size_t blocks_count;
    std::size_t files_count;
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#include "model/cluster.h"
#include "pending_devices.h"
#include "some_devices.hpp"
#include "model/diff/cluster_visitor.h"

using namespace syncspirit::model::diff::load;

auto pending_devices_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    using device_t = model::pending_device_t;
    auto &map = cluster.get_pending_devices();
    auto r = some_devices_t::apply<device_t>(devices, map);
    return r ? applicator_t::apply_sibling(cluster, controller) : r;
}

auto pending_devices_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#pragma once

#include "common.h"

namespace syncspirit::model::diff::load {

struct SYNCSPIRIT_API pending_devices_t final : cluster_diff_t {
    template <typename T> pending_devices_t(T &&devices_) noexcept : devices{std::forward<T>(devices_)} {}

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    container_t devices;
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "pending_folders.h"
#include "model/cluster.h"
#include "model/misc/error_code.h"
#include "proto/proto-helpers-db.h"

using namespace syncspirit::model::diff::load;

auto pending_folders_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &items = cluster.get_pending_folders();
    for (auto &pair : folders) {
        auto db_pf = db::PendingFolder();
        if (auto left = db::decode(pair.value, db_pf); left) {
            return make_error_code(error_code_t::pending_folder_deserialization_failure);
        }
        auto option = pending_folder_t::create(pair.key, db_pf);
        if (!option) {
            return option.assume_error();
        }
        auto &folder = option.value();
        items.put(std::move(folder));
    }
    return applicator_t::apply_sibling(cluster, controller);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#pragma once

#include "common.h"

namespace syncspirit::model::diff::load {

struct SYNCSPIRIT_API pending_folders_t final : cluster_diff_t {
    template <typename T> pending_folders_t(T &&folders_) noexcept : folders{std::forward<T>(folders_)} {}

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;

    container_t folders;
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou

#pragma once

#include "model/device_id.h"
#include "model/misc/error_code.h"
#include "proto/proto-helpers-db.h"
#include "common.h"

#include <boost/outcome.hpp>

namespace syncspirit::model::diff::load {

namespace outcome = boost::outcome_v2;

struct some_devices_t {
    template <typename T> static outcome::result<void> apply(const container_t items, typename T::map_t &map) noexcept {
        for (auto &pair : items) {
            auto key = pair.key;
            auto sha256 = key.subspan(1, T::digest_length);
            auto device_id_opt = device_id_t::from_sha256(sha256);
            if (!device_id_opt) {
                return make_error_code(error_code_t::malformed_deviceid);
            }
            auto &device_id = *device_id_opt;

            auto db_sd = db::SomeDevice();
            if (auto left = db::decode(pair.value, db_sd); left) {
                return make_error_code(error_code_t::some_device_deserialization_failure);
            }
            auto option = T::create(device_id, db_sd);
            if (!option) {
                return option.assume_error();
            }
            auto &device = option.assume_value();
            map.put(std::move(device));
        }
        return outcome::success();
    }
};

} // namespace syncspirit::model::diff::load


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#include "blocks_availability.h"
#include "model/diff/cluster_visitor.h"
#include "model/cluster.h"

using namespace syncspirit::model::diff::local;

blocks_availability_t::blocks_availability_t(const file_info_t &file, valid_blocks_map_t valid_blocks_map_) noexcept
    : block_diff_t{file}, valid_blocks_map{std::move(valid_blocks_map_)} {
    LOG_DEBUG(log, "blocks_availability_t, file = {}, valid blocks = {}", file.get_name(), valid_blocks_map.size());
    assert(!file.is_locally_available());
    assert(file.get_blocks().size() == valid_blocks_map.size());
}

auto blocks_availability_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto folder = cluster.get_folders().by_id(folder_id);
    auto folder_info = folder->get_folder_infos().by_device_id(device_id);
    auto file = folder_info->get_file_infos().by_name(file_name);
    for (size_t i = 0; i < valid_blocks_map.size(); ++i) {
        if (valid_blocks_map[i]) {
            file->mark_local_available(i);
        }
    }
    return applicator_t::apply_sibling(cluster, controller);
}

auto blocks_availability_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting blocks_availability_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#pragma once

#include "../block_diff.h"
#include "model/file_info.h"
#include <vector>

namespace syncspirit::model::diff::local {

struct SYNCSPIRIT_API blocks_availability_t final : block_diff_t {
    using valid_blocks_map_t = std::vector<bool>;

    blocks_availability_t(const file_info_t &file, valid_blocks_map_t valid_blocks_map) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    valid_blocks_map_t valid_blocks_map;
};

} // namespace syncspirit::model::diff::local


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#include "custom.h"
#include "model/diff/cluster_visitor.h"

using namespace syncspirit::model::diff::local;

auto custom_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting custom_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#pragma once

#include "../cluster_diff.h"

namespace syncspirit::model::diff::local {

struct SYNCSPIRIT_API custom_t : cluster_diff_t {
    custom_t() = default;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;
};

} // namespace syncspirit::model::diff::local


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "file_availability.h"
#include "../cluster_visitor.h"

using namespace syncspirit::model::diff::local;

file_availability_t::file_availability_t(file_info_ptr_t file_) noexcept : file{file_} {
    LOG_DEBUG(log, "file_availability_t, file = {}", file->get_name());
    folder_id = file_->get_folder_info()->get_folder()->get_id();
    version.reset(new version_t(file->get_version()->as_proto()));
}

auto file_availability_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto folder = cluster.get_folders().by_id(folder_id);
    if (folder) {
        auto folder_info = folder->get_folder_infos().by_device(*cluster.get_device());
        auto f = folder_info->get_file_infos().by_name(file->get_name());
        if (f->get_version()->identical_to(*version)) {
            f->mark_local();
            LOG_TRACE(log, "file_availability_t, mark local file = {}, folder = {}, ", file->get_name(), folder_id);
            auto &blocks = f->get_blocks();
            for (size_t i = 0; i < blocks.size(); ++i) {
                if (!f->is_locally_available(i)) {
                    f->mark_local_available(i);
                }
            }
            f->notify_update();
        }
    }
    return applicator_t::apply_sibling(cluster, controller);
}

auto file_availability_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting file_availability_t, folder = {}, file = {}", folder_id, file->get_name());
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou

#pragma once

#include "model/cluster.h"
#include "../cluster_diff.h"
#include "../cluster_visitor.h"

namespace syncspirit::model::diff::local {

struct SYNCSPIRIT_API file_availability_t final : cluster_diff_t {

    file_availability_t(file_info_ptr_t file) noexcept;

    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    std::string folder_id;
    model::file_info_ptr_t file;
    model::version_ptr_t version;
};

} // namespace syncspirit::model::diff::local


code:
#include "io_failure.h"
#include "../cluster_visitor.h"

using namespace syncspirit::model::diff::local;

io_failure_t::io_failure_t(io_error_t e) noexcept : errors{std::move(e)} {}

io_failure_t::io_failure_t(io_errors_t errors_) noexcept : errors{std::move(errors_)} {}

auto io_failure_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting io_failure_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#pragma once

#include "../cluster_diff.h"
#include <vector>
#include <boost/system/errc.hpp>
#include <filesystem>

namespace syncspirit::model::diff::local {

namespace sys = boost::system;
namespace bfs = std::filesystem;

struct io_error_t {
    bfs::path path;
    sys::error_code ec;
};

using io_errors_t = std::vector<io_error_t>;

struct SYNCSPIRIT_API io_failure_t final : cluster_diff_t {
    io_failure_t(io_error_t) noexcept;
    io_failure_t(io_errors_t) noexcept;

    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    io_errors_t errors;
};

} // namespace syncspirit::model::diff::local


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#include "scan_finish.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"

using namespace syncspirit::model::diff::local;

scan_finish_t::scan_finish_t(std::string_view folder_id_, const pt::ptime &at_)
    : folder_id{std::move(folder_id_)}, at{at_} {
    LOG_DEBUG(log, "scan_finish_t, folder = {}", folder_id);
}

auto scan_finish_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto folder = cluster.get_folders().by_id(folder_id);
    folder->set_scan_finish(at);
    auto r = applicator_t::apply_sibling(cluster, controller);
    folder->notify_update();
    return r;
}

auto scan_finish_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting scan_finish_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#pragma once

#include <string>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "../cluster_diff.h"

namespace syncspirit::model::diff::local {

namespace pt = boost::posix_time;

struct SYNCSPIRIT_API scan_finish_t final : cluster_diff_t {
    scan_finish_t(std::string_view folder_id, const pt::ptime &at);
    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    std::string folder_id;
    pt::ptime at;
};

} // namespace syncspirit::model::diff::local


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#include "scan_request.h"
#include "model/diff/cluster_visitor.h"

using namespace syncspirit::model::diff::local;

scan_request_t::scan_request_t(std::string_view folder_id_) : folder_id{std::move(folder_id_)} {
    LOG_DEBUG(log, "scan_request_t, folder = {}", folder_id);
}

auto scan_request_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting scan_request_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#pragma once

#include <string>
#include "../cluster_diff.h"

namespace syncspirit::model::diff::local {

struct SYNCSPIRIT_API scan_request_t final : cluster_diff_t {
    scan_request_t(std::string_view folder_id);
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    std::string folder_id;
};

} // namespace syncspirit::model::diff::local


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#include "scan_start.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"

using namespace syncspirit::model::diff::local;

scan_start_t::scan_start_t(std::string_view folder_id_, const pt::ptime &at_)
    : folder_id{std::move(folder_id_)}, at{at_} {
    LOG_DEBUG(log, "scan_start_t, folder = {}", folder_id);
}

auto scan_start_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto folder = cluster.get_folders().by_id(folder_id);
    folder->set_scan_start(at);
    auto r = applicator_t::apply_sibling(cluster, controller);
    folder->notify_update();
    return r;
}

auto scan_start_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting scan_start_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#pragma once

#include <string>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "../cluster_diff.h"

namespace syncspirit::model::diff::local {

namespace pt = boost::posix_time;

struct SYNCSPIRIT_API scan_start_t final : cluster_diff_t {
    scan_start_t(std::string_view folder_id, const pt::ptime &at);
    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    std::string folder_id;
    pt::ptime at;
};

} // namespace syncspirit::model::diff::local


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou

#include "synchronization_finish.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"

using namespace syncspirit::model::diff::local;

synchronization_finish_t::synchronization_finish_t(std::string_view folder_id_) : folder_id{std::move(folder_id_)} {
    LOG_DEBUG(log, "synchronization_finish_t, folder = {}", folder_id);
}

auto synchronization_finish_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto folder = cluster.get_folders().by_id(folder_id);
    if (folder) {
        folder->adjust_synchronization(-1);
        folder->notify_update();
    }
    auto r = applicator_t::apply_sibling(cluster, controller);
    return r;
}

auto synchronization_finish_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting synchronization_finish_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#pragma once

#include <string>
#include "../cluster_diff.h"

namespace syncspirit::model::diff::local {

struct SYNCSPIRIT_API synchronization_finish_t final : cluster_diff_t {
    synchronization_finish_t(std::string_view folder_id);
    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    std::string folder_id;
};

} // namespace syncspirit::model::diff::local


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou

#include "synchronization_start.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"

using namespace syncspirit::model::diff::local;

synchronization_start_t::synchronization_start_t(std::string_view folder_id_) : folder_id{std::move(folder_id_)} {
    LOG_DEBUG(log, "synchronization_start_t, folder = {}", folder_id);
}

auto synchronization_start_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto folder = cluster.get_folders().by_id(folder_id);
    if (folder) {
        folder->adjust_synchronization(1);
        folder->notify_update();
    }
    auto r = applicator_t::apply_sibling(cluster, controller);
    return r;
}

auto synchronization_start_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting synchronization_start_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#pragma once

#include <string>
#include "../cluster_diff.h"

namespace syncspirit::model::diff::local {

struct SYNCSPIRIT_API synchronization_start_t final : cluster_diff_t {
    synchronization_start_t(std::string_view folder_id);
    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    std::string folder_id;
};

} // namespace syncspirit::model::diff::local


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou

#include "add_blocks.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"

using namespace syncspirit::model::diff::modify;

add_blocks_t::add_blocks_t(blocks_t blocks_) noexcept : blocks(std::move(blocks_)) {
    LOG_DEBUG(log, "add_blocks_t, count = {}", blocks.size());
}

auto add_blocks_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &bm = cluster.get_blocks();
    for (const auto &b : blocks) {
        auto opt = block_info_t::create(b);
        if (!opt) {
            return opt.assume_error();
        }
        auto block = std::move(opt.assume_value());
        bm.put(block);
    }
    return applicator_t::apply_sibling(cluster, controller);
}

auto add_blocks_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting add_blocks_t");
    return visitor(*this, custom);
}


code:
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 Ivan Baidakou

#pragma once

#include "model/diff/cluster_diff.h"
#include "proto/proto-fwd.hpp"
#include <vector>

namespace syncspirit::model::diff::modify {

struct SYNCSPIRIT_API add_blocks_t : cluster_diff_t {
    using blocks_t = std::vector<proto::BlockInfo>;

    add_blocks_t(blocks_t blocks) noexcept;
    outcome::result<void> apply_impl(cluster_t &, apply_controller_t &) const noexcept override;
    outcome::result<void> visit(cluster_visitor_t &, void *) const noexcept override;

    blocks_t blocks;
};

} // namespace syncspirit::model::diff::modify