mirror of
https://github.com/codex-storage/deluge.git
synced 2025-01-12 04:24:27 +00:00
Merge branch 'master' of deluge-torrent.org:deluge
This commit is contained in:
commit
cc943cea4a
@ -51,6 +51,7 @@
|
||||
* #2201: Fix error in authmanager if auth file has extra newlines
|
||||
* #2109: Fix the Proxy settings not being cleared by setting None
|
||||
* #2110: Fix accepting magnet uris with xt param anywhere within them
|
||||
* #2204: Fix daemon shutdown hang
|
||||
|
||||
==== GtkUI ====
|
||||
* Add move completed option to add torrent dialog
|
||||
|
@ -45,9 +45,9 @@ supports.
|
||||
|
||||
"""
|
||||
|
||||
REQUIRED_VERSION = "0.14.9.0"
|
||||
REQUIRED_VERSION = "0.16.1.0"
|
||||
|
||||
def check_version(LT):
|
||||
def check_version(lt):
|
||||
from deluge.common import VersionSplit
|
||||
if VersionSplit(lt.version) < VersionSplit(REQUIRED_VERSION):
|
||||
raise ImportError("This version of Deluge requires libtorrent >=%s!" % REQUIRED_VERSION)
|
||||
|
@ -34,6 +34,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from twisted.internet.defer import maybeDeferred, succeed, DeferredList, fail
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
@ -225,6 +226,8 @@ class ComponentRegistry(object):
|
||||
"""
|
||||
def __init__(self):
|
||||
self.components = {}
|
||||
# Stores all of the components that are dependent on a particular component
|
||||
self.dependents = defaultdict(list)
|
||||
|
||||
def register(self, obj):
|
||||
"""
|
||||
@ -243,6 +246,9 @@ class ComponentRegistry(object):
|
||||
"Component already registered with name %s" % name)
|
||||
|
||||
self.components[obj._component_name] = obj
|
||||
if obj._component_depend:
|
||||
for depend in obj._component_depend:
|
||||
self.dependents[depend].append(name)
|
||||
|
||||
def deregister(self, obj):
|
||||
"""
|
||||
@ -317,10 +323,22 @@ class ComponentRegistry(object):
|
||||
elif isinstance(names, str):
|
||||
names = [names]
|
||||
|
||||
def on_dependents_stopped(result, name):
|
||||
return self.components[name]._component_stop()
|
||||
|
||||
stopped_in_deferred = set()
|
||||
deferreds = []
|
||||
|
||||
for name in names:
|
||||
if name in stopped_in_deferred:
|
||||
continue
|
||||
if name in self.components:
|
||||
if name in self.dependents:
|
||||
# If other components depend on this component, stop them first
|
||||
d = self.stop(self.dependents[name]).addCallback(on_dependents_stopped, name)
|
||||
deferreds.append(d)
|
||||
stopped_in_deferred.update(self.dependents[name])
|
||||
else:
|
||||
deferreds.append(self.components[name]._component_stop())
|
||||
|
||||
return DeferredList(deferreds)
|
||||
@ -360,7 +378,7 @@ class ComponentRegistry(object):
|
||||
:param names: a list of Components to resume
|
||||
:type names: list
|
||||
|
||||
:returns: a Deferred object that will fire once all Components have been sucessfully resumed
|
||||
:returns: a Deferred object that will fire once all Components have been successfully resumed
|
||||
:rtype: twisted.internet.defer.Deferred
|
||||
|
||||
"""
|
||||
@ -384,16 +402,14 @@ class ComponentRegistry(object):
|
||||
be called when the program is exiting to ensure all Components have a
|
||||
chance to properly shutdown.
|
||||
|
||||
:returns: a Deferred object that will fire once all Components have been sucessfully resumed
|
||||
:returns: a Deferred object that will fire once all Components have been successfully shut down
|
||||
:rtype: twisted.internet.defer.Deferred
|
||||
|
||||
"""
|
||||
deferreds = []
|
||||
def on_stopped(result):
|
||||
return DeferredList(map(lambda c: c._component_shutdown(), self.components.values()))
|
||||
|
||||
for component in self.components.values():
|
||||
deferreds.append(component._component_shutdown())
|
||||
|
||||
return DeferredList(deferreds)
|
||||
return self.stop(self.components.keys()).addCallback(on_stopped)
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
|
@ -75,6 +75,7 @@ class AlertManager(component.Component):
|
||||
|
||||
def stop(self):
|
||||
for dc in self.delayed_calls:
|
||||
if dc.active():
|
||||
dc.cancel()
|
||||
self.delayed_calls = []
|
||||
|
||||
|
@ -94,6 +94,8 @@ class Core(component.Component):
|
||||
self.settings.user_agent = "Deluge/%(deluge_version)s Libtorrent/%(lt_version)s" % \
|
||||
{ 'deluge_version': deluge.common.get_version(),
|
||||
'lt_version': self.get_libtorrent_version().rpartition(".")[0] }
|
||||
# Increase the alert queue size so that alerts don't get lost
|
||||
self.settings.alert_queue_size = 10000
|
||||
|
||||
# Set session settings
|
||||
self.settings.send_redundant_have = True
|
||||
|
@ -37,9 +37,12 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
from urllib import unquote
|
||||
from urlparse import urlparse
|
||||
|
||||
from twisted.internet.defer import Deferred, DeferredList
|
||||
from twisted.internet.task import LoopingCall
|
||||
from deluge._libtorrent import lt
|
||||
|
||||
import deluge.common
|
||||
@ -116,7 +119,6 @@ class Torrent(object):
|
||||
# We use this to return dicts that only contain changes from the previous
|
||||
# {session_id: status_dict, ...}
|
||||
self.prev_status = {}
|
||||
from twisted.internet.task import LoopingCall
|
||||
self.prev_status_cleanup_loop = LoopingCall(self.cleanup_prev_status)
|
||||
self.prev_status_cleanup_loop.start(10)
|
||||
|
||||
@ -125,14 +127,10 @@ class Torrent(object):
|
||||
# Set the torrent_id for this torrent
|
||||
self.torrent_id = str(handle.info_hash())
|
||||
|
||||
# Let's us know if we're waiting on a lt alert
|
||||
self.waiting_on_resume_data = False
|
||||
|
||||
# Keep a list of file indexes we're waiting for file_rename alerts on
|
||||
# This also includes the old_folder and new_folder to know what signal to send
|
||||
# Keep a list of Deferreds for file indexes we're waiting for file_rename alerts on
|
||||
# This is so we can send one folder_renamed signal instead of multiple
|
||||
# file_renamed signals.
|
||||
# [(old_folder, new_folder, [*indexes]), ...]
|
||||
# [{index: Deferred, ...}, ...]
|
||||
self.waiting_on_folder_rename = []
|
||||
|
||||
# We store the filename just in case we need to make a copy of the torrentfile
|
||||
@ -177,13 +175,7 @@ class Torrent(object):
|
||||
# Tracker list
|
||||
self.trackers = []
|
||||
# Create a list of trackers
|
||||
for value in self.handle.trackers():
|
||||
if lt.version_minor < 15:
|
||||
tracker = {}
|
||||
tracker["url"] = value.url
|
||||
tracker["tier"] = value.tier
|
||||
else:
|
||||
tracker = value
|
||||
for tracker in self.handle.trackers():
|
||||
self.trackers.append(tracker)
|
||||
|
||||
# Various torrent options
|
||||
@ -919,7 +911,6 @@ class Torrent(object):
|
||||
"""Signals libtorrent to build resume data for this torrent, it gets
|
||||
returned in a libtorrent alert"""
|
||||
self.handle.save_resume_data()
|
||||
self.waiting_on_resume_data = True
|
||||
|
||||
def write_torrentfile(self):
|
||||
"""Writes the torrent file"""
|
||||
@ -999,8 +990,13 @@ class Torrent(object):
|
||||
self.handle.rename_file(index, filename.encode("utf-8"))
|
||||
|
||||
def rename_folder(self, folder, new_folder):
|
||||
"""Renames a folder within a torrent. This basically does a file rename
|
||||
on all of the folders children."""
|
||||
"""
|
||||
Renames a folder within a torrent. This basically does a file rename
|
||||
on all of the folders children.
|
||||
|
||||
:returns: A deferred which fires when the rename is complete
|
||||
:rtype: twisted.internet.defer.Deferred
|
||||
"""
|
||||
log.debug("attempting to rename folder: %s to %s", folder, new_folder)
|
||||
if len(new_folder) < 1:
|
||||
log.error("Attempting to rename a folder with an invalid folder name: %s", new_folder)
|
||||
@ -1008,13 +1004,57 @@ class Torrent(object):
|
||||
|
||||
new_folder = sanitize_filepath(new_folder, folder=True)
|
||||
|
||||
wait_on_folder = (folder, new_folder, [])
|
||||
def on_file_rename_complete(result, wait_dict, index):
|
||||
wait_dict.pop(index, None)
|
||||
|
||||
wait_on_folder = {}
|
||||
self.waiting_on_folder_rename.append(wait_on_folder)
|
||||
for f in self.get_files():
|
||||
if f["path"].startswith(folder):
|
||||
# Keep a list of filerenames we're waiting on
|
||||
wait_on_folder[2].append(f["index"])
|
||||
# Keep track of filerenames we're waiting on
|
||||
wait_on_folder[f["index"]] = Deferred().addBoth(on_file_rename_complete, wait_on_folder, f["index"])
|
||||
self.handle.rename_file(f["index"], f["path"].replace(folder, new_folder, 1).encode("utf-8"))
|
||||
self.waiting_on_folder_rename.append(wait_on_folder)
|
||||
|
||||
def on_folder_rename_complete(result, torrent, folder, new_folder):
|
||||
component.get("EventManager").emit(TorrentFolderRenamedEvent(torrent.torrent_id, folder, new_folder))
|
||||
# Empty folders are removed after libtorrent folder renames
|
||||
self.remove_empty_folders(folder)
|
||||
torrent.waiting_on_folder_rename = filter(None, torrent.waiting_on_folder_rename)
|
||||
component.get("TorrentManager").save_resume_data((self.torrent_id,))
|
||||
|
||||
d = DeferredList(wait_on_folder.values())
|
||||
d.addBoth(on_folder_rename_complete, self, folder, new_folder)
|
||||
return d
|
||||
|
||||
def remove_empty_folders(self, folder):
|
||||
"""
|
||||
Recursively removes folders but only if they are empty.
|
||||
Cleans up after libtorrent folder renames.
|
||||
|
||||
"""
|
||||
info = self.get_status(['save_path'])
|
||||
# Regex removes leading slashes that causes join function to ignore save_path
|
||||
folder_full_path = os.path.join(info['save_path'], re.sub("^/*", "", folder))
|
||||
folder_full_path = os.path.normpath(folder_full_path)
|
||||
|
||||
try:
|
||||
if not os.listdir(folder_full_path):
|
||||
os.removedirs(folder_full_path)
|
||||
log.debug("Removed Empty Folder %s", folder_full_path)
|
||||
else:
|
||||
for root, dirs, files in os.walk(folder_full_path, topdown=False):
|
||||
for name in dirs:
|
||||
try:
|
||||
os.removedirs(os.path.join(root, name))
|
||||
log.debug("Removed Empty Folder %s", os.path.join(root, name))
|
||||
except OSError as (errno, strerror):
|
||||
from errno import ENOTEMPTY
|
||||
if errno == ENOTEMPTY:
|
||||
# Error raised if folder is not empty
|
||||
log.debug("%s", strerror)
|
||||
|
||||
except OSError as (errno, strerror):
|
||||
log.debug("Cannot Remove Folder: %s (ErrNo %s)", strerror, errno)
|
||||
|
||||
def cleanup_prev_status(self):
|
||||
"""
|
||||
|
@ -38,13 +38,13 @@
|
||||
|
||||
import cPickle
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
import operator
|
||||
import logging
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
from twisted.internet.task import LoopingCall
|
||||
from twisted.internet.defer import Deferred, DeferredList
|
||||
|
||||
from deluge._libtorrent import lt
|
||||
|
||||
@ -133,7 +133,7 @@ class TorrentManager(component.Component):
|
||||
|
||||
def __init__(self):
|
||||
component.Component.__init__(self, "TorrentManager", interval=5,
|
||||
depend=["CorePluginManager"])
|
||||
depend=["CorePluginManager", "AlertManager"])
|
||||
log.debug("TorrentManager init..")
|
||||
# Set the libtorrent session
|
||||
self.session = component.get("Core").session
|
||||
@ -151,15 +151,11 @@ class TorrentManager(component.Component):
|
||||
self.last_seen_complete_loop = None
|
||||
self.queued_torrents = set()
|
||||
|
||||
# This is a list of torrent_id when we shutdown the torrentmanager.
|
||||
# We use this list to determine if all active torrents have been paused
|
||||
# and that their resume data has been written.
|
||||
self.shutdown_torrent_pause_list = []
|
||||
# This is a map of torrent_ids to Deferreds used to track needed resume data.
|
||||
# The Deferreds will be completed when resume data has been saved.
|
||||
self.waiting_on_resume_data = defaultdict(list)
|
||||
|
||||
# self.num_resume_data used to save resume_data in bulk
|
||||
self.num_resume_data = 0
|
||||
|
||||
# Keeps track of resume data that needs to be saved to disk
|
||||
# Keeps track of resume data
|
||||
self.resume_data = {}
|
||||
|
||||
# Register set functions
|
||||
@ -216,11 +212,14 @@ class TorrentManager(component.Component):
|
||||
# Try to load the state from file
|
||||
self.load_state()
|
||||
|
||||
# Save the state every 5 minutes
|
||||
# Save the state periodically
|
||||
self.save_state_timer = LoopingCall(self.save_state)
|
||||
self.save_state_timer.start(200, False)
|
||||
self.save_resume_data_timer = LoopingCall(self.save_resume_data)
|
||||
self.save_resume_data_timer.start(190)
|
||||
self.save_resume_data_timer.start(190, False)
|
||||
# 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)
|
||||
|
||||
if self.last_seen_complete_loop:
|
||||
self.last_seen_complete_loop.start(60)
|
||||
@ -233,45 +232,21 @@ class TorrentManager(component.Component):
|
||||
if self.save_resume_data_timer.running:
|
||||
self.save_resume_data_timer.stop()
|
||||
|
||||
if self.save_all_resume_data_timer.running:
|
||||
self.save_all_resume_data_timer.stop()
|
||||
|
||||
if self.last_seen_complete_loop:
|
||||
self.last_seen_complete_loop.stop()
|
||||
|
||||
# Save state on shutdown
|
||||
self.save_state()
|
||||
|
||||
# Make another list just to make sure all paused torrents will be
|
||||
# passed to self.save_resume_data(). With
|
||||
# self.shutdown_torrent_pause_list it is possible to have a case when
|
||||
# torrent_id is removed from it in self.on_alert_torrent_paused()
|
||||
# before we call self.save_resume_data() here.
|
||||
save_resume_data_list = []
|
||||
self.session.pause()
|
||||
for key in self.torrents:
|
||||
# Stop the status cleanup LoopingCall here
|
||||
self.torrents[key].prev_status_cleanup_loop.stop()
|
||||
if not self.torrents[key].handle.is_paused():
|
||||
# We set auto_managed false to prevent lt from resuming the torrent
|
||||
self.torrents[key].handle.auto_managed(False)
|
||||
self.torrents[key].handle.pause()
|
||||
self.shutdown_torrent_pause_list.append(key)
|
||||
save_resume_data_list.append(key)
|
||||
|
||||
self.save_resume_data(save_resume_data_list)
|
||||
|
||||
# We have to wait for all torrents to pause and write their resume data
|
||||
wait = True
|
||||
while wait:
|
||||
if self.shutdown_torrent_pause_list:
|
||||
wait = True
|
||||
else:
|
||||
wait = False
|
||||
for torrent in self.torrents.values():
|
||||
if torrent.waiting_on_resume_data:
|
||||
wait = True
|
||||
break
|
||||
|
||||
time.sleep(0.01)
|
||||
# Wait for all alerts
|
||||
self.alerts.handle_alerts(True)
|
||||
return self.save_resume_data(self.torrents.keys())
|
||||
|
||||
def update(self):
|
||||
for torrent_id, torrent in self.torrents.items():
|
||||
@ -613,9 +588,7 @@ class TorrentManager(component.Component):
|
||||
return False
|
||||
|
||||
# Remove fastresume data if it is exists
|
||||
resume_data = self.load_resume_data_file()
|
||||
resume_data.pop(torrent_id, None)
|
||||
self.save_resume_data_file(resume_data)
|
||||
self.resume_data.pop(torrent_id, None)
|
||||
|
||||
# Remove the .torrent file in the state
|
||||
self.torrents[torrent_id].delete_torrentfile()
|
||||
@ -777,17 +750,32 @@ class TorrentManager(component.Component):
|
||||
|
||||
def save_resume_data(self, torrent_ids=None):
|
||||
"""
|
||||
Saves resume data for list of torrent_ids or for all torrents if
|
||||
torrent_ids is None
|
||||
Saves resume data for list of torrent_ids or for all torrents
|
||||
needing resume data updated if torrent_ids is None
|
||||
|
||||
:returns: A Deferred whose callback will be invoked when save is complete
|
||||
:rtype: twisted.internet.defer.Deferred
|
||||
"""
|
||||
|
||||
if torrent_ids is None:
|
||||
torrent_ids = self.torrents.keys()
|
||||
torrent_ids = (t[0] for t in self.torrents.iteritems() if t[1].handle.need_save_resume_data())
|
||||
|
||||
deferreds = []
|
||||
|
||||
def on_torrent_resume_save(result, torrent_id):
|
||||
self.waiting_on_resume_data.pop(torrent_id, None)
|
||||
|
||||
for torrent_id in torrent_ids:
|
||||
d = Deferred().addBoth(on_torrent_resume_save, torrent_id)
|
||||
self.waiting_on_resume_data[torrent_id].append(d)
|
||||
deferreds.append(d)
|
||||
self.torrents[torrent_id].save_resume_data()
|
||||
|
||||
self.num_resume_data = len(torrent_ids)
|
||||
def on_all_resume_data_finished(result):
|
||||
if result:
|
||||
self.save_resume_data_file()
|
||||
|
||||
return DeferredList(deferreds).addBoth(on_all_resume_data_finished)
|
||||
|
||||
def load_resume_data_file(self):
|
||||
resume_data = {}
|
||||
@ -807,73 +795,22 @@ class TorrentManager(component.Component):
|
||||
|
||||
return resume_data
|
||||
|
||||
def save_resume_data_file(self, resume_data=None):
|
||||
def save_resume_data_file(self):
|
||||
"""
|
||||
Saves the resume data file with the contents of self.resume_data. If
|
||||
`resume_data` is None, then we grab the resume_data from the file on
|
||||
disk, else, we update `resume_data` with self.resume_data and save
|
||||
that to disk.
|
||||
|
||||
:param resume_data: the current resume_data, this will be loaded from disk if not provided
|
||||
:type resume_data: dict
|
||||
|
||||
Saves the resume data file with the contents of self.resume_data.
|
||||
"""
|
||||
# Check to see if we're waiting on more resume data
|
||||
if self.num_resume_data or not self.resume_data:
|
||||
return
|
||||
|
||||
path = os.path.join(get_config_dir(), "state", "torrents.fastresume")
|
||||
|
||||
# First step is to load the existing file and update the dictionary
|
||||
if resume_data is None:
|
||||
resume_data = self.load_resume_data_file()
|
||||
|
||||
resume_data.update(self.resume_data)
|
||||
self.resume_data = {}
|
||||
|
||||
try:
|
||||
log.debug("Saving fastresume file: %s", path)
|
||||
fastresume_file = open(path, "wb")
|
||||
fastresume_file.write(lt.bencode(resume_data))
|
||||
fastresume_file.write(lt.bencode(self.resume_data))
|
||||
fastresume_file.flush()
|
||||
os.fsync(fastresume_file.fileno())
|
||||
fastresume_file.close()
|
||||
except IOError:
|
||||
log.warning("Error trying to save fastresume file")
|
||||
|
||||
def remove_empty_folders(self, torrent_id, folder):
|
||||
"""
|
||||
Recursively removes folders but only if they are empty.
|
||||
Cleans up after libtorrent folder renames.
|
||||
|
||||
"""
|
||||
if torrent_id not in self.torrents:
|
||||
raise InvalidTorrentError("torrent_id is not in session")
|
||||
|
||||
info = self.torrents[torrent_id].get_status(['save_path'])
|
||||
# Regex removes leading slashes that causes join function to ignore save_path
|
||||
folder_full_path = os.path.join(info['save_path'], re.sub("^/*", "", folder))
|
||||
folder_full_path = os.path.normpath(folder_full_path)
|
||||
|
||||
try:
|
||||
if not os.listdir(folder_full_path):
|
||||
os.removedirs(folder_full_path)
|
||||
log.debug("Removed Empty Folder %s", folder_full_path)
|
||||
else:
|
||||
for root, dirs, files in os.walk(folder_full_path, topdown=False):
|
||||
for name in dirs:
|
||||
try:
|
||||
os.removedirs(os.path.join(root, name))
|
||||
log.debug("Removed Empty Folder %s", os.path.join(root, name))
|
||||
except OSError as (errno, strerror):
|
||||
from errno import ENOTEMPTY
|
||||
if errno == ENOTEMPTY:
|
||||
# Error raised if folder is not empty
|
||||
log.debug("%s", strerror)
|
||||
|
||||
except OSError as (errno, strerror):
|
||||
log.debug("Cannot Remove Folder: %s (ErrNo %s)", strerror, errno)
|
||||
|
||||
def get_queue_position(self, torrent_id):
|
||||
"""Get queue position of torrent"""
|
||||
return self.torrents[torrent_id].get_queue_position()
|
||||
@ -988,14 +925,9 @@ class TorrentManager(component.Component):
|
||||
if torrent.state != old_state:
|
||||
component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state))
|
||||
|
||||
# Don't save resume data for each torrent after self.stop() was called.
|
||||
# We save resume data in bulk in self.stop() in this case.
|
||||
if self.save_resume_data_timer.running:
|
||||
# Write the fastresume file
|
||||
self.save_resume_data((torrent_id, ))
|
||||
|
||||
if torrent_id in self.shutdown_torrent_pause_list:
|
||||
self.shutdown_torrent_pause_list.remove(torrent_id)
|
||||
# Write the fastresume file if we are not waiting on a bulk write
|
||||
if torrent_id not in self.waiting_on_resume_data:
|
||||
self.save_resume_data((torrent_id,))
|
||||
|
||||
def on_alert_torrent_checked(self, alert):
|
||||
log.debug("on_alert_torrent_checked")
|
||||
@ -1105,32 +1037,22 @@ class TorrentManager(component.Component):
|
||||
|
||||
def on_alert_save_resume_data(self, alert):
|
||||
log.debug("on_alert_save_resume_data")
|
||||
try:
|
||||
torrent_id = str(alert.handle.info_hash())
|
||||
torrent = self.torrents[torrent_id]
|
||||
except:
|
||||
return
|
||||
|
||||
if torrent_id in self.torrents:
|
||||
# Libtorrent in add_torrent() expects resume_data to be bencoded
|
||||
self.resume_data[torrent_id] = lt.bencode(alert.resume_data)
|
||||
self.num_resume_data -= 1
|
||||
|
||||
torrent.waiting_on_resume_data = False
|
||||
|
||||
self.save_resume_data_file()
|
||||
if torrent_id in self.waiting_on_resume_data:
|
||||
for d in self.waiting_on_resume_data[torrent_id]:
|
||||
d.callback(None)
|
||||
|
||||
def on_alert_save_resume_data_failed(self, alert):
|
||||
log.debug("on_alert_save_resume_data_failed: %s", alert.message())
|
||||
try:
|
||||
torrent = self.torrents[str(alert.handle.info_hash())]
|
||||
except:
|
||||
return
|
||||
|
||||
self.num_resume_data -= 1
|
||||
torrent.waiting_on_resume_data = False
|
||||
|
||||
self.save_resume_data_file()
|
||||
torrent_id = alert.handle.info_hash()
|
||||
|
||||
if torrent_id in self.waiting_on_resume_data:
|
||||
self.waiting_on_resume_data[torrent_id].errback(Exception(alert.message()))
|
||||
|
||||
def on_alert_file_renamed(self, alert):
|
||||
log.debug("on_alert_file_renamed")
|
||||
@ -1141,24 +1063,12 @@ class TorrentManager(component.Component):
|
||||
except:
|
||||
return
|
||||
|
||||
# We need to see if this file index is in a waiting_on_folder list
|
||||
folder_rename = False
|
||||
for i, wait_on_folder in enumerate(torrent.waiting_on_folder_rename):
|
||||
if alert.index in wait_on_folder[2]:
|
||||
folder_rename = True
|
||||
if len(wait_on_folder[2]) == 1:
|
||||
# This is the last alert we were waiting for, time to send signal
|
||||
component.get("EventManager").emit(TorrentFolderRenamedEvent(torrent_id, wait_on_folder[0], wait_on_folder[1]))
|
||||
# Empty folders are removed after libtorrent folder renames
|
||||
self.remove_empty_folders(torrent_id, wait_on_folder[0])
|
||||
del torrent.waiting_on_folder_rename[i]
|
||||
self.save_resume_data((torrent_id,))
|
||||
# We need to see if this file index is in a waiting_on_folder dict
|
||||
for wait_on_folder in torrent.waiting_on_folder_rename:
|
||||
if alert.index in wait_on_folder:
|
||||
wait_on_folder[alert.index].callback(None)
|
||||
break
|
||||
# This isn't the last file to be renamed in this folder, so just
|
||||
# remove the index and continue
|
||||
torrent.waiting_on_folder_rename[i][2].remove(alert.index)
|
||||
|
||||
if not folder_rename:
|
||||
else:
|
||||
# This is just a regular file rename so send the signal
|
||||
component.get("EventManager").emit(TorrentFileRenamedEvent(torrent_id, alert.index, alert.name))
|
||||
self.save_resume_data((torrent_id,))
|
||||
|
@ -825,14 +825,15 @@ class AddTorrentDialog(component.Component):
|
||||
|
||||
self.save_torrent_options(row)
|
||||
|
||||
# The options we want all the torrents to have
|
||||
options = self.options[model.get_value(row, 0)]
|
||||
# The options, except file renames, we want all the torrents to have
|
||||
options = self.options[model.get_value(row, 0)].copy()
|
||||
del options["mapped_files"]
|
||||
|
||||
# Set all the torrent options
|
||||
row = model.get_iter_first()
|
||||
while row != None:
|
||||
torrent_id = model.get_value(row, 0)
|
||||
self.options[torrent_id] = options
|
||||
self.options[torrent_id].update(options)
|
||||
row = model.iter_next(row)
|
||||
|
||||
def _on_button_revert_clicked(self, widget):
|
||||
@ -863,7 +864,7 @@ class AddTorrentDialog(component.Component):
|
||||
def _on_filename_edited(self, renderer, path, new_text):
|
||||
index = self.files_treestore[path][3]
|
||||
|
||||
new_text = new_text.strip(os.path.sep)
|
||||
new_text = new_text.strip(os.path.sep).strip()
|
||||
|
||||
# Return if the text hasn't changed
|
||||
if new_text == self.files_treestore[path][1]:
|
||||
@ -881,9 +882,14 @@ class AddTorrentDialog(component.Component):
|
||||
|
||||
if index > -1:
|
||||
# We're renaming a file! Yay! That's easy!
|
||||
if not new_text:
|
||||
return
|
||||
parent = self.files_treestore.iter_parent(itr)
|
||||
file_path = os.path.join(self.get_file_path(parent), new_text)
|
||||
|
||||
# Don't rename if filename exists
|
||||
for row in self.files_treestore[parent].iterchildren():
|
||||
if new_text == row[1]:
|
||||
return
|
||||
if os.path.sep in new_text:
|
||||
# There are folders in this path, so we need to create them
|
||||
# and then move the file iter to top
|
||||
|
@ -40,7 +40,7 @@ Deluge.add.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
||||
layout: 'fit',
|
||||
title: _('Files'),
|
||||
|
||||
autoScroll: true,
|
||||
autoScroll: false,
|
||||
animate: false,
|
||||
border: false,
|
||||
disabled: true,
|
||||
|
@ -78,7 +78,6 @@ Deluge.add.OptionsTab = Ext.extend(Ext.form.FormPanel, {
|
||||
border: false,
|
||||
autoHeight: true,
|
||||
defaultType: 'radio',
|
||||
width: 100
|
||||
});
|
||||
|
||||
this.optionsManager.bind('compact_allocation', fieldset.add({
|
||||
@ -86,6 +85,7 @@ Deluge.add.OptionsTab = Ext.extend(Ext.form.FormPanel, {
|
||||
columns: 1,
|
||||
vertical: true,
|
||||
labelSeparator: '',
|
||||
width: 80,
|
||||
items: [{
|
||||
name: 'compact_allocation',
|
||||
value: false,
|
||||
@ -107,31 +107,28 @@ Deluge.add.OptionsTab = Ext.extend(Ext.form.FormPanel, {
|
||||
title: _('Bandwidth'),
|
||||
border: false,
|
||||
autoHeight: true,
|
||||
labelWidth: 100,
|
||||
bodyStyle: 'margin-left: 7px',
|
||||
labelWidth: 105,
|
||||
width: 200,
|
||||
defaultType: 'spinnerfield'
|
||||
});
|
||||
this.optionsManager.bind('max_download_speed', fieldset.add({
|
||||
fieldLabel: _('Max Down Speed'),
|
||||
labelStyle: 'margin-left: 10px',
|
||||
name: 'max_download_speed',
|
||||
width: 60
|
||||
}));
|
||||
this.optionsManager.bind('max_upload_speed', fieldset.add({
|
||||
fieldLabel: _('Max Up Speed'),
|
||||
labelStyle: 'margin-left: 10px',
|
||||
name: 'max_upload_speed',
|
||||
width: 60
|
||||
}));
|
||||
this.optionsManager.bind('max_connections', fieldset.add({
|
||||
fieldLabel: _('Max Connections'),
|
||||
labelStyle: 'margin-left: 10px',
|
||||
name: 'max_connections',
|
||||
width: 60
|
||||
}));
|
||||
this.optionsManager.bind('max_upload_slots', fieldset.add({
|
||||
fieldLabel: _('Max Upload Slots'),
|
||||
labelStyle: 'margin-left: 10px',
|
||||
name: 'max_upload_slots',
|
||||
width: 60
|
||||
}));
|
||||
|
3
setup.py
3
setup.py
@ -152,11 +152,12 @@ else:
|
||||
for include in os.environ.get("INCLUDEDIR", "").split(":"):
|
||||
_include_dirs.append(include)
|
||||
|
||||
_library_dirs += [sysconfig.get_config_var("LIBDIR"), '/opt/local/lib']
|
||||
_library_dirs += [sysconfig.get_config_var("LIBDIR"), '/opt/local/lib', '/usr/local/lib']
|
||||
if osx_check():
|
||||
_include_dirs += [
|
||||
'/opt/local/include/boost-1_35',
|
||||
'/opt/local/include/boost-1_36',
|
||||
'/usr/local/include'
|
||||
'/sw/include/boost-1_35',
|
||||
'/sw/include/boost'
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user