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

#include "add_ignored_device.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"
#include "model/diff/modify/remove_pending_device.h"

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

add_ignored_device_t::add_ignored_device_t(const cluster_t &cluster, const device_id_t &id_,
                                           db::SomeDevice db_device_) noexcept
    : device_id{id_}, db_device{db_device_} {
    auto peer = cluster.get_pending_devices().by_sha256(device_id.get_sha256());
    if (peer) {
        assign_child(new remove_pending_device_t(*peer));
    }
    LOG_DEBUG(log, "add_ignored_device_t, peer = {}, pending = {}", device_id.get_short(), (bool)(peer));
}

auto add_ignored_device_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto r = parent_t::apply_child(cluster, controller);
    if (!r) {
        return r;
    }
    auto opt = ignored_device_t::create(device_id, db_device);
    if (!opt) {
        return opt.assume_error();
    }
    auto &ignored_device = opt.assume_value();
    cluster.get_ignored_devices().put(std::move(ignored_device));
    return applicator_t::apply_sibling(cluster, controller);
}

auto add_ignored_device_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting add_ignored_device_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-helpers.h"

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

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

    add_ignored_device_t(const cluster_t &cluster, const device_id_t &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;

    device_id_t device_id;
    db::SomeDevice db_device;
};

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

repo_name: basiliscos/syncspirit

license: mit


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

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

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

add_pending_device_t::add_pending_device_t(const device_id_t &id_, db::SomeDevice db_device_) noexcept
    : device_id{id_}, db_device{db_device_} {
    LOG_DEBUG(log, "add_pending_device_t, peer = {}", device_id.get_short());
}

auto add_pending_device_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto opt = pending_device_t::create(device_id, db_device);
    if (!opt) {
        return opt.assume_error();
    }
    auto &pending_device = opt.assume_value();
    cluster.get_pending_devices().put(std::move(pending_device));
    return applicator_t::apply_sibling(cluster, controller);
}

auto add_pending_device_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting add_pending_device_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-helpers.h"

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

struct SYNCSPIRIT_API add_pending_device_t final : cluster_diff_t {
    add_pending_device_t(const device_id_t &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;

    device_id_t device_id;
    db::SomeDevice db_device;
};

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


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

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

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

add_pending_folders_t::add_pending_folders_t(container_t items) noexcept : container(std::move(items)) {
    LOG_DEBUG(log, "add_pending_folders_t, count = {}", container.size());
}

auto add_pending_folders_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &pending = cluster.get_pending_folders();
    auto &devices = cluster.get_devices();
    for (auto &item : container) {
        auto peer = devices.by_sha256(item.peer_id);
        auto &db = item.db;
        auto opt = pending_folder_t::create(item.uuid, db, peer->device_id());
        if (!opt) {
            return opt.assume_error();
        }
        pending.put(std::move(opt.value()));
    }
    return applicator_t::apply_sibling(cluster, controller);
}

auto add_pending_folders_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting add_pending_folders_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.h"
#include "model/misc/uuid.h"
#include "proto/proto-fwd.hpp"
#include <vector>

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

struct SYNCSPIRIT_API add_pending_folders_t final : cluster_diff_t {
    struct item_t {
        db::PendingFolder db;
        utils::bytes_t peer_id;
        bu::uuid uuid;
    };
    using container_t = std::vector<item_t>;

    add_pending_folders_t(container_t items) 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;

    container_t container;
};

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


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

#include "add_remote_folder_infos.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"
#include "model/misc/error_code.h"

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

add_remote_folder_infos_t::add_remote_folder_infos_t(const model::device_t &peer, container_t items) noexcept
    : container{std::move(items)} {
    device_id = peer.device_id().get_sha256();
    LOG_DEBUG(log, "add_remote_folder_infos_t, device = {}, items = {}", peer.device_id().get_short(),
              container.size());
}

auto add_remote_folder_infos_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &devices = cluster.get_devices();
    auto &folders = cluster.get_folders();
    auto peer = devices.by_sha256(device_id);
    if (!peer) {
        return make_error_code(error_code_t::no_such_device);
    }
    auto &remote_folders = peer->get_remote_folder_infos();
    for (auto &item : container) {
        auto folder = folders.by_id(item.folder_id);
        if (!folder) {
            return make_error_code(error_code_t::no_such_folder);
        }
        auto opt = model::remote_folder_info_t::create(item.index_id, item.max_sequence, *peer, *folder);
        if (!opt) {
            return opt.assume_error();
        }
        remote_folders.put(opt.assume_value());
    }
    return applicator_t::apply_sibling(cluster, controller);
}

auto add_remote_folder_infos_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept
    -> outcome::result<void> {
    LOG_TRACE(log, "visiting add_remote_folder_infos_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.h"
#include <vector>

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

struct SYNCSPIRIT_API add_remote_folder_infos_t final : cluster_diff_t {
    struct item_t {
        std::string folder_id;
        std::uint64_t index_id;
        std::int64_t max_sequence;
    };
    using container_t = std::vector<item_t>;

    add_remote_folder_infos_t(const model::device_t &peer, container_t items) 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 device_id;
    container_t container;
};

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


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

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

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

append_block_t::append_block_t(const file_info_t &file, size_t block_index_, utils::bytes_t data_) noexcept
    : block_transaction_t{file, block_index_}, data{std::move(data_)} {
    LOG_DEBUG(log, "append_block_t, file = {}, block = {}", file.get_name(), block_index);
}

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


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

#pragma once

#include "block_transaction.h"
#include "model/file_info.h"
#include "utils/bytes.h"

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

struct SYNCSPIRIT_API append_block_t final : block_transaction_t {
    append_block_t(const file_info_t &file, size_t block_index, utils::bytes_t data) noexcept;

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

    utils::bytes_t data;
};

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


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

#include "block_ack.h"
#include "model/file_info.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"

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

block_ack_t::block_ack_t(const block_transaction_t &txn) noexcept : parent_t(txn) {
    LOG_DEBUG(log, "block_ack_t, file = {}, folder = {}, block = {}", file_name, folder_id, block_index);
}

auto block_ack_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto folder = cluster.get_folders().by_id(folder_id);
    bool success = false;
    if (folder) {
        auto folder_info = folder->get_folder_infos().by_device_id(device_id);
        if (folder_info) {
            auto file = folder_info->get_file_infos().by_name(file_name);
            if (file) {
                LOG_TRACE(log, "block_ack_t, '{}' block # {}", file->get_full_name(), block_index);
                file->mark_local_available(block_index);
                success = true;
            }
        }
    }
    if (!success) {
        LOG_TRACE(log, "block_ack_t failed, folder = '{}', file = '{}', block # {}", folder_id, file_name, block_index);
    }
    return applicator_t::apply_sibling(cluster, controller);
}

auto block_ack_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting block_ack_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 "block_transaction.h"

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

struct SYNCSPIRIT_API block_ack_t final : block_diff_t {
    using parent_t = block_diff_t;

    block_ack_t(const block_transaction_t &) 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::modify


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

#include "block_rej.h"
#include "model/diff/cluster_visitor.h"
#include "model/file_info.h"
#include "model/cluster.h"

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

block_rej_t::block_rej_t(const block_transaction_t &txn) noexcept : parent_t(txn) {
    LOG_DEBUG(log, "block_rej_t, file = {}, folder = {}, block = {}", file_name, folder_id, block_index);
}

auto block_rej_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);
    LOG_TRACE(log, "block_rej_t, '{}' block # {}", file->get_full_name(), block_index);
    file->mark_local_available(block_index);
    return applicator_t::apply_sibling(cluster, controller);
}

auto block_rej_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting block_rej_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 "block_transaction.h"

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

struct SYNCSPIRIT_API block_rej_t final : block_diff_t {
    using parent_t = block_diff_t;

    block_rej_t(const block_transaction_t &) 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::modify


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

#include "block_transaction.h"
#include "block_ack.h"
#include "block_rej.h"

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

block_transaction_t::block_transaction_t(const file_info_t &file, size_t block_index) : parent_t(file, block_index) {}

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

auto block_transaction_t::ack() const -> cluster_diff_ptr_t { return new block_ack_t(*this); }

auto block_transaction_t::rej() const -> cluster_diff_ptr_t { return new block_rej_t(*this); }


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

#pragma once

#include "../block_diff.h"

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

struct SYNCSPIRIT_API block_transaction_t : block_diff_t {
    using parent_t = block_diff_t;

    block_transaction_t(const file_info_t &file, size_t block_index);

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

