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

#include "folders.h"
#include "presence_item/folder.h"
#include "../content/folder_table.h"
#include "../table_widget/label.h"
#include "utils/base32.h"
#include "presentation/folder_presence.h"
#include <algorithm>
#include <cctype>
#include <boost/nowide/convert.hpp>
#include <FL/Fl_Button.H>

using namespace syncspirit;
using namespace syncspirit::model::diff;
using namespace syncspirit::fltk;
using namespace syncspirit::fltk::tree_item;
using namespace syncspirit::fltk::presence_item;

static constexpr int padding = 2;

namespace {

using folder_table_t = content::folder_table_t;

static auto make_actions(folder_table_t &container) -> widgetable_ptr_t {
    struct widget_t final : widgetable_t {
        using parent_t = widgetable_t;
        using parent_t::parent_t;

        Fl_Widget *create_widget(int x, int y, int w, int h) override {
            auto group = new Fl_Group(x, y, w, h);
            group->begin();
            group->box(FL_FLAT_BOX);
            auto &container = static_cast<folder_table_t &>(this->container);

            auto yy = y + padding, ww = 100, hh = h - padding * 2;

            auto apply = new Fl_Button(x + padding, yy, ww, hh, "create");
            apply->deactivate();
            apply->callback([](auto, void *data) { static_cast<folder_table_t *>(data)->on_apply(); }, &container);
            container.apply_button = apply;
            int xx = apply->x() + ww + padding * 2;

            auto reset = new Fl_Button(xx, yy, ww, hh, "reset");
            reset->deactivate();
            reset->callback([](auto, void *data) { static_cast<folder_table_t *>(data)->on_reset(); }, &container);
            container.reset_button = reset;
            xx = reset->x() + ww + padding * 2;

            group->resizable(nullptr);
            group->end();
            widget = group;

            this->reset();
            return widget;
        }
    };

    return new widget_t(container);
}

struct table_t : content::folder_table_t {
    using parent_t = content::folder_table_t;

    table_t(tree_item_t &container_, model::folder_info_ptr_t fi_, model::folder_ptr_t folder_, int x, int y, int w,
            int h)
        : parent_t(container_, *fi_, x, y, w, h), fi{fi_}, folder{folder_} {

        scan_start_cell = new static_string_provider_t();
        scan_finish_cell = new static_string_provider_t();

        auto data = table_rows_t();
        data.push_back({"", make_title(*this, "creating new folder")});
        data.push_back({"path", make_path(*this, false)});
        data.push_back({"id", make_id(*this, false)});
        data.push_back({"label", make_label(*this, false)});
        data.push_back({"type", make_folder_type(*this, false)});
        data.push_back({"pull order", make_pull_order(*this, false)});
        data.push_back({"index", make_index(*this, false)});
        data.push_back({"rescan interval", make_rescan_interval(*this, false)});
        data.push_back({"ignore permissions", make_ignore_permissions(*this, false)});
        data.push_back({"ignore delete", make_ignore_delete(*this, false)});
        data.push_back({"disable temp indixes", make_disable_tmp(*this, false)});
        data.push_back({"scheduled", make_scheduled(*this, false)});
        data.push_back({"paused", make_paused(*this, false)});
        data.push_back({"shared_with", make_shared_with(*this, {}, false)});
        data.push_back({"", notice = make_notice(*this)});
        data.push_back({"actions", make_actions(*this)});

        initially_shared_with = *shared_with;
        initially_non_shared_with = *non_shared_with;

        assign_rows(std::move(data));

        refresh();
    }

    void refresh() override {
        serialization_context_t ctx;
        description.get_folder()->serialize(ctx.folder);

        auto copy_data = db::encode(ctx.folder);
        error = {};
        auto valid = store(&ctx);

        // clang-format off
        auto is_same = (copy_data == db::encode(ctx.folder))
                    && (initially_shared_with == ctx.shared_with);
        // clang-format on
        if (!is_same) {
            if (valid) {
                apply_button->activate();
            }
            reset_button->activate();
        } else {
            apply_button->deactivate();
            reset_button->deactivate();
        }

        if (valid) {
            auto db_path = db::get_path(ctx.folder);
            if (db_path.empty()) {
                error = "path should be defined";
            } else {
                auto path = bfs::path(boost::nowide::widen(db_path));
                auto ec = sys::error_code{};
                if (bfs::exists(path, ec)) {
                    if (!bfs::is_empty(path, ec)) {
                        error = "referred directory should be empty";
                    }
                }
            }
        }

        if (valid && error.empty()) {
            apply_button->activate();
        } else {
            apply_button->deactivate();
        }

        notice->reset();
        redraw();
    }

    model::folder_info_ptr_t fi;
    model::folder_ptr_t folder;
};

} // namespace

folders_t::folders_t(app_supervisor_t &supervisor, Fl_Tree *tree) : parent_t(supervisor, tree, false) {
    supervisor.set_folders(this);
    update_label();
}

void folders_t::update_label() {
    auto l = fmt::format("folders ({})", children());
    this->label(l.data());
}

auto folders_t::add_folder(presentation::folder_entity_t &folder_entity) -> augmentation_ptr_t {
    auto augmentation = within_tree([&]() {
        auto self = supervisor.get_cluster()->get_device();
        auto presence = folder_entity.get_presence(self.get());
        auto folder_presence = static_cast<presentation::folder_presence_t *>(presence);
        auto item = new folder_t(*folder_presence, supervisor, tree());
        return insert_by_label(item)->get_proxy();
    });
    update_label();
    return augmentation;
}

void folders_t::remove_child(tree_item_t *child) {
    parent_t::remove_child(child);
    update_label();
}

void folders_t::select_folder(std::string_view folder_id) {
    auto t = tree();
    for (int i = 0; i < children(); ++i) {
        auto folder = static_cast<folder_t *>(child(i));
        auto &fp = static_cast<presentation::folder_presence_t &>(folder->get_presence());
        if (fp.get_folder_info().get_folder()->get_id() == folder_id) {
            while (auto selected = t->first_selected_item()) {
                t->deselect(selected);
            }
            t->select(folder, 1);
            t->redraw();
            break;
        }
    }
}

bool folders_t::on_select() {
    content = supervisor.replace_content([&](content_t *content) -> content_t * {
        auto cluster = supervisor.get_cluster();
        auto &self = *cluster->get_device();
        auto &sequencer = supervisor.get_sequencer();

        auto random_id = sequencer.next_uint64();
        auto random_id_ptr = reinterpret_cast<unsigned char *>(&random_id);
        auto sample_id = utils::base32::encode(utils::bytes_view_t(random_id_ptr, sizeof(random_id)));
        auto lower_caser = [](unsigned char c) { return std::tolower(c); };
        std::transform(sample_id.begin(), sample_id.end(), sample_id.begin(), lower_caser);
        auto sz = sample_id.size();
        auto id = sample_id.substr(0, sz / 2) + "-" + sample_id.substr(sz / 2 + 1);
        auto &path = supervisor.get_app_config().default_location;

        auto db_folder = db::Folder();
        db::set_rescan_interval(db_folder, 3600);
        db::set_path(db_folder, boost::nowide::narrow(path.wstring()));
        db::set_id(db_folder, id);
        auto folder = model::folder_t::create(sequencer.next_uuid(), db_folder).value();
        folder->assign_cluster(cluster);

        auto db_folder_info = db::FolderInfo();
        db::set_index_id(db_folder_info, sequencer.next_uint64());
        auto fi = model::folder_info_t::create(sequencer.next_uuid(), db_folder_info, &self, folder).value();

        auto prev = content->get_widget();
        int x = prev->x(), y = prev->y(), w = prev->w(), h = prev->h();
        return new table_t(*this, std::move(fi), std::move(folder), x, y, w, h);
    });
    return true;
}


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

#pragma once

#include "../tree_item.h"
#include "presentation/folder_entity.h"

namespace syncspirit::fltk::tree_item {

struct folders_t final : tree_item_t {
    using parent_t = tree_item_t;
    folders_t(app_supervisor_t &supervisor, Fl_Tree *tree);

    void select_folder(std::string_view folder_id);
    augmentation_ptr_t add_folder(presentation::folder_entity_t &folder);
    void update_label() override;

    bool on_select() override;
    void remove_child(tree_item_t *child) override;
};

} // namespace syncspirit::fltk::tree_item

repo_name: basiliscos/syncspirit

license: mit


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

#include "ignored_device.h"
#include "model/diff/modify/remove_ignored_device.h"
#include "model/diff/modify/update_peer.h"
#include "../static_table.h"
#include "../qr_button.h"

#include <FL/Fl_Tile.H>
#include <FL/Fl_Button.H>
#include <spdlog/fmt/fmt.h>

using namespace syncspirit;
using namespace model::diff;
using namespace fltk;
using namespace fltk::tree_item;

static constexpr int padding = 2;

namespace {

struct my_table_t;

static widgetable_ptr_t make_actions(my_table_t &container);

struct my_table_t : static_table_t {
    using parent_t = static_table_t;

    my_table_t(ignored_device_t &container_, int x, int y, int w, int h) : parent_t(x, y, w, h), container{container_} {
        auto &device = container.device;
        auto &device_id = device.get_device_id();
        auto data = table_rows_t();

        name_cell = new static_string_provider_t();
        client_name_cell = new static_string_provider_t();
        client_version_cell = new static_string_provider_t();
        address_cell = new static_string_provider_t();
        last_seen_cell = new static_string_provider_t();

        data.push_back({"name", name_cell});
        data.push_back({"device id (short)", new static_string_provider_t(device_id.get_short())});
        data.push_back({"device id", new static_string_provider_t(device_id.get_value())});
        data.push_back({"client", client_name_cell});
        data.push_back({"client version", client_version_cell});
        data.push_back({"address", address_cell});
        data.push_back({"last_seen", last_seen_cell});
        data.push_back({"actions", make_actions(*this)});
        assign_rows(std::move(data));
        refresh();
    }

    void refresh() override {
        auto &device = container.device;
        name_cell->update(device.get_name());
        client_name_cell->update(device.get_client_name());
        client_version_cell->update(device.get_client_version());
        address_cell->update(device.get_address());
        last_seen_cell->update(model::pt::to_simple_string(device.get_last_seen()));
        redraw();
    }

    void on_connect() {
        db::Device db_dev;
        auto &device = container.device;
        auto &supervisor = container.supervisor;
        db::set_name(db_dev, device.get_name());

        auto diff = cluster_diff_ptr_t();
        auto &cluster = *container.supervisor.get_cluster();
        diff = new modify::update_peer_t(std::move(db_dev), device.get_device_id(), cluster);
        supervisor.send_model<model::payload::model_update_t>(std::move(diff), this);
    }

    void on_remove() {
        auto &device = container.device;
        auto &supervisor = container.supervisor;
        auto diff = cluster_diff_ptr_t{};
        diff = new modify::remove_ignored_device_t(device);
        supervisor.send_model<model::payload::model_update_t>(std::move(diff), this);
    }

