Compare commits
137 Commits
deluge-2.0
...
deluge-1.3
Author | SHA1 | Date | |
---|---|---|---|
b0ceae8d28 | |||
dc0bf3bc88 | |||
3b9d7ff9c3 | |||
a165d5d746 | |||
cc02ebea6a | |||
41ffee5d8a | |||
14a89b3f8a | |||
6f0c2af58a | |||
84cccabf19 | |||
7fb483adde | |||
28ce7a70a0 | |||
14565977fa | |||
e4420ef354 | |||
02ad0b93ab | |||
6d2a001635 | |||
2a3eb0578c | |||
60fac28217 | |||
59e01e7ecf | |||
4c52ee4229 | |||
8428524793 | |||
21c8d02d9a | |||
0c687c7684 | |||
78f9efefd9 | |||
6b228ce31f | |||
40ce4ec731 | |||
c029c312e4 | |||
16c38cd027 | |||
e23a6b852a | |||
90e4de54e9 | |||
c1505bea3a | |||
6235e832fe | |||
a71f14c47e | |||
ed3b23b0fc | |||
6402634ec1 | |||
3e68733cfd | |||
f847a7dc4e | |||
c7954c20eb | |||
dc7ed11601 | |||
d898def9ec | |||
3e2f6c4060 | |||
321a22a6f0 | |||
b4774af2f3 | |||
d0fd709c74 | |||
e24212b3f8 | |||
f8f72af6dc | |||
b9caa4eeeb | |||
6c3b216b40 | |||
eaad867885 | |||
f6b9f67df8 | |||
24fe3f7fd5 | |||
da2fb41a3a | |||
f8d7f22167 | |||
b75abc70e5 | |||
2d821bd79a | |||
12d9a7a5bd | |||
c118fa36a9 | |||
82c91cdc51 | |||
5501094214 | |||
b41aa808be | |||
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
|
81
ChangeLog
81
ChangeLog
@ -1,4 +1,63 @@
|
||||
=== Deluge 1.3.0 (In Development) ===
|
||||
=== Deluge 1.3.1 (31 October 2010) ===
|
||||
==== Core ====
|
||||
* #1369: Fix non-ascii config folders not working in windows
|
||||
|
||||
==== GtkUI ====
|
||||
* #1365: Fix sidebar not updating show/hide trackers
|
||||
* #1247: Fix hang on quit
|
||||
|
||||
==== WebUI ====
|
||||
* #1364: Fix preferences not saving when the web ui plugin is enabled in classic mode
|
||||
* #1377: Fix bug when enabling plugins
|
||||
* #1370: Fix issues with preferences
|
||||
* #1312: Fix deluge-web using 100% CPU
|
||||
|
||||
=== Deluge 1.3.0 (18 September 2010) ===
|
||||
==== Core ====
|
||||
* Fix issue where the save_timer is cancelled when it's not active
|
||||
* Fix unhandled exception when adding a torrent to the session
|
||||
* Moved xdg import so it is not called on Windows, where it is unused. fixes #1343
|
||||
* Fix key error after enabling a plugin that introduces a new status key
|
||||
* Ignore global stop ratio related settings in logic, so per torrent ones are used.
|
||||
* Ensure preferencesmanager only changes intended libtorrent session settings.
|
||||
* Fix issue when adding torrents without a 'session'. This can happen when a plugin adds a torrent, like how the AutoAdd plugin works. The user that adds this torrent will be an empty string.
|
||||
* Add TorrentFileCompleted event
|
||||
|
||||
==== GtkUI ====
|
||||
* Increase max piece size to 8 MiB in create torrent dialog (closes #1358)
|
||||
|
||||
==== Scheduler ====
|
||||
* Add max active downloading and seeding options to scheduler.
|
||||
* Fix scheduler so that it keeps current state, even after global settings change.
|
||||
|
||||
==== AutoAdd ====
|
||||
* AutoAdd plugin can now recover when one of the watchfolders has an unhandled exception.
|
||||
* Fix bug in AutoAdd plugin where watchdirs would not display in gtkui when first enabled.
|
||||
* Fix bugs with unicode torrents in AutoAdd plugin.
|
||||
|
||||
=== Deluge 1.3.0-rc2 (20 August 2010) ===
|
||||
==== Core ====
|
||||
* Fix tracker_icons failing on windows
|
||||
* Fix #1302 an uncaught exception in an state_changed event handler in SessionProxy was preventing the TorrentManager's stop method from properly saving all the resume data
|
||||
* Fix issue with SessionProxy not updating the torrent status correctly when get_torrent_status calls take place within the cache_expiry time
|
||||
|
||||
==== 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 +66,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 ====
|
||||
|
4
DEPENDS
4
DEPENDS
@ -7,6 +7,7 @@
|
||||
* setuptools
|
||||
* gettext
|
||||
* pyxdg
|
||||
* chardet
|
||||
* geoip-database (optional)
|
||||
|
||||
* libtorrent >= 0.14, or build the included version
|
||||
@ -16,9 +17,6 @@
|
||||
* openssl
|
||||
* zlib
|
||||
|
||||
=== UIs ===
|
||||
* chardet
|
||||
|
||||
=== Gtk ===
|
||||
* python-notify (libnotify python wrapper)
|
||||
* pygame
|
||||
|
@ -41,6 +41,7 @@ import time
|
||||
import subprocess
|
||||
import platform
|
||||
import sys
|
||||
import chardet
|
||||
|
||||
try:
|
||||
import json
|
||||
@ -63,7 +64,6 @@ if not hasattr(json, "dumps"):
|
||||
json.load = load
|
||||
|
||||
import pkg_resources
|
||||
import xdg, xdg.BaseDirectory
|
||||
import gettext
|
||||
import locale
|
||||
|
||||
@ -150,6 +150,7 @@ def get_default_config_dir(filename=None):
|
||||
else:
|
||||
return os.path.join(os.environ.get("APPDATA"), "deluge")
|
||||
else:
|
||||
import xdg.BaseDirectory
|
||||
if filename:
|
||||
return os.path.join(xdg.BaseDirectory.save_config_path("deluge"), filename)
|
||||
else:
|
||||
@ -474,7 +475,7 @@ def free_space(path):
|
||||
sectors, bytes, free, total = map(long, win32file.GetDiskFreeSpace(path))
|
||||
return (free * sectors * bytes)
|
||||
else:
|
||||
disk_data = os.statvfs(path)
|
||||
disk_data = os.statvfs(path.encode("utf8"))
|
||||
block_size = disk_data.f_bsize
|
||||
return disk_data.f_bavail * block_size
|
||||
|
||||
@ -560,6 +561,41 @@ def xml_encode(string):
|
||||
string = string.replace(char, escape)
|
||||
return string
|
||||
|
||||
def decode_string(s, encoding="utf8"):
|
||||
"""
|
||||
Decodes a string and re-encodes it in utf8. If it cannot decode using
|
||||
`:param:encoding` then it will try to detect the string encoding and
|
||||
decode it.
|
||||
|
||||
:param s: string to decode
|
||||
:type s: string
|
||||
:keyword encoding: the encoding to use in the decoding
|
||||
:type encoding: string
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
s = s.decode(encoding).encode("utf8", "ignore")
|
||||
except UnicodeDecodeError:
|
||||
s = s.decode(chardet.detect(s)["encoding"], "ignore").encode("utf8", "ignore")
|
||||
return s
|
||||
|
||||
def utf8_encoded(s):
|
||||
"""
|
||||
Returns a utf8 encoded string of s
|
||||
|
||||
:param s: (unicode) string to (re-)encode
|
||||
:type s: basestring
|
||||
:returns: a utf8 encoded string of s
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
if isinstance(s, str):
|
||||
s = decode_string(s, locale.getpreferredencoding())
|
||||
elif isinstance(s, unicode):
|
||||
s = s.encode("utf8", "ignore")
|
||||
return s
|
||||
|
||||
class VersionSplit(object):
|
||||
"""
|
||||
Used for comparing version numbers.
|
||||
@ -570,13 +606,15 @@ class VersionSplit(object):
|
||||
"""
|
||||
def __init__(self, ver):
|
||||
ver = ver.lower()
|
||||
vs = ver.split("_") if "_" in ver else ver.split("-")
|
||||
vs = ver.replace("_", "-").split("-")
|
||||
self.version = [int(x) for x in vs[0].split(".")]
|
||||
self.suffix = None
|
||||
self.dev = False
|
||||
if len(vs) > 1:
|
||||
for s in ("rc", "alpha", "beta", "dev"):
|
||||
if s in vs[1][:len(s)]:
|
||||
self.suffix = vs[1]
|
||||
if vs[1].startswith(("rc", "alpha", "beta")):
|
||||
self.suffix = vs[1]
|
||||
if vs[-1] == 'dev':
|
||||
self.dev = True
|
||||
|
||||
def __cmp__(self, ver):
|
||||
"""
|
||||
@ -587,19 +625,8 @@ class VersionSplit(object):
|
||||
|
||||
"""
|
||||
|
||||
if self.version > ver.version or (self.suffix and self.suffix[:3] == "dev"):
|
||||
return 1
|
||||
if self.version < ver.version:
|
||||
return -1
|
||||
|
||||
if self.version == ver.version:
|
||||
if self.suffix == ver.suffix:
|
||||
return 0
|
||||
if self.suffix is None:
|
||||
return 1
|
||||
if ver.suffix is None:
|
||||
return -1
|
||||
if self.suffix < ver.suffix:
|
||||
return -1
|
||||
if self.suffix > ver.suffix:
|
||||
return 1
|
||||
# If there is no suffix we use z because we want final
|
||||
# to appear after alpha, beta, and rc alphabetically.
|
||||
v1 = [self.version, self.suffix or 'z', self.dev]
|
||||
v2 = [ver.version, ver.suffix or 'z', ver.dev]
|
||||
return cmp(v1, v2)
|
||||
|
@ -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
|
||||
@ -146,7 +146,8 @@ class Config(object):
|
||||
self._save_timer = None
|
||||
|
||||
if defaults:
|
||||
self.__config = dict(defaults)
|
||||
for key, value in defaults.iteritems():
|
||||
self.set_item(key, value)
|
||||
|
||||
# Load the config from file in the config_dir
|
||||
if config_dir:
|
||||
@ -187,6 +188,10 @@ what is currently in the config and it could not convert the value
|
||||
5
|
||||
|
||||
"""
|
||||
if isinstance(value, basestring):
|
||||
value = deluge.common.utf8_encoded(value)
|
||||
|
||||
|
||||
if not self.__config.has_key(key):
|
||||
self.__config[key] = value
|
||||
log.debug("Setting '%s' to %s of %s", key, value, type(value))
|
||||
@ -200,7 +205,10 @@ what is currently in the config and it could not convert the value
|
||||
|
||||
if value is not None and oldtype != type(None) and oldtype != newtype:
|
||||
try:
|
||||
value = oldtype(value)
|
||||
if oldtype == unicode:
|
||||
value = oldtype(value, "utf8")
|
||||
else:
|
||||
value = oldtype(value)
|
||||
except ValueError:
|
||||
log.warning("Type '%s' invalid for '%s'", newtype, key)
|
||||
raise
|
||||
@ -250,7 +258,10 @@ what is currently in the config and it could not convert the value
|
||||
5
|
||||
|
||||
"""
|
||||
return self.__config[key]
|
||||
if isinstance(self.__config[key], str):
|
||||
return self.__config[key].decode("utf8")
|
||||
else:
|
||||
return self.__config[key]
|
||||
|
||||
def register_change_callback(self, callback):
|
||||
"""
|
||||
@ -348,21 +359,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 +382,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 +407,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()
|
||||
return
|
||||
except Exception, e:
|
||||
log.warning("Unable to open config file: %s", filename)
|
||||
|
||||
|
||||
if self._save_timer and self._save_timer.active():
|
||||
self._save_timer.cancel()
|
||||
return True
|
||||
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):
|
||||
|
@ -196,9 +196,8 @@ class FilterManager(component.Component):
|
||||
value = status[field]
|
||||
items[field][value] = items[field].get(value, 0) + 1
|
||||
|
||||
items["tracker_host"]["All"] = len(torrent_ids)
|
||||
|
||||
if "tracker_host" in items:
|
||||
items["tracker_host"]["All"] = len(torrent_ids)
|
||||
items["tracker_host"]["Error"] = len(tracker_error_filter(torrent_ids, ("Error",)))
|
||||
|
||||
if "state" in tree_keys and not show_zero_hits:
|
||||
|
@ -152,7 +152,6 @@ class PreferencesManager(component.Component):
|
||||
def start(self):
|
||||
self.core = component.get("Core")
|
||||
self.session = component.get("Core").session
|
||||
self.settings = component.get("Core").settings
|
||||
|
||||
# Register set functions in the Config
|
||||
self.config.register_set_function("torrentfiles_location",
|
||||
@ -233,6 +232,11 @@ class PreferencesManager(component.Component):
|
||||
self.new_release_timer.stop()
|
||||
|
||||
# Config set functions
|
||||
def session_set_setting(self, key, value):
|
||||
settings = self.session.settings()
|
||||
setattr(settings, key, value)
|
||||
self.session.set_settings(settings)
|
||||
|
||||
def _on_config_value_change(self, key, value):
|
||||
component.get("EventManager").emit(ConfigValueChangedEvent(key, value))
|
||||
|
||||
@ -274,8 +278,7 @@ class PreferencesManager(component.Component):
|
||||
def _on_set_outgoing_ports(self, key, value):
|
||||
if not self.config["random_outgoing_ports"]:
|
||||
log.debug("outgoing port range set to %s-%s", value[0], value[1])
|
||||
self.settings.outgoing_ports = value[0], value[1]
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("outgoing_ports", (value[0], value[1]))
|
||||
|
||||
def _on_set_random_outgoing_ports(self, key, value):
|
||||
if value:
|
||||
@ -284,13 +287,11 @@ class PreferencesManager(component.Component):
|
||||
def _on_set_peer_tos(self, key, value):
|
||||
log.debug("setting peer_tos to: %s", value)
|
||||
try:
|
||||
self.settings.peer_tos = chr(int(value, 16))
|
||||
self.session_set_setting("peer_tos", chr(int(value, 16)))
|
||||
except ValueError, e:
|
||||
log.debug("Invalid tos byte: %s", e)
|
||||
return
|
||||
|
||||
self.session.set_settings(self.settings)
|
||||
|
||||
def _on_set_dht(self, key, value):
|
||||
log.debug("dht value set to %s", value)
|
||||
state_file = deluge.configmanager.get_config_dir("dht.state")
|
||||
@ -387,51 +388,39 @@ class PreferencesManager(component.Component):
|
||||
self.session.set_max_half_open_connections(value)
|
||||
|
||||
def _on_set_max_connections_per_second(self, key, value):
|
||||
self.settings.connection_speed = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("connection_speed", value)
|
||||
|
||||
def _on_ignore_limits_on_local_network(self, key, value):
|
||||
self.settings.ignore_limits_on_local_network = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("ignore_limits_on_local_network", value)
|
||||
|
||||
def _on_set_share_ratio_limit(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
self.settings.share_ratio_limit = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("share_ratio_limit", value)
|
||||
|
||||
def _on_set_seed_time_ratio_limit(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
self.settings.seed_time_ratio_limit = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("seed_time_ratio_limit", value)
|
||||
|
||||
def _on_set_seed_time_limit(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
# This value is stored in minutes in deluge, but libtorrent wants seconds
|
||||
self.settings.seed_time_limit = int(value * 60)
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("seed_time_limit", int(value * 60))
|
||||
|
||||
def _on_set_max_active_downloading(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
log.debug("active_downloads: %s", self.settings.active_downloads)
|
||||
self.settings.active_downloads = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("active_downloads", value)
|
||||
|
||||
def _on_set_max_active_seeding(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
log.debug("active_seeds: %s", self.settings.active_seeds)
|
||||
self.settings.active_seeds = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("active_seeds", value)
|
||||
|
||||
def _on_set_max_active_limit(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
log.debug("active_limit: %s", self.settings.active_limit)
|
||||
self.settings.active_limit = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("active_limit", value)
|
||||
|
||||
def _on_set_dont_count_slow_torrents(self, key, value):
|
||||
log.debug("%s set to %s..", key, value)
|
||||
self.settings.dont_count_slow_torrents = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("dont_count_slow_torrents", value)
|
||||
|
||||
def _on_send_info(self, key, value):
|
||||
log.debug("Sending anonymous stats..")
|
||||
@ -491,8 +480,7 @@ class PreferencesManager(component.Component):
|
||||
|
||||
def _on_rate_limit_ip_overhead(self, key, value):
|
||||
log.debug("%s: %s", key, value)
|
||||
self.settings.rate_limit_ip_overhead = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("rate_limit_ip_overhead", value)
|
||||
|
||||
def _on_geoip_db_location(self, key, value):
|
||||
log.debug("%s: %s", key, value)
|
||||
@ -514,10 +502,8 @@ class PreferencesManager(component.Component):
|
||||
|
||||
def _on_cache_size(self, key, value):
|
||||
log.debug("%s: %s", key, value)
|
||||
self.settings.cache_size = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("cache_size", value)
|
||||
|
||||
def _on_cache_expiry(self, key, value):
|
||||
log.debug("%s: %s", key, value)
|
||||
self.settings.cache_expiry = value
|
||||
self.session.set_settings(self.settings)
|
||||
self.session_set_setting("cache_expiry", value)
|
||||
|
@ -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.
|
||||
|
@ -179,6 +179,11 @@ class Torrent(object):
|
||||
else:
|
||||
self.time_added = time.time()
|
||||
|
||||
# Keep track if we're forcing a recheck of the torrent so that we can
|
||||
# repause it after its done if necessary
|
||||
self.forcing_recheck = False
|
||||
self.forcing_recheck_paused = False
|
||||
|
||||
log.debug("Torrent object created.")
|
||||
|
||||
## Options methods ##
|
||||
@ -393,12 +398,11 @@ class Torrent(object):
|
||||
else:
|
||||
status = self.status
|
||||
|
||||
if self.is_finished and (self.options["stop_at_ratio"] or self.config["stop_seed_at_ratio"]):
|
||||
if self.is_finished and self.options["stop_at_ratio"]:
|
||||
# We're a seed, so calculate the time to the 'stop_share_ratio'
|
||||
if not status.upload_payload_rate:
|
||||
return 0
|
||||
stop_ratio = self.config["stop_seed_ratio"] if self.config["stop_seed_at_ratio"] else self.options["stop_ratio"]
|
||||
|
||||
stop_ratio = self.options["stop_ratio"]
|
||||
return ((status.all_time_download * stop_ratio) - status.all_time_upload) / status.upload_payload_rate
|
||||
|
||||
left = status.total_wanted - status.total_done
|
||||
@ -761,13 +765,8 @@ class Torrent(object):
|
||||
|
||||
if self.handle.is_finished():
|
||||
# If the torrent has already reached it's 'stop_seed_ratio' then do not do anything
|
||||
if self.config["stop_seed_at_ratio"] or self.options["stop_at_ratio"]:
|
||||
if self.options["stop_at_ratio"]:
|
||||
ratio = self.options["stop_ratio"]
|
||||
else:
|
||||
ratio = self.config["stop_seed_ratio"]
|
||||
|
||||
if self.get_ratio() >= ratio:
|
||||
if self.options["stop_at_ratio"]:
|
||||
if self.get_ratio() >= self.options["stop_ratio"]:
|
||||
#XXX: This should just be returned in the RPC Response, no event
|
||||
#self.signals.emit_event("torrent_resume_at_stop_ratio")
|
||||
return
|
||||
@ -794,6 +793,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:
|
||||
@ -857,12 +864,15 @@ class Torrent(object):
|
||||
|
||||
def force_recheck(self):
|
||||
"""Forces a recheck of the torrents pieces"""
|
||||
paused = self.handle.is_paused()
|
||||
try:
|
||||
self.handle.force_recheck()
|
||||
self.handle.resume()
|
||||
except Exception, e:
|
||||
log.debug("Unable to force recheck: %s", e)
|
||||
return False
|
||||
self.forcing_recheck = True
|
||||
self.forcing_recheck_paused = paused
|
||||
return True
|
||||
|
||||
def rename_files(self, filenames):
|
||||
|
@ -47,16 +47,14 @@ from twisted.internet.task import LoopingCall
|
||||
|
||||
from deluge._libtorrent import lt
|
||||
|
||||
|
||||
from deluge.event import *
|
||||
from deluge.error import *
|
||||
import deluge.common
|
||||
import deluge.component as component
|
||||
from deluge.configmanager import ConfigManager, get_config_dir
|
||||
from deluge.core.torrent import Torrent
|
||||
from deluge.core.torrent import TorrentOptions
|
||||
import deluge.core.oldstateupgrader
|
||||
from deluge.ui.common import utf8_encoded
|
||||
from deluge.common import utf8_encoded
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
||||
@ -192,6 +190,8 @@ class TorrentManager(component.Component):
|
||||
self.on_alert_metadata_received)
|
||||
self.alerts.register_handler("file_error_alert",
|
||||
self.on_alert_file_error)
|
||||
self.alerts.register_handler("file_completed_alert",
|
||||
self.on_alert_file_completed)
|
||||
|
||||
def start(self):
|
||||
# Get the pluginmanager reference
|
||||
@ -256,16 +256,13 @@ class TorrentManager(component.Component):
|
||||
|
||||
def update(self):
|
||||
for torrent_id, torrent in self.torrents.items():
|
||||
if self.config["stop_seed_at_ratio"] or torrent.options["stop_at_ratio"] and torrent.state not in ("Checking", "Allocating", "Paused", "Queued"):
|
||||
if torrent.options["stop_at_ratio"] and torrent.state not in ("Checking", "Allocating", "Paused", "Queued"):
|
||||
# If the global setting is set, but the per-torrent isn't.. Just skip to the next torrent
|
||||
# This is so that a user can turn-off the stop at ratio option on a per-torrent basis
|
||||
if self.config["stop_seed_at_ratio"] and not torrent.options["stop_at_ratio"]:
|
||||
if not torrent.options["stop_at_ratio"]:
|
||||
continue
|
||||
stop_ratio = self.config["stop_seed_ratio"]
|
||||
if torrent.options["stop_at_ratio"]:
|
||||
stop_ratio = torrent.options["stop_ratio"]
|
||||
if torrent.get_ratio() >= stop_ratio and torrent.is_finished:
|
||||
if self.config["remove_seed_at_ratio"] or torrent.options["remove_at_ratio"]:
|
||||
if torrent.get_ratio() >= torrent.options["stop_ratio"] and torrent.is_finished:
|
||||
if torrent.options["remove_at_ratio"]:
|
||||
self.remove(torrent_id)
|
||||
break
|
||||
if not torrent.handle.is_paused():
|
||||
@ -476,6 +473,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 +513,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 +562,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):
|
||||
@ -849,7 +849,14 @@ class TorrentManager(component.Component):
|
||||
torrent = self.torrents[str(alert.handle.info_hash())]
|
||||
except:
|
||||
return
|
||||
|
||||
|
||||
# Check to see if we're forcing a recheck and set it back to paused
|
||||
# if necessary
|
||||
if torrent.forcing_recheck:
|
||||
torrent.forcing_recheck = False
|
||||
if torrent.forcing_recheck_paused:
|
||||
torrent.handle.pause()
|
||||
|
||||
# Set the torrent state
|
||||
torrent.update_state()
|
||||
|
||||
@ -1012,3 +1019,9 @@ class TorrentManager(component.Component):
|
||||
except:
|
||||
return
|
||||
torrent.update_state()
|
||||
|
||||
def on_alert_file_completed(self, alert):
|
||||
log.debug("file_completed_alert: %s", alert.message())
|
||||
torrent_id = str(alert.handle.info_hash())
|
||||
component.get("EventManager").emit(
|
||||
TorrentFileCompletedEvent(torrent_id, alert.index))
|
||||
|
@ -164,6 +164,22 @@ class TorrentResumedEvent(DelugeEvent):
|
||||
"""
|
||||
self._args = [torrent_id]
|
||||
|
||||
class TorrentFileCompletedEvent(DelugeEvent):
|
||||
"""
|
||||
Emitted when a file completes.
|
||||
|
||||
This will only work with libtorrent 0.15 or greater.
|
||||
|
||||
"""
|
||||
def __init__(self, torrent_id, index):
|
||||
"""
|
||||
:param torrent_id: the torrent_id
|
||||
:type torrent_id: string
|
||||
:param index: the file index
|
||||
:type index: int
|
||||
"""
|
||||
self._args = [torrent_id, index]
|
||||
|
||||
class NewVersionAvailableEvent(DelugeEvent):
|
||||
"""
|
||||
Emitted when a more recent version of Deluge is available.
|
||||
|
@ -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
|
||||
@ -81,6 +82,7 @@ class Core(CorePluginBase):
|
||||
self.is_importing = False
|
||||
self.has_imported = False
|
||||
self.up_to_date = False
|
||||
self.need_to_resume_session = False
|
||||
self.num_blocked = 0
|
||||
self.file_progress = 0.0
|
||||
|
||||
@ -94,7 +96,7 @@ class Core(CorePluginBase):
|
||||
|
||||
update_now = False
|
||||
if self.config["load_on_start"]:
|
||||
self.pause_transfers()
|
||||
self.pause_session()
|
||||
if self.config["last_update"]:
|
||||
last_update = datetime.fromtimestamp(self.config["last_update"])
|
||||
check_period = timedelta(days=self.config["check_after_days"])
|
||||
@ -103,7 +105,8 @@ class Core(CorePluginBase):
|
||||
else:
|
||||
d = self.import_list(deluge.configmanager.get_config_dir("blocklist.cache"))
|
||||
d.addCallbacks(self.on_import_complete, self.on_import_error)
|
||||
d.addBoth(self.resume_transfers)
|
||||
if self.need_to_resume_session:
|
||||
d.addBoth(self.resume_session)
|
||||
|
||||
# This function is called every 'check_after_days' days, to download
|
||||
# and import a new list if needed.
|
||||
@ -149,7 +152,8 @@ class Core(CorePluginBase):
|
||||
else:
|
||||
d = self.import_list(self.config["url"])
|
||||
d.addCallbacks(self.on_import_complete, self.on_import_error)
|
||||
d.addBoth(self.resume_transfers)
|
||||
if self.need_to_resume_session:
|
||||
d.addBoth(self.resume_session)
|
||||
|
||||
return d
|
||||
|
||||
@ -281,7 +285,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
|
||||
@ -417,13 +421,14 @@ class Core(CorePluginBase):
|
||||
else:
|
||||
self.reader = create_reader(self.config["list_type"], self.config["list_compression"])
|
||||
|
||||
def pause_transfers(self):
|
||||
self.session_was_paused = self.core.session.is_paused()
|
||||
if not self.session_was_paused:
|
||||
def pause_session(self):
|
||||
if not self.core.session.is_paused():
|
||||
self.core.session.pause()
|
||||
self.need_to_resume_session = True
|
||||
else:
|
||||
self.need_to_resume_session = False
|
||||
|
||||
def resume_transfers(self, result):
|
||||
if not self.session_was_paused:
|
||||
self.session_was_paused = True
|
||||
self.core.session.resume()
|
||||
def resume_session(self, result):
|
||||
self.core.session.resume()
|
||||
self.need_to_resume_session = False
|
||||
return result
|
||||
|
@ -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]:
|
||||
|
@ -42,7 +42,6 @@ import os
|
||||
import sys
|
||||
import urlparse
|
||||
|
||||
import chardet
|
||||
import locale
|
||||
|
||||
try:
|
||||
@ -50,45 +49,11 @@ try:
|
||||
except ImportError:
|
||||
from sha import sha
|
||||
|
||||
from deluge import bencode, common
|
||||
from deluge import bencode
|
||||
from deluge.common import decode_string, path_join
|
||||
from deluge.log import LOG as log
|
||||
import deluge.configmanager
|
||||
|
||||
def decode_string(s, encoding="utf8"):
|
||||
"""
|
||||
Decodes a string and re-encodes it in utf8. If it cannot decode using
|
||||
`:param:encoding` then it will try to detect the string encoding and
|
||||
decode it.
|
||||
|
||||
:param s: string to decode
|
||||
:type s: string
|
||||
:keyword encoding: the encoding to use in the decoding
|
||||
:type encoding: string
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
s = s.decode(encoding).encode("utf8", "ignore")
|
||||
except UnicodeDecodeError:
|
||||
s = s.decode(chardet.detect(s)["encoding"], "ignore").encode("utf8", "ignore")
|
||||
return s
|
||||
|
||||
def utf8_encoded(s):
|
||||
"""
|
||||
Returns a utf8 encoded string of s
|
||||
|
||||
:param s: (unicode) string to (re-)encode
|
||||
:type s: basestring
|
||||
:returns: a utf8 encoded string of s
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
if isinstance(s, str):
|
||||
s = decode_string(s, locale.getpreferredencoding())
|
||||
elif isinstance(s, unicode):
|
||||
s = s.encode("utf8", "ignore")
|
||||
return s
|
||||
|
||||
class TorrentInfo(object):
|
||||
"""
|
||||
Collects information about a torrent file.
|
||||
@ -336,7 +301,7 @@ class FileTree2(object):
|
||||
"""
|
||||
def walk(directory, parent_path):
|
||||
for path in directory["contents"].keys():
|
||||
full_path = common.path_join(parent_path, path)
|
||||
full_path = path_join(parent_path, path)
|
||||
if directory["contents"][path]["type"] == "dir":
|
||||
directory["contents"][path] = callback(full_path, directory["contents"][path]) or \
|
||||
directory["contents"][path]
|
||||
|
@ -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,
|
||||
|
@ -122,7 +122,10 @@ class FilterTreeView(component.Component):
|
||||
self.label_view.set_show_expanders(True)
|
||||
self.label_view.set_headers_visible(False)
|
||||
self.label_view.set_level_indentation(-35)
|
||||
|
||||
# Force the theme to use an expander-size of 15 so that we don't cut out
|
||||
# entries due to our indentation hack.
|
||||
gtk.rc_parse_string('style "treeview-style" { GtkTreeView::expander-size = 15 } class "GtkTreeView" style "treeview-style"')
|
||||
|
||||
self.label_view.set_model(self.treestore)
|
||||
self.label_view.get_selection().connect("changed", self.on_selection_changed)
|
||||
self.create_model_filter()
|
||||
@ -210,7 +213,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>
|
||||
|
@ -47,6 +47,7 @@ import sys
|
||||
|
||||
# Initialize gettext
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
if hasattr(locale, "bindtextdomain"):
|
||||
locale.bindtextdomain("deluge", pkg_resources.resource_filename("deluge", "i18n"))
|
||||
if hasattr(locale, "textdomain"):
|
||||
@ -252,12 +253,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):
|
||||
|
@ -152,7 +152,15 @@ class MainWindow(component.Component):
|
||||
"""Returns a reference to the main window glade object."""
|
||||
return self.main_glade
|
||||
|
||||
def quit(self):
|
||||
def quit(self, shutdown=False):
|
||||
"""
|
||||
Quits the GtkUI
|
||||
|
||||
:param shutdown: whether or not to shutdown the daemon as well
|
||||
:type shutdown: boolean
|
||||
"""
|
||||
if shutdown:
|
||||
client.daemon.shutdown()
|
||||
reactor.stop()
|
||||
|
||||
def load_window_state(self):
|
||||
|
@ -253,15 +253,10 @@ class MenuBar(component.Component):
|
||||
|
||||
def on_menuitem_quitdaemon_activate(self, data=None):
|
||||
log.debug("on_menuitem_quitdaemon_activate")
|
||||
# Tell the core to shutdown
|
||||
def on_shutdown(result):
|
||||
self.window.quit()
|
||||
client.daemon.shutdown().addCallback(on_shutdown)
|
||||
self.window.quit(shutdown=True)
|
||||
|
||||
def on_menuitem_quit_activate(self, data=None):
|
||||
log.debug("on_menuitem_quit_activate")
|
||||
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'"""
|
||||
|
@ -323,9 +323,6 @@ class SystemTray(component.Component):
|
||||
if self.config["lock_tray"] and not self.window.visible():
|
||||
self.unlock_tray()
|
||||
|
||||
if self.config["classic_mode"]:
|
||||
client.daemon.shutdown()
|
||||
|
||||
self.window.quit()
|
||||
|
||||
def on_menuitem_quitdaemon_activate(self, menuitem):
|
||||
@ -333,8 +330,7 @@ class SystemTray(component.Component):
|
||||
if self.config["lock_tray"] and not self.window.visible():
|
||||
self.unlock_tray()
|
||||
|
||||
client.daemon.shutdown()
|
||||
self.window.quit()
|
||||
self.window.quit(shutdown=True)
|
||||
|
||||
def tray_setbwdown(self, widget, data=None):
|
||||
self.setbwlimit(widget, _("Set Maximum Download Speed"), "max_download_speed",
|
||||
|
@ -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/images/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';
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -174,7 +174,7 @@ Deluge.MultiOptionsManager = Ext.extend(Deluge.OptionsManager, {
|
||||
this.stored[this.currentId][option] = value;
|
||||
|
||||
if (!this.isDirty(option)) {
|
||||
this.fireEvent('changed', this.currentId, option, value, oldValue);
|
||||
this.fireEvent('changed', option, value, oldValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -232,9 +232,9 @@ Deluge.preferences.Plugins = Ext.extend(Ext.Panel, {
|
||||
},
|
||||
|
||||
onPluginEnabled: function(pluginName) {
|
||||
var index = this.grid.getStore().find('plugin', pluginName);
|
||||
var index = this.list.getStore().find('plugin', pluginName);
|
||||
if (index == -1) return;
|
||||
var plugin = this.grid.getStore().getAt(index);
|
||||
var plugin = this.list.getStore().getAt(index);
|
||||
plugin.set('enabled', true);
|
||||
plugin.commit();
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user