    cluster_diff_ptr_t ack() const;
    cluster_diff_ptr_t rej() const;
};

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


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

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

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

clone_block_t::clone_block_t(const file_block_t &file_block) noexcept
    : block_transaction_t{*file_block.file(), file_block.block_index()} {
    const file_info_t *source_file = nullptr;
    auto &block_pieces = file_block.block()->get_file_blocks();
    for (auto &b : block_pieces) {
        if (b.is_locally_available()) {
            source_file = b.file();
            source_block_index = b.block_index();
            break;
        }
    }
    assert(source_file);
    assert(file_block.file()->get_blocks().at(block_index)->get_hash() == source_file->get_folder_info()
                                                                              ->get_folder()
                                                                              ->get_cluster()
                                                                              ->get_folders()
                                                                              .by_id(folder_id)
                                                                              ->get_folder_infos()
                                                                              .by_device_id(device_id)
                                                                              ->get_file_infos()
                                                                              .by_name(file_name)
                                                                              ->get_blocks()
                                                                              .at(block_index)
                                                                              ->get_hash());
    auto source_fi = source_file->get_folder_info();
    source_device_id = source_fi->get_device()->device_id().get_sha256();
    source_folder_id = source_fi->get_folder()->get_id();
    source_file_name = source_file->get_name();

    LOG_DEBUG(log, "clone_block_t, file = {}, folder = {}, block = {}", file_name, folder_id, block_index);
}

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


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

#pragma once

#include "block_transaction.h"
#include "model/misc/file_block.h"

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

struct SYNCSPIRIT_API clone_block_t final : block_transaction_t {
    clone_block_t(const file_block_t &file_block) noexcept;

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

    utils::bytes_t source_device_id;
    std::string source_folder_id;
    std::string source_file_name;
    size_t source_block_index;
};

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


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

#include "finish_file.h"

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

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

finish_file_t::finish_file_t(const model::file_info_t &file) noexcept {
    auto fi = file.get_folder_info();
    auto folder = fi->get_folder();
    auto &device_id = fi->get_device()->device_id();
    folder_id = folder->get_id();
    file_name = file.get_name();
    peer_id = device_id.get_sha256();
    assert(device_id != folder->get_cluster()->get_device()->device_id());
    action = resolve(file);
    assert(action != advance_action_t::ignore);
    LOG_DEBUG(log, "finish_file_t, file = {}, folder = {}, peer = {}, action = {}", file_name, folder_id, device_id,
              (int)action);
}

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


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

#pragma once

#include "../cluster_diff.h"
#include "model/file_info.h"
#include "model/device.h"
#include "model/misc/resolver.h"

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

struct SYNCSPIRIT_API finish_file_t final : cluster_diff_t {
    finish_file_t(const model::file_info_t &file) noexcept;

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

    std::string folder_id;
    utils::bytes_t peer_id;
    std::string file_name;
    model::advance_action_t action;
};

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


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

#include "generic_remove.h"

#include <algorithm>

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

generic_remove_t::generic_remove_t(keys_t keys_) noexcept : keys{std::move(keys_)} {}

generic_remove_t::generic_remove_t(unique_keys_t keys_) noexcept {
    keys.reserve(keys_.size());
    std::copy(keys_.begin(), keys_.end(), std::back_insert_iterator(keys));
}


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

#pragma once

#include "model/diff/cluster_diff.h"
#include "utils/bytes_comparator.hpp"
#include "utils/bytes.h"
#include <set>

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

struct SYNCSPIRIT_API generic_remove_t : cluster_diff_t {
    using unique_keys_t = std::set<utils::bytes_t, utils::bytes_comparator_t>;
    using keys_t = std::vector<utils::bytes_t>;

    generic_remove_t() = default;
    generic_remove_t(keys_t keys) noexcept;
    generic_remove_t(unique_keys_t keys) noexcept;

    keys_t keys;
};

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


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

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

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

lock_file_t::lock_file_t(const model::file_info_t &file, bool locked_) noexcept
    : folder_id{file.get_folder_info()->get_folder()->get_id()}, file_name{file.get_name()}, locked{locked_} {
    auto &peer = file.get_folder_info()->get_device()->device_id();
    device_id = peer.get_sha256();
    LOG_DEBUG(log, "lock_file_t, file = {}, folder = {}, device = {}, value = {}", file_name, folder_id, peer, locked);
}

auto lock_file_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 d = cluster.get_devices().by_sha256(this->device_id);
    auto folder_info = folder->get_folder_infos().by_device(*d);
    auto file = folder_info->get_file_infos().by_name(file_name);

    if (locked) {
        assert(!file->is_locked());
        file->lock();
    } else {
        assert(file->is_locked());
        file->unlock();
    }
    return applicator_t::apply_sibling(cluster, controller);
}

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


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

#pragma once

#include "utils/platform.h"
#include "../cluster_diff.h"
#include "model/file_info.h"

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

struct SYNCSPIRIT_API lock_file_t final : cluster_diff_t {

    lock_file_t(const model::file_info_t &file, bool locked) 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;
    utils::bytes_t device_id;
    std::string file_name;
    bool locked;
};

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


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

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

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

mark_reachable_t::mark_reachable_t(const model::file_info_t &file, bool reachable_) noexcept
    : folder_id{file.get_folder_info()->get_folder()->get_id()}, file_name{file.get_name()}, reachable{reachable_} {
    auto &peer = file.get_folder_info()->get_device()->device_id();
    device_id = peer.get_sha256();
    LOG_DEBUG(log, "mark_reachable_t, file = {}, folder = {}, device = {}, value = {}", file_name, folder_id, peer,
              reachable);
}

auto mark_reachable_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 device = cluster.get_devices().by_sha256(device_id);
    auto folder_info = folder->get_folder_infos().by_device(*device);
    auto file = folder_info->get_file_infos().by_name(file_name);

    LOG_TRACE(log, "applying reachable ({}) for '{}'", reachable, file->get_full_name());
    file->mark_unreachable(!reachable);

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

auto mark_reachable_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting mark_reachable_t");
    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/file_info.h"

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

struct SYNCSPIRIT_API mark_reachable_t final : cluster_diff_t {

    mark_reachable_t(const model::file_info_t &file, bool reachable) 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;
    utils::bytes_t device_id;
    std::string file_name;
    bool reachable;
};

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


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

#include "remove_blocks.h"

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

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

auto remove_blocks_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    if (!keys.empty()) {
        LOG_TRACE(log, "applying remove_blocks_t, blocks = {}", keys.size());
        auto &blocks = cluster.get_blocks();
        for (auto &block_key : keys) {
            auto block_hash = utils::bytes_view_t(block_key.data() + 1, block_key.size() - 1);
            auto b = blocks.by_hash(block_hash);
            blocks.remove(b);
        }
    }
    return applicator_t::apply_sibling(cluster, controller);
}

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


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

#pragma once

#include "generic_remove.h"

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

struct SYNCSPIRIT_API remove_blocks_t final : generic_remove_t {
    using generic_remove_t::generic_remove_t;
    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::modify


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

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

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

remove_files_t::remove_files_t(const device_t &device, const file_infos_map_t &files,
                               orphaned_blocks_t *orphaned_blocks_) noexcept {
    device_id = device.device_id().get_sha256();
    keys.reserve(files.size());
    folder_ids.reserve(files.size());
    auto local_orphaned_blocks = orphaned_blocks_t();
    auto &orphaned_blocks = orphaned_blocks_ ? *orphaned_blocks_ : local_orphaned_blocks;
    for (auto &it : files) {
        auto &file = it.item;
        orphaned_blocks.record(*file);
        auto folder_id = file->get_folder_info()->get_folder()->get_id();
        folder_ids.push_back(std::string(folder_id));
        auto file_key = file->get_key();
        keys.push_back(utils::bytes_t(file_key.begin(), file_key.end()));
    }
    if (!orphaned_blocks_) {
        auto block_keys = local_orphaned_blocks.deduce();
        if (block_keys.size()) {
            assign_child(new remove_blocks_t(std::move(block_keys)));
        }
    }
    LOG_DEBUG(log, "remove_files_t, device = {}, files count = {}", device.device_id(), files.size());
}

auto remove_files_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 &folders = cluster.get_folders();
    for (size_t i = 0; i < folder_ids.size(); ++i) {
        auto folder = folders.by_id(folder_ids[i]);
        auto &folder_infos = folder->get_folder_infos();
        auto folder_info = folder_infos.by_device_id(device_id);
        auto &file_infos = folder_info->get_file_infos();
        auto decomposed = file_info_t::decompose_key(keys[i]);
        auto file = file_infos.get(decomposed.file_id);
        file_infos.remove(file);
    }
    return applicator_t::apply_sibling(cluster, controller);
}

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


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