    ignored_device_t &container;
    static_string_provider_ptr_t name_cell;
    static_string_provider_ptr_t client_name_cell;
    static_string_provider_ptr_t client_version_cell;
    static_string_provider_ptr_t address_cell;
    static_string_provider_ptr_t last_seen_cell;
};

static widgetable_ptr_t make_actions(my_table_t &container) {
    struct widget_t final : widgetable_t {
        using parent_t = widgetable_t;
        widget_t(my_table_t &container_) : parent_t(container_) {}

        Fl_Widget *create_widget(int x, int y, int w, int h) override {
            auto group = new Fl_Group(x, y, w, h);
            group->begin();
            group->box(FL_FLAT_BOX);
            auto yy = y + padding, ww = 100, hh = h - padding * 2;
            auto connect = new Fl_Button(x + padding, yy, ww, hh, "connect");
            auto remove = new Fl_Button(connect->x() + ww + padding * 2, yy, ww, hh, "remove");
            remove->color(FL_RED);

            group->resizable(nullptr);
            group->end();

            connect->callback([](auto, void *data) { static_cast<my_table_t *>(data)->on_connect(); }, &container);
            remove->callback([](auto, void *data) { static_cast<my_table_t *>(data)->on_remove(); }, &container);

            widget = group;
            return widget;
        }
    };

    return new widget_t(container);
}
} // namespace

ignored_device_t::ignored_device_t(model::ignored_device_t &device_, app_supervisor_t &supervisor, Fl_Tree *tree)
    : parent_t(supervisor, tree), device{device_} {
    update_label();
}

void ignored_device_t::update_label() {
    auto name = device.get_name();
    auto id = device.get_device_id().get_short();
    auto value = fmt::format("{}, {}", name, id);
    label(value.data());
    tree()->redraw();
}

bool ignored_device_t::on_select() {
    content = supervisor.replace_content([&](content_t *content) -> content_t * {
        struct tile_t : contentable_t<Fl_Tile> {
            using parent_t = contentable_t<Fl_Tile>;
            using parent_t::parent_t;
        };

        auto prev = content->get_widget();
        auto &device_id = device.get_device_id();

        int x = prev->x(), y = prev->y(), w = prev->w(), h = prev->h();
        int bot_h = 100;

        auto group = new tile_t(x, y, w, h);
        auto resizeable_area = new Fl_Box(x + w * 1. / 6, y + h * 1. / 2, w * 4. / 6, h / 2. - bot_h);
        group->resizable(resizeable_area);

        group->begin();
        auto top = [&]() -> Fl_Widget * {
            int x = prev->x(), y = prev->y(), w = prev->w(), h = prev->h();
            return new my_table_t(*this, x, y, w, h - bot_h);
        }();
        auto bot = [&]() -> Fl_Widget * { return new qr_button_t(device_id, supervisor, x, y + top->h(), w, bot_h); }();
        group->add(top);
        group->add(bot);
        group->end();
        return group;
    });
    return true;
}


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

#pragma once

#include "model/ignored_device.h"
#include "../tree_item.h"

namespace syncspirit::fltk::tree_item {

struct ignored_device_t : tree_item_t {
    using parent_t = tree_item_t;

    ignored_device_t(model::ignored_device_t &device, app_supervisor_t &supervisor, Fl_Tree *tree);
    void update_label() override;

    bool on_select() override;

    model::ignored_device_t &device;
};

}; // namespace syncspirit::fltk::tree_item


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

#include "ignored_devices.h"
#include "ignored_device.h"

using namespace syncspirit;
using namespace syncspirit::model::diff;
using namespace syncspirit::fltk;
using namespace syncspirit::fltk::tree_item;

ignored_devices_t::ignored_devices_t(app_supervisor_t &supervisor, Fl_Tree *tree) : parent_t(supervisor, tree, false) {
    supervisor.set_ignored_devices(this);
    update_label();
    tree->close(this, 0);
}

void ignored_devices_t::update_label() {
    auto cluster = supervisor.get_cluster();
    auto count = cluster ? cluster->get_ignored_devices().size() : 0;
    auto l = fmt::format("ignored devices ({})", count);
    label(l.data());
    tree()->redraw();
}

augmentation_ptr_t ignored_devices_t::add_device(model::ignored_device_t &device) {
    return within_tree([&]() {
        auto node = insert_by_label(new ignored_device_t(device, supervisor, tree()));
        update_label();
        return node->get_proxy();
    });
}

void ignored_devices_t::remove_device(tree_item_t *item) {
    remove_child(item);
    update_label();
    tree()->redraw();
}


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

#pragma once

#include "../tree_item.h"

namespace syncspirit::fltk::tree_item {

struct ignored_devices_t : tree_item_t {
    using parent_t = tree_item_t;
    ignored_devices_t(app_supervisor_t &supervisor, Fl_Tree *tree);

    void update_label() override;

    augmentation_ptr_t add_device(model::ignored_device_t &device);
    void remove_device(tree_item_t *item);
};

} // namespace syncspirit::fltk::tree_item


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

#include "peer_device.h"
#include "peer_folders.h"
#include "pending_folders.h"
#include "../symbols.h"
#include "../utils.hpp"
#include "../table_widget/checkbox.h"
#include "model/diff/modify/remove_peer.h"
#include "model/diff/modify/update_peer.h"
#include "utils/format.hpp"

#include <boost/asio.hpp>
#include <vector>
#include <fmt/ranges.h>

#include <FL/Fl_Tile.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Choice.H>
#include <FL/Fl_Input_Choice.H>
#include <FL/fl_ask.H>

using namespace syncspirit;
using namespace model::diff;
using namespace syncspirit::fltk;
using namespace syncspirit::fltk::tree_item;

static constexpr int padding = 2;

static std::string format_urls(const utils::uri_container_t &uris) { return fmt::format("{}", fmt::join(uris, ",")); }

namespace {

struct my_table_t;

static widgetable_ptr_t make_name(my_table_t &container);
static widgetable_ptr_t make_introducer(my_table_t &container);
static widgetable_ptr_t make_auto_accept(my_table_t &container);
static widgetable_ptr_t make_auto_accept(my_table_t &container);
static widgetable_ptr_t make_paused(my_table_t &container);
static widgetable_ptr_t make_compressions(my_table_t &container);
static widgetable_ptr_t make_addresses(my_table_t &container);
static widgetable_ptr_t make_actions(my_table_t &container);

struct my_table_t : static_table_t {
    using parent_t = static_table_t;

    my_table_t(peer_device_t &container_, int x, int y, int w, int h)
        : parent_t(x, y, w, h), container{container_}, apply_button{nullptr}, reset_button{nullptr} {
        auto data = table_rows_t();
        auto &peer = container.peer;

        auto device_id = peer.device_id().get_value();
        auto device_id_short = peer.device_id().get_short();

        last_seen_cell = new static_string_provider_t();
        endpoint_cell = new static_string_provider_t();
        state_cell = new static_string_provider_t();
        rx_cell = new static_string_provider_t();
        tx_cell = new static_string_provider_t();
        certname_cell = new static_string_provider_t();
        client_name_cell = new static_string_provider_t();
        client_version_cell = new static_string_provider_t();

        data.push_back({"name", make_name(*this)});
        data.push_back({"last_seen", last_seen_cell});
        data.push_back({"addresses", make_addresses(*this)});
        data.push_back({"endpoint", endpoint_cell});
        data.push_back({"state", state_cell});
        data.push_back({"received", rx_cell});
        data.push_back({"send", tx_cell});
        data.push_back({"cert name", certname_cell});
        data.push_back({"client name", client_name_cell});
        data.push_back({"client version", client_version_cell});
        data.push_back({"device id (short)", new static_string_provider_t(device_id_short)});
        data.push_back({"device id", new static_string_provider_t(device_id)});
        data.push_back({"introducer", make_introducer(*this)});
        data.push_back({"auto accept", make_auto_accept(*this)});
        data.push_back({"paused", make_paused(*this)});
        data.push_back({"compression", make_compressions(*this)});
        data.push_back({"actions", make_actions(*this)});

        assign_rows(std::move(data));
        refresh();
    }

    void on_remove() {
        auto r = fl_choice("Are you sure?", "Yes", "No", nullptr);
        if (r != 0) {
            return;
        }
        auto &supervisor = container.supervisor;
        auto &cluster = *supervisor.get_cluster();
        auto diff = cluster_diff_ptr_t(new modify::remove_peer_t(cluster, container.peer));
        supervisor.send_model<model::payload::model_update_t>(std::move(diff), this);
    }

    void on_apply() {
        auto data = container.peer.serialize();
        auto device = db::Device();
        auto undecoded_bytes = db::decode(data, device);
        assert(undecoded_bytes == 0);
        (void)undecoded_bytes;
        auto valid = store(&device);
        if (valid) {
            auto &supervisor = container.supervisor;
            auto &device_id = container.peer.device_id();
            auto &cluster = *supervisor.get_cluster();
            auto diff = cluster_diff_ptr_t(new modify::update_peer_t(std::move(device), device_id, cluster));
            supervisor.send_model<model::payload::model_update_t>(std::move(diff), this);
        }
    }

    void on_reset() {
        reset();
        refresh();
    }

    void refresh() override {
        auto &peer = container.peer;
        auto initial_data = peer.serialize();
        auto current = db::Device();
        auto undecoded_bytes = db::decode(initial_data, current);
        assert(undecoded_bytes == 0);
        (void)undecoded_bytes;
        auto valid = store(&current);

        auto current_data = db::encode(current);
        if (initial_data != current_data) {
            if (valid) {
                apply_button->activate();
            }
            reset_button->activate();
        } else {
            apply_button->deactivate();
            reset_button->deactivate();
        }

        auto last_seen = peer.get_endpoint().port() ? "now" : model::pt::to_simple_string(peer.get_last_seen());
        auto endpoint = peer.get_endpoint().port() ? fmt::format("{}", peer.get_endpoint()) : "";

        last_seen_cell->update(std::move(last_seen));
        endpoint_cell->update(std::move(endpoint));

        auto state_symbol = container.get_state();
        auto state = fmt::format("{} ({})", state_symbol, symbols::get_description(state_symbol));
        auto rx = get_file_size(peer.get_rx_bytes());
        auto tx = get_file_size(peer.get_tx_bytes());

        state_cell->update(std::move(state));
        rx_cell->update(std::move(rx));
        tx_cell->update(std::move(tx));

        certname_cell->update(peer.get_cert_name().value_or(""));
        client_name_cell->update(peer.get_client_name());
        client_version_cell->update(peer.get_client_version());
        redraw();
    }

    peer_device_t &container;
    Fl_Widget *apply_button;
    Fl_Widget *reset_button;
    static_string_provider_ptr_t last_seen_cell;
    static_string_provider_ptr_t endpoint_cell;
    static_string_provider_ptr_t state_cell;
    static_string_provider_ptr_t rx_cell;
    static_string_provider_ptr_t tx_cell;
    static_string_provider_ptr_t certname_cell;
    static_string_provider_ptr_t client_name_cell;
    static_string_provider_ptr_t client_version_cell;
};

struct checkbox_widget_t : table_widget::checkbox_t {
    using parent_t = table_widget::checkbox_t;
    using parent_t::parent_t;

