Cleanup torrentmanger code

* Remove legacy/old state file code
 * Passes flake8 cleanly
 * Most pylint messages dealt with
 * Code now uses >=Python 2.6 'with' and 'as' statements
This commit is contained in:
Calum Lind 2013-05-22 19:10:45 +01:00
parent 77cb794e4d
commit e3f3b6d751
2 changed files with 204 additions and 378 deletions

View File

@ -1,143 +0,0 @@
#
# oldstateupgrader.py
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
#
import os
import os.path
import pickle
import cPickle
import shutil
import logging
from deluge._libtorrent import lt
from deluge.configmanager import ConfigManager, get_config_dir
import deluge.core.torrentmanager
log = logging.getLogger(__name__)
#start : http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286203
def makeFakeClass(module, name):
class FakeThing(object):
pass
FakeThing.__name__ = name
FakeThing.__module__ = '(fake)' + module
return FakeThing
class PickleUpgrader(pickle.Unpickler):
def find_class(self, module, cname):
# Pickle tries to load a couple things like copy_reg and
# __builtin__.object even though a pickle file doesn't
# explicitly reference them (afaict): allow them to be loaded
# normally.
if module in ('copy_reg', '__builtin__'):
thing = pickle.Unpickler.find_class(self, module, cname)
return thing
return makeFakeClass(module, cname)
# end: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286203
class OldStateUpgrader:
def __init__(self):
self.config = ConfigManager("core.conf")
self.state05_location = os.path.join(get_config_dir(), "persistent.state")
self.state10_location = os.path.join(get_config_dir(), "state", "torrents.state")
if os.path.exists(self.state05_location) and not os.path.exists(self.state10_location):
# If the 0.5 state file exists and the 1.0 doesn't, then let's upgrade it
self.upgrade05()
def upgrade05(self):
try:
state = PickleUpgrader(open(self.state05_location, "rb")).load()
except Exception, e:
log.debug("Unable to open 0.5 state file: %s", e)
return
# Check to see if we can upgrade this file
if type(state).__name__ == 'list':
log.warning("0.5 state file is too old to upgrade")
return
new_state = deluge.core.torrentmanager.TorrentManagerState()
for ti, uid in state.torrents.items():
torrent_path = os.path.join(get_config_dir(), "torrentfiles", ti.filename)
try:
torrent_info = None
log.debug("Attempting to create torrent_info from %s", torrent_path)
_file = open(torrent_path, "rb")
torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
_file.close()
except (IOError, RuntimeError), e:
log.warning("Unable to open %s: %s", torrent_path, e)
# Copy the torrent file to the new location
import shutil
shutil.copyfile(torrent_path, os.path.join(get_config_dir(), "state", str(torrent_info.info_hash()) + ".torrent"))
# Set the file prioritiy property if not already there
if not hasattr(ti, "priorities"):
ti.priorities = [1] * torrent_info.num_files()
# Create the new TorrentState object
new_torrent = deluge.core.torrentmanager.TorrentState(
torrent_id=str(torrent_info.info_hash()),
filename=ti.filename,
save_path=ti.save_dir,
compact=ti.compact,
paused=ti.user_paused,
total_uploaded=ti.uploaded_memory,
max_upload_speed=ti.upload_rate_limit,
max_download_speed=ti.download_rate_limit,
file_priorities=ti.priorities,
queue=state.queue.index(ti)
)
# Append the object to the state list
new_state.torrents.append(new_torrent)
# Now we need to write out the new state file
try:
log.debug("Saving torrent state file.")
state_file = open(
os.path.join(get_config_dir(), "state", "torrents.state"), "wb")
cPickle.dump(new_state, state_file)
state_file.close()
except IOError, e:
log.warning("Unable to save state file: %s", e)
return
# Rename the persistent.state file
try:
os.rename(self.state05_location, self.state05_location + ".old")
except Exception, e:
log.debug("Unable to rename old persistent.state file! %s", e)

View File

