Merge branch 'master' of deluge-torrent.org:deluge

This commit is contained in:
Andrew Resch 2012-12-08 12:57:13 -08:00
commit cc943cea4a
11 changed files with 172 additions and 198 deletions

View file

@ -51,6 +51,7 @@
* #2201: Fix error in authmanager if auth file has extra newlines * #2201: Fix error in authmanager if auth file has extra newlines
* #2109: Fix the Proxy settings not being cleared by setting None * #2109: Fix the Proxy settings not being cleared by setting None
* #2110: Fix accepting magnet uris with xt param anywhere within them * #2110: Fix accepting magnet uris with xt param anywhere within them
* #2204: Fix daemon shutdown hang
==== GtkUI ==== ==== GtkUI ====
* Add move completed option to add torrent dialog * Add move completed option to add torrent dialog

View file

@ -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 from deluge.common import VersionSplit
if VersionSplit(lt.version) < VersionSplit(REQUIRED_VERSION): if VersionSplit(lt.version) < VersionSplit(REQUIRED_VERSION):
raise ImportError("This version of Deluge requires libtorrent >=%s!" % REQUIRED_VERSION) raise ImportError("This version of Deluge requires libtorrent >=%s!" % REQUIRED_VERSION)

View file

@ -34,6 +34,7 @@
# #
import logging import logging
from collections import defaultdict
from twisted.internet.defer import maybeDeferred, succeed, DeferredList, fail from twisted.internet.defer import maybeDeferred, succeed, DeferredList, fail
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
@ -225,6 +226,8 @@ class ComponentRegistry(object):
""" """
def __init__(self): def __init__(self):
self.components = {} self.components = {}
# Stores all of the components that are dependent on a particular component
self.dependents = defaultdict(list)
def register(self, obj): def register(self, obj):
""" """
@ -243,6 +246,9 @@ class ComponentRegistry(object):
"Component already registered with name %s" % name) "Component already registered with name %s" % name)
self.components[obj._component_name] = obj 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): def deregister(self, obj):
""" """
@ -317,10 +323,22 @@ class ComponentRegistry(object):
elif isinstance(names, str): elif isinstance(names, str):
names = [names] names = [names]
def on_dependents_stopped(result, name):
return self.components[name]._component_stop()
stopped_in_deferred = set()
deferreds = [] deferreds = []
for name in names: for name in names:
if name in stopped_in_deferred:
continue
if name in self.components: 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()) deferreds.append(self.components[name]._component_stop())
return DeferredList(deferreds) return DeferredList(deferreds)
@ -360,7 +378,7 @@ class ComponentRegistry(object):
:param names: a list of Components to resume :param names: a list of Components to resume
:type names: list :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 :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 be called when the program is exiting to ensure all Components have a
chance to properly shutdown. 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 :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(): return self.stop(self.components.keys()).addCallback(on_stopped)
deferreds.append(component._component_shutdown())
return DeferredList(deferreds)
def update(self): def update(self):
""" """

View file

@ -75,6 +75,7 @@ class AlertManager(component.Component):
def stop(self): def stop(self):
for dc in self.delayed_calls: for dc in self.delayed_calls:
if dc.active():
dc.cancel() dc.cancel()
self.delayed_calls = [] self.delayed_calls = []

View file