    Fl_Widget *create_widget(int x, int y, int w, int h) override {
        auto r = parent_t::create_widget(x, y, w, h);
        input->callback([](auto, void *data) { reinterpret_cast<my_table_t *>(data)->refresh(); }, &container);
        return r;
    }
};

static widgetable_ptr_t make_actions(my_table_t &container) {
    struct widget_t final : widgetable_t {
        using parent_t = widgetable_t;
        using parent_t::parent_t;

        Fl_Widget *create_widget(int x, int y, int w, int h) override {
            auto group = new Fl_Group(x, y, w, h);
            group->begin();
            group->box(FL_FLAT_BOX);
            auto yy = y + padding, ww = 100, hh = h - padding * 2;
            auto apply = new Fl_Button(x + padding, yy, ww, hh, "apply");
            auto reset = new Fl_Button(apply->x() + ww + padding * 2, yy, ww, hh, "reset");
            auto remove = new Fl_Button(reset->x() + ww + padding * 2, yy, ww, hh, "remove");
            apply->deactivate();
            reset->deactivate();
            remove->color(FL_RED);

            group->resizable(nullptr);

            group->end();
            widget = group;

            apply->callback([](auto, void *data) { static_cast<my_table_t *>(data)->on_apply(); }, &container);
            reset->callback([](auto, void *data) { static_cast<my_table_t *>(data)->on_reset(); }, &container);
            remove->callback([](auto, void *data) { static_cast<my_table_t *>(data)->on_remove(); }, &container);

            this->reset();
            auto &container = static_cast<my_table_t &>(this->container);
            container.apply_button = apply;
            container.reset_button = reset;
            return widget;
        }
    };

    return new widget_t(container);
}

static widgetable_ptr_t make_name(my_table_t &container) {
    struct widget_t final : widgetable_t {
        using parent_t = widgetable_t;
        using parent_t::parent_t;

        Fl_Widget *create_widget(int x, int y, int w, int h) override {
            auto group = new Fl_Group(x, y, w, h);
            group->begin();
            group->box(FL_FLAT_BOX);
            auto yy = y + padding, ww = w - padding * 2, hh = h - padding * 2;
            ww = std::min(300, ww);

            input = new Fl_Input(x + padding, yy, ww, hh);
            input->when(input->when() | FL_WHEN_CHANGED);
            input->callback([](auto, void *data) { reinterpret_cast<my_table_t *>(data)->refresh(); }, &container);

            group->end();
            group->resizable(nullptr);
            widget = group;

            reset();
            return widget;
        }

        void reset() override {
            auto &container = static_cast<my_table_t &>(this->container);
            input->value(container.container.peer.get_name().data());
        }

        bool store(void *data) override {
            auto &device = *reinterpret_cast<db::Device *>(data);
            db::set_name(device, input->value());
            return true;
        };

        mutable Fl_Input *input;
    };

    return new widget_t(container);
}

static widgetable_ptr_t make_introducer(my_table_t &container) {
    struct widget_t final : checkbox_widget_t {
        using parent_t = checkbox_widget_t;
        using parent_t::parent_t;

        void reset() override {
            auto &container = static_cast<my_table_t &>(this->container);
            input->value(container.container.peer.is_introducer());
        }

        bool store(void *data) override {
            auto &device = *reinterpret_cast<db::Device *>(data);
            db::set_introducer(device, input->value());
            return true;
        };
    };

    return new widget_t(container);
}

static widgetable_ptr_t make_auto_accept(my_table_t &container) {
    struct widget_t final : checkbox_widget_t {
        using parent_t = checkbox_widget_t;
        using parent_t::parent_t;

        void reset() override {
            auto &container = static_cast<my_table_t &>(this->container);
            input->value(container.container.peer.has_auto_accept());
        }

        bool store(void *data) override {
            auto &device = *reinterpret_cast<db::Device *>(data);
            db::set_auto_accept(device, input->value());
            return true;
        };
    };

    return new widget_t(container);
}

static widgetable_ptr_t make_paused(my_table_t &container) {
    struct widget_t final : checkbox_widget_t {
        using parent_t = checkbox_widget_t;
        using parent_t::parent_t;

        void reset() override {
            auto &container = static_cast<my_table_t &>(this->container);
            input->value(container.container.peer.is_paused());
        }

        bool store(void *data) override {
            auto &device = *reinterpret_cast<db::Device *>(data);
            db::set_paused(device, input->value());
            return true;
        };
    };

    return new widget_t(container);
}

static widgetable_ptr_t make_compressions(my_table_t &container) {
    struct widget_t final : checkbox_widget_t {
        using parent_t = checkbox_widget_t;
        using parent_t::parent_t;

        Fl_Widget *create_widget(int x, int y, int w, int h) override {
            auto group = new Fl_Group(x, y, w, h);
            group->begin();
            group->box(FL_FLAT_BOX);
            auto yy = y + padding, ww = w - padding * 2, hh = h - padding * 2;
            ww = std::min(150, ww);

            input = new Fl_Choice(x + padding, yy, ww, hh);
            input->add("metadata");
            input->add("never");
            input->add("always");
            input->value(0);
            input->callback([](auto, void *data) { reinterpret_cast<my_table_t *>(data)->refresh(); }, &container);

            group->end();
            group->resizable(nullptr);
            widget = group;

            reset();
            return widget;
        }

        void reset() override {
            auto &container = static_cast<my_table_t &>(this->container);
            using C = proto::Compression;
            auto c = container.container.peer.get_compression();
            if (c == C::METADATA) {
                input->value(0);
            } else if (c == C::NEVER) {
                input->value(1);
            } else {
                input->value(2);
            }
        }

        bool store(void *data) override {
            auto &device = *reinterpret_cast<db::Device *>(data);
            auto value = static_cast<proto::Compression>(input->value());
            db::set_compression(device, value);
            return true;
        };

        mutable Fl_Choice *input;
    };

    return new widget_t(container);
}

static widgetable_ptr_t make_addresses(my_table_t &container) {
    struct widget_t final : widgetable_t {
        using parent_t = widgetable_t;
        using parent_t::parent_t;

        Fl_Widget *create_widget(int x, int y, int w, int h) override {
            auto group = new Fl_Group(x, y, w, h);
            group->begin();
            group->box(FL_FLAT_BOX);
            auto yy = y + padding, ww = w - padding * 2, hh = h - padding * 2;
            ww = std::min(300, ww);
            input = new Fl_Input_Choice(x + padding, yy, ww, hh);
            input->when(input->when() | FL_WHEN_CHANGED);
            input->callback([](auto, void *data) { reinterpret_cast<widget_t *>(data)->on_change(); }, this);
            input->tooltip("comma-separated urls like tcp://ip:port");

            group->end();
            widget = group;

            reset();
            return widget;
        }

        void reset() override {
            auto &container = static_cast<my_table_t &>(this->container);
            auto &uris = container.container.peer.get_static_uris();
            auto menu = input->menubutton();
            input->clear();
            menu->add("dynamic", 0, 0, 0, (uris.empty() ? FL_MENU_INACTIVE : 0));
            menu->add("static", 0, 0, 0, (uris.empty() ? 0 : FL_MENU_INACTIVE));

            if (uris.empty()) {
                menu->value(0);
                input->value("dynamic");
                input->input()->deactivate();
            } else {
                menu->value(1);
                input->input()->activate();
                auto value = format_urls(uris);
                input->value(value.data());
            }
        }

        bool store(void *data) override {
            auto &device = *reinterpret_cast<db::Device *>(data);
            db::clear_addresses(device);
            if (input->input()->active() == 0) {
                return true;
            } else {
                std::vector<std::string_view> addresses;
                std::string_view data = input->input()->value();
                auto left = data;
                while (left.size()) {
                    if (left.front() == ' ' || left.front() == ',') {
                        left = left.substr(1);
                        continue;
                    }
                    auto e = left.find_first_of(", ");
                    auto piece = left.substr(0, e);
                    auto url = utils::parse(piece);
                    if (url) {
                        addresses.push_back(piece);
                        if (e != left.npos) {
                            left = left.substr(piece.size() + 1);
                        } else {
                            left = {};
                        }
                    } else {
                        return false;
                    }
                }
                for (auto addr : addresses) {
                    db::add_addresses(device, addr);
                }
                return true;
            }
        };

        void on_change() {
            auto menu = input->menubutton();
            auto &container = static_cast<my_table_t &>(this->container);
            if (menu->changed()) {
                if (menu->value() == 0) {
                    input->value("dynamic");
                    menu->clear();
                    menu->add("dynamic", 0, 0, 0, FL_MENU_INACTIVE);
                    menu->add("static", 0, 0, 0, 0);
                    input->input()->deactivate();
                } else {
                    auto &uris = container.container.peer.get_static_uris();
                    auto value = format_urls(uris);
                    input->value(value.data());
                    input->input()->activate();
                    input->input()->take_focus();
                    menu->clear();
                    menu->add("dynamic", 0, 0, 0, 0);
                    menu->add("static", 0, 0, 0, FL_MENU_INACTIVE);
                }
            }
            container.refresh();
        }

        mutable Fl_Input_Choice *input;
    };
    return new widget_t(container);
}

} // namespace

peer_device_t::peer_device_t(model::device_t &peer_, app_supervisor_t &supervisor, Fl_Tree *tree)
    : parent_t(supervisor, tree), peer{peer_}, folders{nullptr}, pending_folders{nullptr} {
    auto &cluster = *supervisor.get_cluster();
    bool has_folders = false;
    auto &folders = cluster.get_folders();
    auto total_files = std::size_t{0};
    for (auto &it : folders) {
        if (auto fi = it.item->is_shared_with(peer); fi) {
            has_folders = true;
            total_files += fi->get_file_infos().size();
            break;
        }
    }
    if (has_folders) {
        get_folders();
    }

    bool has_pending_folders = false;
    auto &pending_folders = cluster.get_pending_folders();
    for (auto &it : pending_folders) {
        auto &uf = *it.item;
        if (uf.device_id() == peer.device_id()) {
            has_pending_folders = true;
            break;
        }
    }
    if (has_pending_folders) {
        get_pending_folders();
    }

    if ((total_files == 0) && !has_pending_folders) {
        tree->close(this, 0);
    }

    update_label();
}

void peer_device_t::update_label() {
    auto name = peer.get_name();
    auto id = peer.device_id().get_short();
    auto value = fmt::format("{}, {} {}", name, id, get_state());
    label(value.data());
    tree()->redraw();
}

bool peer_device_t::on_select() {
    content = supervisor.replace_content([&](content_t *content) -> content_t * {
        auto prev = content->get_widget();
        return new my_table_t(*this, prev->x(), prev->y(), prev->w(), prev->h());
    });

    return true;
}

std::string_view peer_device_t::get_state() {
    return [this]() -> std::string_view {
        switch (peer.get_state()) {
        case model::device_state_t::online:
            return symbols::online;
        case model::device_state_t::discovering:
            return symbols::discovering;
        case model::device_state_t::connecting:
            return symbols::connecting;
        default:
            return symbols::offline;
        }
    }();
}

