Compare commits
31 Commits
deluge-1.3
...
deluge-1.3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
90e4de54e9 | ||
![]() |
c1505bea3a | ||
![]() |
6235e832fe | ||
![]() |
a71f14c47e | ||
![]() |
ed3b23b0fc | ||
![]() |
6402634ec1 | ||
![]() |
3e68733cfd | ||
![]() |
f847a7dc4e | ||
![]() |
c7954c20eb | ||
![]() |
dc7ed11601 | ||
![]() |
d898def9ec | ||
![]() |
3e2f6c4060 | ||
![]() |
321a22a6f0 | ||
![]() |
b4774af2f3 | ||
![]() |
d0fd709c74 | ||
![]() |
e24212b3f8 | ||
![]() |
f8f72af6dc | ||
![]() |
b9caa4eeeb | ||
![]() |
6c3b216b40 | ||
![]() |
eaad867885 | ||
![]() |
f6b9f67df8 | ||
![]() |
24fe3f7fd5 | ||
![]() |
da2fb41a3a | ||
![]() |
f8d7f22167 | ||
![]() |
b75abc70e5 | ||
![]() |
2d821bd79a | ||
![]() |
12d9a7a5bd | ||
![]() |
c118fa36a9 | ||
![]() |
82c91cdc51 | ||
![]() |
5501094214 | ||
![]() |
b41aa808be |
18
ChangeLog
18
ChangeLog
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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))
|
||||
|
@@ -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.
|
||||
|
@@ -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']:
|
||||
|
@@ -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">•</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"><b>Torrent File Action</b></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">●</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>
|
||||
|
@@ -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):
|
||||
|
@@ -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."
|
||||
|
@@ -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()
|
||||
|
@@ -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"))
|
||||
|
@@ -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."
|
||||
|
@@ -557,6 +557,7 @@
|
||||
1 MiB
|
||||
2 MiB
|
||||
4 MiB
|
||||
8 MiB
|
||||
</property>
|
||||
</widget>
|
||||
<packing>
|
||||
|
@@ -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'"""
|
||||
|
@@ -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
|
||||
|
25
setup.py
25
setup.py
@@ -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 = """
|
||||
|
@@ -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"))
|
||||
|
@@ -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()
|
||||
|
@@ -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)
|
||||
|
@@ -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
191
win32/icon.py
Normal 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)
|
Reference in New Issue
Block a user