@ -94,6 +94,8 @@ class Core(component.Component):
self.settings.user_agent = "Deluge/%(deluge_version)s Libtorrent/%(lt_version)s" % \ self.settings.user_agent = "Deluge/%(deluge_version)s Libtorrent/%(lt_version)s" % \
{ 'deluge_version': deluge.common.get_version(), { 'deluge_version': deluge.common.get_version(),
'lt_version': self.get_libtorrent_version().rpartition(".")[0] } '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 # Set session settings
self.settings.send_redundant_have = True self.settings.send_redundant_have = True

View file

@ -37,9 +37,12 @@
import os import os
import time import time
import logging import logging
import re
from urllib import unquote from urllib import unquote
from urlparse import urlparse from urlparse import urlparse
from twisted.internet.defer import Deferred, DeferredList
from twisted.internet.task import LoopingCall
from deluge._libtorrent import lt from deluge._libtorrent import lt
import deluge.common import deluge.common
@ -116,7 +119,6 @@ class Torrent(object):
# We use this to return dicts that only contain changes from the previous # We use this to return dicts that only contain changes from the previous
# {session_id: status_dict, ...} # {session_id: status_dict, ...}
self.prev_status = {} self.prev_status = {}
from twisted.internet.task import LoopingCall
self.prev_status_cleanup_loop = LoopingCall(self.cleanup_prev_status) self.prev_status_cleanup_loop = LoopingCall(self.cleanup_prev_status)
self.prev_status_cleanup_loop.start(10) self.prev_status_cleanup_loop.start(10)
@ -125,14 +127,10 @@ class Torrent(object):
# Set the torrent_id for this torrent # Set the torrent_id for this torrent
self.torrent_id = str(handle.info_hash()) self.torrent_id = str(handle.info_hash())
# Let's us know if we're waiting on a lt alert # Keep a list of Deferreds for file indexes we're waiting for file_rename alerts on
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
# This is so we can send one folder_renamed signal instead of multiple # This is so we can send one folder_renamed signal instead of multiple
# file_renamed signals. # file_renamed signals.
# [(old_folder, new_folder, [*indexes]), ...] # [{index: Deferred, ...}, ...]
self.waiting_on_folder_rename = [] self.waiting_on_folder_rename = []
# We store the filename just in case we need to make a copy of the torrentfile # 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 # Tracker list
self.trackers = [] self.trackers = []
# Create a list of trackers # Create a list of trackers
for value in self.handle.trackers(): for tracker in self.handle.trackers():
if lt.version_minor < 15:
tracker = {}
tracker["url"] = value.url
tracker["tier"] = value.tier
else:
tracker = value
self.trackers.append(tracker) self.trackers.append(tracker)
# Various torrent options # Various torrent options
@ -919,7 +911,6 @@ class Torrent(object):
"""Signals libtorrent to build resume data for this torrent, it gets """Signals libtorrent to build resume data for this torrent, it gets
returned in a libtorrent alert""" returned in a libtorrent alert"""
self.handle.save_resume_data() self.handle.save_resume_data()
self.waiting_on_resume_data = True
def write_torrentfile(self): def write_torrentfile(self):
"""Writes the torrent file""" """Writes the torrent file"""
@ -999,8 +990,13 @@ class Torrent(object):
self.handle.rename_file(index, filename.encode("utf-8")) self.handle.rename_file(index, filename.encode("utf-8"))
def rename_folder(self, folder, new_folder): 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) log.debug("attempting to rename folder: %s to %s", folder, new_folder)
if len(new_folder) < 1: if len(new_folder) < 1:
log.error("Attempting to rename a folder with an invalid folder name: %s", new_folder) 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) 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(): for f in self.get_files():
if f["path"].startswith(folder): if f["path"].startswith(folder):
# Keep a list of filerenames we're waiting on # Keep track of filerenames we're waiting on
wait_on_folder[2].append(f["index"]) 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.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): def cleanup_prev_status(self):
""" """

View file

