mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-10-15 13:46:17 +00:00
Run multiple session save requests in parallel.
This commit is contained in:
+2
-2
@@ -13,8 +13,6 @@ libsub_root_a_SOURCES = \
|
|||||||
core/download_factory.h \
|
core/download_factory.h \
|
||||||
core/download_list.cc \
|
core/download_list.cc \
|
||||||
core/download_list.h \
|
core/download_list.h \
|
||||||
core/download_store.cc \
|
|
||||||
core/download_store.h \
|
|
||||||
core/http_queue.cc \
|
core/http_queue.cc \
|
||||||
core/http_queue.h \
|
core/http_queue.h \
|
||||||
core/manager.cc \
|
core/manager.cc \
|
||||||
@@ -128,6 +126,8 @@ libsub_root_a_SOURCES = \
|
|||||||
scgi/thread_scgi.cc \
|
scgi/thread_scgi.cc \
|
||||||
scgi/thread_scgi.h \
|
scgi/thread_scgi.h \
|
||||||
\
|
\
|
||||||
|
session/download_storer.cc \
|
||||||
|
session/download_storer.h \
|
||||||
session/session_manager.cc \
|
session/session_manager.cc \
|
||||||
session/session_manager.h \
|
session/session_manager.h \
|
||||||
session/thread_session.cc \
|
session/thread_session.cc \
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
#include <torrent/utils/option_strings.h>
|
#include <torrent/utils/option_strings.h>
|
||||||
|
|
||||||
#include "core/download.h"
|
#include "core/download.h"
|
||||||
#include "core/download_store.h"
|
|
||||||
#include "core/manager.h"
|
#include "core/manager.h"
|
||||||
#include "rpc/parse.h"
|
#include "rpc/parse.h"
|
||||||
|
#include "session/session_manager.h"
|
||||||
|
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include "control.h"
|
#include "control.h"
|
||||||
@@ -700,8 +700,8 @@ initialize_command_download() {
|
|||||||
CMD2_DL_V ("d.erase", std::bind(&core::DownloadList::erase_ptr, control->core()->download_list(), std::placeholders::_1));
|
CMD2_DL_V ("d.erase", std::bind(&core::DownloadList::erase_ptr, control->core()->download_list(), std::placeholders::_1));
|
||||||
CMD2_DL_V ("d.check_hash", std::bind(&core::DownloadList::check_hash, control->core()->download_list(), std::placeholders::_1));
|
CMD2_DL_V ("d.check_hash", std::bind(&core::DownloadList::check_hash, control->core()->download_list(), std::placeholders::_1));
|
||||||
|
|
||||||
CMD2_DL ("d.save_resume", std::bind(&core::DownloadStore::save_resume, control->core()->download_store(), std::placeholders::_1));
|
CMD2_DL_V ("d.save_resume", [](core::Download* download, auto) { session_thread::manager()->save_resume_download(download); });
|
||||||
CMD2_DL ("d.save_full_session", std::bind(&core::DownloadStore::save_full, control->core()->download_store(), std::placeholders::_1));
|
CMD2_DL_V ("d.save_full_session", [](core::Download* download, auto) { session_thread::manager()->save_full_download(download); });
|
||||||
|
|
||||||
CMD2_DL_V ("d.update_priorities", CMD2_ON_DL(update_priorities));
|
CMD2_DL_V ("d.update_priorities", CMD2_ON_DL(update_priorities));
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
#include "core/download.h"
|
#include "core/download.h"
|
||||||
#include "core/download_list.h"
|
#include "core/download_list.h"
|
||||||
#include "core/download_store.h"
|
|
||||||
#include "core/manager.h"
|
#include "core/manager.h"
|
||||||
#include "rak/string_manip.h"
|
#include "rak/string_manip.h"
|
||||||
#include "rpc/parse_commands.h"
|
#include "rpc/parse_commands.h"
|
||||||
@@ -212,7 +211,8 @@ initialize_command_local() {
|
|||||||
CMD2_ANY_VALUE_V ("system.files.advise_random.set", std::bind(&FM_t::set_advise_random, fileManager, std::placeholders::_2));
|
CMD2_ANY_VALUE_V ("system.files.advise_random.set", std::bind(&FM_t::set_advise_random, fileManager, std::placeholders::_2));
|
||||||
CMD2_ANY ("system.files.advise_random.hashing", std::bind(&FM_t::advise_random_hashing, fileManager));
|
CMD2_ANY ("system.files.advise_random.hashing", std::bind(&FM_t::advise_random_hashing, fileManager));
|
||||||
CMD2_ANY_VALUE_V ("system.files.advise_random.hashing.set", std::bind(&FM_t::set_advise_random_hashing, fileManager, std::placeholders::_2));
|
CMD2_ANY_VALUE_V ("system.files.advise_random.hashing.set", std::bind(&FM_t::set_advise_random_hashing, fileManager, std::placeholders::_2));
|
||||||
CMD2_VAR_BOOL ("system.files.session.fdatasync", true);
|
CMD2_ANY ("system.files.session.fdatasync", [](auto, auto) { return session_thread::manager()->use_fsyncdisk(); });
|
||||||
|
CMD2_ANY_VALUE_V ("system.files.session.fdatasync.set", [](auto, auto& value) { return session_thread::manager()->set_use_fsyncdisk(value); });
|
||||||
|
|
||||||
CMD2_ANY ("system.files.opened_counter", std::bind(&FM_t::files_opened_counter, fileManager));
|
CMD2_ANY ("system.files.opened_counter", std::bind(&FM_t::files_opened_counter, fileManager));
|
||||||
CMD2_ANY ("system.files.closed_counter", std::bind(&FM_t::files_closed_counter, fileManager));
|
CMD2_ANY ("system.files.closed_counter", std::bind(&FM_t::files_closed_counter, fileManager));
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include <torrent/utils/directory_events.h>
|
#include <torrent/utils/directory_events.h>
|
||||||
|
|
||||||
#include "core/dht_manager.h"
|
#include "core/dht_manager.h"
|
||||||
#include "core/download_store.h"
|
|
||||||
#include "core/http_queue.h"
|
#include "core/http_queue.h"
|
||||||
#include "core/manager.h"
|
#include "core/manager.h"
|
||||||
#include "core/view_manager.h"
|
#include "core/view_manager.h"
|
||||||
@@ -59,9 +58,7 @@ Control::~Control() {
|
|||||||
|
|
||||||
void
|
void
|
||||||
Control::initialize() {
|
Control::initialize() {
|
||||||
session_thread::manager()->start();
|
|
||||||
session_thread::thread()->start_thread();
|
session_thread::thread()->start_thread();
|
||||||
|
|
||||||
scgi_thread::thread()->start_thread();
|
scgi_thread::thread()->start_thread();
|
||||||
|
|
||||||
display::Canvas::initialize();
|
display::Canvas::initialize();
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
#include "control.h"
|
#include "control.h"
|
||||||
#include "download.h"
|
#include "download.h"
|
||||||
#include "download_store.h"
|
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "rpc/parse_commands.h"
|
#include "rpc/parse_commands.h"
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include "control.h"
|
#include "control.h"
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include "core/download.h"
|
#include "core/download.h"
|
||||||
#include "core/download_store.h"
|
|
||||||
#include "core/http_queue.h"
|
#include "core/http_queue.h"
|
||||||
#include "core/manager.h"
|
#include "core/manager.h"
|
||||||
#include "rpc/parse_commands.h"
|
#include "rpc/parse_commands.h"
|
||||||
|
|||||||
+11
-15
@@ -22,10 +22,10 @@
|
|||||||
#include "view.h"
|
#include "view.h"
|
||||||
#include "view_manager.h"
|
#include "view_manager.h"
|
||||||
|
|
||||||
#include "dht_manager.h"
|
#include "core/dht_manager.h"
|
||||||
#include "download.h"
|
#include "core/download.h"
|
||||||
#include "download_list.h"
|
#include "core/download_list.h"
|
||||||
#include "download_store.h"
|
#include "session/session_manager.h"
|
||||||
#include "ui/root.h"
|
#include "ui/root.h"
|
||||||
|
|
||||||
#define DL_TRIGGER_EVENT(download, event_name) \
|
#define DL_TRIGGER_EVENT(download, event_name) \
|
||||||
@@ -68,10 +68,8 @@ DownloadList::clear() {
|
|||||||
|
|
||||||
void
|
void
|
||||||
DownloadList::session_save() {
|
DownloadList::session_save() {
|
||||||
unsigned int c = std::count_if(begin(), end(), [&](Download* d) { return control->core()->download_store()->save_resume(d); });
|
for (auto& download : *this)
|
||||||
|
session_thread::manager()->save_resume_download(download);
|
||||||
if (c != size())
|
|
||||||
lt_log_print(torrent::LOG_ERROR, "Failed to save session torrents.");
|
|
||||||
|
|
||||||
control->dht_manager()->save_dht_cache();
|
control->dht_manager()->save_dht_cache();
|
||||||
control->ui()->save_input_history();
|
control->ui()->save_input_history();
|
||||||
@@ -199,10 +197,10 @@ DownloadList::erase(iterator itr) {
|
|||||||
(*itr)->set_hash_failed(true);
|
(*itr)->set_hash_failed(true);
|
||||||
|
|
||||||
close(*itr);
|
close(*itr);
|
||||||
|
session_thread::manager()->remove_download(*itr);
|
||||||
control->core()->download_store()->remove(*itr);
|
|
||||||
|
|
||||||
DL_TRIGGER_EVENT(*itr, "event.download.erased");
|
DL_TRIGGER_EVENT(*itr, "event.download.erased");
|
||||||
|
|
||||||
for (auto v : *control->view_manager())
|
for (auto v : *control->view_manager())
|
||||||
v->erase(*itr);
|
v->erase(*itr);
|
||||||
|
|
||||||
@@ -639,10 +637,8 @@ DownloadList::confirm_finished(Download* download) {
|
|||||||
// the download.
|
// the download.
|
||||||
//
|
//
|
||||||
// Obsolete.
|
// Obsolete.
|
||||||
if (!download->is_active() && rpc::call_command_value("session.on_completion") != 0) {
|
if (!download->is_active() && rpc::call_command_value("session.on_completion") != 0)
|
||||||
// torrent::resume_save_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume"));
|
session_thread::manager()->save_resume_download(download);
|
||||||
control->core()->download_store()->save_resume(download);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the completed request before resuming so we don't reset the
|
// Send the completed request before resuming so we don't reset the
|
||||||
// up/downloaded baseline.
|
// up/downloaded baseline.
|
||||||
@@ -655,7 +651,7 @@ DownloadList::confirm_finished(Download* download) {
|
|||||||
|
|
||||||
if (find(infohash) == end())
|
if (find(infohash) == end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// if (download->resume_flags() != ~uint32_t())
|
// if (download->resume_flags() != ~uint32_t())
|
||||||
// throw torrent::internal_error("DownloadList::confirm_finished(...) download->resume_flags() != ~uint32_t().");
|
// throw torrent::internal_error("DownloadList::confirm_finished(...) download->resume_flags() != ~uint32_t().");
|
||||||
|
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
// DownloadStore handles the saving and listing of session torrents.
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sstream>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <rak/error_number.h>
|
|
||||||
#include <rak/path.h>
|
|
||||||
#include <rak/string_manip.h>
|
|
||||||
#include <torrent/utils/resume.h>
|
|
||||||
#include <torrent/object.h>
|
|
||||||
#include <torrent/exceptions.h>
|
|
||||||
#include <torrent/torrent.h>
|
|
||||||
#include <torrent/rate.h>
|
|
||||||
#include <torrent/object_stream.h>
|
|
||||||
|
|
||||||
#include "download.h"
|
|
||||||
#include "download_store.h"
|
|
||||||
#include "rpc/parse_commands.h"
|
|
||||||
#include "session/session_manager.h"
|
|
||||||
#include "utils/directory.h"
|
|
||||||
|
|
||||||
namespace core {
|
|
||||||
|
|
||||||
bool
|
|
||||||
DownloadStore::save(Download* d, int flags) {
|
|
||||||
if (!session_thread::manager()->is_used())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
torrent::Object* resume_base = &d->download()->bencode()->get_key("libtorrent_resume");
|
|
||||||
torrent::Object* rtorrent_base = &d->download()->bencode()->get_key("rtorrent");
|
|
||||||
|
|
||||||
// Move this somewhere else?
|
|
||||||
rtorrent_base->insert_key("chunks_done", d->download()->file_list()->completed_chunks());
|
|
||||||
rtorrent_base->insert_key("chunks_wanted", d->download()->data()->wanted_chunks());
|
|
||||||
rtorrent_base->insert_key("total_uploaded", d->info()->up_rate()->total());
|
|
||||||
rtorrent_base->insert_key("total_downloaded", d->info()->down_rate()->total());
|
|
||||||
|
|
||||||
// Don't save for completed torrents when we've cleared the uncertain_pieces.
|
|
||||||
torrent::resume_save_progress(*d->download(), *resume_base);
|
|
||||||
torrent::resume_save_uncertain_pieces(*d->download(), *resume_base);
|
|
||||||
|
|
||||||
torrent::resume_save_addresses(*d->download(), *resume_base);
|
|
||||||
torrent::resume_save_file_priorities(*d->download(), *resume_base);
|
|
||||||
torrent::resume_save_tracker_settings(*d->download(), *resume_base);
|
|
||||||
|
|
||||||
// Temp fixing of all flags, move to a better place:
|
|
||||||
resume_base->set_flags(torrent::Object::flag_session_data);
|
|
||||||
rtorrent_base->set_flags(torrent::Object::flag_session_data);
|
|
||||||
|
|
||||||
auto download_stream = std::unique_ptr<std::stringstream>();
|
|
||||||
auto resume_stream = std::make_unique<std::stringstream>();
|
|
||||||
auto rtorrent_stream = std::make_unique<std::stringstream>();
|
|
||||||
|
|
||||||
if (!(flags & flag_skip_static)) {
|
|
||||||
download_stream = std::make_unique<std::stringstream>();
|
|
||||||
torrent::object_write_bencode(&*download_stream, d->bencode(), torrent::Object::flag_session_data);
|
|
||||||
|
|
||||||
if (!download_stream->good())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
torrent::object_write_bencode(&*resume_stream, resume_base, 0);
|
|
||||||
|
|
||||||
// TODO: Add logging.
|
|
||||||
if (!resume_stream->good())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
torrent::object_write_bencode(&*rtorrent_stream, rtorrent_base, 0);
|
|
||||||
|
|
||||||
if (!rtorrent_stream->good())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto base_filename = create_filename(d);
|
|
||||||
|
|
||||||
session_thread::manager()->save_download(d, base_filename, std::move(download_stream), std::move(resume_stream), std::move(rtorrent_stream));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
DownloadStore::remove(Download* d) {
|
|
||||||
session_thread::manager()->remove_download(d, create_filename(d));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This also needs to check that it isn't a directory.
|
|
||||||
bool
|
|
||||||
not_correct_format(const utils::directory_entry& entry) {
|
|
||||||
return !DownloadStore::is_correct_format(entry.s_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
utils::Directory
|
|
||||||
DownloadStore::get_formated_entries() {
|
|
||||||
if (!session_thread::manager()->is_used())
|
|
||||||
return utils::Directory();
|
|
||||||
|
|
||||||
utils::Directory d(session_thread::manager()->path());
|
|
||||||
|
|
||||||
if (!d.update(utils::Directory::update_hide_dot))
|
|
||||||
throw torrent::storage_error("core::DownloadStore::update() could not open session directory: " + session_thread::manager()->path());
|
|
||||||
|
|
||||||
d.erase(std::remove_if(d.begin(), d.end(), [&](const utils::directory_entry& entry) { return not_correct_format(entry); }), d.end());
|
|
||||||
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
DownloadStore::is_correct_format(const std::string& f) {
|
|
||||||
if (f.size() != 48 || f.substr(40) != ".torrent")
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (std::string::const_iterator itr = f.begin(); itr != f.end() - 8; ++itr)
|
|
||||||
if (!(*itr >= '0' && *itr <= '9') &&
|
|
||||||
!(*itr >= 'A' && *itr <= 'F'))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string
|
|
||||||
DownloadStore::create_filename(Download* d) {
|
|
||||||
return session_thread::manager()->path() + rak::transform_hex(d->info()->hash().begin(), d->info()->hash().end()) + ".torrent";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#ifndef RTORRENT_CORE_DOWNLOAD_STORE_H
|
|
||||||
#define RTORRENT_CORE_DOWNLOAD_STORE_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <torrent/common.h>
|
|
||||||
|
|
||||||
namespace utils {
|
|
||||||
class Directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace core {
|
|
||||||
|
|
||||||
class Download;
|
|
||||||
|
|
||||||
class DownloadStore {
|
|
||||||
public:
|
|
||||||
static const int flag_skip_static = 0x1;
|
|
||||||
|
|
||||||
bool save(Download* d, int flags);
|
|
||||||
bool save_full(Download* d) { return save(d, 0); }
|
|
||||||
bool save_resume(Download* d) { return save(d, flag_skip_static); }
|
|
||||||
void remove(Download* d);
|
|
||||||
|
|
||||||
// Currently shows all entries in the correct format.
|
|
||||||
utils::Directory get_formated_entries();
|
|
||||||
|
|
||||||
static bool is_correct_format(const std::string& f);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string create_filename(Download* d);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
+2
-4
@@ -32,7 +32,6 @@
|
|||||||
#include "control.h"
|
#include "control.h"
|
||||||
#include "core/download.h"
|
#include "core/download.h"
|
||||||
#include "core/download_factory.h"
|
#include "core/download_factory.h"
|
||||||
#include "core/download_store.h"
|
|
||||||
#include "core/http_queue.h"
|
#include "core/http_queue.h"
|
||||||
#include "core/manager.h"
|
#include "core/manager.h"
|
||||||
#include "core/view.h"
|
#include "core/view.h"
|
||||||
@@ -50,11 +49,10 @@ Manager::push_log(const char* msg) {
|
|||||||
m_log_complete->lock_and_push_log(msg, strlen(msg), 0);
|
m_log_complete->lock_and_push_log(msg, strlen(msg), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Manager::Manager() :
|
Manager::Manager()
|
||||||
m_log_important(torrent::log_open_log_buffer("important")),
|
: m_log_important(torrent::log_open_log_buffer("important")),
|
||||||
m_log_complete(torrent::log_open_log_buffer("complete")) {
|
m_log_complete(torrent::log_open_log_buffer("complete")) {
|
||||||
|
|
||||||
m_download_store = std::make_unique<DownloadStore>();
|
|
||||||
m_download_list = std::make_unique<DownloadList>();
|
m_download_list = std::make_unique<DownloadList>();
|
||||||
m_file_status_cache = std::make_unique<FileStatusCache>();
|
m_file_status_cache = std::make_unique<FileStatusCache>();
|
||||||
m_http_queue = std::make_unique<HttpQueue>();
|
m_http_queue = std::make_unique<HttpQueue>();
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ class FileStatusCache;
|
|||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
class DownloadStore;
|
|
||||||
class HttpQueue;
|
class HttpQueue;
|
||||||
|
|
||||||
typedef std::map<std::string, torrent::ThrottlePair> ThrottleMap;
|
typedef std::map<std::string, torrent::ThrottlePair> ThrottleMap;
|
||||||
@@ -37,7 +36,6 @@ public:
|
|||||||
~Manager();
|
~Manager();
|
||||||
|
|
||||||
DownloadList* download_list() { return m_download_list.get(); }
|
DownloadList* download_list() { return m_download_list.get(); }
|
||||||
DownloadStore* download_store() { return m_download_store.get(); }
|
|
||||||
FileStatusCache* file_status_cache() { return m_file_status_cache.get(); }
|
FileStatusCache* file_status_cache() { return m_file_status_cache.get(); }
|
||||||
|
|
||||||
HttpQueue* http_queue() { return m_http_queue.get(); }
|
HttpQueue* http_queue() { return m_http_queue.get(); }
|
||||||
@@ -95,7 +93,6 @@ private:
|
|||||||
void receive_hashing_changed();
|
void receive_hashing_changed();
|
||||||
|
|
||||||
std::unique_ptr<DownloadList> m_download_list;
|
std::unique_ptr<DownloadList> m_download_list;
|
||||||
std::unique_ptr<DownloadStore> m_download_store;
|
|
||||||
std::unique_ptr<FileStatusCache> m_file_status_cache;
|
std::unique_ptr<FileStatusCache> m_file_status_cache;
|
||||||
std::unique_ptr<HttpQueue> m_http_queue;
|
std::unique_ptr<HttpQueue> m_http_queue;
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -22,7 +22,6 @@
|
|||||||
#include "core/dht_manager.h"
|
#include "core/dht_manager.h"
|
||||||
#include "core/download.h"
|
#include "core/download.h"
|
||||||
#include "core/download_factory.h"
|
#include "core/download_factory.h"
|
||||||
#include "core/download_store.h"
|
|
||||||
#include "core/manager.h"
|
#include "core/manager.h"
|
||||||
#include "display/canvas.h"
|
#include "display/canvas.h"
|
||||||
#include "display/window.h"
|
#include "display/window.h"
|
||||||
@@ -32,7 +31,9 @@
|
|||||||
#include "rpc/command_scheduler_item.h"
|
#include "rpc/command_scheduler_item.h"
|
||||||
#include "rpc/parse_commands.h"
|
#include "rpc/parse_commands.h"
|
||||||
#include "scgi/thread_scgi.h"
|
#include "scgi/thread_scgi.h"
|
||||||
|
#include "session/download_storer.h"
|
||||||
#include "session/thread_session.h"
|
#include "session/thread_session.h"
|
||||||
|
#include "session/session_manager.h"
|
||||||
#include "ui/root.h"
|
#include "ui/root.h"
|
||||||
#include "utils/directory.h"
|
#include "utils/directory.h"
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ initialize_rpc_slots() {
|
|||||||
|
|
||||||
void
|
void
|
||||||
load_session_torrents() {
|
load_session_torrents() {
|
||||||
utils::Directory entries = control->core()->download_store()->get_formated_entries();
|
utils::Directory entries = session::DownloadStorer::get_formated_entries(session_thread::manager()->path());
|
||||||
|
|
||||||
for (const auto& entry : entries) {
|
for (const auto& entry : entries) {
|
||||||
// We don't really support session torrents that are links. These
|
// We don't really support session torrents that are links. These
|
||||||
|
|||||||
@@ -38,11 +38,6 @@ ThreadScgi::thread_scgi() {
|
|||||||
return m_thread_scgi;
|
return m_thread_scgi;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove '= 0'.
|
|
||||||
void
|
|
||||||
ThreadScgi::init_thread() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
ThreadScgi::cleanup_thread() {
|
ThreadScgi::cleanup_thread() {
|
||||||
if (m_scgi != nullptr)
|
if (m_scgi != nullptr)
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ public:
|
|||||||
static void destroy_thread();
|
static void destroy_thread();
|
||||||
static ThreadScgi* thread_scgi();
|
static ThreadScgi* thread_scgi();
|
||||||
|
|
||||||
const char* name() const override { return "rtorrent-scgi"; }
|
const char* name() const override { return "rtorrent-scgi"; }
|
||||||
|
|
||||||
void init_thread() override;
|
|
||||||
void cleanup_thread() override;
|
|
||||||
|
|
||||||
rpc::SCgi* scgi();
|
rpc::SCgi* scgi();
|
||||||
bool set_scgi(rpc::SCgi* scgi);
|
bool set_scgi(rpc::SCgi* scgi);
|
||||||
@@ -37,6 +34,8 @@ protected:
|
|||||||
|
|
||||||
static auto internal_thread_scgi() { return m_thread_scgi; }
|
static auto internal_thread_scgi() { return m_thread_scgi; }
|
||||||
|
|
||||||
|
void cleanup_thread() override;
|
||||||
|
|
||||||
void call_events() override;
|
void call_events() override;
|
||||||
std::chrono::microseconds next_timeout() override;
|
std::chrono::microseconds next_timeout() override;
|
||||||
|
|
||||||
@@ -46,10 +45,8 @@ private:
|
|||||||
|
|
||||||
static ThreadScgi* m_thread_scgi;
|
static ThreadScgi* m_thread_scgi;
|
||||||
|
|
||||||
std::atomic<rpc::SCgi*> m_scgi{nullptr};
|
std::atomic<rpc::SCgi*> m_scgi{nullptr};
|
||||||
std::string m_rpc_log_filename;
|
std::string m_rpc_log_filename;
|
||||||
|
|
||||||
// std::unique_ptr<ScgiManager> m_manager;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace scgi
|
} // namespace scgi
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "download_storer.h"
|
||||||
|
|
||||||
|
#include <torrent/exceptions.h>
|
||||||
|
#include <torrent/object.h>
|
||||||
|
#include <torrent/object_stream.h>
|
||||||
|
#include <torrent/utils/log.h>
|
||||||
|
#include <torrent/utils/resume.h>
|
||||||
|
|
||||||
|
#include "globals.h"
|
||||||
|
#include "core/download.h"
|
||||||
|
#include "utils/directory.h"
|
||||||
|
|
||||||
|
namespace session {
|
||||||
|
|
||||||
|
DownloadStorer::DownloadStorer(core::Download* download)
|
||||||
|
: m_download(download) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
DownloadStorer::build_streams(bool skip_static) {
|
||||||
|
auto* download = m_download->download();
|
||||||
|
|
||||||
|
auto& resume_base = download->bencode()->get_key("libtorrent_resume");
|
||||||
|
auto& rtorrent_base = download->bencode()->get_key("rtorrent");
|
||||||
|
|
||||||
|
rtorrent_base.insert_key("chunks_done", download->file_list()->completed_chunks());
|
||||||
|
rtorrent_base.insert_key("chunks_wanted", download->data()->wanted_chunks());
|
||||||
|
rtorrent_base.insert_key("total_uploaded", m_download->info()->up_rate()->total());
|
||||||
|
rtorrent_base.insert_key("total_downloaded", m_download->info()->down_rate()->total());
|
||||||
|
|
||||||
|
// Don't save for completed torrents when we've cleared the uncertain_pieces.
|
||||||
|
torrent::resume_save_progress(*download, resume_base);
|
||||||
|
torrent::resume_save_uncertain_pieces(*download, resume_base);
|
||||||
|
|
||||||
|
torrent::resume_save_addresses(*download, resume_base);
|
||||||
|
torrent::resume_save_file_priorities(*download, resume_base);
|
||||||
|
torrent::resume_save_tracker_settings(*download, resume_base);
|
||||||
|
|
||||||
|
// Temp fixing of all flags, move to a better place:
|
||||||
|
resume_base.set_flags(torrent::Object::flag_session_data);
|
||||||
|
rtorrent_base.set_flags(torrent::Object::flag_session_data);
|
||||||
|
|
||||||
|
auto torrent_stream = std::unique_ptr<std::stringstream>();
|
||||||
|
auto resume_stream = std::make_unique<std::stringstream>();
|
||||||
|
auto rtorrent_stream = std::make_unique<std::stringstream>();
|
||||||
|
|
||||||
|
if (!skip_static) {
|
||||||
|
torrent_stream = std::make_unique<std::stringstream>();
|
||||||
|
torrent::object_write_bencode(&*torrent_stream, m_download->bencode(), torrent::Object::flag_session_data);
|
||||||
|
|
||||||
|
if (!torrent_stream->good())
|
||||||
|
throw torrent::internal_error("DownloadStorer::build_streams() failed to write torrent stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
torrent::object_write_bencode(&*resume_stream, &resume_base, 0);
|
||||||
|
|
||||||
|
if (!resume_stream->good())
|
||||||
|
throw torrent::internal_error("DownloadStorer::build_streams() failed to write resume stream.");
|
||||||
|
|
||||||
|
torrent::object_write_bencode(&*rtorrent_stream, &rtorrent_base, 0);
|
||||||
|
|
||||||
|
if (!rtorrent_stream->good())
|
||||||
|
throw torrent::internal_error("DownloadStorer::build_streams() failed to write rtorrent stream.");
|
||||||
|
|
||||||
|
m_torrent_stream = std::move(torrent_stream);
|
||||||
|
m_rtorrent_stream = std::move(rtorrent_stream);
|
||||||
|
m_libtorrent_stream = std::move(resume_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
DownloadStorer::build_path(const std::string& session_path) {
|
||||||
|
auto info_hash = m_download->info()->info_hash();
|
||||||
|
|
||||||
|
if (session_path.empty())
|
||||||
|
throw torrent::internal_error("DownloadStorer::build_path() called with empty session path.");
|
||||||
|
|
||||||
|
if (session_path.back() != '/')
|
||||||
|
throw torrent::internal_error("DownloadStorer::build_path() session path missing trailing slash.");
|
||||||
|
|
||||||
|
return session_path + torrent::hash_string_to_hex_str(info_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_correct_format(const std::string& f) {
|
||||||
|
if (f.size() != 48 || f.substr(40) != ".torrent")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (std::string::const_iterator itr = f.begin(); itr != f.end() - 8; ++itr)
|
||||||
|
if (!(*itr >= '0' && *itr <= '9') &&
|
||||||
|
!(*itr >= 'A' && *itr <= 'F'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
utils::Directory
|
||||||
|
DownloadStorer::get_formated_entries(const std::string& session_path) {
|
||||||
|
if (session_path.empty())
|
||||||
|
return utils::Directory();
|
||||||
|
|
||||||
|
utils::Directory d(session_path);
|
||||||
|
|
||||||
|
if (!d.update(utils::Directory::update_hide_dot))
|
||||||
|
throw torrent::storage_error("session::DownloadStorer::update() could not open session directory: " + session_path);
|
||||||
|
|
||||||
|
d.erase(std::remove_if(d.begin(), d.end(), [](auto& entry) { return !is_correct_format(entry.s_name); }), d.end());
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace session
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef RTORRENT_SESSION_DOWNLOAD_STORER_H
|
||||||
|
#define RTORRENT_SESSION_DOWNLOAD_STORER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <torrent/common.h>
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
class Download;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace utils {
|
||||||
|
class Directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace session {
|
||||||
|
|
||||||
|
class DownloadStorer {
|
||||||
|
public:
|
||||||
|
DownloadStorer(core::Download* download);
|
||||||
|
|
||||||
|
core::Download* download() const { return m_download; }
|
||||||
|
|
||||||
|
void build_streams(bool skip_static);
|
||||||
|
std::string build_path(const std::string& session_path);
|
||||||
|
|
||||||
|
auto torrent_stream() { return std::move(m_torrent_stream); }
|
||||||
|
auto rtorrent_stream() { return std::move(m_rtorrent_stream); }
|
||||||
|
auto libtorrent_stream() { return std::move(m_libtorrent_stream); }
|
||||||
|
|
||||||
|
static utils::Directory get_formated_entries(const std::string& session_path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
core::Download* m_download;
|
||||||
|
|
||||||
|
std::unique_ptr<std::stringstream> m_torrent_stream;
|
||||||
|
std::unique_ptr<std::stringstream> m_rtorrent_stream;
|
||||||
|
std::unique_ptr<std::stringstream> m_libtorrent_stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace session
|
||||||
|
|
||||||
|
#endif // RTORRENT_SESSION_DOWNLOAD_STORER_H
|
||||||
+173
-75
@@ -1,6 +1,6 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "session_manager.h"
|
#include "session/session_manager.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <torrent/utils/log.h>
|
#include <torrent/utils/log.h>
|
||||||
|
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
|
#include "session/download_storer.h"
|
||||||
#include "utils/lockfile.h"
|
#include "utils/lockfile.h"
|
||||||
|
|
||||||
#define LT_LOG(log_fmt, ...) \
|
#define LT_LOG(log_fmt, ...) \
|
||||||
@@ -27,18 +28,6 @@ SessionManager::SessionManager(torrent::utils::Thread* thread)
|
|||||||
|
|
||||||
SessionManager::~SessionManager() = default;
|
SessionManager::~SessionManager() = default;
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// * Lock session directory.
|
|
||||||
// * On shutdown, wait for all saves to finish.
|
|
||||||
// * Add is_empty_and_done() that also include async fdisksync tasks.
|
|
||||||
// * Then unlock session directory.
|
|
||||||
|
|
||||||
bool
|
|
||||||
SessionManager::is_empty() {
|
|
||||||
std::lock_guard<std::mutex> guard(m_mutex);
|
|
||||||
return m_save_requests.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
SessionManager::set_path(const std::string& path) {
|
SessionManager::set_path(const std::string& path) {
|
||||||
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
||||||
@@ -52,6 +41,16 @@ SessionManager::set_path(const std::string& path) {
|
|||||||
m_path = path + '/';
|
m_path = path + '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SessionManager::set_use_fsyncdisk(bool use_fsyncdisk) {
|
||||||
|
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
||||||
|
|
||||||
|
if (m_freeze_info)
|
||||||
|
throw torrent::input_error("Session fsyncdisk option cannot be changed after startup.");
|
||||||
|
|
||||||
|
m_use_fsyncdisk = use_fsyncdisk;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
SessionManager::set_use_lock(bool use_lock) {
|
SessionManager::set_use_lock(bool use_lock) {
|
||||||
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
||||||
@@ -62,65 +61,62 @@ SessionManager::set_use_lock(bool use_lock) {
|
|||||||
m_use_lock = use_lock;
|
m_use_lock = use_lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Derive path from download info hash.
|
// TODO: Add separate mutex for queuing save requests in session save.
|
||||||
// TODO: Generate streams here, not in download store.
|
|
||||||
void
|
void
|
||||||
SessionManager::save_download(core::Download* download, std::string path, stream_ptr torrent_stream, stream_ptr rtorrent_stream, stream_ptr libtorrent_stream) {
|
SessionManager::save_download(core::Download* download, bool skip_static) {
|
||||||
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
||||||
|
|
||||||
if (m_path.empty())
|
if (m_path.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
{
|
DownloadStorer storer(download);
|
||||||
std::lock_guard<std::mutex> guard(m_mutex);
|
|
||||||
|
|
||||||
LT_LOG("requesting save : download:%p path:%s", download, path.c_str());
|
storer.build_streams(skip_static);
|
||||||
|
|
||||||
|
auto save_request = SaveRequest{
|
||||||
|
download,
|
||||||
|
storer.build_path(m_path),
|
||||||
|
storer.torrent_stream(),
|
||||||
|
storer.rtorrent_stream(),
|
||||||
|
storer.libtorrent_stream()
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
LT_LOG("requesting save : download:%p path:%s", download, save_request.path.c_str());
|
||||||
|
|
||||||
if (!m_active)
|
if (!m_active)
|
||||||
throw torrent::internal_error("SessionManager::save_download() called while not active.");
|
throw torrent::internal_error("SessionManager::save_download() called while not active.");
|
||||||
|
|
||||||
// TODO: Remove is already queued entries.
|
if (remove_save_request_unsafe(download, lock))
|
||||||
|
LT_LOG("replacing pending save request : download:%p", download);
|
||||||
|
|
||||||
// TODO: Add these to a temp structure
|
m_save_requests.push_back(std::move(save_request));
|
||||||
// TODO: When a download already exists, replace it.
|
|
||||||
|
|
||||||
m_save_requests.push_back(SaveRequest{
|
|
||||||
download,
|
|
||||||
std::move(path),
|
|
||||||
std::move(torrent_stream),
|
|
||||||
std::move(rtorrent_stream),
|
|
||||||
std::move(libtorrent_stream)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
session_thread::callback(nullptr, [this]() { process_save_request(); });
|
session_thread::callback(this, [this]() { process_save_request(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move various low-level stuff to DownloadStorer.
|
||||||
|
|
||||||
void
|
void
|
||||||
SessionManager::remove_download(core::Download* download, std::string base_path) {
|
SessionManager::remove_download(core::Download* download) {
|
||||||
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
||||||
|
|
||||||
if (m_path.empty())
|
if (m_path.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(m_mutex);
|
auto base_path = DownloadStorer(download).build_path(m_path);
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
if (!m_active)
|
if (!m_active)
|
||||||
throw torrent::internal_error("SessionManager::remove_download() called while not active.");
|
throw torrent::internal_error("SessionManager::remove_download() called while not active.");
|
||||||
|
|
||||||
// TODO: Add these to a temp structure, and remove from to-be-added temp struct.
|
if (remove_save_request_unsafe(download, lock))
|
||||||
|
LT_LOG("canceled pending save request : download:%p", download);
|
||||||
auto itr = std::remove_if(m_save_requests.begin(), m_save_requests.end(), [download](auto& req) {
|
|
||||||
return req.download == download;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (itr != m_save_requests.end()) {
|
|
||||||
LT_LOG("canceling save request : download:%p", download);
|
|
||||||
m_save_requests.erase(itr, m_save_requests.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use atomic download ptr to check if we're currently processing this download
|
|
||||||
// TODO: If so, use a lock to wait for save to finish before returning
|
|
||||||
|
|
||||||
auto torrent_path = base_path;
|
auto torrent_path = base_path;
|
||||||
auto libtorrent_path = base_path + ".libtorrent_resume";
|
auto libtorrent_path = base_path + ".libtorrent_resume";
|
||||||
@@ -135,7 +131,7 @@ void
|
|||||||
SessionManager::start() {
|
SessionManager::start() {
|
||||||
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(m_mutex);
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
if (m_active || m_freeze_info)
|
if (m_active || m_freeze_info)
|
||||||
throw torrent::internal_error("SessionManager::start() called while already started.");
|
throw torrent::internal_error("SessionManager::start() called while already started.");
|
||||||
@@ -168,7 +164,7 @@ void
|
|||||||
SessionManager::cleanup() {
|
SessionManager::cleanup() {
|
||||||
assert(m_thread == torrent::this_thread::thread());
|
assert(m_thread == torrent::this_thread::thread());
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(m_mutex);
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
if (!m_active)
|
if (!m_active)
|
||||||
throw torrent::internal_error("SessionManager::cleanup() called while not active.");
|
throw torrent::internal_error("SessionManager::cleanup() called while not active.");
|
||||||
@@ -182,12 +178,18 @@ SessionManager::cleanup() {
|
|||||||
|
|
||||||
LT_LOG("cleaning up session manager with path: %s", m_path.c_str());
|
LT_LOG("cleaning up session manager with path: %s", m_path.c_str());
|
||||||
|
|
||||||
|
flush_all_and_wait_unsafe(lock);
|
||||||
|
|
||||||
if (m_use_lock) {
|
if (m_use_lock) {
|
||||||
if (!m_lockfile->unlock())
|
if (!m_lockfile->unlock())
|
||||||
LT_LOG("could not unlock session directory: %s", m_path.c_str());
|
LT_LOG("could not unlock session directory: %s", m_path.c_str());
|
||||||
|
|
||||||
LT_LOG("unlocked session directory: %s", m_path.c_str());
|
LT_LOG("unlocked session directory: %s", m_path.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LT_LOG("session manager cleaned up", 0);
|
||||||
|
|
||||||
|
session_thread::cancel_callback(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -197,36 +199,138 @@ SessionManager::process_save_request() {
|
|||||||
if (m_path.empty())
|
if (m_path.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(m_mutex);
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
if (!m_active)
|
if (!m_active)
|
||||||
throw torrent::internal_error("SessionManager::process_save_request() called while not active.");
|
throw torrent::internal_error("SessionManager::process_save_request() called while not active.");
|
||||||
|
|
||||||
if (m_save_requests.empty())
|
if (m_save_requests.empty() || m_processing_saves.size() >= max_concurrent_saves)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// pick first request
|
process_next_save_request_unsafe();
|
||||||
// process it
|
|
||||||
// add us back to callbacks? we need to disable shutdown while processing all saves... do we do it at thread cleanup?
|
|
||||||
|
|
||||||
|
if (!m_save_requests.empty())
|
||||||
|
session_thread::callback(this, [this]() { process_save_request(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SessionManager::process_next_save_request_unsafe() {
|
||||||
auto request = std::move(m_save_requests.front());
|
auto request = std::move(m_save_requests.front());
|
||||||
m_save_requests.pop_front();
|
m_save_requests.pop_front();
|
||||||
|
|
||||||
// Keep lock while processing to ensure cancellations do not interfere.
|
auto itr = m_processing_saves.insert(m_processing_saves.end(), ProcessingSave{});
|
||||||
|
|
||||||
save_download_unsafe(request);
|
itr->second = std::move(request);
|
||||||
|
itr->first = std::async(std::launch::async, [this, itr]() {
|
||||||
|
// TODO: Properly handle errors here, and report back to session thread.
|
||||||
|
// TODO: Consider adding a failed_saves with error info.
|
||||||
|
|
||||||
if (!m_save_requests.empty())
|
save_download_unsafe(itr->second);
|
||||||
session_thread::callback(nullptr, [this]() { process_save_request(); });
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
if (m_finished_saves.empty())
|
||||||
|
session_thread::callback(this, [this]() { process_finished_saves(); });
|
||||||
|
|
||||||
|
m_finished_saves.push_back(std::move(*itr));
|
||||||
|
m_processing_saves.erase(itr);
|
||||||
|
|
||||||
|
m_finished_condition.notify_all();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add threads/tasklets that calls fdisksync on shutdown.
|
void
|
||||||
// TODO: Parallelize saves.
|
SessionManager::process_finished_saves() {
|
||||||
|
assert(m_thread == torrent::this_thread::thread());
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
if (!m_active)
|
||||||
|
throw torrent::internal_error("SessionManager::process_finished_saves() called while not active.");
|
||||||
|
|
||||||
|
for (auto& request : m_finished_saves)
|
||||||
|
LT_LOG("finished saving download : download:%p path:%s", request.second.download, request.second.path.c_str());
|
||||||
|
|
||||||
|
m_finished_saves.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SessionManager::wait_for_one_save_unsafe(std::unique_lock<std::mutex>& lock) {
|
||||||
|
assert(m_thread == torrent::this_thread::thread());
|
||||||
|
|
||||||
|
if (m_processing_saves.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_finished_condition.wait(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SessionManager::flush_all_and_wait_unsafe(std::unique_lock<std::mutex>& lock) {
|
||||||
|
LT_LOG("flushing all pending saves", 0);
|
||||||
|
|
||||||
|
while (!m_save_requests.empty()) {
|
||||||
|
if (m_processing_saves.size() >= max_cleanup_saves) {
|
||||||
|
wait_for_one_save_unsafe(lock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_next_save_request_unsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!m_processing_saves.empty())
|
||||||
|
wait_for_one_save_unsafe(lock);
|
||||||
|
|
||||||
|
for (auto& request : m_finished_saves)
|
||||||
|
LT_LOG("finished saving download : download:%p path:%s", request.second.download, request.second.path.c_str());
|
||||||
|
|
||||||
|
m_finished_saves.clear();
|
||||||
|
|
||||||
|
LT_LOG("flushed all pending saves", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
SessionManager::remove_save_request_unsafe(core::Download* download, std::unique_lock<std::mutex>& lock) {
|
||||||
|
// Can be run in any thread.
|
||||||
|
|
||||||
|
{
|
||||||
|
auto itr = std::remove_if(m_save_requests.begin(), m_save_requests.end(), [download](auto& req) {
|
||||||
|
return req.download == download;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itr == m_save_requests.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_save_requests.erase(itr, m_save_requests.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if in processing saves, then wait for it to finish using wait_for_one_save_unsafe()
|
||||||
|
while (true) {
|
||||||
|
auto itr = std::find_if(m_processing_saves.begin(), m_processing_saves.end(), [download](auto& req) {
|
||||||
|
return req.second.download == download;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itr == m_processing_saves.end())
|
||||||
|
break;
|
||||||
|
|
||||||
|
wait_for_one_save_unsafe(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checking active after remove_save_request_unsafe to ensure we're calling this after shutdown.
|
||||||
|
if (!m_active)
|
||||||
|
throw torrent::internal_error("SessionManager::remove_save_request_unsafe() called while not active");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Properly handle errors.
|
// TODO: Properly handle errors.
|
||||||
|
// TODO: If no more sockets can be opened, wait for a job to finish. if all is finished, use a
|
||||||
|
// timeout nad try again.
|
||||||
|
|
||||||
void
|
void
|
||||||
SessionManager::save_download_unsafe(const SaveRequest& request) {
|
SessionManager::save_download_unsafe(const SaveRequest& request) {
|
||||||
LT_LOG("saving download : download:%p path:%s", request.download, request.path.c_str());
|
// LT_LOG("saving download : download:%p path:%s", request.download, request.path.c_str());
|
||||||
|
|
||||||
if (m_path.empty())
|
if (m_path.empty())
|
||||||
throw torrent::internal_error("SessionManager::save_download_unsafe() called with empty session path.");
|
throw torrent::internal_error("SessionManager::save_download_unsafe() called with empty session path.");
|
||||||
@@ -248,18 +352,18 @@ SessionManager::save_download_unsafe(const SaveRequest& request) {
|
|||||||
|
|
||||||
if (request.torrent_stream) {
|
if (request.torrent_stream) {
|
||||||
if (::rename((torrent_path + ".new").c_str(), torrent_path.c_str()) == -1) {
|
if (::rename((torrent_path + ".new").c_str(), torrent_path.c_str()) == -1) {
|
||||||
LT_LOG("failed to rename torrent file : %s", torrent_path.c_str());
|
// LT_LOG("failed to rename torrent file : %s", torrent_path.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (::rename((libtorrent_path + ".new").c_str(), libtorrent_path.c_str()) == -1) {
|
if (::rename((libtorrent_path + ".new").c_str(), libtorrent_path.c_str()) == -1) {
|
||||||
LT_LOG("failed to rename libtorrent resume file : %s", libtorrent_path.c_str());
|
// LT_LOG("failed to rename libtorrent resume file : %s", libtorrent_path.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (::rename((rtorrent_path + ".new").c_str(), rtorrent_path.c_str()) == -1) {
|
if (::rename((rtorrent_path + ".new").c_str(), rtorrent_path.c_str()) == -1) {
|
||||||
LT_LOG("failed to rename rtorrent resume file : %s", rtorrent_path.c_str());
|
// LT_LOG("failed to rename rtorrent resume file : %s", rtorrent_path.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,15 +374,17 @@ bool
|
|||||||
SessionManager::save_download_stream_unsafe(const std::string& path, const std::unique_ptr<std::stringstream>& stream) {
|
SessionManager::save_download_stream_unsafe(const std::string& path, const std::unique_ptr<std::stringstream>& stream) {
|
||||||
std::fstream output(path.c_str(), std::ios::out | std::ios::trunc);
|
std::fstream output(path.c_str(), std::ios::out | std::ios::trunc);
|
||||||
|
|
||||||
|
// TODO: If we cannot open more files, wait for some to finish and try again.
|
||||||
|
|
||||||
if (!output.is_open()) {
|
if (!output.is_open()) {
|
||||||
LT_LOG("failed to open file for writing : path:%s", path.c_str());
|
// LT_LOG("failed to open file for writing : path:%s", path.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
output << stream->rdbuf();
|
output << stream->rdbuf();
|
||||||
|
|
||||||
if (!output.good()) {
|
if (!output.good()) {
|
||||||
LT_LOG("failed to write stream to file : path:%s", path.c_str());
|
// LT_LOG("failed to write stream to file : path:%s", path.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,19 +394,11 @@ SessionManager::save_download_stream_unsafe(const std::string& path, const std::
|
|||||||
int fd = ::open(path.c_str(), O_WRONLY);
|
int fd = ::open(path.c_str(), O_WRONLY);
|
||||||
|
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
LT_LOG("failed to open file descriptor for fdatasync : path:%s", path.c_str());
|
// LT_LOG("failed to open file descriptor for fdatasync : path:%s", path.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't care about cancelation here, as the underlying file gets deleted / replaced anyway.
|
if (m_use_fsyncdisk) {
|
||||||
|
|
||||||
// TODO: We can use std::async for these, and only wait for them if we're shutting down.
|
|
||||||
|
|
||||||
// TODO: Use an atomic counter / conditional variable to keep track of async operations count, and
|
|
||||||
// wait for finished operations on shutdown.
|
|
||||||
|
|
||||||
// if (rpc::call_command_value("system.files.session.fdatasync")) {
|
|
||||||
if (true) {
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
::fsync(fd);
|
::fsync(fd);
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
#ifndef RTORRENT_SESSION_SESSION_MANAGER_H
|
#ifndef RTORRENT_SESSION_SESSION_MANAGER_H
|
||||||
#define RTORRENT_SESSION_SESSION_MANAGER_H
|
#define RTORRENT_SESSION_SESSION_MANAGER_H
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <future>
|
||||||
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
#include <torrent/common.h>
|
#include <torrent/common.h>
|
||||||
|
|
||||||
namespace core {
|
namespace core {
|
||||||
@@ -32,36 +36,48 @@ class SessionManager {
|
|||||||
public:
|
public:
|
||||||
typedef std::unique_ptr<std::stringstream> stream_ptr;
|
typedef std::unique_ptr<std::stringstream> stream_ptr;
|
||||||
|
|
||||||
|
// TODO: This should depend on max open sockets / be configurable.
|
||||||
|
constexpr static int max_concurrent_saves = 16;
|
||||||
|
constexpr static int max_cleanup_saves = 64;
|
||||||
|
|
||||||
SessionManager(torrent::utils::Thread* thread);
|
SessionManager(torrent::utils::Thread* thread);
|
||||||
~SessionManager();
|
~SessionManager();
|
||||||
|
|
||||||
bool is_used() const;
|
bool is_used() const;
|
||||||
|
|
||||||
// TODO: Replace with protected `bool shutdown_if_done()`.
|
|
||||||
bool is_empty();
|
|
||||||
// bool is_empty_and_done();
|
|
||||||
|
|
||||||
void start();
|
|
||||||
|
|
||||||
std::string path() const;
|
std::string path() const;
|
||||||
void set_path(const std::string& path);
|
void set_path(const std::string& path);
|
||||||
|
|
||||||
|
bool use_fsyncdisk() const;
|
||||||
|
void set_use_fsyncdisk(bool use_fsyncdisk);
|
||||||
|
|
||||||
bool use_lock() const;
|
bool use_lock() const;
|
||||||
void set_use_lock(bool use_lock);
|
void set_use_lock(bool use_lock);
|
||||||
|
|
||||||
void freeze_info();
|
void save_full_download(core::Download* download) { save_download(download, false); }
|
||||||
|
void save_resume_download(core::Download* download) { save_download(download, true); }
|
||||||
void save_download(core::Download* download, std::string path, stream_ptr torrent_stream, stream_ptr rtorrent_stream, stream_ptr libtorrent_stream);
|
void remove_download(core::Download* download);
|
||||||
void remove_download(core::Download* download, std::string path);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class Control;
|
friend class Control;
|
||||||
friend class ThreadSession;
|
friend class ThreadSession;
|
||||||
|
|
||||||
|
void save_download(core::Download* download, bool skip_static);
|
||||||
|
|
||||||
|
void start();
|
||||||
void cleanup();
|
void cleanup();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void process_save_request();
|
void process_save_request();
|
||||||
|
void process_next_save_request_unsafe();
|
||||||
|
void process_finished_saves();
|
||||||
|
|
||||||
|
void wait_for_one_save_unsafe(std::unique_lock<std::mutex>& lock);
|
||||||
|
|
||||||
|
// Requires a higher number of open sockets, and should only be used during shutdown.
|
||||||
|
void flush_all_and_wait_unsafe(std::unique_lock<std::mutex>& lock);
|
||||||
|
|
||||||
|
bool remove_save_request_unsafe(core::Download* download, std::unique_lock<std::mutex>& lock);
|
||||||
|
|
||||||
void save_download_unsafe(const SaveRequest& request);
|
void save_download_unsafe(const SaveRequest& request);
|
||||||
bool save_download_stream_unsafe(const std::string& path, const std::unique_ptr<std::stringstream>& stream);
|
bool save_download_stream_unsafe(const std::string& path, const std::unique_ptr<std::stringstream>& stream);
|
||||||
@@ -70,18 +86,28 @@ private:
|
|||||||
|
|
||||||
bool m_freeze_info{};
|
bool m_freeze_info{};
|
||||||
std::string m_path;
|
std::string m_path;
|
||||||
|
bool m_use_fsyncdisk{true};
|
||||||
bool m_use_lock{true};
|
bool m_use_lock{true};
|
||||||
|
|
||||||
std::mutex m_mutex;
|
std::mutex m_mutex;
|
||||||
bool m_active{};
|
bool m_active{};
|
||||||
|
|
||||||
std::deque<SaveRequest> m_save_requests;
|
typedef std::pair<std::future<void>, SaveRequest> ProcessingSave;
|
||||||
|
|
||||||
|
// TODO: Add deque of DownloadStorers requests that have yet to build streams.
|
||||||
|
|
||||||
|
std::deque<SaveRequest> m_save_requests;
|
||||||
|
std::list<ProcessingSave> m_processing_saves;
|
||||||
|
std::condition_variable m_finished_condition;
|
||||||
|
std::vector<ProcessingSave> m_finished_saves;
|
||||||
|
|
||||||
std::unique_ptr<utils::Lockfile> m_lockfile;
|
std::unique_ptr<utils::Lockfile> m_lockfile;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool SessionManager::is_used() const { return !m_path.empty(); }
|
inline bool SessionManager::is_used() const { return !m_path.empty(); }
|
||||||
inline std::string SessionManager::path() const { return m_path; }
|
inline std::string SessionManager::path() const { return m_path; }
|
||||||
inline bool SessionManager::use_lock() const { return m_use_lock; }
|
inline bool SessionManager::use_fsyncdisk() const { return true; }
|
||||||
|
inline bool SessionManager::use_lock() const { return m_use_lock; }
|
||||||
|
|
||||||
} // namespace session
|
} // namespace session
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ ThreadSession::thread_session() {
|
|||||||
return m_thread_session;
|
return m_thread_session;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove '= 0'.
|
|
||||||
void
|
void
|
||||||
ThreadSession::init_thread() {
|
ThreadSession::init_thread_pre_start() {
|
||||||
|
m_manager->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make sure we trigger session save before main thread exits, that it adds all required
|
// TODO: Make sure we trigger session save before main thread exits, that it adds all required
|
||||||
@@ -52,18 +52,9 @@ void
|
|||||||
ThreadSession::call_events() {
|
ThreadSession::call_events() {
|
||||||
// lt_log_print_locked(torrent::LOG_THREAD_NOTICE, "Got thread_disk tick.");
|
// lt_log_print_locked(torrent::LOG_THREAD_NOTICE, "Got thread_disk tick.");
|
||||||
|
|
||||||
// TODO: Wait with shutdown until all session data is saved.
|
|
||||||
|
|
||||||
process_callbacks();
|
process_callbacks();
|
||||||
|
|
||||||
if ((m_flags & flag_do_shutdown)) {
|
if ((m_flags & flag_do_shutdown)) {
|
||||||
if (!m_manager->is_empty()) {
|
|
||||||
// TODO: Figure out a better way to wait for session save to complete.
|
|
||||||
// TODO: Sanity check to avoid getting stuck not shutting down.
|
|
||||||
// TODO: Should we depend on next_timeout() instead of callbacks?
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((m_flags & flag_did_shutdown))
|
if ((m_flags & flag_did_shutdown))
|
||||||
throw torrent::internal_error("Already trigged shutdown.");
|
throw torrent::internal_error("Already trigged shutdown.");
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,9 @@ public:
|
|||||||
static void destroy_thread();
|
static void destroy_thread();
|
||||||
static ThreadSession* thread_session();
|
static ThreadSession* thread_session();
|
||||||
|
|
||||||
const char* name() const override { return "rtorrent-session"; }
|
const char* name() const override { return "rtorrent-session"; }
|
||||||
|
|
||||||
void init_thread() override;
|
SessionManager* manager() const { return m_manager.get(); }
|
||||||
void cleanup_thread() override;
|
|
||||||
|
|
||||||
SessionManager* manager() const { return m_manager.get(); }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class ThreadSessionInternal;
|
friend class ThreadSessionInternal;
|
||||||
@@ -29,6 +26,9 @@ protected:
|
|||||||
|
|
||||||
static auto internal_thread_session() { return m_thread_session; }
|
static auto internal_thread_session() { return m_thread_session; }
|
||||||
|
|
||||||
|
void init_thread_pre_start() override;
|
||||||
|
void cleanup_thread() override;
|
||||||
|
|
||||||
void call_events() override;
|
void call_events() override;
|
||||||
std::chrono::microseconds next_timeout() override;
|
std::chrono::microseconds next_timeout() override;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
#include "control.h"
|
#include "control.h"
|
||||||
#include "core/download_list.h"
|
#include "core/download_list.h"
|
||||||
#include "core/download_store.h"
|
|
||||||
#include "core/manager.h"
|
#include "core/manager.h"
|
||||||
#include "display/frame.h"
|
#include "display/frame.h"
|
||||||
#include "display/window_http_queue.h"
|
#include "display/window_http_queue.h"
|
||||||
|
|||||||
Reference in New Issue
Block a user