@ -49,18 +49,19 @@ from twisted.internet import reactor
from deluge._libtorrent import lt from deluge._libtorrent import lt
from deluge.event import * from deluge.event import (TorrentAddedEvent, PreTorrentRemovedEvent, TorrentRemovedEvent,
from deluge.error import * SessionStartedEvent, TorrentFinishedEvent, TorrentStateChangedEvent,
TorrentResumedEvent, TorrentFileRenamedEvent, TorrentFileCompletedEvent)
from deluge.error import InvalidTorrentError
import deluge.component as component import deluge.component as component
from deluge.configmanager import ConfigManager, get_config_dir from deluge.configmanager import ConfigManager, get_config_dir
from deluge.core.authmanager import AUTH_LEVEL_ADMIN from deluge.core.authmanager import AUTH_LEVEL_ADMIN
from deluge.core.torrent import Torrent from deluge.core.torrent import Torrent, TorrentOptions, sanitize_filepath
from deluge.core.torrent import TorrentOptions
import deluge.core.oldstateupgrader
from deluge.common import utf8_encoded, decode_string from deluge.common import utf8_encoded, decode_string
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class TorrentState: class TorrentState:
def __init__(self, def __init__(self,
torrent_id=None, torrent_id=None,
@ -89,8 +90,7 @@ class TorrentState:
time_added=-1, time_added=-1,
last_seen_complete=0, last_seen_complete=0,
owner=None, owner=None,
shared=False shared=False):
):
self.torrent_id = torrent_id self.torrent_id = torrent_id
self.filename = filename self.filename = filename
self.total_uploaded = total_uploaded self.total_uploaded = total_uploaded
@ -121,10 +121,12 @@ class TorrentState:
self.move_completed_path = move_completed_path self.move_completed_path = move_completed_path
self.shared = shared self.shared = shared
class TorrentManagerState: class TorrentManagerState:
def __init__(self): def __init__(self):
self.torrents = [] self.torrents = []
class TorrentManager(component.Component): class TorrentManager(component.Component):
""" """
TorrentManager contains a list of torrents in the current libtorrent TorrentManager contains a list of torrents in the current libtorrent
@ -135,7 +137,7 @@ class TorrentManager(component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, "TorrentManager", interval=5, component.Component.__init__(self, "TorrentManager", interval=5,
depend=["CorePluginManager", "AlertManager"]) depend=["CorePluginManager", "AlertManager"])
log.debug("TorrentManager init..") log.debug("TorrentManager init...")
# Set the libtorrent session # Set the libtorrent session
self.session = component.get("Core").session self.session = component.get("Core").session
# Set the alertmanager # Set the alertmanager
@ -175,48 +177,35 @@ class TorrentManager(component.Component):
self.on_set_max_download_speed_per_torrent) self.on_set_max_download_speed_per_torrent)
# Register alert functions # Register alert functions
self.alerts.register_handler("torrent_finished_alert", self.alerts.register_handler("torrent_finished_alert", self.on_alert_torrent_finished)
self.on_alert_torrent_finished) self.alerts.register_handler("torrent_paused_alert", self.on_alert_torrent_paused)
self.alerts.register_handler("torrent_paused_alert", self.alerts.register_handler("torrent_checked_alert", self.on_alert_torrent_checked)
self.on_alert_torrent_paused) self.alerts.register_handler("tracker_reply_alert", self.on_alert_tracker_reply)
self.alerts.register_handler("torrent_checked_alert", self.alerts.register_handler("tracker_announce_alert", self.on_alert_tracker_announce)
self.on_alert_torrent_checked) self.alerts.register_handler("tracker_warning_alert", self.on_alert_tracker_warning)
self.alerts.register_handler("tracker_reply_alert", self.alerts.register_handler("tracker_error_alert", self.on_alert_tracker_error)
self.on_alert_tracker_reply) self.alerts.register_handler("storage_moved_alert", self.on_alert_storage_moved)
self.alerts.register_handler("tracker_announce_alert", self.alerts.register_handler("torrent_resumed_alert", self.on_alert_torrent_resumed)
self.on_alert_tracker_announce) self.alerts.register_handler("state_changed_alert", self.on_alert_state_changed)
self.alerts.register_handler("tracker_warning_alert", self.alerts.register_handler("save_resume_data_alert", self.on_alert_save_resume_data)
self.on_alert_tracker_warning)
self.alerts.register_handler("tracker_error_alert",
self.on_alert_tracker_error)
self.alerts.register_handler("storage_moved_alert",
self.on_alert_storage_moved)
self.alerts.register_handler("torrent_resumed_alert",
self.on_alert_torrent_resumed)
self.alerts.register_handler("state_changed_alert",
self.on_alert_state_changed)
self.alerts.register_handler("save_resume_data_alert",
self.on_alert_save_resume_data)
self.alerts.register_handler("save_resume_data_failed_alert", self.alerts.register_handler("save_resume_data_failed_alert",
self.on_alert_save_resume_data_failed) self.on_alert_save_resume_data_failed)
self.alerts.register_handler("file_renamed_alert", self.alerts.register_handler("file_renamed_alert", self.on_alert_file_renamed)
self.on_alert_file_renamed) self.alerts.register_handler("metadata_received_alert", self.on_alert_metadata_received)
self.alerts.register_handler("metadata_received_alert", self.alerts.register_handler("file_error_alert", self.on_alert_file_error)
self.on_alert_metadata_received) self.alerts.register_handler("file_completed_alert", self.on_alert_file_completed)
self.alerts.register_handler("file_error_alert", self.alerts.register_handler("state_update_alert", self.on_alert_state_update)
self.on_alert_file_error)
self.alerts.register_handler("file_completed_alert", # Define timers
self.on_alert_file_completed) self.save_state_timer = LoopingCall(self.save_state)
self.alerts.register_handler("state_update_alert", self.save_resume_data_timer = LoopingCall(self.save_resume_data)
self.on_alert_state_update) self.save_all_resume_data_timer = LoopingCall(self.save_resume_data, self.torrents.keys())
def start(self): def start(self):
# Get the pluginmanager reference
self.plugins = component.get("CorePluginManager")
# Check for old temp file to verify safe shutdown # Check for old temp file to verify safe shutdown
if os.path.isfile(self.temp_file): if os.path.isfile(self.temp_file):
def archive_file(filename): def archive_file(filename):
"""Archives the file in 'archive' sub-directory with timestamp appended"""
import datetime import datetime
filepath = os.path.join(self.state_dir, filename) filepath = os.path.join(self.state_dir, filename)
filepath_bak = filepath + ".bak" filepath_bak = filepath + ".bak"
@ -241,19 +230,13 @@ class TorrentManager(component.Component):
with file(self.temp_file, 'a'): with file(self.temp_file, 'a'):
os.utime(self.temp_file, None) os.utime(self.temp_file, None)
# Run the old state upgrader before loading state
deluge.core.oldstateupgrader.OldStateUpgrader()
# Try to load the state from file # Try to load the state from file
self.load_state() self.load_state()
# Save the state periodically # Save the state periodically
self.save_state_timer = LoopingCall(self.save_state)
self.save_state_timer.start(200, False) self.save_state_timer.start(200, False)
self.save_resume_data_timer = LoopingCall(self.save_resume_data)
self.save_resume_data_timer.start(190, False) self.save_resume_data_timer.start(190, False)
# Force update for all resume data a bit less frequently # Force update for all resume data a bit less frequently
self.save_all_resume_data_timer = LoopingCall(self.save_resume_data, self.torrents.keys())
self.save_all_resume_data_timer.start(900, False) self.save_all_resume_data_timer.start(900, False)
def stop(self): def stop(self):
@ -276,6 +259,7 @@ class TorrentManager(component.Component):
self.torrents[key].prev_status_cleanup_loop.stop() self.torrents[key].prev_status_cleanup_loop.stop()
def remove_temp_file(result): def remove_temp_file(result):
"""Remove the temp_file to signify successfully saved state"""
if result and os.path.isfile(self.temp_file): if result and os.path.isfile(self.temp_file):
os.remove(self.temp_file) os.remove(self.temp_file)
@ -287,10 +271,9 @@ class TorrentManager(component.Component):
for torrent_id, torrent in self.torrents.items(): for torrent_id, torrent in self.torrents.items():
if torrent.options["stop_at_ratio"] and torrent.state not in ( if torrent.options["stop_at_ratio"] and torrent.state not in (
"Checking", "Allocating", "Paused", "Queued"): "Checking", "Allocating", "Paused", "Queued"):
# If the global setting is set, but the per-torrent isn't.. # If the global setting is set, but the per-torrent isn't...
# Just skip to the next torrent. # Just skip to the next torrent.
# This is so that a user can turn-off the stop at ratio option # This is so that a user can turn-off the stop at ratio option on a per-torrent basis
# on a per-torrent basis
if not torrent.options["stop_at_ratio"]: if not torrent.options["stop_at_ratio"]:
continue continue
if torrent.get_ratio() >= torrent.options["stop_ratio"] and torrent.is_finished: if torrent.get_ratio() >= torrent.options["stop_ratio"] and torrent.is_finished:
@ -313,46 +296,23 @@ class TorrentManager(component.Component):
current_user = component.get("RPCServer").get_session_user() current_user = component.get("RPCServer").get_session_user()
for torrent_id in torrent_ids[:]: for torrent_id in torrent_ids[:]:
torrent_status = self[torrent_id].get_status(["owner", "shared"]) torrent_status = self[torrent_id].get_status(["owner", "shared"])
if torrent_status["owner"] != current_user and torrent_status["shared"] == False: if torrent_status["owner"] != current_user and not torrent_status["shared"]:
torrent_ids.pop(torrent_ids.index(torrent_id)) torrent_ids.pop(torrent_ids.index(torrent_id))
return torrent_ids return torrent_ids
def get_torrent_info_from_file(self, filepath): def get_torrent_info_from_file(self, filepath):
"""Returns a torrent_info for the file specified or None""" """Returns a torrent_info for the file specified or None"""
torrent_info = None
# Get the torrent data from the torrent file # Get the torrent data from the torrent file
try:
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
log.debug("Attempting to create torrent_info from %s", filepath) log.debug("Attempting to extract torrent_info from %s", filepath)
_file = open(filepath, "rb") try:
with open(filepath, "rb") as _file:
torrent_info = lt.torrent_info(lt.bdecode(_file.read())) torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
_file.close() except (IOError, RuntimeError) as ex:
except (IOError, RuntimeError), e: log.warning("Unable to open torrent file %s: %s", filepath, ex)
log.warning("Unable to open %s: %s", filepath, e) else:
return torrent_info return torrent_info
def legacy_get_resume_data_from_file(self, torrent_id):
"""Returns an entry with the resume data or None"""
fastresume = ""
try:
_file = open(os.path.join(self.state_dir, torrent_id + ".fastresume"), "rb")
fastresume = _file.read()
_file.close()
except IOError, e:
log.debug("Unable to load .fastresume: %s", e)
return str(fastresume)
def legacy_delete_resume_data(self, torrent_id):
"""Deletes the .fastresume file"""
path = os.path.join(self.state_dir, torrent_id + ".fastresume")
log.debug("Deleting fastresume file: %s", path)
try:
os.remove(path)
except Exception, e:
log.warning("Unable to delete the fastresume file: %s", e)
def add(self, torrent_info=None, state=None, options=None, save_state=True, def add(self, torrent_info=None, state=None, options=None, save_state=True,
filedump=None, filename=None, magnet=None, resume_data=None, owner=None): filedump=None, filename=None, magnet=None, resume_data=None, owner=None):
"""Add a torrent to the manager and returns it's torrent_id""" """Add a torrent to the manager and returns it's torrent_id"""
@ -365,9 +325,9 @@ class TorrentManager(component.Component):
if filedump is not None: if filedump is not None:
try: try:
torrent_info = lt.torrent_info(lt.bdecode(filedump)) torrent_info = lt.torrent_info(lt.bdecode(filedump))
except Exception, e: except RuntimeError as ex:
log.error("Unable to decode torrent file!: %s", e) log.error("Unable to decode torrent file!: %s", ex)
# XXX: Probably should raise an exception here.. # XXX: Probably should raise an exception here...
return return
if torrent_info is None and state: if torrent_info is None and state:
@ -395,23 +355,16 @@ class TorrentManager(component.Component):
options["shared"] = state.shared options["shared"] = state.shared
owner = state.owner owner = state.owner
ti = self.get_torrent_info_from_file( torrent_info = self.get_torrent_info_from_file(
os.path.join(get_config_dir(), os.path.join(self.state_dir, state.torrent_id + ".torrent"))
"state", state.torrent_id + ".torrent")) if torrent_info:
if ti: add_torrent_params["ti"] = torrent_info
add_torrent_params["ti"] = ti
elif state.magnet: elif state.magnet:
magnet = state.magnet magnet = state.magnet
else: else:
log.error("Unable to add torrent!") log.error("Unable to add torrent!")
return return
# Handle legacy case with storing resume data in individual files
# for each torrent
if resume_data is None:
resume_data = self.legacy_get_resume_data_from_file(state.torrent_id)
self.legacy_delete_resume_data(state.torrent_id)
if resume_data: if resume_data:
add_torrent_params["resume_data"] = resume_data add_torrent_params["resume_data"] = resume_data
else: else:
@ -445,12 +398,12 @@ class TorrentManager(component.Component):
return return
# Check if options is None and load defaults # Check if options is None and load defaults
if options == None: if options is None:
options = TorrentOptions() options = TorrentOptions()
else: else:
o = TorrentOptions() _options = TorrentOptions()
o.update(options) _options.update(options)
options = o options = _options
# Check for renamed files and if so, rename them in the torrent_info # Check for renamed files and if so, rename them in the torrent_info
# before adding to the session. # before adding to the session.
@ -460,7 +413,7 @@ class TorrentManager(component.Component):
fname = unicode(fname, "utf-8") fname = unicode(fname, "utf-8")
except TypeError: except TypeError:
pass pass
fname = deluge.core.torrent.sanitize_filepath(fname) fname = sanitize_filepath(fname)
log.debug("renaming file index %s to %s", index, fname) log.debug("renaming file index %s to %s", index, fname)
try: try:
torrent_info.rename_file(index, fname) torrent_info.rename_file(index, fname)
@ -501,8 +454,8 @@ class TorrentManager(component.Component):
handle = lt.add_magnet_uri(self.session, utf8_encoded(magnet), add_torrent_params) handle = lt.add_magnet_uri(self.session, utf8_encoded(magnet), add_torrent_params)
else: else:
handle = self.session.add_torrent(add_torrent_params) handle = self.session.add_torrent(add_torrent_params)
except RuntimeError, e: except RuntimeError as ex:
log.warning("Error adding torrent: %s", e) log.warning("Error adding torrent: %s", ex)
if not handle or not handle.is_valid(): if not handle or not handle.is_valid():
log.debug("torrent handle is invalid!") log.debug("torrent handle is invalid!")
@ -534,23 +487,19 @@ class TorrentManager(component.Component):
# Write the .torrent file to the state directory # Write the .torrent file to the state directory
if filedump: if filedump:
try: try:
save_file = open(os.path.join(self.state_dir, torrent.torrent_id + ".torrent"), "wb") with open(os.path.join(self.state_dir, torrent.torrent_id + ".torrent"), "wb") as save_file:
save_file.write(filedump) save_file.write(filedump)
save_file.close() except IOError as ex:
except IOError, e: log.warning("Unable to save torrent file: %s", ex)
log.warning("Unable to save torrent file: %s", e)
# If the user has requested a copy of the torrent be saved elsewhere # If the user has requested a copy of the torrent be saved elsewhere
# we need to do that. # we need to do that.
if self.config["copy_torrent_file"] and filename is not None: if self.config["copy_torrent_file"] and filename is not None:
try: try:
save_file = open( with open(os.path.join(self.config["torrentfiles_location"], filename), "wb") as save_file:
os.path.join(self.config["torrentfiles_location"], filename),
"wb")
save_file.write(filedump) save_file.write(filedump)
save_file.close() except IOError as ex:
except IOError, e: log.warning("Unable to save torrent file: %s", ex)
log.warning("Unable to save torrent file: %s", e)
if save_state: if save_state:
# Save the session state # Save the session state
@ -564,26 +513,21 @@ class TorrentManager(component.Component):
if log.isEnabledFor(logging.INFO): if log.isEnabledFor(logging.INFO):
name_and_owner = torrent.get_status(["name", "owner"]) name_and_owner = torrent.get_status(["name", "owner"])
log.info("Torrent %s from user \"%s\" %s" % ( log.info("Torrent %s from user \"%s\" %s",
name_and_owner["name"], name_and_owner["name"],
name_and_owner["owner"], name_and_owner["owner"],
from_state and "loaded" or "added") from_state and "loaded" or "added")
)
return torrent.torrent_id return torrent.torrent_id
def load_torrent(self, torrent_id): def load_torrent(self, torrent_id):
"""Load a torrent file from state and return it's torrent info""" """Load a torrent file from state and return it's torrent info"""
filedump = None
# Get the torrent data from the torrent file
try: try:
log.debug("Attempting to open %s for add.", torrent_id) log.debug("Attempting to open %s for add.", torrent_id)
_file = open(os.path.join(self.state_dir, torrent_id + ".torrent"), "rb") with open(os.path.join(self.state_dir, torrent_id + ".torrent"), "rb") as _file:
filedump = lt.bdecode(_file.read()) filedump = lt.bdecode(_file.read())
_file.close() except (IOError, RuntimeError) as ex:
except (IOError, RuntimeError), e: log.warning("Unable to open torrent file %s: %s", torrent_id, ex)
log.warning("Unable to open %s: %s", torrent_id, e) else:
return False
return filedump return filedump
def remove(self, torrent_id, remove_data=False): def remove(self, torrent_id, remove_data=False):
@ -610,10 +554,9 @@ class TorrentManager(component.Component):
component.get("EventManager").emit(PreTorrentRemovedEvent(torrent_id)) component.get("EventManager").emit(PreTorrentRemovedEvent(torrent_id))
try: try:
self.session.remove_torrent(self.torrents[torrent_id].handle, self.session.remove_torrent(self.torrents[torrent_id].handle, 1 if remove_data else 0)
1 if remove_data else 0) except (RuntimeError, KeyError) as ex:
except (RuntimeError, KeyError), e: log.warning("Error removing torrent: %s", ex)
log.warning("Error removing torrent: %s", e)
return False return False
# Remove fastresume data if it is exists # Remove fastresume data if it is exists
@ -624,18 +567,13 @@ class TorrentManager(component.Component):
# Remove the torrent file from the user specified directory # Remove the torrent file from the user specified directory
filename = self.torrents[torrent_id].filename filename = self.torrents[torrent_id].filename
if self.config["copy_torrent_file"] \ if self.config["copy_torrent_file"] and self.config["del_copy_torrent_file"] and filename:
and self.config["del_copy_torrent_file"] \ users_torrent_file = os.path.join(self.config["torrentfiles_location"], filename)
and filename: log.info("Delete user's torrent file: %s", users_torrent_file)
try: try:
users_torrent_file = os.path.join(
self.config["torrentfiles_location"],
filename)
log.info("Delete user's torrent file: %s",
users_torrent_file)
os.remove(users_torrent_file) os.remove(users_torrent_file)
except Exception, e: except OSError as ex:
log.warning("Unable to remove copy torrent file: %s", e) log.warning("Unable to remove copy torrent file: %s", ex)
# Stop the looping call # Stop the looping call
self.torrents[torrent_id].prev_status_cleanup_loop.stop() self.torrents[torrent_id].prev_status_cleanup_loop.stop()
@ -673,7 +611,7 @@ class TorrentManager(component.Component):
try: try:
with open(_filepath, "rb") as _file: with open(_filepath, "rb") as _file:
state = cPickle.load(_file) state = cPickle.load(_file)
except (IOError, EOFError, cPickle.UnpicklingError), ex: except (IOError, EOFError, cPickle.UnpicklingError) as ex:
log.warning("Unable to load %s: %s", _filepath, ex) log.warning("Unable to load %s: %s", _filepath, ex)
state = None state = None
else: else:
@ -683,17 +621,6 @@ class TorrentManager(component.Component):
if state is None: if state is None:
state = TorrentManagerState() state = TorrentManagerState()
# Try to use an old state
try:
if len(state.torrents) > 0:
state_tmp = TorrentState()
if dir(state.torrents[0]) != dir(state_tmp):
for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))):
for s in state.torrents:
setattr(s, attr, getattr(state_tmp, attr, None))
except Exception, e:
log.exception("Unable to update state file to a compatible version: %s", e)
# Reorder the state.torrents list to add torrents in the correct queue # Reorder the state.torrents list to add torrents in the correct queue
# order. # order.
state.torrents.sort(key=operator.attrgetter("queue"), reverse=self.config["queue_new_to_top"]) state.torrents.sort(key=operator.attrgetter("queue"), reverse=self.config["queue_new_to_top"])
@ -707,8 +634,8 @@ class TorrentManager(component.Component):
try: try:
self.add(state=torrent_state, save_state=False, self.add(state=torrent_state, save_state=False,
resume_data=resume_data.get(torrent_state.torrent_id)) resume_data=resume_data.get(torrent_state.torrent_id))
except AttributeError, e: except AttributeError as ex:
log.error("Torrent state file is either corrupt or incompatible! %s", e) log.error("Torrent state file is either corrupt or incompatible! %s", ex)
import traceback import traceback
traceback.print_exc() traceback.print_exc()
break break
@ -729,8 +656,7 @@ class TorrentManager(component.Component):
torrent_status = torrent.get_status([ torrent_status = torrent.get_status([
"total_uploaded", "total_uploaded",
"last_seen_complete" "last_seen_complete"
], update=True ], update=True)
)
torrent_state = TorrentState( torrent_state = TorrentState(
torrent.torrent_id, torrent.torrent_id,
@ -802,7 +728,7 @@ class TorrentManager(component.Component):
deferreds = [] deferreds = []
def on_torrent_resume_save(result, torrent_id): def on_torrent_resume_save(dummy_result, torrent_id):
self.waiting_on_resume_data.pop(torrent_id, None) self.waiting_on_resume_data.pop(torrent_id, None)
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
@ -816,11 +742,16 @@ class TorrentManager(component.Component):
def on_all_resume_data_finished(result): def on_all_resume_data_finished(result):
if result: if result:
if self.save_resume_data_file(): if self.save_resume_data_file():
# Return True for the remove_temp_file() callback in stop()
return True return True
return DeferredList(deferreds).addBoth(on_all_resume_data_finished) return DeferredList(deferreds).addBoth(on_all_resume_data_finished)
def load_resume_data_file(self): def load_resume_data_file(self):
"""Loads the resume data from file for all the torrents
:returns: resume_data
:rtype: dict
"""
filename = "torrents.fastresume" filename = "torrents.fastresume"
filepath = os.path.join(self.state_dir, filename) filepath = os.path.join(self.state_dir, filename)
filepath_bak = filepath + ".bak" filepath_bak = filepath + ".bak"
@ -831,7 +762,7 @@ class TorrentManager(component.Component):
try: try:
with open(_filepath, "rb") as _file: with open(_filepath, "rb") as _file:
resume_data = lt.bdecode(_file.read()) resume_data = lt.bdecode(_file.read())
except (IOError, EOFError, RuntimeError), ex: except (IOError, EOFError, RuntimeError) as ex:
log.warning("Unable to load %s: %s", _filepath, ex) log.warning("Unable to load %s: %s", _filepath, ex)
resume_data = None resume_data = None
else: else:
@ -911,28 +842,29 @@ class TorrentManager(component.Component):
def on_set_max_connections_per_torrent(self, key, value): def on_set_max_connections_per_torrent(self, key, value):
"""Sets the per-torrent connection limit""" """Sets the per-torrent connection limit"""
log.debug("max_connections_per_torrent set to %s..", value) log.debug("max_connections_per_torrent set to %s...", value)
for key in self.torrents.keys(): for key in self.torrents.keys():
self.torrents[key].set_max_connections(value) self.torrents[key].set_max_connections(value)
def on_set_max_upload_slots_per_torrent(self, key, value): def on_set_max_upload_slots_per_torrent(self, key, value):
"""Sets the per-torrent upload slot limit""" """Sets the per-torrent upload slot limit"""
log.debug("max_upload_slots_per_torrent set to %s..", value) log.debug("max_upload_slots_per_torrent set to %s...", value)
for key in self.torrents.keys(): for key in self.torrents.keys():
self.torrents[key].set_max_upload_slots(value) self.torrents[key].set_max_upload_slots(value)
def on_set_max_upload_speed_per_torrent(self, key, value): def on_set_max_upload_speed_per_torrent(self, key, value):
log.debug("max_upload_speed_per_torrent set to %s..", value) log.debug("max_upload_speed_per_torrent set to %s...", value)
for key in self.torrents.keys(): for key in self.torrents.keys():
self.torrents[key].set_max_upload_speed(value) self.torrents[key].set_max_upload_speed(value)
def on_set_max_download_speed_per_torrent(self, key, value): def on_set_max_download_speed_per_torrent(self, key, value):
log.debug("max_download_speed_per_torrent set to %s..", value) log.debug("max_download_speed_per_torrent set to %s...", value)
for key in self.torrents.keys(): for key in self.torrents.keys():
self.torrents[key].set_max_download_speed(value) self.torrents[key].set_max_download_speed(value)
## Alert handlers ## ## Alert handlers ##
def on_alert_torrent_finished(self, alert): def on_alert_torrent_finished(self, alert):
"""Alert handler for libtorrent torrent_finished_alert"""
log.debug("on_alert_torrent_finished") log.debug("on_alert_torrent_finished")
try: try:
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
@ -944,6 +876,7 @@ class TorrentManager(component.Component):
# If total_download is 0, do not move, it's likely the torrent wasn't downloaded, but just added. # If total_download is 0, do not move, it's likely the torrent wasn't downloaded, but just added.
total_download = torrent.get_status(["total_payload_download"])["total_payload_download"] total_download = torrent.get_status(["total_payload_download"])["total_payload_download"]
if log.isEnabledFor(logging.DEBUG):
log.debug("Torrent settings: is_finished: %s, total_download: %s, move_completed: %s, move_path: %s", log.debug("Torrent settings: is_finished: %s, total_download: %s, move_completed: %s, move_path: %s",
torrent.is_finished, total_download, torrent.options["move_completed"], torrent.is_finished, total_download, torrent.options["move_completed"],
torrent.options["move_completed_path"]) torrent.options["move_completed_path"])
@ -956,6 +889,7 @@ class TorrentManager(component.Component):
torrent.update_state() torrent.update_state()
if not torrent.is_finished and total_download: if not torrent.is_finished and total_download:
component.get("EventManager").emit(TorrentFinishedEvent(torrent_id)) component.get("EventManager").emit(TorrentFinishedEvent(torrent_id))
torrent.is_finished = True torrent.is_finished = True
# Torrent is no longer part of the queue # Torrent is no longer part of the queue
@ -963,6 +897,7 @@ class TorrentManager(component.Component):
self.queued_torrents.remove(torrent_id) self.queued_torrents.remove(torrent_id)
except KeyError: except KeyError:
# Sometimes libtorrent fires a TorrentFinishedEvent twice # Sometimes libtorrent fires a TorrentFinishedEvent twice
if log.isEnabledFor(logging.DEBUG):
log.debug("%s isn't in queued torrents set?", torrent_id) log.debug("%s isn't in queued torrents set?", torrent_id)
# Only save resume data if it was actually downloaded something. Helps # Only save resume data if it was actually downloaded something. Helps
@ -974,12 +909,13 @@ class TorrentManager(component.Component):
self.save_resume_data((torrent_id, )) self.save_resume_data((torrent_id, ))
def on_alert_torrent_paused(self, alert): def on_alert_torrent_paused(self, alert):
"""Alert handler for libtorrent torrent_paused_alert"""
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_torrent_paused") log.debug("on_alert_torrent_paused")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())]
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
except: torrent = self.torrents[torrent_id]
except (RuntimeError, KeyError):
return return
# Set the torrent state # Set the torrent state
old_state = torrent.state old_state = torrent.state
@ -992,11 +928,12 @@ class TorrentManager(component.Component):
self.save_resume_data((torrent_id,)) self.save_resume_data((torrent_id,))
def on_alert_torrent_checked(self, alert): def on_alert_torrent_checked(self, alert):
"""Alert handler for libtorrent torrent_checked_alert"""
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_torrent_checked") log.debug("on_alert_torrent_checked")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
except: except RuntimeError:
return return
# Check to see if we're forcing a recheck and set it back to paused # Check to see if we're forcing a recheck and set it back to paused
@ -1010,11 +947,12 @@ class TorrentManager(component.Component):
torrent.update_state() torrent.update_state()
def on_alert_tracker_reply(self, alert): def on_alert_tracker_reply(self, alert):
"""Alert handler for libtorrent tracker_reply_alert"""
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_tracker_reply: %s", decode_string(alert.message())) log.debug("on_alert_tracker_reply: %s", decode_string(alert.message()))
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
except: except RuntimeError:
return return
# Set the tracker status for the torrent # Set the tracker status for the torrent
@ -1027,50 +965,55 @@ class TorrentManager(component.Component):
torrent.scrape_tracker() torrent.scrape_tracker()
def on_alert_tracker_announce(self, alert): def on_alert_tracker_announce(self, alert):
"""Alert handler for libtorrent tracker_announce_alert"""
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_tracker_announce") log.debug("on_alert_tracker_announce")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
except: except RuntimeError:
return return
# Set the tracker status for the torrent # Set the tracker status for the torrent
torrent.set_tracker_status(_("Announce Sent")) torrent.set_tracker_status(_("Announce Sent"))
def on_alert_tracker_warning(self, alert): def on_alert_tracker_warning(self, alert):
"""Alert handler for libtorrent tracker_warning_alert"""
log.debug("on_alert_tracker_warning") log.debug("on_alert_tracker_warning")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
except: except RuntimeError:
return return
tracker_status = '%s: %s' % (_("Warning"), decode_string(alert.message())) tracker_status = '%s: %s' % (_("Warning"), decode_string(alert.message()))
# Set the tracker status for the torrent # Set the tracker status for the torrent
torrent.set_tracker_status(tracker_status) torrent.set_tracker_status(tracker_status)
def on_alert_tracker_error(self, alert): def on_alert_tracker_error(self, alert):
"""Alert handler for libtorrent tracker_error_alert"""
log.debug("on_alert_tracker_error") log.debug("on_alert_tracker_error")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
except: except RuntimeError:
return return
tracker_status = "%s: %s" % (_("Error"), decode_string(alert.msg)) tracker_status = "%s: %s" % (_("Error"), decode_string(alert.msg))
torrent.set_tracker_status(tracker_status) torrent.set_tracker_status(tracker_status)
def on_alert_storage_moved(self, alert): def on_alert_storage_moved(self, alert):
"""Alert handler for libtorrent storage_moved_alert"""
log.debug("on_alert_storage_moved") log.debug("on_alert_storage_moved")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
except: except RuntimeError:
return return
torrent.set_save_path(os.path.normpath(alert.handle.save_path())) torrent.set_save_path(os.path.normpath(alert.handle.save_path()))
torrent.set_move_completed(False) torrent.set_move_completed(False)
def on_alert_torrent_resumed(self, alert): def on_alert_torrent_resumed(self, alert):
"""Alert handler for libtorrent torrent_resumed_alert"""
log.debug("on_alert_torrent_resumed") log.debug("on_alert_torrent_resumed")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())]
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
except: torrent = self.torrents[torrent_id]
except (RuntimeError, KeyError):
return return
old_state = torrent.state old_state = torrent.state
torrent.update_state() torrent.update_state()
@ -1080,12 +1023,15 @@ class TorrentManager(component.Component):
component.get("EventManager").emit(TorrentResumedEvent(torrent_id)) component.get("EventManager").emit(TorrentResumedEvent(torrent_id))
def on_alert_state_changed(self, alert): def on_alert_state_changed(self, alert):
"""Alert handler for libtorrent state_changed_alert
Emits a TorrentStateChangedEvent if state has changed
"""
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_state_changed") log.debug("on_alert_state_changed")
try: try:
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
torrent = self.torrents[torrent_id] torrent = self.torrents[torrent_id]
except: except (RuntimeError, KeyError):
return return
old_state = torrent.state old_state = torrent.state
@ -1101,9 +1047,13 @@ class TorrentManager(component.Component):
component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state)) component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state))
def on_alert_save_resume_data(self, alert): def on_alert_save_resume_data(self, alert):
"""Alert handler for libtorrent save_resume_data_alert"""
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_save_resume_data") log.debug("on_alert_save_resume_data")
try:
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
except RuntimeError:
return
if torrent_id in self.torrents: if torrent_id in self.torrents:
# Libtorrent in add_torrent() expects resume_data to be bencoded # Libtorrent in add_torrent() expects resume_data to be bencoded
@ -1113,19 +1063,26 @@ class TorrentManager(component.Component):
self.waiting_on_resume_data[torrent_id].callback(None) self.waiting_on_resume_data[torrent_id].callback(None)
def on_alert_save_resume_data_failed(self, alert): def on_alert_save_resume_data_failed(self, alert):
"""Alert handler for libtorrent save_resume_data_failed_alert"""
log.debug("on_alert_save_resume_data_failed: %s", decode_string(alert.message())) log.debug("on_alert_save_resume_data_failed: %s", decode_string(alert.message()))
try:
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
except RuntimeError:
return
if torrent_id in self.waiting_on_resume_data: if torrent_id in self.waiting_on_resume_data:
self.waiting_on_resume_data[torrent_id].errback(Exception(decode_string(alert.message()))) self.waiting_on_resume_data[torrent_id].errback(Exception(decode_string(alert.message())))
def on_alert_file_renamed(self, alert): def on_alert_file_renamed(self, alert):
"""Alert handler for libtorrent file_renamed_alert
Emits a TorrentFileCompletedEvent for renamed files
"""
log.debug("on_alert_file_renamed") log.debug("on_alert_file_renamed")
log.debug("index: %s name: %s", alert.index, decode_string(alert.name)) log.debug("index: %s name: %s", alert.index, decode_string(alert.name))
try: try:
torrent = self.torrents[str(alert.handle.info_hash())]
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
except: torrent = self.torrents[torrent_id]
except (RuntimeError, KeyError):
return return
# We need to see if this file index is in a waiting_on_folder dict # We need to see if this file index is in a waiting_on_folder dict
@ -1139,30 +1096,53 @@ class TorrentManager(component.Component):
self.save_resume_data((torrent_id,)) self.save_resume_data((torrent_id,))
def on_alert_metadata_received(self, alert): def on_alert_metadata_received(self, alert):
"""Alert handler for libtorrent metadata_received_alert"""
log.debug("on_alert_metadata_received") log.debug("on_alert_metadata_received")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
except: except RuntimeError:
return return
torrent.on_metadata_received() torrent.on_metadata_received()
def on_alert_file_error(self, alert): def on_alert_file_error(self, alert):
"""Alert handler for libtorrent file_error_alert"""
log.debug("on_alert_file_error: %s", decode_string(alert.message())) log.debug("on_alert_file_error: %s", decode_string(alert.message()))
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
except: except RuntimeError:
return return
torrent.update_state() torrent.update_state()
def on_alert_file_completed(self, alert): def on_alert_file_completed(self, alert):
"""Alert handler for libtorrent file_completed_alert
Emits a TorrentFileCompletedEvent when an individual file completes downloading
"""
log.debug("file_completed_alert: %s", decode_string(alert.message())) log.debug("file_completed_alert: %s", decode_string(alert.message()))
try: try:
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
except: except RuntimeError:
return return
component.get("EventManager").emit( component.get("EventManager").emit(
TorrentFileCompletedEvent(torrent_id, alert.index)) TorrentFileCompletedEvent(torrent_id, alert.index))
def on_alert_state_update(self, alert):
"""Alert handler for libtorrent state_update_alert
Result of a session.post_torrent_updates() call and contains the torrent status
of all torrents that changed since last time this was posted.
"""
log.debug("on_status_notification: %s", alert.message())
self.last_state_update_alert_ts = time.time()
for t_status in alert.status:
try:
torrent_id = str(t_status.info_hash)
except RuntimeError:
continue
if torrent_id in self.torrents:
self.torrents[torrent_id].update_status(t_status)
self.handle_torrents_status_callback(self.torrents_status_requests.pop())
def separate_keys(self, keys, torrent_ids): def separate_keys(self, keys, torrent_ids):
"""Separates the input keys into keys for the Torrent class """Separates the input keys into keys for the Torrent class
and keys for plugins. and keys for plugins.
@ -1176,17 +1156,6 @@ class TorrentManager(component.Component):
return torrent_keys, leftover_keys return torrent_keys, leftover_keys
return [], [] return [], []
def on_alert_state_update(self, alert):
log.debug("on_status_notification: %s", alert.message())
self.last_state_update_alert_ts = time.time()
for s in alert.status:
torrent_id = str(s.info_hash)
if torrent_id in self.torrents:
self.torrents[torrent_id].update_status(s)
self.handle_torrents_status_callback(self.torrents_status_requests.pop())
def handle_torrents_status_callback(self, status_request): def handle_torrents_status_callback(self, status_request):
""" """
Builds the status dictionary with the values from the Torrent. Builds the status dictionary with the values from the Torrent.