This commit is contained in:
parent
7e9b6e816f
commit
df694996a9
|
@ -25,6 +25,13 @@
|
|||
# pytorrent should be visible, and only it should be imported, in the client.
|
||||
#
|
||||
|
||||
# Documentation:
|
||||
# Torrents have 3 structures:
|
||||
# 1. torrent_info - persistent data, like name, upload speed cap, etc.
|
||||
# 2. core_torrent_state - transient state data from the core. This may take
|
||||
# time to calculate, so we do if efficiently
|
||||
# 3. supp_torrent_state - supplementary torrent data, from pytorrent
|
||||
|
||||
|
||||
import pytorrent_core
|
||||
import os, shutil
|
||||
|
@ -63,9 +70,9 @@ class PyTorrentError(Exception):
|
|||
return repr(self.value)
|
||||
|
||||
|
||||
# Information for a single torrent
|
||||
# Persistent information for a single torrent
|
||||
|
||||
class torrent:
|
||||
class torrent_info:
|
||||
def __init__(self, filename, save_dir, compact):
|
||||
self.filename = filename
|
||||
self.save_dir = save_dir
|
||||
|
@ -112,11 +119,14 @@ class manager:
|
|||
self.constants = pytorrent_core.constants()
|
||||
|
||||
# Unique IDs are NOT in the state, since they are temporary for each session
|
||||
self.unique_IDs = {} # unique_ID -> a torrent object
|
||||
self.unique_IDs = {} # unique_ID -> a torrent object, i.e. persistent data
|
||||
|
||||
# Saved torrent states. We do not poll the core in a costly manner, necessarily
|
||||
self.saved_torrent_states = {} # unique_ID -> torrent_state
|
||||
self.saved_torrent_states_timestamp = {} # time of creation
|
||||
# Saved torrent core_states. We do not poll the core in a costly manner, necessarily
|
||||
self.saved_torrent_core_states = {} # unique_ID -> torrent_state
|
||||
self.saved_torrent_core_states_timestamp = {} # time of creation
|
||||
|
||||
# supplementary torrent states
|
||||
self.supp_torrent_states = {} # unique_ID->dict of data
|
||||
|
||||
# Unpickle the preferences, or create a new one
|
||||
try:
|
||||
|
@ -165,6 +175,15 @@ class manager:
|
|||
# Shutdown torrent core
|
||||
pytorrent_core.quit()
|
||||
|
||||
# This is the EXTERNAL function, for the GUI. It returns the core_state + supp_state
|
||||
def get_torrent_state(self, unique_ID):
|
||||
ret = self.get_torrent_core_state(unique_ID, True).copy()
|
||||
|
||||
if self.get_supp_torrent_state(unique_ID) is not None:
|
||||
ret.update(self.get_supp_torrent_state(unique_ID))
|
||||
|
||||
return ret
|
||||
|
||||
def get_pref(self, key):
|
||||
# If we have a value, return, else fallback on default_prefs, else raise an error
|
||||
# the fallback is useful if the source has newer prefs than the existing pref state,
|
||||
|
@ -243,25 +262,6 @@ class manager:
|
|||
ret['DHT_nodes'] = pytorrent_core.get_DHT_info()
|
||||
return ret
|
||||
|
||||
# Efficient: use a saved state, if it hasn't expired yet
|
||||
def get_torrent_state(self, unique_ID, efficiently = False):
|
||||
if efficiently:
|
||||
try:
|
||||
if time.time() < self.saved_torrent_states_timestamp[unique_ID] + \
|
||||
TORRENT_STATE_EXPIRATION:
|
||||
return self.saved_torrent_states[unique_ID]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
self.saved_torrent_states_timestamp[unique_ID] = time.time()
|
||||
self.saved_torrent_states[unique_ID] = pytorrent_core.get_state(unique_ID)
|
||||
|
||||
return self.saved_torrent_states[unique_ID]
|
||||
|
||||
def mark_state_dirty(self, unique_ID):
|
||||
del self.saved_torrent_states[unique_ID]
|
||||
del self.saved_torrent_states_timestamp[unique_ID]
|
||||
|
||||
def queue_up(self, unique_ID):
|
||||
curr_index = self.get_queue_index(unique_ID)
|
||||
if curr_index > 0:
|
||||
|
@ -284,7 +284,7 @@ class manager:
|
|||
|
||||
def clear_completed(self):
|
||||
for unique_ID in self.unique_IDs:
|
||||
torrent_state = self.get_torrent_state(unique_ID, True)
|
||||
torrent_state = self.get_torrent_core_state(unique_ID)
|
||||
if torrent_state['progress'] == 100.0:
|
||||
self.remove_torrent_ns(unique_ID)
|
||||
|
||||
|
@ -304,13 +304,13 @@ class manager:
|
|||
# This should be called after changes to relevant parameters (user_pausing, or
|
||||
# altering max_active_torrents), or just from time to time
|
||||
# ___ALL queuing code should be in this function, and ONLY here___
|
||||
def apply_queue(self):
|
||||
def apply_queue(self, efficient = True):
|
||||
# Handle autoseeding - downqueue as needed
|
||||
|
||||
if self.auto_seed_ratio != -1:
|
||||
for unique_ID in self.unique_IDs:
|
||||
if self.get_torrent_state(unique_ID, True)['is_seed']:
|
||||
torrent_state = self.get_torrent_state(unique_ID, True)
|
||||
if self.get_torrent_core_state(unique_ID, efficient)['is_seed']:
|
||||
torrent_state = self.get_torrent_core_state(unique_ID, efficient)
|
||||
ratio = self.calc_ratio(unique_ID, torrent_state)
|
||||
if ratio >= self.auto_seed_ratio:
|
||||
self.queue_bottom(unique_ID)
|
||||
|
@ -319,10 +319,10 @@ class manager:
|
|||
for index in range(len(self.state.queue)):
|
||||
unique_ID = self.state.queue[index]
|
||||
if (index < self.state.max_active_torrents or self.state_max_active_torrents == -1) \
|
||||
and self.get_torrent_state(unique_ID, True)['is_paused'] \
|
||||
and self.get_torrent_core_state(unique_ID, efficient)['is_paused'] \
|
||||
and not self.is_user_paused(unique_ID):
|
||||
pytorrent_core.resume(unique_ID)
|
||||
elif not self.get_torrent_state(unique_ID, True)['is_paused'] and \
|
||||
elif not self.get_torrent_core_state(unique_ID, efficient)['is_paused'] and \
|
||||
(index >= self.state.max_active_torrents or self.is_user_paused(unique_ID)):
|
||||
pytorrent_core.pause(unique_ID)
|
||||
|
||||
|
@ -340,49 +340,69 @@ class manager:
|
|||
def get_num_torrents(self):
|
||||
return pytorrent_core.get_num_torrents()
|
||||
|
||||
# Handle them for the backend's purposes, but still send them up in case the client
|
||||
# wants to do something - show messages, for example
|
||||
def handle_events(self):
|
||||
ret = []
|
||||
|
||||
event = pytorrent_core.pop_event()
|
||||
|
||||
while event is not None:
|
||||
# print "EVENT: ", event
|
||||
|
||||
ret.append(event)
|
||||
|
||||
if event['event_type'] is self.constants['EVENT_FINISHED']:
|
||||
# If we are autoseeding, then we need to apply the queue
|
||||
if self.auto_seed_ratio == -1:
|
||||
self.mark_state_dirty(event['unique_ID']) # So the queuing will be to new data
|
||||
self.apply_queue()
|
||||
|
||||
elif event['event_type'] is self.constants['EVENT_PEER_ERROR']:
|
||||
# self.parent.addMessage(_("Peer Error") + ": " + str(event), "I") # Debug only!
|
||||
pass
|
||||
elif event['event_type'] is self.constants['EVENT_INVALID_REQUEST']:
|
||||
print 'self.parent.addMessage(_("Invalid request") + ": " + str(event), "W") # Maybe "I"?'
|
||||
elif event['event_type'] is self.constants['EVENT_FILE_ERROR']:
|
||||
# dc.debugmsg("File error! " + str(event))
|
||||
print 'self.parent.addMessage(_("File error") + "! " + str(event), "F")'
|
||||
elif event['event_type'] is self.constants['EVENT_HASH_FAILED_ERROR']:
|
||||
print 'self.parent.addMessage(_("Hash failed") + ": " + str(event), "I")'
|
||||
elif event['event_type'] is self.constants['EVENT_PEER_BAN_ERROR']:
|
||||
print 'self.parent.addMessage(_("Peer banned") + ": " + str(event), "I")'
|
||||
elif event['event_type'] is self.constants['EVENT_FASTRESUME_REJECTED_ERROR']:
|
||||
# dc.debugmsg("Fastresume rejected: " + str(event))
|
||||
print 'self.parent.addMessage(_("Fastresume rejected") + ": " + str(event), "W")'
|
||||
self.apply_queue(efficient = False) # To work on current data
|
||||
elif event['event_type'] is self.constants['EVENT_TRACKER']:
|
||||
print event['tracker_status'], event['message']
|
||||
elif event['event_type'] is self.constants['EVENT_OTHER']:
|
||||
print 'self.parent.addMessage(_("Event") + ": " + str(event), "W")'
|
||||
else:
|
||||
# dc.debugmsg("Internal error, undefined event type")
|
||||
# dc.debugmsg("No such event error. Raw data: " + str(event))
|
||||
print 'self.parent.addMessage(_("Event") + ": " + str(event), "C")'
|
||||
self.set_supp_torrent_state_val( event['unique_ID'],
|
||||
"tracker_status",
|
||||
event['tracker_status'])
|
||||
self.set_supp_torrent_state_val( event['unique_ID'],
|
||||
"tracker_message",
|
||||
event['message'])
|
||||
|
||||
event = pytorrent_core.pop_event()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
####################
|
||||
# Internal functions
|
||||
####################
|
||||
|
||||
# Efficient: use a saved state, if it hasn't expired yet
|
||||
def get_torrent_core_state(self, unique_ID, efficiently=True):
|
||||
if efficiently:
|
||||
try:
|
||||
if time.time() < self.saved_torrent_core_states_timestamp[unique_ID] + \
|
||||
TORRENT_STATE_EXPIRATION:
|
||||
return self.saved_torrent_core_states[unique_ID]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
self.saved_torrent_core_states_timestamp[unique_ID] = time.time()
|
||||
self.saved_torrent_core_states[unique_ID] = pytorrent_core.get_torrent_state(unique_ID)
|
||||
|
||||
return self.saved_torrent_core_states[unique_ID]
|
||||
|
||||
def get_supp_torrent_state(self, unique_ID):
|
||||
try:
|
||||
return self.supp_torrent_states[unique_ID]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def set_supp_torrent_state_val(self, unique_ID, key, val):
|
||||
try:
|
||||
if self.supp_torrent_states[unique_ID] is None:
|
||||
self.supp_torrent_states[unique_ID] = {}
|
||||
except KeyError:
|
||||
self.supp_torrent_states[unique_ID] = {}
|
||||
|
||||
self.supp_torrent_states[unique_ID][key] = val
|
||||
|
||||
# Non-syncing functions. Used when we loop over such events, and sync manually at the end
|
||||
|
||||
def add_torrent_ns(self, filename, save_dir, compact):
|
||||
|
@ -399,7 +419,7 @@ class manager:
|
|||
shutil.copy(filename, full_new_name)
|
||||
|
||||
# Create torrent object
|
||||
new_torrent = torrent(full_new_name, save_dir, compact)
|
||||
new_torrent = torrent_info(full_new_name, save_dir, compact)
|
||||
self.state.torrents.append(new_torrent)
|
||||
|
||||
def remove_torrent_ns(self, unique_ID):
|
||||
|
|
|
@ -567,7 +567,7 @@ static PyObject *torrent_get_torrent_info(PyObject *self, PyObject *args)
|
|||
);
|
||||
}
|
||||
|
||||
static PyObject *torrent_get_state(PyObject *self, PyObject *args)
|
||||
static PyObject *torrent_get_torrent_state(PyObject *self, PyObject *args)
|
||||
{
|
||||
python_long unique_ID;
|
||||
if (!PyArg_ParseTuple(args, "i", &unique_ID))
|
||||
|
@ -971,7 +971,7 @@ static PyObject *torrent_start_DHT(PyObject *self, PyObject *args)
|
|||
if (!PyArg_ParseTuple(args, "s", &DHT_path))
|
||||
return NULL;
|
||||
|
||||
printf("Loading DHT state from %s\r\n", DHT_path);
|
||||
// printf("Loading DHT state from %s\r\n", DHT_path);
|
||||
|
||||
boost::filesystem::path tempPath(DHT_path, empty_name_check);
|
||||
boost::filesystem::ifstream DHT_state_file(tempPath, std::ios_base::binary);
|
||||
|
@ -982,10 +982,10 @@ static PyObject *torrent_start_DHT(PyObject *self, PyObject *args)
|
|||
DHT_state = bdecode(std::istream_iterator<char>(DHT_state_file),
|
||||
std::istream_iterator<char>());
|
||||
M_ses->start_dht(DHT_state);
|
||||
printf("DHT state recovered.\r\n");
|
||||
// printf("DHT state recovered.\r\n");
|
||||
|
||||
// Print out the state data from the FILE (not the session!)
|
||||
printf("Number of DHT peers in recovered state: %ld\r\n", count_DHT_peers(DHT_state));
|
||||
// // Print out the state data from the FILE (not the session!)
|
||||
// printf("Number of DHT peers in recovered state: %ld\r\n", count_DHT_peers(DHT_state));
|
||||
|
||||
} catch (std::exception&) {
|
||||
printf("No DHT file to resume\r\n");
|
||||
|
@ -1008,14 +1008,14 @@ static PyObject *torrent_stop_DHT(PyObject *self, PyObject *args)
|
|||
if (!PyArg_ParseTuple(args, "s", &DHT_path))
|
||||
return NULL;
|
||||
|
||||
printf("Saving DHT state to %s\r\n", DHT_path);
|
||||
// printf("Saving DHT state to %s\r\n", DHT_path);
|
||||
|
||||
boost::filesystem::path tempPath = boost::filesystem::path(DHT_path, empty_name_check);
|
||||
|
||||
try {
|
||||
entry DHT_state = M_ses->dht_state();
|
||||
|
||||
printf("Number of DHT peers in state, saving: %ld\r\n", count_DHT_peers(DHT_state));
|
||||
// printf("Number of DHT peers in state, saving: %ld\r\n", count_DHT_peers(DHT_state));
|
||||
|
||||
boost::filesystem::ofstream out(tempPath, std::ios_base::binary);
|
||||
out.unsetf(std::ios_base::skipws);
|
||||
|
@ -1185,7 +1185,7 @@ static PyMethodDef pytorrent_core_methods[] = {
|
|||
{"pause", torrent_pause, METH_VARARGS, "."},
|
||||
{"resume", torrent_resume, METH_VARARGS, "."},
|
||||
{"get_torrent_info", torrent_get_torrent_info, METH_VARARGS, "."},
|
||||
{"get_state", torrent_get_state, METH_VARARGS, "."},
|
||||
{"get_torrent_state", torrent_get_torrent_state, METH_VARARGS, "."},
|
||||
{"pop_event", torrent_pop_event, METH_VARARGS, "."},
|
||||
{"get_session_info", torrent_get_session_info, METH_VARARGS, "."},
|
||||
{"get_peer_info", torrent_get_peer_info, METH_VARARGS, "."},
|
||||
|
|
|
@ -16,7 +16,7 @@ import os
|
|||
manager = pytorrent.manager("PT", "0500", "pytorrent - testing only",
|
||||
os.path.expanduser("~") + "/Temp")
|
||||
|
||||
manager.set_pref('max_upload_rate', 6*1024)
|
||||
#manager.set_pref('max_upload_rate', 6*1024)
|
||||
|
||||
#my_torrent = manager.add_torrent("xubuntu-6.10-desktop-i386.iso.torrent", ".", True)
|
||||
|
||||
|
@ -34,4 +34,5 @@ try:
|
|||
print ""
|
||||
sleep(2)
|
||||
except KeyboardInterrupt:
|
||||
print "Shutting down:"
|
||||
manager.quit()
|
||||
|
|
Loading…
Reference in New Issue