#pragma once

#include "model/device.h"
#include "model/file_info.h"
#include "model/misc/orphaned_blocks.h"
#include "generic_remove.h"

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

struct SYNCSPIRIT_API remove_files_t final : generic_remove_t {
    using generic_remove_t::generic_remove_t;

    remove_files_t(const device_t &device, const file_infos_map_t &files,
                   orphaned_blocks_t *orphaned_blocks = nullptr) 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 device_id;
    std::vector<std::string> folder_ids;
};

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


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

#include "remove_folder.h"
#include "unshare_folder.h"
#include "remove_blocks.h"
#include "model/cluster.h"
#include "add_pending_folders.h"
#include "model/misc/orphaned_blocks.h"
#include "model/diff/cluster_visitor.h"
#include "proto/proto-helpers-db.h"

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

remove_folder_t::remove_folder_t(const model::cluster_t &cluster, model::sequencer_t &sequencer,
                                 const model::folder_t &folder) noexcept
    : folder_id(folder.get_id()) {
    folder_key = folder.get_key();
    auto orphaned_blocks = orphaned_blocks_t();
    auto &folder_infos = folder.get_folder_infos();
    auto current = (cluster_diff_t *){nullptr};
    auto self = cluster.get_device();
    auto pending_folders = add_pending_folders_t::container_t();
    auto assign = [&](cluster_diff_t *next) {
        if (current) {
            current = current->assign_sibling(next);
        } else {
            current = assign_child(next);
        }
    };

    for (auto &it : folder_infos) {
        auto &fi = *it.item;
        auto d = fi.get_device();
        assign(new unshare_folder_t(cluster, *it.item, &orphaned_blocks));
        if (d != self && fi.get_index()) {
            auto db = db::PendingFolder();
            auto &db_fi = db::get_folder_info(db);
            auto &db_f = db::get_folder(db);
            db::set_index_id(db_fi, fi.get_index());
            db::set_max_sequence(db_fi, fi.get_max_sequence());
            db::set_id(db_f, folder.get_id());
            db::set_label(db_f, folder.get_label());
            db::set_ignore_permissions(db_f, folder.are_permissions_ignored());
            db::set_ignore_delete(db_f, folder.is_deletion_ignored());
            db::set_disable_temp_indexes(db_f, folder.are_temp_indixes_disabled());
            db::set_paused(db_f, folder.is_paused());
            db::set_scheduled(db_f, folder.is_scheduled());
            db::set_path(db_f, folder.get_path().string());
            db::set_folder_type(db_f, folder.get_folder_type());
            db::set_pull_order(db_f, folder.get_pull_order());

            auto sha256 = d->device_id().get_sha256();
            auto peer_id = utils::bytes_t{sha256.begin(), sha256.end()};
            auto item = add_pending_folders_t::item_t{std::move(db), std::move(peer_id), sequencer.next_uuid()};
            pending_folders.emplace_back(std::move(item));
        }
    }

    auto block_keys = orphaned_blocks.deduce();
    if (block_keys.size()) {
        assign(new remove_blocks_t(std::move(block_keys)));
    }
    if (pending_folders.size()) {
        assign(new add_pending_folders_t(std::move(pending_folders)));
    }

    LOG_DEBUG(log, "remove_folder_t, folder_id = {}", folder_id);
}

auto remove_folder_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    LOG_TRACE(log, "applying remove_folder_t (folder id = {})", folder_id);
    auto r = applicator_t::apply_child(cluster, controller);
    if (!r) {
        return r;
    }

    auto &folders = cluster.get_folders();
    auto folder = folders.by_id(folder_id);
    folder->mark_suspended(true); // aka deleted object marker
    folders.remove(folder);

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

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


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

#pragma once

#include "model/folder.h"
#include "model/diff/cluster_diff.h"
#include "model/misc/sequencer.h"

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

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

    remove_folder_t(const model::cluster_t &cluster, model::sequencer_t &sequencer,
                    const model::folder_t &folder) 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;
    utils::bytes_t folder_key;
};

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


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

#include "remove_folder_infos.h"
#include "reset_folder_infos.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"
#include "model/misc/file_iterator.h"
#include <algorithm>

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

remove_folder_infos_t::remove_folder_infos_t(const uuid_folder_infos_map_t &map, orphaned_blocks_t *orphaned_blocks_) {
    auto keys = unique_keys_t();
    for (auto &it : map) {
        auto &folder_info = *it.second;
        auto key = folder_info.get_key();
        keys.emplace(utils::bytes_t(key.data(), key.data() + key.size()));
    }
    std::move(keys.begin(), keys.end(), std::back_inserter(this->keys));
    assign_child(new reset_folder_infos_t(map, orphaned_blocks_));
    LOG_DEBUG(log, "remove_folder_infos_t, count = {}", map.size());
}

auto remove_folder_infos_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;
    }
    for (auto &key : keys) {
        auto decomposed = folder_info_t::decompose_key(key);
        auto folder = cluster.get_folders().by_key(decomposed.folder_key());
        auto &folder_infos = folder->get_folder_infos();
        auto device_key = decomposed.device_key();
        auto folder_info = folder_infos.by_device_key(device_key);
        if (auto iterator = folder_info->get_device()->get_iterator(); iterator) {
            if (folder_info->get_device() != cluster.get_device()) {
                iterator->on_remove(folder_info);
            }
        }

        folder_infos.remove(folder_info);
    }
    return applicator_t::apply_sibling(cluster, controller);
}

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


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

#pragma once

#include "generic_remove.h"
#include "model/folder_info.h"
#include "model/misc/orphaned_blocks.h"

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

struct SYNCSPIRIT_API remove_folder_infos_t final : generic_remove_t {
    remove_folder_infos_t(const uuid_folder_infos_map_t &map, orphaned_blocks_t *orphaned_blocks = nullptr);
    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::modify


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

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

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

remove_ignored_device_t::remove_ignored_device_t(const ignored_device_t &device) noexcept {
    device_key = device.get_key();
    LOG_DEBUG(log, "remove_ignored_device_t, device = {}", device.get_device_id());
}

auto remove_ignored_device_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &ignored_devices = cluster.get_ignored_devices();
    auto sha256 = get_device_sha256();
    auto ignored_device = ignored_devices.by_sha256(sha256);
    if (!ignored_device) {
        return make_error_code(error_code_t::no_such_device);
    }
    ignored_devices.remove(ignored_device);
    return applicator_t::apply_sibling(cluster, controller);
}

auto remove_ignored_device_t::get_device_sha256() const noexcept -> utils::bytes_view_t {
    return {device_key.begin() + 1, device_key.end()};
}

auto remove_ignored_device_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting remove_ignored_device_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/ignored_device.h"

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

struct SYNCSPIRIT_API remove_ignored_device_t final : cluster_diff_t {
    remove_ignored_device_t(const ignored_device_t &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;

    utils::bytes_view_t get_device_sha256() const noexcept;

    utils::bytes_t device_key;
};

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


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

#include "remove_peer.h"
#include "unshare_folder.h"
#include "remove_blocks.h"
#include "remove_pending_folders.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"
#include "model/misc/error_code.h"
#include "utils/format.hpp"

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

using blocks_t = remove_blocks_t::unique_keys_t;

remove_peer_t::remove_peer_t(const cluster_t &cluster, const device_t &peer) noexcept : parent_t() {
    auto key = peer.get_key();
    peer_key = utils::bytes_t(key.begin(), key.end());
    LOG_DEBUG(log, "remove_peer_t, device = {}", peer.device_id());
    orphaned_blocks_t orphaned_blocks;

    auto removed_pending_folders = remove_pending_folders_t::unique_keys_t{};
    for (auto &it : cluster.get_pending_folders()) {
        auto &uf = *it.item;
        if (uf.device_id() == peer.device_id()) {
            auto key = uf.get_key();
            removed_pending_folders.emplace(utils::bytes_t(key.begin(), key.end()));
        }
    }

    auto current = (cluster_diff_t *){nullptr};
    if (removed_pending_folders.size()) {
        auto diff = cluster_diff_ptr_t{};
        diff = new remove_pending_folders_t(std::move(removed_pending_folders));
        current = assign_child(diff);
    }

    auto &folders = cluster.get_folders();
    for (auto it : folders) {
        auto &f = it.item;
        if (auto fi = f->is_shared_with(peer); fi) {
            auto diff = cluster_diff_ptr_t{};
            diff = new unshare_folder_t(cluster, *fi, &orphaned_blocks);
            current = current ? current->assign_sibling(diff.get()) : assign_child(diff);
        }
    }

    auto removed_blocks = orphaned_blocks.deduce();
    if (removed_blocks.size()) {
        auto diff = cluster_diff_ptr_t{};
        diff = new remove_blocks_t(std::move(removed_blocks));
        current = current ? current->assign_sibling(diff.get()) : assign_child(diff);
    }
}

auto remove_peer_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 sha256 = get_peer_sha256();
    assert(sha256.size() == device_id_t::digest_length);
    auto peer = cluster.get_devices().by_sha256(sha256);
    if (!peer) {
        return make_error_code(error_code_t::no_such_device);
    }
    if (*peer == *cluster.get_device()) {
        return make_error_code(error_code_t::cannot_remove_self);
    }