tree_item_t *peer_device_t::get_folders() {
    if (!folders) {
        folders = within_tree([&]() {
            auto item = new peer_folders_t(peer, supervisor, tree());
            insert(prefs(), "", 0)->replace(item);
            return item;
        });
    }
    return folders;
}

tree_item_t *peer_device_t::get_pending_folders() {
    if (!pending_folders) {
        pending_folders = new pending_folders_t(peer, supervisor, tree());
        add(prefs(), pending_folders->label(), pending_folders);
        tree()->redraw();
    }
    return pending_folders;
}

void peer_device_t::remove_child(tree_item_t *child) {
    if (child == pending_folders) {
        pending_folders = nullptr;
    }
    if (child == folders) {
        folders = nullptr;
    }
    parent_t::remove_child(child);
}


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

#pragma once

#include "model/device.h"
#include "../tree_item.h"

namespace syncspirit::fltk::tree_item {

struct peer_device_t : tree_item_t {
    using parent_t = tree_item_t;

    peer_device_t(model::device_t &peer, app_supervisor_t &supervisor, Fl_Tree *tree);

    const model::device_t &get_device() const;
    std::string_view get_state();

    void update_label() override;
    bool on_select() override;
    void remove_child(tree_item_t *child) override;

    tree_item_t *get_folders();
    tree_item_t *get_pending_folders();

    model::device_t &peer;
    tree_item_t *folders;
    tree_item_t *pending_folders;
};

} // namespace syncspirit::fltk::tree_item


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

#include "peer_folders.h"
#include "presence_item/folder.h"
#include "presentation/folder_entity.h"
#include "presentation/folder_presence.h"

using namespace syncspirit;
using namespace syncspirit::model::diff;
using namespace syncspirit::fltk;
using namespace syncspirit::fltk::tree_item;
using namespace syncspirit::fltk::presence_item;

peer_folders_t::peer_folders_t(model::device_t &peer_, app_supervisor_t &supervisor, Fl_Tree *tree)
    : parent_t(supervisor, tree, false), peer{peer_} {
    auto cluster = supervisor.get_cluster();
    auto &folders = cluster->get_folders();
    for (auto it : folders) {
        auto &f = *it.item;
        if (auto fi = f.is_shared_with(peer); fi) {
            auto &augmentation = f.get_augmentation();
            if (augmentation) {
                auto folder_entity = static_cast<presentation::folder_entity_t *>(augmentation.get());
                auto presence = folder_entity->get_presence(&peer);
                auto folder_presence = static_cast<presentation::folder_presence_t *>(presence);
                auto folder = new folder_t(*folder_presence, supervisor, tree);
                insert_by_label(folder);
            }
        }
    }
    update_label();
    tree->close(this, 0);
}

void peer_folders_t::update_label() {
    auto l = fmt::format("folders ({})", children());
    this->label(l.data());
}

void peer_folders_t::add_folder(presentation::folder_presence_t &presence) {
    return within_tree([&]() {
        auto folder = new folder_t(presence, supervisor, tree());
        insert_by_label(folder);
        update_label();
        return;
    });
}

void peer_folders_t::remove_child(tree_item_t *item) {
    parent_t::remove_child(item);
    if (!children()) {
        static_cast<tree_item_t *>(parent())->remove_child(this);
    } else {
        update_label();
        tree()->redraw();
    }
}


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

#pragma once

#include "../tree_item.h"
#include "presentation/folder_presence.h"

namespace syncspirit::fltk::tree_item {

struct peer_folders_t : tree_item_t {
    using parent_t = tree_item_t;
    peer_folders_t(model::device_t &peer, app_supervisor_t &supervisor, Fl_Tree *tree);

    void update_label() override;

    void add_folder(presentation::folder_presence_t &presence);
    void remove_child(tree_item_t *child) override;
    model::device_t &peer;
};

} // namespace syncspirit::fltk::tree_item


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

#include "pending_device.h"
#include "model/diff/modify/add_ignored_device.h"
#include "model/diff/modify/remove_pending_device.h"
#include "model/diff/modify/update_peer.h"
#include "../static_table.h"
#include "../qr_button.h"

#include <FL/Fl_Tile.H>
#include <FL/Fl_Button.H>
#include <spdlog/fmt/fmt.h>

using namespace syncspirit;
using namespace model::diff;
using namespace fltk;
using namespace fltk::tree_item;

static constexpr int padding = 2;

namespace {

struct my_table_t;

static widgetable_ptr_t make_actions(my_table_t &container);

struct my_table_t : static_table_t {
    using parent_t = static_table_t;

    my_table_t(pending_device_t &container_, int x, int y, int w, int h) : parent_t(x, y, w, h), container{container_} {
        auto &device = container.device;
        auto &device_id = device.get_device_id();
        auto data = table_rows_t();
        auto last_seen = model::pt::to_simple_string(device.get_last_seen());

        name_cell = new static_string_provider_t();
        client_name_cell = new static_string_provider_t();
        client_version_cell = new static_string_provider_t();
        address_cell = new static_string_provider_t();
        last_seen_cell = new static_string_provider_t();

        data.push_back({"name", name_cell});
        data.push_back({"device id (short)", new static_string_provider_t(device_id.get_short())});
        data.push_back({"device id", new static_string_provider_t(device_id.get_value())});
        data.push_back({"client", client_name_cell});
        data.push_back({"client version", client_version_cell});
        data.push_back({"address", address_cell});
        data.push_back({"last_seen", last_seen_cell});
        data.push_back({"actions", make_actions(*this)});
        assign_rows(std::move(data));
        refresh();
    }

    void refresh() override {
        auto &device = container.device;
        name_cell->update(device.get_name());
        client_name_cell->update(device.get_client_name());
        client_version_cell->update(device.get_client_version());
        address_cell->update(device.get_address());
        name_cell->update(device.get_name());
        last_seen_cell->update(model::pt::to_simple_string(device.get_last_seen()));
        redraw();
    }

    void on_connect() {
        auto &device = container.device;
        auto &supervisor = container.supervisor;
        db::Device db_dev;
        db::set_name(db_dev, device.get_name());

        auto diff = cluster_diff_ptr_t();
        auto &cluster = *supervisor.get_cluster();
        diff = new modify::update_peer_t(std::move(db_dev), device.get_device_id(), cluster);
        supervisor.send_model<model::payload::model_update_t>(std::move(diff), this);
    }

    void on_ignore() {
        auto &device = container.device;
        auto &supervisor = container.supervisor;
        auto diff = cluster_diff_ptr_t{};
        auto &cluster = *supervisor.get_cluster();
        db::SomeDevice db;
        device.serialize(db);
        diff = new modify::add_ignored_device_t(cluster, device.get_device_id(), std::move(db));
        supervisor.send_model<model::payload::model_update_t>(std::move(diff), this);
    }

    void on_remove() {
        auto &device = container.device;
        auto &supervisor = container.supervisor;
        auto diff = cluster_diff_ptr_t{};
        diff = new modify::remove_pending_device_t(device);
        supervisor.send_model<model::payload::model_update_t>(std::move(diff), this);
    }

    pending_device_t &container;
    static_string_provider_ptr_t name_cell;
    static_string_provider_ptr_t client_name_cell;
    static_string_provider_ptr_t client_version_cell;
    static_string_provider_ptr_t address_cell;
    static_string_provider_ptr_t last_seen_cell;
};

static widgetable_ptr_t make_actions(my_table_t &container) {
    struct widget_t final : widgetable_t {
        using parent_t = widgetable_t;
        widget_t(my_table_t &container_) : parent_t(container_) {}

        Fl_Widget *create_widget(int x, int y, int w, int h) override {
            auto group = new Fl_Group(x, y, w, h);
            group->begin();
            group->box(FL_FLAT_BOX);
            auto yy = y + padding, ww = 100, hh = h - padding * 2;
            auto connect = new Fl_Button(x + padding, yy, ww, hh, "connect");
            auto ignore = new Fl_Button(connect->x() + ww + padding * 2, yy, ww, hh, "ignore");
            auto remove = new Fl_Button(ignore->x() + ww + padding * 2, yy, ww, hh, "remove");
            remove->color(FL_RED);

            group->resizable(nullptr);
            group->end();

            connect->callback([](auto, void *data) { static_cast<my_table_t *>(data)->on_connect(); }, &container);
            ignore->callback([](auto, void *data) { static_cast<my_table_t *>(data)->on_ignore(); }, &container);
            remove->callback([](auto, void *data) { static_cast<my_table_t *>(data)->on_remove(); }, &container);

            widget = group;
            return widget;
        }
    };

    return new widget_t(container);
}

} // namespace

pending_device_t::pending_device_t(model::pending_device_t &device_, app_supervisor_t &supervisor, Fl_Tree *tree)
    : parent_t(supervisor, tree), device{device_} {
    update_label();
}

void pending_device_t::update_label() {
    auto name = device.get_name();
    auto id = device.get_device_id().get_short();
    auto value = fmt::format("{}, {}", name, id);
    label(value.data());
    tree()->redraw();
}

bool pending_device_t::on_select() {
    content = supervisor.replace_content([&](content_t *content) -> content_t * {
        struct tile_t : contentable_t<Fl_Tile> {
            using parent_t = contentable_t<Fl_Tile>;
            using parent_t::parent_t;
        };

        auto prev = content->get_widget();
        auto &device_id = device.get_device_id();

        int x = prev->x(), y = prev->y(), w = prev->w(), h = prev->h();
        int bot_h = 100;

        auto group = new tile_t(x, y, w, h);
        auto resizable_area = new Fl_Box(x + w * 1. / 6, y + h * 1. / 2, w * 4. / 6, h / 2. - bot_h);
        group->resizable(resizable_area);

        group->begin();
        auto top = [&]() -> Fl_Widget * {
            int x = prev->x(), y = prev->y(), w = prev->w(), h = prev->h();
            return new my_table_t(*this, x, y, w, h - bot_h);
        }();
        auto bot = [&]() -> Fl_Widget * { return new qr_button_t(device_id, supervisor, x, y + top->h(), w, bot_h); }();
        group->add(top);
        group->add(bot);
        group->end();
        return group;
    });
    return true;
}


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

#pragma once

#include "model/pending_device.h"
#include "../tree_item.h"

namespace syncspirit::fltk::tree_item {

struct pending_device_t : tree_item_t {
    using parent_t = tree_item_t;

    pending_device_t(model::pending_device_t &device, app_supervisor_t &supervisor, Fl_Tree *tree);
    void update_label() override;

    bool on_select() override;

    model::pending_device_t &device;
};

}; // namespace syncspirit::fltk::tree_item


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

#include "pending_devices.h"
#include "pending_device.h"

using namespace syncspirit;
using namespace syncspirit::model::diff;
using namespace syncspirit::fltk;
using namespace syncspirit::fltk::tree_item;

pending_devices_t::pending_devices_t(app_supervisor_t &supervisor, Fl_Tree *tree) : parent_t(supervisor, tree, false) {
    supervisor.set_pending_devices(this);
    update_label();
}

void pending_devices_t::update_label() {
    auto cluster = supervisor.get_cluster();
    auto count = cluster ? cluster->get_pending_devices().size() : 0;
    auto l = fmt::format("pending devices ({})", count);
    label(l.data());
}

