Files
syncspirit/tests/062-presentation.cpp

1931 lines
92 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2026 Ivan Baidakou
#include "test-utils.h"
#include "diff-builder.h"
#include "model/cluster.h"
#include "presentation/cluster_file_presence.h"
#include "presentation/file_presence.h"
#include "presentation/folder_presence.h"
#include "presentation/folder_entity.h"
#include "syncspirit-config.h"
#include <memory_resource>
using namespace syncspirit;
using namespace syncspirit::test;
using namespace syncspirit::utils;
using namespace syncspirit::proto;
using namespace syncspirit::model;
using namespace syncspirit::presentation;
using F = file_presence_t::features_t;
namespace Catch {
template <> struct StringMaker<presence_stats_t> {
static std::string convert(const presence_stats_t &value) {
return fmt::format("presence state, entities[{}, local: {}, cluster: {}], size = {}", value.entities,
value.local_entries, value.cluster_entries, value.size);
}
};
template <> struct StringMaker<entity_stats_t> {
static std::string convert(const entity_stats_t &value) {
return fmt::format("entity stats entities [{}], size = {}", value.entities, value.size);
}
};
} // namespace Catch
struct aug_sample_t : model::augmentation_t {
aug_sample_t(int &deleted_, int &updated_) : deleted{deleted_}, updated{updated_} {}
void on_update() noexcept { ++updated; };
void on_delete() noexcept { ++deleted; };
int &deleted;
int &updated;
};
auto my_id = device_id_t::from_string("KHQNO2S-5QSILRK-YX4JZZ4-7L77APM-QNVGZJT-EKU7IFI-PNEPBMY-4MXFMQD").value();
auto peer_id = device_id_t::from_string("VUV42CZ-IQD5A37-RPEBPM4-VVQK6E4-6WSKC7B-PVJQHHD-4PZD44V-ENC6WAZ").value();
auto peer_2_id =
model::device_id_t::from_string("LYXKCHX-VI3NYZR-ALCJBHF-WMZYSPK-QG6QJA3-MPFYMSO-U56GTUK-NA2MIAW").value();
auto my_device = device_t::create(my_id, "my-device").value();
auto peer_device = device_t::create(peer_id, "peer-device").value();
auto peer_2_device = device_t::create(peer_2_id, "peer-device-2").value();
auto my_device_id = my_device->device_id().get_uint();
auto peer_device_id = peer_device->device_id().get_uint();
TEST_CASE("presentation", "[presentation]") {
auto cluster = cluster_ptr_t(new cluster_t(my_device, 1));
auto sequencer = make_sequencer(4);
cluster->get_devices().put(my_device);
cluster->get_devices().put(peer_device);
cluster->get_devices().put(peer_2_device);
auto builder = diff_builder_t(*cluster);
SECTION("empty folder") {
SECTION("shared with nobody") {
REQUIRE(builder.upsert_folder("1234-5678", "some/path", "my-label").apply());
auto folder = cluster->get_folders().by_id("1234-5678");
CHECK(folder->use_count() == 2);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(&folder_entity->get_folder() == folder.get());
CHECK(folder_entity->get_children().size() == 0);
CHECK(!folder_entity->get_parent());
SECTION("check forwarding") {
int deleted = 0;
int updated = 0;
auto aug = model::augmentation_ptr_t(new aug_sample_t(deleted, updated));
folder_entity->set_augmentation(aug);
REQUIRE(folder->use_count() == 2);
folder->notify_update();
CHECK(updated == 1);
CHECK(deleted == 0);
REQUIRE(folder->use_count() == 2);
REQUIRE(folder_entity->use_count() >= 2);
folder_entity.reset();
CHECK(updated == 1);
CHECK(deleted == 0);
SECTION("direct remvoal") {
folder.reset();
cluster->get_folders().clear();
CHECK(updated == 1);
CHECK(deleted == 1);
}
SECTION("remove via diff") {
REQUIRE(builder.remove_folder(*folder).apply());
folder.reset();
CHECK(updated == 1);
CHECK(deleted == 1);
}
}
SECTION("presence") {
CHECK(!folder_entity->get_presence(&*peer_device));
CHECK(!folder_entity->get_presence(&*peer_2_device));
auto self_presense = static_cast<folder_presence_t *>(folder_entity->get_presence(&*my_device));
CHECK(self_presense);
CHECK(!self_presense->get_parent());
CHECK(self_presense->get_features() & (F::folder));
auto my_fi = folder->get_folder_infos().by_device(*my_device);
CHECK(&self_presense->get_folder_info() == my_fi);
}
}
SECTION("shared with a peer") {
REQUIRE(builder.upsert_folder("1234-5678", "some/path", "my-label").apply());
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto folder = cluster->get_folders().by_id("1234-5678");
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
auto my_presense = static_cast<folder_presence_t *>(folder_entity->get_presence(&*my_device));
auto peer_presense = static_cast<folder_presence_t *>(folder_entity->get_presence(&*peer_device));
REQUIRE(my_presense);
REQUIRE(peer_presense);
CHECK(!folder_entity->get_presence(&*peer_2_device));
auto my_fi = folder->get_folder_infos().by_device(*my_device);
auto peer_fi = folder->get_folder_infos().by_device(*peer_device);
CHECK(&my_presense->get_folder_info() == my_fi);
CHECK(&peer_presense->get_folder_info() == peer_fi);
CHECK(my_presense->get_features() & (F::folder));
CHECK(peer_presense->get_features() & (F::folder));
CHECK(!my_presense->get_parent());
CHECK(!peer_presense->get_parent());
}
}
SECTION("non-emtpy folder, flat hierarchy") {
REQUIRE(builder.upsert_folder("1234-5678", "some/path", "my-label").apply());
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
REQUIRE(builder.share_folder(peer_2_id.get_sha256(), "1234-5678").apply());
auto folder = cluster->get_folders().by_id("1234-5678");
auto add_file = [&](std::string_view name, model::device_t &device) {
auto folder_info = folder->get_folder_infos().by_device(device);
proto::FileInfo pr_fi;
proto::set_name(pr_fi, name);
proto::set_sequence(pr_fi, folder_info->get_max_sequence() + 1);
auto &v = proto::get_version(pr_fi);
proto::add_counters(v, proto::Counter(peer_device->device_id().get_uint(), 1));
auto file = model::file_info_t::create(sequencer->next_uuid(), pr_fi, folder_info.get()).value();
folder_info->add_strict(file);
};
add_file("f.txt", *peer_device);
add_file("f.txt", *peer_2_device);
add_file("e.txt", *peer_2_device);
add_file("d.txt", *peer_device);
add_file("c.txt", *my_device);
add_file("c.txt", *peer_device);
add_file("c.txt", *peer_2_device);
add_file("b.txt", *my_device);
add_file("b.txt", *peer_device);
add_file("a.txt", *my_device);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
auto my_folder_presence = folder_entity->get_presence(&*my_device);
auto peer_folder_presence = folder_entity->get_presence(&*peer_device);
auto peer_2_folder_presence = folder_entity->get_presence(&*peer_2_device);
auto &children = folder_entity->get_children();
REQUIRE(children.size() == 6);
auto it = children.begin();
auto next_entity = [&]() -> presentation::entity_ptr_t {
if (it != children.end()) {
return *it++;
}
return {};
};
auto file_a = next_entity();
auto file_b = next_entity();
auto file_c = next_entity();
auto file_d = next_entity();
auto file_e = next_entity();
auto file_f = next_entity();
REQUIRE(!next_entity());
CHECK(file_a->get_path()->get_own_name() == "a.txt");
CHECK(file_b->get_path()->get_own_name() == "b.txt");
CHECK(file_c->get_path()->get_own_name() == "c.txt");
CHECK(file_d->get_path()->get_own_name() == "d.txt");
CHECK(file_e->get_path()->get_own_name() == "e.txt");
CHECK(file_f->get_path()->get_own_name() == "f.txt");
CHECK(file_a->get_parent() == folder_entity);
CHECK(file_b->get_parent() == folder_entity);
CHECK(file_c->get_parent() == folder_entity);
CHECK(file_d->get_parent() == folder_entity);
CHECK(file_e->get_parent() == folder_entity);
CHECK(file_f->get_parent() == folder_entity);
SECTION("only my device has a file (a.txt)") {
auto f_my = static_cast<cluster_file_presence_t *>(file_a->get_presence(&*my_device));
auto f_peer = static_cast<file_presence_t *>(file_a->get_presence(&*peer_device));
auto f_peer_2 = static_cast<file_presence_t *>(file_a->get_presence(&*peer_2_device));
REQUIRE(f_my);
REQUIRE(f_peer);
REQUIRE(f_peer_2);
REQUIRE(f_my->get_device() == my_device.get());
CHECK(f_my->get_features() & (F::cluster | F::local | F::file));
CHECK(f_peer->get_features() & (F::missing | F::file));
CHECK(f_peer_2->get_features() & (F::missing | F::file));
CHECK(f_peer == f_peer_2);
CHECK(f_my->get_parent() == my_folder_presence);
CHECK(!f_peer->get_parent());
}
SECTION("my & peer device have a file (b.txt)") {
auto f_my = static_cast<cluster_file_presence_t *>(file_b->get_presence(&*my_device));
auto f_peer = file_b->get_presence(&*peer_device);
auto f_peer_2 = file_b->get_presence(&*peer_2_device);
REQUIRE(f_my);
REQUIRE(f_peer);
REQUIRE(f_peer_2);
REQUIRE(f_my->get_device() == my_device.get());
CHECK(f_my->get_features() & (F::cluster | F::local | F::file));
CHECK(f_peer->get_features() & (F::cluster | F::peer | F::file));
CHECK(f_peer_2->get_features() & (F::missing | F::file));
CHECK(f_my->get_parent() == my_folder_presence);
CHECK(f_peer->get_parent() == peer_folder_presence);
CHECK(!f_peer_2->get_parent());
}
SECTION("all devices have a file (c.txt)") {
auto f_my = static_cast<cluster_file_presence_t *>(file_c->get_presence(&*my_device));
auto f_peer = static_cast<cluster_file_presence_t *>(file_c->get_presence(&*peer_device));
auto f_peer_2 = static_cast<cluster_file_presence_t *>(file_c->get_presence(&*peer_2_device));
REQUIRE(f_my);
REQUIRE(f_peer);
REQUIRE(f_peer_2);
REQUIRE(f_my->get_device() == my_device.get());
REQUIRE(f_peer->get_device() == peer_device.get());
REQUIRE(f_peer_2->get_device() == peer_2_device.get());
CHECK(f_my->get_features() & (F::cluster | F::local | F::file));
CHECK(f_peer->get_features() & (F::cluster | F::peer | F::file));
CHECK(f_peer_2->get_features() & (F::cluster | F::peer | F::file));
CHECK(f_my->get_parent() == my_folder_presence);
CHECK(f_peer->get_parent() == peer_folder_presence);
CHECK(f_peer_2->get_parent() == peer_2_folder_presence);
}
SECTION("only peer device has a file (d.txt)") {
auto f_my = file_d->get_presence(&*my_device);
auto f_peer = static_cast<cluster_file_presence_t *>(file_d->get_presence(&*peer_device));
auto f_peer_2 = file_d->get_presence(&*peer_2_device);
REQUIRE(f_my);
REQUIRE(f_peer);
REQUIRE(f_peer_2);
REQUIRE(f_peer->get_device() == peer_device.get());
CHECK(f_my->get_features() & (F::missing | F::file));
CHECK(f_peer->get_features() & (F::cluster | F::peer | F::file));
CHECK(f_peer_2->get_features() & (F::missing | F::file));
CHECK(!f_my->get_parent());
CHECK(f_peer->get_parent() == peer_folder_presence);
CHECK(!f_peer_2->get_parent());
}
SECTION("only peer device has a file (e.txt)") {
auto f_my = file_e->get_presence(&*my_device);
auto f_peer = file_e->get_presence(&*peer_device);
auto f_peer_2 = static_cast<cluster_file_presence_t *>(file_e->get_presence(&*peer_2_device));
REQUIRE(f_my);
REQUIRE(f_peer);
REQUIRE(f_peer_2);
REQUIRE(f_peer_2->get_device() == peer_2_device.get());
CHECK(f_my->get_features() & (F::missing | F::file));
CHECK(f_peer->get_features() & (F::missing | F::file));
CHECK(f_peer_2->get_features() & (F::cluster | F::peer | F::file));
CHECK(!f_my->get_parent());
CHECK(!f_peer->get_parent());
CHECK(f_peer_2->get_parent() == peer_2_folder_presence);
}
SECTION("only peer devices have a file (f.txt)") {
auto f_my = file_f->get_presence(&*my_device);
auto f_peer = static_cast<cluster_file_presence_t *>(file_f->get_presence(&*peer_device));
auto f_peer_2 = static_cast<cluster_file_presence_t *>(file_f->get_presence(&*peer_2_device));
REQUIRE(f_my);
REQUIRE(f_peer);
REQUIRE(f_peer_2);
REQUIRE(f_peer->get_device() == peer_device.get());
REQUIRE(f_peer_2->get_device() == peer_2_device.get());
CHECK(f_my->get_features() & (F::missing | F::file));
CHECK(f_peer->get_features() & (F::cluster | F::peer | F::file));
CHECK(f_peer_2->get_features() & (F::cluster | F::peer | F::file));
CHECK(!f_my->get_parent());
CHECK(f_peer->get_parent() == peer_folder_presence);
CHECK(f_peer_2->get_parent() == peer_2_folder_presence);
}
}
SECTION("folder shared with a peer, simple hierarchy") {
REQUIRE(builder.upsert_folder("1234-5678", "some/path", "my-label").apply());
auto folder = cluster->get_folders().by_id("1234-5678");
auto fi_my = folder->get_folder_infos().by_device(*my_device);
auto add_file = [&](std::string_view name, model::device_t &device,
proto::FileInfoType type = proto::FileInfoType::DIRECTORY) {
auto folder_info = folder->get_folder_infos().by_device(device);
proto::FileInfo pr_fi;
proto::set_name(pr_fi, name);
proto::set_type(pr_fi, type);
proto::set_sequence(pr_fi, folder_info->get_max_sequence() + 1);
auto &v = proto::get_version(pr_fi);
proto::add_counters(v, proto::Counter(peer_device->device_id().get_uint(), 1));
auto file = model::file_info_t::create(sequencer->next_uuid(), pr_fi, folder_info.get()).value();
if (&device == my_device) {
file->mark_local(true);
}
folder_info->add_strict(file);
return file;
};
SECTION("create hierarchy up-front") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
add_file("a", *my_device);
add_file("a/b", *my_device);
add_file("a/b/c", *my_device);
add_file("a/b/c/d", *my_device);
add_file("a/b/c/d/e.txt", *my_device, proto::FileInfoType::FILE);
add_file("a", *peer_device);
add_file("a/b", *peer_device);
add_file("a/b/c", *peer_device);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
auto p_folder_my = folder_entity->get_presence(&*my_device);
auto p_folder_peer = folder_entity->get_presence(&*peer_device);
REQUIRE(p_folder_my);
REQUIRE(p_folder_peer);
CHECK(!p_folder_my->get_parent());
CHECK(!p_folder_peer->get_parent());
auto &children = folder_entity->get_children();
REQUIRE(children.size() == 1);
auto &dir_a_entity = *children.begin();
REQUIRE(dir_a_entity->get_path()->get_own_name() == "a");
REQUIRE(dir_a_entity->get_children().size() == 1);
CHECK(dir_a_entity->get_parent() == folder_entity);
auto p_dir_a_my = dir_a_entity->get_presence(&*my_device);
auto p_dir_a_peer = dir_a_entity->get_presence(&*peer_device);
CHECK(p_dir_a_my->get_features() & (F::directory | F::local));
CHECK(p_dir_a_peer->get_features() & (F::directory | F::peer));
CHECK(p_dir_a_my->get_parent() == p_folder_my);
CHECK(p_dir_a_peer->get_parent() == p_folder_peer);
auto &dir_b_entity = *dir_a_entity->get_children().begin();
REQUIRE(dir_b_entity->get_path()->get_own_name() == "b");
REQUIRE(dir_b_entity->get_children().size() == 1);
CHECK(dir_b_entity->get_parent() == dir_a_entity);
auto p_dir_b_my = dir_b_entity->get_presence(&*my_device);
auto p_dir_b_peer = dir_b_entity->get_presence(&*peer_device);
CHECK(p_dir_b_my->get_features() & (F::directory | F::local));
CHECK(p_dir_b_peer->get_features() & (F::directory | F::peer));
CHECK(p_dir_b_my->get_parent() == p_dir_a_my);
CHECK(p_dir_b_peer->get_parent() == p_dir_a_peer);
auto &dir_c_entity = *dir_b_entity->get_children().begin();
REQUIRE(dir_c_entity->get_path()->get_own_name() == "c");
REQUIRE(dir_c_entity->get_children().size() == 1);
CHECK(dir_c_entity->get_parent() == dir_b_entity);
auto p_dir_c_my = dir_c_entity->get_presence(&*my_device);
auto p_dir_c_peer = dir_c_entity->get_presence(&*peer_device);
CHECK(p_dir_c_my->get_features() & (F::directory | F::local));
CHECK(p_dir_c_peer->get_features() & (F::directory | F::peer));
CHECK(p_dir_c_my->get_parent() == p_dir_b_my);
CHECK(p_dir_c_peer->get_parent() == p_dir_b_peer);
auto &dir_d_entity = *dir_c_entity->get_children().begin();
REQUIRE(dir_d_entity->get_path()->get_own_name() == "d");
REQUIRE(dir_d_entity->get_children().size() == 1);
CHECK(dir_d_entity->get_parent() == dir_c_entity);
auto p_dir_d_my = dir_d_entity->get_presence(&*my_device);
auto p_dir_d_peer = dir_d_entity->get_presence(&*peer_device);
CHECK(p_dir_d_my->get_features() & (F::directory | F::local));
CHECK(p_dir_d_peer->get_features() & (F::directory | F::missing));
CHECK(p_dir_d_my->get_parent() == p_dir_c_my);
CHECK(!p_dir_d_peer->get_parent());
auto &file_e_entity = *dir_d_entity->get_children().begin();
REQUIRE(file_e_entity->get_path()->get_own_name() == "e.txt");
REQUIRE(file_e_entity->get_children().size() == 0);
CHECK(file_e_entity->get_parent() == dir_d_entity);
auto p_file_my = file_e_entity->get_presence(&*my_device);
auto p_file_peer = file_e_entity->get_presence(&*peer_device);
CHECK(p_file_my->get_features() & (F::file | F::local));
CHECK(p_file_peer->get_features() & (F::file | F::missing));
CHECK(p_file_my->get_parent() == p_dir_d_my);
CHECK(!p_file_peer->get_parent());
}
SECTION("dynamically create hierarchy") {
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
add_file("a", *peer_device);
add_file("a/b", *peer_device);
auto fi_peer = folder->get_folder_infos().by_device(*peer_device);
folder_entity->on_insert(*fi_peer);
auto p_folder_my = folder_entity->get_presence(&*my_device);
auto p_folder_peer = folder_entity->get_presence(&*peer_device);
REQUIRE(p_folder_my);
REQUIRE(p_folder_peer);
CHECK(!p_folder_my->get_parent());
CHECK(!p_folder_peer->get_parent());
auto &children = folder_entity->get_children();
REQUIRE(children.size() == 1);
auto &dir_a_entity = *children.begin();
REQUIRE(dir_a_entity->get_path()->get_own_name() == "a");
REQUIRE(dir_a_entity->get_children().size() == 1);
CHECK(dir_a_entity->get_parent() == folder_entity);
auto p_dir_a_my = dir_a_entity->get_presence(&*my_device);
auto p_dir_a_peer = dir_a_entity->get_presence(&*peer_device);
CHECK(p_dir_a_my->get_features() & (F::directory | F::missing));
CHECK(p_dir_a_peer->get_features() & (F::directory | F::peer));
CHECK(!p_dir_a_my->get_parent());
CHECK(p_dir_a_peer->get_parent() == p_folder_peer);
auto &dir_b_entity = *dir_a_entity->get_children().begin();
REQUIRE(dir_b_entity->get_path()->get_own_name() == "b");
REQUIRE(dir_b_entity->get_children().size() == 0);
CHECK(dir_b_entity->get_parent() == dir_a_entity);
auto p_dir_b_my = dir_b_entity->get_presence(&*my_device);
auto p_dir_b_peer = dir_b_entity->get_presence(&*peer_device);
CHECK(p_dir_b_my->get_features() & (F::directory | F::missing));
CHECK(p_dir_b_peer->get_features() & (F::directory | F::peer));
CHECK(!p_dir_b_my->get_parent());
CHECK(p_dir_b_peer->get_parent() == p_dir_a_peer);
auto file_c_peer = add_file("a/b/c", *peer_device);
folder_entity->on_insert(*file_c_peer, *fi_peer);
folder_entity->on_insert(*file_c_peer, *fi_peer); // should be ignored
REQUIRE(dir_b_entity->get_children().size() == 1);
auto &dir_c_entity = *dir_b_entity->get_children().begin();
REQUIRE(dir_c_entity->get_path()->get_own_name() == "c");
REQUIRE(dir_c_entity->get_children().size() == 0);
CHECK(dir_c_entity->get_parent() == dir_b_entity);
auto p_dir_c_my = dir_c_entity->get_presence(&*my_device);
auto p_dir_c_peer = dir_c_entity->get_presence(&*peer_device);
CHECK(p_dir_c_my->get_features() & (F::missing));
CHECK(p_dir_c_peer->get_features() & (F::directory | F::peer | F::cluster));
CHECK(!p_dir_c_my->get_parent());
CHECK(p_dir_c_peer->get_parent() == p_dir_b_peer);
auto file_a_my = add_file("a", *my_device);
folder_entity->on_insert(*file_a_my, *fi_my);
REQUIRE(children.size() == 1);
p_dir_a_my = dir_a_entity->get_presence(&*my_device);
CHECK(p_dir_a_my->get_parent() == p_folder_my);
}
SECTION("dynamically create hierarchy-2") {
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
auto file_1 = add_file("a-file", *my_device, proto::FileInfoType::FILE);
auto e_file_1 = folder_entity->on_insert(*file_1, *fi_my);
REQUIRE(e_file_1);
CHECK(e_file_1->get_path() == file_1->get_name());
auto file_3 = add_file("a-file-3", *my_device, proto::FileInfoType::FILE);
auto e_file_3 = folder_entity->on_insert(*file_3, *fi_my);
REQUIRE(e_file_3);
CHECK(e_file_3->get_path() == file_3->get_name());
auto file_2 = add_file("a-file-2", *my_device, proto::FileInfoType::FILE);
auto e_file_2 = folder_entity->on_insert(*file_2, *fi_my);
REQUIRE(e_file_2);
CHECK(e_file_2->get_path() == file_2->get_name());
}
SECTION("presence & entity hierarchy order") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto file_1 = add_file("file-a", *my_device, proto::FileInfoType::FILE);
auto file_2 = add_file("file-b", *my_device, proto::FileInfoType::DIRECTORY);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
auto &children = folder_entity->get_children();
auto it_children = children.begin();
auto &c1 = *it_children++;
auto &c2 = *it_children++;
CHECK(c1->get_path()->get_full_name() == "file-a");
CHECK(c2->get_path()->get_full_name() == "file-b");
auto &p_my_children = folder_entity->get_presence(&*my_device)->get_children();
REQUIRE(p_my_children.size() == 2);
CHECK(p_my_children[0]->get_entity()->get_path()->get_full_name() == "file-b");
CHECK(p_my_children[1]->get_entity()->get_path()->get_full_name() == "file-a");
auto &p_peer_children = folder_entity->get_presence(&*peer_device)->get_children();
REQUIRE(p_peer_children.size() == 2);
CHECK(p_peer_children[0]->get_entity()->get_path()->get_full_name() == "file-b");
CHECK(p_peer_children[1]->get_entity()->get_path()->get_full_name() == "file-a");
}
SECTION("orphans") {
SECTION("simple") {
add_file("a/b", *my_device);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
auto &children = folder_entity->get_children();
REQUIRE(children.size() == 0);
auto dir_a_my = add_file("a", *my_device);
folder_entity->on_insert(*dir_a_my, *fi_my);
REQUIRE(children.size() == 1);
auto &dir_a_entity = *children.begin();
REQUIRE(dir_a_entity->get_path()->get_own_name() == "a");
REQUIRE(dir_a_entity->get_children().size() == 1);
CHECK(dir_a_entity->get_parent() == folder_entity);
auto p_dir_a_my = dir_a_entity->get_presence(&*my_device);
CHECK(p_dir_a_my->get_features() & (F::directory | F::local));
auto &dir_b_entity = *dir_a_entity->get_children().begin();
REQUIRE(dir_b_entity->get_path()->get_own_name() == "b");
REQUIRE(dir_b_entity->get_children().size() == 0);
CHECK(dir_b_entity->get_parent() == dir_a_entity);
auto p_dir_b_my = dir_b_entity->get_presence(&*my_device);
CHECK(p_dir_b_my->get_features() & (F::directory | F::local));
}
SECTION("lazy") {
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
auto f_b = add_file("a/b", *my_device);
auto f_c = add_file("a/b/c", *my_device);
folder_entity->on_insert(*f_b, *fi_my);
folder_entity->on_insert(*f_c, *fi_my);
auto f_a = add_file("a", *my_device);
folder_entity->on_insert(*f_a, *fi_my);
auto &children = folder_entity->get_children();
REQUIRE(children.size() == 1);
auto &dir_a_entity = *children.begin();
REQUIRE(dir_a_entity->get_path()->get_own_name() == "a");
REQUIRE(dir_a_entity->get_children().size() == 1);
CHECK(dir_a_entity->get_parent() == folder_entity);
auto &dir_b_entity = *dir_a_entity->get_children().begin();
REQUIRE(dir_b_entity->get_path()->get_own_name() == "b");
REQUIRE(dir_b_entity->get_children().size() == 1);
CHECK(dir_b_entity->get_parent() == dir_a_entity);
auto &dir_c_entity = *dir_b_entity->get_children().begin();
REQUIRE(dir_c_entity->get_path()->get_own_name() == "c");
REQUIRE(dir_c_entity->get_children().size() == 0);
CHECK(dir_c_entity->get_parent() == dir_b_entity);
}
SECTION("lazy, with peer") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto fi_peer = folder->get_folder_infos().by_device(*peer_device);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
auto f_b_my = add_file("a/b.bin", *my_device);
auto f_b_peer = add_file("a/b.bin", *peer_device);
folder_entity->on_insert(*f_b_my, *fi_my);
folder_entity->on_insert(*f_b_peer, *fi_peer);
auto f_a_my = add_file("a", *my_device);
folder_entity->on_insert(*f_a_my, *fi_my);
auto f_a_peer = add_file("a", *peer_device);
folder_entity->on_insert(*f_a_peer, *fi_peer);
auto &children = folder_entity->get_children();
REQUIRE(children.size() == 1);
auto &dir_a_entity = *children.begin();
REQUIRE(dir_a_entity->get_path()->get_own_name() == "a");
REQUIRE(dir_a_entity->get_children().size() == 1);
CHECK(dir_a_entity->get_parent() == folder_entity);
auto p_dir_a_my = dir_a_entity->get_presence(&*my_device);
auto p_dir_a_peer = dir_a_entity->get_presence(&*peer_device);
CHECK(p_dir_a_my->get_features() & (F::directory | F::local));
CHECK(p_dir_a_peer->get_features() & (F::directory | F::peer));
CHECK(p_dir_a_my->get_parent());
auto &file_b_entity = *dir_a_entity->get_children().begin();
REQUIRE(file_b_entity->get_path()->get_own_name() == "b.bin");
REQUIRE(file_b_entity->get_children().size() == 0);
CHECK(file_b_entity->get_parent() == dir_a_entity);
auto p_file_my = file_b_entity->get_presence(&*my_device);
auto p_file_peer = file_b_entity->get_presence(&*peer_device);
CHECK(p_file_my->get_features() & (F::file | F::local));
CHECK(p_file_peer->get_features() & (F::file | F::peer));
CHECK(p_file_my->get_parent() == p_dir_a_my);
CHECK(p_file_peer->get_parent() == p_dir_a_peer);
}
SECTION("lazy, with peer-2") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto fi_peer = folder->get_folder_infos().by_device(*peer_device);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
auto f_b_peer = add_file("a/b.bin", *peer_device);
auto f_c_my = add_file("a/c.bin", *my_device);
auto e_b = folder_entity->on_insert(*f_b_peer, *fi_peer);
auto e_c = folder_entity->on_insert(*f_c_my, *fi_my);
auto f_a_my = add_file("a", *my_device);
auto e_a = folder_entity->on_insert(*f_a_my, *fi_my);
CHECK(e_a->get_stats() == entity_stats_t{0, 2});
REQUIRE(folder_entity->get_children().size() == 1);
auto p_a_my = e_a->get_presence(my_device.get());
CHECK(p_a_my->get_children().size() == 1);
CHECK(p_a_my->get_stats() == presence_stats_t{0, 2, 2, 2});
auto p_c_my = p_a_my->get_children()[0];
CHECK(p_c_my->get_children().size() == 0);
CHECK(p_c_my->get_entity()->get_path()->get_full_name() == "a/c.bin");
auto f_a_peer = add_file("a", *peer_device);
auto e_a_2 = folder_entity->on_insert(*f_a_peer, *fi_peer);
CHECK(e_a_2 == e_a);
CHECK(e_a->get_stats() == entity_stats_t{0, 3});
auto p_a_peer = e_a->get_presence(peer_device.get());
REQUIRE(p_a_peer->get_children().size() == 2);
CHECK(p_a_peer->get_stats() == presence_stats_t{0, 2, 2, 0});
auto p_b_peer = p_a_peer->get_children()[0];
CHECK(p_b_peer->get_children().size() == 0);
CHECK(p_b_peer->get_entity()->get_path()->get_full_name() == "a/b.bin");
CHECK(p_a_peer->get_children()[1]->get_features() & F::missing);
CHECK(p_a_peer->get_children()[1]->get_entity()->get_path()->get_full_name() == "a/c.bin");
CHECK(p_a_my->get_children().size() == 2);
CHECK(p_a_my->get_stats() == presence_stats_t{0, 2, 2, 2});
auto p_b_my = p_a_my->get_children()[0];
CHECK(p_b_my->get_children().size() == 0);
CHECK(p_b_my->get_features() & F::missing);
CHECK(p_b_my->get_entity()->get_path()->get_full_name() == "a/b.bin");
p_c_my = p_a_my->get_children()[1];
CHECK(p_c_my->get_children().size() == 0);
CHECK(p_c_my->get_entity()->get_path()->get_full_name() == "a/c.bin");
}
}
SECTION("removal") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
SECTION("empty folder info removal") {
auto fi_peer = folder->get_folder_infos().by_device(*peer_device).get();
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
REQUIRE(folder_entity->get_presence(&*my_device));
REQUIRE(folder_entity->get_presence(&*peer_device));
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_presence(&*my_device));
CHECK(!folder_entity->get_presence(&*peer_device));
}
SECTION("folder info with a file removal") {
auto fi_peer = folder->get_folder_infos().by_device(*peer_device).get();
add_file("a", *peer_device);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
REQUIRE(folder_entity->get_presence(&*my_device));
REQUIRE(folder_entity->get_presence(&*peer_device));
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_presence(&*my_device));
CHECK(!folder_entity->get_presence(&*peer_device));
}
SECTION("folder info with a file hierarchy removal") {
auto fi_peer = folder->get_folder_infos().by_device(*peer_device).get();
add_file("a", *peer_device);
add_file("a/b", *peer_device);
add_file("a/b/c", *peer_device);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
REQUIRE(folder_entity->get_presence(&*my_device));
REQUIRE(folder_entity->get_presence(&*peer_device));
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_presence(&*my_device));
CHECK(!folder_entity->get_presence(&*peer_device));
}
SECTION("folder info with a file hierarchy removal (2)") {
auto fi_peer = folder->get_folder_infos().by_device(*peer_device).get();
add_file("a", *my_device);
add_file("a", *peer_device);
add_file("a/b", *peer_device);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
REQUIRE(folder_entity->get_presence(&*my_device));
REQUIRE(folder_entity->get_presence(&*peer_device));
REQUIRE(folder_entity->get_children().size() == 1);
auto &dir_a_entity = *folder_entity->get_children().begin();
REQUIRE(dir_a_entity->get_path()->get_own_name() == "a");
REQUIRE(dir_a_entity->get_children().size() == 1);
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_presence(&*my_device));
CHECK(!folder_entity->get_presence(&*peer_device));
REQUIRE(dir_a_entity->get_children().size() == 0);
}
SECTION("folder info with a file hierarchy removal (3)") {
using strings_t = std::vector<std::string_view>;
struct sample_aug_t : model::augmentation_t {
sample_aug_t(strings_t &strings_, std::string_view name_) : strings{strings_}, name{name_} {}
~sample_aug_t() { strings.emplace_back(name); }
strings_t &strings;
std::string_view name;
};
auto strings = strings_t();
auto augment = [&](entity_t *presense, std::string_view name) {
auto aug = model::augmentation_ptr_t();
aug = new sample_aug_t(strings, name);
presense->set_augmentation(aug);
};
{
auto fi_peer = folder->get_folder_infos().by_device(*peer_device).get();
add_file("a", *peer_device);
add_file("a/b", *peer_device);
add_file("a/b/c", *peer_device);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
auto &dir_a_entity = *folder_entity->get_children().begin();
REQUIRE(dir_a_entity->get_path()->get_own_name() == "a");
REQUIRE(dir_a_entity->get_children().size() == 1);
auto &dir_b_entity = *dir_a_entity->get_children().begin();
REQUIRE(dir_b_entity->get_path()->get_own_name() == "b");
REQUIRE(dir_a_entity->get_children().size() == 1);
auto &file_c_entity = *dir_b_entity->get_children().begin();
REQUIRE(file_c_entity->get_path()->get_own_name() == "c");
REQUIRE(file_c_entity->get_children().size() == 0);
augment(dir_a_entity, "a");
augment(dir_b_entity, "a/b");
augment(file_c_entity, "a/b/c");
REQUIRE(folder_entity->get_presence(&*my_device));
auto p_folder_peer = folder_entity->get_presence(&*peer_device);
REQUIRE(p_folder_peer);
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_presence(&*my_device));
CHECK(!folder_entity->get_presence(&*peer_device));
}
REQUIRE(strings.size() == 3);
CHECK(strings[0] == "a/b/c");
CHECK(strings[1] == "a/b");
CHECK(strings[2] == "a");
}
SECTION("folder info with an orphan removal") {
auto fi_peer = folder->get_folder_infos().by_device(*peer_device).get();
add_file("a/b", *peer_device);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(!folder_entity->get_parent());
REQUIRE(folder_entity->get_presence(&*my_device));
REQUIRE(folder_entity->get_presence(&*peer_device));
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_presence(&*my_device));
CHECK(!folder_entity->get_presence(&*peer_device));
}
}
}
}
TEST_CASE("statistics, update", "[presentation]") {
auto my_id = device_id_t::from_string("KHQNO2S-5QSILRK-YX4JZZ4-7L77APM-QNVGZJT-EKU7IFI-PNEPBMY-4MXFMQD").value();
auto my_device = device_t::create(my_id, "my-device").value();
auto cluster = cluster_ptr_t(new cluster_t(my_device, 1));
auto sequencer = make_sequencer(4);
cluster->get_devices().put(my_device);
auto builder = diff_builder_t(*cluster);
REQUIRE(builder.upsert_folder("1234-5678", "some/path", "my-label").apply());
auto folder = cluster->get_folders().by_id("1234-5678");
auto &blocks = cluster->get_blocks();
auto add_file = [&](std::string_view name, model::device_t &device, std::int32_t file_size = 0,
proto::FileInfoType type = proto::FileInfoType::DIRECTORY, std::uint64_t modified_by = 0,
std::uint64_t modified_v = 0) {
auto folder_info = folder->get_folder_infos().by_device(device);
proto::FileInfo pr_fi;
proto::set_name(pr_fi, name);
proto::set_type(pr_fi, type);
proto::set_sequence(pr_fi, folder_info->get_max_sequence() + 1);
auto block = model::block_info_ptr_t();
if (file_size) {
proto::set_size(pr_fi, file_size);
proto::set_block_size(pr_fi, file_size);
assert(type == proto::FileInfoType::FILE);
auto bytes = utils::bytes_t(file_size);
auto b_hash = utils::sha256_digest(bytes).value();
auto &b = proto::add_blocks(pr_fi);
proto::set_size(b, file_size);
block = blocks.by_hash(b_hash);
if (!block) {
block = model::block_info_t::create(b).value();
blocks.put(block);
}
}
auto &v = proto::get_version(pr_fi);
auto modified_device = modified_by == 0 ? device.device_id().get_uint() : modified_by;
auto modified_version = modified_v == 0 ? 1 : modified_v;
proto::add_counters(v, proto::Counter(modified_device, modified_version));
proto::set_modified_s(pr_fi, modified_version);
auto file = model::file_info_t::create(sequencer->next_uuid(), pr_fi, folder_info.get()).value();
if (block) {
file->assign_block(block.get(), 0);
}
if (&device == my_device) {
file->mark_local(true);
if (block) {
file->mark_local_available(0);
}
}
folder_info->add_strict(file);
return file;
};
auto f_a_my = add_file("a", *my_device, 0, proto::FileInfoType::DIRECTORY);
auto f_c_my = add_file("a/b.txt", *my_device, 5, proto::FileInfoType::FILE);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{5, 2});
REQUIRE(folder_entity->get_children().size() == 1);
auto dir_a = *folder_entity->get_children().begin();
CHECK(dir_a->get_stats() == entity_stats_t{5, 2});
auto p_a_my = dir_a->get_presence(&*my_device);
CHECK(p_a_my->get_stats() == presence_stats_t{5, 2, 2, 2});
f_a_my->get_augmentation()->on_update();
CHECK(p_a_my->get_stats() == presence_stats_t{5, 2, 2, 2});
CHECK(folder_entity->get_stats() == entity_stats_t{5, 2});
}
TEST_CASE("statistics (size & locality are idenpendent)", "[presentation]") {
auto cluster = cluster_ptr_t(new cluster_t(my_device, 1));
auto sequencer = make_sequencer(4);
cluster->get_devices().put(my_device);
cluster->get_devices().put(peer_device);
auto builder = diff_builder_t(*cluster);
REQUIRE(builder.upsert_folder("1234-5678", "some/path", "my-label").apply());
auto folder = cluster->get_folders().by_id("1234-5678");
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto &blocks = cluster->get_blocks();
auto add_file = [&](std::string_view name, model::device_t &device, std::int32_t file_size = 0,
proto::FileInfoType type = proto::FileInfoType::DIRECTORY, std::uint64_t modified_by = 0,
std::uint64_t modified_v = 0) {
auto folder_info = folder->get_folder_infos().by_device(device);
proto::FileInfo pr_fi;
proto::set_name(pr_fi, name);
proto::set_type(pr_fi, type);
proto::set_sequence(pr_fi, folder_info->get_max_sequence() + 1);
auto block = model::block_info_ptr_t();
if (file_size) {
proto::set_size(pr_fi, file_size);
proto::set_block_size(pr_fi, file_size);
assert(type == proto::FileInfoType::FILE);
auto bytes = utils::bytes_t(file_size);
auto b_hash = utils::sha256_digest(bytes).value();
auto &b = proto::add_blocks(pr_fi);
proto::set_size(b, file_size);
block = blocks.by_hash(b_hash);
if (!block) {
block = model::block_info_t::create(b).value();
blocks.put(block);
}
}
auto &v = proto::get_version(pr_fi);
auto modified_device = modified_by == 0 ? device.device_id().get_uint() : modified_by;
auto modified_version = modified_v == 0 ? 1 : modified_v;
proto::add_counters(v, proto::Counter(modified_device, modified_version));
proto::set_modified_s(pr_fi, modified_version);
auto file = model::file_info_t::create(sequencer->next_uuid(), pr_fi, folder_info.get()).value();
if (block) {
file->assign_block(block.get(), 0);
}
if (&device == my_device) {
file->mark_local(true);
if (block) {
file->mark_local_available(0);
}
}
folder_info->add_strict(file);
return file;
};
auto f_peer = add_file("b.txt", *peer_device, 5, proto::FileInfoType::FILE);
auto f_my = add_file("b.txt", *my_device, 5, proto::FileInfoType::FILE);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
auto file_b = *folder_entity->get_children().begin();
auto file_b_my = file_b->get_presence(my_device.get());
auto file_b_peer = file_b->get_presence(peer_device.get());
CHECK(file_b_my->get_own_stats().size == 5);
CHECK(file_b_my->get_own_stats().local_entries == 1);
CHECK(file_b_peer->get_own_stats().size == 5);
CHECK(file_b_peer->get_own_stats().local_entries == 0);
f_my->mark_local(false);
f_my->notify_update();
CHECK(file_b_my->get_own_stats().size == 5);
CHECK(file_b_my->get_own_stats().local_entries == 0);
f_my->mark_local(true);
f_my->notify_update();
CHECK(file_b_my->get_own_stats().size == 5);
CHECK(file_b_my->get_own_stats().local_entries == 1);
}
TEST_CASE("statistics", "[presentation]") {
auto cluster = cluster_ptr_t(new cluster_t(my_device, 1));
auto sequencer = make_sequencer(4);
cluster->get_devices().put(my_device);
cluster->get_devices().put(peer_device);
cluster->get_devices().put(peer_2_device);
auto builder = diff_builder_t(*cluster);
REQUIRE(builder.upsert_folder("1234-5678", "some/path", "my-label").apply());
auto folder = cluster->get_folders().by_id("1234-5678");
auto fi_my = folder->get_folder_infos().by_device(*my_device);
auto &blocks = cluster->get_blocks();
auto add_file = [&](std::string_view name, model::device_t &device, std::int32_t file_size = 0,
proto::FileInfoType type = proto::FileInfoType::DIRECTORY, std::uint64_t modified_by = 0,
std::uint64_t modified_v = 0) {
auto folder_info = folder->get_folder_infos().by_device(device);
proto::FileInfo pr_fi;
proto::set_name(pr_fi, name);
proto::set_type(pr_fi, type);
proto::set_sequence(pr_fi, folder_info->get_max_sequence() + 1);
auto block = model::block_info_ptr_t();
if (file_size) {
proto::set_size(pr_fi, file_size);
proto::set_block_size(pr_fi, file_size);
assert(type == proto::FileInfoType::FILE);
auto bytes = utils::bytes_t(file_size);
auto b_hash = utils::sha256_digest(bytes).value();
auto &b = proto::add_blocks(pr_fi);
proto::set_size(b, file_size);
block = blocks.by_hash(b_hash);
if (!block) {
block = model::block_info_t::create(b).value();
blocks.put(block);
}
}
auto &v = proto::get_version(pr_fi);
auto modified_device = modified_by == 0 ? device.device_id().get_uint() : modified_by;
auto modified_version = modified_v == 0 ? 1 : modified_v;
proto::add_counters(v, proto::Counter(modified_device, modified_version));
proto::set_modified_s(pr_fi, modified_version);
auto file = model::file_info_t::create(sequencer->next_uuid(), pr_fi, folder_info.get()).value();
if (block) {
file->assign_block(block.get(), 0);
}
if (&device == my_device) {
file->mark_local(true);
if (block) {
file->mark_local_available(0);
}
}
folder_info->add_strict(file);
return file;
};
SECTION("emtpy folder") {
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{});
}
SECTION("shared with nobody folder") {
SECTION("single file") {
add_file("a.txt", *my_device, 5, proto::FileInfoType::FILE);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
auto folder_my = folder_entity->get_presence(&*my_device);
CHECK(folder_my->get_stats() == presence_stats_t{5, 1, 1, 1});
}
SECTION("two files") {
SECTION("flat hierarchy") {
add_file("a.txt", *my_device, 5, proto::FileInfoType::FILE);
add_file("b.txt", *my_device, 4, proto::FileInfoType::FILE);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{9, 2});
}
SECTION("some hierarchy") {
add_file("a", *my_device, 0, proto::FileInfoType::DIRECTORY);
add_file("a/b", *my_device, 0, proto::FileInfoType::DIRECTORY);
add_file("a/b/c.txt", *my_device, 5, proto::FileInfoType::FILE);
add_file("a/d.txt", *my_device, 4, proto::FileInfoType::FILE);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{9, 4});
auto folder_my = folder_entity->get_presence(&*my_device);
CHECK(folder_my->get_stats() == presence_stats_t{9, 4, 4, 4});
auto dir_a = *folder_entity->get_children().begin();
auto p_a = dir_a->get_presence(&*my_device);
auto it_a = dir_a->get_children().begin();
auto dir_b = *it_a++;
auto f_d = *it_a++;
auto p_b = dir_b->get_presence(&*my_device);
auto f_c = *dir_b->get_children().begin();
auto p_c = f_c->get_presence(&*my_device);
auto p_d = f_d->get_presence(&*my_device);
CHECK(dir_a->get_stats() == entity_stats_t{9, 4});
CHECK(p_a->get_stats() == presence_stats_t{9, 4, 4, 4});
CHECK(dir_b->get_stats() == entity_stats_t{5, 2});
CHECK(p_b->get_stats() == presence_stats_t{5, 2, 2, 2});
CHECK(f_c->get_stats() == entity_stats_t{5, 1});
CHECK(p_c->get_stats() == presence_stats_t{5, 1, 1, 1});
CHECK(f_d->get_stats() == entity_stats_t{4, 1});
CHECK(p_d->get_stats() == presence_stats_t{4, 1, 1, 1});
}
}
}
SECTION("shared with a peer") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto fi_peer = folder->get_folder_infos().by_device(*peer_device).get();
SECTION("single file") {
SECTION("same file") {
add_file("a.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 1);
add_file("a.txt", *peer_device, 5, proto::FileInfoType::FILE, my_device_id, 1);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
}
SECTION("peer has newer") {
add_file("a.txt", *my_device, 5, proto::FileInfoType::FILE, peer_device_id, 1);
add_file("a.txt", *peer_device, 6, proto::FileInfoType::FILE, peer_device_id, 2);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{6, 1});
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
}
SECTION("me has newer") {
add_file("a.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 2);
add_file("a.txt", *peer_device, 6, proto::FileInfoType::FILE, my_device_id, 1);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
}
SECTION("coflicted file (peer has newer)") {
add_file("a.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 1);
add_file("a.txt", *peer_device, 6, proto::FileInfoType::FILE, peer_device_id, 2);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
}
SECTION("coflicted file (me has newer)") {
add_file("a.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 2);
add_file("a.txt", *peer_device, 6, proto::FileInfoType::FILE, peer_device_id, 1);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
}
}
SECTION("two files do not intersect") {
SECTION("simple hierarchy") {
add_file("dir", *my_device);
add_file("dir/a.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 1);
add_file("dir", *peer_device);
add_file("dir/b.txt", *peer_device, 6, proto::FileInfoType::FILE, peer_device_id, 1);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{11, 3});
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_stats() == entity_stats_t{5, 2});
}
SECTION("complex hierarchy") {
add_file("dir", *my_device);
add_file("dir/a.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 1);
add_file("dir", *peer_device);
add_file("dir/b", *peer_device);
add_file("dir/b/c", *peer_device);
add_file("dir/b/c/d", *peer_device);
add_file("dir/b/c/d/x.txt", *peer_device, 6, proto::FileInfoType::FILE, peer_device_id, 1);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{11, 6});
REQUIRE(builder.unshare_folder(*fi_peer).apply());
CHECK(folder_entity->get_stats() == entity_stats_t{5, 2});
}
}
}
SECTION("dynamically add files") {
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
SECTION("for local device") {
SECTION("no orphans") {
auto f_a = add_file("a", *my_device, 0, proto::FileInfoType::DIRECTORY);
folder_entity->on_insert(*f_a, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 1});
REQUIRE(folder_entity->get_children().size() == 1);
auto dir_a = *folder_entity->get_children().begin();
CHECK(dir_a->get_stats() == entity_stats_t{0, 1});
auto p_a = dir_a->get_presence(&*my_device);
CHECK(p_a->get_stats() == presence_stats_t{0, 1, 1, 1});
auto f_b = add_file("a/b", *my_device, 0, proto::FileInfoType::DIRECTORY);
folder_entity->on_insert(*f_b, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 2});
auto dir_b = *dir_a->get_children().begin();
CHECK(dir_a->get_stats() == entity_stats_t{0, 2});
CHECK(p_a->get_stats() == presence_stats_t{0, 2, 2, 2});
CHECK(folder_entity->get_stats() == entity_stats_t{0, 2});
auto p_b = dir_b->get_presence(&*my_device);
CHECK(p_b->get_stats() == presence_stats_t{0, 1, 1, 1});
CHECK(dir_b->get_stats() == entity_stats_t{0, 1});
auto f_c = add_file("a/b/c.txt", *my_device, 5, proto::FileInfoType::FILE);
folder_entity->on_insert(*f_c, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{5, 3});
CHECK(dir_a->get_stats() == entity_stats_t{5, 3});
CHECK(p_a->get_stats() == presence_stats_t{5, 3, 3, 3});
CHECK(dir_b->get_stats() == entity_stats_t{5, 2});
CHECK(p_b->get_stats() == presence_stats_t{5, 2, 2, 2});
auto file_c = *dir_b->get_children().begin();
CHECK(file_c->get_stats() == entity_stats_t{5, 1});
auto p_c = file_c->get_presence(&*my_device);
CHECK(p_c->get_stats() == presence_stats_t{5, 1, 1, 1});
}
SECTION("with orphans") {
auto f_c = add_file("a/b/c.txt", *my_device, 5, proto::FileInfoType::FILE);
folder_entity->on_insert(*f_c, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 0});
auto f_b = add_file("a/b", *my_device, 0, proto::FileInfoType::DIRECTORY);
folder_entity->on_insert(*f_b, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 0});
auto f_a = add_file("a", *my_device, 0, proto::FileInfoType::DIRECTORY);
folder_entity->on_insert(*f_a, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{5, 3});
}
}
SECTION("for local & peer device") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto fi_peer = folder->get_folder_infos().by_device(*peer_device);
SECTION("no orphans") {
auto f_a_my = add_file("a", *my_device, 0, proto::FileInfoType::DIRECTORY);
folder_entity->on_insert(*f_a_my, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 1});
auto f_a_peer = add_file("a", *peer_device, 0, proto::FileInfoType::DIRECTORY);
folder_entity->on_insert(*f_a_peer, *fi_peer);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 1});
auto f_b_peer = add_file("a/b", *peer_device, 0, proto::FileInfoType::DIRECTORY);
folder_entity->on_insert(*f_b_peer, *fi_peer);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 2});
auto f_b_my = add_file("a/b", *my_device, 0, proto::FileInfoType::DIRECTORY);
folder_entity->on_insert(*f_b_my, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 2});
auto f_c = add_file("a/b/c.txt", *peer_device, 5, proto::FileInfoType::FILE);
folder_entity->on_insert(*f_c, *fi_peer);
CHECK(folder_entity->get_stats() == entity_stats_t{5, 3});
}
SECTION("simple hierarchy") {
auto f_a_my = add_file("a", *my_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 2);
folder_entity->on_insert(*f_a_my, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 1});
auto dir_a = *folder_entity->get_children().begin();
CHECK(dir_a->get_stats() == entity_stats_t{0, 1});
auto p_a_my = dir_a->get_presence(&*my_device);
CHECK(p_a_my->get_stats() == presence_stats_t{0, 1, 1, 1});
auto p_a_peer = dir_a->get_presence(&*peer_device);
CHECK(p_a_peer->get_stats() == presence_stats_t{0, 0, 0, 0});
auto f_x_my = add_file("a/x.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 2);
folder_entity->on_insert(*f_x_my, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{5, 2});
CHECK(dir_a->get_stats() == entity_stats_t{5, 2});
CHECK(p_a_my->get_stats() == presence_stats_t{5, 2, 2, 2});
CHECK(p_a_peer->get_stats() == presence_stats_t{0, 0});
auto e_x = *dir_a->get_children().begin();
CHECK(e_x->get_stats() == entity_stats_t{5, 1});
auto p_x_my = e_x->get_presence(&*my_device);
CHECK(p_x_my->get_stats() == presence_stats_t{5, 1, 1, 1});
CHECK(p_x_my->get_parent() == p_a_my);
CHECK(p_a_my->get_children().size() == 1);
CHECK(p_a_my->get_children()[0] == p_x_my);
CHECK(p_a_peer->get_children().size() == 1);
CHECK(p_a_peer->get_children()[0]->get_features() & F::missing);
auto f_a_peer = add_file("a", *peer_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
folder_entity->on_insert(*f_a_peer, *fi_peer);
CHECK(folder_entity->get_stats() == entity_stats_t{5, 2});
CHECK(dir_a->get_stats() == entity_stats_t{5, 2});
CHECK(p_a_my->get_stats() == presence_stats_t{5, 2, 2, 2});
CHECK(p_x_my->get_stats() == presence_stats_t{5, 1, 1, 1});
p_a_peer = dir_a->get_presence(&*peer_device);
CHECK(p_a_peer->get_stats() == entity_stats_t{0, 1});
auto p_x_peer = e_x->get_presence(&*peer_device);
CHECK(p_x_peer->get_features() & (F::missing | F::file));
CHECK(p_x_peer->get_stats() == presence_stats_t{0, 0});
auto pr_x = f_x_my->as_proto(true);
auto file_x_peer = model::file_info_t::create(sequencer->next_uuid(), pr_x, fi_peer.get()).value();
auto block = *blocks.begin();
file_x_peer->assign_block(block.get(), 0);
fi_peer->add_strict(file_x_peer);
CHECK(p_a_peer->get_children().size() == 1);
CHECK(p_a_peer->get_children()[0]->get_features() & F::missing);
folder_entity->on_insert(*file_x_peer, *fi_peer);
CHECK(folder_entity->get_stats() == entity_stats_t{5, 2});
CHECK(dir_a->get_stats() == entity_stats_t{5, 2});
CHECK(p_a_my->get_stats() == presence_stats_t{5, 2, 2, 2});
CHECK(p_x_my->get_stats() == presence_stats_t{5, 1, 1, 1});
CHECK(p_a_peer->get_stats() == presence_stats_t{5, 2, 1, 0});
p_x_peer = e_x->get_presence(&*peer_device);
CHECK(p_x_peer->get_parent());
CHECK(p_x_peer->get_stats() == presence_stats_t{5, 1, 1, 0});
CHECK(p_a_peer->get_children().size() == 1);
CHECK(p_a_peer->get_children()[0] == p_x_peer);
}
SECTION("with orphans") {
auto f_c_peer = add_file("a/b/c.txt", *peer_device, 5, proto::FileInfoType::FILE, my_device_id, 1);
folder_entity->on_insert(*f_c_peer, *fi_peer);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 0});
auto f_c_my = add_file("a/b/c.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 1);
folder_entity->on_insert(*f_c_my, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 0});
auto f_b_peer = add_file("a/b", *peer_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
folder_entity->on_insert(*f_b_peer, *fi_peer);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 0});
auto f_b_my = add_file("a/b", *my_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
folder_entity->on_insert(*f_b_my, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{0, 0});
auto f_a_peer = add_file("a", *peer_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
folder_entity->on_insert(*f_a_peer, *fi_peer);
CHECK(folder_entity->get_stats() == entity_stats_t{5, 3});
auto f_a_my = add_file("a", *my_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
folder_entity->on_insert(*f_a_my, *fi_my);
CHECK(folder_entity->get_stats() == entity_stats_t{5, 3});
auto dir_a = *folder_entity->get_children().begin();
auto dir_b = *dir_a->get_children().begin();
auto file_c = *dir_b->get_children().begin();
auto p_a_my = dir_a->get_presence(&*my_device);
CHECK(p_a_my->get_stats() == presence_stats_t{5, 3, 3, 3});
auto p_a_peer = dir_a->get_presence(&*peer_device);
CHECK(p_a_peer->get_stats() == presence_stats_t{5, 3, 3, 0});
auto p_b_my = dir_b->get_presence(&*my_device);
CHECK(p_b_my->get_stats() == presence_stats_t{5, 2, 2, 2});
auto p_b_peer = dir_b->get_presence(&*peer_device);
CHECK(p_b_peer->get_stats() == presence_stats_t{5, 2, 2, 0});
auto p_c_my = file_c->get_presence(&*my_device);
CHECK(p_c_my->get_stats() == presence_stats_t{5, 1, 1, 1});
auto p_c_peer = file_c->get_presence(&*peer_device);
CHECK(p_c_peer->get_stats() == presence_stats_t{5, 1, 1, 0});
}
}
}
SECTION("file updates") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto f_a_my = add_file("a", *my_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
auto f_a_peer = add_file("a", *peer_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
auto f_b_peer = add_file("a/b", *peer_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
auto f_b_my = add_file("a/b", *my_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
auto f_c_my = add_file("a/b/c.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 1);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{5, 3});
REQUIRE(folder_entity->get_children().size() == 1);
auto dir_a = *folder_entity->get_children().begin();
CHECK(dir_a->get_stats() == entity_stats_t{5, 3});
auto p_a_my = dir_a->get_presence(&*my_device);
CHECK(p_a_my->get_stats() == presence_stats_t{5, 3, 3, 3});
auto p_a_peer = dir_a->get_presence(&*peer_device);
CHECK(p_a_peer->get_stats() == presence_stats_t{0, 2, 2, 0});
auto p_b_my = dir_a->get_presence(&*my_device)->get_children().front();
CHECK(p_b_my->get_stats() == presence_stats_t{5, 2, 2, 2});
auto p_c_my = p_b_my->get_entity()->get_presence(&*my_device)->get_children().front();
CHECK(p_c_my->get_stats() == presence_stats_t{5, 1, 1, 1});
auto pr_fi = [&]() {
auto pr_fi = f_c_my->as_proto(false);
auto block = model::block_info_ptr_t();
proto::set_size(pr_fi, 10);
proto::set_block_size(pr_fi, 10);
auto bytes = utils::bytes_t(10);
std::fill_n(bytes.data(), bytes.size(), '1');
auto b_hash = utils::sha256_digest(bytes).value();
auto &b = proto::add_blocks(pr_fi);
proto::set_size(b, 10);
proto::set_hash(b, b_hash);
block = blocks.by_hash(b_hash);
block = model::block_info_t::create(b).value();
blocks.put(block);
return pr_fi;
}();
REQUIRE(builder.local_update("1234-5678", pr_fi).apply());
CHECK(folder_entity->get_stats() == entity_stats_t{10, 3});
CHECK(dir_a->get_stats() == entity_stats_t{10, 3});
CHECK(p_a_my->get_stats() == presence_stats_t{10, 3, 3, 3});
CHECK(p_c_my->get_stats() == presence_stats_t{10, 1, 1, 1});
CHECK(p_a_peer->get_stats() == presence_stats_t{0, 2, 2, 0});
// peer copies
auto fi_peer = folder->get_folder_infos().by_device(*peer_device);
f_c_my = fi_my->get_file_infos().by_name(f_c_my->get_name()->get_full_name());
proto::get_version(pr_fi) = f_c_my->get_version().as_proto();
auto file_c_peer = model::file_info_t::create(sequencer->next_uuid(), pr_fi, fi_peer.get()).value();
{
auto block = *blocks.begin();
file_c_peer->assign_block(block.get(), 0);
fi_peer->add_strict(file_c_peer);
folder_entity->on_insert(*file_c_peer, *fi_peer);
CHECK(folder_entity->get_stats() == entity_stats_t{10, 3});
CHECK(dir_a->get_stats() == entity_stats_t{10, 3});
CHECK(p_a_my->get_stats() == presence_stats_t{10, 3, 3, 3});
CHECK(p_a_peer->get_stats() == presence_stats_t{10, 3, 3, 0});
CHECK(p_c_my->get_stats() == presence_stats_t{10, 1, 1, 1});
}
// peer updates
{
auto [pr_fi, b_15] = [&]() -> std::pair<proto::FileInfo, model::block_info_ptr_t> {
auto pr_fi = f_c_my->as_proto(false);
auto &v = proto::get_version(pr_fi);
auto value = proto::get_value(f_c_my->get_version().get_best());
proto::add_counters(v, proto::Counter(peer_device_id, value + 1));
auto block = model::block_info_ptr_t();
proto::set_size(pr_fi, 15);
proto::set_block_size(pr_fi, 15);
auto bytes = utils::bytes_t(15);
std::fill_n(bytes.data(), bytes.size(), '2');
auto b_hash = utils::sha256_digest(bytes).value();
auto &b = proto::add_blocks(pr_fi);
proto::set_size(b, 15);
proto::set_hash(b, b_hash);
block = blocks.by_hash(b_hash);
block = model::block_info_t::create(b).value();
blocks.put(block);
return {pr_fi, block};
}();
auto fi_peer_2 = folder->get_folder_infos().by_device(*peer_device);
bu::uuid file_uuid;
assign(file_uuid, file_c_peer->get_uuid());
auto file_c_peer_2 = model::file_info_t::create(file_uuid, pr_fi, fi_peer.get()).value();
auto block = *blocks.begin();
file_c_peer_2->assign_block(b_15.get(), 0);
fi_peer_2->add_strict(file_c_peer);
file_c_peer->update(*file_c_peer_2);
file_c_peer->notify_update();
CHECK(folder_entity->get_stats() == entity_stats_t{15, 3});
CHECK(dir_a->get_stats() == entity_stats_t{15, 3});
CHECK(p_a_my->get_stats() == presence_stats_t{10, 3, 2, 3});
CHECK(p_a_peer->get_stats() == presence_stats_t{15, 3, 3, 0});
}
// local copy
{
REQUIRE(builder.remote_copy(*file_c_peer, *fi_peer).apply());
CHECK(folder_entity->get_stats() == entity_stats_t{15, 3});
CHECK(dir_a->get_stats() == entity_stats_t{15, 3});
CHECK(p_a_my->get_stats() == presence_stats_t{15, 3, 3, 3});
CHECK(p_a_peer->get_stats() == presence_stats_t{15, 3, 3, 0});
}
// local update
{
auto pr_fi = [&]() {
auto pr_fi = file_c_peer->as_proto(false);
proto::clear_blocks(pr_fi);
proto::set_deleted(pr_fi, true);
proto::set_size(pr_fi, 0);
auto &v = proto::get_version(pr_fi);
proto::add_counters(v, proto::Counter(my_device_id, 3));
return pr_fi;
}();
REQUIRE(builder.local_update("1234-5678", pr_fi).apply());
CHECK(folder_entity->get_stats() == entity_stats_t{0, 3});
CHECK(dir_a->get_stats() == entity_stats_t{0, 3});
CHECK(p_a_my->get_stats() == presence_stats_t{0, 3, 3, 3});
CHECK(p_a_peer->get_stats() == presence_stats_t{15, 3, 2, 0});
}
}
SECTION("file deletion & insertion") {
auto f_a = add_file("a", *my_device, 5, proto::FileInfoType::FILE);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
REQUIRE(folder_entity->get_children().size() == 1);
auto e_a = *folder_entity->get_children().begin();
CHECK(e_a->get_stats() == entity_stats_t{5, 1});
auto p_a_my = e_a->get_presence(&*my_device);
CHECK(p_a_my->get_stats() == presence_stats_t{5, 1, 1, 1});
CHECK(!(p_a_my->get_features() & F::deleted));
auto pr_fi = [&]() {
auto pr_fi = f_a->as_proto(false);
auto block = model::block_info_ptr_t();
proto::set_size(pr_fi, 0);
proto::set_block_size(pr_fi, 0);
proto::set_deleted(pr_fi, true);
return pr_fi;
}();
REQUIRE(builder.local_update("1234-5678", pr_fi).apply());
CHECK(p_a_my->get_stats() == presence_stats_t{0, 1, 1, 1});
CHECK(p_a_my->get_features() & F::deleted);
}
SECTION("folder updates & cluster entities") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto fi_peer = folder->get_folder_infos().by_device(*peer_device);
auto f_a_peer = add_file("a", *peer_device, 0, proto::FileInfoType::DIRECTORY);
REQUIRE(builder.remote_copy(*f_a_peer, *fi_peer).apply());
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{0, 1});
REQUIRE(folder_entity->get_children().size() == 1);
auto dir_a = *folder_entity->get_children().begin();
CHECK(dir_a->get_stats() == entity_stats_t{0, 1});
auto p_a_my = dir_a->get_presence(&*my_device);
CHECK(p_a_my->get_stats() == presence_stats_t{0, 1, 1, 1});
auto p_a_peer = dir_a->get_presence(&*peer_device);
CHECK(p_a_peer->get_stats() == presence_stats_t{0, 1, 1, 0});
auto pr_fi = [&]() {
auto pr_fi = f_a_peer->as_proto(false);
proto::set_deleted(pr_fi, true);
return pr_fi;
}();
REQUIRE(builder.local_update("1234-5678", pr_fi).apply());
CHECK(p_a_my->get_stats() == presence_stats_t{0, 1, 1, 1});
CHECK(dir_a->get_stats() == entity_stats_t{0, 1});
CHECK(p_a_peer->get_stats() == presence_stats_t{0, 1, 0, 0});
}
SECTION("yet not resolved conficts") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto fi_peer = folder->get_folder_infos().by_device(*peer_device);
SECTION("static") {
SECTION("local win") {
auto f_a_my = add_file("content.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 10);
auto f_a_peer = add_file("content.txt", *peer_device, 6, proto::FileInfoType::FILE, peer_device_id, 9);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{5, 1});
REQUIRE(folder_entity->get_children().size() == 1);
auto file_entity = *folder_entity->get_children().begin();
CHECK(file_entity->get_stats() == entity_stats_t{5, 1});
auto p_my = file_entity->get_presence(&*my_device);
CHECK(p_my->get_stats() == presence_stats_t{5, 1, 1, 1});
auto p_a_peer = file_entity->get_presence(&*peer_device);
CHECK(p_a_peer->get_stats() == presence_stats_t{6, 1, 0, 0});
}
SECTION("remote win") {
auto f_a_my = add_file("content.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 9);
auto f_a_peer = add_file("content.txt", *peer_device, 6, proto::FileInfoType::FILE, peer_device_id, 10);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{6, 1});
REQUIRE(folder_entity->get_children().size() == 1);
auto file_entity = *folder_entity->get_children().begin();
CHECK(file_entity->get_stats() == entity_stats_t{6, 1});
auto p_my = file_entity->get_presence(&*my_device);
CHECK(p_my->get_stats() == presence_stats_t{5, 1, 0, 1});
auto p_a_peer = file_entity->get_presence(&*peer_device);
CHECK(p_a_peer->get_stats() == presence_stats_t{6, 1, 1, 0});
}
}
SECTION("dynamic") {
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
SECTION("local win") {
auto f_a_my = add_file("content-2.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 10);
auto f_a_peer =
add_file("content-2.txt", *peer_device, 6, proto::FileInfoType::FILE, peer_device_id, 9);
folder_entity->on_insert(*f_a_my, *fi_my);
folder_entity->on_insert(*f_a_peer, *fi_peer);
REQUIRE(folder_entity->get_children().size() == 1);
auto file_entity = *folder_entity->get_children().begin();
CHECK(file_entity->get_stats() == entity_stats_t{5, 1});
auto p_my = file_entity->get_presence(&*my_device);
CHECK(p_my->get_stats() == presence_stats_t{5, 1, 1, 1});
auto p_a_peer = file_entity->get_presence(&*peer_device);
CHECK(p_a_peer->get_stats() == presence_stats_t{6, 1, 0, 0});
}
SECTION("remote win") {
auto f_a_my = add_file("content-3.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 9);
auto f_a_peer =
add_file("content-3.txt", *peer_device, 6, proto::FileInfoType::FILE, peer_device_id, 10);
folder_entity->on_insert(*f_a_my, *fi_my);
folder_entity->on_insert(*f_a_peer, *fi_peer);
auto p_folder_my = folder_entity->get_presence(&*my_device);
CHECK(p_folder_my->get_stats() == presence_stats_t{5, 1, 0, 1});
auto p_folder_peer = folder_entity->get_presence(&*peer_device);
CHECK(p_folder_peer->get_stats() == presence_stats_t{6, 1, 1, 0});
REQUIRE(folder_entity->get_children().size() == 1);
auto file_entity = *folder_entity->get_children().begin();
CHECK(file_entity->get_stats() == entity_stats_t{6, 1});
auto p_my = file_entity->get_presence(&*my_device);
CHECK(p_my->get_stats() == presence_stats_t{5, 1, 0, 1});
auto p_a_peer = file_entity->get_presence(&*peer_device);
CHECK(p_a_peer->get_stats() == presence_stats_t{6, 1, 1, 0});
}
}
}
SECTION("updates propagation") {
using sorted_entities_t = std::pmr::set<const entity_t *>;
using unsorted_entities_t = std::pmr::set<entity_ptr_t, std::less<entity_ptr_t>>;
struct monitor_t final : entities_monitor_t {
monitor_t(unsorted_entities_t &deleted_, sorted_entities_t &updated_)
: deleted{deleted_}, updated{updated_} {}
void on_delete(entity_t &entity) noexcept override { deleted.emplace(entity_ptr_t{&entity}); }
void on_update(const entity_t &entity) noexcept override { updated.insert(&entity); }
unsorted_entities_t &deleted;
sorted_entities_t &updated;
};
auto f_a_my = add_file("a", *my_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
auto f_b_my = add_file("a/b", *my_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
auto f_c_my = add_file("a/b/c.txt", *my_device, 5, proto::FileInfoType::FILE, my_device_id, 1);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{5, 3});
REQUIRE(folder_entity->get_children().size() == 1);
auto dir_a = *folder_entity->get_children().begin();
auto dir_b = *dir_a->get_children().begin();
auto file_c = *dir_b->get_children().begin();
auto buffer = std::array<std::byte, 1024>();
auto pool = std::pmr::monotonic_buffer_resource(buffer.data(), buffer.size());
auto allocator = std::pmr::polymorphic_allocator<std::string>(&pool);
auto updated_entities = sorted_entities_t(allocator);
auto deleted_entities = unsorted_entities_t(allocator);
auto monitor = monitor_t(deleted_entities, updated_entities);
auto monitor_guard = folder_entity->monitor(&monitor);
SECTION("file update") {
auto pr_fi = [&]() {
auto pr_fi = f_c_my->as_proto(false);
auto block = model::block_info_ptr_t();
proto::set_size(pr_fi, 6);
proto::set_block_size(pr_fi, 6);
auto bytes = utils::bytes_t(6);
std::fill_n(bytes.data(), bytes.size(), '1');
auto b_hash = utils::sha256_digest(bytes).value();
auto &b = proto::add_blocks(pr_fi);
proto::set_size(b, 6);
proto::set_hash(b, b_hash);
block = blocks.by_hash(b_hash);
block = model::block_info_t::create(b).value();
blocks.put(block);
return pr_fi;
}();
REQUIRE(builder.local_update("1234-5678", pr_fi).apply());
REQUIRE(deleted_entities.size() == 0);
REQUIRE(updated_entities.size() == 1);
auto it = updated_entities.begin();
REQUIRE(*it++ == file_c);
updated_entities.clear();
}
auto f_d_my = add_file("a/b/d.txt", *my_device, 7, proto::FileInfoType::FILE, my_device_id, 1);
folder_entity->on_insert(*f_d_my, *fi_my);
auto file_d = *(++dir_b->get_children().begin());
REQUIRE(deleted_entities.size() == 0);
REQUIRE(updated_entities.size() == 1);
REQUIRE(*updated_entities.begin() == file_d);
updated_entities.clear();
REQUIRE(folder_entity->get_stats() == entity_stats_t{13, 4});
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto fi_peer = folder->get_folder_infos().by_device(*peer_device);
folder_entity->on_insert(*fi_peer);
REQUIRE(folder_entity->get_stats() == entity_stats_t{13, 4});
REQUIRE(deleted_entities.size() == 0);
REQUIRE(updated_entities.size() == 1);
REQUIRE(*updated_entities.begin() == folder_entity);
updated_entities.clear();
auto f_a_peer = add_file("a", *peer_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
folder_entity->on_insert(*f_a_peer, *fi_peer);
REQUIRE(deleted_entities.size() == 0);
REQUIRE(updated_entities.size() == 1);
REQUIRE(*updated_entities.begin() == dir_a);
updated_entities.clear();
auto f_x_peer = add_file("x", *peer_device, 0, proto::FileInfoType::DIRECTORY, peer_device_id, 1);
auto f_y_peer = add_file("x/y", *peer_device, 0, proto::FileInfoType::DIRECTORY, peer_device_id, 1);
folder_entity->on_insert(*f_x_peer, *fi_peer);
folder_entity->on_insert(*f_y_peer, *fi_peer);
REQUIRE(deleted_entities.size() == 0);
REQUIRE(updated_entities.size() == 2);
updated_entities.clear();
f_x_peer.reset();
f_y_peer.reset();
REQUIRE(builder.unshare_folder(*fi_peer).apply());
REQUIRE(deleted_entities.size() == 2);
deleted_entities.clear();
f_a_my.reset();
f_b_my.reset();
f_c_my.reset();
REQUIRE(builder.remove_folder(*folder).apply());
REQUIRE(deleted_entities.size() == 3);
folder.reset();
REQUIRE(deleted_entities.size() == 5);
}
SECTION("detached presentation") {
REQUIRE(builder.share_folder(peer_id.get_sha256(), "1234-5678").apply());
auto f_a_peer = add_file("a", *peer_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
auto f_b_peer = add_file("a/b", *peer_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
auto f_c_peer = add_file("a/b/c.txt", *peer_device, 0, proto::FileInfoType::FILE, my_device_id, 1);
auto f_c_my = add_file("a/b/c.txt", *my_device, 0, proto::FileInfoType::FILE, my_device_id, 1);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
CHECK(folder_entity->get_stats() == entity_stats_t{0, 3});
REQUIRE(folder_entity->get_children().size() == 1);
auto dir_a = *folder_entity->get_children().begin();
auto dir_b = *dir_a->get_children().begin();
auto file_c = *dir_b->get_children().begin();
SECTION("destroy detached") {
CHECK(dir_a->get_presence(my_device.get())->get_features() & F::missing);
CHECK(dir_b->get_presence(my_device.get())->get_features() & F::missing);
CHECK(file_c->get_presence(my_device.get())->get_features() & F::local);
}
SECTION("attach") {
auto f_b_my = add_file("a/b", *my_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
auto f_a_my = add_file("a", *my_device, 0, proto::FileInfoType::DIRECTORY, my_device_id, 1);
folder_entity->on_insert(*f_b_my, *fi_my);
folder_entity->on_insert(*f_a_my, *fi_my);
auto p_a_my = dir_a->get_presence(my_device.get());
auto p_b_my = dir_b->get_presence(my_device.get());
auto p_f_my = file_c->get_presence(my_device.get());
CHECK(p_a_my->get_features() & F::local);
CHECK(p_b_my->get_features() & F::local);
CHECK(p_f_my->get_features() & F::local);
CHECK(p_a_my->get_stats() == presence_stats_t{0, 3, 3, 3});
CHECK(p_b_my->get_stats() == presence_stats_t{0, 2, 2, 2});
CHECK(p_f_my->get_stats() == presence_stats_t{0, 1, 1, 1});
}
}
}
TEST_CASE("file uniqueness", "[presentation]") {
auto cluster = cluster_ptr_t(new cluster_t(my_device, 1));
auto sequencer = make_sequencer(4);
cluster->get_devices().put(my_device);
auto builder = diff_builder_t(*cluster);
REQUIRE(builder.upsert_folder("1234-5678", "some/path", "my-label").apply());
auto folder = cluster->get_folders().by_id("1234-5678");
auto &blocks = cluster->get_blocks();
auto add_file = [&](std::string_view name, bool deleted = false) {
auto folder_info = folder->get_folder_infos().by_device(*my_device);
proto::FileInfo pr_fi;
proto::set_name(pr_fi, name);
proto::set_sequence(pr_fi, folder_info->get_max_sequence() + 1);
proto::set_deleted(pr_fi, deleted);
auto &v = proto::get_version(pr_fi);
auto modified_device = my_device->device_id().get_uint();
auto modified_version = 1;
proto::add_counters(v, proto::Counter(modified_device, modified_version));
proto::set_modified_s(pr_fi, modified_version);
auto file = model::file_info_t::create(sequencer->next_uuid(), pr_fi, folder_info.get()).value();
folder_info->add_strict(file);
return file;
};
auto file_a1 = add_file("a.bin");
auto file_a2 = add_file("A.bin");
auto file_b = add_file("b.bin");
auto file_c1 = add_file("файл-1.bin");
auto file_c2 = add_file("фаЙл-1.bin");
auto file_d = add_file("фаил-2.bin");
auto file_e1 = add_file("Файл-3.bin");
auto file_e2 = add_file("ФАЙЛ-3.bin", true);
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
auto p_file_a1 = static_cast<presentation::file_presence_t *>(file_a1->get_augmentation().get());
auto p_file_a2 = static_cast<presentation::file_presence_t *>(file_a2->get_augmentation().get());
auto p_file_b = static_cast<presentation::file_presence_t *>(file_b->get_augmentation().get());
auto p_file_c1 = static_cast<presentation::file_presence_t *>(file_c1->get_augmentation().get());
auto p_file_c2 = static_cast<presentation::file_presence_t *>(file_c2->get_augmentation().get());
auto p_file_d = static_cast<presentation::file_presence_t *>(file_d->get_augmentation().get());
auto p_file_e1 = static_cast<presentation::file_presence_t *>(file_e1->get_augmentation().get());
auto p_file_e2 = static_cast<presentation::file_presence_t *>(file_e2->get_augmentation().get());
#if defined(SYNCSPIRIT_WIN) || defined(SYNCSPIRIT_MAC)
CHECK(!p_file_a1->is_unique());
CHECK(!p_file_a2->is_unique());
CHECK(p_file_b->is_unique());
CHECK(!p_file_c1->is_unique());
CHECK(!p_file_c2->is_unique());
CHECK(p_file_d->is_unique());
CHECK(p_file_e1->is_unique());
CHECK(p_file_e2->is_unique());
#else
CHECK(p_file_a1->is_unique());
CHECK(p_file_a2->is_unique());
CHECK(p_file_b->is_unique());
CHECK(p_file_c1->is_unique());
CHECK(p_file_c2->is_unique());
CHECK(p_file_d->is_unique());
CHECK(p_file_e1->is_unique());
CHECK(p_file_e2->is_unique());
#endif
}
TEST_CASE("get_child", "[presentation]") {
auto cluster = cluster_ptr_t(new cluster_t(my_device, 1));
auto sequencer = make_sequencer(4);
cluster->get_devices().put(my_device);
auto builder = diff_builder_t(*cluster);
REQUIRE(builder.upsert_folder("1234-5678", "some/path", "my-label").apply());
auto folder = cluster->get_folders().by_id("1234-5678");
auto local_folder = folder->get_folder_infos().by_device(*my_device);
auto add_file = [&](std::string_view name, proto::FileInfoType type = proto::FileInfoType::DIRECTORY) {
proto::FileInfo pr_fi;
proto::set_name(pr_fi, name);
proto::set_sequence(pr_fi, local_folder->get_max_sequence() + 1);
proto::set_type(pr_fi, type);
auto &v = proto::get_version(pr_fi);
auto modified_device = my_device->device_id().get_uint();
auto modified_version = 1;
proto::add_counters(v, proto::Counter(modified_device, modified_version));
proto::set_modified_s(pr_fi, modified_version);
auto file = model::file_info_t::create(sequencer->next_uuid(), pr_fi, local_folder.get()).value();
local_folder->add_strict(file);
return file;
};
auto dir = add_file("dir");
auto file = add_file("dir/b");
auto folder_entity = folder_entity_ptr_t(new folder_entity_t(folder));
auto p_dir = static_cast<presentation::file_presence_t *>(dir->get_augmentation().get());
auto p_file = static_cast<presentation::file_presence_t *>(file->get_augmentation().get());
CHECK(p_dir->get_child("a", true) == nullptr);
CHECK(p_dir->get_child("b", true) == p_file);
CHECK(p_dir->get_child("c", true) == nullptr);
CHECK(p_dir->get_child("a", false) == nullptr);
CHECK(p_dir->get_child("b", false) == nullptr);
CHECK(p_dir->get_child("c", false) == nullptr);
}
static bool _init = []() -> bool {
test::init_logging();
return true;
}();