    LOG_TRACE(log, "applying remove_peer_t (start), for device '{}' ({})", peer->device_id().get_short(),
              peer->get_name());
    cluster.get_devices().remove(peer);
    return applicator_t::apply_sibling(cluster, controller);
}

auto remove_peer_t::get_peer_sha256() const noexcept -> utils::bytes_view_t {
    return utils::bytes_view_t(peer_key.data() + 1, peer_key.size() - 1);
}

auto remove_peer_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting remove_peer_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.h"
#include "model/misc/orphaned_blocks.h"

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

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

    remove_peer_t(const cluster_t &cluster, const 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_view_t get_peer_sha256() const noexcept;

    utils::bytes_t peer_key;
};

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


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

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

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

remove_pending_device_t::remove_pending_device_t(const pending_device_t &device) noexcept {
    device_key = device.get_key();
    LOG_DEBUG(log, "remove_pending_device_t, device = {}", device.get_device_id());
}

auto remove_pending_device_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto &pending_devices = cluster.get_pending_devices();
    auto sha256 = get_device_sha256();
    auto pending_device = pending_devices.by_sha256(sha256);
    if (!pending_device) {
        return make_error_code(error_code_t::no_such_device);
    }
    pending_devices.remove(pending_device);
    return applicator_t::apply_sibling(cluster, controller);
}

auto remove_pending_device_t::get_device_sha256() const noexcept -> utils::bytes_view_t {
    auto sha256 = utils::bytes_view_t(device_key.begin() + 1, device_key.end());
    return sha256;
}

auto remove_pending_device_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting remove_pending_device_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"

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

struct SYNCSPIRIT_API remove_pending_device_t final : cluster_diff_t {
    remove_pending_device_t(const pending_device_t &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;

    utils::bytes_view_t get_device_sha256() const noexcept;

    utils::bytes_t device_key;
};

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


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

#include "remove_pending_folders.h"

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

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

auto remove_pending_folders_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    if (!keys.empty()) {
        LOG_TRACE(log, "applying remove_pending_folders_t, folders = {}", keys.size());
        auto &map = cluster.get_pending_folders();
        for (auto &key : keys) {
            auto folder = map.by_key(key);
            map.remove(folder);
        }
    }
    return applicator_t::apply_sibling(cluster, controller);
}

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


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

#pragma once

#include "generic_remove.h"

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

struct SYNCSPIRIT_API remove_pending_folders_t final : generic_remove_t {
    using generic_remove_t::generic_remove_t;
    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::modify


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

#include "reset_folder_infos.h"
#include "remove_files.h"
#include "remove_blocks.h"
#include "model/diff/cluster_visitor.h"

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

reset_folder_infos_t::reset_folder_infos_t(const uuid_folder_infos_map_t &map, orphaned_blocks_t *orphaned_blocks_) {
    LOG_DEBUG(log, "reset_folder_infos_t, count = {}", map.size());
    auto local_orphaned_blocks = orphaned_blocks_t();
    auto &orphaned_blocks = orphaned_blocks_ ? *orphaned_blocks_ : local_orphaned_blocks;

    auto current = (cluster_diff_t *){nullptr};
    for (auto &it : map) {
        auto &folder_info = *it.second;
        auto &files_info = folder_info.get_file_infos();
        if (files_info.size()) {
            auto diff = cluster_diff_ptr_t{};
            diff = new remove_files_t(*folder_info.get_device(), files_info, &orphaned_blocks);
            current = current ? current->assign_sibling(diff.get()) : assign_child(diff);
        }
    }
    if (!orphaned_blocks_) {
        auto block_keys = local_orphaned_blocks.deduce();
        if (block_keys.size()) {
            auto diff = cluster_diff_ptr_t{};
            diff = new remove_blocks_t(std::move(block_keys));
            current = current ? current->assign_sibling(diff.get()) : assign_child(diff);
        }
    }
}

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


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

#pragma once

#include "model/diff/cluster_diff.h"
#include "model/folder_info.h"
#include "model/misc/orphaned_blocks.h"

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

struct SYNCSPIRIT_API reset_folder_infos_t final : cluster_diff_t {
    reset_folder_infos_t(const uuid_folder_infos_map_t &map, orphaned_blocks_t *orphaned_blocks = nullptr);

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

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


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

#include "share_folder.h"
#include "model/cluster.h"
#include "model/misc/error_code.h"
#include "model/diff/cluster_visitor.h"
#include "add_remote_folder_infos.h"
#include "remove_pending_folders.h"
#include "upsert_folder_info.h"
#include "utils/format.hpp"

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

auto share_folder_t::create(cluster_t &cluster, sequencer_t &sequencer, const model::device_t &peer,
                            const device_id_t &introducer, const model::folder_t &folder) noexcept
    -> outcome::result<cluster_diff_ptr_t> {
    auto folder_info = folder.get_folder_infos().by_device(peer);
    if (folder_info) {
        return make_error_code(error_code_t::folder_is_already_shared);
    }
    auto &pending = cluster.get_pending_folders();
    auto index = uint64_t{0};
    auto pending_folder = model::pending_folder_ptr_t{};
    for (auto it = pending.begin(); it != pending.end(); ++it) {
        auto &uf = *it->item;
        if (uf.device_id() == peer.device_id() && uf.get_id() == folder.get_id()) {
            pending_folder = it->item;
            index = pending_folder->get_index();
            break;
        }
    }

    return new share_folder_t(sequencer.next_uuid(), peer, introducer, folder.get_id(), index, pending_folder);
}

share_folder_t::share_folder_t(const bu::uuid &uuid, const model::device_t &peer, const device_id_t &introducer,
                               std::string_view folder_id_, std::uint64_t index_id,
                               model::pending_folder_ptr_t pf) noexcept
    : folder_id{folder_id_} {
    peer_id = peer.device_id().get_sha256();
    LOG_DEBUG(log, "share_folder_t, with peer = {}, folder_id = {}", peer.device_id(), folder_id);
    auto current = assign_child(new upsert_folder_info_t(uuid, peer.device_id(), introducer, folder_id, index_id));
    if (pf) {
        using container_t = typename add_remote_folder_infos_t::container_t;
        using item_t = typename container_t::value_type;
        auto keys = remove_pending_folders_t::keys_t{};
        auto key = pf->get_key();
        auto bytes = utils::bytes_t(key.begin(), key.end());
        keys.emplace_back(std::move(bytes));
        auto diff = cluster_diff_ptr_t{};
        current = current->assign_sibling(new remove_pending_folders_t(std::move(keys)));

        auto remote_folders = container_t{{item_t{folder_id, index_id, 0}}};
        current = current->assign_sibling(new add_remote_folder_infos_t(peer, std::move(remote_folders)));
    }
}

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

auto share_folder_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting share_folder_t");
    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/misc/sequencer.h"
#include "model/device.h"
#include "model/folder.h"
#include "model/pending_folder.h"

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

struct SYNCSPIRIT_API share_folder_t final : cluster_diff_t {

    static outcome::result<cluster_diff_ptr_t> create(cluster_t &, sequencer_t &sequencer, const model::device_t &,
                                                      const model::device_id_t &introducer,
                                                      const model::folder_t &folder) 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;
    std::string folder_id;