auto pending_devices_t::add_device(model::pending_device_t &device) -> augmentation_ptr_t {
    return within_tree([&]() {
        auto node = insert_by_label(new pending_device_t(device, supervisor, tree()));
        update_label();
        return node->get_proxy();
    });
}

void pending_devices_t::remove_device(tree_item_t *item) {
    remove_child(item);
    update_label();
    tree()->redraw();
}


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

#pragma once

#include "../tree_item.h"

namespace syncspirit::fltk::tree_item {

struct pending_devices_t : tree_item_t {
    using parent_t = tree_item_t;
    pending_devices_t(app_supervisor_t &supervisor, Fl_Tree *tree);

    void update_label() override;

    augmentation_ptr_t add_device(model::pending_device_t &device);
    void remove_device(tree_item_t *item);
};

} // namespace syncspirit::fltk::tree_item


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

#include "pending_folder.h"
#include "pending_folders.h"
#include "../table_widget/checkbox.h"
#include "../content/folder_table.h"

#include <FL/Fl_Check_Button.H>
#include <spdlog/fmt/fmt.h>
#include <boost/nowide/convert.hpp>

using namespace syncspirit;
using namespace model::diff;
using namespace fltk;
using namespace fltk::tree_item;

static constexpr int padding = 2;

namespace {

using folder_table_t = content::folder_table_t;

static auto make_actions(folder_table_t &container) -> widgetable_ptr_t {
    struct widget_t final : widgetable_t {
        using parent_t = widgetable_t;
        using parent_t::parent_t;

        Fl_Widget *create_widget(int x, int y, int w, int h) override {
            auto group = new Fl_Group(x, y, w, h);
            group->begin();
            group->box(FL_FLAT_BOX);
            auto &container = static_cast<folder_table_t &>(this->container);

            auto yy = y + padding, ww = 100, hh = h - padding * 2;

            auto share = new Fl_Button(x + padding, yy, ww, hh, "share");
            share->deactivate();
            share->callback([](auto, void *data) { static_cast<folder_table_t *>(data)->on_share(); }, &container);
            auto xx = share->x() + ww + padding * 2;
            container.share_button = share;

            auto reset = new Fl_Button(xx, yy, ww, hh, "reset");
            reset->deactivate();
            reset->callback([](auto, void *data) { static_cast<folder_table_t *>(data)->on_reset(); }, &container);
            container.reset_button = reset;
            xx = reset->x() + ww + padding * 2;

            group->resizable(nullptr);
            group->end();
            widget = group;

            this->reset();
            return widget;
        }
    };

    return new widget_t(container);
}

struct table_t : content::folder_table_t {
    using parent_t = content::folder_table_t;

    table_t(tree_item_t &container_, model::folder_info_ptr_t fi_, model::folder_ptr_t folder_, int x, int y, int w,
            int h)
        : parent_t(container_, *fi_, x, y, w, h), fi{fi_}, folder{folder_}, existing{false} {

        entries_cell = new static_string_provider_t();
        max_sequence_cell = new static_string_provider_t();
        scan_start_cell = new static_string_provider_t();
        scan_finish_cell = new static_string_provider_t();

        auto folder_id = folder_->get_id();
        existing = (bool)container_.supervisor.get_cluster()->get_folders().by_id(folder_id);

        auto data = table_rows_t();
        data.push_back({"", make_title(*this, "accepting pending folder")});
        data.push_back({"path", make_path(*this, existing)});
        data.push_back({"id", make_id(*this, existing)});
        data.push_back({"label", make_label(*this, existing)});
        data.push_back({"type", make_folder_type(*this, existing)});
        data.push_back({"pull order", make_pull_order(*this, existing)});
        data.push_back({"index", make_index(*this, true)});
        data.push_back({"rescan interval", make_rescan_interval(*this, existing)});
        data.push_back({"ignore permissions", make_ignore_permissions(*this, existing)});
        data.push_back({"ignore delete", make_ignore_delete(*this, existing)});
        data.push_back({"disable temp indixes", make_disable_tmp(*this, existing)});
        data.push_back({"scheduled", make_scheduled(*this, existing)});
        data.push_back({"paused", make_paused(*this, existing)});
        data.push_back({"shared_with", make_shared_with(*this, fi->get_device(), true)});
        data.push_back({"", notice = make_notice(*this)});
        data.push_back({"actions", make_actions(*this)});

        initially_shared_with = *shared_with;
        initially_non_shared_with = *non_shared_with;
        assign_rows(std::move(data));

        refresh();
    }

    void refresh() override {
        serialization_context_t ctx;
        folder->serialize(ctx.folder);

        auto copy_data = db::encode(ctx.folder);
        error = {};
        auto valid = store(&ctx);

        if (valid) {
            auto db_path = db::get_path(ctx.folder);
            if (db_path.empty()) {
                error = "path should be defined";
            } else {
                auto path = bfs::path(boost::nowide::widen(db_path));
                auto ec = sys::error_code{};
                if (bfs::exists(path, ec)) {
                    if (!bfs::is_empty(path, ec)) {
                        error = "referred directory should be empty";
                    }
                }
            }
        }

        if (valid && error.empty()) {
            share_button->activate();
        } else {
            share_button->deactivate();
        }

        notice->reset();
        redraw();
    }

    model::folder_info_ptr_t fi;
    model::folder_ptr_t folder;
    bool existing;
};

} // namespace

pending_folder_t::pending_folder_t(model::pending_folder_t &folder_, app_supervisor_t &supervisor, Fl_Tree *tree)
    : parent_t(supervisor, tree), folder{folder_} {

    auto l = fmt::format("{} ({})", folder.get_label(), folder.get_id());
    label(l.c_str());
}

bool pending_folder_t::on_select() {
    content = supervisor.replace_content([&](content_t *content) -> content_t * {
        auto prev = content->get_widget();
        auto cluster = supervisor.get_cluster();
        auto &sequencer = supervisor.get_sequencer();
        auto &peer = static_cast<pending_folders_t *>(parent())->peer;
        auto &path = supervisor.get_app_config().default_location;

        auto db = db::PendingFolder();
        folder.serialize(db);

        auto &db_folder = db::get_folder(db);
        db::set_path(db_folder, path.string());
        db::set_rescan_interval(db_folder, 3600);

        auto folder = model::folder_t::create(sequencer.next_uuid(), db_folder).value();
        folder->assign_cluster(cluster);

        auto db_folder_info = db::FolderInfo();
        db::set_index_id(db_folder_info, sequencer.next_uint64());
        auto fi = model::folder_info_t::create(sequencer.next_uuid(), db_folder_info, &peer, folder).value();
        folder->get_folder_infos().put(fi);

        int x = prev->x(), y = prev->y(), w = prev->w(), h = prev->h();
        return new table_t(*this, std::move(fi), std::move(folder), x, y, w, h);
    });
    return true;
}


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

#pragma once

#include "model/pending_folder.h"
#include "../tree_item.h"

namespace syncspirit::fltk::tree_item {

struct pending_folder_t : tree_item_t {
    using parent_t = tree_item_t;

    pending_folder_t(model::pending_folder_t &folder, app_supervisor_t &supervisor, Fl_Tree *tree);

    bool on_select() override;

    model::pending_folder_t &folder;
};

}; // namespace syncspirit::fltk::tree_item


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

#include "pending_folders.h"
#include "pending_folder.h"

using namespace syncspirit;
using namespace model::diff;
using namespace fltk;
using namespace fltk::tree_item;

pending_folders_t::pending_folders_t(model::device_t &peer_, app_supervisor_t &supervisor, Fl_Tree *tree)
    : parent_t(supervisor, tree, false), peer{peer_} {
    label("pending folders");

    auto &folders = supervisor.get_cluster()->get_pending_folders();
    for (auto &it : folders) {
        auto &f = *it.item;
        if (f.device_id() == peer.device_id() && !f.get_augmentation()) {
            f.set_augmentation(add_pending_folder(f));
        }
    }
}

augmentation_ptr_t pending_folders_t::add_pending_folder(model::pending_folder_t &uf) {
    for (int i = 0; i < children(); ++i) {
        auto item = static_cast<pending_folder_t *>(this->child(i));
        if (&item->folder == &uf) {
            return {};
        }
    }
    return within_tree([&]() { return insert_by_label(new pending_folder_t(uf, supervisor, tree()))->get_proxy(); });
}

void pending_folders_t::remove_folder(tree_item_t *item) {
    remove_child(item);
    tree()->redraw();
}

void pending_folders_t::remove_child(tree_item_t *child) {
    parent_t::remove_child(child);
    supervisor.with_cluster([&]() {
        auto &folders = supervisor.get_cluster()->get_pending_folders();
        auto count = 0;
        for (auto &it : folders) {
            auto &f = *it.item;
            if (f.device_id() == peer.device_id()) {
                ++count;
                break;
            }
        }
        if (!count) {
            auto upper = static_cast<tree_item_t *>(parent());
            upper->remove_child(this);
        }
    });
}


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

#pragma once

#include "../tree_item.h"
#include "model/device.h"

namespace syncspirit::fltk::tree_item {

struct pending_folders_t : tree_item_t {
    using parent_t = tree_item_t;

    pending_folders_t(model::device_t &peer, app_supervisor_t &supervisor, Fl_Tree *tree);

    augmentation_ptr_t add_pending_folder(model::pending_folder_t &uf);

    void remove_folder(tree_item_t *item);
    void remove_child(tree_item_t *child) override;

    model::device_t &peer;
};

}; // namespace syncspirit::fltk::tree_item


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

#include "self_device.h"

#include "settings.h"
#include "../static_table.h"
#include "../qr_button.h"
#include "../utils.hpp"
#include "../main_window.h"
#include "utils/dns.h"
#include "constants.h"

#include <spdlog/fmt/fmt.h>
#include <FL/Fl.H>
#include <FL/Fl_Tile.H>

#include <lz4.h>
#include <openssl/crypto.h>
#include <openssl/opensslv.h>
#include <mdbx.h>
#include <boost/version.hpp>

using namespace syncspirit;
using namespace syncspirit::fltk;
using namespace syncspirit::fltk::tree_item;

namespace {

static void on_uptime_timeout(void *data);
static void on_db_refresh_timeout(void *data);

struct self_table_t final : static_table_t, db_info_viewer_t {
    using parent_t = static_table_t;
    using db_info_t = syncspirit::net::payload::db_info_response_t;

