Compare commits
59 Commits
deluge-1.3
...
deluge-1.3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b0ceae8d28 | ||
![]() |
dc0bf3bc88 | ||
![]() |
3b9d7ff9c3 | ||
![]() |
a165d5d746 | ||
![]() |
cc02ebea6a | ||
![]() |
41ffee5d8a | ||
![]() |
14a89b3f8a | ||
![]() |
6f0c2af58a | ||
![]() |
84cccabf19 | ||
![]() |
7fb483adde | ||
![]() |
28ce7a70a0 | ||
![]() |
14565977fa | ||
![]() |
e4420ef354 | ||
![]() |
02ad0b93ab | ||
![]() |
6d2a001635 | ||
![]() |
2a3eb0578c | ||
![]() |
60fac28217 | ||
![]() |
59e01e7ecf | ||
![]() |
4c52ee4229 | ||
![]() |
8428524793 | ||
![]() |
21c8d02d9a | ||
![]() |
0c687c7684 | ||
![]() |
78f9efefd9 | ||
![]() |
6b228ce31f | ||
![]() |
40ce4ec731 | ||
![]() |
c029c312e4 | ||
![]() |
16c38cd027 | ||
![]() |
e23a6b852a | ||
![]() |
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 |
40
ChangeLog
40
ChangeLog
@@ -1,9 +1,45 @@
|
||||
=== Deluge 1.3.1 (31 October 2010) ===
|
||||
==== Core ====
|
||||
* #1369: Fix non-ascii config folders not working in windows
|
||||
|
||||
==== GtkUI ====
|
||||
* #1365: Fix sidebar not updating show/hide trackers
|
||||
* #1247: Fix hang on quit
|
||||
|
||||
==== WebUI ====
|
||||
* #1364: Fix preferences not saving when the web ui plugin is enabled in classic mode
|
||||
* #1377: Fix bug when enabling plugins
|
||||
* #1370: Fix issues with preferences
|
||||
* #1312: Fix deluge-web using 100% CPU
|
||||
|
||||
=== Deluge 1.3.0 (18 September 2010) ===
|
||||
==== Core ====
|
||||
* 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
|
||||
* Ignore global stop ratio related settings in logic, so per torrent ones are used.
|
||||
* 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
|
||||
|
||||
==== GtkUI ====
|
||||
* Increase max piece size to 8 MiB in create torrent dialog (closes #1358)
|
||||
|
||||
==== Scheduler ====
|
||||
* Add max active downloading and seeding options to scheduler.
|
||||
* Fix scheduler so that it keeps current state, even after global settings change.
|
||||
|
||||
==== AutoAdd ====
|
||||
* AutoAdd plugin can now recover when one of the watchfolders has an unhandled exception.
|
||||
* 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
|
||||
* Fix #1302 an uncaught exception in an state_changed event handler in SessionProxy was preventing the TorrentManager's stop method from properly saving all the resume data
|
||||
* Fix issue with SessionProxy not updating the torrent status correctly when
|
||||
get_torrent_status calls take place within the cache_expiry time
|
||||
* Fix issue with SessionProxy not updating the torrent status correctly when get_torrent_status calls take place within the cache_expiry time
|
||||
|
||||
==== ConsoleUI ====
|
||||
* #1307: Fix not being able to add torrents
|
||||
|
4
DEPENDS
4
DEPENDS
@@ -7,6 +7,7 @@
|
||||
* setuptools
|
||||
* gettext
|
||||
* pyxdg
|
||||
* chardet
|
||||
* geoip-database (optional)
|
||||
|
||||
* libtorrent >= 0.14, or build the included version
|
||||
@@ -16,9 +17,6 @@
|
||||
* openssl
|
||||
* zlib
|
||||
|
||||
=== UIs ===
|
||||
* chardet
|
||||
|
||||
=== Gtk ===
|
||||
* python-notify (libnotify python wrapper)
|
||||
* pygame
|
||||
|
@@ -41,6 +41,7 @@ import time
|
||||
import subprocess
|
||||
import platform
|
||||
import sys
|
||||
import chardet
|
||||
|
||||
try:
|
||||
import json
|
||||
@@ -63,7 +64,6 @@ if not hasattr(json, "dumps"):
|
||||
json.load = load
|
||||
|
||||
import pkg_resources
|
||||
import xdg, xdg.BaseDirectory
|
||||
import gettext
|
||||
import locale
|
||||
|
||||
@@ -150,6 +150,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:
|
||||
@@ -474,7 +475,7 @@ def free_space(path):
|
||||
sectors, bytes, free, total = map(long, win32file.GetDiskFreeSpace(path))
|
||||
return (free * sectors * bytes)
|
||||
else:
|
||||
disk_data = os.statvfs(path)
|
||||
disk_data = os.statvfs(path.encode("utf8"))
|
||||
block_size = disk_data.f_bsize
|
||||
return disk_data.f_bavail * block_size
|
||||
|
||||
@@ -560,6 +561,41 @@ def xml_encode(string):
|
||||
string = string.replace(char, escape)
|
||||
return string
|
||||
|
||||
def decode_string(s, encoding="utf8"):
|
||||
"""
|
||||
Decodes a string and re-encodes it in utf8. If it cannot decode using
|
||||
`:param:encoding` then it will try to detect the string encoding and
|
||||
decode it.
|
||||
|
||||
:param s: string to decode
|
||||
:type s: string
|
||||
:keyword encoding: the encoding to use in the decoding
|
||||
:type encoding: string
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
s = s.decode(encoding).encode("utf8", "ignore")
|
||||
except UnicodeDecodeError:
|
||||
s = s.decode(chardet.detect(s)["encoding"], "ignore").encode("utf8", "ignore")
|
||||
return s
|
||||
|
||||
def utf8_encoded(s):
|
||||
"""
|
||||
Returns a utf8 encoded string of s
|
||||
|
||||
:param s: (unicode) string to (re-)encode
|
||||
:type s: basestring
|
||||
:returns: a utf8 encoded string of s
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
if isinstance(s, str):
|
||||
s = decode_string(s, locale.getpreferredencoding())
|
||||
elif isinstance(s, unicode):
|
||||
s = s.encode("utf8", "ignore")
|
||||
return s
|
||||
|
||||
class VersionSplit(object):
|
||||
"""
|
||||
Used for comparing version numbers.
|
||||
@@ -570,13 +606,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 +625,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)
|
||||
|
@@ -146,7 +146,8 @@ class Config(object):
|
||||
self._save_timer = None
|
||||
|
||||
if defaults:
|
||||
self.__config = dict(defaults)
|
||||
for key, value in defaults.iteritems():
|
||||
self.set_item(key, value)
|
||||
|
||||
# Load the config from file in the config_dir
|
||||
if config_dir:
|
||||
@@ -187,6 +188,10 @@ what is currently in the config and it could not convert the value
|
||||
5
|
||||
|
||||
"""
|
||||
if isinstance(value, basestring):
|
||||
value = deluge.common.utf8_encoded(value)
|
||||
|
||||
|
||||
if not self.__config.has_key(key):
|
||||
self.__config[key] = value
|
||||
log.debug("Setting '%s' to %s of %s", key, value, type(value))
|
||||
@@ -200,7 +205,10 @@ what is currently in the config and it could not convert the value
|
||||
|
||||
if value is not None and oldtype != type(None) and oldtype != newtype:
|
||||
try:
|
||||
value = oldtype(value)
|
||||
if oldtype == unicode:
|
||||
value = oldtype(value, "utf8")
|
||||
else:
|
||||
value = oldtype(value)
|
||||
except ValueError:
|
||||
log.warning("Type '%s' invalid for '%s'", newtype, key)
|
||||
raise
|
||||
@@ -250,7 +258,10 @@ what is currently in the config and it could not convert the value
|
||||
5
|
||||
|
||||
"""
|
||||
return self.__config[key]
|
||||
if isinstance(self.__config[key], str):
|
||||
return self.__config[key].decode("utf8")
|
||||
else:
|
||||
return self.__config[key]
|
||||
|
||||
def register_change_callback(self, callback):
|
||||
"""
|
||||
@@ -398,9 +409,9 @@ 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
|
||||
return True
|
||||
except IOError, e:
|
||||
log.warning("Unable to open config file: %s because: %s", filename, 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
|
||||
|
@@ -196,9 +196,8 @@ class FilterManager(component.Component):
|
||||
value = status[field]
|
||||
items[field][value] = items[field].get(value, 0) + 1
|
||||
|
||||
items["tracker_host"]["All"] = len(torrent_ids)
|
||||
|
||||
if "tracker_host" in items:
|
||||
items["tracker_host"]["All"] = len(torrent_ids)
|
||||
items["tracker_host"]["Error"] = len(tracker_error_filter(torrent_ids, ("Error",)))
|
||||
|
||||
if "state" in tree_keys and not show_zero_hits:
|
||||
|
@@ -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
|
||||
|
@@ -179,6 +179,11 @@ class Torrent(object):
|
||||
else:
|
||||
self.time_added = time.time()
|
||||
|
||||
# Keep track if we're forcing a recheck of the torrent so that we can
|
||||
# repause it after its done if necessary
|
||||
self.forcing_recheck = False
|
||||
self.forcing_recheck_paused = False
|
||||
|
||||
log.debug("Torrent object created.")
|
||||
|
||||
## Options methods ##
|
||||
@@ -393,12 +398,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 +765,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
|
||||
@@ -865,12 +864,15 @@ class Torrent(object):
|
||||
|
||||
def force_recheck(self):
|
||||
"""Forces a recheck of the torrents pieces"""
|
||||
paused = self.handle.is_paused()
|
||||
try:
|
||||
self.handle.force_recheck()
|
||||
self.handle.resume()
|
||||
except Exception, e:
|
||||
log.debug("Unable to force recheck: %s", e)
|
||||
return False
|
||||
self.forcing_recheck = True
|
||||
self.forcing_recheck_paused = paused
|
||||
return True
|
||||
|
||||
def rename_files(self, filenames):
|
||||
|
@@ -47,16 +47,14 @@ from twisted.internet.task import LoopingCall
|
||||
|
||||
from deluge._libtorrent import lt
|
||||
|
||||
|
||||
from deluge.event import *
|
||||
from deluge.error import *
|
||||
import deluge.common
|
||||
import deluge.component as component
|
||||
from deluge.configmanager import ConfigManager, get_config_dir
|
||||
from deluge.core.torrent import Torrent
|
||||
from deluge.core.torrent import TorrentOptions
|
||||
import deluge.core.oldstateupgrader
|
||||
from deluge.ui.common import utf8_encoded
|
||||
from deluge.common import utf8_encoded
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
||||
@@ -192,6 +190,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 +256,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():
|
||||
@@ -852,7 +849,14 @@ class TorrentManager(component.Component):
|
||||
torrent = self.torrents[str(alert.handle.info_hash())]
|
||||
except:
|
||||
return
|
||||
|
||||
|
||||
# Check to see if we're forcing a recheck and set it back to paused
|
||||
# if necessary
|
||||
if torrent.forcing_recheck:
|
||||
torrent.forcing_recheck = False
|
||||
if torrent.forcing_recheck_paused:
|
||||
torrent.handle.pause()
|
||||
|
||||
# Set the torrent state
|
||||
torrent.update_state()
|
||||
|
||||
@@ -1015,3 +1019,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."
|
||||
|
@@ -82,6 +82,7 @@ class Core(CorePluginBase):
|
||||
self.is_importing = False
|
||||
self.has_imported = False
|
||||
self.up_to_date = False
|
||||
self.need_to_resume_session = False
|
||||
self.num_blocked = 0
|
||||
self.file_progress = 0.0
|
||||
|
||||
@@ -95,7 +96,7 @@ class Core(CorePluginBase):
|
||||
|
||||
update_now = False
|
||||
if self.config["load_on_start"]:
|
||||
self.pause_transfers()
|
||||
self.pause_session()
|
||||
if self.config["last_update"]:
|
||||
last_update = datetime.fromtimestamp(self.config["last_update"])
|
||||
check_period = timedelta(days=self.config["check_after_days"])
|
||||
@@ -104,7 +105,8 @@ class Core(CorePluginBase):
|
||||
else:
|
||||
d = self.import_list(deluge.configmanager.get_config_dir("blocklist.cache"))
|
||||
d.addCallbacks(self.on_import_complete, self.on_import_error)
|
||||
d.addBoth(self.resume_transfers)
|
||||
if self.need_to_resume_session:
|
||||
d.addBoth(self.resume_session)
|
||||
|
||||
# This function is called every 'check_after_days' days, to download
|
||||
# and import a new list if needed.
|
||||
@@ -150,7 +152,8 @@ class Core(CorePluginBase):
|
||||
else:
|
||||
d = self.import_list(self.config["url"])
|
||||
d.addCallbacks(self.on_import_complete, self.on_import_error)
|
||||
d.addBoth(self.resume_transfers)
|
||||
if self.need_to_resume_session:
|
||||
d.addBoth(self.resume_session)
|
||||
|
||||
return d
|
||||
|
||||
@@ -418,13 +421,14 @@ class Core(CorePluginBase):
|
||||
else:
|
||||
self.reader = create_reader(self.config["list_type"], self.config["list_compression"])
|
||||
|
||||
def pause_transfers(self):
|
||||
self.session_was_paused = self.core.session.is_paused()
|
||||
if not self.session_was_paused:
|
||||
def pause_session(self):
|
||||
if not self.core.session.is_paused():
|
||||
self.core.session.pause()
|
||||
self.need_to_resume_session = True
|
||||
else:
|
||||
self.need_to_resume_session = False
|
||||
|
||||
def resume_transfers(self, result):
|
||||
if not self.session_was_paused:
|
||||
self.session_was_paused = True
|
||||
self.core.session.resume()
|
||||
def resume_session(self, result):
|
||||
self.core.session.resume()
|
||||
self.need_to_resume_session = False
|
||||
return result
|
||||
|
@@ -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."
|
||||
|
@@ -42,7 +42,6 @@ import os
|
||||
import sys
|
||||
import urlparse
|
||||
|
||||
import chardet
|
||||
import locale
|
||||
|
||||
try:
|
||||
@@ -50,45 +49,11 @@ try:
|
||||
except ImportError:
|
||||
from sha import sha
|
||||
|
||||
from deluge import bencode, common
|
||||
from deluge import bencode
|
||||
from deluge.common import decode_string, path_join
|
||||
from deluge.log import LOG as log
|
||||
import deluge.configmanager
|
||||
|
||||
def decode_string(s, encoding="utf8"):
|
||||
"""
|
||||
Decodes a string and re-encodes it in utf8. If it cannot decode using
|
||||
`:param:encoding` then it will try to detect the string encoding and
|
||||
decode it.
|
||||
|
||||
:param s: string to decode
|
||||
:type s: string
|
||||
:keyword encoding: the encoding to use in the decoding
|
||||
:type encoding: string
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
s = s.decode(encoding).encode("utf8", "ignore")
|
||||
except UnicodeDecodeError:
|
||||
s = s.decode(chardet.detect(s)["encoding"], "ignore").encode("utf8", "ignore")
|
||||
return s
|
||||
|
||||
def utf8_encoded(s):
|
||||
"""
|
||||
Returns a utf8 encoded string of s
|
||||
|
||||
:param s: (unicode) string to (re-)encode
|
||||
:type s: basestring
|
||||
:returns: a utf8 encoded string of s
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
if isinstance(s, str):
|
||||
s = decode_string(s, locale.getpreferredencoding())
|
||||
elif isinstance(s, unicode):
|
||||
s = s.encode("utf8", "ignore")
|
||||
return s
|
||||
|
||||
class TorrentInfo(object):
|
||||
"""
|
||||
Collects information about a torrent file.
|
||||
@@ -336,7 +301,7 @@ class FileTree2(object):
|
||||
"""
|
||||
def walk(directory, parent_path):
|
||||
for path in directory["contents"].keys():
|
||||
full_path = common.path_join(parent_path, path)
|
||||
full_path = path_join(parent_path, path)
|
||||
if directory["contents"][path]["type"] == "dir":
|
||||
directory["contents"][path] = callback(full_path, directory["contents"][path]) or \
|
||||
directory["contents"][path]
|
||||
|
@@ -122,7 +122,10 @@ class FilterTreeView(component.Component):
|
||||
self.label_view.set_show_expanders(True)
|
||||
self.label_view.set_headers_visible(False)
|
||||
self.label_view.set_level_indentation(-35)
|
||||
|
||||
# Force the theme to use an expander-size of 15 so that we don't cut out
|
||||
# entries due to our indentation hack.
|
||||
gtk.rc_parse_string('style "treeview-style" { GtkTreeView::expander-size = 15 } class "GtkTreeView" style "treeview-style"')
|
||||
|
||||
self.label_view.set_model(self.treestore)
|
||||
self.label_view.get_selection().connect("changed", self.on_selection_changed)
|
||||
self.create_model_filter()
|
||||
|
@@ -557,6 +557,7 @@
|
||||
1 MiB
|
||||
2 MiB
|
||||
4 MiB
|
||||
8 MiB
|
||||
</property>
|
||||
</widget>
|
||||
<packing>
|
||||
|
@@ -47,6 +47,7 @@ import sys
|
||||
|
||||
# Initialize gettext
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
if hasattr(locale, "bindtextdomain"):
|
||||
locale.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
|
||||
if hasattr(locale, "textdomain"):
|
||||
|
@@ -152,11 +152,16 @@ class MainWindow(component.Component):
|
||||
"""Returns a reference to the main window glade object."""
|
||||
return self.main_glade
|
||||
|
||||
def quit(self):
|
||||
if client.is_classicmode():
|
||||
gtk.main_quit()
|
||||
else:
|
||||
reactor.stop()
|
||||
def quit(self, shutdown=False):
|
||||
"""
|
||||
Quits the GtkUI
|
||||
|
||||
:param shutdown: whether or not to shutdown the daemon as well
|
||||
:type shutdown: boolean
|
||||
"""
|
||||
if shutdown:
|
||||
client.daemon.shutdown()
|
||||
reactor.stop()
|
||||
|
||||
def load_window_state(self):
|
||||
x = self.config["window_x_pos"]
|
||||
|
@@ -253,10 +253,7 @@ class MenuBar(component.Component):
|
||||
|
||||
def on_menuitem_quitdaemon_activate(self, data=None):
|
||||
log.debug("on_menuitem_quitdaemon_activate")
|
||||
# Tell the core to shutdown
|
||||
def on_shutdown(result):
|
||||
self.window.quit()
|
||||
client.daemon.shutdown().addCallback(on_shutdown)
|
||||
self.window.quit(shutdown=True)
|
||||
|
||||
def on_menuitem_quit_activate(self, data=None):
|
||||
log.debug("on_menuitem_quit_activate")
|
||||
|
@@ -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'"""
|
||||
|
@@ -323,9 +323,6 @@ class SystemTray(component.Component):
|
||||
if self.config["lock_tray"] and not self.window.visible():
|
||||
self.unlock_tray()
|
||||
|
||||
if self.config["classic_mode"]:
|
||||
client.daemon.shutdown()
|
||||
|
||||
self.window.quit()
|
||||
|
||||
def on_menuitem_quitdaemon_activate(self, menuitem):
|
||||
@@ -333,8 +330,7 @@ class SystemTray(component.Component):
|
||||
if self.config["lock_tray"] and not self.window.visible():
|
||||
self.unlock_tray()
|
||||
|
||||
client.daemon.shutdown()
|
||||
self.window.quit()
|
||||
self.window.quit(shutdown=True)
|
||||
|
||||
def tray_setbwdown(self, widget, data=None):
|
||||
self.setbwlimit(widget, _("Set Maximum Download Speed"), "max_download_speed",
|
||||
|
@@ -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
|
||||
|
@@ -31,7 +31,7 @@
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-image: url('${base}themes/default/tree/loading.gif');"></div>
|
||||
<div style="background-image: url('${base}themes/images/default/tree/loading.gif');"></div>
|
||||
|
||||
<!-- Preload icon classes -->
|
||||
<div class="ext-mb-error"></div>
|
||||
|
@@ -174,7 +174,7 @@ Deluge.MultiOptionsManager = Ext.extend(Deluge.OptionsManager, {
|
||||
this.stored[this.currentId][option] = value;
|
||||
|
||||
if (!this.isDirty(option)) {
|
||||
this.fireEvent('changed', this.currentId, option, value, oldValue);
|
||||
this.fireEvent('changed', option, value, oldValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -232,9 +232,9 @@ Deluge.preferences.Plugins = Ext.extend(Ext.Panel, {
|
||||
},
|
||||
|
||||
onPluginEnabled: function(pluginName) {
|
||||
var index = this.grid.getStore().find('plugin', pluginName);
|
||||
var index = this.list.getStore().find('plugin', pluginName);
|
||||
if (index == -1) return;
|
||||
var plugin = this.grid.getStore().getAt(index);
|
||||
var plugin = this.list.getStore().getAt(index);
|
||||
plugin.set('enabled', true);
|
||||
plugin.commit();
|
||||
},
|
||||
|
35
deluge/ui/web/js/ext-extensions/form/SpinnerFieldFix.js
Normal file
35
deluge/ui/web/js/ext-extensions/form/SpinnerFieldFix.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
* Ext.ux.form.SpinnerField.js
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2010 <damoxc@gmail.com>
|
||||
*
|
||||
* 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 3, 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.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link the code of portions of this program with the OpenSSL
|
||||
* library.
|
||||
* You must obey the GNU General Public License in all respects for all of
|
||||
* the code used other than OpenSSL. If you modify file(s) with this
|
||||
* exception, you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete
|
||||
* this exception statement from your version. If you delete this exception
|
||||
* statement from all source files in the program, then also delete it here.
|
||||
*/
|
||||
|
||||
Ext.override(Ext.ux.form.SpinnerField, {
|
||||
onBlur: Ext.form.Field.prototype.onBlur
|
||||
});
|
@@ -150,7 +150,8 @@ class JSON(resource.Resource, component.Component):
|
||||
return d
|
||||
|
||||
def disable(self):
|
||||
client.disconnect()
|
||||
if not client.is_classicmode():
|
||||
client.disconnect()
|
||||
|
||||
def enable(self):
|
||||
if component.get("DelugeWeb").config["default_daemon"]:
|
||||
@@ -381,16 +382,21 @@ class EventQueue(object):
|
||||
|
||||
# Create a deferred to and check again in 100ms
|
||||
d = Deferred()
|
||||
reactor.callLater(0.5, self._get_events, listener_id, d)
|
||||
reactor.callLater(0.1, self._get_events, listener_id, 0, d)
|
||||
return d
|
||||
|
||||
def _get_events(self, listener_id, d):
|
||||
def _get_events(self, listener_id, count, d):
|
||||
if listener_id in self.__queue:
|
||||
queue = self.__queue[listener_id]
|
||||
del self.__queue[listener_id]
|
||||
d.callback(queue)
|
||||
else:
|
||||
reactor.callLater(0.1, self._get_events, listener_id, d)
|
||||
# Prevent this loop going on indefinitely incase a client leaves
|
||||
# the page or disconnects uncleanly.
|
||||
if count >= 3000:
|
||||
d.callback(None)
|
||||
else:
|
||||
reactor.callLater(0.1, self._get_events, listener_id, count + 1, d)
|
||||
|
||||
def remove_listener(self, listener_id, event):
|
||||
"""
|
||||
|
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import cPickle as pickle
|
||||
if sys.version_info > (2, 6):
|
||||
import json
|
||||
else:
|
||||
import simplejson as json
|
||||
|
||||
from deluge.common import get_default_config_dir
|
||||
|
||||
config_dir = get_default_config_dir()
|
||||
files = []
|
||||
for filename in os.listdir(config_dir):
|
||||
filename = os.path.join(config_dir, filename)
|
||||
if not os.path.isfile(filename):
|
||||
continue
|
||||
if filename.endswith(".log"):
|
||||
continue
|
||||
|
||||
basename = os.path.basename(filename)
|
||||
sys.stdout.write("Converting %s..." % (basename) + ' '*(20-len(basename)))
|
||||
try:
|
||||
config = json.load(open(filename, "r"))
|
||||
pickle.dump(config, open(filename, "wb"))
|
||||
print "\033[032mdone\033[0m"
|
||||
except:
|
||||
try:
|
||||
pickle.load(open(filename, "rb"))
|
||||
print "\033[032malready converted\033[0m"
|
||||
except:
|
||||
print "\033[031mfailed\033[1;m"
|
50
setup.py
50
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
|
||||
@@ -432,10 +422,26 @@ _data_files = [
|
||||
'docs/man/deluge-console.1'])
|
||||
]
|
||||
|
||||
entry_points = {
|
||||
"console_scripts": [
|
||||
"deluge-console = deluge.ui.console:start",
|
||||
"deluge-web = deluge.ui.web:start",
|
||||
"deluged = deluge.main:start_daemon"
|
||||
],
|
||||
"gui_scripts": [
|
||||
"deluge = deluge.main:start_ui",
|
||||
"deluge-gtk = deluge.ui.gtkui:start"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
if windows_check():
|
||||
entry_points["console_scripts"].append("deluge-debug = deluge.main:start_ui")
|
||||
|
||||
# Main setup
|
||||
setup(
|
||||
name = "deluge",
|
||||
version = "1.3.0-rc2",
|
||||
version = "1.3.1",
|
||||
fullname = "Deluge Bittorrent Client",
|
||||
description = "Bittorrent Client",
|
||||
author = "Andrew Resch, Damien Churchill",
|
||||
@@ -452,14 +458,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,16 +473,17 @@ 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 = """
|
||||
[console_scripts]
|
||||
deluge = deluge.main:start_ui
|
||||
deluge-console = deluge.ui.console:start
|
||||
deluge-gtk = deluge.ui.gtkui:start
|
||||
deluge-web = deluge.ui.web:start
|
||||
deluged = deluge.main:start_daemon
|
||||
""",
|
||||
entry_points = 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"))
|
||||
|
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from twisted.trial import unittest
|
||||
from twisted.python.failure import Failure
|
||||
|
||||
@@ -6,7 +8,7 @@ import os
|
||||
|
||||
from deluge.config import Config
|
||||
|
||||
DEFAULTS = {"string": "foobar", "int": 1, "float": 0.435, "bool": True}
|
||||
DEFAULTS = {"string": "foobar", "int": 1, "float": 0.435, "bool": True, "unicode": u"foobar"}
|
||||
|
||||
class ConfigTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@@ -27,6 +29,9 @@ class ConfigTestCase(unittest.TestCase):
|
||||
config["foo"] = 2
|
||||
self.assertEquals(config.get_item("foo"), 2)
|
||||
|
||||
config["unicode"] = u"ВИДЕОФИЛЬМЫ"
|
||||
self.assertEquals(config["unicode"], u"ВИДЕОФИЛЬМЫ")
|
||||
|
||||
config._save_timer.cancel()
|
||||
|
||||
def test_load(self):
|
||||
|
@@ -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,45 @@
|
||||
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.1"
|
||||
python_path = "C:\\Python26\\"
|
||||
|
||||
import os, glob
|
||||
import shutil
|
||||
shutil.copy(python_path + "Scripts\deluge-script.pyw", python_path + "Scripts\deluge.py")
|
||||
shutil.copy(python_path + "Scripts\deluge-script.pyw", 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.pyw", 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_path = os.path.join(os.path.dirname(__file__), "deluge.ico")
|
||||
icon.CopyIcons(dst+"deluge.exe", icon_path)
|
||||
icon.CopyIcons(dst+"deluge-debug.exe", icon_path)
|
||||
icon.CopyIcons(dst+"deluged.exe", icon_path)
|
||||
icon.CopyIcons(dst+"deluge-web.exe", icon_path)
|
||||
icon.CopyIcons(dst+"deluge-gtk.exe", icon_path)
|
||||
icon.CopyIcons(dst+"deluge-console.exe", icon_path)
|
||||
|
||||
# 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.1"
|
||||
!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