  private:
    share_folder_t(const bu::uuid &uuid, const model::device_t &peer, const model::device_id_t &introducer,
                   std::string_view folder_id, std::uint64_t index_id, model::pending_folder_ptr_t pf) noexcept;
};

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


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

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

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

suspend_folder_t::suspend_folder_t(const model::folder_t &folder) noexcept : folder_id{folder.get_id()} {
    LOG_DEBUG(log, "suspend_folder_t, folder_id = {}", folder_id);
}

auto suspend_folder_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    LOG_TRACE(log, "applying suspend_folder_t");
    auto folder = cluster.get_folders().by_id(folder_id);
    folder->mark_suspended(true);
    return applicator_t::apply_impl(cluster, controller);
}

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


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

#pragma once

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

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

struct SYNCSPIRIT_API suspend_folder_t final : cluster_diff_t {

    suspend_folder_t(const model::folder_t &folder) 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;

  private:
};

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


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

#include "unshare_folder.h"
#include "remove_blocks.h"
#include "remove_folder_infos.h"
#include "model/diff/cluster_visitor.h"
#include "model/cluster.h"
#include "utils/format.hpp"

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

unshare_folder_t::unshare_folder_t(const model::cluster_t &, model::folder_info_t &folder_info,
                                   orphaned_blocks_t *orphaned_blocks_) noexcept {
    LOG_DEBUG(log, "unshare_folder_t folder = {}, peer = {}", folder_info.get_folder()->get_id(),
              folder_info.get_device()->device_id());

    auto &peer = *folder_info.get_device();
    peer_id = peer.device_id().get_sha256();
    auto local_orphaned_blocks = orphaned_blocks_t();
    auto &orphaned_blocks = orphaned_blocks_ ? *orphaned_blocks_ : local_orphaned_blocks;

    auto remove_folders_map = uuid_folder_infos_map_t();
    remove_folders_map.emplace(folder_info.get_uuid(), &folder_info);
    auto current = assign_child(new remove_folder_infos_t(std::move(remove_folders_map), &orphaned_blocks));

    if (!orphaned_blocks_) {
        auto block_keys = local_orphaned_blocks.deduce();
        if (block_keys.size()) {
            current = current->assign_sibling(new remove_blocks_t(std::move(block_keys)));
        }
    }
}

auto unshare_folder_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;
    }
    LOG_TRACE(log, "applying unshare_folder_t");
    return applicator_t::apply_sibling(cluster, controller);
}

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


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

#pragma once

#include "model/diff/cluster_diff.h"
#include "model/folder_info.h"
#include "model/misc/orphaned_blocks.h"

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

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

    unshare_folder_t(const model::cluster_t &cluster, model::folder_info_t &folder,
                     orphaned_blocks_t *orphaned_blocks = nullptr) 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::modify


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

#include "update_peer.h"
#include "remove_ignored_device.h"
#include "remove_pending_device.h"
#include "db/prefix.h"
#include "model/cluster.h"
#include "model/misc/error_code.h"
#include "model/diff/cluster_visitor.h"
#include "utils/format.hpp"

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

update_peer_t::update_peer_t(db::Device db, const model::device_id_t &device_id,
                             const model::cluster_t &cluster) noexcept
    : item{std::move(db)} {
    peer_id = device_id.get_sha256();
    LOG_DEBUG(log, "update_peer_t, peer = {}", device_id.get_short());
    auto &ignored_devices = cluster.get_ignored_devices();
    auto &pending_devices = cluster.get_pending_devices();
    auto current = (cluster_diff_t *){nullptr};
    if (auto pending_device = pending_devices.by_sha256(peer_id); pending_device) {
        auto diff = cluster_diff_ptr_t{};
        diff = new remove_pending_device_t(*pending_device);
        current = assign_child(diff);
    }
    if (auto ignored_device = ignored_devices.by_sha256(peer_id); ignored_device) {
        auto diff = cluster_diff_ptr_t{};
        diff = new remove_ignored_device_t(*ignored_device);
        current = current ? current->assign_sibling(diff.get()) : assign_child(diff);
    }
}

auto update_peer_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 &devices = cluster.get_devices();
    auto peer = devices.by_sha256(peer_id);
    if (!peer) {
        auto prefix = (char)db::prefix::device;
        auto device_id_opt = device_id_t::from_sha256(peer_id);
        if (!device_id_opt) {
            return make_error_code(error_code_t::malformed_deviceid);
        }
        auto key = utils::bytes_t(peer_id.size() + 1);
        key[0] = prefix;
        std::copy(peer_id.begin(), peer_id.end(), key.data() + 1);
        auto device_opt = device_t::create(key, item);
        if (!device_opt) {
            return device_opt.assume_error();
        }
        peer = device_opt.assume_value();
        devices.put(peer);
    } else {
        auto ec = peer->update(item);
        if (!ec) {
            auto &error = ec.error();
            LOG_ERROR(log, "applying update_peer_t, device {} fail: {}", peer->device_id(), error.message());
            return error;
        }
        peer->notify_update();
    }
    LOG_TRACE(log, "applying update_peer_t, device {}", peer->device_id());
    return applicator_t::apply_sibling(cluster, controller);
}

auto update_peer_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting update_peer_t");
    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 "proto/proto-helpers.h"
#include "model/device_id.h"

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

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

    update_peer_t(db::Device db, const model::device_id_t &device_id, const model::cluster_t &) 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;

    db::Device item;
    utils::bytes_t peer_id;
};

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


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

#include "upsert_folder.h"
#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"
#include "model/misc/file_iterator.h"
#include "upsert_folder_info.h"
#include "proto/proto-helpers-db.h"
#include "utils/format.hpp"

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

auto upsert_folder_t::create(const cluster_t &cluster, sequencer_t &sequencer, db::Folder db,
                             std::uint64_t index_id) noexcept -> outcome::result<cluster_diff_ptr_t> {
    auto &folders = cluster.get_folders();
    auto &device = *cluster.get_device();
    auto prev_folder = folders.by_id(db::get_id(db));
    auto folder_info = prev_folder ? prev_folder->get_folder_infos().by_device(device) : nullptr;
    auto uuid = bu::uuid{};
    if (prev_folder) {
        assign(uuid, prev_folder->get_uuid());
    } else {
        uuid = sequencer.next_uuid();
    }

    if (!index_id) {
        index_id = sequencer.next_uint64();
    }

    auto diff = cluster_diff_ptr_t{};
    diff = new upsert_folder_t(sequencer, uuid, std::move(db), folder_info, device.device_id(), index_id);
    return outcome::success(diff);
}

upsert_folder_t::upsert_folder_t(sequencer_t &sequencer, bu::uuid uuid_, db::Folder db_,
                                 model::folder_info_ptr_t folder_info, const device_id_t &device,
                                 std::uint64_t index_id) noexcept
    : uuid{uuid_}, db{std::move(db_)} {
    auto folder_id = db::get_id(db);
    LOG_DEBUG(log, "upsert_folder_t, folder_id = {}, device = {}", folder_id, device);

    if (!folder_info) {
        auto fi_uuid = sequencer.next_uuid();
        auto diff = cluster_diff_ptr_t{};
        diff = new upsert_folder_info_t(fi_uuid, device, device, folder_id, index_id);
        assign_child(diff);
    }
}

auto upsert_folder_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto folder_id = db::get_id(db);
    LOG_TRACE(log, "applying upsert_folder_t, folder_id: {}", folder_id);
    auto folder_opt = folder_t::create(uuid, db);
    if (!folder_opt) {
        return folder_opt.assume_error();
    }
    auto &folder = folder_opt.value();

    auto &folders = cluster.get_folders();
    auto prev_folder = folders.by_id(folder_id);
    if (!prev_folder) {
        folder->assign_cluster(&cluster);
        folders.put(folder);
    } else {
        prev_folder->assign_fields(db);
        if (auto aug = prev_folder->get_augmentation(); aug) {
            aug->on_update();
        }
    }

    auto r = applicator_t::apply_child(cluster, controller);
    if (!r) {
        return r;
    }

    if (prev_folder) {
        for (auto it : cluster.get_devices()) {
            auto &device = *it.item;
            if (&device != cluster.get_device().get()) {
                if (prev_folder->is_shared_with(device)) {
                    if (auto it = device.get_iterator(); it) {
                        it->on_upsert(*prev_folder);
                    }
                }
            }
        }
    }

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

auto upsert_folder_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting upsert_folder_t");
    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/device.h"
#include "model/misc/sequencer.h"
#include "proto/proto-fwd.hpp"

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

struct SYNCSPIRIT_API upsert_folder_t final : cluster_diff_t {