    self_table_t(main_window_t *owner_, int x, int y, int w, int h)
        : parent_t(x, y, w, h), owner{owner_}, db_info_guard(nullptr) {

        auto v = OPENSSL_VERSION_NUMBER;
        // clang-format off
        //                     0x1010113fL
        auto openssl_major  = (0xF0000000L & v) >> 7 * 4;
        auto openssl_minor  = (0x0FF00000L & v) >> 5 * 4;
        auto openssl_patch  = (0x000FF000L & v) >> 3 * 4;
        auto openssl_nibble = (0x00000FF0L & v) >> 1 * 4;
        // clang-format on
        auto openssl_nibble_c = static_cast<char>(openssl_nibble);
        if (openssl_nibble) {
            openssl_nibble_c += 'a' - 1;
        }

        auto openssl_version = fmt::format("{}.{}.{}{}", openssl_major, openssl_minor, openssl_patch, openssl_nibble_c);
        auto &mdbx_v = ::mdbx_version;
        auto mdbx_version = fmt::format("{}.{}.{}_{}", mdbx_v.major, mdbx_v.minor, mdbx_v.patch, mdbx_v.tweak);
        auto app_version = fmt::format("{} {}", constants::client_name, constants::client_version);
        auto fltk_version = fmt::format("{}", Fl::version());
        auto ares_version = std::string(utils::cares_version());
        auto boost_version = []() {
            auto maj = BOOST_VERSION / 100000;
            auto min = (BOOST_VERSION / 100 % 1000);
            auto patch = (BOOST_VERSION % 100);
            return fmt::format("{}.{}.{}", maj, min, patch);
        }();

        device_id_short_cell = new static_string_provider_t();
        device_id_cell = new static_string_provider_t();
        uptime_cell = new static_string_provider_t();
        rx_cell = new static_string_provider_t();
        tx_cell = new static_string_provider_t();
        mdbx_entries_cell = new static_string_provider_t();
        mdbx_pages_cell = new static_string_provider_t();
        mdbx_size_cell = new static_string_provider_t();

        auto data = table_rows_t();
        data.push_back({"device id (short)", device_id_short_cell});
        data.push_back({"device id", device_id_cell});
        data.push_back({"uptime", uptime_cell});
        data.push_back({"received", rx_cell});
        data.push_back({"send", tx_cell});
        data.push_back({"mdbx entries", mdbx_entries_cell});
        data.push_back({"mdbx pages", mdbx_pages_cell});
        data.push_back({"mdbx size, Kb", mdbx_size_cell});
        data.push_back({"app version", new static_string_provider_t(app_version)});
        data.push_back({"mdbx version", new static_string_provider_t(mdbx_version)});
        data.push_back({"boost version", new static_string_provider_t(boost_version)});
        data.push_back({"lz4 version", new static_string_provider_t(LZ4_versionString())});
        data.push_back({"openssl version", new static_string_provider_t(openssl_version)});
        data.push_back({"fltk version", new static_string_provider_t(fltk_version)});
        data.push_back({"ares version", new static_string_provider_t(ares_version)});

        assign_rows(std::move(data));

        update_db_info();
        Fl::add_timeout(1.0, on_uptime_timeout, this);
        Fl::add_timeout(10.0, on_db_refresh_timeout, this);
        refresh();
    }

    void refresh() override {
        auto sup = owner->get_supervisor();
        if (!sup) {
            return;
        }
        auto cluster = sup->get_cluster();
        auto rx_bytes = std::int64_t{0};
        auto tx_bytes = std::int64_t{0};

        auto device_id_short = std::string_view("XXXXXXX");
        auto device_id = std::string_view("XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX");
        if (cluster) {
            auto &self = cluster->get_device();
            auto &id = self->device_id();
            device_id_short = id.get_short();
            device_id = id.get_value();
            rx_bytes = static_cast<std::int64_t>(self->get_rx_bytes());
            tx_bytes = static_cast<std::int64_t>(self->get_tx_bytes());
        }
        auto pages = db_info.leaf_pages + db_info.ms_branch_pages + db_info.overflow_pages;
        auto size = pages * db_info.page_size / 1024;

        device_id_short_cell->update(device_id_short);
        device_id_cell->update(device_id);
        uptime_cell->update(sup->get_uptime());
        rx_cell->update(get_file_size(rx_bytes));
        tx_cell->update(get_file_size(tx_bytes));
        mdbx_entries_cell->update(fmt::format("{}", db_info.entries));
        mdbx_pages_cell->update(fmt::format("{}", pages));
        mdbx_size_cell->update(fmt::format("{}", size));
        redraw();
    }

    void view(const net::payload::db_info_response_t &res) override {
        db_info = res;
        refresh();
    }

    ~self_table_t() {
        db_info_guard.reset();
        Fl::remove_timeout(on_uptime_timeout, this);
        Fl::remove_timeout(on_db_refresh_timeout, this);
    }

    void update_db_info() {
        if (auto sup = owner->get_supervisor(); sup) {
            db_info_guard = sup->request_db_info(this);
        }
    }

    main_window_t *owner;
    db_info_viewer_guard_t db_info_guard;
    db_info_t db_info;
    static_string_provider_ptr_t device_id_short_cell;
    static_string_provider_ptr_t device_id_cell;
    static_string_provider_ptr_t uptime_cell;
    static_string_provider_ptr_t rx_cell;
    static_string_provider_ptr_t tx_cell;
    static_string_provider_ptr_t mdbx_entries_cell;
    static_string_provider_ptr_t mdbx_pages_cell;
    static_string_provider_ptr_t mdbx_size_cell;
};

static void on_uptime_timeout(void *data) {
    auto self = reinterpret_cast<self_table_t *>(data);
    self->refresh();
    Fl::repeat_timeout(1.0, on_uptime_timeout, data);
}
static void on_db_refresh_timeout(void *data) {
    auto self = reinterpret_cast<self_table_t *>(data);
    self->update_db_info();
    Fl::repeat_timeout(10.0, on_db_refresh_timeout, data);
}

} // namespace

self_device_t::self_device_t(model::device_t &, app_supervisor_t &supervisor, Fl_Tree *tree)
    : parent_t(supervisor, tree) {
    add(prefs(), "settings", new settings_t(supervisor, tree));
    update_label();
}

void self_device_t::update_label() {
    auto self = supervisor.get_cluster()->get_device();
    auto device_id = self->device_id().get_short();
    auto label = fmt::format("(self) {}, {}", supervisor.get_app_config().device_name, device_id);
    this->label(label.data());
}

bool self_device_t::on_select() {
    content = supervisor.replace_content([&](content_t *content) -> content_t * {
        struct tile_t : contentable_t<Fl_Tile> {
            using parent_t = contentable_t<Fl_Tile>;
            using parent_t::parent_t;
        };

        auto prev = content->get_widget();
        int x = prev->x(), y = prev->y(), w = prev->w(), h = prev->h();
        int bot_h = 100;

        // auto group = new Fl_Group(x, y, w, h);
        auto group = new tile_t(x, y, w, h);
        auto resizable_area = new Fl_Box(x + w * 1. / 6, y + h * 1. / 2, w * 4. / 6, h / 2. - bot_h);
        group->resizable(resizable_area);

        group->begin();
        auto top = new self_table_t(supervisor.get_main_window(), x, y, w, h - bot_h);
        auto bot = [&]() -> Fl_Widget * {
            auto &device = supervisor.get_cluster()->get_device()->device_id();
            return new qr_button_t(device, supervisor, x, y + top->h(), w, bot_h);
        }();
        group->add(top->get_widget());
        group->add(bot);
        group->end();
        return group;
    });
    return true;
}


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

#pragma once

#include "../tree_item.h"
#include "FL/Fl_Widget.H"

namespace syncspirit::fltk::tree_item {

struct self_device_t final : tree_item_t {
    using parent_t = tree_item_t;
    self_device_t(model::device_t &self, app_supervisor_t &supervisor, Fl_Tree *tree);

    bool on_select() override;
    void update_label() override;
};

} // namespace syncspirit::fltk::tree_item


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

#include "settings.h"
#include "../config/control.h"
#include <spdlog/fmt/fmt.h>

using namespace syncspirit::fltk::tree_item;

settings_t::settings_t(app_supervisor_t &supervisor, Fl_Tree *tree) : parent_t(supervisor, tree, false) {
    update_label(false);
}

bool settings_t::on_select() {
    content = supervisor.replace_content([&](content_t *content) -> content_t * {
        auto prev = content->get_widget();
        content = new config::control_t(*this, prev->x(), prev->y(), prev->w(), prev->h());
        return content;
    });
    return true;
}

void settings_t::update_label(bool modified) {
    auto l = std::string("settings");
    if (modified) {
        l = fmt::format("({}) {}", "*", l);
    }
    label(l.data());
    tree()->redraw();
}


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

#pragma once

#include "../tree_item.h"

namespace syncspirit::fltk::tree_item {

struct settings_t : tree_item_t {
    using parent_t = tree_item_t;
    settings_t(app_supervisor_t &supervisor, Fl_Tree *tree);

    void update_label(bool modified);
    bool on_select() override;
};

} // namespace syncspirit::fltk::tree_item


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

#include "tree_view.h"
#include "tree_item/devices.h"
#include "tree_item/folders.h"
#include "tree_item/ignored_devices.h"
#include "tree_item/pending_devices.h"

#include <FL/Fl.H>

using namespace syncspirit::fltk;

static void tree_view_callback(Fl_Widget *w, void *) {
    auto tree = reinterpret_cast<tree_view_t *>(w);
    auto item = static_cast<tree_item_t *>(tree->callback_item());
    switch (tree->callback_reason()) {
    case FL_TREE_REASON_SELECTED:
        if (tree->current != item) {
            tree->current = item;
            item->on_select();
        }
        break;
    case FL_TREE_REASON_DESELECTED:
        item->on_deselect();
        tree->current = nullptr;
        break;
        /*
      case FL_TREE_REASON_RESELECTED: [..]
        */
    case FL_TREE_REASON_OPENED:
        item->on_open();
        break;
    case FL_TREE_REASON_CLOSED:
        item->on_close();
        break;
    default:
        /* NO-OP */;
    }
}

tree_view_t::tree_view_t(app_supervisor_t &supervisor_, int x, int y, int w, int h)
    : parent_t(x, y, w, h), supervisor{supervisor_}, current{nullptr} {
    resizable(this);
    showroot(false);
    auto folders_node = new tree_item::folders_t(supervisor, this);
    auto devices_node = new tree_item::devices_t(supervisor, this);
    auto pending_devices_node = new tree_item::pending_devices_t(supervisor, this);
    auto ignored_devices_node = new tree_item::ignored_devices_t(supervisor, this);

    add(folders_node->label(), folders_node);
    add(devices_node->label(), devices_node);
    add(pending_devices_node->label(), pending_devices_node);
    add(ignored_devices_node->label(), ignored_devices_node);

    callback(tree_view_callback);
}


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

#pragma once

#include "app_supervisor.h"
#include <FL/Fl_Tree.H>

namespace syncspirit::fltk {

struct tree_view_t : Fl_Tree {
    using parent_t = Fl_Tree;
    tree_view_t(app_supervisor_t &supervisor, int x, int y, int w, int h);

    app_supervisor_t &supervisor;
    Fl_Tree_Item *current;
};

} // namespace syncspirit::fltk


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

#include "utils.hpp"
#include <fmt/format.h>
#include <array>
#include <string_view>

namespace syncspirit::fltk {

static std::array<std::string_view, 5> suffix = {
    "b", "Kb", "Mb", "Gb", "Tb",
};

std::string get_file_size(std::int64_t value) {
    auto v = double(value);
    size_t i = 0;
    for (; i < suffix.size() && v > 1024; ++i) {
        v /= 1024.0;
    }
    if (i) {
        return fmt::format("{:.2f}{} ({}b)", v, suffix[i], value);
    }
    return fmt::format("{}b", value);
}

} // namespace syncspirit::fltk


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

