6#include <unordered_map>
7#include <unordered_set>
10#define POOLSTL_STD_SUPPLEMENT 1
11#include <poolstl/poolstl.hpp>
19 auto entry_path = path;
20 std::string entry_extension;
21 while(entry_path.has_extension())
23 entry_extension = entry_path.extension().string() + entry_extension;
24 entry_path.replace_extension();
26 return entry_extension;
32 if(path.has_extension())
34 fs::create_directories(fs::path(path).parent_path(), err);
38 fs::create_directories(path, err);
45 const auto normalize_key = [](
const fs::path& path) -> std::string
47 return path.lexically_normal().generic_string();
52 const fs::path parent_old =
entry.last_path.parent_path();
53 const fs::path parent_new =
entry.path.parent_path();
54 if(parent_old == parent_new)
56 const fs::path key = parent_old.empty() ? watch_root : parent_old;
57 return normalize_key(key);
60 return normalize_key(watch_root);
63 if(
entry.type == fs::file_type::directory)
65 return normalize_key(
entry.path);
68 const fs::path parent =
entry.path.parent_path();
69 const fs::path key = parent.empty() ? watch_root : parent;
70 return normalize_key(key);
79 const std::vector<std::string>& synced_ext,
85 std::lock_guard<std::mutex> lock(mutex_);
86 auto&
mapping = mapping_[ref_ext];
99 std::lock_guard<std::mutex> lock(mutex_);
109 const auto id = watch_id_.exchange(0);
116auto syncer::get_mapping(
const std::string& ext) -> mapping
118 std::lock_guard<std::mutex> lock(mutex_);
119 auto it = mapping_.find(ext);
120 if(it != mapping_.end())
128auto syncer::get_on_created_callback(
const std::string& ext) -> on_entry_created_t
130 return get_mapping(ext).on_entry_created;
133auto syncer::get_on_modified_callback(
const std::string& ext) -> on_entry_modified_t
135 return get_mapping(ext).on_entry_modified;
138auto syncer::get_on_removed_callback(
const std::string& ext) -> on_entry_removed_t
140 return get_mapping(ext).on_entry_removed;
143auto syncer::get_on_renamed_callback(
const std::string& ext) -> on_entry_renamed_t
145 return get_mapping(ext).on_entry_renamed;
153 std::lock_guard<std::mutex> lock(mutex_);
154 reference_dir_ = reference_dir;
155 synced_dir_ = synced_dir;
156 reference_dir_.make_preferred();
157 synced_dir_.make_preferred();
162 const auto on_change = [
this, on_progress](
const auto&
entries,
bool is_initial_listing)
166 const bool is_directory = (
entry.type == fs::file_type::directory);
173 const auto synced_entries = this->get_synced_entries(
entry.path, is_directory);
175 for(
const auto& synced_entry : synced_entries)
180 auto callback = this->get_on_created_callback(entry_extension);
183 callback(entry_extension,
entry.path, synced_entries, is_initial_listing);
189 auto callback = this->get_on_modified_callback(entry_extension);
192 const auto synced_entries = this->get_synced_entries(
entry.path, is_directory);
193 callback(entry_extension,
entry.path, synced_entries, is_initial_listing);
199 const auto callback = this->get_on_removed_callback(entry_extension);
203 const auto synced_entries = this->get_synced_entries(
entry.path, is_directory);
204 callback(entry_extension,
entry.path, synced_entries);
210 const auto last_synced_entries = this->get_synced_entries(
entry.last_path, is_directory);
211 const auto synced_entries = this->get_synced_entries(
entry.path, is_directory);
212 auto callback = this->get_on_renamed_callback(entry_extension);
214 if(callback && synced_entries.size() == last_synced_entries.size())
216 std::vector<rename_pair_t> synced_renamed;
217 synced_renamed.reserve(synced_entries.size());
219 for(std::size_t i = 0; i < synced_entries.size(); ++i)
221 const auto& last_synced_entry = last_synced_entries[i];
222 const auto& synced_entry = synced_entries[i];
224 synced_renamed.emplace_back(std::move(p));
227 callback(entry_extension, p, synced_renamed);
236 const fs::path watch_root = get_watch_path();
243 const auto needs_per_folder_ordering = [&](
auto begin,
auto end) ->
bool
245 if(is_initial_listing)
249 for(
auto it = begin; it != end; ++it)
260 const auto run_entry_batch = [&](
auto begin,
auto end)
262 if(!needs_per_folder_ordering(begin, end))
264 std::for_each(poolstl::par, begin, end, process_entry);
268 std::unordered_map<std::string, std::vector<const fs::watcher::entry*>> groups;
269 groups.reserve(
static_cast<std::size_t
>(std::distance(begin, end)) / 4 + 1);
271 for(
auto it = begin; it != end; ++it)
274 groups[key].push_back(&(*it));
277 std::vector<std::vector<const fs::watcher::entry*>> group_list;
278 group_list.reserve(groups.size());
279 for(
auto& group : groups)
281 group_list.push_back(std::move(group.second));
284 const auto process_group = [&](
const std::vector<const fs::watcher::entry*>& group_entries)
288 process_entry(*
entry);
292 std::for_each(poolstl::par, group_list.begin(), group_list.end(), process_group);
302 if(!is_initial_listing || !on_progress)
308 constexpr size_t k_max_progress_updates = 16;
309 const size_t progress_stride =
310 std::max<size_t>(1, (
entries.size() + k_max_progress_updates - 1) / k_max_progress_updates);
312 size_t completed = 0;
313 for(
size_t offset = 0; offset <
entries.size(); offset += progress_stride)
315 const auto chunk_begin =
entries.begin() +
static_cast<std::ptrdiff_t
>(offset);
316 const auto chunk_end =
317 entries.begin() +
static_cast<std::ptrdiff_t
>(std::min(offset + progress_stride,
entries.size()));
319 run_entry_batch(chunk_begin, chunk_end);
321 completed = std::min(offset + progress_stride,
entries.size());
415 using namespace std::literals;
416 const fs::path watch_dir = get_watch_path();
420auto syncer::get_synced_entries(
const fs::path& path,
bool is_directory) -> std::vector<fs::path>
422 std::vector<fs::path> synced_entries;
423 auto synced_dir = get_synced_directory(path);
427 synced_entries.emplace_back(std::move(synced_dir));
431 auto entry_path = path;
432 std::string entry_extension;
433 while(entry_path.has_extension())
435 auto ext = entry_path.extension().string() + entry_extension;
436 entry_extension = ext;
437 entry_path.replace_extension();
441 std::lock_guard<std::mutex> lock(mutex_);
442 auto it = mapping_.find(entry_extension);
443 if(it != mapping_.end())
445 const auto& mapping = it->second;
446 const auto& extensions = mapping.extensions;
448 synced_entries.reserve(extensions.size());
449 for(
const auto& cache_ext : extensions)
451 fs::path file = synced_dir / path.filename();
452 file.concat(cache_ext);
454 synced_entries.emplace_back(std::move(file));
460 return synced_entries;
463auto syncer::get_watch_path() -> fs::path
465 std::lock_guard<std::mutex> lock(mutex_);
466 const fs::path watch_dir = reference_dir_;
470auto syncer::get_synced_directory(
const fs::path& path) -> fs::path
475 std::lock_guard<std::mutex> lock(mutex_);
476 result =
fs::replace(path, reference_dir_, synced_dir_);
480 if(fs::is_directory(path, err) || !path.has_extension())
485 return result.parent_path();
A filter that combines include and exclude patterns for file/directory filtering.
std::pair< fs::path, fs::path > rename_pair_t
std::function< void(const std::string &, const fs::path &, const std::vector< fs::path > &, bool)> on_entry_created_t
std::function< void(const std::string &, const fs::path &, const std::vector< fs::path > &, bool)> on_entry_modified_t
void unsync()
Stops syncing.
void sync(const fs::path &reference_dir, const fs::path &synced_dir, const on_sync_progress_t &on_progress=nullptr)
Start syncing the synced_dir with reference to the reference_dir i.e changes that occur in the refere...
void set_mapping(const std::string &ref_ext, const std::vector< std::string > &synced_ext, on_entry_created_t on_entry_created, on_entry_modified_t on_entry_modified, on_entry_removed_t on_entry_removed, on_entry_renamed_t on_entry_renamed)
Remaps a specific extension of the reference directory to extensions of the synced directory....
std::function< void(const std::string &, const rename_pair_t &, const std::vector< rename_pair_t > &)> on_entry_renamed_t
void set_directory_mapping(on_entry_created_t on_entry_created, on_entry_modified_t on_entry_modified, on_entry_removed_t on_entry_removed, on_entry_renamed_t on_entry_renamed)
std::function< void(const std::string &, const fs::path &, const std::vector< fs::path > &)> on_entry_removed_t
std::function< void(size_t completed, size_t total, const std::string ¤t_job)> on_sync_progress_t
static void unwatch(std::uint64_t key)
Un-watches a previously registered file or directory.
static auto watch(const fs::path &path, const pattern_filter &filter, bool recursive, bool initial_list, clock_t::duration poll_interval, notify_callback callback, const std::string &watcher_name="") -> std::uint64_t
Watches a file or directory for modification and call back the specified std::function....
static void ensure_directory_exists(const fs::path &path)
static auto extract_entry_extension(const fs::path &path) -> std::string
path replace(const path &_path, const path &_sequence, const path &_new_sequence)
Replacing any occurences of the specified path sequence with another.
static auto entry_serialization_key(const fs::path &watch_root, const fs::watcher::entry &entry) -> std::string
Paths that share a key are processed on one worker in order (create -> rename, etc....
std::vector< std::string > extensions
on_entry_removed_t on_entry_removed
on_entry_modified_t on_entry_modified
on_entry_renamed_t on_entry_renamed
on_entry_created_t on_entry_created