Merge branch 'master' into master-decode-string
|
@ -9,3 +9,4 @@ dist
|
|||
_trial_temp
|
||||
deluge/i18n/*/
|
||||
*.desktop
|
||||
.build_data
|
||||
|
|
53
ChangeLog
|
@ -32,6 +32,7 @@
|
|||
* Implemented sequential downloads UI handling.
|
||||
* #378: Allow showing a pieces bar instead of a regular progress bar in a
|
||||
torrent's status tab.
|
||||
* #2093: Make torrent opening compatible with all unicode paths.
|
||||
|
||||
==== Blocklist Plugin ====
|
||||
* #1382: Implemented whitelist support to both core and GTK UI.
|
||||
|
@ -44,6 +45,58 @@
|
|||
=== Deluge 1.3.6 (In Development) ===
|
||||
==== Core ====
|
||||
* Catch & log KeyError when removing a torrent from the queued torrents set
|
||||
* Fix moving/renaming torrents with non-ascii characters in libtorrent 0.16
|
||||
* Make sure queue order is preserved when restarting
|
||||
* #2160: Disable use of python bindings for libtorrent extensions and replace with session flag
|
||||
* #2163: Fix unable add torrent file with empty (0:) encoding tag
|
||||
* #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
|
||||
* Prevent jitter in torrent view
|
||||
* Fix torrent creation with non-ascii characters
|
||||
* Fix #2100 : Add option not to bring main window to front when adding torrents through ipcinterface
|
||||
* Add Quit Dialog when toggling classic mode in preferences and only show connection manager when not in classic mode.
|
||||
* #2169: Fix 'Download Location' in the Add Torrent Dialog not set correctly when folder typed into Other->Location field
|
||||
* #2171: Fix the Add Peer dialog not responding if empty or invalid values entered
|
||||
* #2104: Fix no title set for the appindicator
|
||||
* #2086: Fix submenus and icons for appindicator
|
||||
* #2146: Fix missing translations in View|Tabs submenu
|
||||
* Fix torrent names on libtorrent 0.16 on windows
|
||||
* #2147: Fix missing translations for plugin preferences page
|
||||
* #1474: Fix the on_show_prefs hook not being executed immediatly after enabling a plugin
|
||||
|
||||
==== Console ====
|
||||
* LP#1004793: Enable use of connect command in non-interactive mode
|
||||
* Ensure console commands are executed in order
|
||||
* #2065: Fix crash with missing closing quote
|
||||
* #1397: Add support for -s STATE in info command
|
||||
|
||||
==== WebUI ====
|
||||
* #2112: Fix world readable tmp directory in json_api
|
||||
* #2069: Fix login window layout problem when using larger than default font size
|
||||
* #1890: Fix columns in files and peers view could use some spacing
|
||||
* #2103: Fix sorting by name is case-sensitive [sedulous]
|
||||
* #2120: Fix manually entered values not being saved in spinners
|
||||
* #2212: Fix unable to scroll in proxy preferences page
|
||||
* Fix autoconnecting to the default host
|
||||
|
||||
==== Windows OS ====
|
||||
* Hide the cmd windows when running deluged.exe or deluge-web.exe
|
||||
* Add deluged-debug.exe and deluge-web-debug.exe that still show the cmd window
|
||||
* Add gtk locale files to fix untranslated text
|
||||
* Fix the Open Folder option not working with non-ascii paths
|
||||
* Fix the daemon starting with config dir containing spaces
|
||||
* Fix Windows tray submenu items requiring right-click instead of left-click
|
||||
|
||||
==== OS X ====
|
||||
* Fix Open File/Folder option
|
||||
|
||||
==== Execute ====
|
||||
* Fix execute plugin not working with unicode torrent names
|
||||
|
||||
=== Deluge 1.3.5 (09 April 2012) ===
|
||||
==== GtkUI ====
|
||||
|
|
1
DEPENDS
|
@ -11,6 +11,7 @@
|
|||
* chardet
|
||||
* geoip-database (optional)
|
||||
* setproctitle (optional)
|
||||
* rencode >= 1.0.2 (optional), a pure python version is included
|
||||
|
||||
* libtorrent >= 0.16.1, or build the included version
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -68,6 +68,7 @@ if not hasattr(json, "dumps"):
|
|||
import pkg_resources
|
||||
import gettext
|
||||
import locale
|
||||
import sys
|
||||
|
||||
from deluge.error import *
|
||||
|
||||
|
@ -231,7 +232,7 @@ def open_file(path):
|
|||
|
||||
"""
|
||||
if windows_check():
|
||||
os.startfile("%s" % path)
|
||||
os.startfile(path.decode("utf8"))
|
||||
elif osx_check():
|
||||
subprocess.Popen(["open", "%s" % path])
|
||||
else:
|
||||
|
@ -448,7 +449,9 @@ def is_magnet(uri):
|
|||
True
|
||||
|
||||
"""
|
||||
if uri[:20] == "magnet:?xt=urn:btih:":
|
||||
magnet_scheme = 'magnet:?'
|
||||
xt_param = 'xt=urn:btih:'
|
||||
if uri.startswith(magnet_scheme) and xt_param in uri:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -542,15 +545,23 @@ def is_ip(ip):
|
|||
import socket
|
||||
#first we test ipv4
|
||||
try:
|
||||
if socket.inet_pton(socket.AF_INET, "%s" % (ip)):
|
||||
return True
|
||||
if windows_check():
|
||||
if socket.inet_aton("%s" % (ip)):
|
||||
return True
|
||||
else:
|
||||
if socket.inet_pton(socket.AF_INET, "%s" % (ip)):
|
||||
return True
|
||||
except socket.error:
|
||||
if not socket.has_ipv6:
|
||||
return False
|
||||
#now test ipv6
|
||||
try:
|
||||
if socket.inet_pton(socket.AF_INET6, "%s" % (ip)):
|
||||
if windows_check():
|
||||
log.warning("ipv6 check unavailable on windows")
|
||||
return True
|
||||
else:
|
||||
if socket.inet_pton(socket.AF_INET6, "%s" % (ip)):
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
|
@ -757,3 +768,39 @@ def setup_translations(setup_pygtk=False):
|
|||
log.exception(e)
|
||||
import __builtin__
|
||||
__builtin__.__dict__["_"] = lambda x: x
|
||||
|
||||
def unicode_argv():
|
||||
""" Gets sys.argv as list of unicode objects on any platform."""
|
||||
if windows_check():
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'.
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
else:
|
||||
# On other platforms, we have to find the likely encoding of the args and decode
|
||||
# First check if sys.stdout or stdin have encoding set
|
||||
encoding = getattr(sys.stdout, "encoding") or getattr(sys.stdin, "encoding")
|
||||
# If that fails, check what the locale is set to
|
||||
encoding = encoding or locale.getpreferredencoding()
|
||||
# As a last resort, just default to utf-8
|
||||
encoding = encoding or "utf-8"
|
||||
|
||||
return [arg.decode(encoding) for arg in sys.argv]
|
||||
|
|
|
@ -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,11 +323,23 @@ 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:
|
||||
deferreds.append(self.components[name]._component_stop())
|
||||
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,7 +75,8 @@ class AlertManager(component.Component):
|
|||
|
||||
def stop(self):
|
||||
for dc in self.delayed_calls:
|
||||
dc.cancel()
|
||||
if dc.active():
|
||||
dc.cancel()
|
||||
self.delayed_calls = []
|
||||
|
||||
def register_handler(self, alert_type, handler):
|
||||
|
|
|
@ -236,10 +236,10 @@ class AuthManager(component.Component):
|
|||
f = open(auth_file, "r").readlines()
|
||||
|
||||
for line in f:
|
||||
if line.startswith("#"):
|
||||
# This is a comment line
|
||||
continue
|
||||
line = line.strip()
|
||||
if line.startswith("#") or not line:
|
||||
# This line is a comment or empty
|
||||
continue
|
||||
try:
|
||||
lsplit = line.split(":")
|
||||
except Exception, e:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -153,9 +153,8 @@ class FilterManager(component.Component):
|
|||
|
||||
#sanitize input: filter-value must be a list of strings
|
||||
for key, value in filter_dict.items():
|
||||
if isinstance(value, str):
|
||||
filter_dict[key] = [value]
|
||||
|
||||
if isinstance(value, basestring):
|
||||
filter_dict[key] = [value]
|
||||
|
||||
if "id"in filter_dict: #optimized filter for id:
|
||||
torrent_ids = list(filter_dict["id"])
|
||||
|
|
|
@ -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
|
||||
|
@ -252,7 +244,7 @@ class Torrent(object):
|
|||
|
||||
def get_name(self):
|
||||
if self.handle.has_metadata():
|
||||
name = self.torrent_info.file_at(0).path.split("/", 1)[0]
|
||||
name = self.torrent_info.file_at(0).path.replace("\\", "/", 1).split("/", 1)[0]
|
||||
if not name:
|
||||
name = self.torrent_info.name()
|
||||
try:
|
||||
|
@ -885,31 +877,32 @@ class Torrent(object):
|
|||
|
||||
def move_storage(self, dest):
|
||||
"""Move a torrent's storage location"""
|
||||
|
||||
if deluge.common.windows_check():
|
||||
# Attempt to convert utf8 path to unicode
|
||||
# Note: Inconsistent encoding for 'dest', needs future investigation
|
||||
try:
|
||||
dest_u = unicode(dest, "utf-8")
|
||||
except TypeError:
|
||||
# String is already unicode
|
||||
dest_u = dest
|
||||
else:
|
||||
dest_u = dest
|
||||
try:
|
||||
dest = unicode(dest, "utf-8")
|
||||
except TypeError:
|
||||
# String is already unicode
|
||||
pass
|
||||
|
||||
if not os.path.exists(dest_u):
|
||||
if not os.path.exists(dest):
|
||||
try:
|
||||
# Try to make the destination path if it doesn't exist
|
||||
os.makedirs(dest_u)
|
||||
os.makedirs(dest)
|
||||
except IOError, e:
|
||||
log.exception(e)
|
||||
log.error("Could not move storage for torrent %s since %s does "
|
||||
"not exist and could not create the directory.",
|
||||
self.torrent_id, dest_u)
|
||||
self.torrent_id, dest)
|
||||
return False
|
||||
|
||||
dest_bytes = dest.encode('utf-8')
|
||||
try:
|
||||
self.handle.move_storage(dest_u)
|
||||
except:
|
||||
# libtorrent needs unicode object if wstrings are enabled, utf8 bytestring otherwise
|
||||
try:
|
||||
self.handle.move_storage(dest)
|
||||
except TypeError:
|
||||
self.handle.move_storage(dest_bytes)
|
||||
except Exception, e:
|
||||
log.error("Error calling libtorrent move_storage: %s" % e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@ -918,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"""
|
||||
|
@ -985,12 +977,26 @@ class Torrent(object):
|
|||
"""Renames files in the torrent. 'filenames' should be a list of
|
||||
(index, filename) pairs."""
|
||||
for index, filename in filenames:
|
||||
# Make sure filename is a unicode object
|
||||
try:
|
||||
filename = unicode(filename, "utf-8")
|
||||
except TypeError:
|
||||
pass
|
||||
filename = sanitize_filepath(filename)
|
||||
self.handle.rename_file(index, filename.encode("utf-8"))
|
||||
# libtorrent needs unicode object if wstrings are enabled, utf8 bytestring otherwise
|
||||
try:
|
||||
self.handle.rename_file(index, filename)
|
||||
except TypeError:
|
||||
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)
|
||||
|
@ -998,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,12 @@
|
|||
|
||||
import cPickle
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
import operator
|
||||
import logging
|
||||
import re
|
||||
|
||||
from twisted.internet.task import LoopingCall
|
||||
from twisted.internet.defer import Deferred, DeferredList
|
||||
|
||||
from deluge._libtorrent import lt
|
||||
|
||||
|
@ -133,7 +132,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 +150,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 = {}
|
||||
|
||||
# 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 +211,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 +231,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():
|
||||
|
@ -452,9 +426,16 @@ class TorrentManager(component.Component):
|
|||
# before adding to the session.
|
||||
if options["mapped_files"]:
|
||||
for index, fname in options["mapped_files"].items():
|
||||
try:
|
||||
fname = unicode(fname, "utf-8")
|
||||
except TypeError:
|
||||
pass
|
||||
fname = deluge.core.torrent.sanitize_filepath(fname)
|
||||
log.debug("renaming file index %s to %s", index, fname)
|
||||
torrent_info.rename_file(index, utf8_encoded(fname))
|
||||
try:
|
||||
torrent_info.rename_file(index, fname)
|
||||
except TypeError:
|
||||
torrent_info.rename_file(index, fname.encode("utf-8"))
|
||||
|
||||
add_torrent_params["ti"] = torrent_info
|
||||
|
||||
|
@ -589,12 +570,11 @@ class TorrentManager(component.Component):
|
|||
:raises InvalidTorrentError: if the torrent_id is not in the session
|
||||
|
||||
"""
|
||||
|
||||
if torrent_id not in self.torrents:
|
||||
try:
|
||||
torrent_name = self.torrents[torrent_id].get_status(["name"])["name"]
|
||||
except KeyError:
|
||||
raise InvalidTorrentError("torrent_id not in session")
|
||||
|
||||
torrent_name = self.torrents[torrent_id].get_status(["name"])["name"]
|
||||
|
||||
# Emit the signal to the clients
|
||||
component.get("EventManager").emit(PreTorrentRemovedEvent(torrent_id))
|
||||
|
||||
|
@ -606,9 +586,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()
|
||||
|
@ -678,7 +656,7 @@ class TorrentManager(component.Component):
|
|||
|
||||
# Reorder the state.torrents list to add torrents in the correct queue
|
||||
# order.
|
||||
state.torrents.sort(key=operator.attrgetter("queue"))
|
||||
state.torrents.sort(key=operator.attrgetter("queue"), reverse=self.config["queue_new_to_top"])
|
||||
|
||||
resume_data = self.load_resume_data_file()
|
||||
|
||||
|
@ -770,17 +748,34 @@ 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 = self.waiting_on_resume_data.get(torrent_id)
|
||||
if not d:
|
||||
d = Deferred().addBoth(on_torrent_resume_save, torrent_id)
|
||||
self.waiting_on_resume_data[torrent_id] = 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 = {}
|
||||
|
@ -800,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()
|
||||
|
@ -981,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")
|
||||
|
@ -1098,32 +1037,21 @@ 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
|
||||
torrent_id = str(alert.handle.info_hash())
|
||||
|
||||
# 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
|
||||
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)
|
||||
|
||||
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].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 = str(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")
|
||||
|
@ -1134,24 +1062,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,))
|
||||
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:
|
||||
# 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
|
||||
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,))
|
||||
|
|
|
@ -86,7 +86,7 @@ def start_ui():
|
|||
help="Rotate logfiles.", action="store_true", default=False)
|
||||
|
||||
# Get the options and args from the OptionParser
|
||||
(options, args) = parser.parse_args()
|
||||
(options, args) = parser.parse_args(deluge.common.unicode_argv()[1:])
|
||||
|
||||
if options.quiet:
|
||||
options.loglevel = "none"
|
||||
|
|
|
@ -403,12 +403,11 @@ class GtkUI(GtkPluginBase):
|
|||
sw.add(self.treeView)
|
||||
sw.show_all()
|
||||
component.get("Preferences").add_page(
|
||||
"AutoAdd", self.glade.get_widget("prefs_box")
|
||||
_("AutoAdd"), self.glade.get_widget("prefs_box")
|
||||
)
|
||||
self.on_show_prefs()
|
||||
|
||||
def disable(self):
|
||||
component.get("Preferences").remove_page("AutoAdd")
|
||||
component.get("Preferences").remove_page(_("AutoAdd"))
|
||||
component.get("PluginManager").deregister_hook(
|
||||
"on_apply_prefs", self.on_apply_prefs
|
||||
)
|
||||
|
|
|
@ -54,13 +54,13 @@ class GtkUI(GtkPluginBase):
|
|||
def enable(self):
|
||||
self.glade = gtk.glade.XML(get_resource("extractor_prefs.glade"))
|
||||
|
||||
component.get("Preferences").add_page("Extractor", self.glade.get_widget("extractor_prefs_box"))
|
||||
component.get("Preferences").add_page(_("Extractor"), self.glade.get_widget("extractor_prefs_box"))
|
||||
component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs)
|
||||
component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs)
|
||||
self.on_show_prefs()
|
||||
|
||||
def disable(self):
|
||||
component.get("Preferences").remove_page("Extractor")
|
||||
component.get("Preferences").remove_page(_("Extractor"))
|
||||
component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs)
|
||||
component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs)
|
||||
del self.glade
|
||||
|
|
|
@ -287,7 +287,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||
if parent:
|
||||
parent.remove(self.prefs)
|
||||
index = prefs.notebook.append_page(self.prefs)
|
||||
prefs.liststore.append([index, "Notifications"])
|
||||
prefs.liststore.append([index, _("Notifications")])
|
||||
|
||||
component.get("PluginManager").register_hook("on_apply_prefs",
|
||||
self.on_apply_prefs)
|
||||
|
@ -320,7 +320,7 @@ class GtkUI(GtkPluginBase, GtkUiNotifications):
|
|||
|
||||
def disable(self):
|
||||
GtkUiNotifications.disable(self)
|
||||
component.get("Preferences").remove_page("Notifications")
|
||||
component.get("Preferences").remove_page(_("Notifications"))
|
||||
component.get("PluginManager").deregister_hook("on_apply_prefs",
|
||||
self.on_apply_prefs)
|
||||
component.get("PluginManager").deregister_hook("on_show_prefs",
|
||||
|
|
|
@ -170,7 +170,7 @@ class GtkUI(GtkPluginBase):
|
|||
client.register_event_handler("SchedulerEvent", self.on_scheduler_event)
|
||||
|
||||
def disable(self):
|
||||
component.get("Preferences").remove_page("Scheduler")
|
||||
component.get("Preferences").remove_page(_("Scheduler"))
|
||||
# Remove status item
|
||||
component.get("StatusBar").remove_item(self.status_item)
|
||||
del self.status_item
|
||||
|
@ -294,4 +294,4 @@ class GtkUI(GtkPluginBase):
|
|||
vbox.pack_start(frame, False, False)
|
||||
|
||||
vbox.show_all()
|
||||
component.get("Preferences").add_page("Scheduler", vbox)
|
||||
component.get("Preferences").add_page(_("Scheduler"), vbox)
|
||||
|
|
|
@ -53,14 +53,14 @@ class GtkUI(GtkPluginBase):
|
|||
def enable(self):
|
||||
self.glade = gtk.glade.XML(get_resource("config.glade"))
|
||||
|
||||
component.get("Preferences").add_page("WebUi", self.glade.get_widget("prefs_box"))
|
||||
component.get("Preferences").add_page(_("WebUi"), self.glade.get_widget("prefs_box"))
|
||||
component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs)
|
||||
component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs)
|
||||
client.webui.get_config().addCallback(self.cb_get_config)
|
||||
client.webui.got_deluge_web().addCallback(self.cb_chk_deluge_web)
|
||||
|
||||
def disable(self):
|
||||
component.get("Preferences").remove_page("WebUi")
|
||||
component.get("Preferences").remove_page(_("WebUi"))
|
||||
component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs)
|
||||
component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ rencode module versions, so you should check that you are using the
|
|||
same rencode version throughout your project.
|
||||
"""
|
||||
|
||||
__version__ = '1.0.1'
|
||||
__version__ = '1.0.2'
|
||||
__all__ = ['dumps', 'loads']
|
||||
|
||||
# Original bencode module by Petru Paler, et al.
|
||||
|
@ -107,6 +107,9 @@ STR_FIXED_COUNT = 64
|
|||
LIST_FIXED_START = STR_FIXED_START+STR_FIXED_COUNT
|
||||
LIST_FIXED_COUNT = 64
|
||||
|
||||
# Whether strings should be decoded when loading
|
||||
_decode_utf8 = False
|
||||
|
||||
def decode_int(x, f):
|
||||
f += 1
|
||||
newf = x.index(CHR_TERM, f)
|
||||
|
@ -159,12 +162,8 @@ def decode_string(x, f):
|
|||
raise ValueError
|
||||
colon += 1
|
||||
s = x[colon:colon+n]
|
||||
try:
|
||||
t = s.decode("utf8")
|
||||
if len(t) != len(s):
|
||||
s = t
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
if _decode_utf8:
|
||||
s = s.decode('utf8')
|
||||
return (s, colon+n)
|
||||
|
||||
def decode_list(x, f):
|
||||
|
@ -218,12 +217,8 @@ def make_fixed_length_string_decoders():
|
|||
def make_decoder(slen):
|
||||
def f(x, f):
|
||||
s = x[f+1:f+1+slen]
|
||||
try:
|
||||
t = s.decode("utf8")
|
||||
if len(t) != len(s):
|
||||
s = t
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
if _decode_utf8:
|
||||
s = s.decode("utf8")
|
||||
return (s, f+1+slen)
|
||||
return f
|
||||
for i in range(STR_FIXED_COUNT):
|
||||
|
@ -279,7 +274,9 @@ def encode_dict(x,r):
|
|||
r.append(CHR_TERM)
|
||||
|
||||
|
||||
def loads(x):
|
||||
def loads(x, decode_utf8=False):
|
||||
global _decode_utf8
|
||||
_decode_utf8 = decode_utf8
|
||||
try:
|
||||
r, l = decode_func[x[0]](x, 0)
|
||||
except (IndexError, KeyError):
|
||||
|
|
|
@ -3,8 +3,8 @@ from twisted.internet import threads
|
|||
import deluge.component as component
|
||||
|
||||
class testcomponent(component.Component):
|
||||
def __init__(self, name):
|
||||
component.Component.__init__(self, name)
|
||||
def __init__(self, name, depend=None):
|
||||
component.Component.__init__(self, name, depend=depend)
|
||||
self.start_count = 0
|
||||
self.stop_count = 0
|
||||
|
||||
|
@ -51,6 +51,7 @@ class testcomponent_shutdown(component.Component):
|
|||
|
||||
class ComponentTestClass(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
component.stop()
|
||||
component._ComponentRegistry.components = {}
|
||||
|
||||
def test_start_component(self):
|
||||
|
@ -63,16 +64,22 @@ class ComponentTestClass(unittest.TestCase):
|
|||
d.addCallback(on_start, c)
|
||||
return d
|
||||
|
||||
def test_start_depends(self):
|
||||
def test_start_stop_depends(self):
|
||||
def on_stop(result, c1, c2):
|
||||
self.assertEquals(c1._component_state, "Stopped")
|
||||
self.assertEquals(c2._component_state, "Stopped")
|
||||
self.assertEquals(c1.stop_count, 1)
|
||||
self.assertEquals(c2.stop_count, 1)
|
||||
|
||||
def on_start(result, c1, c2):
|
||||
self.assertEquals(c1._component_state, "Started")
|
||||
self.assertEquals(c2._component_state, "Started")
|
||||
self.assertEquals(c1.start_count, 1)
|
||||
self.assertEquals(c2.start_count, 1)
|
||||
return component.stop(["test_start_depends_c1"]).addCallback(on_stop, c1, c2)
|
||||
|
||||
c1 = testcomponent("test_start_depends_c1")
|
||||
c2 = testcomponent("test_start_depends_c2")
|
||||
c2._component_depend = ["test_start_depends_c1"]
|
||||
c2 = testcomponent("test_start_depends_c2", depend=["test_start_depends_c1"])
|
||||
|
||||
d = component.start(["test_start_depends_c2"])
|
||||
d.addCallback(on_start, c1, c2)
|
||||
|
@ -80,15 +87,12 @@ class ComponentTestClass(unittest.TestCase):
|
|||
|
||||
def start_with_depends(self):
|
||||
c1 = testcomponent_delaystart("test_start_all_c1")
|
||||
c2 = testcomponent("test_start_all_c2")
|
||||
c3 = testcomponent_delaystart("test_start_all_c3")
|
||||
c4 = testcomponent("test_start_all_c4")
|
||||
c2 = testcomponent("test_start_all_c2", depend=["test_start_all_c4"])
|
||||
c3 = testcomponent_delaystart("test_start_all_c3",
|
||||
depend=["test_start_all_c5", "test_start_all_c1"])
|
||||
c4 = testcomponent("test_start_all_c4", depend=["test_start_all_c3"])
|
||||
c5 = testcomponent("test_start_all_c5")
|
||||
|
||||
c3._component_depend = ["test_start_all_c5", "test_start_all_c1"]
|
||||
c4._component_depend = ["test_start_all_c3"]
|
||||
c2._component_depend = ["test_start_all_c4"]
|
||||
|
||||
d = component.start()
|
||||
return (d, c1, c2, c3, c4, c5)
|
||||
|
||||
|
@ -130,15 +134,15 @@ class ComponentTestClass(unittest.TestCase):
|
|||
return d
|
||||
|
||||
def test_stop_all(self):
|
||||
def on_stop(*args):
|
||||
for c in args[1:]:
|
||||
def on_stop(result, *args):
|
||||
for c in args:
|
||||
self.assertEquals(c._component_state, "Stopped")
|
||||
self.assertEquals(c.stop_count, 1)
|
||||
|
||||
def on_start(*args):
|
||||
for c in args[1:]:
|
||||
def on_start(result, *args):
|
||||
for c in args:
|
||||
self.assertEquals(c._component_state, "Started")
|
||||
return component.stop().addCallback(on_stop, *args[1:])
|
||||
return component.stop().addCallback(on_stop, *args)
|
||||
|
||||
ret = self.start_with_depends()
|
||||
ret[0].addCallback(on_start, *ret[1:])
|
||||
|
|
|
@ -140,7 +140,7 @@ class DelugeTransferProtocol(Protocol):
|
|||
|
||||
"""
|
||||
try:
|
||||
self.message_received(rencode.loads(zlib.decompress(data)))
|
||||
self.message_received(rencode.loads(zlib.decompress(data), decode_utf8=True))
|
||||
except Exception, e:
|
||||
log.warn("Failed to decompress (%d bytes) and load serialized data "\
|
||||
"with rencode: %s" % (len(data), str(e)))
|
||||
|
|
|
@ -37,17 +37,14 @@
|
|||
import logging
|
||||
from twisted.internet.protocol import ClientFactory
|
||||
from twisted.internet import reactor, ssl, defer
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
import deluge.common
|
||||
from deluge import error
|
||||
from deluge.event import known_events
|
||||
from deluge.transfer import DelugeTransferProtocol
|
||||
|
||||
if deluge.common.windows_check():
|
||||
import win32api
|
||||
else:
|
||||
import subprocess
|
||||
|
||||
RPC_RESPONSE = 1
|
||||
RPC_ERROR = 2
|
||||
RPC_EVENT = 3
|
||||
|
@ -628,13 +625,10 @@ class Client(object):
|
|||
:raises OSError: received from subprocess.call()
|
||||
|
||||
"""
|
||||
# subprocess.popen does not work with unicode args (with non-ascii characters) on windows
|
||||
config = config.encode(sys.getfilesystemencoding())
|
||||
try:
|
||||
if deluge.common.windows_check():
|
||||
win32api.WinExec("deluged --port=%s --config=\"%s\"" % (port, config))
|
||||
elif deluge.common.osx_check():
|
||||
subprocess.call(["nohup", "deluged", "--port=%s" % port, "--config=%s" % config])
|
||||
else:
|
||||
subprocess.call(["deluged", "--port=%s" % port, "--config=%s" % config])
|
||||
subprocess.Popen(["deluged", "--port=%s" % port, "--config=%s" % config])
|
||||
except OSError, e:
|
||||
from errno import ENOENT
|
||||
if e.errno == ENOENT:
|
||||
|
|
|
@ -106,8 +106,11 @@ class TorrentInfo(object):
|
|||
else:
|
||||
path = utf8_encoded(os.path.join(prefix, utf8_encoded(os.path.join(*f["path"]), self.encoding)), self.encoding)
|
||||
f["index"] = index
|
||||
if "sha1" in f and len(f["sha1"]) == 20:
|
||||
f["sha1"] = f["sha1"].encode('hex')
|
||||
if "ed2k" in f and len(f["ed2k"]) == 16:
|
||||
f["ed2k"] = f["ed2k"].encode('hex')
|
||||
paths[path] = f
|
||||
|
||||
dirname = os.path.dirname(path)
|
||||
while dirname:
|
||||
dirinfo = dirs.setdefault(dirname, {})
|
||||
|
|
|
@ -133,7 +133,22 @@ class Command(BaseCommand):
|
|||
deferred.callback(True)
|
||||
|
||||
self.console.write("Setting %s to %s for torrents %s.." % (key, val, torrent_ids))
|
||||
client.core.set_torrent_options(torrent_ids, {key: val}).addCallback(on_set_config)
|
||||
|
||||
|
||||
for tid in torrent_ids:
|
||||
if key == "move_on_completed_path":
|
||||
client.core.set_torrent_move_completed_path(tid, val).addCallback(on_set_config)
|
||||
elif key == "move_on_completed":
|
||||
client.core.set_torrent_move_completed(tid, val).addCallback(on_set_config)
|
||||
elif key == "is_auto_managed":
|
||||
client.core.set_torrent_auto_managed(tid, val).addCallback(on_set_config)
|
||||
elif key == "remove_at_ratio":
|
||||
client.core.set_torrent_remove_at_ratio(tid, val).addCallback(on_set_config)
|
||||
elif key == "prioritize_first_last":
|
||||
client.core.set_torrent_prioritize_first_last(tid, val).addCallback(on_set_config)
|
||||
else:
|
||||
client.core.set_torrent_options(torrent_ids, {key: val}).addCallback(on_set_config)
|
||||
break
|
||||
return deferred
|
||||
|
||||
def complete(self, line):
|
||||
|
|
|
@ -250,6 +250,8 @@ def torrent_action(idx, data, mode, ids):
|
|||
for tid in ids:
|
||||
if "move_on_completed_path" in options:
|
||||
client.core.set_torrent_move_completed_path(tid, options["move_on_completed_path"])
|
||||
if "move_on_completed" in options:
|
||||
client.core.set_torrent_move_completed(tid, options["move_on_completed"])
|
||||
if "is_auto_managed" in options:
|
||||
client.core.set_torrent_auto_managed(tid, options["is_auto_managed"])
|
||||
if "remove_at_ratio" in options:
|
||||
|
|
|
@ -310,6 +310,8 @@ class TorrentDetail(BaseMode, component.Component):
|
|||
color_partially_selected = "magenta"
|
||||
color_highlighted = "white"
|
||||
for fl in files:
|
||||
#from sys import stderr
|
||||
#print >> stderr, fl[6]
|
||||
# kick out if we're going to draw too low on the screen
|
||||
if (off >= self.rows-1):
|
||||
self.more_to_draw = True
|
||||
|
@ -317,18 +319,34 @@ class TorrentDetail(BaseMode, component.Component):
|
|||
|
||||
self.file_limit = idx
|
||||
|
||||
if idx >= self.file_off:
|
||||
# set fg/bg colors based on if we are selected/marked or not
|
||||
|
||||
# default values
|
||||
fg = "white"
|
||||
bg = "black"
|
||||
# default color values
|
||||
fg = "white"
|
||||
bg = "black"
|
||||
attr = ""
|
||||
|
||||
if fl[6] == -2: priority = -1 #Mixed
|
||||
elif fl[6] == 0:
|
||||
priority = 0 #Do Not Download
|
||||
fg = "red"
|
||||
elif fl[6] == 1:
|
||||
priority = 1 #Normal
|
||||
elif fl[6] <= 6:
|
||||
priority = 2 #High
|
||||
fg = "yellow"
|
||||
elif fl[6] == 7:
|
||||
priority = 3 #Highest
|
||||
fg = "green"
|
||||
|
||||
if idx >= self.file_off:
|
||||
# set fg/bg colors based on whether the file is selected/marked or not
|
||||
|
||||
if fl[1] in self.marked:
|
||||
bg = color_selected
|
||||
if fl[3]:
|
||||
if self.marked[fl[1]] < self.__get_contained_files_count(file_list=fl[3]):
|
||||
bg = color_partially_selected
|
||||
attr = "bold"
|
||||
|
||||
if idx == self.current_file_idx:
|
||||
self.current_file = fl
|
||||
|
@ -339,9 +357,14 @@ class TorrentDetail(BaseMode, component.Component):
|
|||
if self.marked[fl[1]] < self.__get_contained_files_count(file_list = fl[3]):
|
||||
fg = color_partially_selected
|
||||
else:
|
||||
fg = "black"
|
||||
if fg == "white":
|
||||
fg = "black"
|
||||
attr = "bold"
|
||||
|
||||
color_string = "{!%s,%s!}"%(fg,bg)
|
||||
if attr:
|
||||
color_string = "{!%s,%s,%s!}"%(fg, bg, attr)
|
||||
else:
|
||||
color_string = "{!%s,%s!}"%(fg, bg)
|
||||
|
||||
#actually draw the dir/file string
|
||||
if fl[3] and fl[4]: # this is an expanded directory
|
||||
|
|
|
@ -213,9 +213,6 @@ class AddTorrentDialog(component.Component):
|
|||
new_row = None
|
||||
|
||||
for filename in filenames:
|
||||
# Convert the path to unicode
|
||||
filename = unicode(filename)
|
||||
|
||||
# Get the torrent data from the torrent file
|
||||
try:
|
||||
info = deluge.ui.common.TorrentInfo(filename)
|
||||
|
@ -825,14 +822,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 +861,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 +879,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
|
||||
|
|
|
@ -111,7 +111,7 @@ def build_menu_radio_list(value_list, callback, pref_value=None,
|
|||
|
||||
if show_notset:
|
||||
menuitem = gtk.RadioMenuItem(group, notset_label)
|
||||
menuitem.set_name(notset_label)
|
||||
menuitem.set_name("unlimited")
|
||||
if pref_value < notset_lessthan and pref_value != None:
|
||||
menuitem.set_active(True)
|
||||
if show_activated and pref_value == 1:
|
||||
|
@ -124,7 +124,7 @@ def build_menu_radio_list(value_list, callback, pref_value=None,
|
|||
menuitem = gtk.SeparatorMenuItem()
|
||||
menu.append(menuitem)
|
||||
menuitem = gtk.MenuItem(_("Other..."))
|
||||
menuitem.set_name(_("Other..."))
|
||||
menuitem.set_name("other")
|
||||
menuitem.connect("activate", callback)
|
||||
menu.append(menuitem)
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ class CreateTorrentDialog:
|
|||
chooser.destroy()
|
||||
return
|
||||
|
||||
path = result.decode('utf-8').encode(sys.getfilesystemencoding())
|
||||
path = result.decode('utf-8')
|
||||
|
||||
self.files_treestore.clear()
|
||||
self.files_treestore.append(None, [result, gtk.STOCK_FILE, deluge.common.get_path_size(path)])
|
||||
|
@ -202,7 +202,7 @@ class CreateTorrentDialog:
|
|||
chooser.destroy()
|
||||
return
|
||||
|
||||
path = result.decode('utf-8').encode(sys.getfilesystemencoding())
|
||||
path = result.decode('utf-8')
|
||||
|
||||
self.files_treestore.clear()
|
||||
self.files_treestore.append(None, [result, gtk.STOCK_OPEN, deluge.common.get_path_size(path)])
|
||||
|
|
|
@ -2465,6 +2465,19 @@ used sparingly.</property>
|
|||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="chk_focus_main_window_on_add">
|
||||
<property name="label" translatable="yes">Focus window when adding torrent</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="piecesbar_toggle">
|
||||
<property name="visible">True</property>
|
||||
|
@ -2491,7 +2504,7 @@ status tab (<b>EXPERIMENTAL!!!</b>)</property>
|
|||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
<property name="use_underline">True</property>
|
||||
<property name="image">menu-item-image1</property>
|
||||
<property name="use_stock">False</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="activate" handler="on_menuitem_add_torrent_activate" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
|
@ -109,6 +110,7 @@
|
|||
<property name="use_underline">True</property>
|
||||
<property name="image">download-limit-image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -120,6 +122,7 @@
|
|||
<property name="use_underline">True</property>
|
||||
<property name="image">upload-limit-image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
|
|
@ -149,6 +149,7 @@ DEFAULT_PREFS = {
|
|||
"pieces_color_waiting": [4874, 56494, 0],
|
||||
"pieces_color_downloading": [65535, 55255, 0],
|
||||
"pieces_color_completed": [4883, 26985, 56540],
|
||||
"focus_main_window_on_add": True,
|
||||
}
|
||||
|
||||
class GtkUI(object):
|
||||
|
@ -245,9 +246,8 @@ class GtkUI(object):
|
|||
component.stop()
|
||||
|
||||
# Process any pending gtk events since the mainloop has been quit
|
||||
if not deluge.common.windows_check():
|
||||
while gtk.events_pending() and reactor.running:
|
||||
reactor.doIteration(0)
|
||||
while gtk.events_pending():
|
||||
gtk.main_iteration(0)
|
||||
|
||||
# Shutdown all components
|
||||
component.shutdown()
|
||||
|
|
|
@ -59,8 +59,10 @@ log = logging.getLogger(__name__)
|
|||
|
||||
class IPCProtocolServer(Protocol):
|
||||
def dataReceived(self, data):
|
||||
data = rencode.loads(data)
|
||||
component.get("MainWindow").present()
|
||||
config = ConfigManager("gtkui.conf")
|
||||
data = rencode.loads(data, decode_utf8=True)
|
||||
if not data or config["focus_main_window_on_add"]:
|
||||
component.get("MainWindow").present()
|
||||
process_args(data)
|
||||
|
||||
class IPCProtocolClient(Protocol):
|
||||
|
|
|
@ -93,6 +93,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
|
|||
|
||||
def _on_plugin_enabled_event(self, name):
|
||||
self.enable_plugin(name)
|
||||
self.run_on_show_prefs()
|
||||
|
||||
def _on_plugin_disabled_event(self, name):
|
||||
self.disable_plugin(name)
|
||||
|
|
|
@ -121,7 +121,8 @@ class Preferences(component.Component):
|
|||
self.accounts_frame = self.builder.get_object("AccountsFrame")
|
||||
|
||||
# Setup plugin tab listview
|
||||
self.plugin_liststore = gtk.ListStore(str, bool)
|
||||
# The third entry is for holding translated plugin names
|
||||
self.plugin_liststore = gtk.ListStore(str, bool, str)
|
||||
self.plugin_liststore.set_sort_column_id(0, gtk.SORT_ASCENDING)
|
||||
self.plugin_listview = self.builder.get_object("plugin_listview")
|
||||
self.plugin_listview.set_model(self.plugin_liststore)
|
||||
|
@ -131,7 +132,7 @@ class Preferences(component.Component):
|
|||
self.plugin_listview.append_column(
|
||||
gtk.TreeViewColumn(_("Enabled"), render, active=1))
|
||||
self.plugin_listview.append_column(
|
||||
gtk.TreeViewColumn(_("Plugin"), gtk.CellRendererText(), text=0))
|
||||
gtk.TreeViewColumn(_("Plugin"), gtk.CellRendererText(), text=2))
|
||||
|
||||
# Connect to the 'changed' event of TreeViewSelection to get selection
|
||||
# changes.
|
||||
|
@ -552,6 +553,8 @@ class Preferences(component.Component):
|
|||
self.gtkui_config["classic_mode"])
|
||||
self.builder.get_object("chk_show_rate_in_title").set_active(
|
||||
self.gtkui_config["show_rate_in_title"])
|
||||
self.builder.get_object("chk_focus_main_window_on_add").set_active(
|
||||
self.gtkui_config["focus_main_window_on_add"])
|
||||
self.builder.get_object("piecesbar_toggle").set_active(
|
||||
self.gtkui_config["show_piecesbar"]
|
||||
)
|
||||
|
@ -583,6 +586,7 @@ class Preferences(component.Component):
|
|||
row = self.plugin_liststore.append()
|
||||
self.plugin_liststore.set_value(row, 0, plugin)
|
||||
self.plugin_liststore.set_value(row, 1, enabled)
|
||||
self.plugin_liststore.set_value(row, 2, _(plugin))
|
||||
|
||||
# Now show the dialog
|
||||
self.pref_dialog.show()
|
||||
|
@ -739,6 +743,8 @@ class Preferences(component.Component):
|
|||
|
||||
new_gtkui_config["show_rate_in_title"] = \
|
||||
self.builder.get_object("chk_show_rate_in_title").get_active()
|
||||
new_gtkui_config["focus_main_window_on_add"] = \
|
||||
self.builder.get_object("chk_focus_main_window_on_add").get_active()
|
||||
|
||||
## Other tab ##
|
||||
new_gtkui_config["show_new_releases"] = \
|
||||
|
|
|
@ -173,7 +173,7 @@ class QueuedTorrents(component.Component):
|
|||
def on_button_add_clicked(self, widget):
|
||||
# Add all the torrents in the liststore
|
||||
def add_torrent(model, path, iter, data):
|
||||
torrent_path = model.get_value(iter, 1)
|
||||
torrent_path = model.get_value(iter, 1).decode('utf-8')
|
||||
process_args([torrent_path])
|
||||
|
||||
self.liststore.foreach(add_torrent, None)
|
||||
|
|
|
@ -392,9 +392,9 @@ class StatusBar(component.Component):
|
|||
def _on_set_download_speed(self, widget):
|
||||
log.debug("_on_set_download_speed")
|
||||
|
||||
if widget.get_name() == _("Unlimited"):
|
||||
if widget.get_name() == "unlimited":
|
||||
value = -1
|
||||
elif widget.get_name() == _("Other..."):
|
||||
elif widget.get_name() == "other":
|
||||
value = common.show_other_dialog(
|
||||
_("Set Maximum Download Speed"), _("KiB/s"), None, "downloading.svg", self.max_download_speed)
|
||||
if value == None:
|
||||
|
@ -420,9 +420,9 @@ class StatusBar(component.Component):
|
|||
def _on_set_upload_speed(self, widget):
|
||||
log.debug("_on_set_upload_speed")
|
||||
|
||||
if widget.get_name() == _("Unlimited"):
|
||||
if widget.get_name() == "unlimited":
|
||||
value = -1
|
||||
elif widget.get_name() == _("Other..."):
|
||||
elif widget.get_name() == "other":
|
||||
value = common.show_other_dialog(
|
||||
_("Set Maximum Upload Speed"), _("KiB/s"), None, "seeding.svg", self.max_upload_speed)
|
||||
if value == None:
|
||||
|
@ -447,9 +447,9 @@ class StatusBar(component.Component):
|
|||
def _on_set_connection_limit(self, widget):
|
||||
log.debug("_on_set_connection_limit")
|
||||
|
||||
if widget.get_name() == _("Unlimited"):
|
||||
if widget.get_name() == "unlimited":
|
||||
value = -1
|
||||
elif widget.get_name() == _("Other..."):
|
||||
elif widget.get_name() == "other":
|
||||
value = common.show_other_dialog(
|
||||
_("Set Maximum Connections"), "", gtk.STOCK_NETWORK, None, self.max_connections)
|
||||
if value == None:
|
||||
|
|
|
@ -138,10 +138,9 @@ class SystemTray(component.Component):
|
|||
self.tray.connect("activate", self.on_tray_clicked)
|
||||
self.tray.connect("popup-menu", self.on_tray_popup)
|
||||
|
||||
# For some reason these icons do not display in appindicator
|
||||
self.builder.get_object("download-limit-image").set_from_file(
|
||||
self.builder.get_object("download-limit-image").set_from_file(
|
||||
deluge.common.get_pixmap("downloading16.png"))
|
||||
self.builder.get_object("upload-limit-image").set_from_file(
|
||||
self.builder.get_object("upload-limit-image").set_from_file(
|
||||
deluge.common.get_pixmap("seeding16.png"))
|
||||
|
||||
client.register_event_handler("ConfigValueChangedEvent", self.config_value_changed)
|
||||
|
@ -157,21 +156,14 @@ class SystemTray(component.Component):
|
|||
if self.config["enable_system_tray"]:
|
||||
|
||||
if self.config["classic_mode"]:
|
||||
self.hide_widget_list.remove("menuitem_quitdaemon")
|
||||
self.hide_widget_list.remove("separatormenuitem4")
|
||||
try:
|
||||
self.hide_widget_list.remove("menuitem_quitdaemon")
|
||||
self.hide_widget_list.remove("separatormenuitem4")
|
||||
except ValueError:
|
||||
pass
|
||||
self.builder.get_object("menuitem_quitdaemon").hide()
|
||||
self.builder.get_object("separatormenuitem4").hide()
|
||||
|
||||
# These do not work with appindicator currently and can crash Deluge.
|
||||
# Related to Launchpad bug #608219
|
||||
if appindicator and self.config["enable_appindicator"]:
|
||||
self.hide_widget_list.remove("menuitem_download_limit")
|
||||
self.hide_widget_list.remove("menuitem_upload_limit")
|
||||
self.hide_widget_list.remove("separatormenuitem3")
|
||||
self.builder.get_object("menuitem_download_limit").hide()
|
||||
self.builder.get_object("menuitem_upload_limit").hide()
|
||||
self.builder.get_object("separatormenuitem3").hide()
|
||||
|
||||
# Show widgets in the hide list because we've connected to a host
|
||||
for widget in self.hide_widget_list:
|
||||
self.builder.get_object(widget).show()
|
||||
|
@ -266,16 +258,15 @@ class SystemTray(component.Component):
|
|||
def build_tray_bwsetsubmenu(self):
|
||||
# Create the Download speed list sub-menu
|
||||
submenu_bwdownset = common.build_menu_radio_list(
|
||||
self.config["tray_download_speed_list"], self.tray_setbwdown,
|
||||
self.config["tray_download_speed_list"], self.on_tray_setbwdown,
|
||||
self.max_download_speed,
|
||||
_("KiB/s"), show_notset=True, show_other=True)
|
||||
|
||||
# Create the Upload speed list sub-menu
|
||||
submenu_bwupset = common.build_menu_radio_list(
|
||||
self.config["tray_upload_speed_list"], self.tray_setbwup,
|
||||
self.config["tray_upload_speed_list"], self.on_tray_setbwup,
|
||||
self.max_upload_speed,
|
||||
_("KiB/s"), show_notset=True, show_other=True)
|
||||
|
||||
# Add the sub-menus to the tray menu
|
||||
self.builder.get_object("menuitem_download_limit").set_submenu(
|
||||
submenu_bwdownset)
|
||||
|
@ -286,10 +277,6 @@ class SystemTray(component.Component):
|
|||
submenu_bwdownset.show_all()
|
||||
submenu_bwupset.show_all()
|
||||
|
||||
# Re-set the menu to partly work around Launchpad bug #608219
|
||||
if appindicator and self.config["enable_appindicator"]:
|
||||
self.indicator.set_menu(self.tray_menu)
|
||||
|
||||
def disable(self,invert_app_ind_conf=False):
|
||||
"""Disables the system tray icon or appindicator."""
|
||||
try:
|
||||
|
@ -360,6 +347,7 @@ class SystemTray(component.Component):
|
|||
popup_function = gtk.status_icon_position_menu
|
||||
if deluge.common.windows_check():
|
||||
popup_function = None
|
||||
button = 0
|
||||
self.tray_menu.popup(None, None, popup_function,
|
||||
button, activate_time, status_icon)
|
||||
|
||||
|
@ -399,10 +387,22 @@ class SystemTray(component.Component):
|
|||
|
||||
self.window.quit(shutdown=True)
|
||||
|
||||
def tray_setbwdown(self, widget, data=None):
|
||||
def on_tray_setbwdown(self, widget, data=None):
|
||||
if isinstance(widget, gtk.RadioMenuItem):
|
||||
#ignore previous radiomenuitem value
|
||||
if not widget.get_active():
|
||||
return
|
||||
self.setbwlimit(widget, _("Set Maximum Download Speed"), "max_download_speed",
|
||||
"tray_download_speed_list", self.max_download_speed, "downloading.svg")
|
||||
|
||||
def on_tray_setbwup(self, widget, data=None):
|
||||
if isinstance(widget, gtk.RadioMenuItem):
|
||||
#ignore previous radiomenuitem value
|
||||
if not widget.get_active():
|
||||
return
|
||||
self.setbwlimit(widget, _("Set Maximum Upload Speed"), "max_upload_speed",
|
||||
"tray_upload_speed_list", self.max_upload_speed, "seeding.svg")
|
||||
|
||||
def _on_window_hide(self, widget, data=None):
|
||||
"""_on_window_hide - update the menuitem's status"""
|
||||
log.debug("_on_window_hide")
|
||||
|
@ -413,26 +413,21 @@ class SystemTray(component.Component):
|
|||
log.debug("_on_window_show")
|
||||
self.builder.get_object("menuitem_show_deluge").set_active(True)
|
||||
|
||||
def tray_setbwup(self, widget, data=None):
|
||||
self.setbwlimit(widget, _("Set Maximum Upload Speed"), "max_upload_speed",
|
||||
"tray_upload_speed_list", self.max_upload_speed, "seeding.svg")
|
||||
|
||||
def setbwlimit(self, widget, string, core_key, ui_key, default, image):
|
||||
"""Sets the bandwidth limit based on the user selection."""
|
||||
value = widget.get_children()[0].get_text().rstrip(" " + _("KiB/s"))
|
||||
if value == _("Unlimited"):
|
||||
value = widget.get_children()[0].get_text().split(" ")[0]
|
||||
log.debug('setbwlimit: %s', value)
|
||||
if widget.get_name() == "unlimited":
|
||||
value = -1
|
||||
|
||||
if value == _("Other..."):
|
||||
if widget.get_name() == "other":
|
||||
value = common.show_other_dialog(string, _("KiB/s"), None, image, default)
|
||||
if value == None:
|
||||
return
|
||||
|
||||
elif value == 0:
|
||||
value = -1
|
||||
# Set the config in the core
|
||||
client.core.set_config({core_key: value})
|
||||
|
||||
self.build_tray_bwsetsubmenu()
|
||||
|
||||
def unlock_tray(self, is_showing_dlg=[False]):
|
||||
try:
|
||||
from hashlib import sha1 as sha_hash
|
||||
|
|
|
@ -113,6 +113,15 @@ class TorrentDetails(component.Component):
|
|||
("Options", True)
|
||||
]
|
||||
|
||||
self.translate_tabs = {
|
||||
"All" : _("_All"),
|
||||
"Status" : _("_Status"),
|
||||
"Details" : _("_Details"),
|
||||
"Files" : _("_Files"),
|
||||
"Peers" : _("_Peers"),
|
||||
"Options" : _("_Options")
|
||||
}
|
||||
|
||||
# Get the state from saved file
|
||||
state = self.load_state()
|
||||
|
||||
|
@ -242,8 +251,8 @@ class TorrentDetails(component.Component):
|
|||
|
||||
def hide_tab(self, tab_name):
|
||||
"""Hides tab by name"""
|
||||
self.notebook.remove_page(self.tabs[tab_name].position)
|
||||
self.tabs[tab_name].is_visible = False
|
||||
self.notebook.remove_page(self.tabs[tab_name].position)
|
||||
self.regenerate_positions()
|
||||
self.generate_menu()
|
||||
|
||||
|
@ -275,7 +284,8 @@ class TorrentDetails(component.Component):
|
|||
"""Generates the checklist menu for all the tabs and attaches it"""
|
||||
menu = gtk.Menu()
|
||||
# Create 'All' menuitem and a separator
|
||||
menuitem = gtk.CheckMenuItem("All")
|
||||
menuitem = gtk.CheckMenuItem(self.translate_tabs["All"], True)
|
||||
menuitem.set_name("All")
|
||||
|
||||
all_tabs = True
|
||||
for key in self.tabs:
|
||||
|
@ -297,7 +307,8 @@ class TorrentDetails(component.Component):
|
|||
menuitem_list.sort()
|
||||
|
||||
for pos, name in menuitem_list:
|
||||
menuitem = gtk.CheckMenuItem(name)
|
||||
menuitem = gtk.CheckMenuItem(self.translate_tabs[name], True)
|
||||
menuitem.set_name(name)
|
||||
menuitem.set_active(self.tabs[name].is_visible)
|
||||
menuitem.connect("toggled", self._on_menuitem_toggled)
|
||||
menu.append(menuitem)
|
||||
|
@ -386,7 +397,7 @@ class TorrentDetails(component.Component):
|
|||
|
||||
def _on_menuitem_toggled(self, widget):
|
||||
# Get the tab name
|
||||
name = widget.get_child().get_text()
|
||||
name = widget.get_name()
|
||||
if name == "All":
|
||||
if widget.get_active():
|
||||
self.show_all_tabs()
|
||||
|
|
|
@ -107,7 +107,9 @@ class _UI(object):
|
|||
return self.__args
|
||||
|
||||
def start(self):
|
||||
(self.__options, self.__args) = self.__parser.parse_args()
|
||||
# Make sure all arguments are unicode
|
||||
argv = deluge.common.unicode_argv()[1:]
|
||||
(self.__options, self.__args) = self.__parser.parse_args(argv)
|
||||
|
||||
if self.__options.quiet:
|
||||
self.__options.loglevel = "none"
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
* Add Window torrent options
|
||||
- Add an options manager
|
||||
* Preferences window options
|
||||
- Add an options manager
|
|
@ -244,10 +244,57 @@ dl.singleline dd {
|
|||
}
|
||||
|
||||
/* Files TreeGrid */
|
||||
.x-treegrid-col {
|
||||
overflow: hidden;
|
||||
.x-treegrid-root-table {
|
||||
border-right: 1px solid;
|
||||
}
|
||||
|
||||
.x-treegrid-root-node {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.x-treegrid-hd-hidden {
|
||||
visibility: hidden;
|
||||
border: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.x-treegrid-col {
|
||||
border-bottom: 1px solid;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
vertical-align: top;
|
||||
-o-text-overflow: ellipsis;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.x-treegrid-text {
|
||||
padding-left: 4px;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
}
|
||||
|
||||
.x-treegrid-resizer {
|
||||
border-left:1px solid;
|
||||
border-right:1px solid;
|
||||
position:absolute;
|
||||
left:0;
|
||||
top:0;
|
||||
}
|
||||
|
||||
.x-treegrid-header-inner {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.x-treegrid-root-table,
|
||||
.x-treegrid-col {
|
||||
border-color: #ededed;
|
||||
}
|
||||
|
||||
.x-treegrid-resizer {
|
||||
border-left-color:#555;
|
||||
border-right-color:#555;
|
||||
}
|
||||
|
||||
/* Options Tab Styles */
|
||||
.x-deluge-options-label {
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
"""
|
||||
Script to go through the javascript files and dynamically generate gettext.js
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
import cStringIO as StringIO
|
||||
|
||||
output_file = "gettext.js"
|
||||
string_re = re.compile('_\\(\'(.*?)\'\\)')
|
||||
strings = {}
|
||||
|
||||
|
||||
gettext_tpl = """## -*- coding: utf-8 -*-
|
||||
/*
|
||||
* Script: gettext.js
|
||||
|
@ -59,10 +58,10 @@ for root, dnames, files in os.walk('js/deluge-all'):
|
|||
keys = strings.keys()
|
||||
keys.sort()
|
||||
|
||||
fp = StringIO.StringIO()
|
||||
fp = open(output_file, 'w')
|
||||
fp.write(gettext_tpl)
|
||||
for key in keys:
|
||||
fp.write('// %s\n' % ', '.join(map(lambda x: '%s:%s' % x, strings[key])))
|
||||
fp.write("GetText.add('%(key)s', '${escape(_(\"%(key)s\"))}')\n\n" % locals())
|
||||
fp.seek(0)
|
||||
print fp.read()
|
||||
fp.close()
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
images from the fruge icon set.
|
||||
See LICENSE for a list of icons not taken from fruge.
|
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 722 B |
Before Width: | Height: | Size: 722 B |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 588 B |
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Deluge.data.SortTypes.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -39,7 +39,7 @@ Ext.namespace('Deluge.data');
|
|||
*
|
||||
* @class Deluge.data.SortTypes
|
||||
* @singleton
|
||||
*/
|
||||
*/
|
||||
Deluge.data.SortTypes = {
|
||||
asIPAddress: function(value) {
|
||||
var d = value.match(/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\:(\d+)/);
|
||||
|
@ -48,6 +48,10 @@ Deluge.data.SortTypes = {
|
|||
|
||||
asQueuePosition: function(value) {
|
||||
return (value > -1) ? value : Number.MAX_VALUE;
|
||||
},
|
||||
|
||||
asName: function(value) {
|
||||
return String(value).toLowerCase();
|
||||
}
|
||||
}
|
||||
/*!
|
||||
|
@ -121,7 +125,7 @@ Deluge.data.Peer = Ext.data.Record.create([
|
|||
]);
|
||||
/*!
|
||||
* Deluge.data.TorrentRecord.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -168,7 +172,8 @@ Deluge.data.Torrent = Ext.data.Record.create([{
|
|||
type: 'int'
|
||||
}, {
|
||||
name: 'name',
|
||||
type: 'string'
|
||||
type: 'string',
|
||||
sortType: Deluge.data.SortTypes.asName
|
||||
}, {
|
||||
name: 'total_size',
|
||||
type: 'int'
|
||||
|
@ -349,7 +354,7 @@ Deluge.details.DetailsTab = Ext.extend(Ext.Panel, {
|
|||
title: _('Details'),
|
||||
|
||||
fields: {},
|
||||
|
||||
autoScroll: true,
|
||||
queuedItems: {},
|
||||
|
||||
oldData: {},
|
||||
|
@ -428,7 +433,7 @@ Deluge.details.DetailsTab = Ext.extend(Ext.Panel, {
|
|||
});
|
||||
/*!
|
||||
* Deluge.details.FilesTab.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -457,12 +462,11 @@ Deluge.details.DetailsTab = Ext.extend(Ext.Panel, {
|
|||
* this exception statement from your version. If you delete this exception
|
||||
* statement from all source files in the program, then also delete it here.
|
||||
*/
|
||||
|
||||
|
||||
Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
||||
|
||||
title: _('Files'),
|
||||
|
||||
autoScroll: true,
|
||||
rootVisible: false,
|
||||
|
||||
columns: [{
|
||||
|
@ -502,7 +506,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
}
|
||||
})
|
||||
}],
|
||||
|
||||
|
||||
selModel: new Ext.tree.MultiSelectionModel(),
|
||||
|
||||
initComponent: function() {
|
||||
|
@ -558,7 +562,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
this.clear();
|
||||
this.torrentId = torrentId;
|
||||
}
|
||||
|
||||
|
||||
deluge.client.web.get_torrent_files(torrentId, {
|
||||
success: this.onRequestComplete,
|
||||
scope: this,
|
||||
|
@ -591,7 +595,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
folderSort: true
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
onContextMenu: function(node, e) {
|
||||
e.stopEvent();
|
||||
var selModel = this.getSelectionModel();
|
||||
|
@ -601,7 +605,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
}
|
||||
deluge.menus.filePriorities.showAt(e.getPoint());
|
||||
},
|
||||
|
||||
|
||||
onItemClick: function(baseItem, e) {
|
||||
switch (baseItem.id) {
|
||||
case 'expandAll':
|
||||
|
@ -628,7 +632,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var priorities = new Array(Ext.keys(indexes).length);
|
||||
for (var index in indexes) {
|
||||
priorities[index] = indexes[index];
|
||||
|
@ -645,7 +649,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onRequestComplete: function(files, options) {
|
||||
if (!this.getRootNode().hasChildNodes()) {
|
||||
this.createFileTree(files);
|
||||
|
@ -1544,6 +1548,7 @@ Deluge.add.AddWindow = Ext.extend(Deluge.add.Window, {
|
|||
}, {
|
||||
text: _('Infohash'),
|
||||
iconCls: 'icon-add-magnet',
|
||||
hidden: true,
|
||||
disabled: true
|
||||
}, '->', {
|
||||
text: _('Remove'),
|
||||
|
@ -4392,7 +4397,7 @@ Deluge.preferences.ProxyField = Ext.extend(Ext.form.FieldSet, {
|
|||
});
|
||||
/*!
|
||||
* Deluge.preferences.ProxyPage.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -4432,11 +4437,12 @@ Deluge.preferences.Proxy = Ext.extend(Ext.form.FormPanel, {
|
|||
config = Ext.apply({
|
||||
border: false,
|
||||
title: _('Proxy'),
|
||||
layout: 'form'
|
||||
layout: 'form',
|
||||
autoScroll: true
|
||||
}, config);
|
||||
Deluge.preferences.Proxy.superclass.constructor.call(this, config);
|
||||
},
|
||||
|
||||
|
||||
initComponent: function() {
|
||||
Deluge.preferences.Proxy.superclass.initComponent.call(this);
|
||||
this.peer = this.add(new Deluge.preferences.ProxyField({
|
||||
|
@ -4444,28 +4450,28 @@ Deluge.preferences.Proxy = Ext.extend(Ext.form.FormPanel, {
|
|||
name: 'peer'
|
||||
}));
|
||||
this.peer.on('change', this.onProxyChange, this);
|
||||
|
||||
|
||||
this.web_seed = this.add(new Deluge.preferences.ProxyField({
|
||||
title: _('Web Seed'),
|
||||
name: 'web_seed'
|
||||
}));
|
||||
this.web_seed.on('change', this.onProxyChange, this);
|
||||
|
||||
|
||||
this.tracker = this.add(new Deluge.preferences.ProxyField({
|
||||
title: _('Tracker'),
|
||||
name: 'tracker'
|
||||
}));
|
||||
this.tracker.on('change', this.onProxyChange, this);
|
||||
|
||||
|
||||
this.dht = this.add(new Deluge.preferences.ProxyField({
|
||||
title: _('DHT'),
|
||||
name: 'dht'
|
||||
}));
|
||||
this.dht.on('change', this.onProxyChange, this);
|
||||
|
||||
|
||||
deluge.preferences.getOptionsManager().bind('proxies', this);
|
||||
},
|
||||
|
||||
|
||||
getValue: function() {
|
||||
return {
|
||||
'dht': this.dht.getValue(),
|
||||
|
@ -4474,18 +4480,18 @@ Deluge.preferences.Proxy = Ext.extend(Ext.form.FormPanel, {
|
|||
'web_seed': this.web_seed.getValue()
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
setValue: function(value) {
|
||||
for (var proxy in value) {
|
||||
this[proxy].setValue(value[proxy]);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onProxyChange: function(field, newValue, oldValue) {
|
||||
var newValues = this.getValue();
|
||||
var oldValues = Ext.apply({}, newValues);
|
||||
oldValues[field.getName()] = oldValue;
|
||||
|
||||
|
||||
this.fireEvent('change', this, newValues, oldValues);
|
||||
}
|
||||
});
|
||||
|
@ -7039,7 +7045,7 @@ Ext.each(Deluge.Keys.Grid, function(key) {
|
|||
});
|
||||
/*!
|
||||
* Deluge.LoginWindow.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -7070,7 +7076,7 @@ Ext.each(Deluge.Keys.Grid, function(key) {
|
|||
*/
|
||||
|
||||
Deluge.LoginWindow = Ext.extend(Ext.Window, {
|
||||
|
||||
|
||||
firstShow: true,
|
||||
bodyStyle: 'padding: 10px 5px;',
|
||||
buttonAlign: 'center',
|
||||
|
@ -7084,36 +7090,39 @@ Deluge.LoginWindow = Ext.extend(Ext.Window, {
|
|||
title: _('Login'),
|
||||
width: 300,
|
||||
height: 120,
|
||||
|
||||
|
||||
initComponent: function() {
|
||||
Deluge.LoginWindow.superclass.initComponent.call(this);
|
||||
this.on('show', this.onShow, this);
|
||||
|
||||
|
||||
this.addButton({
|
||||
text: _('Login'),
|
||||
handler: this.onLogin,
|
||||
scope: this
|
||||
});
|
||||
|
||||
|
||||
this.form = this.add({
|
||||
xtype: 'form',
|
||||
baseCls: 'x-plain',
|
||||
labelWidth: 55,
|
||||
width: 300,
|
||||
defaults: {width: 200},
|
||||
labelWidth: 120,
|
||||
labelAlign: 'right',
|
||||
defaults: {width: 110},
|
||||
defaultType: 'textfield'
|
||||
});
|
||||
|
||||
this.passwordField = this.form.add({
|
||||
xtype: 'textfield',
|
||||
fieldLabel: _('Password'),
|
||||
grow: true,
|
||||
growMin: '110',
|
||||
growMax: '145',
|
||||
id: '_password',
|
||||
name: 'password',
|
||||
inputType: 'password'
|
||||
});
|
||||
this.passwordField.on('specialkey', this.onSpecialKey, this);
|
||||
},
|
||||
|
||||
|
||||
logout: function() {
|
||||
deluge.events.fire('logout');
|
||||
deluge.client.auth.delete_session({
|
||||
|
@ -7123,17 +7132,17 @@ Deluge.LoginWindow = Ext.extend(Ext.Window, {
|
|||
scope: this
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
show: function(skipCheck) {
|
||||
if (this.firstShow) {
|
||||
deluge.client.on('error', this.onClientError, this);
|
||||
this.firstShow = false;
|
||||
}
|
||||
|
||||
|
||||
if (skipCheck) {
|
||||
return Deluge.LoginWindow.superclass.show.call(this);
|
||||
}
|
||||
|
||||
|
||||
deluge.client.auth.check_session({
|
||||
success: function(result) {
|
||||
if (result) {
|
||||
|
@ -7148,11 +7157,11 @@ Deluge.LoginWindow = Ext.extend(Ext.Window, {
|
|||
scope: this
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
onSpecialKey: function(field, e) {
|
||||
if (e.getKey() == 13) this.onLogin();
|
||||
},
|
||||
|
||||
|
||||
onLogin: function() {
|
||||
var passwordField = this.passwordField;
|
||||
deluge.client.auth.login(passwordField.getValue(), {
|
||||
|
@ -7178,16 +7187,16 @@ Deluge.LoginWindow = Ext.extend(Ext.Window, {
|
|||
scope: this
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
onClientError: function(errorObj, response, requestOptions) {
|
||||
if (errorObj.error.code == 1) {
|
||||
deluge.events.fire('logout');
|
||||
this.show(true);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onShow: function() {
|
||||
this.passwordField.focus(true, 100);
|
||||
this.passwordField.focus(true, 300);
|
||||
}
|
||||
});
|
||||
/*!
|
||||
|
@ -9046,7 +9055,7 @@ Deluge.Toolbar = Ext.extend(Ext.Toolbar, {
|
|||
idProperty: 'id',
|
||||
fields: [
|
||||
{name: 'queue', sortType: Deluge.data.SortTypes.asQueuePosition},
|
||||
{name: 'name'},
|
||||
{name: 'name', sortType: Deluge.data.SortTypes.asName},
|
||||
{name: 'total_size', type: 'int'},
|
||||
{name: 'state'},
|
||||
{name: 'progress', type: 'float'},
|
||||
|
@ -9055,7 +9064,7 @@ Deluge.Toolbar = Ext.extend(Ext.Toolbar, {
|
|||
{name: 'num_peers', type: 'int'},
|
||||
{name: 'total_peers', type: 'int'},
|
||||
{name: 'download_payload_rate', type: 'int'},
|
||||
{name: 'upload_payload_speed', type: 'int'},
|
||||
{name: 'upload_payload_rate', type: 'int'},
|
||||
{name: 'eta', type: 'int', sortType: etaSorter},
|
||||
{name: 'ratio', type: 'float'},
|
||||
{name: 'distributed_copies', type: 'float'},
|
||||
|
|
|
@ -59,15 +59,18 @@ Deluge.LoginWindow = Ext.extend(Ext.Window, {
|
|||
this.form = this.add({
|
||||
xtype: 'form',
|
||||
baseCls: 'x-plain',
|
||||
labelWidth: 55,
|
||||
width: 300,
|
||||
defaults: {width: 200},
|
||||
labelWidth: 120,
|
||||
labelAlign: 'right',
|
||||
defaults: {width: 110},
|
||||
defaultType: 'textfield'
|
||||
});
|
||||
|
||||
this.passwordField = this.form.add({
|
||||
xtype: 'textfield',
|
||||
fieldLabel: _('Password'),
|
||||
grow: true,
|
||||
growMin: '110',
|
||||
growMax: '145',
|
||||
id: '_password',
|
||||
name: 'password',
|
||||
inputType: 'password'
|
||||
|
|
|
@ -265,7 +265,7 @@
|
|||
idProperty: 'id',
|
||||
fields: [
|
||||
{name: 'queue', sortType: Deluge.data.SortTypes.asQueuePosition},
|
||||
{name: 'name'},
|
||||
{name: 'name', sortType: Deluge.data.SortTypes.asName},
|
||||
{name: 'total_size', type: 'int'},
|
||||
{name: 'state'},
|
||||
{name: 'progress', type: 'float'},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Deluge.add.FilesTab.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -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,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Deluge.add.OptionsPanel.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -67,7 +67,7 @@ Deluge.add.OptionsTab = Ext.extend(Ext.form.FormPanel, {
|
|||
width: 400,
|
||||
labelSeparator: ''
|
||||
}));
|
||||
|
||||
|
||||
var panel = this.add({
|
||||
border: false,
|
||||
layout: 'column',
|
||||
|
@ -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,35 +107,32 @@ 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
|
||||
}));
|
||||
|
||||
|
||||
fieldset = panel.add({
|
||||
title: _('General'),
|
||||
border: false,
|
||||
|
|
|
@ -81,7 +81,7 @@ Deluge.add.UrlWindow = Ext.extend(Deluge.add.Window, {
|
|||
var cookies = this.cookieField.getValue();
|
||||
var torrentId = this.createTorrentId();
|
||||
|
||||
if (url.substring(0,20) == 'magnet:?xt=urn:btih:') {
|
||||
if (url.indexOf('magnet:?') == 0 && url.indexOf('xt=urn:btih') > -1) {
|
||||
deluge.client.web.get_magnet_info(url, {
|
||||
success: this.onGotInfo,
|
||||
scope: this,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Deluge.data.SortTypes.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -39,7 +39,7 @@ Ext.namespace('Deluge.data');
|
|||
*
|
||||
* @class Deluge.data.SortTypes
|
||||
* @singleton
|
||||
*/
|
||||
*/
|
||||
Deluge.data.SortTypes = {
|
||||
asIPAddress: function(value) {
|
||||
var d = value.match(/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\:(\d+)/);
|
||||
|
@ -48,5 +48,9 @@ Deluge.data.SortTypes = {
|
|||
|
||||
asQueuePosition: function(value) {
|
||||
return (value > -1) ? value : Number.MAX_VALUE;
|
||||
},
|
||||
|
||||
asName: function(value) {
|
||||
return String(value).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Deluge.data.TorrentRecord.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -47,7 +47,8 @@ Deluge.data.Torrent = Ext.data.Record.create([{
|
|||
type: 'int'
|
||||
}, {
|
||||
name: 'name',
|
||||
type: 'string'
|
||||
type: 'string',
|
||||
sortType: Deluge.data.SortTypes.asName
|
||||
}, {
|
||||
name: 'total_size',
|
||||
type: 'int'
|
||||
|
|
|
@ -36,7 +36,7 @@ Deluge.details.DetailsTab = Ext.extend(Ext.Panel, {
|
|||
title: _('Details'),
|
||||
|
||||
fields: {},
|
||||
|
||||
autoScroll: true,
|
||||
queuedItems: {},
|
||||
|
||||
oldData: {},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Deluge.details.FilesTab.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -29,12 +29,11 @@
|
|||
* this exception statement from your version. If you delete this exception
|
||||
* statement from all source files in the program, then also delete it here.
|
||||
*/
|
||||
|
||||
|
||||
Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
||||
|
||||
title: _('Files'),
|
||||
|
||||
autoScroll: true,
|
||||
rootVisible: false,
|
||||
|
||||
columns: [{
|
||||
|
@ -74,7 +73,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
}
|
||||
})
|
||||
}],
|
||||
|
||||
|
||||
selModel: new Ext.tree.MultiSelectionModel(),
|
||||
|
||||
initComponent: function() {
|
||||
|
@ -130,7 +129,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
this.clear();
|
||||
this.torrentId = torrentId;
|
||||
}
|
||||
|
||||
|
||||
deluge.client.web.get_torrent_files(torrentId, {
|
||||
success: this.onRequestComplete,
|
||||
scope: this,
|
||||
|
@ -163,7 +162,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
folderSort: true
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
onContextMenu: function(node, e) {
|
||||
e.stopEvent();
|
||||
var selModel = this.getSelectionModel();
|
||||
|
@ -173,7 +172,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
}
|
||||
deluge.menus.filePriorities.showAt(e.getPoint());
|
||||
},
|
||||
|
||||
|
||||
onItemClick: function(baseItem, e) {
|
||||
switch (baseItem.id) {
|
||||
case 'expandAll':
|
||||
|
@ -200,7 +199,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var priorities = new Array(Ext.keys(indexes).length);
|
||||
for (var index in indexes) {
|
||||
priorities[index] = indexes[index];
|
||||
|
@ -217,7 +216,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
|
|||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onRequestComplete: function(files, options) {
|
||||
if (!this.getRootNode().hasChildNodes()) {
|
||||
this.createFileTree(files);
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
if (!value.replace(' ', '').replace(' ', '')){
|
||||
return '';
|
||||
}
|
||||
return String.format('<img src="flag/{0}" />', value);
|
||||
return String.format('<img src="{0}flag/{1}" />', deluge.config.base, value);
|
||||
}
|
||||
function peerAddressRenderer(value, p, record) {
|
||||
var seed = (record.data['seed'] == 1024) ? 'x-deluge-seed' : 'x-deluge-peer';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Deluge.preferences.ProxyPage.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -40,11 +40,12 @@ Deluge.preferences.Proxy = Ext.extend(Ext.form.FormPanel, {
|
|||
config = Ext.apply({
|
||||
border: false,
|
||||
title: _('Proxy'),
|
||||
layout: 'form'
|
||||
layout: 'form',
|
||||
autoScroll: true
|
||||
}, config);
|
||||
Deluge.preferences.Proxy.superclass.constructor.call(this, config);
|
||||
},
|
||||
|
||||
|
||||
initComponent: function() {
|
||||
Deluge.preferences.Proxy.superclass.initComponent.call(this);
|
||||
this.peer = this.add(new Deluge.preferences.ProxyField({
|
||||
|
@ -52,28 +53,28 @@ Deluge.preferences.Proxy = Ext.extend(Ext.form.FormPanel, {
|
|||
name: 'peer'
|
||||
}));
|
||||
this.peer.on('change', this.onProxyChange, this);
|
||||
|
||||
|
||||
this.web_seed = this.add(new Deluge.preferences.ProxyField({
|
||||
title: _('Web Seed'),
|
||||
name: 'web_seed'
|
||||
}));
|
||||
this.web_seed.on('change', this.onProxyChange, this);
|
||||
|
||||
|
||||
this.tracker = this.add(new Deluge.preferences.ProxyField({
|
||||
title: _('Tracker'),
|
||||
name: 'tracker'
|
||||
}));
|
||||
this.tracker.on('change', this.onProxyChange, this);
|
||||
|
||||
|
||||
this.dht = this.add(new Deluge.preferences.ProxyField({
|
||||
title: _('DHT'),
|
||||
name: 'dht'
|
||||
}));
|
||||
this.dht.on('change', this.onProxyChange, this);
|
||||
|
||||
|
||||
deluge.preferences.getOptionsManager().bind('proxies', this);
|
||||
},
|
||||
|
||||
|
||||
getValue: function() {
|
||||
return {
|
||||
'dht': this.dht.getValue(),
|
||||
|
@ -82,18 +83,18 @@ Deluge.preferences.Proxy = Ext.extend(Ext.form.FormPanel, {
|
|||
'web_seed': this.web_seed.getValue()
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
setValue: function(value) {
|
||||
for (var proxy in value) {
|
||||
this[proxy].setValue(value[proxy]);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onProxyChange: function(field, newValue, oldValue) {
|
||||
var newValues = this.getValue();
|
||||
var oldValues = Ext.apply({}, newValues);
|
||||
oldValues[field.getName()] = oldValue;
|
||||
|
||||
|
||||
this.fireEvent('change', this, newValues, oldValues);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1543,7 +1543,7 @@ Ext.override(Ext.ux.form.SpinnerField, {
|
|||
});
|
||||
/*!
|
||||
* Ext.ux.form.SpinnerGroup.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -1581,6 +1581,7 @@ Ext.ux.form.SpinnerGroup = Ext.extend(Ext.form.CheckboxGroup, {
|
|||
|
||||
// private
|
||||
defaultType: 'spinnerfield',
|
||||
anchor: '98%',
|
||||
|
||||
// private
|
||||
groupCls: 'x-form-spinner-group',
|
||||
|
@ -1697,6 +1698,7 @@ Ext.ux.form.SpinnerGroup = Ext.extend(Ext.form.CheckboxGroup, {
|
|||
|
||||
this.items.each(function(field) {
|
||||
field.on('spin', this.onFieldChange, this);
|
||||
field.on('change', this.onFieldChange, this);
|
||||
}, this);
|
||||
|
||||
if (this.lazyValueSet) {
|
||||
|
|
|
@ -191,7 +191,7 @@ Ext.ns("Ext.ux.form");Ext.ux.form.SpinnerField=Ext.extend(Ext.form.NumberField,{
|
|||
Ext.override(Ext.ux.form.SpinnerField,{onBlur:Ext.form.Field.prototype.onBlur});
|
||||
/*
|
||||
* Ext.ux.form.SpinnerGroup.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -220,7 +220,7 @@ Ext.override(Ext.ux.form.SpinnerField,{onBlur:Ext.form.Field.prototype.onBlur});
|
|||
* this exception statement from your version. If you delete this exception
|
||||
* statement from all source files in the program, then also delete it here.
|
||||
*/
|
||||
Ext.ns("Ext.ux.form");Ext.ux.form.SpinnerGroup=Ext.extend(Ext.form.CheckboxGroup,{defaultType:"spinnerfield",groupCls:"x-form-spinner-group",colCfg:{},onRender:function(h,f){if(!this.el){var o={cls:this.groupCls,layout:"column",border:false,renderTo:h};var a=Ext.apply({defaultType:this.defaultType,layout:"form",border:false,labelWidth:60,defaults:{hideLabel:true,anchor:"60%"}},this.colCfg);if(this.items[0].items){Ext.apply(o,{layoutConfig:{columns:this.items.length},defaults:this.defaults,items:this.items});for(var e=0,k=this.items.length;e<k;e++){Ext.applyIf(this.items[e],a)}}else{var d,m=[];if(typeof this.columns=="string"){this.columns=this.items.length}if(!Ext.isArray(this.columns)){var j=[];for(var e=0;e<this.columns;e++){j.push((100/this.columns)*0.01)}this.columns=j}d=this.columns.length;for(var e=0;e<d;e++){var b=Ext.apply({items:[]},a);b[this.columns[e]<=1?"columnWidth":"width"]=this.columns[e];if(this.defaults){b.defaults=Ext.apply(b.defaults||{},this.defaults)}m.push(b)}if(this.vertical){var q=Math.ceil(this.items.length/d),n=0;for(var e=0,k=this.items.length;e<k;e++){if(e>0&&e%q==0){n++}if(this.items[e].fieldLabel){this.items[e].hideLabel=false}m[n].items.push(this.items[e])}}else{for(var e=0,k=this.items.length;e<k;e++){var p=e%d;if(this.items[e].fieldLabel){this.items[e].hideLabel=false}m[p].items.push(this.items[e])}}Ext.apply(o,{layoutConfig:{columns:d},items:m})}this.panel=new Ext.Panel(o);this.el=this.panel.getEl();if(this.forId&&this.itemCls){var c=this.el.up(this.itemCls).child("label",true);if(c){c.setAttribute("htmlFor",this.forId)}}var g=this.panel.findBy(function(l){return l.isFormField},this);this.items=new Ext.util.MixedCollection();this.items.addAll(g);this.items.each(function(l){l.on("spin",this.onFieldChange,this)},this);if(this.lazyValueSet){this.setValue(this.value);delete this.value;delete this.lazyValueSet}if(this.lazyRawValueSet){this.setRawValue(this.rawValue);delete this.rawValue;delete this.lazyRawValueSet}}Ext.ux.form.SpinnerGroup.superclass.onRender.call(this,h,f)},onFieldChange:function(a){this.fireEvent("change",this,this.getValue())},initValue:Ext.emptyFn,getValue:function(){var a=[this.items.getCount()];this.items.each(function(c,b){a[b]=Number(c.getValue())});return a},getRawValue:function(){var a=[this.items.getCount()];this.items.each(function(c,b){a[b]=Number(c.getRawValue())});return a},setValue:function(a){if(!this.rendered){this.value=a;this.lazyValueSet=true}else{this.items.each(function(c,b){c.setValue(a[b])})}},setRawValue:function(a){if(!this.rendered){this.rawValue=a;this.lazyRawValueSet=true}else{this.items.each(function(c,b){c.setRawValue(a[b])})}}});Ext.reg("spinnergroup",Ext.ux.form.SpinnerGroup);
|
||||
Ext.ns("Ext.ux.form");Ext.ux.form.SpinnerGroup=Ext.extend(Ext.form.CheckboxGroup,{defaultType:"spinnerfield",anchor:"98%",groupCls:"x-form-spinner-group",colCfg:{},onRender:function(h,f){if(!this.el){var o={cls:this.groupCls,layout:"column",border:false,renderTo:h};var a=Ext.apply({defaultType:this.defaultType,layout:"form",border:false,labelWidth:60,defaults:{hideLabel:true,anchor:"60%"}},this.colCfg);if(this.items[0].items){Ext.apply(o,{layoutConfig:{columns:this.items.length},defaults:this.defaults,items:this.items});for(var e=0,k=this.items.length;e<k;e++){Ext.applyIf(this.items[e],a)}}else{var d,m=[];if(typeof this.columns=="string"){this.columns=this.items.length}if(!Ext.isArray(this.columns)){var j=[];for(var e=0;e<this.columns;e++){j.push((100/this.columns)*0.01)}this.columns=j}d=this.columns.length;for(var e=0;e<d;e++){var b=Ext.apply({items:[]},a);b[this.columns[e]<=1?"columnWidth":"width"]=this.columns[e];if(this.defaults){b.defaults=Ext.apply(b.defaults||{},this.defaults)}m.push(b)}if(this.vertical){var q=Math.ceil(this.items.length/d),n=0;for(var e=0,k=this.items.length;e<k;e++){if(e>0&&e%q==0){n++}if(this.items[e].fieldLabel){this.items[e].hideLabel=false}m[n].items.push(this.items[e])}}else{for(var e=0,k=this.items.length;e<k;e++){var p=e%d;if(this.items[e].fieldLabel){this.items[e].hideLabel=false}m[p].items.push(this.items[e])}}Ext.apply(o,{layoutConfig:{columns:d},items:m})}this.panel=new Ext.Panel(o);this.el=this.panel.getEl();if(this.forId&&this.itemCls){var c=this.el.up(this.itemCls).child("label",true);if(c){c.setAttribute("htmlFor",this.forId)}}var g=this.panel.findBy(function(l){return l.isFormField},this);this.items=new Ext.util.MixedCollection();this.items.addAll(g);this.items.each(function(l){l.on("spin",this.onFieldChange,this);l.on("change",this.onFieldChange,this)},this);if(this.lazyValueSet){this.setValue(this.value);delete this.value;delete this.lazyValueSet}if(this.lazyRawValueSet){this.setRawValue(this.rawValue);delete this.rawValue;delete this.lazyRawValueSet}}Ext.ux.form.SpinnerGroup.superclass.onRender.call(this,h,f)},onFieldChange:function(a){this.fireEvent("change",this,this.getValue())},initValue:Ext.emptyFn,getValue:function(){var a=[this.items.getCount()];this.items.each(function(c,b){a[b]=Number(c.getValue())});return a},getRawValue:function(){var a=[this.items.getCount()];this.items.each(function(c,b){a[b]=Number(c.getRawValue())});return a},setValue:function(a){if(!this.rendered){this.value=a;this.lazyValueSet=true}else{this.items.each(function(c,b){c.setValue(a[b])})}},setRawValue:function(a){if(!this.rendered){this.rawValue=a;this.lazyRawValueSet=true}else{this.items.each(function(c,b){c.setRawValue(a[b])})}}});Ext.reg("spinnergroup",Ext.ux.form.SpinnerGroup);
|
||||
/*
|
||||
* Ext.ux.form.ToggleField.js
|
||||
*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
* Ext.ux.form.SpinnerGroup.js
|
||||
*
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -38,6 +38,7 @@ Ext.ux.form.SpinnerGroup = Ext.extend(Ext.form.CheckboxGroup, {
|
|||
|
||||
// private
|
||||
defaultType: 'spinnerfield',
|
||||
anchor: '98%',
|
||||
|
||||
// private
|
||||
groupCls: 'x-form-spinner-group',
|
||||
|
@ -154,6 +155,7 @@ Ext.ux.form.SpinnerGroup = Ext.extend(Ext.form.CheckboxGroup, {
|
|||
|
||||
this.items.each(function(field) {
|
||||
field.on('spin', this.onFieldChange, this);
|
||||
field.on('change', this.onFieldChange, this);
|
||||
}, this);
|
||||
|
||||
if (this.lazyValueSet) {
|
||||
|
|
|
@ -722,24 +722,29 @@ class WebApi(JSONComponent):
|
|||
|
||||
:rtype: dictionary
|
||||
"""
|
||||
try:
|
||||
s = uri.split("&")[0][20:]
|
||||
if len(s) == 32:
|
||||
info_hash = base64.b32decode(s).encode("hex")
|
||||
elif len(s) == 40:
|
||||
info_hash = s
|
||||
else:
|
||||
return False
|
||||
magnet_scheme = 'magnet:?'
|
||||
xt_param = 'xt=urn:btih:'
|
||||
dn_param = 'dn='
|
||||
if uri.startswith(magnet_scheme):
|
||||
name = None
|
||||
for i in uri.split("&")[1:]:
|
||||
if i[:3] == "dn=":
|
||||
name = unquote_plus(i.split("=")[1])
|
||||
if not name:
|
||||
name = info_hash
|
||||
return {"name":name, "info_hash":info_hash, "files_tree":''}
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
return False
|
||||
info_hash = None
|
||||
for param in uri[len(magnet_scheme):].split('&'):
|
||||
if param.startswith(xt_param):
|
||||
xt_hash = param[len(xt_param):]
|
||||
if len(xt_hash) == 32:
|
||||
info_hash = base64.b32decode(xt_hash).encode("hex")
|
||||
elif len(xt_hash) == 40:
|
||||
info_hash = xt_hash
|
||||
else:
|
||||
break
|
||||
elif param.startswith(dn_param):
|
||||
name = unquote_plus(param[len(dn_param):])
|
||||
|
||||
if info_hash:
|
||||
if not name:
|
||||
name = info_hash
|
||||
return {"name":name, "info_hash":info_hash, "files_tree":''}
|
||||
return False
|
||||
|
||||
@export
|
||||
def add_torrents(self, torrents):
|
||||
|
@ -754,7 +759,7 @@ class WebApi(JSONComponent):
|
|||
|
||||
>>> json_api.web.add_torrents([{
|
||||
"path": "/tmp/deluge-web/some-torrent-file.torrent",
|
||||
"options": {"download_path": "/home/deluge/"}
|
||||
"options": {"download_location": "/home/deluge/"}
|
||||
}])
|
||||
|
||||
"""
|
||||
|
|
Before Width: | Height: | Size: 865 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 350 B |
Before Width: | Height: | Size: 333 B |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 995 B |
Before Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 462 B |
Before Width: | Height: | Size: 992 B |
Before Width: | Height: | Size: 833 B |
Before Width: | Height: | Size: 817 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 817 B |
Before Width: | Height: | Size: 829 B |
Before Width: | Height: | Size: 842 B |
Before Width: | Height: | Size: 842 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 842 B |
Before Width: | Height: | Size: 843 B |
Before Width: | Height: | Size: 829 B |
Before Width: | Height: | Size: 888 B |
Before Width: | Height: | Size: 846 B |
Before Width: | Height: | Size: 872 B |
Before Width: | Height: | Size: 880 B |
Before Width: | Height: | Size: 844 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 43 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 839 B |
Before Width: | Height: | Size: 854 B |