From 222bf7ae8f5b79252bdd331cfbde1aa34164c543 Mon Sep 17 00:00:00 2001 From: rakshasa Date: Mon, 8 Feb 2010 19:41:46 +0000 Subject: [PATCH] * Better handling of resume after crash/reboot. Sponsored by anonymous source. git-svn-id: svn://rakshasa.no/libtorrent/trunk/rtorrent@1126 e378c898-3ddf-0310-93e7-cc216c733640 --- rak/timer.h | 3 +- src/command_download.cc | 3 +- src/core/download_factory.cc | 27 +++++++++-- src/core/download_list.cc | 10 +++-- src/core/download_store.cc | 86 +++++++++++++++++++++++------------- src/core/download_store.h | 7 ++- src/main.cc | 4 +- 7 files changed, 98 insertions(+), 42 deletions(-) diff --git a/rak/timer.h b/rak/timer.h index e1b6af38..988bba2e 100644 --- a/rak/timer.h +++ b/rak/timer.h @@ -64,8 +64,9 @@ class timer { static timer current(); static int64_t current_seconds() { return current().seconds(); } static int64_t current_usec() { return current().usec(); } + static timer from_minutes(uint32_t minutes) { return rak::timer((uint64_t)minutes * 60 * 1000000); } static timer from_seconds(uint32_t seconds) { return rak::timer((uint64_t)seconds * 1000000); } - static timer from_milliseconds(uint32_t seconds) { return rak::timer((uint64_t)seconds * 1000); } + static timer from_milliseconds(uint32_t msec) { return rak::timer((uint64_t)msec * 1000); } static timer max() { return std::numeric_limits::max(); } diff --git a/src/command_download.cc b/src/command_download.cc index 60436622..9eff993d 100644 --- a/src/command_download.cc +++ b/src/command_download.cc @@ -593,7 +593,8 @@ initialize_command_download() { ADD_CD_F_VOID("erase", rak::make_mem_fun(control->core()->download_list(), &core::DownloadList::erase_ptr)); ADD_CD_F_VOID("check_hash", rak::make_mem_fun(control->core()->download_list(), &core::DownloadList::check_hash)); - ADD_CD_F_VOID("save_session", rak::make_mem_fun(control->core()->download_store(), &core::DownloadStore::save)); + ADD_CD_F_VOID("save_resume", rak::make_mem_fun(control->core()->download_store(), &core::DownloadStore::save_resume)); + ADD_CD_F_VOID("save_full_session", rak::make_mem_fun(control->core()->download_store(), &core::DownloadStore::save_full)); ADD_CD_F_VOID("update_priorities", rak::on(std::mem_fun(&core::Download::download), std::mem_fun(&torrent::Download::update_priorities))); diff --git a/src/core/download_factory.cc b/src/core/download_factory.cc index e2d8ee88..40150916 100644 --- a/src/core/download_factory.cc +++ b/src/core/download_factory.cc @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -161,6 +162,23 @@ DownloadFactory::receive_commit() { receive_success(); } +static bool +download_factory_add_stream(torrent::Object* root, const char* key, const char* filename) { + std::fstream stream(filename, std::ios::in | std::ios::binary); + + if (!stream.is_open()) + return false; + + torrent::Object obj; + stream >> obj; + + if (!stream.good()) + return false; + + root->insert_key_swap(key, obj); + return true; +} + void DownloadFactory::receive_success() { if (m_stream == NULL) @@ -177,15 +195,19 @@ DownloadFactory::receive_success() { torrent::Object* root = download->bencode(); - if (!m_session) { + if (m_session) { + download_factory_add_stream(root, "rtorrent", (rak::path_expand(m_uri) + ".rtorrent").c_str()); + download_factory_add_stream(root, "libtorrent_resume", (rak::path_expand(m_uri) + ".libtorrent_resume").c_str()); + + } else { // We only allow session torrents to keep their // 'rtorrent/libtorrent' sections. The "fast_resume" section // should be safe to keep. root->erase_key("rtorrent"); - root->erase_key("libtorrent"); } torrent::Object* rtorrent = &root->insert_preserve_copy("rtorrent", torrent::Object::create_map()).first->second; + torrent::Object& resumeObject = root->insert_preserve_copy("libtorrent_resume", torrent::Object::create_map()).first->second; initialize_rtorrent(download, rtorrent); @@ -233,7 +255,6 @@ DownloadFactory::receive_success() { rpc::call_command("d.set_peer_exchange", rpc::call_command_value("get_peer_exchange"), rpc::make_target(download)); - torrent::Object& resumeObject = root->insert_preserve_copy("libtorrent_resume", torrent::Object::create_map()).first->second; torrent::resume_load_addresses(*download->download(), resumeObject); torrent::resume_load_file_priorities(*download->download(), resumeObject); torrent::resume_load_tracker_settings(*download->download(), resumeObject); diff --git a/src/core/download_list.cc b/src/core/download_list.cc index 551f8732..c2537618 100644 --- a/src/core/download_list.cc +++ b/src/core/download_list.cc @@ -82,7 +82,7 @@ DownloadList::clear() { void DownloadList::session_save() { - unsigned int c = std::count_if(begin(), end(), std::bind1st(std::mem_fun(&DownloadStore::save), control->core()->download_store())); + unsigned int c = std::count_if(begin(), end(), std::bind1st(std::mem_fun(&DownloadStore::save_resume), control->core()->download_store())); if (c != size()) control->core()->push_log("Failed to save session torrents."); @@ -342,7 +342,9 @@ DownloadList::resume(Download* download, int flags) { // on non-complete downloads after a crash. This shouldn't be // needed, but for some reason linux 2.6 is very lazy about // updating mtime. - torrent::resume_save_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume"), true); + // + // Disabling this due to the new resume code. + // torrent::resume_save_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume"), true); } // If the DHT server is set to auto, start it now. @@ -559,8 +561,8 @@ DownloadList::confirm_finished(Download* download) { // // Obsolete. if (!download->is_active() && rpc::call_command_value("get_session_on_completion") != 0) { - torrent::resume_save_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume")); - control->core()->download_store()->save(download); + // torrent::resume_save_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume")); + control->core()->download_store()->save_resume(download); } // Send the completed request before resuming so we don't reset the diff --git a/src/core/download_store.cc b/src/core/download_store.cc index 17dd4b04..af61a663 100644 --- a/src/core/download_store.cc +++ b/src/core/download_store.cc @@ -99,56 +99,82 @@ DownloadStore::set_path(const std::string& path) { } bool -DownloadStore::save(Download* d) { - if (!is_enabled()) - return true; - - std::fstream f((create_filename(d) + ".new").c_str(), std::ios::out | std::ios::trunc); - - if (!f.is_open()) - return false; - - // Move this somewhere else? - d->bencode()->get_key("rtorrent").insert_key("total_uploaded", d->download()->up_rate()->total()); - d->bencode()->get_key("rtorrent").insert_key("chunks_done", d->download()->file_list()->completed_chunks()); - +DownloadStore::write_bencode(const std::string& filename, const torrent::Object& obj, uint32_t skip_mask) { torrent::Object tmp; - torrent::Object& resumeObject = d->download()->bencode()->get_key("libtorrent_resume"); + std::fstream output(filename.c_str(), std::ios::out | std::ios::trunc); - torrent::resume_save_addresses(*d->download(), resumeObject); - torrent::resume_save_file_priorities(*d->download(), resumeObject); - torrent::resume_save_tracker_settings(*d->download(), resumeObject); - - f << *d->bencode(); - - if (!f.good()) + if (!output.is_open()) goto download_store_save_error; - f.close(); + torrent::object_write_bencode(&output, &obj, skip_mask); + + if (!output.good()) + goto download_store_save_error; + + output.close(); // Test the new file, to ensure it is a valid bencode string. - f.open((create_filename(d) + ".new").c_str(), std::ios::in); + output.open(filename.c_str(), std::ios::in); + output >> tmp; - f >> tmp; - - if (!f.good()) + if (!output.good()) goto download_store_save_error; - f.close(); - - ::rename((create_filename(d) + ".new").c_str(), create_filename(d).c_str()); + output.close(); return true; download_store_save_error: - f.close(); + output.close(); return false; } +bool +DownloadStore::save(Download* d, int flags) { + if (!is_enabled()) + 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("total_uploaded", d->download()->up_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); + + std::string base_filename = create_filename(d); + + if (!write_bencode(base_filename + ".libtorrent_resume.new", *resume_base, 0) || + !write_bencode(base_filename + ".rtorrent.new", *rtorrent_base, 0)) + return false; + + ::rename((base_filename + ".libtorrent_resume.new").c_str(), (base_filename + ".libtorrent_resume").c_str()); + ::rename((base_filename + ".rtorrent.new").c_str(), (base_filename + ".rtorrent").c_str()); + + if (!(flags & flag_skip_static) && + write_bencode(base_filename + ".new", *d->bencode(), torrent::Object::flag_session_data)) + ::rename((base_filename + ".new").c_str(), base_filename.c_str()); + + return true; +} + void DownloadStore::remove(Download* d) { if (!is_enabled()) return; + ::unlink((create_filename(d) + ".libtorrent_resume").c_str()); + ::unlink((create_filename(d) + ".rtorrent").c_str()); ::unlink(create_filename(d).c_str()); } diff --git a/src/core/download_store.h b/src/core/download_store.h index 70d60625..94ab5a74 100644 --- a/src/core/download_store.h +++ b/src/core/download_store.h @@ -51,6 +51,7 @@ class Download; class DownloadStore { public: + static const int flag_skip_static = 0x1; bool is_enabled() { return m_lockfile.is_locked(); } @@ -60,7 +61,9 @@ public: const std::string& path() const { return m_path; } void set_path(const std::string& path); - bool save(Download* d); + 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. @@ -71,6 +74,8 @@ public: private: std::string create_filename(Download* d); + bool write_bencode(const std::string& filename, const torrent::Object& obj, uint32_t skip_mask); + std::string m_path; utils::Lockfile m_lockfile; }; diff --git a/src/main.cc b/src/main.cc index 6f07f706..c0f79133 100644 --- a/src/main.cc +++ b/src/main.cc @@ -211,7 +211,7 @@ main(int argc, char** argv) { "system.method.insert = event.download.hash_queued,multi\n" "system.method.set_key = event.download.inserted, 1_connect_logs, d.initialize_logs=\n" - "system.method.set_key = event.download.inserted_new, 1_prepare, \"branch=d.get_state=,view.set_visible=started,view.set_visible=stopped ;d.save_session=\"\n" + "system.method.set_key = event.download.inserted_new, 1_prepare, \"branch=d.get_state=,view.set_visible=started,view.set_visible=stopped ;d.save_full_session=\"\n" "system.method.set_key = event.download.inserted_session, 1_prepare, \"branch=d.get_state=,view.set_visible=started,view.set_visible=stopped\"\n" "system.method.set_key = event.download.erased, !_download_list, ui.unfocus_download=\n" @@ -290,7 +290,7 @@ main(int argc, char** argv) { "schedule = view_main,10,10,\"view_sort=main,20\"\n" "schedule = view_name,10,10,\"view_sort=name,20\"\n" - "schedule = session_save,1800,1800,session_save=\n" + "schedule = session_save,1200,1200,session_save=\n" "schedule = low_diskspace,5,60,close_low_diskspace=500M\n" "schedule = prune_file_status,3600,86400,system.file_status_cache.prune=\n"