#pragma once

#include <string_view>
#include <algorithm>
#include <cstdint>

namespace syncspirit::fltk {

std::string get_file_size(std::int64_t value);

template <typename NameProvider>
int bisect(std::string_view new_label, int start_index, int end_index, int children_count,
           NameProvider &&name_provider) {
    end_index = std::min(children_count - 1, end_index);
    if (end_index < 0) {
        return 0;
    }
    if (start_index > end_index) {
        return start_index;
    }

    auto right = name_provider(end_index);
    if (new_label > right) {
        return end_index + 1;
    } else if (new_label >= right) {
        return end_index;
    }
    auto left = name_provider(start_index);
    if (left >= new_label) {
        return start_index;
    }

    // int pos = start_index;
    while (start_index <= end_index) {
        auto left = name_provider(start_index);
        if (left >= new_label) {
            return start_index;
        }
        auto right = name_provider(end_index);
        if (right <= new_label) {
            return end_index;
        }

        auto mid_index = (start_index + end_index) / 2;
        auto mid = name_provider(mid_index);
        if (mid > new_label) {
            end_index = mid_index;
        } else {
            if (mid_index == start_index) {
                return start_index + 1;
            }
            start_index = mid_index;
        }
    }
    return start_index;
}

} // namespace syncspirit::fltk


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

#pragma once

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

namespace syncspirit::utils {

namespace outcome = boost::outcome_v2;

struct SYNCSPIRIT_API base32 {

    static const char in_alphabet[];
    static const std::int32_t out_alphabet[];

    static inline std::size_t encoded_size(std::size_t dec_len) noexcept { return (dec_len + 4) / 5 * 8; }
    static inline std::size_t decoded_size(std::size_t dec_len) noexcept { return dec_len * 5 / 8; }

    static std::string encode(bytes_view_t input) noexcept;
    static outcome::result<bytes_t> decode(std::string_view input) noexcept;
};

} // namespace syncspirit::utils


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

#include "beast_support.h"
#include <boost/beast/version.hpp>

namespace asio = boost::asio;

namespace syncspirit::utils {

template <typename Request> void set_ua(Request &req) noexcept {
    req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
}

outcome::result<void> serialize(http::request<http::empty_body> &req, bytes_t &buff) {

    set_ua(req);
    auto serializer = http::serializer<true, http::empty_body>(req);
    serializer.split(false);

    sys::error_code ec;
    serializer.next(ec, [&](auto ec, const auto &buff_seq) {
        if (!ec) {
            auto sz = buffer_size(buff_seq);
            buff.resize(sz);
            buffer_copy(asio::mutable_buffer(buff.data(), sz), buff_seq);
        }
    });
    if (ec) {
        return ec;
    }
    return outcome::success();
}

outcome::result<void> serialize(http::request<http::string_body> &req, bytes_t &buff) {
    set_ua(req);

    sys::error_code ec;
    auto serializer = http::serializer<true, http::string_body>(req);
    serializer.next(ec, [&](auto ec, const auto &buff_seq) {
        if (!ec) {
            auto sz = buffer_size(buff_seq);
            buff.resize(sz);
            buffer_copy(asio::mutable_buffer(buff.data(), sz), buff_seq);
        }
    });
    if (ec) {
        return ec;
    }
    return outcome::success();
}

} // namespace syncspirit::utils


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

#pragma once

#include <boost/beast/http.hpp>
#include <boost/outcome.hpp>
#include "utils/bytes.h"
#include "syncspirit-export.h"

namespace syncspirit::utils {
namespace sys = boost::system;
namespace outcome = boost::outcome_v2;
namespace http = boost::beast::http;

SYNCSPIRIT_API outcome::result<void> serialize(http::request<http::empty_body> &req, utils::bytes_t &buff);

SYNCSPIRIT_API outcome::result<void> serialize(http::request<http::string_body> &req, utils::bytes_t &buff);

} // namespace syncspirit::utils


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

#include "bytes.h"
#include <algorithm>

namespace syncspirit::utils {

bytes_view_t::bytes_view_t(const bytes_t &bytes) noexcept : parent_t(bytes.begin(), bytes.end()) {}

bool bytes_view_t::operator==(const bytes_view_t &other) const noexcept {
    return std::equal(begin(), end(), other.begin(), other.end());
}

bytes_t::bytes_t(parent_t data) noexcept : parent_t(std::move(data)) {}

bytes_t::bytes_t(bytes_view_t data) noexcept : parent_t(data.begin(), data.end()) {}

bool bytes_t::operator==(const bytes_view_t &other) const noexcept {
    return std::equal(begin(), end(), other.begin(), other.end());
}

bool bytes_t::operator==(const bytes_t &other) const noexcept {
    return std::equal(begin(), end(), other.begin(), other.end());
}

bytes_t &bytes_t::operator=(bytes_view_t other) noexcept {
    auto self = &static_cast<parent_t &>(*this);
    *self = parent_t(other.begin(), other.end());
    return *this;
}

} // namespace syncspirit::utils


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

#pragma once

#include <span>
#include <vector>
#include <string_view>
#include "syncspirit-export.h"

namespace syncspirit::utils {

struct SYNCSPIRIT_API bytes_t;

struct SYNCSPIRIT_API bytes_view_t : std::span<const unsigned char> {
    using parent_t = std::span<const unsigned char>;
    using parent_t::parent_t;
    bytes_view_t(const bytes_t &) noexcept;

    inline bytes_view_t subspan(size_type offset, size_type count = std::dynamic_extent) const noexcept {
        auto sz = count == std::dynamic_extent ? size() - offset : count;
        return bytes_view_t(data() + offset, sz);
    }

    bool operator==(const bytes_view_t &) const noexcept;
};

struct SYNCSPIRIT_API bytes_t : std::vector<unsigned char> {
    using parent_t = std::vector<unsigned char>;
    using parent_t::parent_t;
    using parent_t::operator=;
    bytes_t(parent_t data) noexcept;
    explicit bytes_t(bytes_view_t data) noexcept;

    bytes_t &operator=(bytes_view_t) noexcept;
    bool operator==(const bytes_view_t &) const noexcept;
    bool operator==(const bytes_t &) const noexcept;
};

} // namespace syncspirit::utils

namespace std {

template <> struct hash<syncspirit::utils::bytes_view_t> {
    inline size_t operator()(syncspirit::utils::bytes_view_t bytes) const noexcept {
        auto ptr = (const char *)bytes.data();
        auto str = std::string_view(ptr, bytes.size());
        return std::hash<std::string_view>()(str);
    }
};

template <> struct hash<syncspirit::utils::bytes_t> {
    inline size_t operator()(const syncspirit::utils::bytes_t &bytes) const noexcept {
        auto ptr = (const char *)bytes.data();
        auto str = std::string_view(ptr, bytes.size());
        return std::hash<std::string_view>()(str);
    }
};

} // namespace std


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

#pragma once

#include <algorithm>

namespace syncspirit::utils {

struct bytes_comparator_t {
    using is_transparent = void;

    template <typename T1, typename T2> bool operator()(const T1 &k1, const T2 &k2) const {
        return std::lexicographical_compare(k1.begin(), k1.end(), k2.begin(), k2.end());
    }
};

} // namespace syncspirit::utils


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

#include "dns.h"

#include <cctype>
#include <charconv>
#include <ares.h>
#include <boost/asio/ip/address.hpp>

namespace syncspirit::utils {

using size_t = typename std::string::size_type;
using tcp = boost::asio::ip::tcp;
using udp = boost::asio::ip::udp;

std::vector<endpoint_t> parse_dns_servers(std::string_view str) noexcept {
    std::vector<endpoint_t> r;
    auto begin = size_t{0};
    auto end = str.find(',', begin);
    while (begin != std::string::npos) {
        auto full_addr = str.substr(begin, end - begin);
        begin = (end == std::string::npos) ? std::string::npos : end + 1;
        if (end == std::string::npos) {
            begin = std::string::npos;
        } else {
            begin = end + 1;
            end = str.find(',', begin);
        }
        auto colon_start = full_addr.rfind(':');
        size_t addr_start = 0;
        size_t addr_end = colon_start;
        if (full_addr.size() && full_addr[0] == '[') {
            if (auto pos = full_addr.rfind(']'); pos != std::string::npos) {
                addr_start = 1;
                addr_end = pos - 1;
            }
        }

        auto ip_str = full_addr.substr(addr_start, addr_end);
        auto ec = boost::system::error_code{};
        auto ip_addr = boost::asio::ip::make_address(ip_str, ec);
        if (ec) {
            continue;
        }
        std::uint16_t port = 53;
        if (colon_start != std::string::npos) {
            auto port_start = colon_start + 1;
            auto port_end = port_start;
            while (port_end < full_addr.size() && std::isdigit(full_addr[port_end])) {
                ++port_end;
            }
            auto port_str = full_addr.substr(port_start, port_end - port_start);
            uint16_t p;
            auto result = std::from_chars(port_str.begin(), port_str.end(), p);
            if (result.ec == std::errc()) {
                port = p;
            }
        }
        r.emplace_back(endpoint_t{ip_addr, port});
    }
    return r;
}

template <typename Protocol, typename Endpoint>
auto make_endpoints(const std::vector<ip::address> &addresses, std::uint16_t port) noexcept -> addresses_t<Endpoint> {
    using R = std::vector<Endpoint>;
    R r;
    r.reserve(addresses.size());
    for (auto &ip : addresses) {
        r.emplace_back(ip, port);
    }
    return std::make_shared<R>(std::move(r));
}

template auto make_endpoints<tcp, typename tcp::endpoint>(const std::vector<ip::address> &addresses,
                                                          std::uint16_t port) noexcept -> addresses_t<tcp::endpoint>;

std::string_view cares_version() noexcept { return std::string_view(::ares_version(nullptr)); }

} // namespace syncspirit::utils


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

#pragma once

#include <vector>
#include <string>
#include <cstdint>
#include <boost/asio.hpp>
#include <memory>
#include "syncspirit-export.h"

namespace syncspirit::utils {

namespace ip = boost::asio::ip;

struct SYNCSPIRIT_API endpoint_t {
    ip::address ip;
    std::uint16_t port;
    bool operator==(const endpoint_t &other) const noexcept { return ip == other.ip && port == other.port; }
};

struct SYNCSPIRIT_API dns_query_t {
    std::string host;
    bool operator==(const dns_query_t &other) const noexcept = default;
};

using endpoints_t = std::vector<endpoint_t>;

template <typename Endpoint> using addresses_t = std::shared_ptr<std::vector<Endpoint>>;

template <typename Protocol, typename Endpoint = typename Protocol::endpoint>
addresses_t<Endpoint> make_endpoints(const std::vector<ip::address> &addresses, std::uint16_t port) noexcept;

} // namespace syncspirit::utils

namespace std {
using dns_query_t = syncspirit::utils::dns_query_t;

template <> struct hash<dns_query_t> {
    inline size_t operator()(const dns_query_t &query) const noexcept { return std::hash<std::string>()(query.host); }
};

} // namespace std

