Compare commits
109 Commits
deluge-2.0
...
deluge-1.3
Author | SHA1 | Date | |
---|---|---|---|
90e4de54e9 | |||
c1505bea3a | |||
6235e832fe | |||
a71f14c47e | |||
ed3b23b0fc | |||
6402634ec1 | |||
3e68733cfd | |||
f847a7dc4e | |||
c7954c20eb | |||
dc7ed11601 | |||
d898def9ec | |||
3e2f6c4060 | |||
321a22a6f0 | |||
b4774af2f3 | |||
d0fd709c74 | |||
e24212b3f8 | |||
f8f72af6dc | |||
b9caa4eeeb | |||
6c3b216b40 | |||
eaad867885 | |||
f6b9f67df8 | |||
24fe3f7fd5 | |||
da2fb41a3a | |||
f8d7f22167 | |||
b75abc70e5 | |||
2d821bd79a | |||
12d9a7a5bd | |||
c118fa36a9 | |||
82c91cdc51 | |||
5501094214 | |||
b41aa808be | |||
b9336889f5 | |||
995f5387eb | |||
38958d3c4f | |||
b45e019f08 | |||
d93fcf6eea | |||
a2d75a5274 | |||
8797c3ce1b | |||
79749cca03 | |||
5fd8628761 | |||
0e80b3ea0a | |||
aa61d33ee2 | |||
13f29a77dd | |||
97453d1411 | |||
62d02091b3 | |||
161ad0ff0d | |||
7f323ec0fc | |||
05aebbb575 | |||
de85e1dcdc | |||
1ce480ff23 | |||
007a9912d2 | |||
d793b9e6b8 | |||
72ec926c1a | |||
0d431ae7db | |||
f1e0e3be15 | |||
e8bf5eb592 | |||
d9a2c4db72 | |||
8fb7277a82 | |||
35128cf18f | |||
6ff1da2391 | |||
4614188c62 | |||
80297b8e45 | |||
9173a9cfdd | |||
571d1079f6 | |||
0497c407e1 | |||
8b58c960f3 | |||
b615ebe1b8 | |||
3ed8279219 | |||
f2944bdeef | |||
0fcd90ee2c | |||
26460808e7 | |||
7aba1af0b2 | |||
4d2b7df49d | |||
bd775d0d40 | |||
7fb3c3c04c | |||
19c27ee8c5 | |||
d69b8e1099 | |||
88daf82cb0 | |||
99c1a61383 | |||
2e55769c18 | |||
259d2633e7 | |||
8e5aab660c | |||
fc96e9d02c | |||
821d403a6c | |||
5e0d988ef0 | |||
925ac42f7c | |||
1ac72b81b6 | |||
3417caf1d2 | |||
1bcfc91c35 | |||
6ee0e5b6be | |||
58a74202e1 | |||
a4c6f4e8c9 | |||
60f3d32de7 | |||
b3eed8a1f0 | |||
37137d9b54 | |||
4fb14b581d | |||
98da4d0291 | |||
f0c06f4bc5 | |||
63d701305c | |||
99396afa0c | |||
6231dbd1ca | |||
8f021c7f06 | |||
6bb4559d18 | |||
7c9eea0361 | |||
15247507d4 | |||
10de8d5475 | |||
e304c1f719 | |||
48d3e89d84 | |||
44f9e17a09 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "libtorrent"]
|
||||
path = libtorrent
|
||||
url = git://deluge-torrent.org/libtorrent
|
63
ChangeLog
63
ChangeLog
@ -1,4 +1,45 @@
|
||||
=== Deluge 1.3.0 (In Development) ===
|
||||
=== Deluge 1.3.0 (18 September 2010) ===
|
||||
* Fix issue where the save_timer is cancelled when it's not active
|
||||
* Fix unhandled exception when adding a torrent to the session
|
||||
* Moved xdg import so it is not called on Windows, where it is unused. fixes #1343
|
||||
* Fix key error after enabling a plugin that introduces a new status key
|
||||
* Add max active downloading and seeding options to scheduler.
|
||||
* Ignore global stop ratio related settings in logic, so per torrent ones are used.
|
||||
* Fix scheduler so that it keeps current state, even after global settings change.
|
||||
* Ensure preferencesmanager only changes intended libtorrent session settings.
|
||||
* Fix issue when adding torrents without a 'session'. This can happen when
|
||||
a plugin adds a torrent, like how the AutoAdd plugin works. The user that
|
||||
adds this torrent will be an empty string.
|
||||
* Add TorrentFileCompleted event
|
||||
* AutoAdd plugin can now recover when one of the watchfolders has an unhandled exception.
|
||||
* Increase max piece size to 8 MiB in create torrent dialog (closes #1358)
|
||||
* Fix bug in AutoAdd plugin where watchdirs would not display in gtkui when first enabled.
|
||||
* Fix bugs with unicode torrents in AutoAdd plugin.
|
||||
|
||||
=== Deluge 1.3.0-rc2 (20 August 2010) ===
|
||||
==== Core ====
|
||||
* Fix tracker_icons failing on windows
|
||||
* 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
|
||||
|
||||
==== ConsoleUI ====
|
||||
* #1307: Fix not being able to add torrents
|
||||
* #1293: Fix not being able to add paths that contain backslashes
|
||||
|
||||
==== GtkUI ====
|
||||
* Fix uncaught exception when closing deluge in classic mode
|
||||
|
||||
==== Execute ====
|
||||
* #1306: Fix always executing last event
|
||||
|
||||
==== Label ====
|
||||
* Fix being able to remove labels in web ui
|
||||
|
||||
==== WebUI ====
|
||||
* #1319: Fix shift selecting in file trees
|
||||
|
||||
=== Deluge 1.3.0-rc1 (08 May 2010) ===
|
||||
==== Core ====
|
||||
* Implement #1063 option to delete torrent file copy on torrent removal - patch from Ghent
|
||||
* Implement #457 progress bars for folders
|
||||
@ -7,15 +48,33 @@
|
||||
* #1112: Fix renaming files in add torrent dialog
|
||||
* #1247: Fix deluge-gtk from hanging on shutdown
|
||||
* #995: Rewrote tracker_icons
|
||||
* Add AutoAdd plugin
|
||||
* Add Notifications plugin
|
||||
|
||||
==== GtkUI ====
|
||||
* Use new SessionProxy class for caching torrent status client-side
|
||||
* Use torrent status diffs to reduce RPC traffic
|
||||
|
||||
==== Blocklist ====
|
||||
* Implement local blocklist support
|
||||
* #861: Pause transfers until blocklist is imported
|
||||
* Fix redirection not working with relative paths
|
||||
|
||||
==== Execute ====
|
||||
* Fix running commands with the TorrentAdded event
|
||||
* Fix the web interface
|
||||
|
||||
==== Label ====
|
||||
* Fix the web interface (#733)
|
||||
|
||||
==== Web ====
|
||||
* Migrate to ExtJS 3.1
|
||||
* Add gzip compression of HTTP data to the server
|
||||
* Improve the efficiency of the TorrentGrid
|
||||
* Improve the efficiency of the TorrentGrid with lots of torrents (#1026)
|
||||
* Add a base parameter to allow reverse proxying (#1076)
|
||||
* Fix showing all the peers in the details tab (#1054)
|
||||
* Fix uploading torrent files in Opera or IE (#1087)
|
||||
* Complete IE support
|
||||
|
||||
=== Deluge 1.2.0 - "Bursting like an infected kidney" (10 January 2010) ===
|
||||
==== Core ====
|
||||
|
@ -63,7 +63,6 @@ if not hasattr(json, "dumps"):
|
||||
json.load = load
|
||||
|
||||
import pkg_resources
|
||||
import xdg, xdg.BaseDirectory
|
||||
import gettext
|
||||
import locale
|
||||
|
||||
@ -150,6 +149,7 @@ def get_default_config_dir(filename=None):
|
||||
else:
|
||||
return os.path.join(os.environ.get("APPDATA"), "deluge")
|
||||
else:
|
||||
import xdg.BaseDirectory
|
||||
if filename:
|
||||
return os.path.join(xdg.BaseDirectory.save_config_path("deluge"), filename)
|
||||
else:
|
||||
@ -570,13 +570,15 @@ class VersionSplit(object):
|
||||
"""
|
||||
def __init__(self, ver):
|
||||
ver = ver.lower()
|
||||
vs = ver.split("_") if "_" in ver else ver.split("-")
|
||||
vs = ver.replace("_", "-").split("-")
|
||||
self.version = [int(x) for x in vs[0].split(".")]
|
||||
self.suffix = None
|
||||
self.dev = False
|
||||
if len(vs) > 1:
|
||||
for s in ("rc", "alpha", "beta", "dev"):
|
||||
if s in vs[1][:len(s)]:
|
||||
self.suffix = vs[1]
|
||||
if vs[1].startswith(("rc", "alpha", "beta")):
|
||||
self.suffix = vs[1]
|
||||
if vs[-1] == 'dev':
|
||||
self.dev = True
|
||||
|
||||
def __cmp__(self, ver):
|
||||
"""
|
||||
@ -587,19 +589,8 @@ class VersionSplit(object):
|
||||
|
||||
"""
|
||||
|
||||
if self.version > ver.version or (self.suffix and self.suffix[:3] == "dev"):
|
||||
return 1
|
||||
if self.version < ver.version:
|
||||
return -1
|
||||
|
||||
if self.version == ver.version:
|
||||
if self.suffix == ver.suffix:
|
||||
return 0
|
||||
if self.suffix is None:
|
||||
return 1
|
||||
if ver.suffix is None:
|
||||
return -1
|
||||
if self.suffix < ver.suffix:
|
||||
return -1
|
||||
if self.suffix > ver.suffix:
|
||||
return 1
|
||||
# If there is no suffix we use z because we want final
|
||||
# to appear after alpha, beta, and rc alphabetically.
|
||||
v1 = [self.version, self.suffix or 'z', self.dev]
|
||||
v2 = [ver.version, ver.suffix or 'z', ver.dev]
|
||||
return cmp(v1, v2)
|
||||
|
@ -45,9 +45,9 @@ The format of the config file is two json encoded dicts:
|
||||
<version dict>
|
||||
<content dict>
|
||||
|
||||
The version dict contains two keys: file and format. The format version is
|
||||
controlled by the Config class. It should only be changed when anything below
|
||||
it is changed directly by the Config class. An example of this would be if we
|
||||
The version dict contains two keys: file and format. The format version is
|
||||
controlled by the Config class. It should only be changed when anything below
|
||||
it is changed directly by the Config class. An example of this would be if we
|
||||
changed the serializer for the content to something different.
|
||||
|
||||
The config file version is changed by the 'owner' of the config file. This is
|
||||
@ -93,13 +93,13 @@ def prop(func):
|
||||
def find_json_objects(s):
|
||||
"""
|
||||
Find json objects in a string.
|
||||
|
||||
|
||||
:param s: the string to find json objects in
|
||||
:type s: string
|
||||
|
||||
|
||||
:returns: a list of tuples containing start and end locations of json objects in the string `s`
|
||||
:rtype: [(start, end), ...]
|
||||
|
||||
|
||||
"""
|
||||
objects = []
|
||||
opens = 0
|
||||
@ -119,8 +119,8 @@ def find_json_objects(s):
|
||||
start = index + offset + 1
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""
|
||||
This class is used to access/create/modify config files
|
||||
@ -348,21 +348,21 @@ what is currently in the config and it could not convert the value
|
||||
return
|
||||
|
||||
objects = find_json_objects(data)
|
||||
|
||||
|
||||
if not len(objects):
|
||||
# No json objects found, try depickling it
|
||||
try:
|
||||
self.__config.update(pickle.loads(data))
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
elif len(objects) == 1:
|
||||
start, end = objects[0]
|
||||
try:
|
||||
self.__config.update(json.loads(data[start:end]))
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
elif len(objects) == 2:
|
||||
try:
|
||||
start, end = objects[0]
|
||||
@ -371,8 +371,8 @@ what is currently in the config and it could not convert the value
|
||||
self.__config.update(json.loads(data[start:end]))
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
|
||||
log.debug("Config %s version: %s.%s loaded: %s", filename,
|
||||
self.__version["format"], self.__version["file"], self.__config)
|
||||
|
||||
@ -396,26 +396,24 @@ what is currently in the config and it could not convert the value
|
||||
version = json.loads(data[start:end])
|
||||
start, end = objects[1]
|
||||
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
|
||||
self._save_timer.cancel()
|
||||
if self._save_timer and self._save_timer.active():
|
||||
self._save_timer.cancel()
|
||||
return
|
||||
except Exception, e:
|
||||
log.warning("Unable to open config file: %s", filename)
|
||||
|
||||
|
||||
except IOError, e:
|
||||
log.warning("Unable to open config file: %s because: %s", filename, e)
|
||||
|
||||
# Save the new config and make sure it's written to disk
|
||||
try:
|
||||
log.debug("Saving new config file %s", filename + ".new")
|
||||
f = open(filename + ".new", "wb")
|
||||
json.dump(self.__version, f, indent=2)
|
||||
json.dump(self.__version, f, indent=2)
|
||||
json.dump(self.__config, f, indent=2)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
f.close()
|
||||
except Exception, e:
|
||||
except IOError, e:
|
||||
log.error("Error writing new config file: %s", e)
|
||||
return False
|
||||
|
||||
|
@ -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
|
||||
@ -768,7 +775,10 @@ class Core(component.Component):
|
||||
"""
|
||||
if not path:
|
||||
path = self.config["download_location"]
|
||||
return deluge.common.free_space(path)
|
||||
try:
|
||||
return deluge.common.free_space(path)
|
||||
except InvalidPathError:
|
||||
return 0
|
||||
|
||||
@export
|
||||
def get_libtorrent_version(self):
|
||||
|
@ -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)
|
||||
|
@ -47,7 +47,11 @@ from twisted.internet import ssl, reactor, defer
|
||||
from OpenSSL import crypto, SSL
|
||||
from types import FunctionType
|
||||
|
||||
import deluge.rencode as rencode
|
||||
try:
|
||||
import rencode
|
||||
except ImportError:
|
||||
import deluge.rencode as rencode
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
||||
import deluge.component as component
|
||||
@ -90,13 +94,13 @@ def export(auth_level=AUTH_LEVEL_DEFAULT):
|
||||
def format_request(call):
|
||||
"""
|
||||
Format the RPCRequest message for debug printing
|
||||
|
||||
|
||||
:param call: the request
|
||||
:type call: a RPCRequest
|
||||
|
||||
|
||||
:returns: a formatted string for printing
|
||||
:rtype: str
|
||||
|
||||
|
||||
"""
|
||||
try:
|
||||
s = call[1] + "("
|
||||
@ -111,7 +115,7 @@ def format_request(call):
|
||||
return "UnicodeEncodeError, call: %s" % call
|
||||
else:
|
||||
return s
|
||||
|
||||
|
||||
class DelugeError(Exception):
|
||||
pass
|
||||
|
||||
@ -139,7 +143,7 @@ class DelugeRPCProtocol(Protocol):
|
||||
"""
|
||||
This method is called whenever data is received from a client. The
|
||||
only message that a client sends to the server is a RPC Request message.
|
||||
If the RPC Request message is valid, then the method is called in
|
||||
If the RPC Request message is valid, then the method is called in
|
||||
:meth:`dispatch`.
|
||||
|
||||
:param data: the data from the client. It should be a zlib compressed
|
||||
@ -187,7 +191,7 @@ class DelugeRPCProtocol(Protocol):
|
||||
:param data: the object that is to be sent to the client. This should
|
||||
be one of the RPC message types.
|
||||
:type data: object
|
||||
|
||||
|
||||
"""
|
||||
self.transport.write(zlib.compress(rencode.dumps(data)))
|
||||
|
||||
@ -254,7 +258,7 @@ class DelugeRPCProtocol(Protocol):
|
||||
try:
|
||||
ret = component.get("AuthManager").authorize(*args, **kwargs)
|
||||
if ret:
|
||||
self.factory.authorized_sessions[self.transport.sessionno] = ret
|
||||
self.factory.authorized_sessions[self.transport.sessionno] = (ret, args[0])
|
||||
self.factory.session_protocols[self.transport.sessionno] = self
|
||||
except Exception, e:
|
||||
sendError()
|
||||
@ -283,7 +287,7 @@ class DelugeRPCProtocol(Protocol):
|
||||
if method in self.factory.methods and self.transport.sessionno in self.factory.authorized_sessions:
|
||||
try:
|
||||
method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level
|
||||
auth_level = self.factory.authorized_sessions[self.transport.sessionno]
|
||||
auth_level = self.factory.authorized_sessions[self.transport.sessionno][0]
|
||||
if auth_level < method_auth_requirement:
|
||||
# This session is not allowed to call this method
|
||||
log.debug("Session %s is trying to call a method it is not authorized to call!", self.transport.sessionno)
|
||||
@ -338,7 +342,7 @@ class RPCServer(component.Component):
|
||||
self.factory = Factory()
|
||||
self.factory.protocol = DelugeRPCProtocol
|
||||
self.factory.session_id = -1
|
||||
|
||||
|
||||
# Holds the registered methods
|
||||
self.factory.methods = {}
|
||||
# Holds the session_ids and auth levels
|
||||
@ -417,26 +421,41 @@ class RPCServer(component.Component):
|
||||
def get_session_id(self):
|
||||
"""
|
||||
Returns the session id of the current RPC.
|
||||
|
||||
|
||||
:returns: the session id, this will be -1 if no connections have been made
|
||||
:rtype: int
|
||||
|
||||
"""
|
||||
return self.factory.session_id
|
||||
|
||||
|
||||
def get_session_user(self):
|
||||
"""
|
||||
Returns the username calling the current RPC.
|
||||
|
||||
:returns: the username of the user calling the current RPC
|
||||
:rtype: string
|
||||
|
||||
"""
|
||||
session_id = self.get_session_id()
|
||||
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
|
||||
return ""
|
||||
|
||||
def is_session_valid(self, session_id):
|
||||
"""
|
||||
Checks if the session is still valid, eg, if the client is still connected.
|
||||
|
||||
|
||||
:param session_id: the session id
|
||||
:type session_id: int
|
||||
|
||||
|
||||
:returns: True if the session is valid
|
||||
:rtype: bool
|
||||
|
||||
|
||||
"""
|
||||
return session_id in self.factory.authorized_sessions
|
||||
|
||||
|
||||
def emit_event(self, event):
|
||||
"""
|
||||
Emits the event to interested clients.
|
||||
|
@ -393,12 +393,11 @@ class Torrent(object):
|
||||
else:
|
||||
status = self.status
|
||||
|
||||
if self.is_finished and (self.options["stop_at_ratio"] or self.config["stop_seed_at_ratio"]):
|
||||
if self.is_finished and self.options["stop_at_ratio"]:
|
||||
# We're a seed, so calculate the time to the 'stop_share_ratio'
|
||||
if not status.upload_payload_rate:
|
||||
return 0
|
||||
stop_ratio = self.config["stop_seed_ratio"] if self.config["stop_seed_at_ratio"] else self.options["stop_ratio"]
|
||||
|
||||
stop_ratio = self.options["stop_ratio"]
|
||||
return ((status.all_time_download * stop_ratio) - status.all_time_upload) / status.upload_payload_rate
|
||||
|
||||
left = status.total_wanted - status.total_done
|
||||
@ -761,13 +760,8 @@ class Torrent(object):
|
||||
|
||||
if self.handle.is_finished():
|
||||
# If the torrent has already reached it's 'stop_seed_ratio' then do not do anything
|
||||
if self.config["stop_seed_at_ratio"] or self.options["stop_at_ratio"]:
|
||||
if self.options["stop_at_ratio"]:
|
||||
ratio = self.options["stop_ratio"]
|
||||
else:
|
||||
ratio = self.config["stop_seed_ratio"]
|
||||
|
||||
if self.get_ratio() >= ratio:
|
||||
if self.options["stop_at_ratio"]:
|
||||
if self.get_ratio() >= self.options["stop_ratio"]:
|
||||
#XXX: This should just be returned in the RPC Response, no event
|
||||
#self.signals.emit_event("torrent_resume_at_stop_ratio")
|
||||
return
|
||||
@ -794,6 +788,14 @@ class Torrent(object):
|
||||
|
||||
def move_storage(self, dest):
|
||||
"""Move a torrent's storage location"""
|
||||
if not os.path.exists(dest):
|
||||
try:
|
||||
# Try to make the destination path if it doesn't exist
|
||||
os.makedirs(dest)
|
||||
except IOError, e:
|
||||
log.exception(e)
|
||||
log.error("Could not move storage for torrent %s since %s does not exist and could not create the directory.", self.torrent_id, dest)
|
||||
return False
|
||||
try:
|
||||
self.handle.move_storage(dest.encode("utf8"))
|
||||
except:
|
||||
|
@ -192,6 +192,8 @@ class TorrentManager(component.Component):
|
||||
self.on_alert_metadata_received)
|
||||
self.alerts.register_handler("file_error_alert",
|
||||
self.on_alert_file_error)
|
||||
self.alerts.register_handler("file_completed_alert",
|
||||
self.on_alert_file_completed)
|
||||
|
||||
def start(self):
|
||||
# Get the pluginmanager reference
|
||||
@ -256,16 +258,13 @@ class TorrentManager(component.Component):
|
||||
|
||||
def update(self):
|
||||
for torrent_id, torrent in self.torrents.items():
|
||||
if self.config["stop_seed_at_ratio"] or torrent.options["stop_at_ratio"] and torrent.state not in ("Checking", "Allocating", "Paused", "Queued"):
|
||||
if torrent.options["stop_at_ratio"] and torrent.state not in ("Checking", "Allocating", "Paused", "Queued"):
|
||||
# If the global setting is set, but the per-torrent isn't.. Just skip to the next torrent
|
||||
# This is so that a user can turn-off the stop at ratio option on a per-torrent basis
|
||||
if self.config["stop_seed_at_ratio"] and not torrent.options["stop_at_ratio"]:
|
||||
if not torrent.options["stop_at_ratio"]:
|
||||
continue
|
||||
stop_ratio = self.config["stop_seed_ratio"]
|
||||
if torrent.options["stop_at_ratio"]:
|
||||
stop_ratio = torrent.options["stop_ratio"]
|
||||
if torrent.get_ratio() >= stop_ratio and torrent.is_finished:
|
||||
if self.config["remove_seed_at_ratio"] or torrent.options["remove_at_ratio"]:
|
||||
if torrent.get_ratio() >= torrent.options["stop_ratio"] and torrent.is_finished:
|
||||
if torrent.options["remove_at_ratio"]:
|
||||
self.remove(torrent_id)
|
||||
break
|
||||
if not torrent.handle.is_paused():
|
||||
@ -476,6 +475,7 @@ class TorrentManager(component.Component):
|
||||
# Emit the torrent_added signal
|
||||
component.get("EventManager").emit(TorrentAddedEvent(torrent.torrent_id))
|
||||
|
||||
log.info("Torrent %s added by user: %s", torrent.get_status(["name"])["name"], component.get("RPCServer").get_session_user())
|
||||
return torrent.torrent_id
|
||||
|
||||
def load_torrent(self, torrent_id):
|
||||
@ -515,6 +515,8 @@ class TorrentManager(component.Component):
|
||||
if torrent_id not in self.torrents:
|
||||
raise InvalidTorrentError("torrent_id not in session")
|
||||
|
||||
torrent_name = self.torrents[torrent_id].get_status(["name"])["name"]
|
||||
|
||||
# Emit the signal to the clients
|
||||
component.get("EventManager").emit(PreTorrentRemovedEvent(torrent_id))
|
||||
|
||||
@ -562,7 +564,7 @@ class TorrentManager(component.Component):
|
||||
|
||||
# Emit the signal to the clients
|
||||
component.get("EventManager").emit(TorrentRemovedEvent(torrent_id))
|
||||
|
||||
log.info("Torrent %s removed by user: %s", torrent_name, component.get("RPCServer").get_session_user())
|
||||
return True
|
||||
|
||||
def load_state(self):
|
||||
@ -1012,3 +1014,9 @@ class TorrentManager(component.Component):
|
||||
except:
|
||||
return
|
||||
torrent.update_state()
|
||||
|
||||
def on_alert_file_completed(self, alert):
|
||||
log.debug("file_completed_alert: %s", alert.message())
|
||||
torrent_id = str(alert.handle.info_hash())
|
||||
component.get("EventManager").emit(
|
||||
TorrentFileCompletedEvent(torrent_id, alert.index))
|
||||
|
@ -164,6 +164,22 @@ class TorrentResumedEvent(DelugeEvent):
|
||||
"""
|
||||
self._args = [torrent_id]
|
||||
|
||||
class TorrentFileCompletedEvent(DelugeEvent):
|
||||
"""
|
||||
Emitted when a file completes.
|
||||
|
||||
This will only work with libtorrent 0.15 or greater.
|
||||
|
||||
"""
|
||||
def __init__(self, torrent_id, index):
|
||||
"""
|
||||
:param torrent_id: the torrent_id
|
||||
:type torrent_id: string
|
||||
:param index: the file index
|
||||
:type index: int
|
||||
"""
|
||||
self._args = [torrent_id, index]
|
||||
|
||||
class NewVersionAvailableEvent(DelugeEvent):
|
||||
"""
|
||||
Emitted when a more recent version of Deluge is available.
|
||||
|
@ -197,6 +197,11 @@ this should be an IP address", metavar="IFACE",
|
||||
write_pidfile()
|
||||
|
||||
# Setup the logger
|
||||
try:
|
||||
# Try to make the logfile's directory if it doesn't exist
|
||||
os.makedirs(os.path.abspath(os.path.dirname(options.logfile)))
|
||||
except:
|
||||
pass
|
||||
deluge.log.setupLogger(level=options.loglevel, filename=options.logfile)
|
||||
from deluge.log import LOG as log
|
||||
|
||||
|
@ -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."
|
||||
|
@ -2,7 +2,7 @@
|
||||
# core.py
|
||||
#
|
||||
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 John Garland <johnnybg@gmail.com>
|
||||
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
@ -38,6 +38,7 @@ import os
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from wsgiref.handlers import format_date_time
|
||||
from urlparse import urljoin
|
||||
import shutil
|
||||
|
||||
from twisted.internet.task import LoopingCall
|
||||
@ -281,7 +282,7 @@ class Core(CorePluginBase):
|
||||
d = f
|
||||
if f.check(error.PageRedirect):
|
||||
# Handle redirect errors
|
||||
location = error_msg.split(" to ")[1]
|
||||
location = urljoin(self.config["url"], error_msg.split(" to ")[1])
|
||||
if "Moved Permanently" in error_msg:
|
||||
log.debug("Setting blocklist url to %s", location)
|
||||
self.config["url"] = location
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# decompressers.py
|
||||
#
|
||||
# Copyright (C) 2009 John Garland <johnnybg@gmail.com>
|
||||
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# detect.py
|
||||
#
|
||||
# Copyright (C) 2009 John Garland <johnnybg@gmail.com>
|
||||
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# readers.py
|
||||
#
|
||||
# Copyright (C) 2009 John Garland <johnnybg@gmail.com>
|
||||
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
|
@ -35,7 +35,7 @@ from setuptools import setup
|
||||
|
||||
__plugin_name__ = "Blocklist"
|
||||
__author__ = "John Garland"
|
||||
__author_email__ = "johnnybg@gmail.com"
|
||||
__author_email__ = "johnnybg+deluge@gmail.com"
|
||||
__version__ = "1.2"
|
||||
__url__ = "http://deluge-torrent.org"
|
||||
__license__ = "GPLv3"
|
||||
|
@ -1,54 +0,0 @@
|
||||
#
|
||||
# __init__.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
|
||||
from deluge.plugins.init import PluginInitBase
|
||||
|
||||
class CorePlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from core import Core as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(CorePlugin, self).__init__(plugin_name)
|
||||
|
||||
class GtkUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from gtkui import GtkUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||
|
||||
class WebUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from webui import WebUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(WebUIPlugin, self).__init__(plugin_name)
|
@ -1,40 +0,0 @@
|
||||
#
|
||||
# common.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
|
||||
import pkg_resources
|
||||
import os.path
|
||||
|
||||
def get_resource(filename):
|
||||
return pkg_resources.resource_filename("example", os.path.join("data", filename))
|
@ -1,55 +0,0 @@
|
||||
#
|
||||
# core.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.plugins.pluginbase import CorePluginBase
|
||||
import deluge.component as component
|
||||
import deluge.configmanager
|
||||
from deluge.core.rpcserver import export
|
||||
|
||||
class Core(CorePluginBase):
|
||||
def enable(self):
|
||||
log.debug("Example core plugin enabled!")
|
||||
|
||||
def disable(self):
|
||||
log.debug("Example core plugin disabled!")
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
### Exported RPC methods ###
|
||||
@export()
|
||||
def example_method(self):
|
||||
pass
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
Script: example.js
|
||||
The client-side javascript code for the Example plugin.
|
||||
|
||||
Copyright:
|
||||
(C) Damien Churchill 2009 <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.
|
||||
*/
|
||||
|
||||
ExamplePlugin = Ext.extend(Deluge.Plugin, {
|
||||
constructor: function(config) {
|
||||
config = Ext.apply({
|
||||
name: "Example"
|
||||
}, config);
|
||||
ExamplePlugin.superclass.constructor.call(this, config);
|
||||
},
|
||||
|
||||
onDisable: function() {
|
||||
Deluge.Preferences.removePage(this.prefsPage);
|
||||
},
|
||||
|
||||
onEnable: function() {
|
||||
this.prefsPage = new ExamplePreferencesPanel();
|
||||
this.prefsPage = Deluge.Preferences.addPage(this.prefsPage);
|
||||
}
|
||||
});
|
||||
new ExamplePlugin();
|
@ -1,48 +0,0 @@
|
||||
#
|
||||
# gtkui.py
|
||||
#
|
||||
# Copyright (C) 2008 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
|
||||
import gtk
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge.plugins.pluginbase import GtkPluginBase
|
||||
import deluge.component as component
|
||||
import deluge.common
|
||||
|
||||
class GtkUI(GtkPluginBase):
|
||||
def enable(self):
|
||||
pass
|
||||
def disable(self):
|
||||
pass
|
@ -1,54 +0,0 @@
|
||||
#
|
||||
# webui.py
|
||||
#
|
||||
# Copyright (C) 2009 Martijn Voncken <mvoncken@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge import component
|
||||
from deluge.plugins.pluginbase import WebPluginBase
|
||||
|
||||
from common import get_resource
|
||||
|
||||
class WebUI(WebPluginBase):
|
||||
|
||||
scripts = [get_resource("example.js")]
|
||||
|
||||
# The enable and disable methods are not scrictly required on the WebUI
|
||||
# plugins. They are only here if you need to register images/stylesheets
|
||||
# with the webserver.
|
||||
def enable(self):
|
||||
log.debug("Example Web plugin enabled!")
|
||||
|
||||
def disable(self):
|
||||
log.debug("Example Web plugin disabled!")
|
@ -1,67 +0,0 @@
|
||||
#
|
||||
# setup.py
|
||||
#
|
||||
# Copyright (C) 2008 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
#
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
__plugin_name__ = "Example"
|
||||
__author__ = "Andrew Resch"
|
||||
__author_email__ = "andrewresch@gmail.com"
|
||||
__version__ = "1.2"
|
||||
__url__ = "http://deluge-torrent.org"
|
||||
__license__ = "GPLv3"
|
||||
__description__ = "Example plugin"
|
||||
__long_description__ = __description__
|
||||
__pkg_data__ = {__plugin_name__.lower(): []}
|
||||
|
||||
setup(
|
||||
name=__plugin_name__,
|
||||
version=__version__,
|
||||
description=__description__,
|
||||
author=__author__,
|
||||
author_email=__author_email__,
|
||||
url=__url__,
|
||||
license=__license__,
|
||||
long_description=__long_description__,
|
||||
|
||||
packages=[__plugin_name__.lower()],
|
||||
package_data = __pkg_data__,
|
||||
|
||||
entry_points="""
|
||||
[deluge.plugin.core]
|
||||
%s = %s:CorePlugin
|
||||
[deluge.plugin.gtkui]
|
||||
%s = %s:GtkUIPlugin
|
||||
[deluge.plugin.webui]
|
||||
%s = %s:WebUIPlugin
|
||||
""" % ((__plugin_name__, __plugin_name__.lower())*3)
|
||||
)
|
@ -84,8 +84,11 @@ class Core(CorePluginBase):
|
||||
if event in self.registered_events:
|
||||
continue
|
||||
|
||||
def event_handler(torrent_id):
|
||||
self.execute_commands(torrent_id, command[EXECUTE_EVENT])
|
||||
def create_event_handler(event):
|
||||
def event_handler(torrent_id):
|
||||
self.execute_commands(torrent_id, event)
|
||||
return event_handler
|
||||
event_handler = create_event_handler(event)
|
||||
event_manager.register_event_handler(EVENT_MAP[event], event_handler)
|
||||
self.registered_events[event] = event_handler
|
||||
|
||||
@ -93,20 +96,23 @@ class Core(CorePluginBase):
|
||||
|
||||
def execute_commands(self, torrent_id, event):
|
||||
torrent = component.get("TorrentManager").torrents[torrent_id]
|
||||
info = torrent.get_status(["name", "save_path", "move_completed", "move_on_completed_path"])
|
||||
info = torrent.get_status(["name", "save_path", "move_on_completed", "move_on_completed_path"])
|
||||
|
||||
# Grab the torrent name and save path
|
||||
torrent_name = info["name"]
|
||||
if event == "completed":
|
||||
save_path = info["move_on_completed_path"] if info ["move_completed"] else info["save_path"]
|
||||
if event == "complete":
|
||||
save_path = info["move_on_completed_path"] if info ["move_on_completed"] else info["save_path"]
|
||||
else:
|
||||
save_path = info["save_path"]
|
||||
|
||||
log.debug("[execute] Running commands for %s", event)
|
||||
|
||||
# Go through and execute all the commands
|
||||
for command in self.config["commands"]:
|
||||
if command[EXECUTE_EVENT] == event:
|
||||
command = os.path.expandvars(command[EXECUTE_COMMAND])
|
||||
command = os.path.expanduser(command)
|
||||
log.debug("[execute] running %s", command)
|
||||
p = Popen([command, torrent_id, torrent_name, save_path], stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
if p.wait() != 0:
|
||||
log.warn("Execute command failed with exit code %d", p.returncode)
|
||||
@ -123,6 +129,7 @@ class Core(CorePluginBase):
|
||||
def add_command(self, event, command):
|
||||
command_id = hashlib.sha1(str(time.time())).hexdigest()
|
||||
self.config["commands"].append((command_id, event, command))
|
||||
self.config.save()
|
||||
component.get("EventManager").emit(ExecuteCommandAddedEvent(command_id, event, command))
|
||||
|
||||
@export
|
||||
@ -136,6 +143,7 @@ class Core(CorePluginBase):
|
||||
self.config["commands"].remove(command)
|
||||
component.get("EventManager").emit(ExecuteCommandRemovedEvent(command_id))
|
||||
break
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def save_command(self, command_id, event, cmd):
|
||||
@ -143,3 +151,4 @@ class Core(CorePluginBase):
|
||||
if command[EXECUTE_ID] == command_id:
|
||||
self.config["commands"][i] = (command_id, event, cmd)
|
||||
break
|
||||
self.config.save()
|
||||
|
@ -99,7 +99,7 @@ Deluge.ux.EditExecuteCommandWindow = Ext.extend(Deluge.ux.ExecuteWindowBase, {
|
||||
},
|
||||
|
||||
onSaveClick: function() {
|
||||
var values = this.form.getForm().getValues();
|
||||
var values = this.form.getForm().getFieldValues();
|
||||
deluge.client.execute.save_command(this.command.id, values.event, values.command, {
|
||||
success: function() {
|
||||
this.fireEvent('commandedit', this, values.event, values.command);
|
||||
@ -124,7 +124,7 @@ Deluge.ux.AddExecuteCommandWindow = Ext.extend(Deluge.ux.ExecuteWindowBase, {
|
||||
},
|
||||
|
||||
onAddClick: function() {
|
||||
var values = this.form.getForm().getValues();
|
||||
var values = this.form.getForm().getFieldValues();
|
||||
deluge.client.execute.add_command(values.event, values.command, {
|
||||
success: function() {
|
||||
this.fireEvent('commandadd', this, values.event, values.command);
|
||||
|
@ -1,55 +0,0 @@
|
||||
#
|
||||
# feeder/__init__.py
|
||||
#
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
|
||||
from deluge.plugins.init import PluginInitBase
|
||||
|
||||
class CorePlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from core import Core as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(CorePlugin, self).__init__(plugin_name)
|
||||
|
||||
class GtkUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from gtkui import GtkUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||
|
||||
class WebUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from webui import WebUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(WebUIPlugin, self).__init__(plugin_name)
|
@ -1,432 +0,0 @@
|
||||
#
|
||||
# core.py
|
||||
#
|
||||
# Copyright (C) 2008-2009 Fredrik Eriksson <feeder@winterbird.org>
|
||||
# Copyright (C) 2009 David Mohr <david@mcbf.net>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import feedparser # for parsing rss feeds
|
||||
import threading # for threaded updates
|
||||
import re # for regular expressions
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.plugins.pluginbase import CorePluginBase
|
||||
import deluge.component as component
|
||||
import deluge.configmanager
|
||||
from deluge.core.rpcserver import export
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
"feeds": {},
|
||||
"filters": {},
|
||||
"updatetime": 15,
|
||||
"history": []
|
||||
}
|
||||
|
||||
# Helper classes
|
||||
|
||||
class Feed:
|
||||
"""
|
||||
Class for the Feed object (containging feed configurations)
|
||||
"""
|
||||
def __init__(self):
|
||||
self.url = ""
|
||||
self.cookies = {}
|
||||
self.updatetime = 15
|
||||
|
||||
def get_config(self):
|
||||
try:
|
||||
tmp = self.cookies
|
||||
except Exception, e:
|
||||
log.debug("Old feed without cookies... updating")
|
||||
self.cookies = {}
|
||||
return {'url': self.url, 'updatetime': self.updatetime, 'cookies': self.cookies}
|
||||
|
||||
def set_config(self, config):
|
||||
self.url = config['url']
|
||||
self.updatetime = config['updatetime']
|
||||
self.cookies = config['cookies']
|
||||
|
||||
|
||||
class Filter:
|
||||
"""
|
||||
Class for the Filter object (containing filter configurations)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.regex = ""
|
||||
self.feeds = [] #TODO activate filter per feed
|
||||
self.all_feeds = True
|
||||
self.active = True
|
||||
|
||||
# by default, set the configuration to match
|
||||
# the per-torrent settings in deluge
|
||||
def_conf = component.get("Core").get_config()
|
||||
self.max_download_speed = def_conf['max_download_speed_per_torrent']
|
||||
self.max_upload_speed = def_conf['max_upload_speed_per_torrent']
|
||||
self.max_connections = def_conf['max_connections_per_torrent']
|
||||
self.max_upload_slots = def_conf['max_upload_slots_per_torrent']
|
||||
self.prioritize_first_last_pieces = def_conf['prioritize_first_last_pieces']
|
||||
self.auto_managed = def_conf['auto_managed']
|
||||
self.download_location = def_conf['download_location']
|
||||
|
||||
self.stop_at_ratio = def_conf['stop_seed_at_ratio']
|
||||
self.stop_ratio = def_conf['stop_seed_ratio']
|
||||
self.remove_at_ratio = def_conf['remove_seed_at_ratio']
|
||||
|
||||
def get_config(self):
|
||||
def_conf = component.get("Core").get_config()
|
||||
|
||||
try:
|
||||
tmp = self.active
|
||||
except Exception, e:
|
||||
log.debug("Old filter detected (pre 0.3), updating...")
|
||||
self.active = True
|
||||
|
||||
try:
|
||||
tmp = self.stop_at_ratio
|
||||
tmp = self.stop_ratio
|
||||
tmp = self.remove_at_ratio
|
||||
except:
|
||||
log.debug("Old filter detected (pre 0.4), updating...")
|
||||
self.stop_at_ratio = def_conf['stop_seed_at_ratio']
|
||||
self.stop_ratio = def_conf['stop_seed_ratio']
|
||||
self.remove_at_ratio = def_conf['remove_seed_at_ratio']
|
||||
|
||||
conf = {
|
||||
'regex': self.regex,
|
||||
'feeds': self.feeds,
|
||||
'all_feeds': self.all_feeds,
|
||||
'active' : self.active,
|
||||
'max_download_speed': self.max_download_speed,
|
||||
'max_upload_speed': self.max_upload_speed,
|
||||
'max_connections': self.max_connections,
|
||||
'max_upload_slots': self.max_upload_slots,
|
||||
'prioritize_first_last_pieces': self.prioritize_first_last_pieces,
|
||||
'auto_managed': self.auto_managed,
|
||||
'download_location':self.download_location,
|
||||
'remove_at_ratio':self.remove_at_ratio,
|
||||
'stop_ratio': self.stop_ratio,
|
||||
'stop_at_ratio': self.stop_at_ratio }
|
||||
|
||||
return conf
|
||||
|
||||
def set_config(self, conf):
|
||||
self.regex = conf['regex']
|
||||
self.feeds = conf['feeds']
|
||||
self.all_feeds = conf['all_feeds']
|
||||
self.active = conf['active']
|
||||
self.max_download_speed = int(conf['max_download_speed'])
|
||||
self.max_upload_speed = int(conf['max_upload_speed'])
|
||||
self.max_connections = int(conf['max_connections'])
|
||||
self.max_upload_slots = int(conf['max_upload_slots'])
|
||||
self.prioritize_first_last_pieces = conf['prioritize_first_last_pieces']
|
||||
self.auto_managed = conf['auto_managed']
|
||||
self.download_location = conf['download_location']
|
||||
self.remove_at_ratio = conf['remove_at_ratio']
|
||||
self.stop_ratio = float(conf['stop_ratio'])
|
||||
self.stop_at_ratio = conf['stop_at_ratio']
|
||||
|
||||
|
||||
class Core(CorePluginBase):
|
||||
def enable(self):
|
||||
self.config = deluge.configmanager.ConfigManager("feeder.conf", DEFAULT_PREFS)
|
||||
self.feeds = {}
|
||||
self.timers = {}
|
||||
self.history = self.config['history']
|
||||
self.time = 0
|
||||
|
||||
# Setting default timer to configured update time
|
||||
for feed in self.config['feeds']:
|
||||
self.timers[feed] = LoopingCall(self.update_feed, feed)
|
||||
self.timers[feed].start( self.config['feeds'][feed].updatetime * 60)
|
||||
|
||||
|
||||
def disable(self):
|
||||
self.config['history'] = self.history
|
||||
self.config.save()
|
||||
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
#=================Exported functions==================
|
||||
|
||||
@export
|
||||
def set_config(self, config):
|
||||
"""sets the config dictionary"""
|
||||
for key in config.keys():
|
||||
self.config[key] = config[key]
|
||||
self.config.save()
|
||||
|
||||
####################Configuration Getters##################
|
||||
|
||||
@export
|
||||
def get_config(self):
|
||||
"""returns the config dictionary"""
|
||||
return self.config.config
|
||||
|
||||
@export
|
||||
def get_feed_config(self, feedname):
|
||||
"""Returns configuration for a feed"""
|
||||
return self.config['feeds'][feedname].get_config()
|
||||
|
||||
@export
|
||||
def get_filter_config(self, filtername):
|
||||
"""Returns a configuration for a filter"""
|
||||
return self.config['filters'][filtername].get_config()
|
||||
|
||||
####################Information Getters####################
|
||||
|
||||
@export
|
||||
def get_feeds(self):
|
||||
"""Returns a list of the configured feeds"""
|
||||
feeds = []
|
||||
for feedname in self.config['feeds']:
|
||||
feeds.append(feedname)
|
||||
feeds.sort(key=string.lower)
|
||||
return feeds
|
||||
|
||||
@export
|
||||
def get_filters(self):
|
||||
"""Returns a list of all available filters"""
|
||||
filters = []
|
||||
for filter in self.config['filters']:
|
||||
filters.append(filter)
|
||||
filters.sort(key=string.lower)
|
||||
return filters
|
||||
|
||||
@export
|
||||
def get_items(self, feedname):
|
||||
"""Returns a dictionary with feedname:link"""
|
||||
try:
|
||||
items = {}
|
||||
feed = self.feeds[feedname]
|
||||
for entry in feed['entries']:
|
||||
items[entry.title] = entry.link
|
||||
except Exception, e:
|
||||
items = {}
|
||||
log.warning("Feed '%s' not loaded", feedname)
|
||||
return items
|
||||
|
||||
@export
|
||||
def test_filter(self, regex):
|
||||
filters = { "to_test":Filter() }
|
||||
conf = filters["to_test"].get_config()
|
||||
conf["regex"] = regex
|
||||
filters["to_test"].set_config(conf)
|
||||
hits = {}
|
||||
for feed in self.feeds:
|
||||
hits.update(self.run_filters(feed, filters, test=True))
|
||||
return hits
|
||||
|
||||
@export
|
||||
def add_feed(self, config):
|
||||
"""adds/updates a feed and, for whatever reason, sets the default timeout"""
|
||||
|
||||
# save the feedname and remove it from the config
|
||||
feedname = config['name']
|
||||
del config['name']
|
||||
|
||||
# check if the feed already exists and save config
|
||||
try:
|
||||
conf = self.config['feeds'][feedname].get_config()
|
||||
del self.config['feeds'][feedname]
|
||||
except Exception, e:
|
||||
conf = {}
|
||||
|
||||
# update configuration
|
||||
for var in config:
|
||||
conf[var] = config[var]
|
||||
|
||||
# save as default update time
|
||||
try:
|
||||
self.config['updatetime'] = config['updatetime']
|
||||
except Exception, e:
|
||||
log.warning("updatetime not set when adding feed %s", feedname)
|
||||
|
||||
# Create the new feed
|
||||
newfeed = Feed()
|
||||
newfeed.set_config(conf)
|
||||
|
||||
# Add a timer (with default timer for now, since we can't get ttl just yet)...
|
||||
self.timers[feedname] = LoopingCall(self.update_feed, feedname)
|
||||
|
||||
# Save the new feed
|
||||
self.config['feeds'].update({feedname: newfeed })
|
||||
self.config.save()
|
||||
|
||||
# Start the timeout, which will also update the new feed
|
||||
self.timers[feedname].start(newfeed.updatetime * 60)
|
||||
|
||||
@export
|
||||
def remove_feed(self, feedname):
|
||||
"""Remove a feed"""
|
||||
if self.feeds.has_key(feedname): # Check if we have the feed saved and remove it
|
||||
del self.feeds[feedname]
|
||||
if self.timers.has_key(feedname): # Check if we have a timer for this feed and remove it
|
||||
self.timers[feedname].stop()
|
||||
del self.timers[feedname]
|
||||
if self.config['feeds'].has_key(feedname): # Check if we have the feed in the configuration and remove it
|
||||
del self.config['feeds'][feedname]
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def add_filter(self, name):
|
||||
"""Adds a new filter to the configuration"""
|
||||
if not self.config['filters'].has_key(name): # we don't want to add a filter that already exists
|
||||
self.config['filters'][name] = Filter()
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def set_filter_config(self, filtername, conf):
|
||||
"""Changes the options for a filter"""
|
||||
oldconf = self.config['filters'][filtername].get_config()
|
||||
for item in conf:
|
||||
oldconf[item] = conf[item]
|
||||
|
||||
self.config['filters'][filtername].set_config(oldconf)
|
||||
self.config.save()
|
||||
for feed in self.config['feeds']: # we would like to check if the filter now matches something new
|
||||
self.run_filters(feed)
|
||||
|
||||
@export
|
||||
def remove_filter(self, name):
|
||||
"""Removes a filter"""
|
||||
if self.config['filters'].has_key(name): # Can't remove a filter that doesn't exists
|
||||
del self.config['filters'][name]
|
||||
self.config.save()
|
||||
|
||||
#=================Internal functions================
|
||||
|
||||
def update_feed(self, feedname):
|
||||
"""Start a thread to update a single feed"""
|
||||
threading.Thread(
|
||||
target=self.update_feed_thread,
|
||||
args=(self.on_feed_updated, feedname)).start()
|
||||
|
||||
# Need to return true to not destoy timer...
|
||||
return True
|
||||
|
||||
def update_feed_thread(self, callback, feedname):
|
||||
"""updates a feed"""
|
||||
feed = self.config['feeds'][feedname]
|
||||
try:
|
||||
self.feeds[feedname] = feedparser.parse(feed.url)
|
||||
except Exception, e:
|
||||
log.warning("Error parsing feed %s: %s", feedname, e)
|
||||
else:
|
||||
callback(feedname)
|
||||
|
||||
def on_feed_updated(self, feedname):
|
||||
"""Run stuff when a feed has been updated"""
|
||||
|
||||
# Not all feeds contain a ttl value, but if it does
|
||||
# we would like to obey it
|
||||
try:
|
||||
if not self.feeds[feedname].ttl == self.config['feeds'][feedname].updatetime:
|
||||
log.debug("feed '%s' request a ttl of %s, updating timer", feedname, self.feeds[feedname].ttl)
|
||||
self.config['feeds'][feedname].updatetime = self.feeds[feedname].ttl
|
||||
self.timers[feedname].stop()
|
||||
self.timers[feedname].start(self.config['feeds'][feedname].updatetime * 60)
|
||||
except Exception, e:
|
||||
log.debug("feed '%s' has no ttl set, will use default timer", feedname)
|
||||
|
||||
# Run filters on the feed
|
||||
self.run_filters(feedname)
|
||||
|
||||
def run_filters(self, feedname, filters={}, test=False):
|
||||
"""Test all available filters on the given feed"""
|
||||
if not filters:
|
||||
filters = self.config['filters']
|
||||
log.debug("will test filters %s", filters)
|
||||
hits = {}
|
||||
# Test every entry...
|
||||
for entry in self.feeds[feedname]['entries']:
|
||||
# ...and every filter
|
||||
for filter in filters:
|
||||
# We need to be able to run feeds saved before implementation of actiave/deactivate filter (pre 0.3) TODO
|
||||
try:
|
||||
if not filters[filter].active:
|
||||
continue
|
||||
except:
|
||||
log.debug("old filter, will assume filter is activated")
|
||||
|
||||
if filters[filter].regex == "": # we don't want a empty regex...
|
||||
log.warning("Filter '%s' has not been configured, ignoring!", filter)
|
||||
continue
|
||||
|
||||
# if the filter isn't supposed to be run on this feed we don't want to run it...
|
||||
# if filter.all_feeds or self.config['filters'][filter].feeds.has_element(feedname) : # ...apparently has_element doesn't work on arrays... TODO
|
||||
if self.test_filter(entry, filters[filter].regex):
|
||||
if test:
|
||||
hits[entry.title] = entry.link
|
||||
else:
|
||||
opts = filters[filter].get_config()
|
||||
#remove filter options that should not be passed on to the torrent.
|
||||
del opts['regex']
|
||||
del opts['feeds']
|
||||
del opts['all_feeds']
|
||||
|
||||
# history patch from Darrell Enns, slightly modified :)
|
||||
# check history to prevent multiple adds of the same torrent
|
||||
log.debug("testing %s", entry.link)
|
||||
if not entry.link in self.history:
|
||||
self.add_torrent(entry.link, opts, self.feeds[feedname].cookies)
|
||||
self.history.append(entry.link)
|
||||
|
||||
#limit history to 50 entries
|
||||
if len(self.history)>50:
|
||||
self.history=self.history[-50:]
|
||||
log.debug("wrapping history")
|
||||
else:
|
||||
log.debug("'%s' is in history, will not download", entry.link)
|
||||
return hits
|
||||
|
||||
|
||||
def test_filter(self, entry, filter):
|
||||
"""Tests a filter to a given rss entry"""
|
||||
f = re.compile(filter, re.IGNORECASE)
|
||||
if f.search(entry.title) or f.search(entry.link):
|
||||
log.debug("RSS item '%s' matches filter '%s'", entry.title, filter)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def add_torrent(self, url, torrent_options, headers):
|
||||
log.debug("Attempting to add torrent %s", url)
|
||||
component.get("Core").add_torrent_url(url, torrent_options, headers)
|
@ -1,13 +0,0 @@
|
||||
$def with (entries, feedname)
|
||||
$:render.header("things", '')
|
||||
<div class="panel" >
|
||||
<div>
|
||||
<h3>Feed items for feed $feedname</h3>
|
||||
<ul>
|
||||
$entries
|
||||
</ul>
|
||||
</div>
|
||||
<a href="/config/feeder">back to config</a>
|
||||
</div>
|
||||
|
||||
$:render.footer()
|
@ -1,50 +0,0 @@
|
||||
$def with (filter_settings_form, filter)
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Deluge:things</title>
|
||||
<link rel="icon" href="/static/images/deluge-icon.png" type="image/png" />
|
||||
<link rel="shortcut icon" href="/static/images/deluge-icon.png" type="image/png" />
|
||||
<link rel="stylesheet" type="text/css" href="/template_style.css" />
|
||||
<script language="javascript" src="/static/deluge.js"></script>
|
||||
<script language="javascript" src="/static/mootools-1.2-core.js"></script>
|
||||
|
||||
<script language="javascript" src="/static/mootools-1.2-more.js"></script>
|
||||
<script language="javascript" src="/static/mooui.js"></script>
|
||||
<script language="javascript" src="/static/deluge-moo.js"></script>
|
||||
<script language="javascript" src="/gettext.js"></script>
|
||||
<script language="javascript" src="/label/data/label.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<form method="post" action='$base/feeder/filter_settings/$filter'>
|
||||
<div class="info">
|
||||
<h3>Filter</h3>
|
||||
<table>
|
||||
$:(filter_settings_form.as_table(["regex", "active"]))
|
||||
</table>
|
||||
|
||||
<h3>Speed</h3>
|
||||
<table>
|
||||
$:(filter_settings_form.as_table(["max_download_speed", "max_upload_speed", "max_upload_slots", "max_connections"]))
|
||||
</table>
|
||||
|
||||
<h3>Seed options</h3>
|
||||
<table>
|
||||
$:(filter_settings_form.as_table(["stop_ratio", "stop_at_ratio", "remove_at_ratio"]))
|
||||
</table>
|
||||
|
||||
<h3>Other options</h3>
|
||||
<table>
|
||||
$:(filter_settings_form.as_table(["prioritize_first_last_pieces", "auto_managed", "download_location"]))
|
||||
</table>
|
||||
<input type="submit" name="submit" value="$_('Save')" />
|
||||
|
||||
</form>
|
||||
<h3>Matches</h3>
|
||||
$:filter_settings_form.post_html()
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,39 +0,0 @@
|
||||
$def with (filters, new_filter_form)
|
||||
$:render.header("things", '')
|
||||
<table><tr>
|
||||
<td>
|
||||
<table><tr></td>
|
||||
<div class="info">
|
||||
<h3>Filters</h3>
|
||||
</div></td></tr>
|
||||
<tr><td>
|
||||
<div class="info">
|
||||
<ul>
|
||||
$filters
|
||||
</ul>
|
||||
<form method="post" action='$base/feeder/filters'>
|
||||
$:(new_filter_form.as_p(["name"]))
|
||||
<input type="submit" name="submit" class="form_input" value="$_('Add filter')" />
|
||||
</form>
|
||||
<p><a href="/config/feeder">back to config</a>
|
||||
</div></td></tr></table>
|
||||
</td>
|
||||
<td>
|
||||
<div class="panel">
|
||||
<iframe style="border-style:hidden;" id="filter_settings" width=100% height=600>
|
||||
</iframe>
|
||||
</div>
|
||||
</td>
|
||||
</tr></table>
|
||||
|
||||
<script language="javascript">
|
||||
function load_options(filter){
|
||||
\$('filter_settings').src = state.base_url + '/feeder/filter_settings/' + filter;
|
||||
}
|
||||
</script>
|
||||
<script language="javascript">
|
||||
new InputSensitivitySetter({prefix:"id_",groups:[
|
||||
["name"]
|
||||
]});
|
||||
</script>
|
||||
$:render.footer()
|
@ -1,277 +0,0 @@
|
||||
#
|
||||
# webui.py
|
||||
#
|
||||
# Copyright (C) 2008 Fredrik Eriksson <feeder@winterbird.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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
|
||||
|
||||
import feedparser # for proccessing feed entries
|
||||
import os
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import sclient, aclient
|
||||
from deluge.plugins.webuipluginbase import WebUIPluginBase
|
||||
from deluge import component
|
||||
|
||||
api = component.get("WebPluginApi")
|
||||
forms = api.forms
|
||||
|
||||
class feed_page:
|
||||
"Class for showing feed items"
|
||||
@api.deco.deluge_page
|
||||
def GET(self, feedname):
|
||||
entries = sclient.feeder_get_items(feedname)
|
||||
items = ""
|
||||
for item in entries:
|
||||
items = """%(old)s
|
||||
<a href="%(link)s">
|
||||
<li>%(entry)s</li>
|
||||
</a>""" % { "old":items, "entry":item, "link":entries[item]}
|
||||
return api.render.feeder.feeds(items, feedname)
|
||||
|
||||
class filter_page:
|
||||
"Class for showing filters / filter settings"
|
||||
@api.deco.deluge_page
|
||||
def GET(self, args):
|
||||
new_filter = new_filter_form()
|
||||
filters = sclient.feeder_get_filters()
|
||||
|
||||
# List filters
|
||||
txt = ""
|
||||
for filter in filters:
|
||||
txt = """%(old)s
|
||||
<li onclick=\"load_options('%(new)s')\">
|
||||
%(new)s
|
||||
</li>""" % {'old':txt, 'new':filter}
|
||||
|
||||
return api.render.feeder.filters(txt, new_filter)
|
||||
|
||||
def POST(self):
|
||||
"Saves the new filter"
|
||||
name = api.utils.get_newforms_data(new_filter_form)['name']
|
||||
sclient.feeder_add_filter(name)
|
||||
return self.GET(name)
|
||||
|
||||
class new_filter_form(forms.Form):
|
||||
"basic form for a new label"
|
||||
name = forms.CharField(label="")
|
||||
|
||||
class filter_settings_page:
|
||||
"Class for showing filter settings"
|
||||
@api.deco.deluge_page
|
||||
def GET(self, filter):
|
||||
form = filter_settings_form(filter)
|
||||
return api.render.feeder.filter_settings(form, filter)
|
||||
|
||||
def POST(self, filter):
|
||||
opts = api.utils.get_newforms_data(filter_settings_form)
|
||||
|
||||
# apparently the "Unlimited" options still have to be changed
|
||||
# to -1 (wtf?)
|
||||
# FIXME there is probably a very much better way to ensure that
|
||||
# all values have the right types... not to mention to convert "Unlimited"
|
||||
# to -1...
|
||||
try:
|
||||
opts['max_upload_speed'] = int(opts['max_upload_speed'])
|
||||
except:
|
||||
opts['max_upload_speed'] = int(-1)
|
||||
try:
|
||||
opts['max_download_speed'] = int(opts['max_download_speed'])
|
||||
except:
|
||||
opts['max_download_speed'] = int(-1)
|
||||
try:
|
||||
opts['max_connections'] = int(opts['max_connections'])
|
||||
except:
|
||||
opts['max_connections'] = int(-1)
|
||||
try:
|
||||
opts['max_upload_slots'] = int(opts['max_upload_slots'])
|
||||
except:
|
||||
opts['max_upload_slots'] = int(-1)
|
||||
"""opts['max_upload_slots'] = long(opts['max_upload_slots'])
|
||||
opts['max_connections'] = long(opts['max_connections'])"""
|
||||
|
||||
# TODO filter settings per feed not implemented.
|
||||
opts['feeds'] = []
|
||||
|
||||
sclient.feeder_set_filter_config(filter, opts)
|
||||
return self.GET(filter)
|
||||
|
||||
class filter_settings_form(forms.Form):
|
||||
"form for filter settings"
|
||||
|
||||
def __init__(self, filter, test=False):
|
||||
self.filtername = filter # We want to save our filtername
|
||||
forms.Form.__init__(self)
|
||||
|
||||
def initial_data(self):
|
||||
self.conf = sclient.feeder_get_filter_config(self.filtername)
|
||||
return self.conf
|
||||
|
||||
def post_html(self):
|
||||
regex = self.conf["regex"]
|
||||
hits = sclient.feeder_test_filter(regex)
|
||||
if not hits:
|
||||
return "No hits"
|
||||
list = ""
|
||||
for hit in hits:
|
||||
list = """%(old)s
|
||||
<li><a href="%(link)s" >%(name)s</a></li>
|
||||
""" % { "old":list, "link":hits[hit], "name":hit }
|
||||
return """
|
||||
<ul>
|
||||
%s
|
||||
</ul>
|
||||
""" % list
|
||||
|
||||
regex = forms.CharField(_("regular_expression"))
|
||||
all_feeds = forms.CheckBox(_("all_feeds"))
|
||||
active = forms.CheckBox(_("active"))
|
||||
|
||||
#maximum:
|
||||
max_download_speed = forms.DelugeFloat(_("max_download_speed"))
|
||||
max_upload_speed = forms.DelugeFloat(_("max_upload_speed"))
|
||||
max_upload_slots = forms.DelugeInt(_("max_upload_slots"))
|
||||
max_connections = forms.DelugeInt(_("max_connections"))
|
||||
|
||||
stop_ratio = forms.DelugeFloat(_("stop_ratio"))
|
||||
stop_at_ratio = forms.CheckBox(_("stop_at_ratio"))
|
||||
remove_at_ratio = forms.CheckBox(_("remove_at_ratio"))
|
||||
|
||||
#queue:
|
||||
auto_managed = forms.CheckBox(_("is_auto_managed"))
|
||||
prioritize_first_last_pieces = forms.CheckBox(_("prioritize_first_last_pieces"))
|
||||
|
||||
download_location = forms.ServerFolder(_("download_location"))
|
||||
|
||||
class remove_feed_page:
|
||||
"Class for deleting feeds, redirects to setting page"
|
||||
@api.deco.deluge_page
|
||||
def GET(self, feedname):
|
||||
sclient.feeder_remove_feed(feedname)
|
||||
return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Redirecting back to settings</title>
|
||||
<meta http-equiv="refresh" content="0; URL=/config/feeder">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>"""
|
||||
|
||||
class remove_filter_page:
|
||||
"Class for deleting filters, redirects to setting page"
|
||||
@api.deco.deluge_page
|
||||
def GET(self, name):
|
||||
sclient.feeder_remove_filter(name)
|
||||
return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Redirecting back to settings</title>
|
||||
<meta http-equiv="refresh" content="0; URL=/config/feeder">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>"""
|
||||
|
||||
|
||||
class WebUI(WebUIPluginBase):
|
||||
#map url's to classes: [(url,class), ..]
|
||||
urls = [('/feeder/filters', filter_page),
|
||||
('/feeder/filter_settings/(.*)', filter_settings_page),
|
||||
('/feeder/feed_remove/(.*)', remove_feed_page),
|
||||
('/feeder/filter_remove/(.*)', remove_filter_page),
|
||||
('/feeder/feed/(.*)', feed_page)]
|
||||
|
||||
def enable(self):
|
||||
api.config_page_manager.register('plugins', 'feeder' ,ConfigForm)
|
||||
|
||||
def disable(self):
|
||||
api.config_page_manager.deregister('feeder')
|
||||
|
||||
class ConfigForm(forms.Form):
|
||||
#meta:
|
||||
title = _("feeder")
|
||||
|
||||
#load/save:
|
||||
def initial_data(self):
|
||||
return sclient.feeder_get_config()
|
||||
|
||||
def save(self, data):
|
||||
cfg = dict(data)
|
||||
sclient.feeder_add_feed(cfg)
|
||||
|
||||
def pre_html(self):
|
||||
feeds = sclient.feeder_get_feeds()
|
||||
filters = sclient.feeder_get_filters()
|
||||
filterlist = ""
|
||||
for filter in filters:
|
||||
filterlist = """ %(old)s <li>%(new)s
|
||||
<a href="/feeder/filter_remove/%(new)s">
|
||||
<img src="/static/images/16/list-remove.png" alt="Remove" />
|
||||
</a></li>""" % {'old':filterlist, 'new':filter}
|
||||
feedlist = ""
|
||||
for feed in feeds:
|
||||
feedlist = """%(old)s
|
||||
<li> <a href="/feeder/feed/%(new)s"> %(new)s (%(entrys)s torrents)</a>
|
||||
<a href="/feeder/feed_remove/%(new)s">
|
||||
<img src="/static/images/16/list-remove.png" alt="Remove" />
|
||||
</a></li>""" % {'old':feedlist, 'new':feed, 'entrys':len(sclient.feeder_get_items(feed))}
|
||||
|
||||
return """
|
||||
<table width=100%%><tr><td>
|
||||
<h3>Feeds</h3>
|
||||
</td>
|
||||
<td>
|
||||
<h3>Filters</h3>
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
<div class="info">
|
||||
<ul>
|
||||
%(feeds)s
|
||||
</ul></div>
|
||||
</td><td>
|
||||
<div class="info">
|
||||
<ul>
|
||||
%(filters)s
|
||||
</ul></div>
|
||||
<a href="/feeder/filters">Add/modify filters</a>
|
||||
</td></tr>
|
||||
</table>
|
||||
<h3>Add/change feed settings</h3>""" % {'feeds':feedlist, 'filters':filterlist}
|
||||
|
||||
name = forms.CharField(label=_("Name of feed"))
|
||||
url = forms.URLField(label=_("URL of feed"))
|
||||
updatetime = forms.IntegerField(label=_("Defualt refresh time"))
|
||||
|
@ -1,70 +0,0 @@
|
||||
#
|
||||
# setup.py
|
||||
#
|
||||
# Copyright (C) 2008 Fredrik Eriksson <feeder@winterbird.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
__plugin_name__ = "feeder"
|
||||
__author__ = "Fredrik Eriksson"
|
||||
__author_email__ = "feeder@winterbird.org"
|
||||
__version__ = "0.4"
|
||||
__url__ = ""
|
||||
__license__ = "GPLv3"
|
||||
__description__ = "A plugin for automatically downloadning torrents from a RSS-feed"
|
||||
__long_description__ = """"""
|
||||
__pkg_data__ = {__plugin_name__.lower(): ["template/*", "data/*"]}
|
||||
|
||||
setup(
|
||||
name=__plugin_name__,
|
||||
version=__version__,
|
||||
description=__description__,
|
||||
author=__author__,
|
||||
author_email=__author_email__,
|
||||
url=__url__,
|
||||
license=__license__,
|
||||
long_description=__long_description__,
|
||||
|
||||
packages=[__plugin_name__.lower()],
|
||||
package_data = __pkg_data__,
|
||||
|
||||
entry_points="""
|
||||
[deluge.plugin.core]
|
||||
%s = %s:CorePlugin
|
||||
[deluge.plugin.gtkui]
|
||||
%s = %s:GtkUIPlugin
|
||||
[deluge.plugin.webui]
|
||||
%s = %s:WebUIPlugin
|
||||
""" % ((__plugin_name__, __plugin_name__.lower())*3)
|
||||
)
|
@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
cd /home/vampas/projects/DelugeNotify/deluge/plugins/freespace
|
||||
mkdir temp
|
||||
export PYTHONPATH=./temp
|
||||
python setup.py build develop --install-dir ./temp
|
||||
cp ./temp/FreeSpace.egg-link .config/plugins
|
||||
rm -fr ./temp
|
@ -1,58 +0,0 @@
|
||||
#
|
||||
# __init__.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from deluge.plugins.init import PluginInitBase
|
||||
|
||||
class CorePlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from core import Core as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(CorePlugin, self).__init__(plugin_name)
|
||||
|
||||
class GtkUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from gtkui import GtkUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||
|
||||
class WebUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from webui import WebUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(WebUIPlugin, self).__init__(plugin_name)
|
@ -1,42 +0,0 @@
|
||||
#
|
||||
# common.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
def get_resource(filename):
|
||||
import pkg_resources, os
|
||||
return pkg_resources.resource_filename("freespace", os.path.join("data", filename))
|
@ -1,201 +0,0 @@
|
||||
#
|
||||
# core.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
||||
import os, statvfs
|
||||
from datetime import datetime, timedelta
|
||||
from twisted.internet import task
|
||||
from deluge.log import LOG as log
|
||||
from deluge.plugins.pluginbase import CorePluginBase
|
||||
from deluge.event import DelugeEvent
|
||||
import deluge.component as component
|
||||
import deluge.configmanager
|
||||
from deluge.core.rpcserver import export
|
||||
|
||||
|
||||
class LowDiskSpaceEvent(DelugeEvent):
|
||||
"""Triggered when the available space for a specific path is getting
|
||||
too low.
|
||||
"""
|
||||
def __init__(self, percents_dict):
|
||||
"""
|
||||
:param percents: dictionary of path keys with their respecive
|
||||
occupation percentages.
|
||||
"""
|
||||
self._args = [percents_dict]
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
"enabled": False,
|
||||
"percent": 90
|
||||
}
|
||||
|
||||
class Core(CorePluginBase):
|
||||
CLEANUP_TIMEOUT_SECS = 3600 # One hour
|
||||
|
||||
def enable(self):
|
||||
self.config = deluge.configmanager.ConfigManager("freespace.conf",
|
||||
DEFAULT_PREFS)
|
||||
self.notifications_sent = {}
|
||||
|
||||
self._timer = task.LoopingCall(self.update)
|
||||
self._interval = 60 * 5 # every 5 minutes
|
||||
if self.config['enabled']:
|
||||
self._timer.start(self._interval, False)
|
||||
|
||||
self._cleanup = task.LoopingCall(self.__cleanup_notifications)
|
||||
self._cleanup.start(self._interval, False)
|
||||
|
||||
try:
|
||||
component.get("CorePlugin.Notifications"). \
|
||||
register_custom_email_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_email_notification
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
component.get("EventManager").register_event_handler(
|
||||
"PluginEnabledEvent", self.__on_plugin_enabled
|
||||
)
|
||||
component.get("EventManager").register_event_handler(
|
||||
"PluginDisabledEvent", self.__on_plugin_disabled
|
||||
)
|
||||
|
||||
def disable(self):
|
||||
try:
|
||||
component.get("CorePlugin.Notifications"). \
|
||||
deregister_custom_email_notification("LowDiskSpaceEvent")
|
||||
except KeyError:
|
||||
pass
|
||||
component.get("EventManager").deregister_event_handler(
|
||||
"PluginEnabledEvent", self.__on_plugin_enabled
|
||||
)
|
||||
component.get("EventManager").deregister_event_handler(
|
||||
"PluginDisabledEvent", self.__on_plugin_disabled
|
||||
)
|
||||
self._cleanup.stop()
|
||||
if self._timer.running:
|
||||
self._timer.stop()
|
||||
|
||||
def update(self):
|
||||
log.debug('Updating %s FreeSpace', self.__class__.__name__)
|
||||
nots = {}
|
||||
for path in self.__gather_paths_to_check():
|
||||
log.debug("Checking path %s", path)
|
||||
if os.path.exists(path):
|
||||
free_percent = self.__get_free_space(path)
|
||||
if (100 - free_percent) > self.config['percent']:
|
||||
if path not in self.notifications_sent:
|
||||
self.notifications_sent[path] = datetime.utcnow()
|
||||
nots[path] = (100 - free_percent)
|
||||
else:
|
||||
log.warning("Running low on disk space on %s but "
|
||||
"notifications were already triggered.",
|
||||
path)
|
||||
if nots:
|
||||
component.get("EventManager").emit(LowDiskSpaceEvent(nots))
|
||||
|
||||
|
||||
@export
|
||||
def set_config(self, config):
|
||||
"sets the config dictionary"
|
||||
if not self.config['enabled'] and config['enabled']:
|
||||
self._timer.start(self._interval, False)
|
||||
elif self.config['enabled'] and not config['enabled']:
|
||||
self._timer.stop()
|
||||
|
||||
for key in config.keys():
|
||||
self.config[key] = config[key]
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def get_config(self):
|
||||
"returns the config dictionary"
|
||||
return self.config.config
|
||||
|
||||
def __gather_paths_to_check(self):
|
||||
self.over_percentage = {}
|
||||
torrent_manager = component.get('TorrentManager')
|
||||
paths = set()
|
||||
for torrent_id in torrent_manager.get_torrent_list():
|
||||
status = torrent_manager[torrent_id].get_status([
|
||||
'is_finished',
|
||||
'move_on_completed_path',
|
||||
'save_path'
|
||||
])
|
||||
if not status['is_finished']:
|
||||
paths.add(status['move_on_completed_path'])
|
||||
paths.add(status['save_path'])
|
||||
return paths
|
||||
|
||||
def __get_free_space(self, path):
|
||||
log.debug("Calculating free space on %s", path)
|
||||
stat = os.statvfs(path)
|
||||
free_blocks = stat[statvfs.F_BAVAIL]
|
||||
total_blocks = stat[statvfs.F_BLOCKS]
|
||||
free_percent = free_blocks * 100 / total_blocks
|
||||
return free_percent
|
||||
|
||||
def __custom_email_notification(self, ocupied_percents):
|
||||
|
||||
subject = _("Low Disk Space Warning")
|
||||
message = _("You're running low on disk space:\n")
|
||||
|
||||
for path, ocupied_percent in ocupied_percents.iteritems():
|
||||
message += _(' %s%% ocupation in %s\n') % (ocupied_percent, path)
|
||||
# "\"%s\"%% space occupation on %s") % (ocupied_percent, path)
|
||||
return subject, message
|
||||
|
||||
def __on_plugin_enabled(self, plugin_name):
|
||||
if plugin_name == 'Notifications':
|
||||
component.get("CorePlugin.Notifications"). \
|
||||
register_custom_email_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_email_notification
|
||||
)
|
||||
|
||||
def __on_plugin_disabled(self, plugin_name):
|
||||
if plugin_name == 'Notifications':
|
||||
component.get("CorePlugin.Notifications"). \
|
||||
deregister_custom_email_notification("LowDiskSpaceEvent")
|
||||
|
||||
def __cleanup_notifications(self):
|
||||
now = datetime.now()
|
||||
for path, when in self.notifications_sent.copy().iteritems():
|
||||
if when <= (now -timedelta(seconds=self.CLEANUP_TIMEOUT_SECS)):
|
||||
log.debug("Removing old(%s) path from notified paths: %s",
|
||||
when, path)
|
||||
self.notifications_sent.pop(path)
|
@ -1,100 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<glade-interface>
|
||||
<!-- interface-requires gtk+ 2.6 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<widget class="GtkWindow" id="window1">
|
||||
<child>
|
||||
<widget class="GtkHBox" id="prefs_box">
|
||||
<property name="visible">True</property>
|
||||
<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="GtkVBox" id="vbox2">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox2">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="enabled">
|
||||
<property name="label" translatable="yes">Consider low when</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSpinButton" id="percent">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="max_length">2</property>
|
||||
<property name="invisible_char">●</property>
|
||||
<property name="adjustment">90 0 99 1 10 0</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label14">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">%</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label4">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">of the disk is occupied.</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes"><b>Free Space Checking</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">label_item</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
Script: freespace.js
|
||||
The client-side javascript code for the FreeSpace plugin.
|
||||
|
||||
Copyright:
|
||||
(C) Pedro Algarvio 2009 <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.
|
||||
*/
|
||||
|
||||
FreeSpacePlugin = Ext.extend(Deluge.Plugin, {
|
||||
constructor: function(config) {
|
||||
config = Ext.apply({
|
||||
name: "FreeSpace"
|
||||
}, config);
|
||||
FreeSpacePlugin.superclass.constructor.call(this, config);
|
||||
},
|
||||
|
||||
onDisable: function() {
|
||||
|
||||
},
|
||||
|
||||
onEnable: function() {
|
||||
|
||||
}
|
||||
});
|
||||
new FreeSpacePlugin();
|
@ -1,174 +0,0 @@
|
||||
#
|
||||
# gtkui.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import gtk
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge.plugins.pluginbase import GtkPluginBase
|
||||
import deluge.component as component
|
||||
import deluge.common
|
||||
|
||||
from common import get_resource
|
||||
|
||||
class GtkUI(GtkPluginBase):
|
||||
|
||||
def enable(self):
|
||||
log.debug('Enabling %s FreeSpace', self.__class__.__name__)
|
||||
self.glade = gtk.glade.XML(get_resource("config.glade"))
|
||||
self.prefs = self.glade.get_widget('prefs_box')
|
||||
parent = self.prefs.get_parent()
|
||||
if parent:
|
||||
parent.remove(self.prefs)
|
||||
|
||||
# chk_ap = component.get("Preferences").glade.get_widget('chk_add_paused')
|
||||
# downloads_vbox = chk_ap.get_parent().get_parent().get_parent().get_parent()
|
||||
|
||||
downloads_vbox = component.get("Preferences").glade.get_widget('vbox1')
|
||||
downloads_vbox.pack_start(self.prefs, False, True, 0)
|
||||
# self.prefs.set_parent(frame)
|
||||
|
||||
# component.get("Preferences").add_page("FreeSpace", self.glade.get_widget("prefs_box"))
|
||||
component.get("PluginManager").register_hook("on_apply_prefs",
|
||||
self.on_apply_prefs)
|
||||
component.get("PluginManager").register_hook("on_show_prefs",
|
||||
self.on_show_prefs)
|
||||
|
||||
try:
|
||||
notifications = component.get("GtkPlugin.Notifications")
|
||||
notifications.register_custom_popup_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_popup_notification
|
||||
)
|
||||
notifications.register_custom_blink_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_blink_notification
|
||||
)
|
||||
notifications.register_custom_sound_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_sound_notification
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
client.register_event_handler("PluginEnabledEvent",
|
||||
self.__on_plugin_enabled)
|
||||
|
||||
client.register_event_handler("PluginDisabledEvent",
|
||||
self.__on_plugin_disabled)
|
||||
|
||||
def disable(self):
|
||||
component.get("Preferences").remove_page("FreeSpace")
|
||||
component.get("PluginManager").deregister_hook("on_apply_prefs",
|
||||
self.on_apply_prefs)
|
||||
component.get("PluginManager").deregister_hook("on_show_prefs",
|
||||
self.on_show_prefs)
|
||||
try:
|
||||
notifications = component.get("GtkPlugin.Notifications")
|
||||
notifications.deregister_custom_popup_notification(
|
||||
"LowDiskSpaceEvent"
|
||||
)
|
||||
notifications.deregister_custom_blink_notification(
|
||||
"LowDiskSpaceEvent"
|
||||
)
|
||||
notifications.deregister_custom_sound_notification(
|
||||
"LowDiskSpaceEvent"
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
client.deregister_event_handler("PluginEnabledEvent",
|
||||
self.__on_plugin_enabled)
|
||||
|
||||
client.deregister_event_handler("PluginDisabledEvent",
|
||||
self.__on_plugin_disabled)
|
||||
|
||||
def on_apply_prefs(self):
|
||||
log.debug("applying prefs for FreeSpace")
|
||||
config = {
|
||||
"enabled": self.glade.get_widget('enabled').get_active(),
|
||||
"percent": self.glade.get_widget('percent').get_value()
|
||||
}
|
||||
client.freespace.set_config(config)
|
||||
|
||||
def on_show_prefs(self):
|
||||
client.freespace.get_config().addCallback(self.cb_get_config)
|
||||
|
||||
def cb_get_config(self, config):
|
||||
"callback for on show_prefs"
|
||||
self.glade.get_widget('enabled').set_active(config['enabled'])
|
||||
self.glade.get_widget('percent').set_value(config['percent'])
|
||||
|
||||
def __custom_popup_notification(self, ocupied_percents):
|
||||
title = _("Low Free Space")
|
||||
message = ''
|
||||
for path, percent in ocupied_percents.iteritems():
|
||||
message += '%s%% %s\n' % (percent, path)
|
||||
message += '\n'
|
||||
return title, message
|
||||
|
||||
def __custom_blink_notification(self, ocupied_percents):
|
||||
return True # Yes, do blink
|
||||
|
||||
def __custom_sound_notification(self, ocupied_percents):
|
||||
return '' # Use default sound
|
||||
|
||||
def __on_plugin_enabled(self, plugin_name):
|
||||
if plugin_name == 'Notifications':
|
||||
notifications = component.get("GtkPlugin.Notifications")
|
||||
notifications.register_custom_popup_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_popup_notification
|
||||
)
|
||||
notifications.register_custom_blink_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_blink_notification
|
||||
)
|
||||
notifications.register_custom_sound_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_sound_notification
|
||||
)
|
||||
|
||||
def __on_plugin_disabled(self, plugin_name):
|
||||
pass
|
||||
# if plugin_name == 'Notifications':
|
||||
# notifications = component.get("GtkPlugin.Notifications")
|
||||
# notifications.deregister_custom_popup_notification(
|
||||
# "LowDiskSpaceEvent"
|
||||
# )
|
||||
# notifications.deregister_custom_blink_notification(
|
||||
# "LowDiskSpaceEvent"
|
||||
# )
|
||||
# notifications.deregister_custom_sound_notification(
|
||||
# "LowDiskSpaceEvent"
|
||||
# )
|
@ -1,55 +0,0 @@
|
||||
#
|
||||
# webui.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge import component
|
||||
from deluge.plugins.pluginbase import WebPluginBase
|
||||
|
||||
from common import get_resource
|
||||
|
||||
class WebUI(WebPluginBase):
|
||||
|
||||
scripts = [get_resource("freespace.js")]
|
||||
|
||||
def enable(self):
|
||||
pass
|
||||
|
||||
def disable(self):
|
||||
pass
|
@ -1,71 +0,0 @@
|
||||
#
|
||||
# setup.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
__plugin_name__ = "FreeSpace"
|
||||
__author__ = "Pedro Algarvio"
|
||||
__author_email__ = "ufs@ufsoft.org"
|
||||
__version__ = "0.1"
|
||||
__url__ = "http://deluge.ufsoft.org/hg/Notification/"
|
||||
__license__ = "GPLv3"
|
||||
__description__ = "Plugin which continuously checks for available free space."
|
||||
__long_description__ = __description__
|
||||
__pkg_data__ = {__plugin_name__.lower(): ["template/*", "data/*"]}
|
||||
|
||||
setup(
|
||||
name=__plugin_name__,
|
||||
version=__version__,
|
||||
description=__description__,
|
||||
author=__author__,
|
||||
author_email=__author_email__,
|
||||
url=__url__,
|
||||
license=__license__,
|
||||
long_description=__long_description__ if __long_description__ else __description__,
|
||||
|
||||
packages=[__plugin_name__.lower()],
|
||||
package_data = __pkg_data__,
|
||||
|
||||
entry_points="""
|
||||
[deluge.plugin.core]
|
||||
%s = %s:CorePlugin
|
||||
[deluge.plugin.gtkui]
|
||||
%s = %s:GtkUIPlugin
|
||||
""" % ((__plugin_name__, __plugin_name__.lower())*2)
|
||||
)
|
@ -188,6 +188,7 @@ class Core(CorePluginBase):
|
||||
CheckInput(not (label_id in self.labels) , _("Label already exists"))
|
||||
|
||||
self.labels[label_id] = dict(OPTIONS_DEFAULTS)
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def remove(self, label_id):
|
||||
@ -259,7 +260,7 @@ class Core(CorePluginBase):
|
||||
return False
|
||||
|
||||
@export
|
||||
def set_options(self, label_id, options_dict , apply = False):
|
||||
def set_options(self, label_id, options_dict):
|
||||
"""update the label options
|
||||
|
||||
options_dict :
|
||||
@ -271,8 +272,6 @@ class Core(CorePluginBase):
|
||||
"apply_max":bool(),
|
||||
"move_completed_to":string() or None
|
||||
}
|
||||
|
||||
apply : applies download-options to all torrents currently labelled by label_id
|
||||
"""
|
||||
CheckInput(label_id in self.labels , _("Unknown Label"))
|
||||
for key in options_dict.keys():
|
||||
|
@ -49,6 +49,7 @@ Deluge.ux.AddLabelWindow = Ext.extend(Ext.Window, {
|
||||
this.form = this.add({
|
||||
xtype: 'form',
|
||||
height: 35,
|
||||
baseCls: 'x-plain',
|
||||
bodyStyle:'padding:5px 5px 0',
|
||||
defaultType: 'textfield',
|
||||
labelWidth: 50,
|
||||
@ -496,7 +497,7 @@ Deluge.plugins.LabelPlugin = Ext.extend(Deluge.Plugin, {
|
||||
},
|
||||
|
||||
onLabelRemoveClick: function() {
|
||||
var state = this.filter.getFilter();
|
||||
var state = this.filter.getState();
|
||||
deluge.client.label.remove(state, {
|
||||
success: function() {
|
||||
deluge.ui.update();
|
||||
|
@ -46,11 +46,8 @@ import sidebar_menu
|
||||
import label_config
|
||||
import submenu
|
||||
|
||||
from deluge.configmanager import ConfigManager
|
||||
config = ConfigManager("label.conf")
|
||||
NO_LABEL = "No Label"
|
||||
|
||||
|
||||
def cell_data_label(column, cell, model, row, data):
|
||||
cell.set_property('text', str(model.get_value(row, data)))
|
||||
|
||||
|
@ -41,8 +41,6 @@ from deluge import component # for systray
|
||||
import gtk, gobject
|
||||
from deluge.ui.client import client
|
||||
|
||||
from deluge.configmanager import ConfigManager
|
||||
config = ConfigManager("label.conf")
|
||||
NO_LABEL = "No Label"
|
||||
|
||||
class LabelMenu(gtk.MenuItem):
|
||||
|
@ -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."
|
||||
|
@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
mkdir temp
|
||||
export PYTHONPATH=./temp
|
||||
python setup.py develop --install-dir ./temp
|
||||
cp ./temp/Stats.egg-link ~/.config/deluge/plugins
|
||||
rm -fr ./temp
|
@ -1,81 +0,0 @@
|
||||
#
|
||||
# setup.py
|
||||
#
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
# 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
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
__plugin_name__ = "Stats"
|
||||
__author__ = "Martijn Voncken"
|
||||
__author_email__ = "mvoncken@gmail.com"
|
||||
__version__ = "0.1"
|
||||
__url__ = "http://deluge-torrent.org"
|
||||
__license__ = "GPLv3"
|
||||
__description__ = ""
|
||||
__long_description__ = """"""
|
||||
__pkg_data__ = {__plugin_name__.lower(): ["template/*", "data/*"]}
|
||||
|
||||
setup(
|
||||
name=__plugin_name__,
|
||||
version=__version__,
|
||||
description=__description__,
|
||||
author=__author__,
|
||||
author_email=__author_email__,
|
||||
url=__url__,
|
||||
license=__license__,
|
||||
long_description=__long_description__,
|
||||
|
||||
packages=[__plugin_name__.lower()],
|
||||
package_data = __pkg_data__,
|
||||
|
||||
entry_points="""
|
||||
[deluge.plugin.core]
|
||||
%s = %s:CorePlugin
|
||||
[deluge.plugin.gtkui]
|
||||
%s = %s:GtkUIPlugin
|
||||
[deluge.plugin.web]
|
||||
%s = %s:WebUIPlugin
|
||||
""" % ((__plugin_name__, __plugin_name__.lower())*3)
|
||||
)
|
@ -1,57 +0,0 @@
|
||||
#
|
||||
# __init__.py
|
||||
#
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from deluge.plugins.init import PluginInitBase
|
||||
|
||||
class CorePlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from core import Core as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(CorePlugin, self).__init__(plugin_name)
|
||||
|
||||
class GtkUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from gtkui import GtkUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||
|
||||
class WebUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from webui import WebUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(WebUIPlugin, self).__init__(plugin_name)
|
@ -1,39 +0,0 @@
|
||||
#
|
||||
# common.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import pkg_resources
|
||||
import os.path
|
||||
|
||||
def get_resource(filename):
|
||||
return pkg_resources.resource_filename("stats", os.path.join("data", filename))
|
@ -1,169 +0,0 @@
|
||||
#
|
||||
# core.py
|
||||
#
|
||||
# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
|
||||
# Copyright (C) 2008 Damien Churchill <damoxc@gmail.com>
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) Marcos Pinto 2007 <markybob@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
# 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
|
||||
|
||||
from twisted.internet.task import LoopingCall
|
||||
import time
|
||||
|
||||
import deluge
|
||||
from deluge.log import LOG as log
|
||||
from deluge.plugins.pluginbase import CorePluginBase
|
||||
from deluge import component
|
||||
from deluge import configmanager
|
||||
from deluge.core.rpcserver import export
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
"test": "NiNiNi",
|
||||
"update_interval": 2, #2 seconds.
|
||||
"length": 150, # 2 seconds * 150 --> 5 minutes.
|
||||
}
|
||||
|
||||
DEFAULT_TOTALS = {
|
||||
"total_upload": 0,
|
||||
"total_download": 0,
|
||||
"total_payload_upload": 0,
|
||||
"total_payload_download": 0,
|
||||
"stats": {}
|
||||
}
|
||||
|
||||
class Core(CorePluginBase):
|
||||
totals = {} #class var to catch only updating this once per session in enable.
|
||||
|
||||
def enable(self):
|
||||
self.core = component.get("Core")
|
||||
self.stats ={}
|
||||
|
||||
self.config = configmanager.ConfigManager("stats.conf", DEFAULT_PREFS)
|
||||
self.saved_stats = configmanager.ConfigManager("stats.totals", DEFAULT_TOTALS)
|
||||
if self.totals == {}:
|
||||
self.totals.update(self.saved_stats.config)
|
||||
|
||||
self.stats = self.saved_stats["stats"] or {}
|
||||
|
||||
self.stats_keys = [
|
||||
"payload_download_rate",
|
||||
"payload_upload_rate"
|
||||
]
|
||||
self.update_stats()
|
||||
|
||||
self.update_timer = LoopingCall(self.update_stats)
|
||||
self.update_timer.start(self.config["update_interval"])
|
||||
|
||||
self.save_timer = LoopingCall(self.save_stats)
|
||||
self.save_timer.start(60)
|
||||
|
||||
def disable(self):
|
||||
self.save_stats()
|
||||
try:
|
||||
self.update_timer.stop()
|
||||
self.save_timer.stop()
|
||||
except:
|
||||
pass
|
||||
|
||||
def update_stats(self):
|
||||
try:
|
||||
status = self.core.get_session_status(self.stats_keys)
|
||||
for key, value in status.items():
|
||||
if key not in self.stats:
|
||||
self.stats[key] = []
|
||||
self.stats[key].insert(0, value)
|
||||
|
||||
for stat_list in self.stats.values():
|
||||
if len(stat_list) > self.config["length"]:
|
||||
stat_list.pop()
|
||||
self.last_update = time.time()
|
||||
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
|
||||
def save_stats(self):
|
||||
try:
|
||||
self.saved_stats["stats"] = self.stats
|
||||
self.saved_stats.config.update(self.get_totals())
|
||||
self.saved_stats.save()
|
||||
except Exception,e:
|
||||
log.exception(e)
|
||||
return True
|
||||
|
||||
|
||||
# export:
|
||||
@export
|
||||
def get_stats(self, keys):
|
||||
stats_dict = {}
|
||||
for key in keys:
|
||||
if key in self.stats:
|
||||
stats_dict[key] = self.stats[key]
|
||||
stats_dict["_last_update"] = self.last_update
|
||||
return stats_dict
|
||||
|
||||
@export
|
||||
def get_totals(self):
|
||||
result = {}
|
||||
session_totals = self.get_session_totals()
|
||||
for key in session_totals:
|
||||
result[key] = self.totals[key] + session_totals[key]
|
||||
return result
|
||||
|
||||
@export
|
||||
def get_session_totals(self):
|
||||
status = self.core.session.status()
|
||||
return {
|
||||
"total_upload": status.total_upload,
|
||||
"total_download": status.total_download,
|
||||
"total_payload_upload": status.total_payload_upload,
|
||||
"total_payload_download": status.total_payload_download
|
||||
}
|
||||
|
||||
@export
|
||||
def set_config(self, config):
|
||||
"sets the config dictionary"
|
||||
for key in config.keys():
|
||||
self.config[key] = config[key]
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def get_config(self):
|
||||
"returns the config dictionary"
|
||||
return self.config.config
|
@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.5 on Fri Aug 8 23:34:44 2008 -->
|
||||
<glade-interface>
|
||||
<widget class="GtkWindow" id="window1">
|
||||
<child>
|
||||
<widget class="GtkHBox" id="prefs_box">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Test config value:</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkEntry" id="txt_test">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
Script: stats.js
|
||||
The javascript client-side code for the Stats plugin.
|
||||
|
||||
Copyright:
|
||||
(C) Damien Churchill 2009 <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.
|
||||
*/
|
||||
|
||||
StatsPlugin = Ext.extend(Deluge.Plugin, {
|
||||
constructor: function(config) {
|
||||
config = Ext.apply({
|
||||
name: "Stats"
|
||||
}, config);
|
||||
StatsPlugin.superclass.constructor.call(this, config);
|
||||
},
|
||||
|
||||
onDisable: function() {
|
||||
},
|
||||
|
||||
onEnable: function() {
|
||||
}
|
||||
});
|
||||
new StatsPlugin();
|
@ -1,103 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.5 on Mon Oct 13 20:17:39 2008 -->
|
||||
<glade-interface>
|
||||
<widget class="GtkWindow" id="window1">
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox1">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="graph_label">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkImage" id="image1">
|
||||
<property name="visible">True</property>
|
||||
<property name="stock">gtk-page-setup</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="graph_label_text">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Graphs</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="graph_tab">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<child>
|
||||
<widget class="GtkNotebook" id="graph_notebook">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tab_pos">GTK_POS_LEFT</property>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="bandwidth_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="bandwidth_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Bandwidth</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="connections_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="connections_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Connections</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="seeds_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="seeds_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Seeds/Peers</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">2</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
@ -1,262 +0,0 @@
|
||||
#
|
||||
# graph.py
|
||||
#
|
||||
# Copyright (C) 2008 Damien Churchill <damoxc@gmail.com>
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) Marcos Pinto 2007 <markybob@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
# 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
|
||||
|
||||
"""
|
||||
port of old plugin by markybob.
|
||||
"""
|
||||
import time
|
||||
import cairo
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
|
||||
black = (0, 0, 0)
|
||||
gray = (0.75, 0.75, 0.75)
|
||||
white = (1.0, 1.0, 1.0)
|
||||
darkred = (0.65, 0, 0)
|
||||
red = (1.0, 0, 0)
|
||||
green = (0, 1.0, 0)
|
||||
blue = (0, 0, 1.0)
|
||||
orange = (1.0, 0.74, 0)
|
||||
|
||||
def default_formatter(value):
|
||||
return str(value)
|
||||
|
||||
def change_opacity(color, opactiy):
|
||||
"""A method to assist in changing the opactiy of a color inorder to draw the
|
||||
fills.
|
||||
"""
|
||||
color = list(color)
|
||||
if len(color) == 4:
|
||||
color[3] = opactiy
|
||||
else:
|
||||
color.append(opactiy)
|
||||
return tuple(color)
|
||||
|
||||
class Graph:
|
||||
def __init__(self):
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
self.length = 150
|
||||
self.stat_info = {}
|
||||
self.line_size = 2
|
||||
self.mean_selected = True
|
||||
self.legend_selected = True
|
||||
self.max_selected = True
|
||||
self.black = (0, 0 , 0,)
|
||||
self.interval = 2 # 2 secs
|
||||
self.text_bg = (255, 255 , 255, 128) # prototyping
|
||||
self.set_left_axis()
|
||||
|
||||
def set_left_axis(self, **kargs):
|
||||
self.left_axis = kargs
|
||||
|
||||
def add_stat(self, stat, label='', axis='left', line=True, fill=True, color=None):
|
||||
self.stat_info[stat] = {
|
||||
'axis': axis,
|
||||
'label': label,
|
||||
'line': line,
|
||||
'fill': fill,
|
||||
'color': color
|
||||
}
|
||||
|
||||
def set_stats(self, stats):
|
||||
self.last_update = stats["_last_update"]
|
||||
log.debug("Last update: %s" % self.last_update)
|
||||
del stats["_last_update"]
|
||||
self.stats = stats
|
||||
|
||||
def set_config(self, config):
|
||||
self.length = config["length"]
|
||||
self.interval = config["update_interval"]
|
||||
|
||||
def draw_to_context(self, context, width, height):
|
||||
self.ctx = context
|
||||
self.width, self.height = width, height
|
||||
try:
|
||||
self.draw_rect(white, 0, 0, self.width, self.height)
|
||||
self.draw_x_axis()
|
||||
self.draw_left_axis()
|
||||
|
||||
if self.legend_selected:
|
||||
self.draw_legend()
|
||||
except cairo.Error, e:
|
||||
log.exception(e)
|
||||
return self.ctx
|
||||
|
||||
def draw(self, width, height):
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height)
|
||||
self.ctx = cairo.Context(self.surface)
|
||||
self.draw_rect(white, 0, 0, self.width, self.height)
|
||||
self.draw_x_axis()
|
||||
self.draw_left_axis()
|
||||
|
||||
if self.legend_selected:
|
||||
self.draw_legend()
|
||||
return self.surface
|
||||
|
||||
def draw_x_axis(self):
|
||||
duration = float(self.length * self.interval)
|
||||
start = self.last_update - duration
|
||||
ratio = (self.width - 40) / duration
|
||||
seconds_to_minute = 60 - time.localtime(start)[5]
|
||||
|
||||
for i in xrange(0, 5):
|
||||
text = time.strftime('%H:%M', time.localtime(start + seconds_to_minute + (60*i)))
|
||||
x = int(ratio * (seconds_to_minute + (60*i)))
|
||||
self.draw_text(text, x + 46, self.height - 20)
|
||||
x = x + 59.5
|
||||
self.draw_dotted_line(gray, x, 20, x, self.height - 20)
|
||||
|
||||
y = self.height - 22.5
|
||||
self.draw_dotted_line(gray, 60, y, int(self.width), y)
|
||||
|
||||
def draw_left_axis(self):
|
||||
stats = {}
|
||||
max_values = []
|
||||
for stat in self.stat_info:
|
||||
if self.stat_info[stat]['axis'] == 'left':
|
||||
stats[stat] = self.stat_info[stat]
|
||||
stats[stat]['values'] = self.stats[stat]
|
||||
stats[stat]['fill_color'] = change_opacity(stats[stat]['color'], 0.5)
|
||||
stats[stat]['color'] = change_opacity(stats[stat]['color'], 0.8)
|
||||
stats[stat]['max_value'] = max(self.stats[stat])
|
||||
max_values.append(stats[stat]['max_value'])
|
||||
if len(max_values) > 1:
|
||||
max_value = max(*max_values)
|
||||
else:
|
||||
max_value = max_values[0]
|
||||
|
||||
if max_value < self.left_axis['min']:
|
||||
max_value = self.left_axis['min']
|
||||
|
||||
height = self.height - self.line_size - 22
|
||||
#max_value = float(round(max_value, len(str(max_value)) * -1))
|
||||
max_value = float(max_value)
|
||||
ratio = height / max_value
|
||||
|
||||
for i in xrange(1, 6):
|
||||
y = int(ratio * ((max_value / 5) * i)) - 0.5
|
||||
if i < 5:
|
||||
self.draw_dotted_line(gray, 60, y, self.width, y)
|
||||
text = self.left_axis['formatter']((max_value / 5) * (5 - i))
|
||||
self.draw_text(text, 0, y - 6)
|
||||
self.draw_dotted_line(gray, 60.5, 20, 60.5, self.height - 20)
|
||||
|
||||
for stat, info in stats.iteritems():
|
||||
self.draw_value_poly(info['values'], info['color'], max_value)
|
||||
self.draw_value_poly(info['values'], info['fill_color'], max_value, info['fill'])
|
||||
|
||||
def draw_legend(self):
|
||||
pass
|
||||
|
||||
def trace_path(self, values, max_value):
|
||||
height = self.height - 24
|
||||
width = self.width
|
||||
line_width = self.line_size
|
||||
|
||||
self.ctx.set_line_width(line_width)
|
||||
self.ctx.move_to(width, height)
|
||||
|
||||
self.ctx.line_to(width,
|
||||
int(height - ((height - 28) * values[0] / max_value)))
|
||||
|
||||
x = width
|
||||
step = (width - 60) / float(self.length)
|
||||
for i, value in enumerate(values):
|
||||
if i == self.length - 1:
|
||||
x = 62
|
||||
self.ctx.line_to(x,
|
||||
int(height - 1 - ((height - 28) * value / max_value))
|
||||
)
|
||||
x -= step
|
||||
|
||||
self.ctx.line_to(
|
||||
int(width + 62 - (((len(values) - 1) * width) / (self.length - 1))),
|
||||
height)
|
||||
self.ctx.close_path()
|
||||
|
||||
def draw_value_poly(self, values, color, max_value, fill=False):
|
||||
self.trace_path(values, max_value)
|
||||
self.ctx.set_source_rgba(*color)
|
||||
|
||||
if fill:
|
||||
self.ctx.fill()
|
||||
else:
|
||||
self.ctx.stroke()
|
||||
|
||||
def draw_text(self, text, x, y):
|
||||
self.ctx.set_font_size(9)
|
||||
self.ctx.move_to(x, y + 9)
|
||||
self.ctx.set_source_rgba(*self.black)
|
||||
self.ctx.show_text(text)
|
||||
|
||||
def draw_rect(self, color, x, y, height, width):
|
||||
self.ctx.set_source_rgba(*color)
|
||||
self.ctx.rectangle(x, y, height, width)
|
||||
self.ctx.fill()
|
||||
|
||||
def draw_line(self, color, x1, y1, x2, y2):
|
||||
self.ctx.set_source_rgba(*color)
|
||||
self.ctx.set_line_width(1)
|
||||
self.ctx.move_to(x1, y1)
|
||||
self.ctx.line_to(x2, y2)
|
||||
self.ctx.stroke()
|
||||
|
||||
def draw_dotted_line(self, color, x1, y1, x2, y2):
|
||||
self.ctx.set_source_rgba(*color)
|
||||
self.ctx.set_line_width(1)
|
||||
self.ctx.move_to(x1, y1)
|
||||
self.ctx.line_to(x2, y2)
|
||||
#self.ctx.stroke_preserve()
|
||||
#self.ctx.set_source_rgba(*white)
|
||||
#self.ctx.set_dash((1, 1), 4)
|
||||
self.ctx.stroke()
|
||||
#self.ctx.set_dash((1, 1), 0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import test
|
@ -1,151 +0,0 @@
|
||||
#
|
||||
# gtkui.py
|
||||
#
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
# 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
|
||||
|
||||
import gtk
|
||||
import gobject
|
||||
from gtk.glade import XML
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
import graph
|
||||
from deluge import component
|
||||
from deluge.log import LOG as log
|
||||
from deluge.common import fspeed
|
||||
from deluge.ui.client import client
|
||||
from deluge.ui.gtkui.torrentdetails import Tab
|
||||
from deluge.plugins.pluginbase import GtkPluginBase
|
||||
|
||||
class GraphsTab(Tab):
|
||||
def __init__(self, glade):
|
||||
Tab.__init__(self)
|
||||
self._name = 'Graphs'
|
||||
self.glade = glade
|
||||
self.window = self.glade.get_widget('graph_tab')
|
||||
self._child_widget = self.window
|
||||
self.notebook = self.glade.get_widget('graph_notebook')
|
||||
self.label = self.glade.get_widget('graph_label')
|
||||
self._tab_label = self.label
|
||||
self.bandwidth_graph = self.glade.get_widget('bandwidth_graph')
|
||||
self.bandwidth_graph.connect('expose_event', self.expose)
|
||||
self.window.unparent()
|
||||
self.label.unparent()
|
||||
|
||||
self.graph_widget = self.bandwidth_graph
|
||||
self.graph = graph.Graph()
|
||||
self.graph.add_stat('payload_download_rate', label='Download Rate', color=graph.green)
|
||||
self.graph.add_stat('payload_upload_rate', label='Upload Rate', color=graph.blue)
|
||||
self.graph.set_left_axis(formatter=fspeed, min=10240)
|
||||
|
||||
def expose(self, widget, event):
|
||||
"""Redraw"""
|
||||
context = self.graph_widget.window.cairo_create()
|
||||
# set a clip region
|
||||
context.rectangle(event.area.x, event.area.y,
|
||||
event.area.width, event.area.height)
|
||||
context.clip()
|
||||
|
||||
width, height = self.graph_widget.allocation.width, self.graph_widget.allocation.height
|
||||
self.graph.draw_to_context(context, width, height)
|
||||
#Do not propagate the event
|
||||
return False
|
||||
|
||||
def update(self):
|
||||
log.debug("getstat keys: %s", self.graph.stat_info.keys())
|
||||
d1 = client.stats.get_stats(self.graph.stat_info.keys())
|
||||
d1.addCallback(self.graph.set_stats)
|
||||
d2 = client.stats.get_config()
|
||||
d2.addCallback(self.graph.set_config)
|
||||
dl = defer.DeferredList([d1, d2])
|
||||
|
||||
def _on_update(result):
|
||||
width, height = self.graph_widget.allocation.width, self.graph_widget.allocation.height
|
||||
rect = gtk.gdk.Rectangle(0, 0, width, height)
|
||||
self.graph_widget.window.invalidate_rect(rect, True)
|
||||
|
||||
dl.addCallback(_on_update)
|
||||
|
||||
def clear(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class GtkUI(GtkPluginBase):
|
||||
def enable(self):
|
||||
log.debug("Stats plugin enable called")
|
||||
self.glade = XML(self.get_resource("config.glade"))
|
||||
component.get("Preferences").add_page("Stats", self.glade.get_widget("prefs_box"))
|
||||
component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs)
|
||||
component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs)
|
||||
self.on_show_prefs()
|
||||
|
||||
self.graphs_tab = GraphsTab(XML(self.get_resource("tabs.glade")))
|
||||
self.torrent_details = component.get('TorrentDetails')
|
||||
self.torrent_details.add_tab(self.graphs_tab)
|
||||
|
||||
def disable(self):
|
||||
component.get("Preferences").remove_page("Stats")
|
||||
component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs)
|
||||
component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs)
|
||||
self.torrent_details.remove_tab(self.graphs_tab.get_name())
|
||||
|
||||
def on_apply_prefs(self):
|
||||
log.debug("applying prefs for Stats")
|
||||
config = {
|
||||
"test":self.glade.get_widget("txt_test").get_text()
|
||||
}
|
||||
client.stats.set_config(config)
|
||||
|
||||
def on_show_prefs(self):
|
||||
client.stats.get_config().addCallback(self.cb_get_config)
|
||||
|
||||
def cb_get_config(self, config):
|
||||
"callback for on show_prefs"
|
||||
self.glade.get_widget("txt_test").set_text(config["test"])
|
||||
|
||||
def get_resource(self, filename):
|
||||
import pkg_resources, os
|
||||
return pkg_resources.resource_filename("stats", os.path.join("data", filename))
|
@ -1,9 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="2" />
|
||||
</head>
|
||||
<body>
|
||||
<img src="output_async.png" /> <br />
|
||||
<img src="output_dht.png" />
|
||||
</body>
|
||||
</html>
|
@ -1,78 +0,0 @@
|
||||
from deluge.ui.client import sclient, aclient
|
||||
sclient.set_core_uri()
|
||||
import graph
|
||||
import deluge
|
||||
|
||||
def test_sync():
|
||||
if 1:
|
||||
upload = sclient.graph_get_upload()
|
||||
download = sclient.graph_get_download()
|
||||
print upload
|
||||
print download
|
||||
else:
|
||||
upload = [66804, 66915, 66974, 67447, 67540, 67318, 67320, 67249, 66659, 66489, 67027, 66914, 66802, 67303, 67654, 67643, 67763, 67528, 67523, 67431, 67214, 66939, 67316, 67020, 66881, 67103, 67377, 67141, 67366, 67492, 67375, 67203, 67056, 67010, 67029, 66741, 66695, 66868, 66805, 66264, 66249, 66317, 66459, 66306, 66681, 66954, 66662, 66278, 65921, 65695, 65681, 65942, 66000, 66140, 66424, 66480, 66257, 66271, 66145, 65854, 65568, 65268, 65112, 65050, 65027, 64676, 64655, 64178, 64386, 63979, 63271, 62746, 62337, 62297, 62496, 62902, 63801, 64121, 62957, 62921, 63051, 62644, 63240, 64107, 63968, 63987, 63644, 63263, 63153, 62999, 62843, 62777, 63101, 63078, 63178, 63126, 63401, 62630, 62451, 62505, 62254, 61485, 61264, 60937, 60568, 61011, 61109, 60325, 60196, 59640, 59619, 59514, 60813, 60572, 61632, 61689, 63365, 64583, 66396, 67179, 68209, 68295, 67674, 67559, 67195, 66178, 65632, 66124, 66456, 66676, 67183, 67620, 66960, 66347, 65925, 65907, 65896, 66738, 66703, 67060, 67004, 67007, 66329, 65304, 52002, 38969, 25433, 12426, 0, 0]
|
||||
download = [42926, 43853, 43157, 45470, 44254, 46272, 45083, 47344, 46716, 51963, 50112, 52334, 55525, 57545, 53691, 51637, 49574, 49836, 48295, 49843, 52878, 56014, 56966, 56938, 60065, 60461, 56542, 59526, 58678, 54424, 51862, 55109, 52132, 53783, 51687, 56567, 52182, 50758, 46714, 50511, 48161, 50920, 48694, 50528, 55074, 55420, 55882, 59268, 59958, 57938, 57115, 51424, 51180, 53184, 52879, 51177, 54417, 51097, 47901, 49870, 55865, 61118, 61476, 63498, 58878, 49630, 45975, 45632, 45892, 44855, 49495, 48304, 45829, 42152, 39403, 37574, 32384, 34933, 34901, 33492, 31953, 36271, 33826, 34515, 36408, 41106, 43054, 44110, 40810, 41383, 37267, 35881, 38660, 37525, 34857, 36718, 36842, 34281, 39528, 41854, 42952, 40021, 41722, 41045, 42917, 39287, 38672, 32824, 28765, 22686, 18490, 15714, 15268, 14793, 15305, 16354, 16720, 17502, 17857, 16622, 18447, 19929, 31138, 36965, 36158, 32795, 30445, 21997, 18100, 22491, 27227, 29317, 32436, 35700, 39140, 36258, 33697, 24751, 20354, 8211, 3836, 1560, 834, 2034, 1744, 1637, 1637, 1637, 0, 0]
|
||||
|
||||
from graph import NetworkGraph
|
||||
n = NetworkGraph()
|
||||
n.savedUpSpeeds = upload
|
||||
n.savedDownSpeeds = download
|
||||
|
||||
n.draw(800,200)
|
||||
n.surface.write_to_png('output_sync.png')
|
||||
|
||||
def test_async():
|
||||
g = graph.Graph()
|
||||
g.add_stat('download_rate', color=graph.green)
|
||||
g.add_stat('upload_rate', color=graph.blue)
|
||||
g.set_left_axis(formatter=deluge.common.fspeed, min=10240)
|
||||
g.async_request()
|
||||
aclient.force_call(True)
|
||||
surface = g.draw(600, 300)
|
||||
surface.write_to_png('output_async.png')
|
||||
|
||||
def test_dht():
|
||||
"""'boring graph, but testing if it works'"""
|
||||
|
||||
g = graph.Graph()
|
||||
g.add_stat('dht_nodes', color=graph.orange)
|
||||
g.add_stat('dht_cache_nodes', color=graph.blue)
|
||||
g.add_stat('dht_torrents', color=graph.green)
|
||||
g.add_stat('num_connections', color=graph.darkred) #testing : non dht
|
||||
g.set_left_axis(formatter=str, min=10)
|
||||
g.async_request()
|
||||
aclient.force_call(True)
|
||||
surface = g.draw(600, 300)
|
||||
surface.write_to_png('output_dht.png')
|
||||
|
||||
|
||||
def test_write():
|
||||
"""
|
||||
writing to a file-like object; need this for webui.
|
||||
"""
|
||||
class fake_file:
|
||||
def __init__(self):
|
||||
self.data = []
|
||||
def write(self, str):
|
||||
self.data.append(str)
|
||||
|
||||
g = graph.Graph()
|
||||
g.add_stat('download_rate', color=graph.green)
|
||||
g.add_stat('upload_rate', color=graph.blue)
|
||||
g.set_left_axis(formatter=deluge.common.fspeed, min=10240)
|
||||
g.async_request()
|
||||
aclient.force_call(True)
|
||||
surface = g.draw(900, 150)
|
||||
|
||||
file_like = fake_file()
|
||||
surface.write_to_png(file_like)
|
||||
data = "".join(file_like.data)
|
||||
|
||||
f = open("file_like.png","wb")
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
#test_sync()
|
||||
test_async()
|
||||
test_dht()
|
||||
#test_write()
|
@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
while true; do
|
||||
python test.py
|
||||
sleep 2
|
||||
done;
|
@ -1,23 +0,0 @@
|
||||
from deluge.ui.client import sclient, aclient
|
||||
from deluge.common import fsize
|
||||
sclient.set_core_uri()
|
||||
|
||||
def print_totals(totals):
|
||||
for name, value in totals.iteritems():
|
||||
print name , fsize(value)
|
||||
|
||||
print "overhead:"
|
||||
print "up:", fsize(totals["total_upload"] - totals["total_payload_upload"] )
|
||||
print "down:", fsize(totals["total_download"] - totals["total_payload_download"] )
|
||||
|
||||
|
||||
print "==totals=="
|
||||
print_totals(sclient.stats_get_totals())
|
||||
|
||||
print "==session totals=="
|
||||
print_totals(sclient.stats_get_session_totals())
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,54 +0,0 @@
|
||||
#
|
||||
# webui.py
|
||||
#
|
||||
# Copyright (C) 2009 Damien Churchill <mvoncken@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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge import component
|
||||
from deluge.plugins.pluginbase import WebPluginBase
|
||||
|
||||
from common import get_resource
|
||||
|
||||
class WebUI(WebPluginBase):
|
||||
|
||||
scripts = [get_resource("stats.js")]
|
||||
|
||||
# The enable and disable methods are not scrictly required on the WebUI
|
||||
# plugins. They are only here if you need to register images/stylesheets
|
||||
# with the webserver.
|
||||
def enable(self):
|
||||
log.debug("Stats Web plugin enabled!")
|
||||
|
||||
def disable(self):
|
||||
log.debug("Stats Web plugin disabled!")
|
@ -35,7 +35,11 @@
|
||||
|
||||
from twisted.internet.protocol import Protocol, ClientFactory
|
||||
from twisted.internet import reactor, ssl, defer
|
||||
import deluge.rencode as rencode
|
||||
try:
|
||||
import rencode
|
||||
except ImportError:
|
||||
import deluge.rencode as rencode
|
||||
|
||||
import zlib
|
||||
|
||||
import deluge.common
|
||||
@ -430,7 +434,7 @@ class DaemonClassicProxy(DaemonProxy):
|
||||
self.connected = True
|
||||
self.host = "localhost"
|
||||
self.port = 58846
|
||||
self.user = "localclient"
|
||||
self.username = "localclient"
|
||||
# Register the event handlers
|
||||
for event in event_handlers:
|
||||
for handler in event_handlers[event]:
|
||||
|
@ -126,7 +126,8 @@ def get_line_length(line, encoding="UTF-8"):
|
||||
if line.count("{!") != line.count("!}"):
|
||||
raise BadColorString("Number of {! is not equal to number of !}")
|
||||
|
||||
line = line.encode(encoding, "replace")
|
||||
if isinstance(line, unicode):
|
||||
line = line.encode(encoding, "replace")
|
||||
|
||||
# Remove all the color tags
|
||||
line = strip_colors(line)
|
||||
@ -146,7 +147,8 @@ def parse_color_string(s, encoding="UTF-8"):
|
||||
if s.count("{!") != s.count("!}"):
|
||||
raise BadColorString("Number of {! is not equal to number of !}")
|
||||
|
||||
s = s.encode(encoding, "replace")
|
||||
if isinstance(s, unicode):
|
||||
s = s.encode(encoding, "replace")
|
||||
|
||||
ret = []
|
||||
# Keep track of where the strings
|
||||
|
@ -63,6 +63,9 @@ class Command(BaseCommand):
|
||||
# Keep a list of deferreds to make a DeferredList
|
||||
deferreds = []
|
||||
for arg in args:
|
||||
if not os.path.exists(arg):
|
||||
self.console.write("{!error!}%s doesn't exist!" % arg)
|
||||
continue
|
||||
if not os.path.isfile(arg):
|
||||
self.console.write("{!error!}This is a directory!")
|
||||
continue
|
||||
|
@ -109,6 +109,8 @@ class BaseCommand(object):
|
||||
return self.__doc__
|
||||
|
||||
def split(self, text):
|
||||
if deluge.common.windows_check():
|
||||
text = text.replace('\\', '\\\\')
|
||||
return shlex.split(text)
|
||||
|
||||
def create_parser(self):
|
||||
|
@ -308,7 +308,7 @@ class Screen(CursesStdIO):
|
||||
if c == curses.KEY_ENTER or c == 10:
|
||||
if self.input:
|
||||
self.add_line(">>> " + self.input)
|
||||
self.command_parser(self.input)
|
||||
self.command_parser(self.input.encode(self.encoding))
|
||||
if len(self.input_history) == INPUT_HISTORY_SIZE:
|
||||
# Remove the oldest input history if the max history size
|
||||
# is reached.
|
||||
@ -404,21 +404,13 @@ class Screen(CursesStdIO):
|
||||
if c > 31 and c < 256:
|
||||
# Emulate getwch
|
||||
stroke = chr(c)
|
||||
|
||||
uchar = None
|
||||
|
||||
while 1:
|
||||
uchar = ""
|
||||
while not uchar:
|
||||
try:
|
||||
uchar = stroke.decode(self.encoding)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
|
||||
c = self.stdscr.getch()
|
||||
|
||||
if c == -1:
|
||||
break
|
||||
|
||||
stroke += chr(c)
|
||||
c = self.stdscr.getch()
|
||||
stroke += chr(c)
|
||||
|
||||
if uchar:
|
||||
if self.input_cursor == len(self.input):
|
||||
@ -426,7 +418,7 @@ class Screen(CursesStdIO):
|
||||
else:
|
||||
# Insert into string
|
||||
self.input = self.input[:self.input_cursor] + uchar + self.input[self.input_cursor:]
|
||||
|
||||
|
||||
# Move the cursor forward
|
||||
self.input_cursor += 1
|
||||
|
||||
|
@ -214,7 +214,7 @@ class CreateTorrentDialog:
|
||||
client.core.get_path_size(result).addCallback(_on_get_path_size)
|
||||
client.force_call(True)
|
||||
|
||||
dialog.destroy()
|
||||
dialog.hide()
|
||||
|
||||
def _on_button_cancel_clicked(self, widget):
|
||||
log.debug("_on_button_cancel_clicked")
|
||||
@ -228,11 +228,14 @@ class CreateTorrentDialog:
|
||||
is_remote = self.files_treestore[0][1] == gtk.STOCK_NETWORK
|
||||
if is_remote:
|
||||
# This is a remote path
|
||||
response = self.glade.get_widget("remote_save_dialog").run()
|
||||
dialog = self.glade.get_widget("remote_save_dialog")
|
||||
response = dialog.run()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
result = self.glade.get_widget("entry_save_path").get_text()
|
||||
else:
|
||||
dialog.hide()
|
||||
return
|
||||
dialog.hide()
|
||||
else:
|
||||
# Setup the filechooserdialog
|
||||
chooser = gtk.FileChooserDialog(_("Save .torrent file"),
|
||||
@ -344,7 +347,6 @@ class CreateTorrentDialog:
|
||||
trackers,
|
||||
add_to_session).addCallback(hide_progress)
|
||||
|
||||
chooser.destroy()
|
||||
self.dialog.destroy()
|
||||
|
||||
def create_torrent(self, path, tracker, piece_length, progress, comment, target,
|
||||
|
@ -210,7 +210,7 @@ class FilterTreeView(component.Component):
|
||||
row = self.treestore.append(self.cat_nodes[cat],[cat, value, label, count , pix, True])
|
||||
self.filters[(cat, value)] = row
|
||||
|
||||
if cat == "tracker_host" and value not in ("All", "Error"):
|
||||
if cat == "tracker_host" and value not in ("All", "Error") and value:
|
||||
d = self.tracker_icons.get(value)
|
||||
d.addCallback(on_get_icon)
|
||||
|
||||
|
@ -557,6 +557,7 @@
|
||||
1 MiB
|
||||
2 MiB
|
||||
4 MiB
|
||||
8 MiB
|
||||
</property>
|
||||
</widget>
|
||||
<packing>
|
||||
|
@ -252,12 +252,6 @@ class GtkUI(object):
|
||||
# Shutdown all components
|
||||
component.shutdown()
|
||||
|
||||
if self.started_in_classic:
|
||||
try:
|
||||
client.daemon.shutdown()
|
||||
except:
|
||||
pass
|
||||
|
||||
# Make sure the config is saved.
|
||||
self.config.save()
|
||||
|
||||
|
@ -38,7 +38,11 @@ import sys
|
||||
import os
|
||||
import base64
|
||||
|
||||
import deluge.rencode
|
||||
try:
|
||||
import rencode
|
||||
except ImportError:
|
||||
import deluge.rencode as rencode
|
||||
|
||||
import deluge.component as component
|
||||
from deluge.ui.client import client
|
||||
import deluge.common
|
||||
@ -51,12 +55,12 @@ import twisted.internet.error
|
||||
|
||||
class IPCProtocolServer(Protocol):
|
||||
def dataReceived(self, data):
|
||||
data = deluge.rencode.loads(data)
|
||||
data = rencode.loads(data)
|
||||
process_args(data)
|
||||
|
||||
class IPCProtocolClient(Protocol):
|
||||
def connectionMade(self):
|
||||
self.transport.write(deluge.rencode.dumps(self.factory.args))
|
||||
self.transport.write(rencode.dumps(self.factory.args))
|
||||
self.transport.loseConnection()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
|
@ -153,7 +153,10 @@ class MainWindow(component.Component):
|
||||
return self.main_glade
|
||||
|
||||
def quit(self):
|
||||
reactor.stop()
|
||||
if client.is_classicmode():
|
||||
gtk.main_quit()
|
||||
else:
|
||||
reactor.stop()
|
||||
|
||||
def load_window_state(self):
|
||||
x = self.config["window_x_pos"]
|
||||
|
@ -260,8 +260,6 @@ class MenuBar(component.Component):
|
||||
|
||||
def on_menuitem_quit_activate(self, data=None):
|
||||
log.debug("on_menuitem_quit_activate")
|
||||
if self.config["classic_mode"] and client.is_classicmode():
|
||||
client.daemon.shutdown()
|
||||
self.window.quit()
|
||||
|
||||
## Edit Menu ##
|
||||
|
@ -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'"""
|
||||
|
@ -88,21 +88,25 @@ def cell_data_statusicon(column, cell, model, row, data):
|
||||
|
||||
def cell_data_trackericon(column, cell, model, row, data):
|
||||
def on_get_icon(icon):
|
||||
def create_blank_icon():
|
||||
i = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 16, 16)
|
||||
i.fill(0x00000000)
|
||||
return i
|
||||
|
||||
if icon:
|
||||
try:
|
||||
icon = gtk.gdk.pixbuf_new_from_file_at_size(icon.get_filename(), 16, 16)
|
||||
except Exception, e:
|
||||
pass
|
||||
icon = create_blank_icon()
|
||||
else:
|
||||
icon = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 16, 16)
|
||||
icon.fill(0x00000000)
|
||||
icon = create_blank_icon()
|
||||
|
||||
if cell.get_property("pixbuf") != icon:
|
||||
cell.set_property("pixbuf", icon)
|
||||
|
||||
host = model[row][data]
|
||||
if host:
|
||||
d = component.get("TrackerIcons").get(model[row][data])
|
||||
d = component.get("TrackerIcons").get(host)
|
||||
d.addCallback(on_get_icon)
|
||||
else:
|
||||
on_get_icon(None)
|
||||
|
@ -62,6 +62,9 @@ class SessionProxy(component.Component):
|
||||
# Hold the torrents' status.. {torrent_id: [time, {status_dict}], ...}
|
||||
self.torrents = {}
|
||||
|
||||
# Holds the time of the last key update.. {torrent_id: {key1, time, ...}, ...}
|
||||
self.cache_times = {}
|
||||
|
||||
client.register_event_handler("TorrentStateChangedEvent", self.on_torrent_state_changed)
|
||||
client.register_event_handler("TorrentRemovedEvent", self.on_torrent_removed)
|
||||
client.register_event_handler("TorrentAddedEvent", self.on_torrent_added)
|
||||
@ -72,10 +75,16 @@ class SessionProxy(component.Component):
|
||||
t = time.time()
|
||||
for key, value in status.items():
|
||||
self.torrents[key] = [t, value]
|
||||
self.cache_times[key] = {}
|
||||
for k, v in value.items():
|
||||
self.cache_times[key][k] = t
|
||||
|
||||
return client.core.get_torrents_status({}, [], True).addCallback(on_torrent_status)
|
||||
|
||||
def stop(self):
|
||||
client.deregister_event_handler("TorrentStateChangedEvent", self.on_torrent_state_changed)
|
||||
client.deregister_event_handler("TorrentRemovedEvent", self.on_torrent_removed)
|
||||
client.deregister_event_handler("TorrentAddedEvent", self.on_torrent_added)
|
||||
self.torrents = {}
|
||||
|
||||
def create_status_dict(self, torrent_ids, keys):
|
||||
@ -93,7 +102,11 @@ class SessionProxy(component.Component):
|
||||
"""
|
||||
sd = {}
|
||||
for torrent_id in torrent_ids:
|
||||
sd[torrent_id] = dict([(x, y) for x, y in self.torrents[torrent_id][1].iteritems() if x in keys])
|
||||
if keys:
|
||||
sd[torrent_id] = dict([(x, y) for x, y in self.torrents[torrent_id][1].iteritems() if x in keys])
|
||||
else:
|
||||
sd[torrent_id] = dict(self.torrents[torrent_id][1])
|
||||
|
||||
return sd
|
||||
|
||||
def get_torrent_status(self, torrent_id, keys):
|
||||
@ -110,20 +123,37 @@ class SessionProxy(component.Component):
|
||||
|
||||
"""
|
||||
if torrent_id in self.torrents:
|
||||
if time.time() - self.torrents[torrent_id][0] < self.cache_time:
|
||||
return succeed(self.create_status_dict([torrent_id], keys)[torrent_id])
|
||||
# Keep track of keys we need to request from the core
|
||||
keys_to_get = []
|
||||
if not keys:
|
||||
keys = self.torrents[torrent_id][1].keys()
|
||||
|
||||
for key in keys:
|
||||
if time.time() - self.cache_times[torrent_id][key] > self.cache_time:
|
||||
keys_to_get.append(key)
|
||||
|
||||
if not keys_to_get:
|
||||
return succeed(self.create_status_dict([torrent_id], keys)[torrent_id])
|
||||
else:
|
||||
d = client.core.get_torrent_status(torrent_id, keys, True)
|
||||
d = client.core.get_torrent_status(torrent_id, keys_to_get, True)
|
||||
def on_status(result, torrent_id):
|
||||
self.torrents[torrent_id][0] = time.time()
|
||||
t = time.time()
|
||||
self.torrents[torrent_id][0] = t
|
||||
self.torrents[torrent_id][1].update(result)
|
||||
for key in keys_to_get:
|
||||
self.cache_times[torrent_id][key] = t
|
||||
return self.create_status_dict([torrent_id], keys)[torrent_id]
|
||||
return d.addCallback(on_status, torrent_id)
|
||||
else:
|
||||
d = client.core.get_torrent_status(torrent_id, keys, True)
|
||||
def on_status(result):
|
||||
if result:
|
||||
self.torrents[torrent_id] = (time.time(), result)
|
||||
t = time.time()
|
||||
self.torrents[torrent_id] = (t, result)
|
||||
self.cache_times[torrent_id] = {}
|
||||
for key in result:
|
||||
self.cache_times[torrent_id][key] = t
|
||||
|
||||
return result
|
||||
return d.addCallback(on_status)
|
||||
|
||||
@ -151,6 +181,8 @@ class SessionProxy(component.Component):
|
||||
for key, value in result.items():
|
||||
self.torrents[key][0] = t
|
||||
self.torrents[key][1].update(value)
|
||||
for k in value:
|
||||
self.cache_times[key][k] = t
|
||||
|
||||
# Create the status dict
|
||||
if not torrent_ids:
|
||||
@ -161,10 +193,16 @@ class SessionProxy(component.Component):
|
||||
def find_torrents_to_fetch(torrent_ids):
|
||||
to_fetch = []
|
||||
t = time.time()
|
||||
for key in torrent_ids:
|
||||
torrent = self.torrents[key]
|
||||
for torrent_id in torrent_ids:
|
||||
torrent = self.torrents[torrent_id]
|
||||
if t - torrent[0] > self.cache_time:
|
||||
to_fetch.append(key)
|
||||
to_fetch.append(torrent_id)
|
||||
else:
|
||||
# We need to check if a key is expired
|
||||
for key in keys:
|
||||
if t - self.cache_times[torrent_id].get(key, 0.0) > self.cache_time:
|
||||
to_fetch.append(torrent_id)
|
||||
break
|
||||
|
||||
return to_fetch
|
||||
#-----------------------------------------------------------------------
|
||||
@ -197,13 +235,20 @@ class SessionProxy(component.Component):
|
||||
return d.addCallback(on_status, None, keys)
|
||||
|
||||
def on_torrent_state_changed(self, torrent_id, state):
|
||||
self.torrents[torrent_id][1]["state"] = state
|
||||
if torrent_id in self.torrents:
|
||||
self.torrents[torrent_id][1]["state"] = state
|
||||
self.cache_times[torrent_id]["state"] = time.time()
|
||||
|
||||
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)
|
||||
t = time.time()
|
||||
for key in status:
|
||||
self.cache_times[torrent_id][key] = t
|
||||
client.core.get_torrent_status(torrent_id, []).addCallback(on_status)
|
||||
|
||||
def on_torrent_removed(self, torrent_id):
|
||||
del self.torrents[torrent_id]
|
||||
del self.cache_times[torrent_id]
|
||||
|
@ -34,7 +34,7 @@
|
||||
#
|
||||
|
||||
import os
|
||||
from HTMLParser import HTMLParser
|
||||
from HTMLParser import HTMLParser, HTMLParseError
|
||||
from urlparse import urljoin, urlparse
|
||||
from tempfile import mkstemp
|
||||
|
||||
@ -121,37 +121,39 @@ class TrackerIcons(Component):
|
||||
"""
|
||||
A TrackerIcon factory class
|
||||
"""
|
||||
def __init__(self, dir=None, noIcon=None):
|
||||
def __init__(self, icon_dir=None, no_icon=None):
|
||||
"""
|
||||
Initialises a new TrackerIcons object
|
||||
|
||||
:param dir: the (optional) directory of where to store the icons
|
||||
:type dir: string
|
||||
:param noIcon: the (optional) path name of the icon to show when no icon
|
||||
:param icon_dir: the (optional) directory of where to store the icons
|
||||
:type icon_dir: string
|
||||
:param no_icon: the (optional) path name of the icon to show when no icon
|
||||
can be fetched
|
||||
:type noIcon: string
|
||||
:type no_icon: string
|
||||
"""
|
||||
Component.__init__(self, "TrackerIcons")
|
||||
if not dir:
|
||||
dir = get_config_dir("icons")
|
||||
self.dir = dir
|
||||
if not icon_dir:
|
||||
icon_dir = get_config_dir("icons")
|
||||
self.dir = icon_dir
|
||||
if not os.path.isdir(self.dir):
|
||||
os.makedirs(self.dir)
|
||||
|
||||
self.icons = {}
|
||||
for icon in os.listdir(self.dir):
|
||||
if icon != noIcon:
|
||||
if icon != no_icon:
|
||||
host = icon_name_to_host(icon)
|
||||
try:
|
||||
self.icons[host] = TrackerIcon(os.path.join(self.dir, icon))
|
||||
except KeyError:
|
||||
log.warning("invalid icon %s", icon)
|
||||
if noIcon:
|
||||
self.icons[None] = TrackerIcon(noIcon)
|
||||
if no_icon:
|
||||
self.icons[None] = TrackerIcon(no_icon)
|
||||
else:
|
||||
self.icons[None] = None
|
||||
self.icons[''] = self.icons[None]
|
||||
|
||||
self.pending = {}
|
||||
self.redirects = {}
|
||||
|
||||
def get(self, host):
|
||||
"""
|
||||
@ -202,7 +204,7 @@ class TrackerIcons(Component):
|
||||
:rtype: Deferred
|
||||
"""
|
||||
if not url:
|
||||
url = host_to_url(host)
|
||||
url = self.host_to_url(host)
|
||||
log.debug("Downloading %s", url)
|
||||
return download_file(url, mkstemp()[1], force_filename=True)
|
||||
|
||||
@ -235,7 +237,8 @@ class TrackerIcons(Component):
|
||||
d = f
|
||||
if f.check(error.PageRedirect):
|
||||
# Handle redirect errors
|
||||
location = urljoin(host_to_url(host), error_msg.split(" to ")[1])
|
||||
location = urljoin(self.host_to_url(host), error_msg.split(" to ")[1])
|
||||
self.redirects[host] = url_to_host(location)
|
||||
d = self.download_page(host, url=location)
|
||||
d.addCallbacks(self.on_download_page_complete, self.on_download_page_fail,
|
||||
errbackArgs=(host,))
|
||||
@ -260,7 +263,10 @@ class TrackerIcons(Component):
|
||||
break
|
||||
parser.close()
|
||||
f.close()
|
||||
os.remove(page)
|
||||
try:
|
||||
os.remove(page)
|
||||
except Exception, e:
|
||||
log.warning("Couldn't remove temp file: %s", e)
|
||||
return parser.get_icons()
|
||||
|
||||
def on_parse_complete(self, icons, host):
|
||||
@ -275,7 +281,7 @@ class TrackerIcons(Component):
|
||||
:rtype: list
|
||||
"""
|
||||
log.debug("Got icons for %s: %s", host, icons)
|
||||
url = host_to_url(host)
|
||||
url = self.host_to_url(host)
|
||||
icons = [(urljoin(url, icon), mimetype) for icon, mimetype in icons]
|
||||
log.debug("Icon urls: %s", icons)
|
||||
return icons
|
||||
@ -304,13 +310,39 @@ class TrackerIcons(Component):
|
||||
:returns: a Deferred which fires with the downloaded icon's filename
|
||||
:rtype: Deferred
|
||||
"""
|
||||
if len(icons) == 0:
|
||||
raise NoIconsError, "empty icons list"
|
||||
(url, mimetype) = icons.pop(0)
|
||||
d = download_file(url, os.path.join(self.dir, host_to_icon_name(host, mimetype)),
|
||||
force_filename=True)
|
||||
d.addCallback(self.check_icon_is_valid)
|
||||
if icons:
|
||||
d.addErrback(self.on_download_icon_fail, host, icons)
|
||||
return d
|
||||
|
||||
@proxy(threads.deferToThread)
|
||||
def check_icon_is_valid(self, icon_name):
|
||||
"""
|
||||
Performs a sanity check on icon_name
|
||||
|
||||
:param icon_name: the name of the icon to check
|
||||
:type icon_name: string
|
||||
:returns: the name of the validated icon
|
||||
:rtype: string
|
||||
:raises: InvalidIconError
|
||||
"""
|
||||
|
||||
if PIL_INSTALLED:
|
||||
try:
|
||||
Image.open(icon_name)
|
||||
except IOError, e:
|
||||
raise InvalidIconError(e)
|
||||
else:
|
||||
if os.stat(icon_name).st_size == 0L:
|
||||
raise InvalidIconError, "empty icon"
|
||||
|
||||
return icon_name
|
||||
|
||||
def on_download_icon_complete(self, icon_name, host):
|
||||
"""
|
||||
Runs any download cleanup functions
|
||||
@ -345,16 +377,16 @@ class TrackerIcons(Component):
|
||||
d = f
|
||||
if f.check(error.PageRedirect):
|
||||
# Handle redirect errors
|
||||
location = urljoin(host_to_url(host), error_msg.split(" to ")[1])
|
||||
location = urljoin(self.host_to_url(host), error_msg.split(" to ")[1])
|
||||
d = self.download_icon([(location, extension_to_mimetype(location.rpartition('.')[2]))] + icons, host)
|
||||
if not icons:
|
||||
d.addCallbacks(self.on_download_icon_complete, self.on_download_icon_fail,
|
||||
callbackArgs=(host,), errbackArgs=(host,))
|
||||
elif f.check(error.NoResource, error.ForbiddenResource) and icons:
|
||||
d = self.download_icon(icons, host)
|
||||
elif f.check(IndexError):
|
||||
elif f.check(NoIconsError, HTMLParseError):
|
||||
# No icons, try favicon.ico as an act of desperation
|
||||
d = self.download_icon([(urljoin(host_to_url(host), "favicon.ico"), extension_to_mimetype("ico"))], host)
|
||||
d = self.download_icon([(urljoin(self.host_to_url(host), "favicon.ico"), extension_to_mimetype("ico"))], host)
|
||||
d.addCallbacks(self.on_download_icon_complete, self.on_download_icon_fail,
|
||||
callbackArgs=(host,), errbackArgs=(host,))
|
||||
else:
|
||||
@ -404,6 +436,19 @@ class TrackerIcons(Component):
|
||||
del self.pending[host]
|
||||
return icon
|
||||
|
||||
def host_to_url(self, host):
|
||||
"""
|
||||
Given a host, returns the URL to fetch
|
||||
|
||||
:param host: the tracker host
|
||||
:type host: string
|
||||
:returns: the url of the tracker
|
||||
:rtype: string
|
||||
"""
|
||||
if host in self.redirects:
|
||||
host = self.redirects[host]
|
||||
return "http://%s/" % host
|
||||
|
||||
################################ HELPER CLASSES ###############################
|
||||
|
||||
class FaviconParser(HTMLParser):
|
||||
@ -450,17 +495,6 @@ class FaviconParser(HTMLParser):
|
||||
|
||||
############################### HELPER FUNCTIONS ##############################
|
||||
|
||||
def host_to_url(host):
|
||||
"""
|
||||
Given a host, returns the URL to fetch
|
||||
|
||||
:param host: the tracker host
|
||||
:type host: string
|
||||
:returns: the url of the tracker
|
||||
:rtype: string
|
||||
"""
|
||||
return "http://%s/" % host
|
||||
|
||||
def url_to_host(url):
|
||||
"""
|
||||
Given a URL, returns the host it belongs to
|
||||
@ -532,3 +566,11 @@ def extension_to_mimetype(extension):
|
||||
:raises KeyError: if given an invalid filename extension
|
||||
"""
|
||||
return MIME_MAP[extension.lower()]
|
||||
|
||||
################################## EXCEPTIONS #################################
|
||||
|
||||
class NoIconsError(Exception):
|
||||
pass
|
||||
|
||||
class InvalidIconError(Exception):
|
||||
pass
|
||||
|
@ -1,3 +1,20 @@
|
||||
Icons in this folder are copied from the fruge icon set, taken from
|
||||
http://www.pinvoke.com, licensed under the Creative Common
|
||||
Attribution-ShareAlike 3.0 License.
|
||||
|
||||
Exceptions
|
||||
==========
|
||||
apple-pre-57.png
|
||||
apple-pre-72.png
|
||||
apple-pre-114.png
|
||||
|
||||
active.png
|
||||
alert.png
|
||||
all.png
|
||||
deluge.png
|
||||
dht.png
|
||||
downloading.png
|
||||
inactive.png
|
||||
queued.png
|
||||
seeding.png
|
||||
traffic.png
|
||||
|
BIN
deluge/ui/web/icons/apple-pre-114.png
Normal file
BIN
deluge/ui/web/icons/apple-pre-114.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
deluge/ui/web/icons/apple-pre-57.png
Normal file
BIN
deluge/ui/web/icons/apple-pre-57.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
BIN
deluge/ui/web/icons/apple-pre-72.png
Normal file
BIN
deluge/ui/web/icons/apple-pre-72.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
@ -1,37 +1,40 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Deluge: Web UI ${version}</title>
|
||||
|
||||
<link rel="shortcut icon" href="${base}icons/deluge.png" type="image/png" />
|
||||
<link rel="icon" href="${base}icons/deluge.png" type="image/png" />
|
||||
|
||||
<!-- Stylesheets -->
|
||||
% for stylesheet in stylesheets:
|
||||
<link rel="stylesheet" type="text/css" href="${base}${stylesheet}" />
|
||||
% endfor
|
||||
<head>
|
||||
<title>Deluge: Web UI ${version}</title>
|
||||
|
||||
<link rel="shortcut icon" href="${base}icons/deluge.png" type="image/png" />
|
||||
<link rel="icon" href="${base}icons/deluge.png" type="image/png" />
|
||||
<link rel="apple-touch-icon-precomposed" media="screen and (resolution: 163dpi)" href="${base}icons/apple-pre-57.png" />
|
||||
<link rel="apple-touch-icon-precomposed" media="screen and (resolution: 132dpi)" href="${base}icons/apple-pre-72.png" />
|
||||
<link rel="apple-touch-icon-precomposed" media="screen and (resolution: 326dpi)" href="${base}icons/apple-pre-114.png" />
|
||||
|
||||
<!-- Stylesheets -->
|
||||
% for stylesheet in stylesheets:
|
||||
<link rel="stylesheet" type="text/css" href="${base}${stylesheet}" />
|
||||
% endfor
|
||||
|
||||
<script type="text/javascript">
|
||||
<script type="text/javascript">
|
||||
deluge = {
|
||||
author: 'Damien Churchill <damoxc@gmail.com>',
|
||||
version: '${version}',
|
||||
config: ${js_config}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<!-- Javascript -->
|
||||
% for script in scripts:
|
||||
<script type="text/javascript" src="${base}${script}"></script>
|
||||
% endfor
|
||||
<script type="text/javascript">
|
||||
Deluge.debug = ${str(debug).lower()};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-image: url('${base}themes/default/tree/loading.gif');"></div>
|
||||
<!-- Javascript -->
|
||||
% for script in scripts:
|
||||
<script type="text/javascript" src="${base}${script}"></script>
|
||||
% endfor
|
||||
<script type="text/javascript">
|
||||
Deluge.debug = ${str(debug).lower()};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-image: url('${base}themes/default/tree/loading.gif');"></div>
|
||||
|
||||
<!-- Preload icon classes -->
|
||||
<div class="ext-mb-error"></div>
|
||||
<div class="icon-ok"></div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,45 +1,59 @@
|
||||
#!/bin/sh
|
||||
|
||||
add_file "data/SortTypes.js"
|
||||
add_file "data/PeerRecord.js"
|
||||
add_file "data/TorrentRecord.js"
|
||||
add_file "details/DetailsPanel.js"
|
||||
add_file "details/DetailsTab.js"
|
||||
add_file "details/FilesTab.js"
|
||||
add_file "details/OptionsTab.js"
|
||||
add_file "details/PeersTab.js"
|
||||
add_file "details/StatusTab.js"
|
||||
add_file "add/Window.js"
|
||||
add_file "add/AddWindow.js"
|
||||
add_file "add/FileWindow.js"
|
||||
add_file "add/FilesTab.js"
|
||||
add_file "add/Infohash.js"
|
||||
add_file "add/OptionsPanel.js"
|
||||
add_file "add/OptionsTab.js"
|
||||
add_file "add/UrlWindow.js"
|
||||
add_file "preferences/BandwidthPage.js"
|
||||
add_file "preferences/CachePage.js"
|
||||
add_file "preferences/DaemonPage.js"
|
||||
add_file "preferences/DownloadsPage.js"
|
||||
add_file "preferences/EncryptionPage.js"
|
||||
add_file "preferences/InstallPluginWindow.js"
|
||||
add_file "preferences/InterfacePage.js"
|
||||
add_file "preferences/NetworkPage.js"
|
||||
add_file "preferences/OtherPage.js"
|
||||
add_file "preferences/PluginsPage.js"
|
||||
add_file "preferences/PreferencesWindow.js"
|
||||
add_file "preferences/ProxyField.js"
|
||||
add_file "preferences/ProxyPage.js"
|
||||
add_file "preferences/QueuePage.js"
|
||||
add_file "StatusbarMenu.js"
|
||||
add_file "OptionsManager.js"
|
||||
add_file "AddConnectionWindow.js"
|
||||
add_file "AddTrackerWindow.js"
|
||||
add_file "Client.js"
|
||||
add_file "ConnectionManager.js"
|
||||
add_file "Deluge.js"
|
||||
add_file "Deluge.Formatters.js"
|
||||
add_file "Deluge.Keys.js"
|
||||
add_file "Deluge.Menus.js"
|
||||
add_file "Deluge.data.SortTypes.js"
|
||||
add_file "Deluge.EventsManager.js"
|
||||
add_file "Deluge.OptionsManager.js"
|
||||
add_file "Deluge.MultiOptionsManager.js"
|
||||
add_file "Deluge.Add.js"
|
||||
add_file "Deluge.Add.File.js"
|
||||
add_file "Deluge.Add.Url.js"
|
||||
add_file "Deluge.Client.js"
|
||||
add_file "Deluge.ConnectionManager.js"
|
||||
add_file "Deluge.Details.js"
|
||||
add_file "Deluge.Details.Status.js"
|
||||
add_file "Deluge.Details.Details.js"
|
||||
add_file "Deluge.Details.Files.js"
|
||||
add_file "Deluge.Details.Peers.js"
|
||||
add_file "Deluge.Details.Options.js"
|
||||
add_file "Deluge.EditTrackers.js"
|
||||
add_file "Deluge.FileBrowser.js"
|
||||
add_file "Deluge.Login.js"
|
||||
add_file "Deluge.MoveStorage.js"
|
||||
add_file "Deluge.Plugin.js"
|
||||
add_file "Deluge.Preferences.js"
|
||||
add_file "Deluge.Preferences.Downloads.js"
|
||||
add_file "Deluge.Preferences.Network.js"
|
||||
add_file "Deluge.Preferences.Encryption.js"
|
||||
add_file "Deluge.Preferences.Bandwidth.js"
|
||||
add_file "Deluge.Preferences.Interface.js"
|
||||
add_file "Deluge.Preferences.Other.js"
|
||||
add_file "Deluge.Preferences.Daemon.js"
|
||||
add_file "Deluge.Preferences.Queue.js"
|
||||
add_file "Deluge.Preferences.Proxy.js"
|
||||
add_file "Deluge.Preferences.Cache.js"
|
||||
add_file "Deluge.Preferences.Plugins.js"
|
||||
add_file "Deluge.Remove.js"
|
||||
add_file "Deluge.Sidebar.js"
|
||||
add_file "Deluge.Statusbar.js"
|
||||
add_file "Deluge.Toolbar.js"
|
||||
add_file "Deluge.Torrent.js"
|
||||
add_file "Deluge.Torrents.js"
|
||||
add_file "Deluge.UI.js"
|
||||
add_file "EditTrackerWindow.js"
|
||||
add_file "EditTrackersWindow.js"
|
||||
add_file "EventsManager.js"
|
||||
add_file "FileBrowser.js"
|
||||
add_file "FilterPanel.js"
|
||||
add_file "Formatters.js"
|
||||
add_file "Keys.js"
|
||||
add_file "LoginWindow.js"
|
||||
add_file "Menus.js"
|
||||
add_file "MoveStorage.js"
|
||||
add_file "MultiOptionsManager.js"
|
||||
add_file "OtherLimitWindow.js"
|
||||
add_file "Plugin.js"
|
||||
add_file "RemoveWindow.js"
|
||||
add_file "Sidebar.js"
|
||||
add_file "Statusbar.js"
|
||||
add_file "Toolbar.js"
|
||||
add_file "TorrentGrid.js"
|
||||
add_file "UI.js"
|
||||
|
@ -84,12 +84,12 @@ Deluge.Formatters = {
|
||||
/**
|
||||
* Formats a string to display a transfer speed utilizing {@link #size}
|
||||
*
|
||||
* @param {Number} bits the number of bits per second
|
||||
* @param {Number} bytes the number of bytes per second
|
||||
* @param {Boolean} showZero pass in true to displays 0 values
|
||||
* @return {String} formatted string with KiB, MiB or GiB units.
|
||||
*/
|
||||
speed: function(bits, showZero) {
|
||||
return (!bits && !showZero) ? '' : fsize(bits, showZero) + '/s';
|
||||
speed: function(bytes, showZero) {
|
||||
return (!bytes && !showZero) ? '' : fsize(bytes, showZero) + '/s';
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -107,7 +107,7 @@ Deluge.Sidebar = Ext.extend(Ext.Panel, {
|
||||
// Grab the filters from each of the filter panels
|
||||
this.items.each(function(panel) {
|
||||
var state = panel.getState();
|
||||
if (!state == null) return;
|
||||
if (state == null) return;
|
||||
states[panel.filterType] = state;
|
||||
}, this);
|
||||
} else {
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,16 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
add_file "BufferView.js"
|
||||
add_file "FileUploadField.js"
|
||||
add_file "layout/FormLayoutFix.js"
|
||||
add_file "tree/TreeGrid.js"
|
||||
add_file "tree/TreeGridColumnResizer.js"
|
||||
add_file "tree/TreeGridColumns.js"
|
||||
add_file "tree/TreeGridLoader.js"
|
||||
add_file "tree/TreeGridNodeUI.js"
|
||||
add_file "tree/TreeGridNodeUIFix.js"
|
||||
add_file "tree/TreeGridRenderColumn.js"
|
||||
add_file "tree/TreeGridSorter.js"
|
||||
add_file "grid/BufferView.js"
|
||||
add_file "form/FileUploadField.js"
|
||||
add_file "form/RadioGroupFix.js"
|
||||
add_file "form/SpinnerField.js"
|
||||
add_file "form/SpinnerGroup.js"
|
||||
add_file "form/ToggleField.js"
|
||||
add_file "JSLoader.js"
|
||||
add_file "Spinner.js"
|
||||
add_file "SpinnerField.js"
|
||||
add_file "StatusBar.js"
|
||||
add_file "ToggleField.js"
|
||||
add_file "TreeGridSorter.js"
|
||||
add_file "TreeGridColumnResizer.js"
|
||||
add_file "TreeGridNodeUI.js"
|
||||
add_file "TreeGridLoader.js"
|
||||
add_file "TreeGridColumns.js"
|
||||
add_file "TreeGridRenderColumn.js"
|
||||
add_file "TreeGrid.js"
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*!
|
||||
* Ext.ux.tree.MultiSelectionModelFix.js
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This enhances the MSM to allow for shift selecting in tree grids etc.
|
||||
* @author Damien Churchill <damoxc@gmail.com>
|
||||
*/
|
||||
Ext.override(Ext.tree.MultiSelectionModel, {
|
||||
|
||||
onNodeClick: function (node, e) {
|
||||
if (e.ctrlKey && this.isSelected(node)) {
|
||||
this.unselect(node);
|
||||
} else if (e.shiftKey && !this.isSelected(node)) {
|
||||
var parentNode = node.parentNode;
|
||||
// We can only shift select files in the same node
|
||||
if (this.lastSelNode.parentNode.id != parentNode.id) return;
|
||||
|
||||
// Get the node indexes
|
||||
var fi = parentNode.indexOf(node),
|
||||
li = parentNode.indexOf(this.lastSelNode);
|
||||
|
||||
// Select the last clicked node and wipe old selections
|
||||
this.select(this.lastSelNode, e, false, true);
|
||||
|
||||
// Swap the values if required
|
||||
if (fi > li) {
|
||||
fi = fi + li, li = fi - li, fi = fi - li;
|
||||
}
|
||||
|
||||
// Select all the nodes
|
||||
parentNode.eachChild(function(n) {
|
||||
var i = parentNode.indexOf(n);
|
||||
if (fi < i && i < li) {
|
||||
this.select(n, e, true, true);
|
||||
}
|
||||
}, this);
|
||||
|
||||
// Select the clicked node
|
||||
this.select(node, e, true);
|
||||
} else {
|
||||
this.select(node, e, e.ctrlKey);
|
||||
}
|
||||
},
|
||||
|
||||
select: function(node, e, keepExisting, suppressEvent) {
|
||||
if(keepExisting !== true){
|
||||
this.clearSelections(true);
|
||||
}
|
||||
if(this.isSelected(node)){
|
||||
this.lastSelNode = node;
|
||||
return node;
|
||||
}
|
||||
this.selNodes.push(node);
|
||||
this.selMap[node.id] = node;
|
||||
this.lastSelNode = node;
|
||||
node.ui.onSelectedChange(true);
|
||||
if (suppressEvent !== true) {
|
||||
this.fireEvent('selectionchange', this, this.selNodes);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
})
|
@ -110,7 +110,16 @@ class JSON(resource.Resource, component.Component):
|
||||
component.Component.__init__(self, "JSON")
|
||||
self._remote_methods = []
|
||||
self._local_methods = {}
|
||||
client.disconnect_callback = self._on_client_disconnect
|
||||
if client.is_classicmode():
|
||||
def on_got_methods(methods):
|
||||
"""
|
||||
Handles receiving the method names
|
||||
"""
|
||||
self._remote_methods = methods
|
||||
|
||||
client.daemon.get_method_list().addCallback(on_got_methods)
|
||||
else:
|
||||
client.disconnect_callback = self._on_client_disconnect
|
||||
|
||||
def connect(self, host="localhost", port=58846, username="", password=""):
|
||||
"""
|
||||
@ -410,7 +419,10 @@ class WebApi(JSONComponent):
|
||||
self.host_list = ConfigManager("hostlist.conf.1.2", DEFAULT_HOSTS)
|
||||
self.core_config = CoreConfig()
|
||||
self.event_queue = EventQueue()
|
||||
self.sessionproxy = SessionProxy()
|
||||
try:
|
||||
self.sessionproxy = component.get("SessionProxy")
|
||||
except KeyError:
|
||||
self.sessionproxy = SessionProxy()
|
||||
|
||||
def get_host(self, host_id):
|
||||
"""
|
||||
|
@ -188,7 +188,13 @@ class Render(resource.Resource):
|
||||
return compress(template.render(), request)
|
||||
|
||||
class Tracker(resource.Resource):
|
||||
tracker_icons = TrackerIcons()
|
||||
|
||||
def __init__(self):
|
||||
resource.Resource.__init__(self)
|
||||
try:
|
||||
self.tracker_icons = component.get("TrackerIcons")
|
||||
except KeyError:
|
||||
self.tracker_icons = TrackerIcons()
|
||||
|
||||
def getChild(self, path, request):
|
||||
request.tracker_name = path
|
||||
@ -405,7 +411,7 @@ class ScriptResource(resource.Resource, component.Component):
|
||||
|
||||
def getChild(self, path, request):
|
||||
if hasattr(request, "lookup_path"):
|
||||
request.lookup_path = os.path.join(request.lookup_path, path)
|
||||
request.lookup_path += '/' + path
|
||||
else:
|
||||
request.lookup_path = path
|
||||
return self
|
||||
|
@ -27,8 +27,10 @@ Port daemon will listen on, default is 58846
|
||||
.TP
|
||||
.I -i INTERFACE, --interface=INTERFACE
|
||||
Interface daemon will listen for bittorrent connections on, this should be an IP address
|
||||
.TP
|
||||
.I -u UI_INTERFACE, --ui-interface=UI_INTERFACE
|
||||
Interface daemon will listen for UI connections on, this should be an IP address
|
||||
.TP
|
||||
.I -d, --do-not-daemonize
|
||||
Do not daemonize
|
||||
.TP
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user