Compare commits

...

31 Commits

Author SHA1 Message Date
Andrew Resch
90e4de54e9 Do not include unnecessary dlls in windows build 2010-09-18 16:03:21 -07:00
Andrew Resch
c1505bea3a Update versions 2010-09-18 11:31:31 -07:00
John Garland
6235e832fe include missing theme images 2010-09-18 00:48:22 +10:00
Damien Churchill
a71f14c47e include the .order files 2010-09-16 09:23:35 -07:00
Damien Churchill
ed3b23b0fc add all the other scripts to package_data 2010-09-16 09:23:19 -07:00
Andrew Resch
6402634ec1 Update win32 build files 2010-09-14 11:48:05 -07:00
Andrew Resch
3e68733cfd More clean-up of setup.py 2010-09-14 11:40:41 -07:00
Andrew Resch
f847a7dc4e Remove the custom 'install' class and include_package_data 2010-09-14 11:40:34 -07:00
Andrew Resch
c7954c20eb Fix preference page index when removing a preference page 2010-09-13 18:22:08 -07:00
Andrew Resch
dc7ed11601 Update ChangeLog 2010-09-13 16:11:57 -07:00
Chase Sterling
d898def9ec Fix bugs with unicode torrents in AutoAdd plugin. 2010-09-13 02:22:18 -04:00
Chase Sterling
3e2f6c4060 Fix bug in AutoAdd plugin where watchdirs would not display in gtkui when first enabled. 2010-09-13 02:22:17 -04:00
John Garland
321a22a6f0 Increase max piece size to 8 MiB in create torrent dialog (closes #1358) 2010-09-13 08:53:19 +10:00
Chase Sterling
b4774af2f3 Fix VersionSplit behavior when comparing to a dev version. 2010-09-11 05:39:40 -04:00
Chase Sterling
d0fd709c74 AutoAdd plugin can now recover when one of the watchfolders has an unhandled exception. 2010-09-03 22:30:24 -04:00
Chase Sterling
e24212b3f8 Fix "adjustment with non-zero page size" deprication warning in autoadd plugin. 2010-09-03 22:28:41 -04:00
Andrew Resch
f8f72af6dc Add TorrentFileCompleted event. 2010-09-03 17:11:57 -07:00
Andrew Resch
b9caa4eeeb Fix issue when adding torrents without a 'session'. This can happen
when a plugin adds a torrent, like how the AutoAdd plugin works.  The
user that adds this torrent will be an empty string.
2010-09-03 14:29:36 -07:00
John Garland
6c3b216b40 Use a temp filename with add_torrent_url 2010-08-31 00:11:58 +10:00
John Garland
eaad867885 Update get_free_space test 2010-08-31 00:11:54 +10:00
Chase Sterling
f6b9f67df8 Fix error in last commit. 2010-08-26 02:33:24 -04:00
Chase Sterling
24fe3f7fd5 Ensure preferencesmanager only changes intended libtorrent session settings. 2010-08-26 02:23:40 -04:00
Chase Sterling
da2fb41a3a Fix scheduler so that it keeps current state, even after global settings change. 2010-08-26 01:39:40 -04:00
Chase Sterling
f8d7f22167 Ignore global stop ratio related settings in logic, so per torrent ones are used. 2010-08-24 22:47:24 -04:00
Chase Sterling
b75abc70e5 Add max active downloading and seeding options to scheduler. 2010-08-24 00:58:28 -04:00
Andrew Resch
2d821bd79a Merge branch '1.3-stable' of deluge-torrent.org:deluge into 1.3-stable 2010-08-23 17:35:31 -07:00
Andrew Resch
12d9a7a5bd Fix key error after enabling a plugin that introduces a new status key 2010-08-23 17:35:19 -07:00
Chase Sterling
c118fa36a9 Moved xdg import so it is not called on Windows, where it is unused. fixes #1343 2010-08-22 15:38:22 -04:00
Chase Sterling
82c91cdc51 AutoAdd plugin changes
adds queue to top option
adds ability to append extension instead of deleting torrent once added
2010-08-22 00:01:58 -04:00
Andrew Resch
5501094214 Fix unhandled exception when adding a torrent to the session 2010-08-21 12:54:19 -07:00
Andrew Resch
b41aa808be Fix issue where the save_timer is cancelled when it's not active 2010-08-21 12:54:13 -07:00
25 changed files with 924 additions and 536 deletions

View File

@@ -1,3 +1,21 @@
=== Deluge 1.3.0 (18 September 2010) ===
* Fix issue where the save_timer is cancelled when it's not active
* Fix unhandled exception when adding a torrent to the session
* Moved xdg import so it is not called on Windows, where it is unused. fixes #1343
* Fix key error after enabling a plugin that introduces a new status key
* Add max active downloading and seeding options to scheduler.
* Ignore global stop ratio related settings in logic, so per torrent ones are used.
* Fix scheduler so that it keeps current state, even after global settings change.
* Ensure preferencesmanager only changes intended libtorrent session settings.
* Fix issue when adding torrents without a 'session'. This can happen when
a plugin adds a torrent, like how the AutoAdd plugin works. The user that
adds this torrent will be an empty string.
* Add TorrentFileCompleted event
* AutoAdd plugin can now recover when one of the watchfolders has an unhandled exception.
* Increase max piece size to 8 MiB in create torrent dialog (closes #1358)
* Fix bug in AutoAdd plugin where watchdirs would not display in gtkui when first enabled.
* Fix bugs with unicode torrents in AutoAdd plugin.
=== Deluge 1.3.0-rc2 (20 August 2010) ===
==== Core ====
* Fix tracker_icons failing on windows

View File

@@ -63,7 +63,6 @@ if not hasattr(json, "dumps"):
json.load = load
import pkg_resources
import xdg, xdg.BaseDirectory
import gettext
import locale
@@ -150,6 +149,7 @@ def get_default_config_dir(filename=None):
else:
return os.path.join(os.environ.get("APPDATA"), "deluge")
else:
import xdg.BaseDirectory
if filename:
return os.path.join(xdg.BaseDirectory.save_config_path("deluge"), filename)
else:
@@ -570,13 +570,15 @@ class VersionSplit(object):
"""
def __init__(self, ver):
ver = ver.lower()
vs = ver.split("_") if "_" in ver else ver.split("-")
vs = ver.replace("_", "-").split("-")
self.version = [int(x) for x in vs[0].split(".")]
self.suffix = None
self.dev = False
if len(vs) > 1:
for s in ("rc", "alpha", "beta", "dev"):
if s in vs[1][:len(s)]:
self.suffix = vs[1]
if vs[1].startswith(("rc", "alpha", "beta")):
self.suffix = vs[1]
if vs[-1] == 'dev':
self.dev = True
def __cmp__(self, ver):
"""
@@ -587,19 +589,8 @@ class VersionSplit(object):
"""
if self.version > ver.version or (self.suffix and self.suffix[:3] == "dev"):
return 1
if self.version < ver.version:
return -1
if self.version == ver.version:
if self.suffix == ver.suffix:
return 0
if self.suffix is None:
return 1
if ver.suffix is None:
return -1
if self.suffix < ver.suffix:
return -1
if self.suffix > ver.suffix:
return 1
# If there is no suffix we use z because we want final
# to appear after alpha, beta, and rc alphabetically.
v1 = [self.version, self.suffix or 'z', self.dev]
v2 = [ver.version, ver.suffix or 'z', ver.dev]
return cmp(v1, v2)

View File

@@ -398,7 +398,7 @@ what is currently in the config and it could not convert the value
loaded_data = json.loads(data[start:end])
if self.__config == loaded_data and self.__version == version:
# The config has not changed so lets just return
if self._save_timer:
if self._save_timer and self._save_timer.active():
self._save_timer.cancel()
return
except IOError, e:

View File

@@ -42,6 +42,7 @@ import shutil
import threading
import pkg_resources
import warnings
import tempfile
from twisted.internet import reactor, defer
@@ -238,7 +239,13 @@ class Core(component.Component):
log.info("Attempting to add url %s", url)
def on_get_file(filename):
# We got the file, so add it to the session
data = open(filename, "rb").read()
f = open(filename, "rb")
data = f.read()
f.close()
try:
os.remove(filename)
except Exception, e:
log.warning("Couldn't remove temp file: %s", e)
return self.add_torrent_file(filename, base64.encodestring(data), options)
def on_get_file_error(failure):
@@ -247,7 +254,7 @@ class Core(component.Component):
log.error("Reason: %s", failure.getErrorMessage())
return failure
d = download_file(url, url.split("/")[-1], headers=headers)
d = download_file(url, tempfile.mkstemp()[1], headers=headers)
d.addCallback(on_get_file)
d.addErrback(on_get_file_error)
return d

View File

@@ -152,7 +152,6 @@ class PreferencesManager(component.Component):
def start(self):
self.core = component.get("Core")
self.session = component.get("Core").session
self.settings = component.get("Core").settings
# Register set functions in the Config
self.config.register_set_function("torrentfiles_location",
@@ -233,6 +232,11 @@ class PreferencesManager(component.Component):
self.new_release_timer.stop()
# Config set functions
def session_set_setting(self, key, value):
settings = self.session.settings()
setattr(settings, key, value)
self.session.set_settings(settings)
def _on_config_value_change(self, key, value):
component.get("EventManager").emit(ConfigValueChangedEvent(key, value))
@@ -274,8 +278,7 @@ class PreferencesManager(component.Component):
def _on_set_outgoing_ports(self, key, value):
if not self.config["random_outgoing_ports"]:
log.debug("outgoing port range set to %s-%s", value[0], value[1])
self.settings.outgoing_ports = value[0], value[1]
self.session.set_settings(self.settings)
self.session_set_setting("outgoing_ports", (value[0], value[1]))
def _on_set_random_outgoing_ports(self, key, value):
if value:
@@ -284,13 +287,11 @@ class PreferencesManager(component.Component):
def _on_set_peer_tos(self, key, value):
log.debug("setting peer_tos to: %s", value)
try:
self.settings.peer_tos = chr(int(value, 16))
self.session_set_setting("peer_tos", chr(int(value, 16)))
except ValueError, e:
log.debug("Invalid tos byte: %s", e)
return
self.session.set_settings(self.settings)
def _on_set_dht(self, key, value):
log.debug("dht value set to %s", value)
state_file = deluge.configmanager.get_config_dir("dht.state")
@@ -387,51 +388,39 @@ class PreferencesManager(component.Component):
self.session.set_max_half_open_connections(value)
def _on_set_max_connections_per_second(self, key, value):
self.settings.connection_speed = value
self.session.set_settings(self.settings)
self.session_set_setting("connection_speed", value)
def _on_ignore_limits_on_local_network(self, key, value):
self.settings.ignore_limits_on_local_network = value
self.session.set_settings(self.settings)
self.session_set_setting("ignore_limits_on_local_network", value)
def _on_set_share_ratio_limit(self, key, value):
log.debug("%s set to %s..", key, value)
self.settings.share_ratio_limit = value
self.session.set_settings(self.settings)
self.session_set_setting("share_ratio_limit", value)
def _on_set_seed_time_ratio_limit(self, key, value):
log.debug("%s set to %s..", key, value)
self.settings.seed_time_ratio_limit = value
self.session.set_settings(self.settings)
self.session_set_setting("seed_time_ratio_limit", value)
def _on_set_seed_time_limit(self, key, value):
log.debug("%s set to %s..", key, value)
# This value is stored in minutes in deluge, but libtorrent wants seconds
self.settings.seed_time_limit = int(value * 60)
self.session.set_settings(self.settings)
self.session_set_setting("seed_time_limit", int(value * 60))
def _on_set_max_active_downloading(self, key, value):
log.debug("%s set to %s..", key, value)
log.debug("active_downloads: %s", self.settings.active_downloads)
self.settings.active_downloads = value
self.session.set_settings(self.settings)
self.session_set_setting("active_downloads", value)
def _on_set_max_active_seeding(self, key, value):
log.debug("%s set to %s..", key, value)
log.debug("active_seeds: %s", self.settings.active_seeds)
self.settings.active_seeds = value
self.session.set_settings(self.settings)
self.session_set_setting("active_seeds", value)
def _on_set_max_active_limit(self, key, value):
log.debug("%s set to %s..", key, value)
log.debug("active_limit: %s", self.settings.active_limit)
self.settings.active_limit = value
self.session.set_settings(self.settings)
self.session_set_setting("active_limit", value)
def _on_set_dont_count_slow_torrents(self, key, value):
log.debug("%s set to %s..", key, value)
self.settings.dont_count_slow_torrents = value
self.session.set_settings(self.settings)
self.session_set_setting("dont_count_slow_torrents", value)
def _on_send_info(self, key, value):
log.debug("Sending anonymous stats..")
@@ -491,8 +480,7 @@ class PreferencesManager(component.Component):
def _on_rate_limit_ip_overhead(self, key, value):
log.debug("%s: %s", key, value)
self.settings.rate_limit_ip_overhead = value
self.session.set_settings(self.settings)
self.session_set_setting("rate_limit_ip_overhead", value)
def _on_geoip_db_location(self, key, value):
log.debug("%s: %s", key, value)
@@ -514,10 +502,8 @@ class PreferencesManager(component.Component):
def _on_cache_size(self, key, value):
log.debug("%s: %s", key, value)
self.settings.cache_size = value
self.session.set_settings(self.settings)
self.session_set_setting("cache_size", value)
def _on_cache_expiry(self, key, value):
log.debug("%s: %s", key, value)
self.settings.cache_expiry = value
self.session.set_settings(self.settings)
self.session_set_setting("cache_expiry", value)

View File

@@ -437,7 +437,7 @@ class RPCServer(component.Component):
"""
session_id = self.get_session_id()
if session_id > -1:
if session_id > -1 and session_id in self.factory.authorized_sessions:
return self.factory.authorized_sessions[session_id][1]
else:
# No connections made yet

View File

@@ -393,12 +393,11 @@ class Torrent(object):
else:
status = self.status
if self.is_finished and (self.options["stop_at_ratio"] or self.config["stop_seed_at_ratio"]):
if self.is_finished and self.options["stop_at_ratio"]:
# We're a seed, so calculate the time to the 'stop_share_ratio'
if not status.upload_payload_rate:
return 0
stop_ratio = self.config["stop_seed_ratio"] if self.config["stop_seed_at_ratio"] else self.options["stop_ratio"]
stop_ratio = self.options["stop_ratio"]
return ((status.all_time_download * stop_ratio) - status.all_time_upload) / status.upload_payload_rate
left = status.total_wanted - status.total_done
@@ -761,13 +760,8 @@ class Torrent(object):
if self.handle.is_finished():
# If the torrent has already reached it's 'stop_seed_ratio' then do not do anything
if self.config["stop_seed_at_ratio"] or self.options["stop_at_ratio"]:
if self.options["stop_at_ratio"]:
ratio = self.options["stop_ratio"]
else:
ratio = self.config["stop_seed_ratio"]
if self.get_ratio() >= ratio:
if self.options["stop_at_ratio"]:
if self.get_ratio() >= self.options["stop_ratio"]:
#XXX: This should just be returned in the RPC Response, no event
#self.signals.emit_event("torrent_resume_at_stop_ratio")
return

View File

@@ -192,6 +192,8 @@ class TorrentManager(component.Component):
self.on_alert_metadata_received)
self.alerts.register_handler("file_error_alert",
self.on_alert_file_error)
self.alerts.register_handler("file_completed_alert",
self.on_alert_file_completed)
def start(self):
# Get the pluginmanager reference
@@ -256,16 +258,13 @@ class TorrentManager(component.Component):
def update(self):
for torrent_id, torrent in self.torrents.items():
if self.config["stop_seed_at_ratio"] or torrent.options["stop_at_ratio"] and torrent.state not in ("Checking", "Allocating", "Paused", "Queued"):
if torrent.options["stop_at_ratio"] and torrent.state not in ("Checking", "Allocating", "Paused", "Queued"):
# If the global setting is set, but the per-torrent isn't.. Just skip to the next torrent
# This is so that a user can turn-off the stop at ratio option on a per-torrent basis
if self.config["stop_seed_at_ratio"] and not torrent.options["stop_at_ratio"]:
if not torrent.options["stop_at_ratio"]:
continue
stop_ratio = self.config["stop_seed_ratio"]
if torrent.options["stop_at_ratio"]:
stop_ratio = torrent.options["stop_ratio"]
if torrent.get_ratio() >= stop_ratio and torrent.is_finished:
if self.config["remove_seed_at_ratio"] or torrent.options["remove_at_ratio"]:
if torrent.get_ratio() >= torrent.options["stop_ratio"] and torrent.is_finished:
if torrent.options["remove_at_ratio"]:
self.remove(torrent_id)
break
if not torrent.handle.is_paused():
@@ -1015,3 +1014,9 @@ class TorrentManager(component.Component):
except:
return
torrent.update_state()
def on_alert_file_completed(self, alert):
log.debug("file_completed_alert: %s", alert.message())
torrent_id = str(alert.handle.info_hash())
component.get("EventManager").emit(
TorrentFileCompletedEvent(torrent_id, alert.index))

View File

@@ -164,6 +164,22 @@ class TorrentResumedEvent(DelugeEvent):
"""
self._args = [torrent_id]
class TorrentFileCompletedEvent(DelugeEvent):
"""
Emitted when a file completes.
This will only work with libtorrent 0.15 or greater.
"""
def __init__(self, torrent_id, index):
"""
:param torrent_id: the torrent_id
:type torrent_id: string
:param index: the file index
:type index: int
"""
self._args = [torrent_id, index]
class NewVersionAvailableEvent(DelugeEvent):
"""
Emitted when a more recent version of Deluge is available.

View File

@@ -56,6 +56,7 @@ DEFAULT_PREFS = {
OPTIONS_AVAILABLE = { #option: builtin
"enabled":False,
"path":False,
"append_extension":False,
"abspath":False,
"download_location":True,
"max_download_speed":True,
@@ -70,15 +71,14 @@ OPTIONS_AVAILABLE = { #option: builtin
"move_completed":True,
"move_completed_path":True,
"label":False,
"add_paused":True
"add_paused":True,
"queue_to_top":False
}
MAX_NUM_ATTEMPTS = 10
class AutoaddOptionsChangedEvent(DelugeEvent):
"""
Emitted when a new command is added.
"""
"""Emitted when the options for the plugin are changed."""
def __init__(self):
pass
@@ -93,21 +93,20 @@ class Core(CorePluginBase):
self.config = deluge.configmanager.ConfigManager("autoadd.conf", DEFAULT_PREFS)
self.watchdirs = self.config["watchdirs"]
self.core_cfg = deluge.configmanager.ConfigManager("core.conf")
# A list of filenames
self.invalid_torrents = []
# Filename:Attempts
self.attempts = {}
# Dict of Filename:Attempts
self.invalid_torrents = {}
# Loopingcall timers for each enabled watchdir
self.update_timers = {}
# If core autoadd folder is enabled, move it to the plugin
if self.core_cfg.config.get('autoadd_enable'):
# Disable core autoadd
self.core_cfg['autoadd_enable'] = False
self.core_cfg.save()
# Check if core autoadd folder is already added in plugin
for watchdir in self.watchdirs:
if os.path.abspath(self.core_cfg['autoadd_location']) == watchdir['abspath']:
watchdir['enabled'] = True
break
else:
# didn't find core watchdir, add it
@@ -125,18 +124,15 @@ class Core(CorePluginBase):
for loopingcall in self.update_timers.itervalues():
loopingcall.stop()
self.config.save()
pass
def update(self):
pass
@export()
def set_options(self, watchdir_id, options):
"""
update the options for a watch folder
"""
"""Update the options for a watch folder."""
watchdir_id = str(watchdir_id)
options = self._clean_unicode(options)
options = self._make_unicode(options)
CheckInput(watchdir_id in self.watchdirs , _("Watch folder does not exist."))
if options.has_key('path'):
options['abspath'] = os.path.abspath(options['path'])
@@ -146,7 +142,7 @@ class Core(CorePluginBase):
raise Exception("Path is already being watched.")
for key in options.keys():
if not key in OPTIONS_AVAILABLE:
if not key in [key+'_toggle' for key in OPTIONS_AVAILABLE.iterkeys()]:
if not key in [key2+'_toggle' for key2 in OPTIONS_AVAILABLE.iterkeys()]:
raise Exception("autoadd: Invalid options key:%s" % key)
#disable the watch loop if it was active
if watchdir_id in self.update_timers:
@@ -177,33 +173,29 @@ class Core(CorePluginBase):
return filedump
def update_watchdir(self, watchdir_id):
"""Check the watch folder for new torrents to add."""
watchdir_id = str(watchdir_id)
watchdir = self.watchdirs[watchdir_id]
if not watchdir['enabled']:
# We shouldn't be updating because this watchdir is not enabled
#disable the looping call
self.update_timers[watchdir_id].stop()
del self.update_timers[watchdir_id]
self.disable_watchdir(watchdir_id)
return
# Check the auto add folder for new torrents to add
if not os.path.isdir(watchdir["abspath"]):
log.warning("Invalid AutoAdd folder: %s", watchdir["abspath"])
#disable the looping call
watchdir['enabled'] = False
self.update_timers[watchdir_id].stop()
del self.update_timers[watchdir_id]
self.disable_watchdir(watchdir_id)
return
#Generate options dict for watchdir
opts={}
if watchdir.get('stop_at_ratio_toggle'):
watchdir['stop_ratio_toggle'] = True
# Generate options dict for watchdir
opts = {}
if 'stop_at_ratio_toggle' in watchdir:
watchdir['stop_ratio_toggle'] = watchdir['stop_at_ratio_toggle']
# We default to True wher reading _toggle values, so a config
# without them is valid, and applies all its settings.
for option, value in watchdir.iteritems():
if OPTIONS_AVAILABLE.get(option):
if watchdir.get(option+'_toggle', True):
opts[option] = value
opts = self._clean_unicode(opts)
for filename in os.listdir(watchdir["abspath"]):
if filename.split(".")[-1] == "torrent":
try:
@@ -219,49 +211,73 @@ class Core(CorePluginBase):
# torrents may not be fully saved during the pass.
log.debug("Torrent is invalid: %s", e)
if filename in self.invalid_torrents:
self.attempts[filename] += 1
if self.attempts[filename] >= MAX_NUM_ATTEMPTS:
self.invalid_torrents[filename] += 1
if self.invalid_torrents[filename] >= MAX_NUM_ATTEMPTS:
os.rename(filepath, filepath + ".invalid")
del self.attempts[filename]
self.invalid_torrents.remove(filename)
del self.invalid_torrents[filename]
else:
self.invalid_torrents.append(filename)
self.attempts[filename] = 1
self.invalid_torrents[filename] = 1
continue
# The torrent looks good, so lets add it to the session
# The torrent looks good, so lets add it to the session.
torrent_id = component.get("TorrentManager").add(filedump=filedump, filename=filename, options=opts)
if ('Label' in component.get("CorePluginManager").get_enabled_plugins()) and torrent_id:
if watchdir.get('label_toggle', True) and watchdir.get('label'):
label = component.get("CorePlugin.Label")
if not watchdir['label'] in label.get_labels():
label.add(watchdir['label'])
label.set_torrent(torrent_id, watchdir['label'])
os.remove(filepath)
# If the torrent added successfully, set the extra options.
if torrent_id:
if 'Label' in component.get("CorePluginManager").get_enabled_plugins():
if watchdir.get('label_toggle', True) and watchdir.get('label'):
label = component.get("CorePlugin.Label")
if not watchdir['label'] in label.get_labels():
label.add(watchdir['label'])
label.set_torrent(torrent_id, watchdir['label'])
if watchdir.get('queue_to_top_toggle', True) and 'queue_to_top' in watchdir:
if watchdir['queue_to_top']:
component.get("TorrentManager").queue_top(torrent_id)
else:
component.get("TorrentManager").queue_bottom(torrent_id)
# Rename or delete the torrent once added to deluge.
if watchdir.get('append_extension_toggle'):
if not watchdir.get('append_extension'):
watchdir['append_extension'] = ".added"
os.rename(filepath, filepath + watchdir['append_extension'])
else:
os.remove(filepath)
def on_update_watchdir_error(self, failure, watchdir_id):
"""Disables any watch folders with unhandled exceptions."""
self.disable_watchdir(watchdir_id)
log.error("Disabling '%s', error during update: %s" % (self.watchdirs[watchdir_id]["path"], failure))
@export
def enable_watchdir(self, watchdir_id):
watchdir_id = str(watchdir_id)
self.watchdirs[watchdir_id]['enabled'] = True
#Enable the looping call
self.update_timers[watchdir_id] = LoopingCall(self.update_watchdir, watchdir_id)
self.update_timers[watchdir_id].start(5)
self.config.save()
component.get("EventManager").emit(AutoaddOptionsChangedEvent())
# Enable the looping call
if watchdir_id not in self.update_timers or not self.update_timers[watchdir_id].running:
self.update_timers[watchdir_id] = LoopingCall(self.update_watchdir, watchdir_id)
self.update_timers[watchdir_id].start(5).addErrback(self.on_update_watchdir_error, watchdir_id)
# Update the config
if not self.watchdirs[watchdir_id]['enabled']:
self.watchdirs[watchdir_id]['enabled'] = True
self.config.save()
component.get("EventManager").emit(AutoaddOptionsChangedEvent())
@export
def disable_watchdir(self, watchdir_id):
watchdir_id = str(watchdir_id)
self.watchdirs[watchdir_id]['enabled'] = False
#disable the looping call here
self.update_timers[watchdir_id].stop()
del self.update_timers[watchdir_id]
self.config.save()
component.get("EventManager").emit(AutoaddOptionsChangedEvent())
# Disable the looping call
if watchdir_id in self.update_timers:
if self.update_timers[watchdir_id].running:
self.update_timers[watchdir_id].stop()
del self.update_timers[watchdir_id]
# Update the config
if self.watchdirs[watchdir_id]['enabled']:
self.watchdirs[watchdir_id]['enabled'] = False
self.config.save()
component.get("EventManager").emit(AutoaddOptionsChangedEvent())
@export
def set_config(self, config):
"""sets the config dictionary"""
"""Sets the config dictionary."""
config = self._make_unicode(config)
for key in config.keys():
self.config[key] = config[key]
self.config.save()
@@ -269,35 +285,30 @@ class Core(CorePluginBase):
@export
def get_config(self):
"""returns the config dictionary"""
"""Returns the config dictionary."""
return self.config.config
@export()
def get_watchdirs(self):
return self.watchdirs.keys()
def _clean_unicode(self, options):
def _make_unicode(self, options):
opts = {}
for key, value in options.iteritems():
if isinstance(key, unicode):
key = str(key)
if isinstance(value, unicode):
value = str(value)
opts[key] = value
for key in options:
if isinstance(options[key], str):
options[key] = unicode(options[key], "utf8")
opts[key] = options[key]
return opts
#Labels:
@export()
def add(self, options={}):
"""add a watchdir
"""
options = self._clean_unicode(options)
"""Add a watch folder."""
options = self._make_unicode(options)
abswatchdir = os.path.abspath(options['path'])
CheckInput(os.path.isdir(abswatchdir) , _("Path does not exist."))
CheckInput(os.access(abswatchdir, os.R_OK|os.W_OK), "You must have read and write access to watch folder.")
for watchdir_id, watchdir in self.watchdirs.iteritems():
if watchdir['abspath'] == abswatchdir:
raise Exception("Path is already being watched.")
if abswatchdir in [wd['abspath'] for wd in self.watchdirs.itervalues()]:
raise Exception("Path is already being watched.")
options.setdefault('enabled', False)
options['abspath'] = abswatchdir
watchdir_id = self.config['next_id']
@@ -311,7 +322,7 @@ class Core(CorePluginBase):
@export
def remove(self, watchdir_id):
"""remove a label"""
"""Remove a watch folder."""
watchdir_id = str(watchdir_id)
CheckInput(watchdir_id in self.watchdirs, "Unknown Watchdir: %s" % self.watchdirs)
if self.watchdirs[watchdir_id]['enabled']:

View File

@@ -83,9 +83,11 @@
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkNotebook" id="notebook1">
<property name="visible">True</property>
@@ -94,6 +96,7 @@
<widget class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="border_width">6</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkFrame" id="frame2">
<property name="visible">True</property>
@@ -106,6 +109,7 @@
<child>
<widget class="GtkVBox" id="vbox6">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkHBox" id="hbox3">
<property name="visible">True</property>
@@ -170,6 +174,89 @@
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkFrame" id="frame1">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<widget class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="left_padding">12</property>
<child>
<widget class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<child>
<widget class="GtkVBox" id="vbox7">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkRadioButton" id="isnt_append_extension">
<property name="label" translatable="yes">Delete .torrent after adding</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<widget class="GtkRadioButton" id="append_extension_toggle">
<property name="label" translatable="yes">Append extension after adding:</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">isnt_append_extension</property>
<signal name="toggled" handler="on_toggle_toggled"/>
</widget>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="append_extension">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</property>
<property name="text" translatable="yes">.added</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Torrent File Action&lt;/b&gt;</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="type">label_item</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkFrame" id="frame3">
<property name="visible">True</property>
@@ -182,6 +269,7 @@
<child>
<widget class="GtkVBox" id="vbox3">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkCheckButton" id="download_location_toggle">
<property name="label" translatable="yes">Set download location</property>
@@ -244,7 +332,7 @@
</widget>
<packing>
<property name="fill">False</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
<child>
@@ -259,6 +347,7 @@
<child>
<widget class="GtkVBox" id="vbox4">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkCheckButton" id="move_completed_toggle">
<property name="label" translatable="yes">Set move completed location</property>
@@ -336,7 +425,7 @@
</widget>
<packing>
<property name="fill">False</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
<child>
@@ -392,7 +481,7 @@
</child>
</widget>
<packing>
<property name="position">3</property>
<property name="position">4</property>
</packing>
</child>
</widget>
@@ -411,6 +500,7 @@
<widget class="GtkVBox" id="vbox5">
<property name="visible">True</property>
<property name="border_width">6</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
@@ -484,7 +574,7 @@
<widget class="GtkSpinButton" id="max_download_speed">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">-1 -1 10000 1 10 10</property>
<property name="adjustment">-1 -1 10000 1 10 0</property>
<property name="climb_rate">1</property>
<property name="digits">1</property>
</widget>
@@ -498,7 +588,7 @@
<widget class="GtkSpinButton" id="max_upload_speed">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">-1 -1 10000 1 10 10</property>
<property name="adjustment">-1 -1 10000 1 10 0</property>
<property name="climb_rate">1</property>
<property name="digits">1</property>
</widget>
@@ -514,7 +604,7 @@
<widget class="GtkSpinButton" id="max_connections">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">-1 -1 10000 1 10 10</property>
<property name="adjustment">-1 -1 10000 1 10 0</property>
<property name="climb_rate">1</property>
</widget>
<packing>
@@ -529,7 +619,7 @@
<widget class="GtkSpinButton" id="max_upload_slots">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">-1 -1 10000 1 10 10</property>
<property name="adjustment">-1 -1 10000 1 10 0</property>
<property name="climb_rate">1</property>
</widget>
<packing>
@@ -640,14 +730,16 @@
</child>
</widget>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkAlignment" id="alignment14">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="left_padding">12</property>
<child>
<widget class="GtkCheckButton" id="remove_at_ratio">
@@ -661,8 +753,8 @@
</child>
</widget>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
@@ -676,8 +768,8 @@
<signal name="toggled" handler="on_toggle_toggled"/>
</widget>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
@@ -694,8 +786,8 @@
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
@@ -712,8 +804,8 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
@@ -723,21 +815,22 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="adjustment">2 0 100 0.10000000149 10 10</property>
<property name="adjustment">2 0 100 0.10000000149 10 0</property>
<property name="climb_rate">1</property>
<property name="digits">1</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="auto_managed_box">
<property name="visible">True</property>
<property name="homogeneous">True</property>
<child>
<widget class="GtkRadioButton" id="auto_managed">
<property name="label" translatable="yes">Yes</property>
@@ -768,8 +861,8 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
@@ -786,8 +879,8 @@
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
@@ -805,6 +898,7 @@
<child>
<widget class="GtkHBox" id="add_paused_box">
<property name="visible">True</property>
<property name="homogeneous">True</property>
<child>
<widget class="GtkRadioButton" id="add_paused">
<property name="label" translatable="yes">Yes</property>
@@ -824,7 +918,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">add_paused</property>
</widget>
@@ -839,10 +932,56 @@
</packing>
</child>
<child>
<placeholder/>
<widget class="GtkCheckButton" id="queue_to_top_toggle">
<property name="label" translatable="yes">Queue to:</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_toggle_toggled"/>
</widget>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<placeholder/>
<widget class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="homogeneous">True</property>
<child>
<widget class="GtkRadioButton" id="queue_to_top">
<property name="label" translatable="yes">Top</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkRadioButton" id="isnt_queue_to_top">
<property name="label" translatable="yes">Bottom</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">queue_to_top</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<placeholder/>
@@ -911,6 +1050,7 @@
<child>
<widget class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property>
<property name="orientation">vertical</property>
</widget>
<packing>
<property name="position">1</property>
@@ -943,7 +1083,7 @@
</child>
<child>
<widget class="GtkButton" id="opts_add_button">
<property name="label" translatable="no">gtk-add</property>
<property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
@@ -959,7 +1099,7 @@
</child>
<child>
<widget class="GtkButton" id="opts_apply_button">
<property name="label" translatable="no">gtk-apply</property>
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>

View File

@@ -51,11 +51,10 @@ from common import get_resource
class OptionsDialog():
spin_ids = ["max_download_speed", "max_upload_speed", "stop_ratio"]
spin_int_ids = ["max_upload_slots", "max_connections"]
chk_ids = ["stop_at_ratio", "remove_at_ratio", "move_completed", "add_paused", "auto_managed"]
chk_ids = ["stop_at_ratio", "remove_at_ratio", "move_completed", "add_paused", "auto_managed", "queue_to_top"]
def __init__(self):
pass
def show(self, options={}, watchdir_id=None):
self.glade = gtk.glade.XML(get_resource("autoadd_options.glade"))
self.glade.signal_autoconnect({
@@ -87,6 +86,8 @@ class OptionsDialog():
def load_options(self, options):
self.glade.get_widget('enabled').set_active(options.get('enabled', False))
self.glade.get_widget('append_extension_toggle').set_active(options.get('append_extension_toggle', False))
self.glade.get_widget('append_extension').set_text(options.get('append_extension', '.added'))
self.glade.get_widget('download_location_toggle').set_active(options.get('download_location_toggle', False))
self.glade.get_widget('label').set_text(options.get('label', ''))
self.glade.get_widget('label_toggle').set_active(options.get('label_toggle', False))
@@ -97,7 +98,9 @@ class OptionsDialog():
self.glade.get_widget(id).set_active(bool(options.get(id, True)))
self.glade.get_widget(id+'_toggle').set_active(options.get(id+'_toggle', False))
if not options.get('add_paused', True):
self.glade.get_widget('isnt_add_paused').set_active(True)
self.glade.get_widget('isnt_add_paused').set_active(True)
if not options.get('queue_to_top', True):
self.glade.get_widget('isnt_queue_to_top').set_active(True)
if not options.get('auto_managed', True):
self.glade.get_widget('isnt_auto_managed').set_active(True)
for field in ['move_completed_path', 'path', 'download_location']:
@@ -121,9 +124,9 @@ class OptionsDialog():
client.core.get_enabled_plugins().addCallback(on_get_enabled_plugins)
def set_sensitive(self):
maintoggles = ['download_location', 'move_completed', 'label', \
maintoggles = ['download_location', 'append_extension', 'move_completed', 'label', \
'max_download_speed', 'max_upload_speed', 'max_connections', \
'max_upload_slots', 'add_paused', 'auto_managed', 'stop_at_ratio']
'max_upload_slots', 'add_paused', 'auto_managed', 'stop_at_ratio', 'queue_to_top']
[self.on_toggle_toggled(self.glade.get_widget(x+'_toggle')) for x in maintoggles]
def on_toggle_toggled(self, tb):
@@ -132,6 +135,8 @@ class OptionsDialog():
if toggle == 'download_location':
self.glade.get_widget('download_location_chooser').set_sensitive(isactive)
self.glade.get_widget('download_location_entry').set_sensitive(isactive)
elif toggle == 'append_extension':
self.glade.get_widget('append_extension').set_sensitive(isactive)
elif toggle == 'move_completed':
self.glade.get_widget('move_completed_path_chooser').set_sensitive(isactive)
self.glade.get_widget('move_completed_path_entry').set_sensitive(isactive)
@@ -149,6 +154,9 @@ class OptionsDialog():
elif toggle == 'add_paused':
self.glade.get_widget('add_paused').set_sensitive(isactive)
self.glade.get_widget('isnt_add_paused').set_sensitive(isactive)
elif toggle == 'queue_to_top':
self.glade.get_widget('queue_to_top').set_sensitive(isactive)
self.glade.get_widget('isnt_queue_to_top').set_sensitive(isactive)
elif toggle == 'auto_managed':
self.glade.get_widget('auto_managed').set_sensitive(isactive)
self.glade.get_widget('isnt_auto_managed').set_sensitive(isactive)
@@ -193,10 +201,11 @@ class OptionsDialog():
options['path'] = self.glade.get_widget('path_entry').get_text()
options['download_location'] = self.glade.get_widget('download_location_entry').get_text()
options['move_completed_path'] = self.glade.get_widget('move_completed_path_entry').get_text()
options['append_extension_toggle'] = self.glade.get_widget('append_extension_toggle').get_active()
options['append_extension'] = self.glade.get_widget('append_extension').get_text()
options['download_location_toggle'] = self.glade.get_widget('download_location_toggle').get_active()
options['label'] = self.glade.get_widget('label').get_text().lower()
options['label_toggle'] = self.glade.get_widget('label_toggle').get_active()
for id in self.spin_ids:
options[id] = self.glade.get_widget(id).get_value()
@@ -245,6 +254,7 @@ class GtkUI(GtkPluginBase):
sw.add(self.treeView)
sw.show_all()
component.get("Preferences").add_page("AutoAdd", self.glade.get_widget("prefs_box"))
self.on_show_prefs()
def disable(self):
@@ -320,7 +330,6 @@ class GtkUI(GtkPluginBase):
client.autoadd.set_options(watchdir_id, watchdir)
def on_show_prefs(self):
client.autoadd.get_config().addCallback(self.cb_get_config)
def on_options_changed_event(self):

View File

@@ -42,7 +42,7 @@ from setuptools import setup
__plugin_name__ = "AutoAdd"
__author__ = "Chase Sterling"
__author_email__ = "chase.sterling@gmail.com"
__version__ = "0.28"
__version__ = "1.02"
__url__ = "http://forum.deluge-torrent.org/viewtopic.php?f=9&t=26775"
__license__ = "GPLv3"
__description__ = "Monitors folders for .torrent files."

View File

@@ -51,6 +51,8 @@ DEFAULT_PREFS = {
"low_down": -1.0,
"low_up": -1.0,
"low_active": -1,
"low_active_down": -1,
"low_active_up": -1,
"button_state": [[0] * 7 for dummy in xrange(24)]
}
@@ -60,6 +62,14 @@ STATES = {
2: "Red"
}
CONTROLLED_SETTINGS = [
"max_download_speed",
"max_download_speed",
"max_active_limit",
"max_active_downloading",
"max_active_seeding"
]
class SchedulerEvent(DelugeEvent):
"""
Emitted when a schedule state changes.
@@ -77,6 +87,8 @@ class Core(CorePluginBase):
DEFAULT_PREFS["low_down"] = core_config["max_download_speed"]
DEFAULT_PREFS["low_up"] = core_config["max_upload_speed"]
DEFAULT_PREFS["low_active"] = core_config["max_active_limit"]
DEFAULT_PREFS["low_active_down"] = core_config["max_active_downloading"]
DEFAULT_PREFS["low_active_up"] = core_config["max_active_seeding"]
self.config = deluge.configmanager.ConfigManager("scheduler.conf", DEFAULT_PREFS)
@@ -90,26 +102,32 @@ class Core(CorePluginBase):
secs_to_next_hour = ((60 - now[4]) * 60) + (60 - now[5])
self.timer = reactor.callLater(secs_to_next_hour, self.do_schedule)
# Register for config changes so state isn't overridden
component.get("EventManager").register_event_handler("ConfigValueChangedEvent", self.on_config_value_changed)
def disable(self):
try:
self.timer.cancel()
except:
pass
component.get("EventManager").deregister_event_handler("ConfigValueChangedEvent", self.on_config_value_changed)
self.__apply_set_functions()
def update(self):
pass
def on_config_value_changed(self, key, value):
if key in CONTROLLED_SETTINGS:
self.do_schedule(False)
def __apply_set_functions(self):
"""
Have the core apply it's bandwidth settings as specified in core.conf.
"""
core_config = deluge.configmanager.ConfigManager("core.conf")
core_config.apply_set_functions("max_download_speed")
core_config.apply_set_functions("max_upload_speed")
core_config.apply_set_functions("max_active_limit")
for setting in CONTROLLED_SETTINGS:
core_config.apply_set_functions(setting)
# Resume the session if necessary
component.get("Core").session.resume()
@@ -131,6 +149,8 @@ class Core(CorePluginBase):
session.set_upload_rate_limit(int(self.config["low_up"] * 1024))
settings = session.settings()
settings.active_limit = self.config["low_active"]
settings.active_downloads = self.config["low_active_down"]
settings.active_seeds = self.config["low_active_up"]
session.set_settings(settings)
# Resume the session if necessary
component.get("Core").session.resume()

View File

@@ -183,6 +183,8 @@ class GtkUI(GtkPluginBase):
config["low_down"] = self.spin_download.get_value()
config["low_up"] = self.spin_upload.get_value()
config["low_active"] = self.spin_active.get_value_as_int()
config["low_active_down"] = self.spin_active_down.get_value_as_int()
config["low_active_up"] = self.spin_active_up.get_value_as_int()
config["button_state"] = self.scheduler_select.button_state
client.scheduler.set_config(config)
@@ -193,6 +195,8 @@ class GtkUI(GtkPluginBase):
self.spin_download.set_value(config["low_down"])
self.spin_upload.set_value(config["low_up"])
self.spin_active.set_value(config["low_active"])
self.spin_active_down.set_value(config["low_active_down"])
self.spin_active_up.set_value(config["low_active_up"])
client.scheduler.get_config().addCallback(on_get_config)
@@ -229,7 +233,7 @@ class GtkUI(GtkPluginBase):
vbox.pack_start(frame, True, True)
vbox.pack_start(hover)
table = gtk.Table(3, 2)
table = gtk.Table(3, 4)
label = gtk.Label(_("Download Limit:"))
label.set_alignment(0.0, 0.6)
@@ -251,12 +255,30 @@ class GtkUI(GtkPluginBase):
label = gtk.Label(_("Active Torrents:"))
label.set_alignment(0.0, 0.6)
table.attach(label, 0, 1, 2, 3, gtk.FILL)
table.attach(label, 2, 3, 0, 1, gtk.FILL)
self.spin_active = gtk.SpinButton()
self.spin_active.set_numeric(True)
self.spin_active.set_range(-1, 9999)
self.spin_active.set_increments(1, 10)
table.attach(self.spin_active, 1, 2, 2, 3, gtk.FILL)
table.attach(self.spin_active, 3, 4, 0, 1, gtk.FILL)
label = gtk.Label(_("Active Downloading:"))
label.set_alignment(0.0, 0.6)
table.attach(label, 2, 3, 1, 2, gtk.FILL)
self.spin_active_down = gtk.SpinButton()
self.spin_active_down.set_numeric(True)
self.spin_active_down.set_range(-1, 9999)
self.spin_active_down.set_increments(1, 10)
table.attach(self.spin_active_down, 3, 4, 1, 2, gtk.FILL)
label = gtk.Label(_("Active Seeding:"))
label.set_alignment(0.0, 0.6)
table.attach(label, 2, 3, 2, 3, gtk.FILL)
self.spin_active_up = gtk.SpinButton()
self.spin_active_up.set_numeric(True)
self.spin_active_up.set_range(-1, 9999)
self.spin_active_up.set_increments(1, 10)
table.attach(self.spin_active_up, 3, 4, 2, 3, gtk.FILL)
eventbox = gtk.EventBox()
eventbox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#EDD400"))

View File

@@ -41,7 +41,7 @@ from setuptools import setup
__plugin_name__ = "Scheduler"
__author__ = "Andrew Resch"
__author_email__ = "andrewresch@gmail.com"
__version__ = "0.1"
__version__ = "0.2"
__url__ = "http://deluge-torrent.org"
__license__ = "GPLv3"
__description__ = "Schedule limits on a per-hour per-day basis."

View File

@@ -557,6 +557,7 @@
1 MiB
2 MiB
4 MiB
8 MiB
</property>
</widget>
<packing>

View File

@@ -173,6 +173,10 @@ class Preferences(component.Component):
if self.iter_to_remove != None:
self.liststore.remove(self.iter_to_remove)
# We need to re-adjust the index values for the remaining pages
for i, (index, name) in enumerate(self.liststore):
self.liststore[i][0] = i
def show(self, page=None):
"""Page should be the string in the left list.. ie, 'Network' or
'Bandwidth'"""

View File

@@ -200,7 +200,7 @@ class SessionProxy(component.Component):
else:
# We need to check if a key is expired
for key in keys:
if t - self.cache_times[torrent_id][key] > self.cache_time:
if t - self.cache_times[torrent_id].get(key, 0.0) > self.cache_time:
to_fetch.append(torrent_id)
break
@@ -241,9 +241,9 @@ class SessionProxy(component.Component):
def on_torrent_added(self, torrent_id):
self.torrents[torrent_id] = [time.time() - self.cache_time - 1, {}]
self.cache_times[torrent_id] = {}
def on_status(status):
self.torrents[torrent_id][1].update(status)
self.cache_times[torrent_id] = {}
t = time.time()
for key in status:
self.cache_times[torrent_id][key] = t

View File

@@ -35,7 +35,6 @@ from distutils import cmd, sysconfig
from distutils.command.build import build as _build
from distutils.command.build_ext import build_ext as _build_ext
from distutils.command.clean import clean as _clean
from setuptools.command.install import install as _install
try:
from sphinx.setup_command import BuildDoc
except ImportError:
@@ -387,14 +386,6 @@ class clean(_clean):
self.run_command(cmd_name)
_clean.run(self)
class install(_install):
def run(self):
for cmd_name in self.get_sub_commands():
self.run_command(cmd_name)
_install.run(self)
if not self.root:
self.do_egg_install()
cmdclass = {
'build': build,
'build_trans': build_trans,
@@ -404,7 +395,6 @@ cmdclass = {
'build_ext_debug': build_ext_debug,
'clean_plugins': clean_plugins,
'clean': clean,
'install': install
}
# Data files to be installed to the system
@@ -435,7 +425,7 @@ _data_files = [
# Main setup
setup(
name = "deluge",
version = "1.3.0-rc2",
version = "1.3.0",
fullname = "Deluge Bittorrent Client",
description = "Bittorrent Client",
author = "Andrew Resch, Damien Churchill",
@@ -452,14 +442,11 @@ setup(
data_files = _data_files,
ext_package = "deluge",
ext_modules = _ext_modules,
include_package_data = True,
package_data = {"deluge": ["ui/gtkui/glade/*.glade",
"data/pixmaps/*.png",
"data/pixmaps/*.svg",
"data/pixmaps/*.ico",
"data/pixmaps/flags/*.png",
"data/revision",
"data/GeoIP.dat",
"plugins/*.egg",
"i18n/*.pot",
"i18n/*/LC_MESSAGES/*.mo",
@@ -470,8 +457,16 @@ setup(
"ui/web/images/*.gif",
"ui/web/images/*.png",
"ui/web/js/*.js",
"ui/web/js/*/*.js",
"ui/web/js/*/.order",
"ui/web/js/*/*/*.js",
"ui/web/js/*/*/.order",
"ui/web/render/*.html",
"ui/web/themes/*/*/*/*"
"ui/web/themes/css/*.css",
"ui/web/themes/images/*/*.gif",
"ui/web/themes/images/*/*.png",
"ui/web/themes/images/*/*/*.gif",
"ui/web/themes/images/*/*/*.png"
]},
packages = find_packages(exclude=["plugins", "docs", "tests"]),
entry_points = """

View File

@@ -48,7 +48,11 @@ class CommonTestCase(unittest.TestCase):
self.failUnless(VersionSplit("1.2.1") < VersionSplit("1.2.2"))
self.failUnless(VersionSplit("1.1.9") < VersionSplit("1.2.2"))
self.failUnless(VersionSplit("1.2.2") > VersionSplit("1.2.1"))
self.failIf(VersionSplit("1.2.2") == VersionSplit("1.2.2-dev"))
self.failUnless(VersionSplit("1.2.2") < VersionSplit("1.2.2-dev"))
self.failUnless(VersionSplit("1.2.2-dev") < VersionSplit("1.3.0-rc2"))
self.failUnless(VersionSplit("1.2.2") > VersionSplit("1.2.2-rc2"))
self.failUnless(VersionSplit("1.2.2-rc2-dev") > VersionSplit("1.2.2-rc2"))
self.failUnless(VersionSplit("1.2.2-rc3") > VersionSplit("1.2.2-rc2"))
self.failUnless(VersionSplit("0.14.9") == VersionSplit("0.14.9"))
self.failUnless(VersionSplit("0.14.9") > VersionSplit("0.14.5"))
self.failUnless(VersionSplit("0.14.10") >= VersionSplit("0.14.9"))

View File

@@ -102,7 +102,7 @@ class CoreTestCase(unittest.TestCase):
space = self.core.get_free_space(".")
self.assertTrue(type(space) in (int, long))
self.assertTrue(space >= 0)
self.assertRaises(deluge.error.InvalidPathError, self.core.get_free_space, "/someinvalidpath")
self.assertEquals(self.core.get_free_space("/someinvalidpath"), 0)
def test_test_listen_port(self):
d = self.core.test_listen_port()

View File

@@ -1,19 +1,43 @@
build_version = "1.3.0-rc2"
python_path = "C:\\Python26\\"
import shutil
shutil.copy(python_path + "Scripts\deluge-script.py", python_path + "Scripts\deluge.py")
shutil.copy(python_path + "Scripts\deluged-script.py", python_path + "Scripts\deluged.py")
shutil.copy(python_path + "Scripts\deluge-web-script.py", python_path + "Scripts\deluge-web.py")
shutil.copy(python_path + "Scripts\deluge-gtk-script.py", python_path + "Scripts\deluge-gtk.py")
shutil.copy(python_path + "Scripts\deluge-console-script.py", python_path + "Scripts\deluge-console.py")
from bbfreeze import Freezer
f = Freezer("..\\build-win32\\deluge-bbfreeze-" + build_version, includes=("libtorrent", "gzip", "zipfile", "re", "socket", "struct", "cairo", "pangocairo", "atk", "pango", "wsgiref.handlers", "twisted.internet.utils", "gio", "gtk.glade"))
f.addScript(python_path + "Scripts\deluge.py", gui_only=False)
f.addScript(python_path + "Scripts\deluged.py", gui_only=False)
f.addScript(python_path + "Scripts\deluge-web.py", gui_only=False)
f.addScript(python_path + "Scripts\deluge-gtk.py", gui_only=False)
f.addScript(python_path + "Scripts\deluge-console.py", gui_only=False)
f() # starts the freezing process
build_version = "1.3.0"
python_path = "C:\\Python26\\"
import os, glob
import shutil
shutil.copy(python_path + "Scripts\deluge-script.py", python_path + "Scripts\deluge.py")
shutil.copy(python_path + "Scripts\deluge-script.py", python_path + "Scripts\deluge-debug.py")
shutil.copy(python_path + "Scripts\deluged-script.py", python_path + "Scripts\deluged.py")
shutil.copy(python_path + "Scripts\deluge-web-script.py", python_path + "Scripts\deluge-web.py")
shutil.copy(python_path + "Scripts\deluge-gtk-script.py", python_path + "Scripts\deluge-gtk.py")
shutil.copy(python_path + "Scripts\deluge-console-script.py", python_path + "Scripts\deluge-console.py")
includes=("libtorrent", "gzip", "zipfile", "re", "socket", "struct", "cairo", "pangocairo", "atk", "pango", "wsgiref.handlers", "twisted.internet.utils", "gio", "gtk.glade")
excludes=("numpy", "OpenGL", "psyco", "win32ui")
dst = "..\\build-win32\\deluge-bbfreeze-" + build_version + "\\"
from bbfreeze import Freezer
f = Freezer(dst, includes=includes, excludes=excludes)
f.include_py = False
f.addScript(python_path + "Scripts\deluge.py", gui_only=True)
f.addScript(python_path + "Scripts\deluge-debug.py", gui_only=False)
f.addScript(python_path + "Scripts\deluged.py", gui_only=False)
f.addScript(python_path + "Scripts\deluge-web.py", gui_only=False)
f.addScript(python_path + "Scripts\deluge-gtk.py", gui_only=True)
f.addScript(python_path + "Scripts\deluge-console.py", gui_only=False)
f() # starts the freezing process
# add icons to the exe files
import icon
icon.CopyIcons(dst+"deluge.exe", "deluge.ico")
icon.CopyIcons(dst+"deluge-debug.exe", "deluge.ico")
icon.CopyIcons(dst+"deluged.exe", "deluge.ico")
icon.CopyIcons(dst+"deluge-web.exe", "deluge.ico")
icon.CopyIcons(dst+"deluge-gtk.exe", "deluge.ico")
icon.CopyIcons(dst+"deluge-console.exe", "deluge.ico")
# exclude files which are already included in GTK or Windows
excludeFiles = ("MSIMG32.dll", "MSVCR90.dll", "MSVCP90.dll", "POWRPROF.dll", "freetype*.dll", "iconv.dll", "intl.dll", "libatk*.dll", "libcairo*.dll", "libexpat*.dll", "libfontconfig*.dll", "libfreetype*.dll", "libgio*.dll", "libpng*.dll", "libtiff*.dll", "zlib1.dll")
for file in excludeFiles:
for filename in glob.glob(dst + file):
print "removing file:", filename
os.remove(filename)

View File

@@ -1,303 +1,253 @@
# Deluge Windows installer script
# Version 0.4 28-Apr-2009
# Copyright (C) 2009 by
# Jesper Lund <mail@jesperlund.com>
# Andrew Resch <andrewresch@gmail.com>
# John Garland <johnnybg@gmail.com>
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# Set default compressor
SetCompressor lzma
###
### --- The PROGRAM_VERSION !define need to be updated with new Deluge versions ---
###
# Script version; displayed when running the installer
!define DELUGE_INSTALLER_VERSION "0.4"
# Deluge program information
!define PROGRAM_NAME "Deluge"
!define PROGRAM_VERSION "1.3.0-rc2"
!define PROGRAM_WEB_SITE "http://deluge-torrent.org"
# Python files generated with bbfreeze (without DLLs from GTK+ runtime)
!define DELUGE_PYTHON_BBFREEZE_OUTPUT_DIR "..\build-win32\deluge-bbfreeze-${PROGRAM_VERSION}"
# Installer for GTK+ 2.12 runtime; will be downloaded from deluge-torrent.org
!define DELUGE_GTK_DEPENDENCY "gtk2-runtime-2.16.6-2010-05-12-ash.exe"
# --- Interface settings ---
# Modern User Interface 2
!include MUI2.nsh
# Installer
!define MUI_ICON "deluge.ico"
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_RIGHT
!define MUI_HEADERIMAGE_BITMAP "installer-top.bmp"
!define MUI_WELCOMEFINISHPAGE_BITMAP "installer-side.bmp"
!define MUI_COMPONENTSPAGE_SMALLDESC
!define MUI_FINISHPAGE_NOAUTOCLOSE
!define MUI_ABORTWARNING
# Uninstaller
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
!define MUI_HEADERIMAGE_UNBITMAP "installer-top.bmp"
!define MUI_WELCOMEFINISHPAGE_UNBITMAP "installer-side.bmp"
!define MUI_UNFINISHPAGE_NOAUTOCLOSE
# --- Start of Modern User Interface ---
# Welcome page
!insertmacro MUI_PAGE_WELCOME
# License page
!insertmacro MUI_PAGE_LICENSE "..\LICENSE"
# Components page
!insertmacro MUI_PAGE_COMPONENTS
# Let the user select the installation directory
!insertmacro MUI_PAGE_DIRECTORY
# Run installation
!insertmacro MUI_PAGE_INSTFILES
# Display 'finished' page
!insertmacro MUI_PAGE_FINISH
# Uninstaller pages
!insertmacro MUI_UNPAGE_INSTFILES
# Language files
!insertmacro MUI_LANGUAGE "English"
# --- Functions ---
Function un.onUninstSuccess
HideWindow
MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) was successfully removed from your computer."
FunctionEnd
Function un.onInit
MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Do you want to completely remove $(^Name) and all of its components?" IDYES +2
Abort
FunctionEnd
# --- Installation sections ---
# Compare versions
!include "WordFunc.nsh"
!define PROGRAM_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROGRAM_NAME}"
!define PROGRAM_UNINST_ROOT_KEY "HKLM"
# Branding text
BrandingText "Deluge Windows Installer v${DELUGE_INSTALLER_VERSION}"
Name "${PROGRAM_NAME} ${PROGRAM_VERSION}"
OutFile "..\build-win32\deluge-${PROGRAM_VERSION}-win32-setup.exe"
# The Python bbfreeze files will be placed here
!define DELUGE_PYTHON_SUBDIR "$INSTDIR\Deluge-Python"
InstallDir "$PROGRAMFILES\Deluge"
ShowInstDetails show
ShowUnInstDetails show
# Install main application
Section "Deluge Bittorrent Client" Section1
SectionIn RO
Rmdir /r "${DELUGE_PYTHON_SUBDIR}"
SetOutPath "${DELUGE_PYTHON_SUBDIR}"
File /r "${DELUGE_PYTHON_BBFREEZE_OUTPUT_DIR}\*.*"
# Clean up previous confusion between Deluge.ico and deluge.ico (seems to matter on Vista registry settings?)
Delete "$INSTDIR\Deluge.ico"
SetOverwrite ifnewer
SetOutPath $INSTDIR
File "..\LICENSE"
File "StartX.exe"
File "deluge.ico"
# Create deluge.cmd file
fileOpen $0 "$INSTDIR\deluge.cmd" w
fileWrite $0 '@ECHO OFF$\r$\n'
fileWrite $0 'SET DELUGEFOLDER="$INSTDIR"$\r$\n'
fileWrite $0 'SET STARTX_APP="$INSTDIR\StartX.exe"$\r$\n'
fileWrite $0 '$\r$\n'
fileWrite $0 'IF ""%1"" == """" ( $\r$\n'
fileWrite $0 ' %STARTX_APP% /B /D%DELUGEFOLDER% "$INSTDIR\Deluge-Python\deluge.exe"$\r$\n'
fileWrite $0 ') ELSE ( $\r$\n'
fileWrite $0 ' %STARTX_APP% /B /D%DELUGEFOLDER% "$INSTDIR\Deluge-Python\deluge.exe "%1" "%2" "%3" "%4""$\r$\n'
fileWrite $0 ')$\r$\n'
fileClose $0
# Create deluged.cmd file
fileOpen $0 "$INSTDIR\deluged.cmd" w
fileWrite $0 '@ECHO OFF$\r$\n'
fileWrite $0 'SET DELUGEFOLDER="$INSTDIR"$\r$\n'
fileWrite $0 '"$INSTDIR\StartX.exe" /B /D%DELUGEFOLDER% "$INSTDIR\Deluge-Python\deluged.exe "%1" "%2" "%3" "%4""$\r$\n'
fileClose $0
# Create deluge-webui.cmd file
fileOpen $0 "$INSTDIR\deluge-webui.cmd" w
fileWrite $0 '@ECHO OFF$\r$\n'
fileWrite $0 'SET DELUGEFOLDER="$INSTDIR"$\r$\n'
fileWrite $0 '"$INSTDIR\StartX.exe" /B /D%DELUGEFOLDER% "$INSTDIR\Deluge-Python\deluge.exe --ui web"$\r$\n'
fileWrite $0 "ECHO Deluge WebUI started and is running at http://localhost:8112 by default$\r$\n"
fileWrite $0 "ECHO NOTE: The Deluge WebUI process can only be stopped in the Windows Task Manager$\r$\n"
fileWrite $0 "ECHO.$\r$\n"
fileWrite $0 PAUSE
fileClose $0
SectionEnd
Section -StartMenu_Desktop_Links
WriteIniStr "$INSTDIR\homepage.url" "InternetShortcut" "URL" "${PROGRAM_WEB_SITE}"
CreateDirectory "$SMPROGRAMS\Deluge"
CreateShortCut "$SMPROGRAMS\Deluge\Deluge.lnk" "$INSTDIR\deluge.cmd" "" "$INSTDIR\deluge.ico"
CreateShortCut "$SMPROGRAMS\Deluge\Deluge daemon.lnk" "$INSTDIR\deluged.cmd" "" "$INSTDIR\deluge.ico"
CreateShortCut "$SMPROGRAMS\Deluge\Deluge webUI.lnk" "$INSTDIR\deluge-webui.cmd" "" "$INSTDIR\deluge.ico"
CreateShortCut "$SMPROGRAMS\Deluge\Project homepage.lnk" "$INSTDIR\Homepage.url"
CreateShortCut "$SMPROGRAMS\Deluge\Uninstall Deluge.lnk" "$INSTDIR\Deluge-uninst.exe"
CreateShortCut "$DESKTOP\Deluge.lnk" "$INSTDIR\deluge.cmd" "" "$INSTDIR\deluge.ico"
SectionEnd
Section -Uninstaller
WriteUninstaller "$INSTDIR\Deluge-uninst.exe"
WriteRegStr ${PROGRAM_UNINST_ROOT_KEY} "${PROGRAM_UNINST_KEY}" "DisplayName" "$(^Name)"
WriteRegStr ${PROGRAM_UNINST_ROOT_KEY} "${PROGRAM_UNINST_KEY}" "UninstallString" "$INSTDIR\Deluge-uninst.exe"
WriteRegStr ${PROGRAM_UNINST_ROOT_KEY} "${PROGRAM_UNINST_KEY}" "DisplayIcon" "$INSTDIR\deluge.ico"
SectionEnd
# Create file association for .torrent
Section "Create .torrent file association for Deluge" Section2
# Set up file association for .torrent files
DeleteRegKey HKCR ".torrent"
WriteRegStr HKCR ".torrent" "" "Deluge"
WriteRegStr HKCR ".torrent" "Content Type" "application/x-bittorrent"
DeleteRegKey HKCR "Deluge"
WriteRegStr HKCR "Deluge" "" "Deluge"
WriteRegStr HKCR "Deluge\Content Type" "" "application/x-bittorrent"
WriteRegStr HKCR "Deluge\DefaultIcon" "" '"$INSTDIR\deluge.ico"'
WriteRegStr HKCR "Deluge\shell" "" "open"
WriteRegStr HKCR "Deluge\shell\open\command" "" '"$INSTDIR\deluge.cmd" "%1"'
SectionEnd
# Create magnet uri association
Section "Create magnet uri link association for Deluge" Section3
DeleteRegKey HKCR "magnet"
WriteRegStr HKCR "magnet" "" "URL:magnet protocol"
WriteRegStr HKCR "magnet" "URL Protocol" ""
WriteRegStr HKCR "magnet\shell\open\command" "" '"$INSTDIR\deluge.cmd" "%1"'
SectionEnd
# Install GTK+ 2.16
Section "GTK+ 2.16 runtime" Section4
GTK_install_start:
MessageBox MB_OK "You will now download and run the installer for the GTK+ 2.16 runtime. \
You must be connected to the internet before you press the OK button. \
The GTK+ runtime can be installed in any location, \
because the GTK+ installer adds the location to the global PATH variable. \
Please note that the GTK+ 2.16 runtime is not removed by the Deluge uninstaller. \
You must use the GTK+ 2.16 uninstaller if you want to remove it together with Deluge."
# Download GTK+ installer to TEMP dir
NSISdl::download http://download.deluge-torrent.org/windows/deps/${DELUGE_GTK_DEPENDENCY} "$TEMP\${DELUGE_GTK_DEPENDENCY}"
# Get return value (success, cancel, or string describing the network error)
Pop $2
StrCmp $2 "success" 0 GTK_download_error
ExecWait '"$TEMP\${DELUGE_GTK_DEPENDENCY}" /compatdlls=yes'
Goto GTK_install_exit
GTK_download_error:
MessageBox MB_ICONEXCLAMATION|MB_OK "Download of GTK+ 2.16 installer failed (return code: $2). \
You must install the GTK+ 2.16 runtime manually, or Deluge will fail to run on your system."
GTK_install_exit:
SectionEnd
LangString DESC_Section1 ${LANG_ENGLISH} "Install Deluge Bittorrent client."
LangString DESC_Section2 ${LANG_ENGLISH} "Select this option unless you have another torrent client which you want to use for opening .torrent files."
LangString DESC_Section3 ${LANG_ENGLISH} "Select this option to have Deluge handle magnet links."
LangString DESC_Section4 ${LANG_ENGLISH} "Download and install the GTK+ 2.16 runtime. \
This is skipped automatically if GTK+ is already installed."
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${Section1} $(DESC_Section1)
!insertmacro MUI_DESCRIPTION_TEXT ${Section2} $(DESC_Section2)
!insertmacro MUI_DESCRIPTION_TEXT ${Section3} $(DESC_Section3)
!insertmacro MUI_DESCRIPTION_TEXT ${Section4} $(DESC_Section4)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
# --- Uninstallation section(s) ---
Section Uninstall
Rmdir /r "${DELUGE_PYTHON_SUBDIR}"
Delete "$INSTDIR\Deluge-uninst.exe"
Delete "$INSTDIR\LICENSE"
Delete "$INSTDIR\deluge.cmd"
Delete "$INSTDIR\deluged.cmd"
Delete "$INSTDIR\deluge-webui.cmd"
Delete "$INSTDIR\StartX.exe"
Delete "$INSTDIR\Homepage.url"
Delete "$INSTDIR\deluge.ico"
Delete "$SMPROGRAMS\Deluge\Deluge.lnk"
Delete "$SMPROGRAMS\Deluge\Deluge daemon.lnk"
Delete "$SMPROGRAMS\Deluge\Deluge webUI.lnk"
Delete "$SMPROGRAMS\Deluge\Uninstall Deluge.lnk"
Delete "$SMPROGRAMS\Deluge\Project homepage.lnk"
Delete "$DESKTOP\Deluge.lnk"
RmDir "$SMPROGRAMS\Deluge"
RmDir "$INSTDIR"
DeleteRegKey ${PROGRAM_UNINST_ROOT_KEY} "${PROGRAM_UNINST_KEY}"
# Only delete the .torrent association if Deluge owns it
ReadRegStr $1 HKCR ".torrent" ""
StrCmp $1 "Deluge" 0 DELUGE_skip_delete
# Delete the key since it is owned by Deluge; afterwards there is no .torrent association
DeleteRegKey HKCR ".torrent"
DELUGE_skip_delete:
# This key is only used by Deluge, so we should always delete it
DeleteRegKey HKCR "Deluge"
SectionEnd
# Deluge Windows installer script
# Version 0.4 28-Apr-2009
# Copyright (C) 2009 by
# Jesper Lund <mail@jesperlund.com>
# Andrew Resch <andrewresch@gmail.com>
# John Garland <johnnybg@gmail.com>
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# Set default compressor
SetCompressor lzma
###
### --- The PROGRAM_VERSION !define need to be updated with new Deluge versions ---
###
# Script version; displayed when running the installer
!define DELUGE_INSTALLER_VERSION "0.4"
# Deluge program information
!define PROGRAM_NAME "Deluge"
!define PROGRAM_VERSION "1.3.0"
!define PROGRAM_WEB_SITE "http://deluge-torrent.org"
# Python files generated with bbfreeze (without DLLs from GTK+ runtime)
!define DELUGE_PYTHON_BBFREEZE_OUTPUT_DIR "..\build-win32\deluge-bbfreeze-${PROGRAM_VERSION}"
# Installer for GTK+ 2.12 runtime; will be downloaded from deluge-torrent.org
!define DELUGE_GTK_DEPENDENCY "gtk2-runtime-2.16.6-2010-05-12-ash.exe"
# --- Interface settings ---
# Modern User Interface 2
!include MUI2.nsh
# Installer
!define MUI_ICON "deluge.ico"
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_RIGHT
!define MUI_HEADERIMAGE_BITMAP "installer-top.bmp"
!define MUI_WELCOMEFINISHPAGE_BITMAP "installer-side.bmp"
!define MUI_COMPONENTSPAGE_SMALLDESC
!define MUI_FINISHPAGE_NOAUTOCLOSE
!define MUI_ABORTWARNING
# Uninstaller
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
!define MUI_HEADERIMAGE_UNBITMAP "installer-top.bmp"
!define MUI_WELCOMEFINISHPAGE_UNBITMAP "installer-side.bmp"
!define MUI_UNFINISHPAGE_NOAUTOCLOSE
# --- Start of Modern User Interface ---
# Welcome page
!insertmacro MUI_PAGE_WELCOME
# License page
!insertmacro MUI_PAGE_LICENSE "..\LICENSE"
# Components page
!insertmacro MUI_PAGE_COMPONENTS
# Let the user select the installation directory
!insertmacro MUI_PAGE_DIRECTORY
# Run installation
!insertmacro MUI_PAGE_INSTFILES
# Display 'finished' page
!insertmacro MUI_PAGE_FINISH
# Uninstaller pages
!insertmacro MUI_UNPAGE_INSTFILES
# Language files
!insertmacro MUI_LANGUAGE "English"
# --- Functions ---
Function un.onUninstSuccess
HideWindow
MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) was successfully removed from your computer."
FunctionEnd
Function un.onInit
MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Do you want to completely remove $(^Name) and all of its components?" IDYES +2
Abort
FunctionEnd
# --- Installation sections ---
# Compare versions
!include "WordFunc.nsh"
!define PROGRAM_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROGRAM_NAME}"
!define PROGRAM_UNINST_ROOT_KEY "HKLM"
# Branding text
BrandingText "Deluge Windows Installer v${DELUGE_INSTALLER_VERSION}"
Name "${PROGRAM_NAME} ${PROGRAM_VERSION}"
OutFile "..\build-win32\deluge-${PROGRAM_VERSION}-win32-setup.exe"
InstallDir "$PROGRAMFILES\Deluge"
ShowInstDetails show
ShowUnInstDetails show
# Install main application
Section "Deluge Bittorrent Client" Section1
SectionIn RO
SetOutPath $INSTDIR
File /r "${DELUGE_PYTHON_BBFREEZE_OUTPUT_DIR}\*.*"
SetOverwrite ifnewer
File "..\LICENSE"
SectionEnd
Section -StartMenu_Desktop_Links
WriteIniStr "$INSTDIR\homepage.url" "InternetShortcut" "URL" "${PROGRAM_WEB_SITE}"
# create shortcuts for all users
SetShellVarContext all
CreateDirectory "$SMPROGRAMS\Deluge"
CreateShortCut "$SMPROGRAMS\Deluge\Deluge.lnk" "$INSTDIR\deluge.exe"
CreateShortCut "$SMPROGRAMS\Deluge\Deluge daemon.lnk" "$INSTDIR\deluged.exe"
CreateShortCut "$SMPROGRAMS\Deluge\Deluge webUI.lnk" "$INSTDIR\deluge-web.exe"
CreateShortCut "$SMPROGRAMS\Deluge\Project homepage.lnk" "$INSTDIR\Homepage.url"
CreateShortCut "$SMPROGRAMS\Deluge\Uninstall Deluge.lnk" "$INSTDIR\Deluge-uninst.exe"
CreateShortCut "$DESKTOP\Deluge.lnk" "$INSTDIR\deluge.exe"
SectionEnd
Section -Uninstaller
WriteUninstaller "$INSTDIR\Deluge-uninst.exe"
WriteRegStr ${PROGRAM_UNINST_ROOT_KEY} "${PROGRAM_UNINST_KEY}" "DisplayName" "$(^Name)"
WriteRegStr ${PROGRAM_UNINST_ROOT_KEY} "${PROGRAM_UNINST_KEY}" "UninstallString" "$INSTDIR\Deluge-uninst.exe"
SectionEnd
# Create file association for .torrent
Section "Create .torrent file association for Deluge" Section2
# Set up file association for .torrent files
DeleteRegKey HKCR ".torrent"
WriteRegStr HKCR ".torrent" "" "Deluge"
WriteRegStr HKCR ".torrent" "Content Type" "application/x-bittorrent"
DeleteRegKey HKCR "Deluge"
WriteRegStr HKCR "Deluge" "" "Deluge"
WriteRegStr HKCR "Deluge\Content Type" "" "application/x-bittorrent"
WriteRegStr HKCR "Deluge\DefaultIcon" "" "$INSTDIR\deluge.exe,0"
WriteRegStr HKCR "Deluge\shell" "" "open"
WriteRegStr HKCR "Deluge\shell\open\command" "" '"$INSTDIR\deluge.exe" "%1"'
SectionEnd
# Create magnet uri association
Section "Create magnet uri link association for Deluge" Section3
DeleteRegKey HKCR "magnet"
WriteRegStr HKCR "magnet" "" "URL:magnet protocol"
WriteRegStr HKCR "magnet" "URL Protocol" ""
WriteRegStr HKCR "magnet\shell\open\command" "" '"$INSTDIR\deluge.exe" "%1"'
SectionEnd
# Install GTK+ 2.16
Section "GTK+ 2.16 runtime" Section4
GTK_install_start:
MessageBox MB_OK "You will now download and run the installer for the GTK+ 2.16 runtime. \
You must be connected to the internet before you press the OK button. \
The GTK+ runtime can be installed in any location, \
because the GTK+ installer adds the location to the global PATH variable. \
Please note that the GTK+ 2.16 runtime is not removed by the Deluge uninstaller. \
You must use the GTK+ 2.16 uninstaller if you want to remove it together with Deluge."
# Download GTK+ installer to TEMP dir
NSISdl::download http://download.deluge-torrent.org/windows/deps/${DELUGE_GTK_DEPENDENCY} "$TEMP\${DELUGE_GTK_DEPENDENCY}"
# Get return value (success, cancel, or string describing the network error)
Pop $2
StrCmp $2 "success" 0 GTK_download_error
ExecWait '"$TEMP\${DELUGE_GTK_DEPENDENCY}" /compatdlls=yes'
Goto GTK_install_exit
GTK_download_error:
MessageBox MB_ICONEXCLAMATION|MB_OK "Download of GTK+ 2.16 installer failed (return code: $2). \
You must install the GTK+ 2.16 runtime manually, or Deluge will fail to run on your system."
GTK_install_exit:
SectionEnd
LangString DESC_Section1 ${LANG_ENGLISH} "Install Deluge Bittorrent client."
LangString DESC_Section2 ${LANG_ENGLISH} "Select this option unless you have another torrent client which you want to use for opening .torrent files."
LangString DESC_Section3 ${LANG_ENGLISH} "Select this option to have Deluge handle magnet links."
LangString DESC_Section4 ${LANG_ENGLISH} "Download and install the GTK+ 2.16 runtime. \
This is skipped automatically if GTK+ is already installed."
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${Section1} $(DESC_Section1)
!insertmacro MUI_DESCRIPTION_TEXT ${Section2} $(DESC_Section2)
!insertmacro MUI_DESCRIPTION_TEXT ${Section3} $(DESC_Section3)
!insertmacro MUI_DESCRIPTION_TEXT ${Section4} $(DESC_Section4)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
# --- Uninstallation section(s) ---
Section Uninstall
RmDir /r "$INSTDIR"
SetShellVarContext all
Delete "$SMPROGRAMS\Deluge\Deluge.lnk"
Delete "$SMPROGRAMS\Deluge\Deluge daemon.lnk"
Delete "$SMPROGRAMS\Deluge\Deluge webUI.lnk"
Delete "$SMPROGRAMS\Deluge\Uninstall Deluge.lnk"
Delete "$SMPROGRAMS\Deluge\Project homepage.lnk"
Delete "$DESKTOP\Deluge.lnk"
RmDir "$SMPROGRAMS\Deluge"
DeleteRegKey ${PROGRAM_UNINST_ROOT_KEY} "${PROGRAM_UNINST_KEY}"
# Only delete the .torrent association if Deluge owns it
ReadRegStr $1 HKCR ".torrent" ""
StrCmp $1 "Deluge" 0 DELUGE_skip_delete
# Delete the key since it is owned by Deluge; afterwards there is no .torrent association
DeleteRegKey HKCR ".torrent"
DELUGE_skip_delete:
# This key is only used by Deluge, so we should always delete it
DeleteRegKey HKCR "Deluge"
SectionEnd

191
win32/icon.py Normal file
View File

@@ -0,0 +1,191 @@
#! /usr/bin/env python
# Copyright (C) 2005, Giovanni Bajo
# Based on previous work under copyright (c) 2002 McMillan Enterprises, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
# This code is courtesy of Thomas Heller, who
# has kindly donated it to this project.
RT_ICON = 3
RT_GROUP_ICON = 14
LOAD_LIBRARY_AS_DATAFILE = 2
import struct
import types
try:
StringTypes = types.StringTypes
except AttributeError:
StringTypes = [ type("") ]
class Structure:
def __init__ (self):
size = self._sizeInBytes = struct.calcsize (self._format_)
self._fields_ = list (struct.unpack (self._format_, '\000' * size))
indexes = self._indexes_ = {}
for i in range (len (self._names_)):
indexes[self._names_[i]] = i
def dump (self):
print "I: DUMP of", self
for name in self._names_:
if name[0] != '_':
print "I: %20s = %s" % (name, getattr (self, name))
print
def __getattr__ (self, name):
if name in self._names_:
index = self._indexes_[name]
return self._fields_[index]
try:
return self.__dict__[name]
except KeyError:
raise AttributeError, name
def __setattr__ (self, name, value):
if name in self._names_:
index = self._indexes_[name]
self._fields_[index] = value
else:
self.__dict__[name] = value
def tostring (self):
return apply (struct.pack, [self._format_,] + self._fields_)
def fromfile (self, file):
data = file.read (self._sizeInBytes)
self._fields_ = list (struct.unpack (self._format_, data))
class ICONDIRHEADER (Structure):
_names_ = "idReserved", "idType", "idCount"
_format_ = "hhh"
class ICONDIRENTRY (Structure):
_names_ = "bWidth", "bHeight", "bColorCount", "bReserved", "wPlanes", "wBitCount", "dwBytesInRes", "dwImageOffset"
_format_ = "bbbbhhii"
class GRPICONDIR (Structure):
_names_ = "idReserved", "idType", "idCount"
_format_ = "hhh"
class GRPICONDIRENTRY (Structure):
_names_ = "bWidth", "bHeight", "bColorCount", "bReserved", "wPlanes", "wBitCount", "dwBytesInRes", "nID"
_format_ = "bbbbhhih"
class IconFile:
def __init__ (self, path):
self.path = path
file = open (path, "rb")
self.entries = []
self.images = []
header = self.header = ICONDIRHEADER()
header.fromfile (file)
for i in range (header.idCount):
entry = ICONDIRENTRY()
entry.fromfile (file)
self.entries.append (entry)
for e in self.entries:
file.seek (e.dwImageOffset, 0)
self.images.append (file.read (e.dwBytesInRes))
def grp_icon_dir (self):
return self.header.tostring()
def grp_icondir_entries (self, id=1):
data = ""
for entry in self.entries:
e = GRPICONDIRENTRY()
for n in e._names_[:-1]:
setattr(e, n, getattr (entry, n))
e.nID = id
id = id + 1
data = data + e.tostring()
return data
def CopyIcons_FromIco (dstpath, srcpath, id=1):
import win32api #, win32con
icons = map(IconFile, srcpath)
print "I: Updating icons from", srcpath, "to", dstpath
hdst = win32api.BeginUpdateResource (dstpath, 0)
iconid = 1
for i in range(len(icons)):
f = icons[i]
data = f.grp_icon_dir()
data = data + f.grp_icondir_entries(iconid)
win32api.UpdateResource (hdst, RT_GROUP_ICON, i, data)
print "I: Writing RT_GROUP_ICON %d resource with %d bytes" % (i, len(data))
for data in f.images:
win32api.UpdateResource (hdst, RT_ICON, iconid, data)
print "I: Writing RT_ICON %d resource with %d bytes" % (iconid, len (data))
iconid = iconid + 1
win32api.EndUpdateResource (hdst, 0)
def CopyIcons (dstpath, srcpath):
import os.path, string
if type(srcpath) in StringTypes:
srcpath = [ srcpath ]
def splitter(s):
try:
srcpath, index = map(string.strip, string.split(s, ','))
return srcpath, int(index)
except ValueError:
return s, None
srcpath = map(splitter, srcpath)
print "I: SRCPATH", srcpath
if len(srcpath) > 1:
# At the moment, we support multiple icons only from .ico files
srcs = []
for s in srcpath:
e = os.path.splitext(s[0])[1]
if string.lower(e) != '.ico':
raise ValueError, "multiple icons supported only from .ico files"
if s[1] is not None:
raise ValueError, "index not allowed for .ico files"
srcs.append(s[0])
return CopyIcons_FromIco(dstpath, srcs)
srcpath,index = srcpath[0]
srcext = os.path.splitext(srcpath)[1]
if string.lower (srcext) == '.ico':
return CopyIcons_FromIco (dstpath, [srcpath])
if index is not None:
print "I: Updating icons from", srcpath, ", %d to" % index, dstpath
else:
print "I: Updating icons from", srcpath, "to", dstpath
import win32api #, win32con
hdst = win32api.BeginUpdateResource (dstpath, 0)
hsrc = win32api.LoadLibraryEx (srcpath, 0, LOAD_LIBRARY_AS_DATAFILE)
if index is None:
grpname = win32api.EnumResourceNames (hsrc, RT_GROUP_ICON)[0]
elif index >= 0:
grpname = win32api.EnumResourceNames (hsrc, RT_GROUP_ICON)[index]
else:
grpname = -index
data = win32api.LoadResource (hsrc, RT_GROUP_ICON, grpname)
win32api.UpdateResource (hdst, RT_GROUP_ICON, grpname, data)
for iconname in win32api.EnumResourceNames (hsrc, RT_ICON):
data = win32api.LoadResource (hsrc, RT_ICON, iconname)
win32api.UpdateResource (hdst, RT_ICON, iconname, data)
win32api.FreeLibrary (hsrc)
win32api.EndUpdateResource (hdst, 0)
if __name__ == "__main__":
import sys
dstpath = sys.argv[1]
srcpath = sys.argv[2:]
CopyIcons(dstpath, srcpath)