namespace syncspirit::utils {

SYNCSPIRIT_API std::vector<endpoint_t> parse_dns_servers(std::string_view str) noexcept;

std::string_view SYNCSPIRIT_API cares_version() noexcept;

} // namespace syncspirit::utils


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

#include "error_code.h"
#include <map>

namespace syncspirit::utils::detail {

const char *error_code_category::name() const noexcept { return "syncspirit_error"; }

const char *bep_error_code_category::name() const noexcept { return "syncspirit_bep_error"; }

const char *protocol_error_code_category::name() const noexcept { return "syncspirit_proto_error"; }

const char *request_error_code_category::name() const noexcept { return "syncspirit_request_error"; }

std::string error_code_category::message(int c) const {
    std::string r;
    switch (static_cast<error_code_t>(c)) {
    case error_code_t::success:
        r = "success";
        break;
    case error_code_t::no_location:
        r = "no location";
        break;
    case error_code_t::incomplete_discovery_reply:
        r = "incomplete discovery reply";
        break;
    case error_code_t::no_st:
        r = "no st (search target)";
        break;
    case error_code_t::no_usn:
        r = "no usn";
        break;
    case error_code_t::igd_mismatch:
        r = "IGD (InternetGatewayDevice) mismatch";
        break;
    case error_code_t::xml_parse_error:
        r = "Error parsing xml";
        break;
    case error_code_t::wan_notfound:
        r = "WAN device description was not found in the XML";
        break;
    case error_code_t::timed_out:
        r = "timeout occurred";
        break;
    case error_code_t::service_not_available:
        r = "service not available";
        break;
    case error_code_t::cant_determine_config_dir:
        r = "config dir cannot be determined";
        break;
    case error_code_t::unexpected_response_code:
        r = "unexpected response code";
        break;
    case error_code_t::negative_reannounce_interval:
        r = "negative reannounce interval";
        break;
    case error_code_t::malformed_json:
        r = "malformed json";
        break;
    case error_code_t::incorrect_json:
        r = "incorrect json";
        break;
    case error_code_t::malformed_url:
        r = "malformed url";
        break;
    case error_code_t::malformed_date:
        r = "malformed date";
        break;
    case error_code_t::transport_not_available:
        r = "transport is not available";
        break;
    case error_code_t::rx_timeout:
        r = "rx timeout";
        break;
    case error_code_t::tx_timeout:
        r = "tx timeout";
        break;
    case error_code_t::announce_failed:
        r = "announce failed";
        break;
    case error_code_t::discovery_failed:
        r = "discovery failed";
        break;
    case error_code_t::endpoint_failed:
        r = "endpoint failed";
        break;
    case error_code_t::portmapping_failed:
        r = "port mapping failed";
        break;
    case error_code_t::unknown_sink:
        r = "unknown sink";
        break;
    case error_code_t::misconfigured_default_logger:
        r = "default logger is missing or has no sinks";
        break;
    case error_code_t::already_shared:
        r = "folder is already shared with the peer";
        break;
    case error_code_t::connection_impossible:
        r = "cannot establish connection to the peer";
        break;
    case error_code_t::already_connected:
        r = "peer is already connected";
        break;
    case error_code_t::cannot_get_public_relays:
        r = "cannot get public relays";
        break;
    case error_code_t::protocol_error:
        r = "protocol error";
        break;
    case error_code_t::relay_failure:
        r = "relay failure";
        break;
    case error_code_t::invalid_deviceid:
        r = "invalid device id";
        break;
    case error_code_t::cares_failure:
        r = "cares failure";
        break;
    case error_code_t::peer_has_been_removed:
        r = "peer has been removed";
        break;
    default:
        r = "unknown";
    }
    r += " (";
    r += std::to_string(c) + ")";
    return r;
}

std::string bep_error_code_category::message(int c) const {
    std::string r;
    switch (static_cast<bep_error_code_t>(c)) {
    case bep_error_code_t::success:
        r = "success";
        break;
    case bep_error_code_t::protobuf_err:
        r = "error parsing protobuf message";
        break;
    case bep_error_code_t::unexpected_message:
        r = "unexpected message";
        break;
    case bep_error_code_t::unexpected_response:
        r = "response without request";
        break;
    default:
        r = "unknown";
    }
    r += " (";
    r += std::to_string(c) + ")";
    return r;
}

std::string protocol_error_code_category::message(int c) const {
    std::string r;
    switch (static_cast<protocol_error_code_t>(c)) {
    case protocol_error_code_t::success:
        r = "success";
        break;
    case protocol_error_code_t::unknown_folder:
        r = "unknown folder";
        break;
    case protocol_error_code_t::digest_mismatch:
        r = "digest mismatch";
        break;
    default:
        r = "unknown";
    }
    r += " (";
    r += std::to_string(c) + ")";
    return r;
}

std::string request_error_code_category::message(int c) const {
    std::string r;
    switch (static_cast<request_error_code_t>(c)) {
    case request_error_code_t::success:
        r = "success";
        break;
    case request_error_code_t::generic:
        r = "generic error";
        break;
    case request_error_code_t::no_such_file:
        r = "no such a file";
        break;
    case request_error_code_t::invalid_file:
        r = "invalid file";
        break;
    default:
        r = "unknown";
    }
    r += " (";
    r += std::to_string(c) + ")";
    return r;
}

} // namespace syncspirit::utils::detail

namespace syncspirit::utils {

const static detail::error_code_category category;
const static detail::bep_error_code_category bep_category;
const static detail::protocol_error_code_category protocol_category;
const static detail::request_error_code_category request_category;

const detail::error_code_category &error_code_category() { return category; }
const detail::bep_error_code_category &bep_error_code_category() { return bep_category; }
const detail::protocol_error_code_category &protocol_error_code_category() { return protocol_category; }
const detail::request_error_code_category &request_error_code_category() { return request_category; }

boost::system::error_code adapt(const std::error_code &ec) noexcept {
    struct category_adapter_t : public boost::system::error_category {
        category_adapter_t(const std::error_category &category) : m_category(category) {}

        const char *name() const noexcept { return m_category.name(); }

        std::string message(int ev) const { return m_category.message(ev); }

      private:
        const std::error_category &m_category;
    };

    using map_t = std::map<std::string, category_adapter_t>;
    static thread_local map_t name2cat;
    auto result = name2cat.emplace(ec.category().name(), ec.category());
    auto &category = result.first->second;
    return boost::system::error_code(ec.value(), category);
}

} // namespace syncspirit::utils


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

#pragma once

#include <string>
#include <system_error>
#include <boost/system/error_code.hpp>
#include "syncspirit-export.h"

namespace syncspirit::utils {

enum class error_code_t {
    success = 0,
    incomplete_discovery_reply,
    no_location,
    no_st,
    no_usn,
    igd_mismatch,
    xml_parse_error,
    wan_notfound,
    timed_out,
    service_not_available,
    cant_determine_config_dir,
    tls_context_init_failure,
    tls_param_init_failure,
    tls_param_gen_failure,
    tls_key_gen_init_failure,
    tls_key_gen_failure,
    tls_ec_curve_failure,
    tls_ec_group_failure,
    tls_cert_set_failure,
    tls_cert_ext_failure,
    tls_cert_sign_failure,
    tls_cert_save_failure,
    tls_cert_load_failure,
    tls_key_save_failure,
    tls_key_load_failure,
    tls_sha256_init_failure,
    tls_sha256_failure,
    tls_cn_missing,
    base32_decoding_failure,
    unexpected_response_code,
    negative_reannounce_interval,
    malformed_json,
    incorrect_json,
    malformed_url,
    malformed_date,
    transport_not_available,
    wrong_magic,
    cannot_connect_to_peer,
    announce_failed,
    discovery_failed,
    endpoint_failed,
    portmapping_failed,
    connection_impossible,
    missing_device_id,
    missing_cn,
    rx_limit_reached,
    non_authorized,
    igd_description_failed,
    unparsable_control_url,
    external_ip_failed,
    rx_timeout,
    tx_timeout,
    fs_error,
    scan_aborted,
    already_shared,
    unknown_sink,
    misconfigured_default_logger,
    already_connected,
    cannot_get_public_relays,
    protocol_error,
    relay_failure,
    invalid_deviceid,
    cares_failure,
    peer_has_been_removed,
};

enum class bep_error_code_t {
    success = 0,
    protobuf_err,
    lz4_decoding,
    unexpected_message,
    unexpected_response,
    response_mismatch,
    response_missize,
};

enum class protocol_error_code_t {
    success = 0,
    unknown_folder,
    digest_mismatch,
};

enum class request_error_code_t {
    success = 0,
    generic = 1,
    no_such_file = 2,
    invalid_file = 3,
};

namespace detail {

class SYNCSPIRIT_API error_code_category : public boost::system::error_category {
    virtual const char *name() const noexcept override;
    virtual std::string message(int c) const override;
};

class SYNCSPIRIT_API bep_error_code_category : public boost::system::error_category {
    virtual const char *name() const noexcept override;
    virtual std::string message(int c) const override;
};

class SYNCSPIRIT_API protocol_error_code_category : public boost::system::error_category {
    virtual const char *name() const noexcept override;
    virtual std::string message(int c) const override;
};

class SYNCSPIRIT_API request_error_code_category : public boost::system::error_category {
    virtual const char *name() const noexcept override;
    virtual std::string message(int c) const override;
};

} // namespace detail

SYNCSPIRIT_API const detail::error_code_category &error_code_category();

SYNCSPIRIT_API const detail::bep_error_code_category &bep_error_code_category();

SYNCSPIRIT_API const detail::protocol_error_code_category &protocol_error_code_category();

SYNCSPIRIT_API const detail::request_error_code_category &request_error_code_category();

inline boost::system::error_code make_error_code(error_code_t e) {
    return {static_cast<int>(e), error_code_category()};
}
inline boost::system::error_code make_error_code(bep_error_code_t e) {
    return {static_cast<int>(e), bep_error_code_category()};
}

inline boost::system::error_code make_error_code(protocol_error_code_t e) {
    return {static_cast<int>(e), protocol_error_code_category()};
}

inline boost::system::error_code make_error_code(request_error_code_t e) {
    return {static_cast<int>(e), request_error_code_category()};
}

SYNCSPIRIT_API boost::system::error_code adapt(const std::error_code &ec) noexcept;

} // namespace syncspirit::utils

namespace std {
template <> struct is_error_code_enum<syncspirit::utils::error_code_t> : std::true_type {};
} // namespace std

namespace boost {
namespace system {

template <> struct is_error_code_enum<syncspirit::utils::error_code_t> : std::true_type {
    static const bool value = true;
};

template <> struct is_error_code_enum<syncspirit::utils::bep_error_code_t> : std::true_type {
    static const bool value = true;
};

template <> struct is_error_code_enum<syncspirit::utils::protocol_error_code_t> : std::true_type {
    static const bool value = true;
};

template <> struct is_error_code_enum<syncspirit::utils::request_error_code_t> : std::true_type {
    static const bool value = true;
};

} // namespace system
} // namespace boost