mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-08-13 13:42:30 +00:00
Add directory.watch.ready for safe torrent watch folders
This commit is contained in:
committed by
Jari Sundell
parent
e03ab27cf0
commit
84229a85be
@@ -21,13 +21,16 @@ FileStatusCache::insert(const std::string& path) {
|
||||
|
||||
std::pair<iterator, bool> result = base_type::insert(value_type(path, file_status()));
|
||||
|
||||
// Return false if the file hasn't been modified since last time. We
|
||||
// use 'equal to' instead of 'greater than' since the file might
|
||||
// have been replaced by another file, and thus should be re-tried.
|
||||
if (!result.second && result.first->second.m_mtime == (uint32_t)fs.modified_time())
|
||||
// Return false if the file hasn't changed since last time. We use
|
||||
// 'equal to' instead of 'greater than' since the file might have
|
||||
// been replaced by another file, and thus should be re-tried.
|
||||
if (!result.second &&
|
||||
result.first->second.m_mtime == (uint32_t)fs.modified_time() &&
|
||||
result.first->second.m_size == (int64_t)fs.size())
|
||||
return false;
|
||||
|
||||
result.first->second.m_flags = 0;
|
||||
result.first->second.m_size = (int64_t)fs.size();
|
||||
result.first->second.m_mtime = fs.modified_time();
|
||||
|
||||
return true;
|
||||
@@ -41,7 +44,9 @@ FileStatusCache::prune() {
|
||||
torrent::utils::FileStat fs;
|
||||
iterator tmp = itr++;
|
||||
|
||||
if (!fs.update(expand_path(tmp->first)) || tmp->second.m_mtime != (uint32_t)fs.modified_time())
|
||||
if (!fs.update(expand_path(tmp->first)) ||
|
||||
tmp->second.m_mtime != (uint32_t)fs.modified_time() ||
|
||||
tmp->second.m_size != (int64_t)fs.size())
|
||||
base_type::erase(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace utils {
|
||||
|
||||
struct file_status {
|
||||
int m_flags;
|
||||
int64_t m_size;
|
||||
uint32_t m_mtime;
|
||||
};
|
||||
|
||||
@@ -32,14 +33,14 @@ public:
|
||||
|
||||
using base_type::erase;
|
||||
|
||||
// Insert and return true if the entry does not exist or the new
|
||||
// file's mtime is more recent.
|
||||
// Insert and return true if the entry does not exist or the file's
|
||||
// status has changed.
|
||||
bool insert(const std::string& path);
|
||||
|
||||
// Add a function for pruning a sorted list of paths.
|
||||
|
||||
// Function for pruning entries that no longer points to a file, or
|
||||
// has a different mtime.
|
||||
// has different status.
|
||||
void prune();
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "utils/watch_ready_queue.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <torrent/utils/file_stat.h>
|
||||
#include <torrent/utils/log.h>
|
||||
|
||||
#include "globals.h"
|
||||
#include "rpc/rpc_manager.h"
|
||||
|
||||
namespace utils {
|
||||
|
||||
namespace {
|
||||
|
||||
auto compare_next_time = [](const auto* lhs, const auto* rhs) {
|
||||
return lhs->next_time > rhs->next_time;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
WatchReadyQueue::WatchReadyQueue() {
|
||||
m_task_process.slot() = [this]() { process(); };
|
||||
}
|
||||
|
||||
WatchReadyQueue::~WatchReadyQueue() {
|
||||
torrent::this_thread::scheduler()->erase(&m_task_process);
|
||||
}
|
||||
|
||||
void
|
||||
WatchReadyQueue::push(const std::string& command, const std::string& path) {
|
||||
if (!m_active)
|
||||
return;
|
||||
|
||||
auto result = m_entries.emplace(path, Entry());
|
||||
auto& entry = result.first->second;
|
||||
|
||||
if (result.second) {
|
||||
entry.path = path;
|
||||
entry.first_seen = torrent::this_thread::cached_time();
|
||||
}
|
||||
|
||||
entry.command = command;
|
||||
entry.last_changed = torrent::this_thread::cached_time();
|
||||
update_status(&entry);
|
||||
|
||||
if (result.second) {
|
||||
update_next_time(&entry);
|
||||
push_entry(&entry);
|
||||
} else {
|
||||
update_entry(&entry);
|
||||
}
|
||||
|
||||
schedule();
|
||||
}
|
||||
|
||||
void
|
||||
WatchReadyQueue::shutdown() {
|
||||
m_active = false;
|
||||
m_entries.clear();
|
||||
m_entry_queue.clear();
|
||||
torrent::this_thread::scheduler()->erase(&m_task_process);
|
||||
}
|
||||
|
||||
void
|
||||
WatchReadyQueue::push_entry(Entry* entry) {
|
||||
m_entry_queue.push_back(entry);
|
||||
std::push_heap(m_entry_queue.begin(), m_entry_queue.end(), compare_next_time);
|
||||
}
|
||||
|
||||
void
|
||||
WatchReadyQueue::update_entry(Entry* entry) {
|
||||
update_next_time(entry);
|
||||
std::make_heap(m_entry_queue.begin(), m_entry_queue.end(), compare_next_time);
|
||||
}
|
||||
|
||||
void
|
||||
WatchReadyQueue::update_next_time(Entry* entry) {
|
||||
if (entry->regular && entry->size > 0) {
|
||||
entry->next_time = entry->last_changed + quiet_time;
|
||||
return;
|
||||
}
|
||||
|
||||
entry->next_time = std::min(torrent::this_thread::cached_time() + retry_time,
|
||||
entry->first_seen + stale_time);
|
||||
}
|
||||
|
||||
void
|
||||
WatchReadyQueue::update_status(Entry* entry) {
|
||||
torrent::utils::FileStat fs;
|
||||
bool regular = false;
|
||||
int64_t size = -1;
|
||||
time_t mtime = 0;
|
||||
|
||||
if (fs.update(expand_path(entry->path))) {
|
||||
regular = fs.is_regular();
|
||||
size = fs.size();
|
||||
mtime = fs.modified_time();
|
||||
}
|
||||
|
||||
if (entry->regular != regular || entry->size != size || entry->mtime != mtime) {
|
||||
entry->regular = regular;
|
||||
entry->size = size;
|
||||
entry->mtime = mtime;
|
||||
entry->last_changed = torrent::this_thread::cached_time();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WatchReadyQueue::process() {
|
||||
ready_list ready;
|
||||
|
||||
while (!m_entry_queue.empty() && m_entry_queue.front()->next_time <= torrent::this_thread::cached_time()) {
|
||||
std::pop_heap(m_entry_queue.begin(), m_entry_queue.end(), compare_next_time);
|
||||
Entry* entry = m_entry_queue.back();
|
||||
m_entry_queue.pop_back();
|
||||
|
||||
update_status(entry);
|
||||
|
||||
bool unchanged_entry = torrent::this_thread::cached_time() - entry->last_changed >= quiet_time;
|
||||
bool stale_entry = torrent::this_thread::cached_time() - entry->first_seen >= stale_time;
|
||||
bool nonempty_regular = entry->regular && entry->size > 0;
|
||||
|
||||
if (nonempty_regular && unchanged_entry) {
|
||||
ready.emplace_back(entry->command, entry->path);
|
||||
std::string path = entry->path;
|
||||
m_entries.erase(path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!nonempty_regular && stale_entry) {
|
||||
std::string path = entry->path;
|
||||
lt_log_print(torrent::LOG_SYSTEM, "system: Dropped unready watch file after timeout: \"%s\"", path.c_str());
|
||||
m_entries.erase(path);
|
||||
continue;
|
||||
}
|
||||
|
||||
update_next_time(entry);
|
||||
push_entry(entry);
|
||||
}
|
||||
|
||||
schedule();
|
||||
|
||||
for (const auto& item : ready)
|
||||
rpc::commands.call_catch(item.first.c_str(), rpc::make_target(), item.second);
|
||||
}
|
||||
|
||||
void
|
||||
WatchReadyQueue::schedule() {
|
||||
if (m_entry_queue.empty()) {
|
||||
torrent::this_thread::scheduler()->erase(&m_task_process);
|
||||
return;
|
||||
}
|
||||
|
||||
torrent::this_thread::scheduler()->update_wait_until(&m_task_process, m_entry_queue.front()->next_time);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef RTORRENT_UTILS_WATCH_READY_QUEUE_H
|
||||
#define RTORRENT_UTILS_WATCH_READY_QUEUE_H
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <torrent/utils/scheduler.h>
|
||||
|
||||
namespace utils {
|
||||
|
||||
class WatchReadyQueue {
|
||||
public:
|
||||
WatchReadyQueue();
|
||||
~WatchReadyQueue();
|
||||
|
||||
void push(const std::string& command, const std::string& path);
|
||||
void shutdown();
|
||||
bool empty() const { return m_entries.empty(); }
|
||||
|
||||
private:
|
||||
using time_type = std::chrono::microseconds;
|
||||
|
||||
static constexpr auto quiet_time = std::chrono::milliseconds(500);
|
||||
static constexpr auto retry_time = std::chrono::milliseconds(250);
|
||||
static constexpr auto stale_time = std::chrono::seconds(10);
|
||||
|
||||
using ready_list = std::vector<std::pair<std::string, std::string>>;
|
||||
|
||||
struct Entry {
|
||||
std::string command;
|
||||
std::string path;
|
||||
bool regular{};
|
||||
int64_t size{-1};
|
||||
time_t mtime{};
|
||||
time_type first_seen{};
|
||||
time_type last_changed{};
|
||||
time_type next_time{};
|
||||
};
|
||||
|
||||
void process();
|
||||
void push_entry(Entry* entry);
|
||||
void update_entry(Entry* entry);
|
||||
void update_next_time(Entry* entry);
|
||||
void update_status(Entry* entry);
|
||||
void schedule();
|
||||
|
||||
std::map<std::string, Entry> m_entries;
|
||||
std::vector<Entry*> m_entry_queue;
|
||||
torrent::utils::SchedulerEntry m_task_process;
|
||||
bool m_active{true};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user