mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-10-07 14:49:21 +00:00
* A seperate variable for hashing state, now properly restarts hashing
after restart. * Stop a torrent and show a message when it finishes the final hash-check and finds some chunks incomplete. This prevents full disk from causing a torrent to go into an infinit loop of re-downloading the data. * Moved various slots from core::Manager to core::DownloadList. git-svn-id: svn://rakshasa.no/libtorrent/trunk/rtorrent@679 e378c898-3ddf-0310-93e7-cc216c733640
This commit is contained in:
@@ -70,8 +70,16 @@ Download::Download(download_type d) :
|
||||
|
||||
m_variables.insert("connection_leech", new utils::VariableAny(connection_type_to_string(download_type::CONNECTION_LEECH)));
|
||||
m_variables.insert("connection_seed", new utils::VariableAny(connection_type_to_string(download_type::CONNECTION_SEED)));
|
||||
|
||||
// 0 - stopped
|
||||
// 1 - started
|
||||
m_variables.insert("state", new utils::VariableObject(bencode(), "rtorrent", "state", torrent::Object::TYPE_VALUE));
|
||||
m_variables.insert("complete", new utils::VariableObject(bencode(), "rtorrent", "complete", torrent::Object::TYPE_VALUE));
|
||||
|
||||
// 0 - Not hashing
|
||||
// 1 - Normal hashing
|
||||
// 2 - Download finished, hashing
|
||||
m_variables.insert("hashing", new utils::VariableObject(bencode(), "rtorrent", "hashing", torrent::Object::TYPE_VALUE));
|
||||
m_variables.insert("tied_to_file", new utils::VariableObject(bencode(), "rtorrent", "tied_to_file", torrent::Object::TYPE_STRING));
|
||||
|
||||
// The "state_changed" variable is required to be a valid unix time
|
||||
|
||||
@@ -55,6 +55,10 @@ public:
|
||||
typedef download_type::ConnectionType connection_type;
|
||||
typedef utils::VariableMap variable_map_type;
|
||||
|
||||
static const int64_t variable_hashing_stopped = 0;
|
||||
static const int64_t variable_hashing_started = 1;
|
||||
static const int64_t variable_hashing_last = 2;
|
||||
|
||||
Download(download_type d);
|
||||
~Download();
|
||||
|
||||
@@ -73,7 +77,9 @@ public:
|
||||
tracker_list_type* tracker_list() { return &m_trackerList; }
|
||||
|
||||
const std::string& info_hash() const { return m_download.info_hash(); }
|
||||
|
||||
const std::string& message() const { return m_message; }
|
||||
void set_message(const std::string& msg) { m_message = msg; }
|
||||
|
||||
uint32_t chunks_failed() const { return m_chunksFailed; }
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
|
||||
#include "download.h"
|
||||
#include "download_factory.h"
|
||||
#include "download_store.h"
|
||||
|
||||
namespace core {
|
||||
|
||||
@@ -103,7 +104,7 @@ DownloadFactory::receive_load() {
|
||||
if (std::strncmp(m_uri.c_str(), "http://", 7) == 0) {
|
||||
// Http handling here.
|
||||
m_stream = new std::stringstream;
|
||||
HttpQueue::iterator itr = m_manager->http_queue().insert(m_uri, m_stream);
|
||||
HttpQueue::iterator itr = m_manager->http_queue()->insert(m_uri, m_stream);
|
||||
|
||||
(*itr)->signal_done().slots().push_front(sigc::mem_fun(*this, &DownloadFactory::receive_loaded));
|
||||
(*itr)->signal_failed().slots().push_front(sigc::mem_fun(*this, &DownloadFactory::receive_failed));
|
||||
@@ -190,7 +191,10 @@ DownloadFactory::receive_success() {
|
||||
else
|
||||
download->variable()->set("directory", rtorrent->get_key("directory"));
|
||||
|
||||
if (download->variable()->get_value("state") == 1)
|
||||
// This torrent was queued for hashing or hashing when the session
|
||||
// file was saved. Or it was in a started state.
|
||||
if (download->variable()->get_value("hashing") != Download::variable_hashing_stopped ||
|
||||
download->variable()->get_value("state") != 0)
|
||||
m_manager->download_list()->resume(download);
|
||||
|
||||
} else {
|
||||
@@ -203,7 +207,7 @@ DownloadFactory::receive_success() {
|
||||
if (m_start)
|
||||
m_manager->download_list()->start(download);
|
||||
|
||||
m_manager->download_store().save(download);
|
||||
m_manager->download_store()->save(download);
|
||||
}
|
||||
|
||||
m_slotFinished();
|
||||
@@ -237,6 +241,9 @@ DownloadFactory::initialize_rtorrent(Download* download, torrent::Object* rtorre
|
||||
if (!rtorrent->has_key_value("complete"))
|
||||
rtorrent->insert_key("complete", (int64_t)0);
|
||||
|
||||
if (!rtorrent->has_key_value("hashing"))
|
||||
rtorrent->insert_key("hashing", Download::variable_hashing_stopped);
|
||||
|
||||
if (!rtorrent->has_key_string("tied_to_file"))
|
||||
rtorrent->insert_key("tied_to_file", std::string());
|
||||
|
||||
|
||||
+124
-44
@@ -74,6 +74,18 @@ struct download_list_call {
|
||||
Download* m_download;
|
||||
};
|
||||
|
||||
DownloadList::~DownloadList() {
|
||||
std::for_each(begin(), end(), std::bind1st(std::mem_fun(&DownloadList::close), this));
|
||||
std::for_each(begin(), end(), rak::call_delete<Download>());
|
||||
|
||||
base_type::clear();
|
||||
}
|
||||
|
||||
void
|
||||
DownloadList::session_save() {
|
||||
std::for_each(begin(), end(), std::bind1st(std::mem_fun(&DownloadStore::save), control->core()->download_store()));
|
||||
}
|
||||
|
||||
Download*
|
||||
DownloadList::create(std::istream* str, bool printLog) {
|
||||
torrent::Object* object = new torrent::Object;
|
||||
@@ -113,7 +125,7 @@ DownloadList::insert(Download* download) {
|
||||
} catch (torrent::local_error& e) {
|
||||
// Should perhaps relax this, just print an error and remove the
|
||||
// downloads?
|
||||
throw torrent::internal_error("Caught during DownloadList::insert part 2: " + std::string(e.what()));
|
||||
throw torrent::internal_error("Caught during DownloadList::insert(...): " + std::string(e.what()));
|
||||
}
|
||||
|
||||
return itr;
|
||||
@@ -131,9 +143,9 @@ DownloadList::erase(iterator itr) {
|
||||
if (itr == end())
|
||||
throw torrent::internal_error("DownloadList::erase(...) could not find download.");
|
||||
|
||||
// Make safe to erase active downloads.
|
||||
if ((*itr)->download()->is_active())
|
||||
throw torrent::internal_error("DownloadList::erase(...) called on an active download.");
|
||||
close(*itr);
|
||||
|
||||
control->core()->download_store()->remove(*itr);
|
||||
|
||||
std::for_each(m_slotMapErase.begin(), m_slotMapErase.end(), download_list_call(*itr));
|
||||
|
||||
@@ -187,7 +199,14 @@ DownloadList::close_throw(Download* download) {
|
||||
if (download->download()->is_active())
|
||||
pause(download);
|
||||
|
||||
// Save the torrent on close, this covers shutdown and if a torrent
|
||||
// is manually closed which would clear the progress data. For
|
||||
// better crash protection, save regulary in addition to this.
|
||||
//
|
||||
// Used to be in pause, but this was wrong for rehashing etc.
|
||||
control->core()->download_store()->save(download);
|
||||
control->core()->hash_queue()->remove(download);
|
||||
|
||||
download->download()->close();
|
||||
|
||||
std::for_each(m_slotMapClose.begin(), m_slotMapClose.end(), download_list_call(download));
|
||||
@@ -220,29 +239,44 @@ DownloadList::resume(Download* download) {
|
||||
if (download->download()->is_active())
|
||||
return;
|
||||
|
||||
open_throw(download);
|
||||
|
||||
if (download->download()->is_hash_checked()) {
|
||||
|
||||
if (download->is_done())
|
||||
download->set_connection_type(download->variable()->get_string("connection_seed"));
|
||||
else
|
||||
download->set_connection_type(download->variable()->get_string("connection_leech"));
|
||||
|
||||
// Update the priority to ensure it has the correct
|
||||
// seeding/unfinished modifiers.
|
||||
download->set_priority(download->priority());
|
||||
download->download()->start();
|
||||
|
||||
std::for_each(m_slotMapStart.begin(), m_slotMapStart.end(), download_list_call(download));
|
||||
|
||||
} else {
|
||||
// TODO: This can cause infinit looping?
|
||||
control->core()->hash_queue()->insert(download);
|
||||
}
|
||||
// Properly escape when resume get's called during hashing. The
|
||||
// 'state' is changed by the call to DownloadList::start so it
|
||||
// will automagically start afterwards.
|
||||
if (control->core()->hash_queue()->find(download) != control->core()->hash_queue()->end())
|
||||
return;
|
||||
|
||||
download->variable()->set("state_changed", cachedTime.seconds());
|
||||
|
||||
open_throw(download);
|
||||
|
||||
// Manual or end-of-download rehashing clears the resume data so
|
||||
// we can just start the hashing again without clearing it again.
|
||||
//
|
||||
// It is also assumed the is_hash_checked flag gets cleared when
|
||||
// 'hashing' was set.
|
||||
if (!download->download()->is_hash_checked()) {
|
||||
|
||||
// Set 'hashing' to started if hashing wasn't started, else keep
|
||||
// the old value.
|
||||
if (download->variable()->get_value("hashing") == Download::variable_hashing_stopped)
|
||||
download->variable()->set("hashing", Download::variable_hashing_started);
|
||||
|
||||
control->core()->hash_queue()->insert(download);
|
||||
return;
|
||||
}
|
||||
|
||||
if (download->is_done())
|
||||
download->set_connection_type(download->variable()->get_string("connection_seed"));
|
||||
else
|
||||
download->set_connection_type(download->variable()->get_string("connection_leech"));
|
||||
|
||||
// Update the priority to ensure it has the correct
|
||||
// seeding/unfinished modifiers.
|
||||
download->set_priority(download->priority());
|
||||
download->download()->start();
|
||||
|
||||
std::for_each(m_slotMapStart.begin(), m_slotMapStart.end(), download_list_call(download));
|
||||
|
||||
} catch (torrent::local_error& e) {
|
||||
control->core()->push_log(e.what());
|
||||
}
|
||||
@@ -254,6 +288,10 @@ DownloadList::pause(Download* download) {
|
||||
|
||||
try {
|
||||
|
||||
// Make sure we don't start hash checking a download that we won't
|
||||
// start.
|
||||
control->core()->hash_queue()->remove(download);
|
||||
|
||||
if (!download->download()->is_active())
|
||||
return;
|
||||
|
||||
@@ -266,7 +304,7 @@ DownloadList::pause(Download* download) {
|
||||
|
||||
// Save the state after all the slots, etc have been called so we
|
||||
// include the modifications they may make.
|
||||
control->core()->download_store().save(download);
|
||||
//control->core()->download_store()->save(download);
|
||||
|
||||
} catch (torrent::local_error& e) {
|
||||
control->core()->push_log(e.what());
|
||||
@@ -274,10 +312,8 @@ DownloadList::pause(Download* download) {
|
||||
}
|
||||
|
||||
void
|
||||
DownloadList::clear() {
|
||||
std::for_each(begin(), end(), rak::call_delete<Download>());
|
||||
|
||||
base_type::clear();
|
||||
DownloadList::save(Download* d) {
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
@@ -286,17 +322,28 @@ DownloadList::check_hash(Download* download) {
|
||||
|
||||
try {
|
||||
|
||||
close_throw(download);
|
||||
download->download()->hash_resume_clear();
|
||||
open_throw(download);
|
||||
|
||||
control->core()->hash_queue()->insert(download);
|
||||
download->variable()->set("hashing", Download::variable_hashing_started);
|
||||
check_hash_throw(download);
|
||||
|
||||
} catch (torrent::local_error& e) {
|
||||
control->core()->push_log(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// Throw in addition to not setting 'hashing'.
|
||||
void
|
||||
DownloadList::check_hash_throw(Download* download) {
|
||||
check_contains(download);
|
||||
|
||||
close_throw(download);
|
||||
download->download()->hash_resume_clear();
|
||||
open_throw(download);
|
||||
|
||||
// If any more stuff is added here, make sure resume etc are still
|
||||
// correct.
|
||||
control->core()->hash_queue()->insert(download);
|
||||
}
|
||||
|
||||
void
|
||||
DownloadList::hash_done(Download* download) {
|
||||
check_contains(download);
|
||||
@@ -313,18 +360,42 @@ DownloadList::hash_done(Download* download) {
|
||||
// confirm all the data, avoiding large BW usage on f.ex. the
|
||||
// ReiserFS bug with >4GB files.
|
||||
|
||||
// Use just is_done(), have another if statement inside.
|
||||
if (download->is_done() && download->variable()->get_value("complete") == 0) {
|
||||
int64_t hashing = download->variable()->get_value("hashing");
|
||||
download->variable()->set("hashing", Download::variable_hashing_stopped);
|
||||
|
||||
if (control->variable()->get_value("session_on_completion"))
|
||||
control->core()->download_store().save(download);
|
||||
switch (hashing) {
|
||||
case Download::variable_hashing_started:
|
||||
// Normal re/hashing.
|
||||
|
||||
// Send a "truly finished message from here.
|
||||
confirm_finished(download);
|
||||
if (download->is_done())
|
||||
download->variable()->set("complete", (int64_t)1);
|
||||
|
||||
if (download->variable()->get_value("state") == 1)
|
||||
resume(download);
|
||||
|
||||
return;
|
||||
|
||||
case Download::variable_hashing_last:
|
||||
|
||||
if (download->is_done()) {
|
||||
|
||||
confirm_finished(download);
|
||||
|
||||
if (download->variable()->get_value("state") == 1)
|
||||
resume(download);
|
||||
|
||||
} else {
|
||||
download->set_message("Hash check on download completion found bad chunks.");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
case Download::variable_hashing_stopped:
|
||||
default:
|
||||
// Either an error or someone wrote to the hashing variable...
|
||||
download->set_message("Hash check completed but the \"hashing\" variable is in an invalid state.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (download->variable()->get_value("state") == 1)
|
||||
resume(download);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -335,13 +406,15 @@ DownloadList::received_finished(Download* download) {
|
||||
// Set some 'checking_finished_thingie' variable to make hash_done
|
||||
// trigger correctly, also so it can bork on missing data.
|
||||
|
||||
check_hash(download);
|
||||
download->variable()->set("hashing", Download::variable_hashing_last);
|
||||
check_hash_throw(download);
|
||||
|
||||
} else {
|
||||
confirm_finished(download);
|
||||
}
|
||||
}
|
||||
|
||||
// The download must be open when we call this function.
|
||||
void
|
||||
DownloadList::confirm_finished(Download* download) {
|
||||
check_contains(download);
|
||||
@@ -354,6 +427,13 @@ DownloadList::confirm_finished(Download* download) {
|
||||
|
||||
download->download()->tracker_list().send_completed();
|
||||
|
||||
// Do this before the slots are called in case one of them closes
|
||||
// the download.
|
||||
if (!download->is_active() && control->variable()->get_value("session_on_completion") == 1) {
|
||||
download->download()->hash_resume_save();
|
||||
control->core()->download_store()->save(download);
|
||||
}
|
||||
|
||||
std::for_each(m_slotMapFinished.begin(), m_slotMapFinished.end(), download_list_call(download));
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,9 @@ public:
|
||||
using base_type::empty;
|
||||
using base_type::size;
|
||||
|
||||
~DownloadList() { clear(); }
|
||||
~DownloadList();
|
||||
|
||||
void session_save();
|
||||
|
||||
// Might move this to DownloadFactory.
|
||||
Download* create(std::istream* str, bool printLog);
|
||||
@@ -95,7 +97,11 @@ public:
|
||||
void resume(Download* d);
|
||||
void pause(Download* d);
|
||||
|
||||
void save(Download* d);
|
||||
|
||||
void check_hash(Download* d);
|
||||
void check_hash_throw(Download* d);
|
||||
|
||||
void hash_done(Download* d);
|
||||
|
||||
slot_map& slot_map_insert() { return m_slotMapInsert; }
|
||||
@@ -129,8 +135,6 @@ public:
|
||||
private:
|
||||
inline void check_contains(Download* d);
|
||||
|
||||
void clear();
|
||||
|
||||
void received_finished(Download* d);
|
||||
void confirm_finished(Download* d);
|
||||
|
||||
|
||||
@@ -49,8 +49,7 @@ namespace core {
|
||||
|
||||
void
|
||||
HashQueue::insert(Download* download) {
|
||||
if (download->download()->is_hash_checking() ||
|
||||
find(download) != end())
|
||||
if (download->download()->is_hash_checking() || find(download) != end())
|
||||
return;
|
||||
|
||||
if (download->download()->is_hash_checked()) {
|
||||
|
||||
+10
-4
@@ -59,7 +59,9 @@
|
||||
#include "curl_get.h"
|
||||
#include "download.h"
|
||||
#include "download_factory.h"
|
||||
#include "download_store.h"
|
||||
#include "hash_queue.h"
|
||||
#include "http_queue.h"
|
||||
#include "manager.h"
|
||||
#include "poll_manager_epoll.h"
|
||||
#include "poll_manager_select.h"
|
||||
@@ -116,13 +118,19 @@ Manager::Manager() :
|
||||
m_portFirst(6890),
|
||||
m_portLast(6999) {
|
||||
|
||||
m_downloadStore = new DownloadStore();
|
||||
m_downloadList = new DownloadList();
|
||||
|
||||
m_httpQueue = new HttpQueue();
|
||||
m_hashQueue = new HashQueue(m_downloadList);
|
||||
}
|
||||
|
||||
Manager::~Manager() {
|
||||
delete m_hashQueue;
|
||||
delete m_downloadList;
|
||||
delete m_hashQueue;
|
||||
|
||||
delete m_downloadStore;
|
||||
delete m_httpQueue;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -142,7 +150,7 @@ Manager::initialize_first() {
|
||||
void
|
||||
Manager::initialize_second() {
|
||||
torrent::Http::set_factory(m_pollManager->get_http_stack()->get_http_factory());
|
||||
m_httpQueue.slot_factory(m_pollManager->get_http_stack()->get_http_factory());
|
||||
m_httpQueue->slot_factory(m_pollManager->get_http_stack()->get_http_factory());
|
||||
|
||||
CurlStack::global_init();
|
||||
|
||||
@@ -152,8 +160,6 @@ Manager::initialize_second() {
|
||||
m_downloadList->slot_map_insert()["1_connect_storage_log"] = sigc::bind(sigc::ptr_fun(&connect_signal_storage_log), sigc::mem_fun(m_logComplete, &Log::push_front));
|
||||
m_downloadList->slot_map_insert()["1_connect_tracker_dump"] = sigc::bind(sigc::ptr_fun(&connect_signal_tracker_dump), sigc::ptr_fun(&receive_tracker_dump));
|
||||
|
||||
m_downloadList->slot_map_erase()["1_hash_queue_remove"] = sigc::mem_fun(m_hashQueue, &HashQueue::remove);
|
||||
m_downloadList->slot_map_erase()["1_store_remove"] = sigc::mem_fun(m_downloadStore, &DownloadStore::remove);
|
||||
m_downloadList->slot_map_erase()["1_delete_tied"] = sigc::ptr_fun(&delete_tied);
|
||||
}
|
||||
|
||||
|
||||
+7
-6
@@ -40,8 +40,6 @@
|
||||
#include <iosfwd>
|
||||
|
||||
#include "download_list.h"
|
||||
#include "download_store.h"
|
||||
#include "http_queue.h"
|
||||
#include "poll_manager.h"
|
||||
#include "log.h"
|
||||
|
||||
@@ -51,7 +49,9 @@ namespace torrent {
|
||||
|
||||
namespace core {
|
||||
|
||||
class DownloadStore;
|
||||
class HashQueue;
|
||||
class HttpQueue;
|
||||
|
||||
class Manager {
|
||||
public:
|
||||
@@ -62,11 +62,12 @@ public:
|
||||
Manager();
|
||||
~Manager();
|
||||
|
||||
DownloadStore& download_store() { return m_downloadStore; }
|
||||
HttpQueue& http_queue() { return m_httpQueue; }
|
||||
|
||||
DownloadList* download_list() { return m_downloadList; }
|
||||
DownloadStore* download_store() { return m_downloadStore; }
|
||||
|
||||
HashQueue* hash_queue() { return m_hashQueue; }
|
||||
HttpQueue* http_queue() { return m_httpQueue; }
|
||||
|
||||
PollManager* get_poll_manager() { return m_pollManager; }
|
||||
Log& get_log_important() { return m_logImportant; }
|
||||
@@ -104,9 +105,9 @@ private:
|
||||
void receive_http_failed(std::string msg);
|
||||
|
||||
DownloadList* m_downloadList;
|
||||
DownloadStore m_downloadStore;
|
||||
DownloadStore* m_downloadStore;
|
||||
HashQueue* m_hashQueue;
|
||||
HttpQueue m_httpQueue;
|
||||
HttpQueue* m_httpQueue;
|
||||
|
||||
PollManager* m_pollManager;
|
||||
Log m_logImportant;
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <stdexcept>
|
||||
#include <rak/error_number.h>
|
||||
|
||||
#include "poll_manager.h"
|
||||
|
||||
@@ -93,8 +93,8 @@ PollManager::~PollManager() {
|
||||
|
||||
void
|
||||
PollManager::check_error() {
|
||||
if (errno != EINTR)
|
||||
throw std::runtime_error("Poll::work(): select error");
|
||||
if (rak::error_number::current().value() != rak::error_number::e_intr)
|
||||
throw std::runtime_error("Poll::work(): " + std::string(rak::error_number::current().c_str()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user