    static outcome::result<cluster_diff_ptr_t> create(const cluster_t &cluster, sequencer_t &sequencer, db::Folder db,
                                                      std::uint64_t index_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;

    bu::uuid uuid;
    db::Folder db;

  private:
    upsert_folder_t(sequencer_t &sequencer, bu::uuid uuid, db::Folder db, model::folder_info_ptr_t folder_info,
                    const model::device_id_t &device, std::uint64_t index_id) noexcept;
};

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


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

#include "upsert_folder_info.h"

#include "model/cluster.h"
#include "model/diff/cluster_visitor.h"
#include "model/misc/error_code.h"
#include "model/misc/file_iterator.h"
#include "utils/format.hpp"
#include "proto/proto-helpers.h"

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

upsert_folder_info_t::upsert_folder_info_t(const bu::uuid &uuid_, const model::device_id_t &device_id_,
                                           const model::device_id_t &introducer_device_id, std::string_view folder_id_,
                                           std::uint64_t index_id_) noexcept
    : uuid{uuid_}, device_id(device_id_.get_sha256()), introducer_device_key(introducer_device_id.get_key()),
      folder_id{folder_id_}, index_id{index_id_} {
    LOG_DEBUG(log, "upsert_folder_info_t, folder = {}, index = {}", folder_id, index_id);
}

upsert_folder_info_t::upsert_folder_info_t(const model::folder_info_t &original, std::uint64_t new_index_id) noexcept
    : device_id(original.get_device()->device_id().get_sha256()),
      introducer_device_key(original.get_introducer_device_key()), folder_id{original.get_folder()->get_id()},
      index_id{new_index_id} {
    assign(uuid, original.get_uuid());
    LOG_DEBUG(log, "upsert_folder_info_t, folder = {}, index = {}", folder_id, index_id);
}

auto upsert_folder_info_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    auto device = cluster.get_devices().by_sha256(device_id);
    if (!device) {
        return make_error_code(error_code_t::no_such_device);
    }

    auto folder = cluster.get_folders().by_id(folder_id);
    if (!folder) {
        return make_error_code(error_code_t::no_such_folder);
    }
    auto &folder_infos = folder->get_folder_infos();
    auto fi = folder_infos.by_device(*device);
    if (fi) {
        fi->set_index(index_id);
        LOG_TRACE(log, "applying upsert_folder_info_t (update), folder = {} ({}), device = {}, index = {:x}",
                  folder->get_label(), folder_id, device->device_id(), index_id);
        fi->notify_update();
    } else {
        LOG_TRACE(log, "applying upsert_folder_info_t (create), folder = {} ({}), device = {}, index = {:x}",
                  folder->get_label(), folder_id, device->device_id(), index_id);
        db::FolderInfo db;
        db::set_index_id(db, index_id);
        db::set_introducer_device_key(db, introducer_device_key);

        auto opt = folder_info_t::create(uuid, db, device, folder);
        if (!opt) {
            return opt.assume_error();
        }
        fi = std::move(opt.value());
        folder_infos.put(fi);
    }