@ -38,13 +38,13 @@
import cPickle import cPickle
import os import os
import time
import shutil import shutil
import operator import operator
import logging import logging
import re from collections import defaultdict
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
from twisted.internet.defer import Deferred, DeferredList
from deluge._libtorrent import lt from deluge._libtorrent import lt
@ -133,7 +133,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"]) 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
@ -151,15 +151,11 @@ class TorrentManager(component.Component):
self.last_seen_complete_loop = None self.last_seen_complete_loop = None
self.queued_torrents = set() self.queued_torrents = set()
# This is a list of torrent_id when we shutdown the torrentmanager. # This is a map of torrent_ids to Deferreds used to track needed resume data.
# We use this list to determine if all active torrents have been paused # The Deferreds will be completed when resume data has been saved.
# and that their resume data has been written. self.waiting_on_resume_data = defaultdict(list)
self.shutdown_torrent_pause_list = []
# self.num_resume_data used to save resume_data in bulk # Keeps track of resume data
self.num_resume_data = 0
# Keeps track of resume data that needs to be saved to disk
self.resume_data = {} self.resume_data = {}
# Register set functions # Register set functions
@ -216,11 +212,14 @@ class TorrentManager(component.Component):
# Try to load the state from file # Try to load the state from file
self.load_state() 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 = 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 = 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: if self.last_seen_complete_loop:
self.last_seen_complete_loop.start(60) self.last_seen_complete_loop.start(60)
@ -233,45 +232,21 @@ class TorrentManager(component.Component):
if self.save_resume_data_timer.running: if self.save_resume_data_timer.running:
self.save_resume_data_timer.stop() 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: if self.last_seen_complete_loop:
self.last_seen_complete_loop.stop() self.last_seen_complete_loop.stop()
# Save state on shutdown # Save state on shutdown
self.save_state() self.save_state()
# Make another list just to make sure all paused torrents will be self.session.pause()
# 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 = []
for key in self.torrents: for key in self.torrents:
# Stop the status cleanup LoopingCall here # Stop the status cleanup LoopingCall here
self.torrents[key].prev_status_cleanup_loop.stop() 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) return self.save_resume_data(self.torrents.keys())
# 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)
def update(self): def update(self):
for torrent_id, torrent in self.torrents.items(): for torrent_id, torrent in self.torrents.items():
@ -613,9 +588,7 @@ class TorrentManager(component.Component):
return False return False
# Remove fastresume data if it is exists # Remove fastresume data if it is exists
resume_data = self.load_resume_data_file() self.resume_data.pop(torrent_id, None)
resume_data.pop(torrent_id, None)
self.save_resume_data_file(resume_data)
# Remove the .torrent file in the state # Remove the .torrent file in the state
self.torrents[torrent_id].delete_torrentfile() self.torrents[torrent_id].delete_torrentfile()
@ -777,17 +750,32 @@ class TorrentManager(component.Component):
def save_resume_data(self, torrent_ids=None): def save_resume_data(self, torrent_ids=None):
""" """
Saves resume data for list of torrent_ids or for all torrents if Saves resume data for list of torrent_ids or for all torrents
torrent_ids is None 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: 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: 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.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): def load_resume_data_file(self):
resume_data = {} resume_data = {}
@ -807,73 +795,22 @@ class TorrentManager(component.Component):
return resume_data 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 Saves the resume data file with the contents of self.resume_data.
`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
""" """
# 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") 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: try:
log.debug("Saving fastresume file: %s", path) log.debug("Saving fastresume file: %s", path)
fastresume_file = open(path, "wb") fastresume_file = open(path, "wb")
fastresume_file.write(lt.bencode(resume_data)) fastresume_file.write(lt.bencode(self.resume_data))
fastresume_file.flush() fastresume_file.flush()
os.fsync(fastresume_file.fileno()) os.fsync(fastresume_file.fileno())
fastresume_file.close() fastresume_file.close()
except IOError: except IOError:
log.warning("Error trying to save fastresume file") 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): def get_queue_position(self, torrent_id):
"""Get queue position of torrent""" """Get queue position of torrent"""
return self.torrents[torrent_id].get_queue_position() return self.torrents[torrent_id].get_queue_position()
@ -988,14 +925,9 @@ class TorrentManager(component.Component):
if torrent.state != old_state: if torrent.state != old_state:
component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state)) component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state))
# Don't save resume data for each torrent after self.stop() was called. # Write the fastresume file if we are not waiting on a bulk write
# We save resume data in bulk in self.stop() in this case. if torrent_id not in self.waiting_on_resume_data:
if self.save_resume_data_timer.running: self.save_resume_data((torrent_id,))
# 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)
def on_alert_torrent_checked(self, alert): def on_alert_torrent_checked(self, alert):
log.debug("on_alert_torrent_checked") log.debug("on_alert_torrent_checked")
@ -1105,32 +1037,22 @@ class TorrentManager(component.Component):
def on_alert_save_resume_data(self, alert): def on_alert_save_resume_data(self, alert):
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())
torrent = self.torrents[torrent_id]
except:
return
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
self.resume_data[torrent_id] = lt.bencode(alert.resume_data) self.resume_data[torrent_id] = lt.bencode(alert.resume_data)
self.num_resume_data -= 1
torrent.waiting_on_resume_data = False if torrent_id in self.waiting_on_resume_data:
for d in self.waiting_on_resume_data[torrent_id]:
self.save_resume_data_file() d.callback(None)
def on_alert_save_resume_data_failed(self, alert): def on_alert_save_resume_data_failed(self, alert):
log.debug("on_alert_save_resume_data_failed: %s", alert.message()) log.debug("on_alert_save_resume_data_failed: %s", alert.message())
try: torrent_id = alert.handle.info_hash()
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()
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): def on_alert_file_renamed(self, alert):
log.debug("on_alert_file_renamed") log.debug("on_alert_file_renamed")
@ -1141,24 +1063,12 @@ class TorrentManager(component.Component):
except: except:
return return
# We need to see if this file index is in a waiting_on_folder list # We need to see if this file index is in a waiting_on_folder dict
folder_rename = False for wait_on_folder in torrent.waiting_on_folder_rename:
for i, wait_on_folder in enumerate(torrent.waiting_on_folder_rename): if alert.index in wait_on_folder:
if alert.index in wait_on_folder[2]: wait_on_folder[alert.index].callback(None)
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,))
break break
# This isn't the last file to be renamed in this folder, so just else:
# remove the index and continue
torrent.waiting_on_folder_rename[i][2].remove(alert.index)
if not folder_rename:
# This is just a regular file rename so send the signal # This is just a regular file rename so send the signal
component.get("EventManager").emit(TorrentFileRenamedEvent(torrent_id, alert.index, alert.name)) component.get("EventManager").emit(TorrentFileRenamedEvent(torrent_id, alert.index, alert.name))
self.save_resume_data((torrent_id,)) self.save_resume_data((torrent_id,))

View file

@ -825,14 +825,15 @@ class AddTorrentDialog(component.Component):
self.save_torrent_options(row) self.save_torrent_options(row)
# The options we want all the torrents to have # The options, except file renames, we want all the torrents to have
options = self.options[model.get_value(row, 0)] options = self.options[model.get_value(row, 0)].copy()
del options["mapped_files"]
# Set all the torrent options # Set all the torrent options
row = model.get_iter_first() row = model.get_iter_first()
while row != None: while row != None:
torrent_id = model.get_value(row, 0) torrent_id = model.get_value(row, 0)
self.options[torrent_id] = options self.options[torrent_id].update(options)
row = model.iter_next(row) row = model.iter_next(row)
def _on_button_revert_clicked(self, widget): def _on_button_revert_clicked(self, widget):
@ -863,7 +864,7 @@ class AddTorrentDialog(component.Component):
def _on_filename_edited(self, renderer, path, new_text): def _on_filename_edited(self, renderer, path, new_text):
index = self.files_treestore[path][3] 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 # Return if the text hasn't changed
if new_text == self.files_treestore[path][1]: if new_text == self.files_treestore[path][1]:
@ -881,9 +882,14 @@ class AddTorrentDialog(component.Component):
if index > -1: if index > -1:
# We're renaming a file! Yay! That's easy! # We're renaming a file! Yay! That's easy!
if not new_text:
return
parent = self.files_treestore.iter_parent(itr) parent = self.files_treestore.iter_parent(itr)
file_path = os.path.join(self.get_file_path(parent), new_text) 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: if os.path.sep in new_text:
# There are folders in this path, so we need to create them # There are folders in this path, so we need to create them
# and then move the file iter to top # and then move the file iter to top

View file

@ -40,7 +40,7 @@ Deluge.add.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
layout: 'fit', layout: 'fit',
title: _('Files'), title: _('Files'),
autoScroll: true, autoScroll: false,
animate: false, animate: false,
border: false, border: false,
disabled: true, disabled: true,

View file

@ -78,7 +78,6 @@ Deluge.add.OptionsTab = Ext.extend(Ext.form.FormPanel, {
border: false, border: false,
autoHeight: true, autoHeight: true,
defaultType: 'radio', defaultType: 'radio',
width: 100
}); });
this.optionsManager.bind('compact_allocation', fieldset.add({ this.optionsManager.bind('compact_allocation', fieldset.add({
@ -86,6 +85,7 @@ Deluge.add.OptionsTab = Ext.extend(Ext.form.FormPanel, {
columns: 1, columns: 1,
vertical: true, vertical: true,
labelSeparator: '', labelSeparator: '',
width: 80,
items: [{ items: [{
name: 'compact_allocation', name: 'compact_allocation',
value: false, value: false,
@ -107,31 +107,28 @@ Deluge.add.OptionsTab = Ext.extend(Ext.form.FormPanel, {
title: _('Bandwidth'), title: _('Bandwidth'),
border: false, border: false,
autoHeight: true, autoHeight: true,
labelWidth: 100, bodyStyle: 'margin-left: 7px',
labelWidth: 105,
width: 200, width: 200,
defaultType: 'spinnerfield' defaultType: 'spinnerfield'
}); });
this.optionsManager.bind('max_download_speed', fieldset.add({ this.optionsManager.bind('max_download_speed', fieldset.add({
fieldLabel: _('Max Down Speed'), fieldLabel: _('Max Down Speed'),
labelStyle: 'margin-left: 10px',
name: 'max_download_speed', name: 'max_download_speed',
width: 60 width: 60
})); }));
this.optionsManager.bind('max_upload_speed', fieldset.add({ this.optionsManager.bind('max_upload_speed', fieldset.add({
fieldLabel: _('Max Up Speed'), fieldLabel: _('Max Up Speed'),
labelStyle: 'margin-left: 10px',
name: 'max_upload_speed', name: 'max_upload_speed',
width: 60 width: 60
})); }));
this.optionsManager.bind('max_connections', fieldset.add({ this.optionsManager.bind('max_connections', fieldset.add({
fieldLabel: _('Max Connections'), fieldLabel: _('Max Connections'),
labelStyle: 'margin-left: 10px',
name: 'max_connections', name: 'max_connections',
width: 60 width: 60
})); }));
this.optionsManager.bind('max_upload_slots', fieldset.add({ this.optionsManager.bind('max_upload_slots', fieldset.add({
fieldLabel: _('Max Upload Slots'), fieldLabel: _('Max Upload Slots'),
labelStyle: 'margin-left: 10px',
name: 'max_upload_slots', name: 'max_upload_slots',
width: 60 width: 60
})); }));

View file

@ -152,11 +152,12 @@ else:
for include in os.environ.get("INCLUDEDIR", "").split(":"): for include in os.environ.get("INCLUDEDIR", "").split(":"):
_include_dirs.append(include) _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(): if osx_check():
_include_dirs += [ _include_dirs += [
'/opt/local/include/boost-1_35', '/opt/local/include/boost-1_35',
'/opt/local/include/boost-1_36', '/opt/local/include/boost-1_36',
'/usr/local/include'
'/sw/include/boost-1_35', '/sw/include/boost-1_35',
'/sw/include/boost' '/sw/include/boost'
] ]