#include "config.h" #include "core/manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rpc/parse_commands.h" #include "utils/directory.h" #include "utils/base64.h" #include "utils/file_status_cache.h" #include "globals.h" #include "control.h" #include "core/download.h" #include "core/download_factory.h" #include "core/http_queue.h" #include "core/view.h" #include namespace core { const int Manager::create_start; const int Manager::create_tied; const int Manager::create_quiet; const int Manager::create_raw_data; void Manager::push_log(const char* msg) { m_log_important->lock_and_push_log(msg, strlen(msg), 0); m_log_complete->lock_and_push_log(msg, strlen(msg), 0); } Manager::Manager() : m_log_important(torrent::log_open_log_buffer("important")), m_log_complete(torrent::log_open_log_buffer("complete")) { m_download_list = std::make_unique(); m_file_status_cache = std::make_unique(); m_http_queue = std::make_unique(); torrent::Throttle* unthrottled = torrent::Throttle::create_throttle(); unthrottled->set_max_rate(0); m_throttles["NULL"] = std::make_pair(unthrottled, unthrottled); } Manager::~Manager() { torrent::Throttle::destroy_throttle(m_throttles["NULL"].first); } bool Manager::is_download_shutdown_completed() { for (const auto& download : *download_list()) { // TODO: Why is this being checked? // if (download->tracker_controller().is_active()) // return false; if (download->tracker_controller().has_active_trackers_not_dht_scrape_disownable()) return false; } return true; } void Manager::set_hashing_view(View* v) { if (v == nullptr || m_hashingView != nullptr) throw torrent::internal_error("Manager::set_hashing_view(...) received nullptr or is already set."); m_hashingView = v; m_hashingView->signal_changed().push_back(std::bind(&Manager::receive_hashing_changed, this)); } ThrottlePair Manager::get_throttle(const std::string& name) { auto itr = m_throttles.find(name); auto throttles = (itr == m_throttles.end() ? ThrottlePair(nullptr, nullptr) : itr->second); if (throttles.first == nullptr) throttles.first = torrent::up_throttle_global(); if (throttles.second == nullptr) throttles.second = torrent::down_throttle_global(); return throttles; } int64_t Manager::retrieve_throttle_value(const torrent::Object::string_type& name, bool rate, bool up) { ThrottleMap::iterator itr = throttles().find(name); if (itr == throttles().end()) { return (int64_t)-1; } else { torrent::Throttle* throttle = up ? itr->second.first : itr->second.second; // check whether the actual up/down throttle exist (one of the pair can be missing) if (throttle == nullptr) return (int64_t)-1; int64_t throttle_max = (int64_t)throttle->max_rate(); if (rate) { if (throttle_max > 0) return (int64_t)throttle->rate()->rate(); else return (int64_t)-1; } else { return throttle_max; } } } void Manager::set_magnet_path(const std::string& path) { if (path.empty()) m_magnet_path.clear(); else if (path.back() == '/') m_magnet_path = path; else m_magnet_path = path + '/'; } void Manager::cleanup() { // Need to disconnect log signals? Not really since we won't receive // any more. m_download_list->clear(); torrent::cleanup(); } void Manager::shutdown(bool force) { if (!force) { for (auto d : *m_download_list) m_download_list->pause_default(d); } else { for (auto d : *m_download_list) m_download_list->close_quick(d); } } void Manager::receive_http_failed(std::string msg) { push_log_std("Http download error: \"" + msg + "\""); } bool is_data_uri(const std::string& uri) { return std::strncmp(uri.c_str(), "data:", 5) == 0; } std::string decode_data_uri(const std::string& uri) { const auto pos = uri.find("base64,", 5); if (pos == std::string::npos) throw torrent::input_error("Invalid data uri: not base64 encoded."); const auto start = pos + 7; if (start >= uri.size()) throw torrent::input_error("Empty base64."); return utils::decode_base64(uri.substr(start)); } void Manager::try_create_download(const std::string& uri, int flags, const command_list_type& commands) { // If the path was attempted loaded before, skip it. if ((flags & create_tied) && !(flags & create_raw_data) && !is_network_uri(uri) && !is_magnet_uri(uri) && !is_data_uri(uri) && !file_status_cache()->insert(uri)) return; // Adding download. DownloadFactory* f = new DownloadFactory(this); f->variables()["tied_to_file"] = (int64_t)(bool)(flags & create_tied); f->commands().insert(f->commands().end(), commands.begin(), commands.end()); f->set_start(flags & create_start); f->set_print_log(!(flags & create_quiet)); f->slot_finished([f]() { delete f; }); if (is_data_uri(uri)) { // Allow the use of data URIs, primarily for JSON-RPC which // doesn't have a defined mechanism for binary data f->load_raw_data(decode_data_uri(uri)); f->variables()["tied_to_file"] = (int64_t)false; } else if (flags & create_raw_data) { f->load_raw_data(uri); } else { f->load(uri); } f->commit(); } void Manager::try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile) { DownloadFactory* f = new DownloadFactory(this); f->variables()["tied_to_file"] = (int64_t)true; f->variables()["tied_file"] = metafile; torrent::Object& meta = bencode->get_key("rtorrent_meta_download"); torrent::Object::list_type& commands = meta.get_key_list("commands"); for (const auto& command : commands) f->commands().insert(f->commands().end(), command.as_string()); f->set_start(meta.get_key_value("start")); f->set_print_log(meta.get_key_value("print_log")); f->slot_finished([f]() { delete f; }); // Bit of a waste to create the bencode repesentation here // only to have the DownloadFactory decode it. std::stringstream s; s.imbue(std::locale::classic()); s << *bencode; f->load_raw_data(s.str()); f->commit(); } utils::Directory path_expand_transform(std::string path, const utils::directory_entry& entry) { return path + entry.s_name; } namespace { // Consider rewritting such that m_seq is replaced by first/last. template class split_iterator_t { public: typedef typename Sequence::const_iterator const_iterator; typedef typename Sequence::value_type value_type; split_iterator_t() {} split_iterator_t(const Sequence& seq, value_type delim) : m_seq(&seq), m_delim(delim), m_pos(seq.begin()), m_next(std::find(seq.begin(), seq.end(), delim)) { } Sequence operator * () { return Sequence(m_pos, m_next); } split_iterator_t& operator ++ () { m_pos = m_next; if (m_pos == m_seq->end()) return *this; m_pos++; m_next = std::find(m_pos, m_seq->end(), m_delim); return *this; } bool operator == (const split_iterator_t&) const { return m_pos == m_seq->end(); } bool operator != (const split_iterator_t&) const { return m_pos != m_seq->end(); } private: const Sequence* m_seq; value_type m_delim; const_iterator m_pos; const_iterator m_next; }; template inline split_iterator_t split_iterator(const Sequence& seq, typename Sequence::value_type delim) { return split_iterator_t(seq, delim); } template inline split_iterator_t split_iterator(const Sequence&) { return split_iterator_t(); } } // Move this somewhere better. void path_expand(std::vector* paths, const std::string& pattern) { std::vector currentCache; std::vector nextCache; auto first = split_iterator(pattern, '/'); auto last = split_iterator(pattern); if (first == last) return; // Check for initial '/' that indicates the root. if ((*first).empty()) { currentCache.push_back(utils::Directory("/")); ++first; } else if (torrent::utils::trim_spaces_str(*first) == "~") { currentCache.push_back(utils::Directory("~")); ++first; } else { currentCache.push_back(utils::Directory(".")); } // Might be an idea to use depth-first search instead. for (; first != last; ++first) { const std::string& pattern = *first; if (pattern.empty()) continue; // Special case for ".."? for (auto& itr : currentCache) { // Only include filenames starting with '.' if the pattern // starts with the same. itr.update((pattern[0] != '.') ? utils::Directory::update_hide_dot : 0); itr.erase(std::remove_if(itr.begin(), itr.end(), [&pattern](const utils::directory_entry& entry) { return fnmatch(pattern.c_str(), entry.s_name.c_str(), 0) != 0; }), itr.end()); for (const auto& cache : itr) nextCache.push_back(path_expand_transform(itr.path() + (itr.path() == "/" ? "" : "/"), cache)); } currentCache.clear(); currentCache.swap(nextCache); } for (const auto& cache : currentCache) paths->push_back(cache.path()); } bool manager_equal_tied(const std::string& path, Download* download) { return path == rpc::call_command_string("d.tied_to_file", rpc::make_target(download)); } void Manager::try_create_download_expand(const std::string& uri, int flags, command_list_type commands) { if (flags & create_raw_data) { try_create_download(uri, flags, commands); return; } std::vector paths; paths.reserve(256); path_expand(&paths, uri); if (!paths.empty()) for (auto& path : paths) try_create_download(path, flags, commands); else try_create_download(uri, flags, commands); } // DownloadList's hashing related functions don't actually start the // hashing, it only reacts to events. This functions checks the // hashing view and starts hashing if nessesary. void Manager::receive_hashing_changed() { bool foundHashing = std::any_of(m_hashingView->begin_visible(), m_hashingView->end_visible(), std::mem_fn(&Download::is_hash_checking)); // Try quick hashing all those with hashing == initial, set them to // something else when failed. for (View::iterator itr = m_hashingView->begin_visible(), last = m_hashingView->end_visible(); itr != last; ++itr) { if ((*itr)->is_hash_checked()) throw torrent::internal_error("core::Manager::receive_hashing_changed() (*itr)->is_hash_checked()."); if ((*itr)->is_hash_checking() || (*itr)->is_hash_failed()) continue; bool tryQuick = rpc::call_command_value("d.hashing", rpc::make_target(*itr)) == Download::variable_hashing_initial && (*itr)->download()->file_list()->bitfield()->empty(); if (!tryQuick && foundHashing) continue; try { m_download_list->open_throw(*itr); // Since the bitfield is allocated on loading of resume load or // hash start, and unallocated on close, we know that if it it // not empty then we have already loaded any existing resume // data. if ((*itr)->download()->file_list()->bitfield()->empty()) torrent::resume_load_progress(*(*itr)->download(), (*itr)->download()->bencode()->get_key("libtorrent_resume")); if (tryQuick) { if ((*itr)->download()->hash_check(true)) continue; (*itr)->download()->hash_stop(); if (foundHashing) { rpc::call_command_set_value("d.hashing.set", Download::variable_hashing_rehash, rpc::make_target(*itr)); continue; } } (*itr)->download()->hash_check(false); foundHashing = true; } catch (torrent::local_error& e) { if (tryQuick) { // Make sure we don't repeat the quick hashing. rpc::call_command_set_value("d.hashing.set", Download::variable_hashing_rehash, rpc::make_target(*itr)); } else { (*itr)->set_hash_failed(true); (*itr)->set_message("Hashing failed: " + std::string(e.what())); lt_log_print(torrent::LOG_TORRENT_ERROR, "Hashing failed: %s", e.what()); } } } } }