diff --git a/src/fs/fs_supervisor.cpp b/src/fs/fs_supervisor.cpp index 29d8b244..ba1bb0f2 100644 --- a/src/fs/fs_supervisor.cpp +++ b/src/fs/fs_supervisor.cpp @@ -1,4 +1,4 @@ -/// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2019-2026 Ivan Baidakou #include "fs_supervisor.h" diff --git a/src/model/diff/local/scan_request.cpp b/src/model/diff/local/scan_request.cpp index 9d6104ea..a1d979b4 100644 --- a/src/model/diff/local/scan_request.cpp +++ b/src/model/diff/local/scan_request.cpp @@ -1,13 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2024 Ivan Baidakou +// SPDX-FileCopyrightText: 2024-2026 Ivan Baidakou #include "scan_request.h" #include "model/diff/cluster_visitor.h" using namespace syncspirit::model::diff::local; -scan_request_t::scan_request_t(std::string_view folder_id_) : folder_id{std::move(folder_id_)} { - LOG_DEBUG(log, "scan_request_t, folder = {}", folder_id); +scan_request_t::scan_request_t(std::string_view folder_id_, std::string_view sub_dir_) + : folder_id{folder_id_}, sub_dir(sub_dir_) { + LOG_DEBUG(log, "scan_request_t, folder = {}, sub_dir = {}", folder_id, sub_dir); } auto scan_request_t::visit(cluster_visitor_t &visitor, void *custom) const noexcept -> outcome::result { diff --git a/src/model/diff/local/scan_request.h b/src/model/diff/local/scan_request.h index a25fe1bd..76cbf0f7 100644 --- a/src/model/diff/local/scan_request.h +++ b/src/model/diff/local/scan_request.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou +// SPDX-FileCopyrightText: 2024-2026 Ivan Baidakou #pragma once @@ -9,10 +9,11 @@ namespace syncspirit::model::diff::local { struct SYNCSPIRIT_API scan_request_t final : cluster_diff_t { - scan_request_t(std::string_view folder_id); + scan_request_t(std::string_view folder_id, std::string_view sub_dir); outcome::result visit(cluster_visitor_t &, void *) const noexcept override; std::string folder_id; + std::string sub_dir; }; } // namespace syncspirit::model::diff::local diff --git a/src/model/diff/local/scan_start.cpp b/src/model/diff/local/scan_start.cpp index 8f242889..3163d85b 100644 --- a/src/model/diff/local/scan_start.cpp +++ b/src/model/diff/local/scan_start.cpp @@ -8,9 +8,9 @@ using namespace syncspirit::model::diff::local; -scan_start_t::scan_start_t(std::string_view folder_id_, const pt::ptime &at_) - : folder_id{std::move(folder_id_)}, at{at_} { - LOG_DEBUG(log, "scan_start_t, folder = {}", folder_id); +scan_start_t::scan_start_t(std::string_view folder_id_, std::string_view sub_dir_, const pt::ptime &at_) + : folder_id{folder_id_}, sub_dir{sub_dir_}, at{at_} { + LOG_DEBUG(log, "scan_start_t, folder = {}, sub_dir = {}", folder_id, sub_dir); } auto scan_start_t::apply_impl(apply_controller_t &controller, void *custom) const noexcept -> outcome::result { @@ -22,16 +22,32 @@ auto scan_start_t::apply_impl(apply_controller_t &controller, void *custom) cons auto local_device = cluster.get_device(); auto &local_folder = *folder->get_folder_infos().by_device(*local_device); auto &local_files = local_folder.get_file_infos(); + auto updated = false; for (auto &f : local_files) { auto &local_file = *f; if (local_file.is_local()) { - local_file.mark_local(false); - local_file.notify_update(); + auto name = local_file.get_name()->get_full_name(); + if (name.size() >= sub_dir.size()) { + bool match = true; + for (size_t i = 0; i < sub_dir.size(); ++i) { + if (sub_dir[i] != name[i]) { + match = false; + break; + } + } + if (match) { + local_file.mark_local(false); + local_file.notify_update(); + updated = true; + } + } } } - local_folder.notify_update(); - folder->notify_update(); + if (updated) { + local_folder.notify_update(); + folder->notify_update(); + } return r; } diff --git a/src/model/diff/local/scan_start.h b/src/model/diff/local/scan_start.h index 5c9e0ecc..3b393bff 100644 --- a/src/model/diff/local/scan_start.h +++ b/src/model/diff/local/scan_start.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou +// SPDX-FileCopyrightText: 2024-2026 Ivan Baidakou #pragma once @@ -12,12 +12,13 @@ namespace syncspirit::model::diff::local { namespace pt = boost::posix_time; struct SYNCSPIRIT_API scan_start_t final : cluster_diff_t { - scan_start_t(std::string_view folder_id, const pt::ptime &at); + scan_start_t(std::string_view folder_id, std::string_view sub_dir, const pt::ptime &at); outcome::result apply_impl(apply_controller_t &, void *) const noexcept override; outcome::result apply_forward(apply_controller_t &, void *) const noexcept override; outcome::result visit(cluster_visitor_t &, void *) const noexcept override; std::string folder_id; + std::string sub_dir; pt::ptime at; }; diff --git a/src/net/scheduler.cpp b/src/net/scheduler.cpp index b385d32a..fb398fc0 100644 --- a/src/net/scheduler.cpp +++ b/src/net/scheduler.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou +// SPDX-FileCopyrightText: 2024-2026 Ivan Baidakou #include "scheduler.h" #include "net/names.h" @@ -11,6 +11,29 @@ using namespace syncspirit::net; +enum class subpath_comparison_t { equal, includes, is_included, no_intersection }; + +static inline subpath_comparison_t compare(std::string_view a, std::string_view b) noexcept { + using C = subpath_comparison_t; + if (a.size() < b.size()) { + for (size_t i = 0; i < a.size(); ++i) { + if (a[i] != b[i]) { + return C::no_intersection; + } + } + return C::includes; + } else if (a.size() > b.size()) { + for (size_t i = 0; i < b.size(); ++i) { + if (a[i] != b[i]) { + return C::no_intersection; + } + } + return C::is_included; + } else { + return a == b ? C::equal : C::no_intersection; + } +} + void scheduler_t::configure(r::plugin::plugin_base_t &plugin) noexcept { r::actor_base_t::configure(plugin); plugin.with_casted([&](auto &p) { @@ -64,9 +87,35 @@ auto scheduler_t::operator()(const model::diff::modify::upsert_folder_t &diff, v auto scheduler_t::operator()(const model::diff::local::scan_request_t &diff, void *custom) noexcept -> outcome::result { - scan_queue.emplace_back(diff.folder_id); - if (!scan_in_progress) { - scan_next_or_schedule(); + bool updated = false; + bool ignore = false; + for (auto it = scan_queue.begin(); it != scan_queue.end();) { + if (it->folder_id == diff.folder_id) { + using C = subpath_comparison_t; + auto r = compare(it->sub_dir, diff.sub_dir); + if (r == C::equal || r == C::includes) { + LOG_DEBUG(log, "ignored scan request of '{}' of '{}' ", diff.sub_dir, diff.folder_id, it->sub_dir); + ignore = true; + } else if (r == subpath_comparison_t::is_included) { + ignore = true; + updated = false; + LOG_DEBUG(log, "updated scan request of '{}': '{}' -> '{}'", diff.folder_id, it->sub_dir, diff.sub_dir); + it->sub_dir = diff.sub_dir; + } else { + // NOOP. will be included + } + break; + } else { + ++it; + } + } + if (!ignore) { + scan_queue.emplace_back(scan_item_t{diff.folder_id, diff.sub_dir}); + } + if (!ignore || updated) { + if (!scan_in_progress) { + scan_next_or_schedule(); + } } return diff.visit_next(*this, custom); } @@ -110,20 +159,20 @@ void scheduler_t::scan_next_or_schedule() noexcept { auto scheduler_t::scan_next() noexcept -> schedule_option_t { while (!scan_queue.empty()) { - auto folder_id = std::move(scan_queue.front()); + auto item = std::move(scan_queue.front()); scan_queue.pop_front(); - auto folder = cluster->get_folders().by_id(folder_id); + auto folder = cluster->get_folders().by_id(item.folder_id); if (!folder || folder->is_synchronizing() || ((folder->is_suspended() && !folder->get_suspend_reason()))) { continue; } for (auto it = scan_queue.begin(); it != scan_queue.end();) { - if (*it == folder_id) { + if (it->folder_id == item.folder_id && it->sub_dir == item.sub_dir) { it = scan_queue.erase(it); } else { ++it; } } - initiate_scan(folder_id); + initiate_scan(item.folder_id, item.sub_dir); return {}; } @@ -148,12 +197,13 @@ auto scheduler_t::scan_next() noexcept -> schedule_option_t { } if (folder && deadline <= now) { - initiate_scan(folder->get_id()); + initiate_scan(folder->get_id(), {}); return {}; } if (folder) { auto folder_id = std::string(folder->get_id()); - return next_schedule_t{deadline - now, deadline, folder_id}; + auto item = scan_item_t{folder_id, {}}; + return next_schedule_t{std::move(item), deadline - now, deadline}; } return {}; } @@ -161,8 +211,8 @@ auto scheduler_t::scan_next() noexcept -> schedule_option_t { void scheduler_t::on_timer(r::request_id_t, bool cancelled) noexcept { timer_id = {}; if (!cancelled) { - auto &folder_id = schedule_option->folder_id; - if (auto folder = cluster->get_folders().by_id(folder_id); folder) { + auto item = std::move(schedule_option->item); + if (auto folder = cluster->get_folders().by_id(item.folder_id); folder) { bool do_scan = false; auto last_scan = folder->get_scan_finish(); if (!last_scan.is_not_a_date_time()) { @@ -180,17 +230,17 @@ void scheduler_t::on_timer(r::request_id_t, bool cancelled) noexcept { } } if (do_scan) { - initiate_scan(folder->get_id()); + initiate_scan(item.folder_id, item.sub_dir); } } } } -void scheduler_t::initiate_scan(std::string_view folder_id) noexcept { - LOG_DEBUG(log, "initiating folder '{}' scan", folder_id); +void scheduler_t::initiate_scan(std::string_view folder_id, std::string_view sub_dir) noexcept { + LOG_DEBUG(log, "initiating folder '{}' scan (sub_dir: {})", folder_id, sub_dir); auto diff = model::diff::cluster_diff_ptr_t{}; auto now = r::pt::microsec_clock::local_time(); - diff = new model::diff::local::scan_start_t(folder_id, now); + diff = new model::diff::local::scan_start_t(folder_id, sub_dir, now); send(coordinator, std::move(diff)); scan_in_progress = true; } diff --git a/src/net/scheduler.h b/src/net/scheduler.h index 5da51c8f..da2cf836 100644 --- a/src/net/scheduler.h +++ b/src/net/scheduler.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2024-2025 Ivan Baidakou +// SPDX-FileCopyrightText: 2024-2026 Ivan Baidakou #pragma once @@ -23,14 +23,21 @@ struct SYNCSPIRIT_API scheduler_t : public r::actor_base_t, private model::diff: void configure(r::plugin::plugin_base_t &plugin) noexcept override; + template auto &access() noexcept; + private: + struct scan_item_t { + std::string folder_id; + std::string sub_dir; + }; struct next_schedule_t { + scan_item_t item; r::pt::time_duration interval; r::pt::ptime at; - std::string folder_id; }; + using schedule_option_t = std::optional; - using scan_queue_t = std::list; + using scan_queue_t = std::list; void on_model_update(model::message::model_update_t &message) noexcept; void on_thread_ready(model::message::thread_ready_t &) noexcept; @@ -39,7 +46,7 @@ struct SYNCSPIRIT_API scheduler_t : public r::actor_base_t, private model::diff: schedule_option_t scan_next() noexcept; void scan_next_or_schedule() noexcept; - void initiate_scan(std::string_view folder_id) noexcept; + void initiate_scan(std::string_view folder_id, std::string_view sub_dir) noexcept; outcome::result operator()(const model::diff::modify::upsert_folder_t &, void *custom) noexcept override; outcome::result operator()(const model::diff::local::scan_request_t &, void *custom) noexcept override; diff --git a/src/ui-fltk/content/folder_table.cpp b/src/ui-fltk/content/folder_table.cpp index 12d7e100..4cddec68 100644 --- a/src/ui-fltk/content/folder_table.cpp +++ b/src/ui-fltk/content/folder_table.cpp @@ -859,6 +859,6 @@ void folder_table_t::on_rescan() { auto &sup = container.supervisor; auto diff = model::diff::cluster_diff_ptr_t{}; auto folder_id = description.get_folder()->get_id(); - diff = new model::diff::local::scan_request_t(folder_id); + diff = new model::diff::local::scan_request_t(folder_id, {}); sup.send_model(std::move(diff), this); } diff --git a/tests/085-scan-scheduler.cpp b/tests/085-scan-scheduler.cpp index 7f6ab1ad..6145be11 100644 --- a/tests/085-scan-scheduler.cpp +++ b/tests/085-scan-scheduler.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou +// SPDX-FileCopyrightText: 2019-2026 Ivan Baidakou #include "test-utils.h" #include "access.h" @@ -14,6 +14,20 @@ using namespace syncspirit; using namespace syncspirit::test; using namespace syncspirit::model; +namespace { +namespace ns { +namespace to { +struct scan_queue {}; +} // namespace to +} // namespace ns +} // namespace + +namespace syncspirit::net { + +template <> inline auto &scheduler_t::access() noexcept { return scan_queue; } + +} // namespace syncspirit::net + using Clock = r::pt::microsec_clock; struct fixture_t { @@ -122,6 +136,22 @@ void test_1_folder() { sup->do_process(); REQUIRE(folder->is_scanning()); } + + SECTION("subdir coverage") { + REQUIRE(sup->timers.size() == 0); + REQUIRE(target->access().size() == 0); + + builder.scan_request(folder_id, "a-dir").apply(*sup); + REQUIRE(target->access().size() == 1); + + builder.scan_request(folder_id, "a-dir/b-subdir").apply(*sup); + REQUIRE(target->access().size() == 1); + CHECK(target->access().front().sub_dir == "a-dir"); + + builder.scan_request(folder_id, "").apply(*sup); + REQUIRE(target->access().size() == 1); + CHECK(target->access().front().sub_dir == ""); + } } SECTION("suspending") { @@ -157,10 +187,10 @@ void test_2_folders() { builder.upsert_folder(db_folder_1) .upsert_folder(db_folder_2) .apply(*sup) - .scan_start(f2_id, C::local_time() - r::pt::seconds{rescan_min + 10}) + .scan_start(f2_id, {}, C::local_time() - r::pt::seconds{rescan_min + 10}) .scan_finish(f2_id, C::local_time() - r::pt::seconds{rescan_min + 1}) .apply(*sup) - .scan_start(f1_id, C::local_time() - r::pt::seconds{rescan_min + 10}) + .scan_start(f1_id, {}, C::local_time() - r::pt::seconds{rescan_min + 10}) .scan_finish(f1_id, C::local_time() - r::pt::seconds{rescan_min + 1}) .apply(*sup); diff --git a/tests/diff-builder.cpp b/tests/diff-builder.cpp index 2dfb2060..e6ad0858 100644 --- a/tests/diff-builder.cpp +++ b/tests/diff-builder.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou +// SPDX-FileCopyrightText: 2019-2026 Ivan Baidakou #include "diff-builder.h" #include "model/messages.h" @@ -314,9 +314,10 @@ diff_builder_t &diff_builder_t::remove_unknown_device(const model::pending_devic return assign(new diff::modify::remove_pending_device_t(device)); } -diff_builder_t &diff_builder_t::scan_start(std::string_view id, const r::pt::ptime &at) noexcept { +diff_builder_t &diff_builder_t::scan_start(std::string_view id, std::string_view sub_dir, + const r::pt::ptime &at) noexcept { auto final_at = at.is_not_a_date_time() ? r::pt::microsec_clock::local_time() : at; - return assign(new model::diff::local::scan_start_t(std::string(id), final_at)); + return assign(new model::diff::local::scan_start_t(id, sub_dir, final_at)); } diff_builder_t &diff_builder_t::scan_finish(std::string_view id, const r::pt::ptime &at) noexcept { @@ -324,8 +325,8 @@ diff_builder_t &diff_builder_t::scan_finish(std::string_view id, const r::pt::pt return assign(new model::diff::local::scan_finish_t(std::string(id), final_at)); } -diff_builder_t &diff_builder_t::scan_request(std::string_view id) noexcept { - return assign(new model::diff::local::scan_request_t(std::string(id))); +diff_builder_t &diff_builder_t::scan_request(std::string_view id, std::string_view sub_dir) noexcept { + return assign(new model::diff::local::scan_request_t(id, sub_dir)); } diff_builder_t &diff_builder_t::synchronization_start(std::string_view id) noexcept { diff --git a/tests/diff-builder.h b/tests/diff-builder.h index 19a7d8e7..e3080ac1 100644 --- a/tests/diff-builder.h +++ b/tests/diff-builder.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2019-2024 Ivan Baidakou +// SPDX-FileCopyrightText: 2019-2026 Ivan Baidakou #pragma once @@ -91,9 +91,9 @@ struct SYNCSPIRIT_TEST_API diff_builder_t : private model::diff::apply_controlle diff_builder_t &add_unknown_device(const model::device_id_t &device, db::SomeDevice db_device) noexcept; diff_builder_t &remove_ignored_device(const model::ignored_device_t &device) noexcept; diff_builder_t &remove_unknown_device(const model::pending_device_t &device) noexcept; - diff_builder_t &scan_start(std::string_view id, const r::pt::ptime & = {}) noexcept; + diff_builder_t &scan_start(std::string_view id, std::string_view sub_dir = {}, const r::pt::ptime & = {}) noexcept; diff_builder_t &scan_finish(std::string_view id, const r::pt::ptime & = {}) noexcept; - diff_builder_t &scan_request(std::string_view id) noexcept; + diff_builder_t &scan_request(std::string_view id, std::string_view sub_dir = {}) noexcept; diff_builder_t &synchronization_start(std::string_view id) noexcept; diff_builder_t &synchronization_finish(std::string_view id) noexcept; diff_builder_t &mark_reacheable(model::file_info_ptr_t peer_file, const model::folder_info_t &peer_fi,