    if (auto iterator = fi->get_device()->get_iterator(); iterator) {
        iterator->on_upsert(fi);
    }
    folder->notify_update();

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

auto upsert_folder_info_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result<void> {
    LOG_TRACE(log, "visiting upsert_folder_info_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/misc/uuid.h"
#include "model/device_id.h"
#include "model/folder_info.h"
#include "utils/bytes.h"

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

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

    upsert_folder_info_t(const bu::uuid &uuid, const model::device_id_t &device_id,
                         const model::device_id_t &introducer_device_id, std::string_view folder_id,
                         std::uint64_t index_id) noexcept;
    upsert_folder_info_t(const model::folder_info_t &original, std::uint64_t new_index_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;

    bu::uuid uuid;
    utils::bytes_t device_id;
    utils::bytes_t introducer_device_key;
    std::string folder_id;
    std::uint64_t index_id;
};

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


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

#include "cluster_update.h"
#include "model/diff/modify/add_remote_folder_infos.h"
#include "model/diff/modify/add_pending_folders.h"
#include "model/diff/modify/remove_blocks.h"
#include "model/diff/modify/remove_folder_infos.h"
#include "model/diff/modify/remove_peer.h"
#include "model/diff/modify/remove_pending_folders.h"
#include "model/diff/modify/reset_folder_infos.h"
#include "model/diff/modify/update_peer.h"
#include "model/diff/modify/upsert_folder.h"
#include "model/diff/modify/upsert_folder_info.h"
#include "model/diff/cluster_visitor.h"
#include "model/cluster.h"
#include "model/misc/orphaned_blocks.h"
#include "proto/proto-helpers-bep.h"
#include "proto/proto-helpers-db.h"
#include "utils/error_code.h"
#include "utils/format.hpp"
#include "utils/uri.h"
#include <spdlog/fmt/bin_to_hex.h>
#include <spdlog/spdlog.h>
#include <boost/nowide/convert.hpp>

using namespace syncspirit;
using namespace syncspirit::model::diff::peer;

using keys_t = syncspirit::model::diff::modify::generic_remove_t::unique_keys_t;
using keys_view_t = std::set<utils::bytes_view_t, utils::bytes_comparator_t>;

auto cluster_update_t::create(const bfs::path &default_path, const cluster_t &cluster, sequencer_t &sequencer,
                              const device_t &source, const message_t &message) noexcept
    -> outcome::result<cluster_diff_ptr_t> {
    auto diff = cluster_diff_ptr_t();
    auto ptr = new cluster_update_t(default_path, cluster, sequencer, source, message);
    diff.reset(ptr);
    if (ptr->ec) {
        return ptr->ec;
    }
    return diff;
};

cluster_update_t::cluster_update_t(const bfs::path &default_path, const cluster_t &cluster, sequencer_t &sequencer,
                                   const device_t &source, const message_t &message) noexcept {
    struct introduced_device_t {
        db::Device device;
        model::device_id_t device_id;
    };
    using introduced_devices_t = std::vector<introduced_device_t>;
    struct upserted_folder_info_t {
        std::string_view folder_id;
        std::uint64_t new_index_id;
        model::device_id_t device_id;
    };
    using upserted_folder_infos_t = std::vector<upserted_folder_info_t>;
    using upserted_folders_t = std::vector<db::Folder>;

    auto sha256 = source.device_id().get_sha256();
    peer_id = sha256;
    LOG_DEBUG(log, "cluster_update_t, source = {}", source.device_id().get_short());

    auto &known_pending_folders = cluster.get_pending_folders();
    auto new_pending_folders = diff::modify::add_pending_folders_t::container_t{};
    auto remote_folders = diff::modify::add_remote_folder_infos_t::container_t{};
    uuid_folder_infos_map_t reset_folders;
    uuid_folder_infos_map_t removed_folders;
    folder_infos_map_t reshared_folders;
    keys_t removed_pending_folders;
    keys_t confirmed_pending_folders;
    keys_view_t confirmed_folders;
    keys_view_t processed_introduced_devices;
    keys_view_t seen_devices;
    auto &folders = cluster.get_folders();
    auto &devices = cluster.get_devices();
    auto orphaned_blocks = orphaned_blocks_t{};
    auto introduced_devices = introduced_devices_t();
    auto upserted_folder_infos = upserted_folder_infos_t();
    auto upserted_folders = upserted_folders_t();

    auto add_pending = [&](const proto::Folder &f, const proto::Device &d) noexcept {
        auto folder_id = proto::get_id(f);
        for (auto &it : known_pending_folders) {
            auto &uf = it.item;
            auto match = uf->device_id().get_sha256() == sha256 && uf->get_id() == folder_id;
            if (match) {
                auto index_id = proto::get_index_id(d);
                auto key = uf->get_key();
                auto uf_key = utils::bytes_t(key.begin(), key.end());
                confirmed_pending_folders.emplace(uf_key);
                auto index_match = uf->get_index() == index_id;
                auto sequence_match = uf->get_max_sequence() == proto::get_max_sequence(d);
                bool actual = index_match && sequence_match;
                if (!actual) {
                    removed_pending_folders.emplace(std::move(uf_key));
                } else {
                    return;
                }
            }
        }

        using item_t = decltype(new_pending_folders)::value_type;
        auto label = proto::get_label(f);
        auto device_id = proto::get_id(d);
        auto device_short = device_id_t::make_short(device_id);
        LOG_DEBUG(log, "cluster_update_t, adding pending folder {}({}) non-shared with '{}'", label, folder_id,
                  device_short);

        auto ro = proto::get_read_only(f);
        db::PendingFolder db;
        auto &db_fi = db::get_folder_info(db);
        auto &db_f = db::get_folder(db);
        db::set_index_id(db_fi, proto::get_index_id(d));
        db::set_max_sequence(db_fi, proto::get_max_sequence(d));
        db::set_id(db_f, proto::get_id(f));
        db::set_label(db_f, label);
        db::set_ignore_permissions(db_f, proto::get_ignore_permissions(f));
        db::set_ignore_delete(db_f, proto::get_ignore_delete(f));
        db::set_disable_temp_indexes(db_f, proto::get_disable_temp_indexes(f));
        db::set_paused(db_f, proto::get_paused(f));
        db::set_folder_type(db_f, db::FolderType::send_and_receive);

        auto id = utils::bytes_t(sha256.begin(), sha256.end());
        new_pending_folders.push_back(item_t{std::move(db), std::move(id), sequencer.next_uuid()});
    };

    auto upsert_folder_info = [&](const model::folder_info_t &fi, std::uint64_t new_index_id) noexcept {
        auto folder_id = fi.get_folder()->get_id();
        auto &device_id = fi.get_device()->device_id();
        upserted_folder_infos.emplace_back(upserted_folder_info_t(folder_id, new_index_id, device_id));
    };

    auto add_folder_info = [&](std::string_view folder_id, const proto::Device &device, const device_id_t &device_id) {
        LOG_DEBUG(log, "cluster_update_t, going to share folder '{}' with introduced device '{}'", folder_id,
                  device_id);
        auto index_id = proto::get_index_id(device);
        upserted_folder_infos.emplace_back(upserted_folder_info_t(folder_id, index_id, device_id));
    };

    auto add_folder = [&](const proto::Folder &folder) -> bool {
        auto folder_id = proto::get_id(folder);
        auto label = proto::get_label(folder);
        auto try_make_path = [&](std::string_view label) -> outcome::result<bfs::path> {
            auto path = default_path / boost::nowide::widen(label);
            if (label.empty()) {
                return model::make_error_code(model::error_code_t::empty_folder_name);
            }
            auto ec = sys::error_code();
            bfs::create_directories(path, ec);
            LOG_TRACE(log, "cluster_update_t, trying to make a dir: '{}' for folder '{}', result: {}", path.string(),
                      folder_id, ec.message());
            if (ec) {
                return ec;
            }
            return path;
        };

        auto r = try_make_path(label);
        if (!r) {
            r = try_make_path(folder_id);
        }
        if (!r) {
            this->ec = r.assume_error();
            return false;
        }

        LOG_DEBUG(log, "cluster_update_t, going to create folder '{}' ('{}')", folder_id, label);
        auto db = db::Folder();
        db::set_id(db, folder_id);
        db::set_label(db, label);
        db::set_ignore_permissions(db, proto::get_ignore_permissions(folder));
        db::set_ignore_delete(db, proto::get_ignore_delete(folder));
        db::set_disable_temp_indexes(db, proto::get_disable_temp_indexes(folder));
        db::set_paused(db, proto::get_paused(folder));
        db::set_path(db, boost::nowide::narrow(r.value().wstring()));
        db::set_rescan_interval(db, 3600);
        db::set_folder_type(db, db::FolderType::send_and_receive);
        upserted_folders.emplace_back(std::move(db));
        return true;
    };

    auto introduce_device = [&](std::string_view folder_id, const proto::Device &device,
                                const device_id_t &device_id) noexcept -> bool {
        auto device_name = proto::get_name(device);
        auto sha256 = proto::get_id(device);
        if (!processed_introduced_devices.count(sha256)) {
            processed_introduced_devices.emplace(sha256);
            seen_devices.emplace(sha256);
            auto db_peer = db::Device();
            auto addresses_count = proto::get_addresses_size(device);
            for (size_t i = 0; i < addresses_count; ++i) {
                auto address = proto::get_addresses(device, i);
                if (address != "dynamic") {
                    if (utils::is_parsable(address)) {
                        db::add_addresses(db_peer, address);
                    } else {
                        ec = make_error_code(utils::error_code_t::malformed_url);
                        return false;
                    }
                }
            }
            db::set_name(db_peer, device_name);
            db::set_compression(db_peer, proto::get_compression(device));
            db::set_introducer(db_peer, proto::get_introducer(device));
            db::set_skip_introduction_removals(db_peer, proto::get_skip_introduction_removals(device));
            introduced_devices.emplace_back(introduced_device_t{std::move(db_peer), device_id});
        }

        add_folder_info(folder_id, device, device_id);
        return true;
    };

    auto is_shared_with_source = [&](std::string_view folder_id) {
        auto folder = cluster.get_folders().by_id(folder_id);
        if (folder) {
            if (folder->get_folder_infos().by_device(source)) {
                return true;
            }
        }
        for (auto &db_f : upserted_folders) {
            if (db::get_id(db_f) == folder_id) {
                return true;
            }
        }
        return false;
    };

    auto folders_count = proto::get_folders_size(message);

    // 1st pass, for source device & self only
    for (size_t i = 0; i < folders_count; ++i) {
        auto &f = proto::get_folders(message, i);
        auto folder_id = proto::get_id(f);
        auto folder_label = proto::get_label(f);
        auto folder = folders.by_id(folder_id);
        LOG_DEBUG(log, "cluster_update_t, folder label = '{}', id = '{}'", folder_label, folder_id);
        auto devices_count = proto::get_devices_size(f);
        for (int j = 0; j < devices_count; ++j) {
            auto &d = proto::get_devices(f, j);
            auto device_sha = proto::get_id(d);
            auto device = devices.by_sha256(device_sha);
            auto device_opt = model::device_id_t::from_sha256(device_sha);
            if (!device_opt) {
                auto device_hex = spdlog::to_hex(device_sha.begin(), device_sha.end());
                LOG_WARN(log, "cluster_update_t, malformed device id: {}", device_hex);
                ec = make_error_code(utils::error_code_t::malformed_url);
                return;
            }
            auto index_id = proto::get_index_id(d);
            auto &device_id = *device_opt;
            auto max_sequence = proto::get_max_sequence(d);
            LOG_DEBUG(log, "cluster_update_t, shared with device = '{}', index = {:#x}, max seq. = {}", device_id,
                      index_id, max_sequence);

            if (!device) {
                continue;
            }
            seen_devices.emplace(device_sha);

            auto folder_info = model::folder_info_ptr_t();
            if (folder && device) {
                folder_info = folder->get_folder_infos().by_device_id(device_sha);
            }

            if (device.get() == cluster.get_device()) {
                if (is_shared_with_source(folder_id)) {
                    remote_folders.emplace_back(std::string(folder_id), index_id, max_sequence);
                    LOG_DEBUG(log, "cluster_update_t, remote folder = {}, device = {}, max seq. = {}", folder_label,
                              device_id.get_short(), max_sequence);
                } else {
                    LOG_DEBUG(log, "cluster_update_t, our side is no longer shares folder '{}' with {}", folder_id,
                              source.device_id());
                }
                continue;
            }

            if (device_sha != sha256) {
                continue;
            }

            if (!folder_info) {
                bool share = false;
                if (device_sha == source.device_id().get_sha256()) {
                    if (source.has_auto_accept()) {
                        if (!folder) {
                            if (!add_folder(f)) {
                                return;
                            }
                            share = true;
                        } else {
                            share = true;
                        }
                    } else {
                        add_pending(f, d);
                    }
                } else if (source.is_introducer()) {
                    share = true;
                }
                if (share) {
                    add_folder_info(folder_id, d, device_id);
                }
                continue;
            } else {
                bool do_update = false;
                if (index_id != folder_info->get_index()) {
                    do_update = true;
                    LOG_DEBUG(log, "cluster_update_t, reseting folder: {}, new index = {:#x}, max_seq = {}",
                              folder_label, index_id, max_sequence);
                    reset_folders.emplace(folder_info->get_uuid(), folder_info.get());
                } else if (max_sequence > folder_info->get_max_sequence()) {
                    LOG_DEBUG(log, "cluster_update_t, updating folder = {}, index = {:#x}, max seq = {} -> {}",
                              folder->get_label(), folder_info->get_index(), folder_info->get_max_sequence(),
                              max_sequence);
                }
                if (do_update) {
                    upsert_folder_info(*folder_info, index_id);
                }
                confirmed_folders.emplace(folder_info->get_key());
            }
        }
    }

    // 2st pass, for all other devices
    for (size_t i = 0; i < folders_count; ++i) {
        auto &f = proto::get_folders(message, i);
        auto folder_id = proto::get_id(f);
        auto folder_label = proto::get_label(f);
        auto folder = folders.by_id(folder_id);
        auto devices_count = proto::get_devices_size(f);
        for (int j = 0; j < devices_count; ++j) {
            auto &d = proto::get_devices(f, j);
            auto device_sha = proto::get_id(d);
            if ((device_sha == sha256) || device_sha == cluster.get_device()->device_id().get_sha256()) {
                continue;
            }
            auto device = devices.by_sha256(device_sha);
            auto device_opt = model::device_id_t::from_sha256(device_sha);
            auto index_id = proto::get_index_id(d);
            auto &device_id = *device_opt;
            auto max_sequence = proto::get_max_sequence(d);
            LOG_DEBUG(log, "cluster_update_t, shared with device = '{}', index = {:#x}, max seq. = {}", device_id,
                      index_id, max_sequence);

            if (!device) {
                if (source.is_introducer()) {
                    if (is_shared_with_source(folder_id)) {
                        if (!introduce_device(folder_id, d, device_id)) {
                            return;
                        }
                    }
                }
                continue;
            }
            seen_devices.emplace(device_sha);

            auto folder_info = model::folder_info_ptr_t();
            if (folder && device) {
                folder_info = folder->get_folder_infos().by_device_id(device_sha);
            }

            if (!folder_info && source.is_introducer()) {
                add_folder_info(folder_id, d, device_id);
            } else {
                confirmed_folders.emplace(folder_info->get_key());
            }
        }
    }

    for (auto &it : known_pending_folders) {
        auto &uf = it.item;
        if (uf->device_id().get_sha256() != sha256) {
            continue;
        }
        if (confirmed_pending_folders.contains(uf->get_key())) {
            continue;
        }
        if (uf->device_id().get_sha256() == sha256) {
            auto key = uf->get_key();
            auto uf_key = utils::bytes_t(key.begin(), key.end());
            removed_pending_folders.emplace(std::move(uf_key));
        }
    }

    for (auto it_f : folders) {
        auto &folder = it_f.item;
        auto folder_info = folder->get_folder_infos().by_device(source);
        if (folder_info) {
            if (!confirmed_folders.contains(folder_info->get_key())) {
                removed_folders.emplace(folder_info->get_uuid(), folder_info.get());
                reshared_folders.put(folder_info);
            }
        }
    }

    keys_view_t removed_introduced_devices;
    if (!source.get_skip_introduction_removals()) {
        for (auto fit : cluster.get_folders()) {
            auto &folder_infos = fit.item->get_folder_infos();
            for (auto it : folder_infos) {
                auto &fi = *it.item;
                auto &i_device = fi.get_device()->device_id();
                if (fi.is_introduced_by(source.device_id())) {
                    auto remove_fi = !confirmed_folders.count(fi.get_key()) && !removed_folders.count(fi.get_uuid());
                    if (remove_fi) {
                        auto sha256 = i_device.get_sha256();
                        if (!seen_devices.count(sha256)) {
                            removed_introduced_devices.emplace(sha256);
                        } else {
                            removed_folders.emplace(fi.get_uuid(), &fi);
                            LOG_DEBUG(log, "unsharing folder '{}' with introduced device '{}'",
                                      fi.get_folder()->get_id(), i_device);
                        }
                    }
                }
            }
        }
    }

    auto current = (cluster_diff_t *){nullptr};
    auto update_current = [&](cluster_diff_t *diff) {
        current = current ? current->assign_sibling(diff) : assign_child(diff);
    };

    for (auto &db : upserted_folders) {
        auto opt = modify::upsert_folder_t::create(cluster, sequencer, db, 0);
        if (!opt) {
            ec = opt.assume_error();
            return;
        }
        update_current(opt.assume_value().get());
    }

    if (reset_folders.size()) {
        auto ptr = new modify::reset_folder_infos_t(std::move(reset_folders), &orphaned_blocks);
        update_current(ptr);
    }
    if (!removed_introduced_devices.empty()) {
        for (auto sha256 : removed_introduced_devices) {
            auto peer = devices.by_sha256(sha256);
            update_current(new modify::remove_peer_t(cluster, *peer));
            LOG_DEBUG(log, "removing introduced device '{}'", peer->device_id());
        }
    }
    if (removed_folders.size()) {
        auto ptr = new modify::remove_folder_infos_t(std::move(removed_folders), &orphaned_blocks);
        update_current(ptr);
    }
    auto removed_blocks = orphaned_blocks.deduce();
    if (!removed_blocks.empty()) {
        auto diff = cluster_diff_ptr_t{};
        diff = new modify::remove_blocks_t(std::move(removed_blocks));
        current = current ? current->assign_sibling(diff.get()) : assign_child(diff);
    }
    if (!removed_pending_folders.empty()) {
        auto ptr = new modify::remove_pending_folders_t(std::move(removed_pending_folders));
        update_current(ptr);
    }
    if (reshared_folders.size()) {
        for (auto &it : reshared_folders) {
            auto &f = *it.item;
            update_current(new modify::upsert_folder_info_t(f, 0));
        }
    }
    if (!new_pending_folders.empty()) {
        auto ptr = new modify::add_pending_folders_t(std::move(new_pending_folders));
        update_current(ptr);
    }
    if (!remote_folders.empty()) {
        auto ptr = new modify::add_remote_folder_infos_t(source, std::move(remote_folders));
        update_current(ptr);
    }
    for (auto &id : introduced_devices) {
        auto ptr = new diff::modify::update_peer_t(std::move(id.device), id.device_id, cluster);
        update_current(ptr);
    }
    for (auto &info : upserted_folder_infos) {
        auto ptr = new diff::modify::upsert_folder_info_t(sequencer.next_uuid(), info.device_id, source.device_id(),
                                                          info.folder_id, info.new_index_id);
        update_current(ptr);
    }
}

auto cluster_update_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept
    -> outcome::result<void> {
    LOG_TRACE(log, "applying cluster_update_t (self)");
    auto peer = cluster.get_devices().by_sha256(peer_id);
    peer->get_remote_folder_infos().clear();

    LOG_TRACE(log, "applying cluster_update_t (children)");
    auto r = applicator_t::apply_child(cluster, controller);
    if (!r) {
        return r;
    }

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

auto cluster_update_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 "proto/bep_support.h"
#include "model/device.h"
#include "model/misc/sequencer.h"
#include "model/diff/cluster_diff.h"
#include "config/main.h"

namespace syncspirit::model::diff::peer {

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

struct SYNCSPIRIT_API cluster_update_t final : cluster_diff_t {
    using message_t = proto::ClusterConfig;
    using parent_t = cluster_diff_t;

    static outcome::result<cluster_diff_ptr_t> create(const bfs::path &default_path, const cluster_t &cluster,
                                                      sequencer_t &sequencer, const model::device_t &source,
                                                      const message_t &message) 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;

    cluster_update_t(const bfs::path &default_path, const cluster_t &cluster, sequencer_t &sequencer,
                     const model::device_t &source, const message_t &message) noexcept;

    sys::error_code ec;
    utils::bytes_t peer_id;
};

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


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

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

using namespace syncspirit::model::diff::peer;

rx_tx_t::rx_tx_t(utils::bytes_view_t sha256, std::size_t rx_size_, std::size_t tx_size_) noexcept
    : peer_id{sha256.begin(), sha256.end()}, rx_size{rx_size_}, tx_size{tx_size_} {}

auto rx_tx_t::apply_impl(cluster_t &cluster, apply_controller_t &controller) const noexcept -> outcome::result<void> {
    auto peer = cluster.get_devices().by_sha256(peer_id);
    if (peer) {
        peer->set_rx_bytes(peer->get_rx_bytes() + rx_size);
        peer->set_tx_bytes(peer->get_tx_bytes() + tx_size);
        peer->notify_update();
    }

    auto self = cluster.get_device();
    self->set_rx_bytes(self->get_rx_bytes() + rx_size);
    self->set_tx_bytes(self->get_tx_bytes() + tx_size);
    self->notify_update();

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

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


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

#pragma once

#include "model/device.h"
#include "model/diff/cluster_diff.h"
#include <cstddef>

namespace syncspirit::model::diff::peer {

struct SYNCSPIRIT_API rx_tx_t final : cluster_diff_t {
    rx_tx_t(utils::bytes_view_t sha256, std::size_t rx_size, std::size_t tx_size) 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;
    std::size_t rx_size;
    std::size_t tx_size;
};

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