Compare commits
78 Commits
develop
...
deluge-1.3
Author | SHA1 | Date | |
---|---|---|---|
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
|
45
ChangeLog
45
ChangeLog
@ -1,4 +1,27 @@
|
||||
=== Deluge 1.3.0 (In Development) ===
|
||||
=== 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 +30,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 ====
|
||||
|
@ -45,9 +45,9 @@ The format of the config file is two json encoded dicts:
|
||||
<version dict>
|
||||
<content dict>
|
||||
|
||||
The version dict contains two keys: file and format. The format version is
|
||||
controlled by the Config class. It should only be changed when anything below
|
||||
it is changed directly by the Config class. An example of this would be if we
|
||||
The version dict contains two keys: file and format. The format version is
|
||||
controlled by the Config class. It should only be changed when anything below
|
||||
it is changed directly by the Config class. An example of this would be if we
|
||||
changed the serializer for the content to something different.
|
||||
|
||||
The config file version is changed by the 'owner' of the config file. This is
|
||||
@ -93,13 +93,13 @@ def prop(func):
|
||||
def find_json_objects(s):
|
||||
"""
|
||||
Find json objects in a string.
|
||||
|
||||
|
||||
:param s: the string to find json objects in
|
||||
:type s: string
|
||||
|
||||
|
||||
:returns: a list of tuples containing start and end locations of json objects in the string `s`
|
||||
:rtype: [(start, end), ...]
|
||||
|
||||
|
||||
"""
|
||||
objects = []
|
||||
opens = 0
|
||||
@ -119,8 +119,8 @@ def find_json_objects(s):
|
||||
start = index + offset + 1
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""
|
||||
This class is used to access/create/modify config files
|
||||
@ -348,21 +348,21 @@ what is currently in the config and it could not convert the value
|
||||
return
|
||||
|
||||
objects = find_json_objects(data)
|
||||
|
||||
|
||||
if not len(objects):
|
||||
# No json objects found, try depickling it
|
||||
try:
|
||||
self.__config.update(pickle.loads(data))
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
elif len(objects) == 1:
|
||||
start, end = objects[0]
|
||||
try:
|
||||
self.__config.update(json.loads(data[start:end]))
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
elif len(objects) == 2:
|
||||
try:
|
||||
start, end = objects[0]
|
||||
@ -371,8 +371,8 @@ what is currently in the config and it could not convert the value
|
||||
self.__config.update(json.loads(data[start:end]))
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
|
||||
log.warning("Unable to load config file: %s", filename)
|
||||
|
||||
log.debug("Config %s version: %s.%s loaded: %s", filename,
|
||||
self.__version["format"], self.__version["file"], self.__config)
|
||||
|
||||
@ -396,26 +396,24 @@ what is currently in the config and it could not convert the value
|
||||
version = json.loads(data[start:end])
|
||||
start, end = objects[1]
|
||||
loaded_data = json.loads(data[start:end])
|
||||
|
||||
if self.__config == loaded_data and self.__version == version:
|
||||
# The config has not changed so lets just return
|
||||
self._save_timer.cancel()
|
||||
if self._save_timer:
|
||||
self._save_timer.cancel()
|
||||
return
|
||||
except Exception, e:
|
||||
log.warning("Unable to open config file: %s", filename)
|
||||
|
||||
|
||||
except IOError, e:
|
||||
log.warning("Unable to open config file: %s because: %s", filename, e)
|
||||
|
||||
# Save the new config and make sure it's written to disk
|
||||
try:
|
||||
log.debug("Saving new config file %s", filename + ".new")
|
||||
f = open(filename + ".new", "wb")
|
||||
json.dump(self.__version, f, indent=2)
|
||||
json.dump(self.__version, f, indent=2)
|
||||
json.dump(self.__config, f, indent=2)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
f.close()
|
||||
except Exception, e:
|
||||
except IOError, e:
|
||||
log.error("Error writing new config file: %s", e)
|
||||
return False
|
||||
|
||||
|
@ -768,7 +768,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):
|
||||
|
@ -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:
|
||||
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.
|
||||
|
@ -794,6 +794,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:
|
||||
|
@ -476,6 +476,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 +516,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 +565,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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
# core.py
|
||||
#
|
||||
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 John Garland <johnnybg@gmail.com>
|
||||
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
@ -38,6 +38,7 @@ import os
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from wsgiref.handlers import format_date_time
|
||||
from urlparse import urljoin
|
||||
import shutil
|
||||
|
||||
from twisted.internet.task import LoopingCall
|
||||
@ -281,7 +282,7 @@ class Core(CorePluginBase):
|
||||
d = f
|
||||
if f.check(error.PageRedirect):
|
||||
# Handle redirect errors
|
||||
location = error_msg.split(" to ")[1]
|
||||
location = urljoin(self.config["url"], error_msg.split(" to ")[1])
|
||||
if "Moved Permanently" in error_msg:
|
||||
log.debug("Setting blocklist url to %s", location)
|
||||
self.config["url"] = location
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# decompressers.py
|
||||
#
|
||||
# Copyright (C) 2009 John Garland <johnnybg@gmail.com>
|
||||
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# detect.py
|
||||
#
|
||||
# Copyright (C) 2009 John Garland <johnnybg@gmail.com>
|
||||
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# readers.py
|
||||
#
|
||||
# Copyright (C) 2009 John Garland <johnnybg@gmail.com>
|
||||
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
|
@ -35,7 +35,7 @@ from setuptools import setup
|
||||
|
||||
__plugin_name__ = "Blocklist"
|
||||
__author__ = "John Garland"
|
||||
__author_email__ = "johnnybg@gmail.com"
|
||||
__author_email__ = "johnnybg+deluge@gmail.com"
|
||||
__version__ = "1.2"
|
||||
__url__ = "http://deluge-torrent.org"
|
||||
__license__ = "GPLv3"
|
||||
|
@ -1,54 +0,0 @@
|
||||
#
|
||||
# __init__.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
|
||||
from deluge.plugins.init import PluginInitBase
|
||||
|
||||
class CorePlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from core import Core as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(CorePlugin, self).__init__(plugin_name)
|
||||
|
||||
class GtkUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from gtkui import GtkUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||
|
||||
class WebUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from webui import WebUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(WebUIPlugin, self).__init__(plugin_name)
|
@ -1,40 +0,0 @@
|
||||
#
|
||||
# common.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
|
||||
import pkg_resources
|
||||
import os.path
|
||||
|
||||
def get_resource(filename):
|
||||
return pkg_resources.resource_filename("example", os.path.join("data", filename))
|
@ -1,55 +0,0 @@
|
||||
#
|
||||
# core.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.plugins.pluginbase import CorePluginBase
|
||||
import deluge.component as component
|
||||
import deluge.configmanager
|
||||
from deluge.core.rpcserver import export
|
||||
|
||||
class Core(CorePluginBase):
|
||||
def enable(self):
|
||||
log.debug("Example core plugin enabled!")
|
||||
|
||||
def disable(self):
|
||||
log.debug("Example core plugin disabled!")
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
### Exported RPC methods ###
|
||||
@export()
|
||||
def example_method(self):
|
||||
pass
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
Script: example.js
|
||||
The client-side javascript code for the Example plugin.
|
||||
|
||||
Copyright:
|
||||
(C) Damien Churchill 2009 <damoxc@gmail.com>
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, write to:
|
||||
The Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor
|
||||
Boston, MA 02110-1301, USA.
|
||||
|
||||
In addition, as a special exception, the copyright holders give
|
||||
permission to link the code of portions of this program with the OpenSSL
|
||||
library.
|
||||
You must obey the GNU General Public License in all respects for all of
|
||||
the code used other than OpenSSL. If you modify file(s) with this
|
||||
exception, you may extend this exception to your version of the file(s),
|
||||
but you are not obligated to do so. If you do not wish to do so, delete
|
||||
this exception statement from your version. If you delete this exception
|
||||
statement from all source files in the program, then also delete it here.
|
||||
*/
|
||||
|
||||
ExamplePlugin = Ext.extend(Deluge.Plugin, {
|
||||
constructor: function(config) {
|
||||
config = Ext.apply({
|
||||
name: "Example"
|
||||
}, config);
|
||||
ExamplePlugin.superclass.constructor.call(this, config);
|
||||
},
|
||||
|
||||
onDisable: function() {
|
||||
Deluge.Preferences.removePage(this.prefsPage);
|
||||
},
|
||||
|
||||
onEnable: function() {
|
||||
this.prefsPage = new ExamplePreferencesPanel();
|
||||
this.prefsPage = Deluge.Preferences.addPage(this.prefsPage);
|
||||
}
|
||||
});
|
||||
new ExamplePlugin();
|
@ -1,48 +0,0 @@
|
||||
#
|
||||
# gtkui.py
|
||||
#
|
||||
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
|
||||
import gtk
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge.plugins.pluginbase import GtkPluginBase
|
||||
import deluge.component as component
|
||||
import deluge.common
|
||||
|
||||
class GtkUI(GtkPluginBase):
|
||||
def enable(self):
|
||||
pass
|
||||
def disable(self):
|
||||
pass
|
@ -1,54 +0,0 @@
|
||||
#
|
||||
# webui.py
|
||||
#
|
||||
# Copyright (C) 2009 Martijn Voncken <mvoncken@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge import component
|
||||
from deluge.plugins.pluginbase import WebPluginBase
|
||||
|
||||
from common import get_resource
|
||||
|
||||
class WebUI(WebPluginBase):
|
||||
|
||||
scripts = [get_resource("example.js")]
|
||||
|
||||
# The enable and disable methods are not scrictly required on the WebUI
|
||||
# plugins. They are only here if you need to register images/stylesheets
|
||||
# with the webserver.
|
||||
def enable(self):
|
||||
log.debug("Example Web plugin enabled!")
|
||||
|
||||
def disable(self):
|
||||
log.debug("Example Web plugin disabled!")
|
@ -1,67 +0,0 @@
|
||||
#
|
||||
# setup.py
|
||||
#
|
||||
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
__plugin_name__ = "Example"
|
||||
__author__ = "Andrew Resch"
|
||||
__author_email__ = "andrewresch@gmail.com"
|
||||
__version__ = "1.2"
|
||||
__url__ = "http://deluge-torrent.org"
|
||||
__license__ = "GPLv3"
|
||||
__description__ = "Example plugin"
|
||||
__long_description__ = __description__
|
||||
__pkg_data__ = {__plugin_name__.lower(): []}
|
||||
|
||||
setup(
|
||||
name=__plugin_name__,
|
||||
version=__version__,
|
||||
description=__description__,
|
||||
author=__author__,
|
||||
author_email=__author_email__,
|
||||
url=__url__,
|
||||
license=__license__,
|
||||
long_description=__long_description__,
|
||||
|
||||
packages=[__plugin_name__.lower()],
|
||||
package_data = __pkg_data__,
|
||||
|
||||
entry_points="""
|
||||
[deluge.plugin.core]
|
||||
%s = %s:CorePlugin
|
||||
[deluge.plugin.gtkui]
|
||||
%s = %s:GtkUIPlugin
|
||||
[deluge.plugin.webui]
|
||||
%s = %s:WebUIPlugin
|
||||
""" % ((__plugin_name__, __plugin_name__.lower())*3)
|
||||
)
|
@ -84,8 +84,11 @@ class Core(CorePluginBase):
|
||||
if event in self.registered_events:
|
||||
continue
|
||||
|
||||
def event_handler(torrent_id):
|
||||
self.execute_commands(torrent_id, command[EXECUTE_EVENT])
|
||||
def create_event_handler(event):
|
||||
def event_handler(torrent_id):
|
||||
self.execute_commands(torrent_id, event)
|
||||
return event_handler
|
||||
event_handler = create_event_handler(event)
|
||||
event_manager.register_event_handler(EVENT_MAP[event], event_handler)
|
||||
self.registered_events[event] = event_handler
|
||||
|
||||
@ -93,20 +96,23 @@ class Core(CorePluginBase):
|
||||
|
||||
def execute_commands(self, torrent_id, event):
|
||||
torrent = component.get("TorrentManager").torrents[torrent_id]
|
||||
info = torrent.get_status(["name", "save_path", "move_completed", "move_on_completed_path"])
|
||||
info = torrent.get_status(["name", "save_path", "move_on_completed", "move_on_completed_path"])
|
||||
|
||||
# Grab the torrent name and save path
|
||||
torrent_name = info["name"]
|
||||
if event == "completed":
|
||||
save_path = info["move_on_completed_path"] if info ["move_completed"] else info["save_path"]
|
||||
if event == "complete":
|
||||
save_path = info["move_on_completed_path"] if info ["move_on_completed"] else info["save_path"]
|
||||
else:
|
||||
save_path = info["save_path"]
|
||||
|
||||
log.debug("[execute] Running commands for %s", event)
|
||||
|
||||
# Go through and execute all the commands
|
||||
for command in self.config["commands"]:
|
||||
if command[EXECUTE_EVENT] == event:
|
||||
command = os.path.expandvars(command[EXECUTE_COMMAND])
|
||||
command = os.path.expanduser(command)
|
||||
log.debug("[execute] running %s", command)
|
||||
p = Popen([command, torrent_id, torrent_name, save_path], stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
if p.wait() != 0:
|
||||
log.warn("Execute command failed with exit code %d", p.returncode)
|
||||
@ -123,6 +129,7 @@ class Core(CorePluginBase):
|
||||
def add_command(self, event, command):
|
||||
command_id = hashlib.sha1(str(time.time())).hexdigest()
|
||||
self.config["commands"].append((command_id, event, command))
|
||||
self.config.save()
|
||||
component.get("EventManager").emit(ExecuteCommandAddedEvent(command_id, event, command))
|
||||
|
||||
@export
|
||||
@ -136,6 +143,7 @@ class Core(CorePluginBase):
|
||||
self.config["commands"].remove(command)
|
||||
component.get("EventManager").emit(ExecuteCommandRemovedEvent(command_id))
|
||||
break
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def save_command(self, command_id, event, cmd):
|
||||
@ -143,3 +151,4 @@ class Core(CorePluginBase):
|
||||
if command[EXECUTE_ID] == command_id:
|
||||
self.config["commands"][i] = (command_id, event, cmd)
|
||||
break
|
||||
self.config.save()
|
||||
|
@ -99,7 +99,7 @@ Deluge.ux.EditExecuteCommandWindow = Ext.extend(Deluge.ux.ExecuteWindowBase, {
|
||||
},
|
||||
|
||||
onSaveClick: function() {
|
||||
var values = this.form.getForm().getValues();
|
||||
var values = this.form.getForm().getFieldValues();
|
||||
deluge.client.execute.save_command(this.command.id, values.event, values.command, {
|
||||
success: function() {
|
||||
this.fireEvent('commandedit', this, values.event, values.command);
|
||||
@ -124,7 +124,7 @@ Deluge.ux.AddExecuteCommandWindow = Ext.extend(Deluge.ux.ExecuteWindowBase, {
|
||||
},
|
||||
|
||||
onAddClick: function() {
|
||||
var values = this.form.getForm().getValues();
|
||||
var values = this.form.getForm().getFieldValues();
|
||||
deluge.client.execute.add_command(values.event, values.command, {
|
||||
success: function() {
|
||||
this.fireEvent('commandadd', this, values.event, values.command);
|
||||
|
@ -1,55 +0,0 @@
|
||||
#
|
||||
# feeder/__init__.py
|
||||
#
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
|
||||
from deluge.plugins.init import PluginInitBase
|
||||
|
||||
class CorePlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from core import Core as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(CorePlugin, self).__init__(plugin_name)
|
||||
|
||||
class GtkUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from gtkui import GtkUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||
|
||||
class WebUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from webui import WebUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(WebUIPlugin, self).__init__(plugin_name)
|
@ -1,432 +0,0 @@
|
||||
#
|
||||
# core.py
|
||||
#
|
||||
# Copyright (C) 2008-2009 Fredrik Eriksson <feeder@winterbird.org>
|
||||
# Copyright (C) 2009 David Mohr <david@mcbf.net>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
|
||||
import feedparser # for parsing rss feeds
|
||||
import threading # for threaded updates
|
||||
import re # for regular expressions
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.plugins.pluginbase import CorePluginBase
|
||||
import deluge.component as component
|
||||
import deluge.configmanager
|
||||
from deluge.core.rpcserver import export
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
"feeds": {},
|
||||
"filters": {},
|
||||
"updatetime": 15,
|
||||
"history": []
|
||||
}
|
||||
|
||||
# Helper classes
|
||||
|
||||
class Feed:
|
||||
"""
|
||||
Class for the Feed object (containging feed configurations)
|
||||
"""
|
||||
def __init__(self):
|
||||
self.url = ""
|
||||
self.cookies = {}
|
||||
self.updatetime = 15
|
||||
|
||||
def get_config(self):
|
||||
try:
|
||||
tmp = self.cookies
|
||||
except Exception, e:
|
||||
log.debug("Old feed without cookies... updating")
|
||||
self.cookies = {}
|
||||
return {'url': self.url, 'updatetime': self.updatetime, 'cookies': self.cookies}
|
||||
|
||||
def set_config(self, config):
|
||||
self.url = config['url']
|
||||
self.updatetime = config['updatetime']
|
||||
self.cookies = config['cookies']
|
||||
|
||||
|
||||
class Filter:
|
||||
"""
|
||||
Class for the Filter object (containing filter configurations)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.regex = ""
|
||||
self.feeds = [] #TODO activate filter per feed
|
||||
self.all_feeds = True
|
||||
self.active = True
|
||||
|
||||
# by default, set the configuration to match
|
||||
# the per-torrent settings in deluge
|
||||
def_conf = component.get("Core").get_config()
|
||||
self.max_download_speed = def_conf['max_download_speed_per_torrent']
|
||||
self.max_upload_speed = def_conf['max_upload_speed_per_torrent']
|
||||
self.max_connections = def_conf['max_connections_per_torrent']
|
||||
self.max_upload_slots = def_conf['max_upload_slots_per_torrent']
|
||||
self.prioritize_first_last_pieces = def_conf['prioritize_first_last_pieces']
|
||||
self.auto_managed = def_conf['auto_managed']
|
||||
self.download_location = def_conf['download_location']
|
||||
|
||||
self.stop_at_ratio = def_conf['stop_seed_at_ratio']
|
||||
self.stop_ratio = def_conf['stop_seed_ratio']
|
||||
self.remove_at_ratio = def_conf['remove_seed_at_ratio']
|
||||
|
||||
def get_config(self):
|
||||
def_conf = component.get("Core").get_config()
|
||||
|
||||
try:
|
||||
tmp = self.active
|
||||
except Exception, e:
|
||||
log.debug("Old filter detected (pre 0.3), updating...")
|
||||
self.active = True
|
||||
|
||||
try:
|
||||
tmp = self.stop_at_ratio
|
||||
tmp = self.stop_ratio
|
||||
tmp = self.remove_at_ratio
|
||||
except:
|
||||
log.debug("Old filter detected (pre 0.4), updating...")
|
||||
self.stop_at_ratio = def_conf['stop_seed_at_ratio']
|
||||
self.stop_ratio = def_conf['stop_seed_ratio']
|
||||
self.remove_at_ratio = def_conf['remove_seed_at_ratio']
|
||||
|
||||
conf = {
|
||||
'regex': self.regex,
|
||||
'feeds': self.feeds,
|
||||
'all_feeds': self.all_feeds,
|
||||
'active' : self.active,
|
||||
'max_download_speed': self.max_download_speed,
|
||||
'max_upload_speed': self.max_upload_speed,
|
||||
'max_connections': self.max_connections,
|
||||
'max_upload_slots': self.max_upload_slots,
|
||||
'prioritize_first_last_pieces': self.prioritize_first_last_pieces,
|
||||
'auto_managed': self.auto_managed,
|
||||
'download_location':self.download_location,
|
||||
'remove_at_ratio':self.remove_at_ratio,
|
||||
'stop_ratio': self.stop_ratio,
|
||||
'stop_at_ratio': self.stop_at_ratio }
|
||||
|
||||
return conf
|
||||
|
||||
def set_config(self, conf):
|
||||
self.regex = conf['regex']
|
||||
self.feeds = conf['feeds']
|
||||
self.all_feeds = conf['all_feeds']
|
||||
self.active = conf['active']
|
||||
self.max_download_speed = int(conf['max_download_speed'])
|
||||
self.max_upload_speed = int(conf['max_upload_speed'])
|
||||
self.max_connections = int(conf['max_connections'])
|
||||
self.max_upload_slots = int(conf['max_upload_slots'])
|
||||
self.prioritize_first_last_pieces = conf['prioritize_first_last_pieces']
|
||||
self.auto_managed = conf['auto_managed']
|
||||
self.download_location = conf['download_location']
|
||||
self.remove_at_ratio = conf['remove_at_ratio']
|
||||
self.stop_ratio = float(conf['stop_ratio'])
|
||||
self.stop_at_ratio = conf['stop_at_ratio']
|
||||
|
||||
|
||||
class Core(CorePluginBase):
|
||||
def enable(self):
|
||||
self.config = deluge.configmanager.ConfigManager("feeder.conf", DEFAULT_PREFS)
|
||||
self.feeds = {}
|
||||
self.timers = {}
|
||||
self.history = self.config['history']
|
||||
self.time = 0
|
||||
|
||||
# Setting default timer to configured update time
|
||||
for feed in self.config['feeds']:
|
||||
self.timers[feed] = LoopingCall(self.update_feed, feed)
|
||||
self.timers[feed].start( self.config['feeds'][feed].updatetime * 60)
|
||||
|
||||
|
||||
def disable(self):
|
||||
self.config['history'] = self.history
|
||||
self.config.save()
|
||||
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
#=================Exported functions==================
|
||||
|
||||
@export
|
||||
def set_config(self, config):
|
||||
"""sets the config dictionary"""
|
||||
for key in config.keys():
|
||||
self.config[key] = config[key]
|
||||
self.config.save()
|
||||
|
||||
####################Configuration Getters##################
|
||||
|
||||
@export
|
||||
def get_config(self):
|
||||
"""returns the config dictionary"""
|
||||
return self.config.config
|
||||
|
||||
@export
|
||||
def get_feed_config(self, feedname):
|
||||
"""Returns configuration for a feed"""
|
||||
return self.config['feeds'][feedname].get_config()
|
||||
|
||||
@export
|
||||
def get_filter_config(self, filtername):
|
||||
"""Returns a configuration for a filter"""
|
||||
return self.config['filters'][filtername].get_config()
|
||||
|
||||
####################Information Getters####################
|
||||
|
||||
@export
|
||||
def get_feeds(self):
|
||||
"""Returns a list of the configured feeds"""
|
||||
feeds = []
|
||||
for feedname in self.config['feeds']:
|
||||
feeds.append(feedname)
|
||||
feeds.sort(key=string.lower)
|
||||
return feeds
|
||||
|
||||
@export
|
||||
def get_filters(self):
|
||||
"""Returns a list of all available filters"""
|
||||
filters = []
|
||||
for filter in self.config['filters']:
|
||||
filters.append(filter)
|
||||
filters.sort(key=string.lower)
|
||||
return filters
|
||||
|
||||
@export
|
||||
def get_items(self, feedname):
|
||||
"""Returns a dictionary with feedname:link"""
|
||||
try:
|
||||
items = {}
|
||||
feed = self.feeds[feedname]
|
||||
for entry in feed['entries']:
|
||||
items[entry.title] = entry.link
|
||||
except Exception, e:
|
||||
items = {}
|
||||
log.warning("Feed '%s' not loaded", feedname)
|
||||
return items
|
||||
|
||||
@export
|
||||
def test_filter(self, regex):
|
||||
filters = { "to_test":Filter() }
|
||||
conf = filters["to_test"].get_config()
|
||||
conf["regex"] = regex
|
||||
filters["to_test"].set_config(conf)
|
||||
hits = {}
|
||||
for feed in self.feeds:
|
||||
hits.update(self.run_filters(feed, filters, test=True))
|
||||
return hits
|
||||
|
||||
@export
|
||||
def add_feed(self, config):
|
||||
"""adds/updates a feed and, for whatever reason, sets the default timeout"""
|
||||
|
||||
# save the feedname and remove it from the config
|
||||
feedname = config['name']
|
||||
del config['name']
|
||||
|
||||
# check if the feed already exists and save config
|
||||
try:
|
||||
conf = self.config['feeds'][feedname].get_config()
|
||||
del self.config['feeds'][feedname]
|
||||
except Exception, e:
|
||||
conf = {}
|
||||
|
||||
# update configuration
|
||||
for var in config:
|
||||
conf[var] = config[var]
|
||||
|
||||
# save as default update time
|
||||
try:
|
||||
self.config['updatetime'] = config['updatetime']
|
||||
except Exception, e:
|
||||
log.warning("updatetime not set when adding feed %s", feedname)
|
||||
|
||||
# Create the new feed
|
||||
newfeed = Feed()
|
||||
newfeed.set_config(conf)
|
||||
|
||||
# Add a timer (with default timer for now, since we can't get ttl just yet)...
|
||||
self.timers[feedname] = LoopingCall(self.update_feed, feedname)
|
||||
|
||||
# Save the new feed
|
||||
self.config['feeds'].update({feedname: newfeed })
|
||||
self.config.save()
|
||||
|
||||
# Start the timeout, which will also update the new feed
|
||||
self.timers[feedname].start(newfeed.updatetime * 60)
|
||||
|
||||
@export
|
||||
def remove_feed(self, feedname):
|
||||
"""Remove a feed"""
|
||||
if self.feeds.has_key(feedname): # Check if we have the feed saved and remove it
|
||||
del self.feeds[feedname]
|
||||
if self.timers.has_key(feedname): # Check if we have a timer for this feed and remove it
|
||||
self.timers[feedname].stop()
|
||||
del self.timers[feedname]
|
||||
if self.config['feeds'].has_key(feedname): # Check if we have the feed in the configuration and remove it
|
||||
del self.config['feeds'][feedname]
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def add_filter(self, name):
|
||||
"""Adds a new filter to the configuration"""
|
||||
if not self.config['filters'].has_key(name): # we don't want to add a filter that already exists
|
||||
self.config['filters'][name] = Filter()
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def set_filter_config(self, filtername, conf):
|
||||
"""Changes the options for a filter"""
|
||||
oldconf = self.config['filters'][filtername].get_config()
|
||||
for item in conf:
|
||||
oldconf[item] = conf[item]
|
||||
|
||||
self.config['filters'][filtername].set_config(oldconf)
|
||||
self.config.save()
|
||||
for feed in self.config['feeds']: # we would like to check if the filter now matches something new
|
||||
self.run_filters(feed)
|
||||
|
||||
@export
|
||||
def remove_filter(self, name):
|
||||
"""Removes a filter"""
|
||||
if self.config['filters'].has_key(name): # Can't remove a filter that doesn't exists
|
||||
del self.config['filters'][name]
|
||||
self.config.save()
|
||||
|
||||
#=================Internal functions================
|
||||
|
||||
def update_feed(self, feedname):
|
||||
"""Start a thread to update a single feed"""
|
||||
threading.Thread(
|
||||
target=self.update_feed_thread,
|
||||
args=(self.on_feed_updated, feedname)).start()
|
||||
|
||||
# Need to return true to not destoy timer...
|
||||
return True
|
||||
|
||||
def update_feed_thread(self, callback, feedname):
|
||||
"""updates a feed"""
|
||||
feed = self.config['feeds'][feedname]
|
||||
try:
|
||||
self.feeds[feedname] = feedparser.parse(feed.url)
|
||||
except Exception, e:
|
||||
log.warning("Error parsing feed %s: %s", feedname, e)
|
||||
else:
|
||||
callback(feedname)
|
||||
|
||||
def on_feed_updated(self, feedname):
|
||||
"""Run stuff when a feed has been updated"""
|
||||
|
||||
# Not all feeds contain a ttl value, but if it does
|
||||
# we would like to obey it
|
||||
try:
|
||||
if not self.feeds[feedname].ttl == self.config['feeds'][feedname].updatetime:
|
||||
log.debug("feed '%s' request a ttl of %s, updating timer", feedname, self.feeds[feedname].ttl)
|
||||
self.config['feeds'][feedname].updatetime = self.feeds[feedname].ttl
|
||||
self.timers[feedname].stop()
|
||||
self.timers[feedname].start(self.config['feeds'][feedname].updatetime * 60)
|
||||
except Exception, e:
|
||||
log.debug("feed '%s' has no ttl set, will use default timer", feedname)
|
||||
|
||||
# Run filters on the feed
|
||||
self.run_filters(feedname)
|
||||
|
||||
def run_filters(self, feedname, filters={}, test=False):
|
||||
"""Test all available filters on the given feed"""
|
||||
if not filters:
|
||||
filters = self.config['filters']
|
||||
log.debug("will test filters %s", filters)
|
||||
hits = {}
|
||||
# Test every entry...
|
||||
for entry in self.feeds[feedname]['entries']:
|
||||
# ...and every filter
|
||||
for filter in filters:
|
||||
# We need to be able to run feeds saved before implementation of actiave/deactivate filter (pre 0.3) TODO
|
||||
try:
|
||||
if not filters[filter].active:
|
||||
continue
|
||||
except:
|
||||
log.debug("old filter, will assume filter is activated")
|
||||
|
||||
if filters[filter].regex == "": # we don't want a empty regex...
|
||||
log.warning("Filter '%s' has not been configured, ignoring!", filter)
|
||||
continue
|
||||
|
||||
# if the filter isn't supposed to be run on this feed we don't want to run it...
|
||||
# if filter.all_feeds or self.config['filters'][filter].feeds.has_element(feedname) : # ...apparently has_element doesn't work on arrays... TODO
|
||||
if self.test_filter(entry, filters[filter].regex):
|
||||
if test:
|
||||
hits[entry.title] = entry.link
|
||||
else:
|
||||
opts = filters[filter].get_config()
|
||||
#remove filter options that should not be passed on to the torrent.
|
||||
del opts['regex']
|
||||
del opts['feeds']
|
||||
del opts['all_feeds']
|
||||
|
||||
# history patch from Darrell Enns, slightly modified :)
|
||||
# check history to prevent multiple adds of the same torrent
|
||||
log.debug("testing %s", entry.link)
|
||||
if not entry.link in self.history:
|
||||
self.add_torrent(entry.link, opts, self.feeds[feedname].cookies)
|
||||
self.history.append(entry.link)
|
||||
|
||||
#limit history to 50 entries
|
||||
if len(self.history)>50:
|
||||
self.history=self.history[-50:]
|
||||
log.debug("wrapping history")
|
||||
else:
|
||||
log.debug("'%s' is in history, will not download", entry.link)
|
||||
return hits
|
||||
|
||||
|
||||
def test_filter(self, entry, filter):
|
||||
"""Tests a filter to a given rss entry"""
|
||||
f = re.compile(filter, re.IGNORECASE)
|
||||
if f.search(entry.title) or f.search(entry.link):
|
||||
log.debug("RSS item '%s' matches filter '%s'", entry.title, filter)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def add_torrent(self, url, torrent_options, headers):
|
||||
log.debug("Attempting to add torrent %s", url)
|
||||
component.get("Core").add_torrent_url(url, torrent_options, headers)
|
@ -1,13 +0,0 @@
|
||||
$def with (entries, feedname)
|
||||
$:render.header("things", '')
|
||||
<div class="panel" >
|
||||
<div>
|
||||
<h3>Feed items for feed $feedname</h3>
|
||||
<ul>
|
||||
$entries
|
||||
</ul>
|
||||
</div>
|
||||
<a href="/config/feeder">back to config</a>
|
||||
</div>
|
||||
|
||||
$:render.footer()
|
@ -1,50 +0,0 @@
|
||||
$def with (filter_settings_form, filter)
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Deluge:things</title>
|
||||
<link rel="icon" href="/static/images/deluge-icon.png" type="image/png" />
|
||||
<link rel="shortcut icon" href="/static/images/deluge-icon.png" type="image/png" />
|
||||
<link rel="stylesheet" type="text/css" href="/template_style.css" />
|
||||
<script language="javascript" src="/static/deluge.js"></script>
|
||||
<script language="javascript" src="/static/mootools-1.2-core.js"></script>
|
||||
|
||||
<script language="javascript" src="/static/mootools-1.2-more.js"></script>
|
||||
<script language="javascript" src="/static/mooui.js"></script>
|
||||
<script language="javascript" src="/static/deluge-moo.js"></script>
|
||||
<script language="javascript" src="/gettext.js"></script>
|
||||
<script language="javascript" src="/label/data/label.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<form method="post" action='$base/feeder/filter_settings/$filter'>
|
||||
<div class="info">
|
||||
<h3>Filter</h3>
|
||||
<table>
|
||||
$:(filter_settings_form.as_table(["regex", "active"]))
|
||||
</table>
|
||||
|
||||
<h3>Speed</h3>
|
||||
<table>
|
||||
$:(filter_settings_form.as_table(["max_download_speed", "max_upload_speed", "max_upload_slots", "max_connections"]))
|
||||
</table>
|
||||
|
||||
<h3>Seed options</h3>
|
||||
<table>
|
||||
$:(filter_settings_form.as_table(["stop_ratio", "stop_at_ratio", "remove_at_ratio"]))
|
||||
</table>
|
||||
|
||||
<h3>Other options</h3>
|
||||
<table>
|
||||
$:(filter_settings_form.as_table(["prioritize_first_last_pieces", "auto_managed", "download_location"]))
|
||||
</table>
|
||||
<input type="submit" name="submit" value="$_('Save')" />
|
||||
|
||||
</form>
|
||||
<h3>Matches</h3>
|
||||
$:filter_settings_form.post_html()
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,39 +0,0 @@
|
||||
$def with (filters, new_filter_form)
|
||||
$:render.header("things", '')
|
||||
<table><tr>
|
||||
<td>
|
||||
<table><tr></td>
|
||||
<div class="info">
|
||||
<h3>Filters</h3>
|
||||
</div></td></tr>
|
||||
<tr><td>
|
||||
<div class="info">
|
||||
<ul>
|
||||
$filters
|
||||
</ul>
|
||||
<form method="post" action='$base/feeder/filters'>
|
||||
$:(new_filter_form.as_p(["name"]))
|
||||
<input type="submit" name="submit" class="form_input" value="$_('Add filter')" />
|
||||
</form>
|
||||
<p><a href="/config/feeder">back to config</a>
|
||||
</div></td></tr></table>
|
||||
</td>
|
||||
<td>
|
||||
<div class="panel">
|
||||
<iframe style="border-style:hidden;" id="filter_settings" width=100% height=600>
|
||||
</iframe>
|
||||
</div>
|
||||
</td>
|
||||
</tr></table>
|
||||
|
||||
<script language="javascript">
|
||||
function load_options(filter){
|
||||
\$('filter_settings').src = state.base_url + '/feeder/filter_settings/' + filter;
|
||||
}
|
||||
</script>
|
||||
<script language="javascript">
|
||||
new InputSensitivitySetter({prefix:"id_",groups:[
|
||||
["name"]
|
||||
]});
|
||||
</script>
|
||||
$:render.footer()
|
@ -1,277 +0,0 @@
|
||||
#
|
||||
# webui.py
|
||||
#
|
||||
# Copyright (C) 2008 Fredrik Eriksson <feeder@winterbird.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
|
||||
import feedparser # for proccessing feed entries
|
||||
import os
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import sclient, aclient
|
||||
from deluge.plugins.webuipluginbase import WebUIPluginBase
|
||||
from deluge import component
|
||||
|
||||
api = component.get("WebPluginApi")
|
||||
forms = api.forms
|
||||
|
||||
class feed_page:
|
||||
"Class for showing feed items"
|
||||
@api.deco.deluge_page
|
||||
def GET(self, feedname):
|
||||
entries = sclient.feeder_get_items(feedname)
|
||||
items = ""
|
||||
for item in entries:
|
||||
items = """%(old)s
|
||||
<a href="%(link)s">
|
||||
<li>%(entry)s</li>
|
||||
</a>""" % { "old":items, "entry":item, "link":entries[item]}
|
||||
return api.render.feeder.feeds(items, feedname)
|
||||
|
||||
class filter_page:
|
||||
"Class for showing filters / filter settings"
|
||||
@api.deco.deluge_page
|
||||
def GET(self, args):
|
||||
new_filter = new_filter_form()
|
||||
filters = sclient.feeder_get_filters()
|
||||
|
||||
# List filters
|
||||
txt = ""
|
||||
for filter in filters:
|
||||
txt = """%(old)s
|
||||
<li onclick=\"load_options('%(new)s')\">
|
||||
%(new)s
|
||||
</li>""" % {'old':txt, 'new':filter}
|
||||
|
||||
return api.render.feeder.filters(txt, new_filter)
|
||||
|
||||
def POST(self):
|
||||
"Saves the new filter"
|
||||
name = api.utils.get_newforms_data(new_filter_form)['name']
|
||||
sclient.feeder_add_filter(name)
|
||||
return self.GET(name)
|
||||
|
||||
class new_filter_form(forms.Form):
|
||||
"basic form for a new label"
|
||||
name = forms.CharField(label="")
|
||||
|
||||
class filter_settings_page:
|
||||
"Class for showing filter settings"
|
||||
@api.deco.deluge_page
|
||||
def GET(self, filter):
|
||||
form = filter_settings_form(filter)
|
||||
return api.render.feeder.filter_settings(form, filter)
|
||||
|
||||
def POST(self, filter):
|
||||
opts = api.utils.get_newforms_data(filter_settings_form)
|
||||
|
||||
# apparently the "Unlimited" options still have to be changed
|
||||
# to -1 (wtf?)
|
||||
# FIXME there is probably a very much better way to ensure that
|
||||
# all values have the right types... not to mention to convert "Unlimited"
|
||||
# to -1...
|
||||
try:
|
||||
opts['max_upload_speed'] = int(opts['max_upload_speed'])
|
||||
except:
|
||||
opts['max_upload_speed'] = int(-1)
|
||||
try:
|
||||
opts['max_download_speed'] = int(opts['max_download_speed'])
|
||||
except:
|
||||
opts['max_download_speed'] = int(-1)
|
||||
try:
|
||||
opts['max_connections'] = int(opts['max_connections'])
|
||||
except:
|
||||
opts['max_connections'] = int(-1)
|
||||
try:
|
||||
opts['max_upload_slots'] = int(opts['max_upload_slots'])
|
||||
except:
|
||||
opts['max_upload_slots'] = int(-1)
|
||||
"""opts['max_upload_slots'] = long(opts['max_upload_slots'])
|
||||
opts['max_connections'] = long(opts['max_connections'])"""
|
||||
|
||||
# TODO filter settings per feed not implemented.
|
||||
opts['feeds'] = []
|
||||
|
||||
sclient.feeder_set_filter_config(filter, opts)
|
||||
return self.GET(filter)
|
||||
|
||||
class filter_settings_form(forms.Form):
|
||||
"form for filter settings"
|
||||
|
||||
def __init__(self, filter, test=False):
|
||||
self.filtername = filter # We want to save our filtername
|
||||
forms.Form.__init__(self)
|
||||
|
||||
def initial_data(self):
|
||||
self.conf = sclient.feeder_get_filter_config(self.filtername)
|
||||
return self.conf
|
||||
|
||||
def post_html(self):
|
||||
regex = self.conf["regex"]
|
||||
hits = sclient.feeder_test_filter(regex)
|
||||
if not hits:
|
||||
return "No hits"
|
||||
list = ""
|
||||
for hit in hits:
|
||||
list = """%(old)s
|
||||
<li><a href="%(link)s" >%(name)s</a></li>
|
||||
""" % { "old":list, "link":hits[hit], "name":hit }
|
||||
return """
|
||||
<ul>
|
||||
%s
|
||||
</ul>
|
||||
""" % list
|
||||
|
||||
regex = forms.CharField(_("regular_expression"))
|
||||
all_feeds = forms.CheckBox(_("all_feeds"))
|
||||
active = forms.CheckBox(_("active"))
|
||||
|
||||
#maximum:
|
||||
max_download_speed = forms.DelugeFloat(_("max_download_speed"))
|
||||
max_upload_speed = forms.DelugeFloat(_("max_upload_speed"))
|
||||
max_upload_slots = forms.DelugeInt(_("max_upload_slots"))
|
||||
max_connections = forms.DelugeInt(_("max_connections"))
|
||||
|
||||
stop_ratio = forms.DelugeFloat(_("stop_ratio"))
|
||||
stop_at_ratio = forms.CheckBox(_("stop_at_ratio"))
|
||||
remove_at_ratio = forms.CheckBox(_("remove_at_ratio"))
|
||||
|
||||
#queue:
|
||||
auto_managed = forms.CheckBox(_("is_auto_managed"))
|
||||
prioritize_first_last_pieces = forms.CheckBox(_("prioritize_first_last_pieces"))
|
||||
|
||||
download_location = forms.ServerFolder(_("download_location"))
|
||||
|
||||
class remove_feed_page:
|
||||
"Class for deleting feeds, redirects to setting page"
|
||||
@api.deco.deluge_page
|
||||
def GET(self, feedname):
|
||||
sclient.feeder_remove_feed(feedname)
|
||||
return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Redirecting back to settings</title>
|
||||
<meta http-equiv="refresh" content="0; URL=/config/feeder">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>"""
|
||||
|
||||
class remove_filter_page:
|
||||
"Class for deleting filters, redirects to setting page"
|
||||
@api.deco.deluge_page
|
||||
def GET(self, name):
|
||||
sclient.feeder_remove_filter(name)
|
||||
return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Redirecting back to settings</title>
|
||||
<meta http-equiv="refresh" content="0; URL=/config/feeder">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>"""
|
||||
|
||||
|
||||
class WebUI(WebUIPluginBase):
|
||||
#map url's to classes: [(url,class), ..]
|
||||
urls = [('/feeder/filters', filter_page),
|
||||
('/feeder/filter_settings/(.*)', filter_settings_page),
|
||||
('/feeder/feed_remove/(.*)', remove_feed_page),
|
||||
('/feeder/filter_remove/(.*)', remove_filter_page),
|
||||
('/feeder/feed/(.*)', feed_page)]
|
||||
|
||||
def enable(self):
|
||||
api.config_page_manager.register('plugins', 'feeder' ,ConfigForm)
|
||||
|
||||
def disable(self):
|
||||
api.config_page_manager.deregister('feeder')
|
||||
|
||||
class ConfigForm(forms.Form):
|
||||
#meta:
|
||||
title = _("feeder")
|
||||
|
||||
#load/save:
|
||||
def initial_data(self):
|
||||
return sclient.feeder_get_config()
|
||||
|
||||
def save(self, data):
|
||||
cfg = dict(data)
|
||||
sclient.feeder_add_feed(cfg)
|
||||
|
||||
def pre_html(self):
|
||||
feeds = sclient.feeder_get_feeds()
|
||||
filters = sclient.feeder_get_filters()
|
||||
filterlist = ""
|
||||
for filter in filters:
|
||||
filterlist = """ %(old)s <li>%(new)s
|
||||
<a href="/feeder/filter_remove/%(new)s">
|
||||
<img src="/static/images/16/list-remove.png" alt="Remove" />
|
||||
</a></li>""" % {'old':filterlist, 'new':filter}
|
||||
feedlist = ""
|
||||
for feed in feeds:
|
||||
feedlist = """%(old)s
|
||||
<li> <a href="/feeder/feed/%(new)s"> %(new)s (%(entrys)s torrents)</a>
|
||||
<a href="/feeder/feed_remove/%(new)s">
|
||||
<img src="/static/images/16/list-remove.png" alt="Remove" />
|
||||
</a></li>""" % {'old':feedlist, 'new':feed, 'entrys':len(sclient.feeder_get_items(feed))}
|
||||
|
||||
return """
|
||||
<table width=100%%><tr><td>
|
||||
<h3>Feeds</h3>
|
||||
</td>
|
||||
<td>
|
||||
<h3>Filters</h3>
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
<div class="info">
|
||||
<ul>
|
||||
%(feeds)s
|
||||
</ul></div>
|
||||
</td><td>
|
||||
<div class="info">
|
||||
<ul>
|
||||
%(filters)s
|
||||
</ul></div>
|
||||
<a href="/feeder/filters">Add/modify filters</a>
|
||||
</td></tr>
|
||||
</table>
|
||||
<h3>Add/change feed settings</h3>""" % {'feeds':feedlist, 'filters':filterlist}
|
||||
|
||||
name = forms.CharField(label=_("Name of feed"))
|
||||
url = forms.URLField(label=_("URL of feed"))
|
||||
updatetime = forms.IntegerField(label=_("Defualt refresh time"))
|
||||
|
@ -1,70 +0,0 @@
|
||||
#
|
||||
# setup.py
|
||||
#
|
||||
# Copyright (C) 2008 Fredrik Eriksson <feeder@winterbird.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
__plugin_name__ = "feeder"
|
||||
__author__ = "Fredrik Eriksson"
|
||||
__author_email__ = "feeder@winterbird.org"
|
||||
__version__ = "0.4"
|
||||
__url__ = ""
|
||||
__license__ = "GPLv3"
|
||||
__description__ = "A plugin for automatically downloadning torrents from a RSS-feed"
|
||||
__long_description__ = """"""
|
||||
__pkg_data__ = {__plugin_name__.lower(): ["template/*", "data/*"]}
|
||||
|
||||
setup(
|
||||
name=__plugin_name__,
|
||||
version=__version__,
|
||||
description=__description__,
|
||||
author=__author__,
|
||||
author_email=__author_email__,
|
||||
url=__url__,
|
||||
license=__license__,
|
||||
long_description=__long_description__,
|
||||
|
||||
packages=[__plugin_name__.lower()],
|
||||
package_data = __pkg_data__,
|
||||
|
||||
entry_points="""
|
||||
[deluge.plugin.core]
|
||||
%s = %s:CorePlugin
|
||||
[deluge.plugin.gtkui]
|
||||
%s = %s:GtkUIPlugin
|
||||
[deluge.plugin.webui]
|
||||
%s = %s:WebUIPlugin
|
||||
""" % ((__plugin_name__, __plugin_name__.lower())*3)
|
||||
)
|
@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
cd /home/vampas/projects/DelugeNotify/deluge/plugins/freespace
|
||||
mkdir temp
|
||||
export PYTHONPATH=./temp
|
||||
python setup.py build develop --install-dir ./temp
|
||||
cp ./temp/FreeSpace.egg-link .config/plugins
|
||||
rm -fr ./temp
|
@ -1,58 +0,0 @@
|
||||
#
|
||||
# __init__.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
|
||||
from deluge.plugins.init import PluginInitBase
|
||||
|
||||
class CorePlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from core import Core as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(CorePlugin, self).__init__(plugin_name)
|
||||
|
||||
class GtkUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from gtkui import GtkUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||
|
||||
class WebUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from webui import WebUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(WebUIPlugin, self).__init__(plugin_name)
|
@ -1,42 +0,0 @@
|
||||
#
|
||||
# common.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
|
||||
def get_resource(filename):
|
||||
import pkg_resources, os
|
||||
return pkg_resources.resource_filename("freespace", os.path.join("data", filename))
|
@ -1,201 +0,0 @@
|
||||
#
|
||||
# core.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
|
||||
|
||||
import os, statvfs
|
||||
from datetime import datetime, timedelta
|
||||
from twisted.internet import task
|
||||
from deluge.log import LOG as log
|
||||
from deluge.plugins.pluginbase import CorePluginBase
|
||||
from deluge.event import DelugeEvent
|
||||
import deluge.component as component
|
||||
import deluge.configmanager
|
||||
from deluge.core.rpcserver import export
|
||||
|
||||
|
||||
class LowDiskSpaceEvent(DelugeEvent):
|
||||
"""Triggered when the available space for a specific path is getting
|
||||
too low.
|
||||
"""
|
||||
def __init__(self, percents_dict):
|
||||
"""
|
||||
:param percents: dictionary of path keys with their respecive
|
||||
occupation percentages.
|
||||
"""
|
||||
self._args = [percents_dict]
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
"enabled": False,
|
||||
"percent": 90
|
||||
}
|
||||
|
||||
class Core(CorePluginBase):
|
||||
CLEANUP_TIMEOUT_SECS = 3600 # One hour
|
||||
|
||||
def enable(self):
|
||||
self.config = deluge.configmanager.ConfigManager("freespace.conf",
|
||||
DEFAULT_PREFS)
|
||||
self.notifications_sent = {}
|
||||
|
||||
self._timer = task.LoopingCall(self.update)
|
||||
self._interval = 60 * 5 # every 5 minutes
|
||||
if self.config['enabled']:
|
||||
self._timer.start(self._interval, False)
|
||||
|
||||
self._cleanup = task.LoopingCall(self.__cleanup_notifications)
|
||||
self._cleanup.start(self._interval, False)
|
||||
|
||||
try:
|
||||
component.get("CorePlugin.Notifications"). \
|
||||
register_custom_email_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_email_notification
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
component.get("EventManager").register_event_handler(
|
||||
"PluginEnabledEvent", self.__on_plugin_enabled
|
||||
)
|
||||
component.get("EventManager").register_event_handler(
|
||||
"PluginDisabledEvent", self.__on_plugin_disabled
|
||||
)
|
||||
|
||||
def disable(self):
|
||||
try:
|
||||
component.get("CorePlugin.Notifications"). \
|
||||
deregister_custom_email_notification("LowDiskSpaceEvent")
|
||||
except KeyError:
|
||||
pass
|
||||
component.get("EventManager").deregister_event_handler(
|
||||
"PluginEnabledEvent", self.__on_plugin_enabled
|
||||
)
|
||||
component.get("EventManager").deregister_event_handler(
|
||||
"PluginDisabledEvent", self.__on_plugin_disabled
|
||||
)
|
||||
self._cleanup.stop()
|
||||
if self._timer.running:
|
||||
self._timer.stop()
|
||||
|
||||
def update(self):
|
||||
log.debug('Updating %s FreeSpace', self.__class__.__name__)
|
||||
nots = {}
|
||||
for path in self.__gather_paths_to_check():
|
||||
log.debug("Checking path %s", path)
|
||||
if os.path.exists(path):
|
||||
free_percent = self.__get_free_space(path)
|
||||
if (100 - free_percent) > self.config['percent']:
|
||||
if path not in self.notifications_sent:
|
||||
self.notifications_sent[path] = datetime.utcnow()
|
||||
nots[path] = (100 - free_percent)
|
||||
else:
|
||||
log.warning("Running low on disk space on %s but "
|
||||
"notifications were already triggered.",
|
||||
path)
|
||||
if nots:
|
||||
component.get("EventManager").emit(LowDiskSpaceEvent(nots))
|
||||
|
||||
|
||||
@export
|
||||
def set_config(self, config):
|
||||
"sets the config dictionary"
|
||||
if not self.config['enabled'] and config['enabled']:
|
||||
self._timer.start(self._interval, False)
|
||||
elif self.config['enabled'] and not config['enabled']:
|
||||
self._timer.stop()
|
||||
|
||||
for key in config.keys():
|
||||
self.config[key] = config[key]
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def get_config(self):
|
||||
"returns the config dictionary"
|
||||
return self.config.config
|
||||
|
||||
def __gather_paths_to_check(self):
|
||||
self.over_percentage = {}
|
||||
torrent_manager = component.get('TorrentManager')
|
||||
paths = set()
|
||||
for torrent_id in torrent_manager.get_torrent_list():
|
||||
status = torrent_manager[torrent_id].get_status([
|
||||
'is_finished',
|
||||
'move_on_completed_path',
|
||||
'save_path'
|
||||
])
|
||||
if not status['is_finished']:
|
||||
paths.add(status['move_on_completed_path'])
|
||||
paths.add(status['save_path'])
|
||||
return paths
|
||||
|
||||
def __get_free_space(self, path):
|
||||
log.debug("Calculating free space on %s", path)
|
||||
stat = os.statvfs(path)
|
||||
free_blocks = stat[statvfs.F_BAVAIL]
|
||||
total_blocks = stat[statvfs.F_BLOCKS]
|
||||
free_percent = free_blocks * 100 / total_blocks
|
||||
return free_percent
|
||||
|
||||
def __custom_email_notification(self, ocupied_percents):
|
||||
|
||||
subject = _("Low Disk Space Warning")
|
||||
message = _("You're running low on disk space:\n")
|
||||
|
||||
for path, ocupied_percent in ocupied_percents.iteritems():
|
||||
message += _(' %s%% ocupation in %s\n') % (ocupied_percent, path)
|
||||
# "\"%s\"%% space occupation on %s") % (ocupied_percent, path)
|
||||
return subject, message
|
||||
|
||||
def __on_plugin_enabled(self, plugin_name):
|
||||
if plugin_name == 'Notifications':
|
||||
component.get("CorePlugin.Notifications"). \
|
||||
register_custom_email_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_email_notification
|
||||
)
|
||||
|
||||
def __on_plugin_disabled(self, plugin_name):
|
||||
if plugin_name == 'Notifications':
|
||||
component.get("CorePlugin.Notifications"). \
|
||||
deregister_custom_email_notification("LowDiskSpaceEvent")
|
||||
|
||||
def __cleanup_notifications(self):
|
||||
now = datetime.now()
|
||||
for path, when in self.notifications_sent.copy().iteritems():
|
||||
if when <= (now -timedelta(seconds=self.CLEANUP_TIMEOUT_SECS)):
|
||||
log.debug("Removing old(%s) path from notified paths: %s",
|
||||
when, path)
|
||||
self.notifications_sent.pop(path)
|
@ -1,100 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<glade-interface>
|
||||
<!-- interface-requires gtk+ 2.6 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<widget class="GtkWindow" id="window1">
|
||||
<child>
|
||||
<widget class="GtkHBox" id="prefs_box">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkFrame" id="frame1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">none</property>
|
||||
<child>
|
||||
<widget class="GtkAlignment" id="alignment1">
|
||||
<property name="visible">True</property>
|
||||
<property name="left_padding">12</property>
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox2">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox2">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="enabled">
|
||||
<property name="label" translatable="yes">Consider low when</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkSpinButton" id="percent">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="max_length">2</property>
|
||||
<property name="invisible_char">●</property>
|
||||
<property name="adjustment">90 0 99 1 10 0</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label14">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">%</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label4">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">of the disk is occupied.</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes"><b>Free Space Checking</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">label_item</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
Script: freespace.js
|
||||
The client-side javascript code for the FreeSpace plugin.
|
||||
|
||||
Copyright:
|
||||
(C) Pedro Algarvio 2009 <damoxc@gmail.com>
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, write to:
|
||||
The Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor
|
||||
Boston, MA 02110-1301, USA.
|
||||
|
||||
In addition, as a special exception, the copyright holders give
|
||||
permission to link the code of portions of this program with the OpenSSL
|
||||
library.
|
||||
You must obey the GNU General Public License in all respects for all of
|
||||
the code used other than OpenSSL. If you modify file(s) with this
|
||||
exception, you may extend this exception to your version of the file(s),
|
||||
but you are not obligated to do so. If you do not wish to do so, delete
|
||||
this exception statement from your version. If you delete this exception
|
||||
statement from all source files in the program, then also delete it here.
|
||||
*/
|
||||
|
||||
FreeSpacePlugin = Ext.extend(Deluge.Plugin, {
|
||||
constructor: function(config) {
|
||||
config = Ext.apply({
|
||||
name: "FreeSpace"
|
||||
}, config);
|
||||
FreeSpacePlugin.superclass.constructor.call(this, config);
|
||||
},
|
||||
|
||||
onDisable: function() {
|
||||
|
||||
},
|
||||
|
||||
onEnable: function() {
|
||||
|
||||
}
|
||||
});
|
||||
new FreeSpacePlugin();
|
@ -1,174 +0,0 @@
|
||||
#
|
||||
# gtkui.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
|
||||
import gtk
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge.plugins.pluginbase import GtkPluginBase
|
||||
import deluge.component as component
|
||||
import deluge.common
|
||||
|
||||
from common import get_resource
|
||||
|
||||
class GtkUI(GtkPluginBase):
|
||||
|
||||
def enable(self):
|
||||
log.debug('Enabling %s FreeSpace', self.__class__.__name__)
|
||||
self.glade = gtk.glade.XML(get_resource("config.glade"))
|
||||
self.prefs = self.glade.get_widget('prefs_box')
|
||||
parent = self.prefs.get_parent()
|
||||
if parent:
|
||||
parent.remove(self.prefs)
|
||||
|
||||
# chk_ap = component.get("Preferences").glade.get_widget('chk_add_paused')
|
||||
# downloads_vbox = chk_ap.get_parent().get_parent().get_parent().get_parent()
|
||||
|
||||
downloads_vbox = component.get("Preferences").glade.get_widget('vbox1')
|
||||
downloads_vbox.pack_start(self.prefs, False, True, 0)
|
||||
# self.prefs.set_parent(frame)
|
||||
|
||||
# component.get("Preferences").add_page("FreeSpace", self.glade.get_widget("prefs_box"))
|
||||
component.get("PluginManager").register_hook("on_apply_prefs",
|
||||
self.on_apply_prefs)
|
||||
component.get("PluginManager").register_hook("on_show_prefs",
|
||||
self.on_show_prefs)
|
||||
|
||||
try:
|
||||
notifications = component.get("GtkPlugin.Notifications")
|
||||
notifications.register_custom_popup_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_popup_notification
|
||||
)
|
||||
notifications.register_custom_blink_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_blink_notification
|
||||
)
|
||||
notifications.register_custom_sound_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_sound_notification
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
client.register_event_handler("PluginEnabledEvent",
|
||||
self.__on_plugin_enabled)
|
||||
|
||||
client.register_event_handler("PluginDisabledEvent",
|
||||
self.__on_plugin_disabled)
|
||||
|
||||
def disable(self):
|
||||
component.get("Preferences").remove_page("FreeSpace")
|
||||
component.get("PluginManager").deregister_hook("on_apply_prefs",
|
||||
self.on_apply_prefs)
|
||||
component.get("PluginManager").deregister_hook("on_show_prefs",
|
||||
self.on_show_prefs)
|
||||
try:
|
||||
notifications = component.get("GtkPlugin.Notifications")
|
||||
notifications.deregister_custom_popup_notification(
|
||||
"LowDiskSpaceEvent"
|
||||
)
|
||||
notifications.deregister_custom_blink_notification(
|
||||
"LowDiskSpaceEvent"
|
||||
)
|
||||
notifications.deregister_custom_sound_notification(
|
||||
"LowDiskSpaceEvent"
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
client.deregister_event_handler("PluginEnabledEvent",
|
||||
self.__on_plugin_enabled)
|
||||
|
||||
client.deregister_event_handler("PluginDisabledEvent",
|
||||
self.__on_plugin_disabled)
|
||||
|
||||
def on_apply_prefs(self):
|
||||
log.debug("applying prefs for FreeSpace")
|
||||
config = {
|
||||
"enabled": self.glade.get_widget('enabled').get_active(),
|
||||
"percent": self.glade.get_widget('percent').get_value()
|
||||
}
|
||||
client.freespace.set_config(config)
|
||||
|
||||
def on_show_prefs(self):
|
||||
client.freespace.get_config().addCallback(self.cb_get_config)
|
||||
|
||||
def cb_get_config(self, config):
|
||||
"callback for on show_prefs"
|
||||
self.glade.get_widget('enabled').set_active(config['enabled'])
|
||||
self.glade.get_widget('percent').set_value(config['percent'])
|
||||
|
||||
def __custom_popup_notification(self, ocupied_percents):
|
||||
title = _("Low Free Space")
|
||||
message = ''
|
||||
for path, percent in ocupied_percents.iteritems():
|
||||
message += '%s%% %s\n' % (percent, path)
|
||||
message += '\n'
|
||||
return title, message
|
||||
|
||||
def __custom_blink_notification(self, ocupied_percents):
|
||||
return True # Yes, do blink
|
||||
|
||||
def __custom_sound_notification(self, ocupied_percents):
|
||||
return '' # Use default sound
|
||||
|
||||
def __on_plugin_enabled(self, plugin_name):
|
||||
if plugin_name == 'Notifications':
|
||||
notifications = component.get("GtkPlugin.Notifications")
|
||||
notifications.register_custom_popup_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_popup_notification
|
||||
)
|
||||
notifications.register_custom_blink_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_blink_notification
|
||||
)
|
||||
notifications.register_custom_sound_notification(
|
||||
"LowDiskSpaceEvent", self.__custom_sound_notification
|
||||
)
|
||||
|
||||
def __on_plugin_disabled(self, plugin_name):
|
||||
pass
|
||||
# if plugin_name == 'Notifications':
|
||||
# notifications = component.get("GtkPlugin.Notifications")
|
||||
# notifications.deregister_custom_popup_notification(
|
||||
# "LowDiskSpaceEvent"
|
||||
# )
|
||||
# notifications.deregister_custom_blink_notification(
|
||||
# "LowDiskSpaceEvent"
|
||||
# )
|
||||
# notifications.deregister_custom_sound_notification(
|
||||
# "LowDiskSpaceEvent"
|
||||
# )
|
@ -1,55 +0,0 @@
|
||||
#
|
||||
# webui.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge import component
|
||||
from deluge.plugins.pluginbase import WebPluginBase
|
||||
|
||||
from common import get_resource
|
||||
|
||||
class WebUI(WebPluginBase):
|
||||
|
||||
scripts = [get_resource("freespace.js")]
|
||||
|
||||
def enable(self):
|
||||
pass
|
||||
|
||||
def disable(self):
|
||||
pass
|
@ -1,71 +0,0 @@
|
||||
#
|
||||
# setup.py
|
||||
#
|
||||
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
__plugin_name__ = "FreeSpace"
|
||||
__author__ = "Pedro Algarvio"
|
||||
__author_email__ = "ufs@ufsoft.org"
|
||||
__version__ = "0.1"
|
||||
__url__ = "http://deluge.ufsoft.org/hg/Notification/"
|
||||
__license__ = "GPLv3"
|
||||
__description__ = "Plugin which continuously checks for available free space."
|
||||
__long_description__ = __description__
|
||||
__pkg_data__ = {__plugin_name__.lower(): ["template/*", "data/*"]}
|
||||
|
||||
setup(
|
||||
name=__plugin_name__,
|
||||
version=__version__,
|
||||
description=__description__,
|
||||
author=__author__,
|
||||
author_email=__author_email__,
|
||||
url=__url__,
|
||||
license=__license__,
|
||||
long_description=__long_description__ if __long_description__ else __description__,
|
||||
|
||||
packages=[__plugin_name__.lower()],
|
||||
package_data = __pkg_data__,
|
||||
|
||||
entry_points="""
|
||||
[deluge.plugin.core]
|
||||
%s = %s:CorePlugin
|
||||
[deluge.plugin.gtkui]
|
||||
%s = %s:GtkUIPlugin
|
||||
""" % ((__plugin_name__, __plugin_name__.lower())*2)
|
||||
)
|
@ -188,6 +188,7 @@ class Core(CorePluginBase):
|
||||
CheckInput(not (label_id in self.labels) , _("Label already exists"))
|
||||
|
||||
self.labels[label_id] = dict(OPTIONS_DEFAULTS)
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def remove(self, label_id):
|
||||
@ -259,7 +260,7 @@ class Core(CorePluginBase):
|
||||
return False
|
||||
|
||||
@export
|
||||
def set_options(self, label_id, options_dict , apply = False):
|
||||
def set_options(self, label_id, options_dict):
|
||||
"""update the label options
|
||||
|
||||
options_dict :
|
||||
@ -271,8 +272,6 @@ class Core(CorePluginBase):
|
||||
"apply_max":bool(),
|
||||
"move_completed_to":string() or None
|
||||
}
|
||||
|
||||
apply : applies download-options to all torrents currently labelled by label_id
|
||||
"""
|
||||
CheckInput(label_id in self.labels , _("Unknown Label"))
|
||||
for key in options_dict.keys():
|
||||
|
@ -49,6 +49,7 @@ Deluge.ux.AddLabelWindow = Ext.extend(Ext.Window, {
|
||||
this.form = this.add({
|
||||
xtype: 'form',
|
||||
height: 35,
|
||||
baseCls: 'x-plain',
|
||||
bodyStyle:'padding:5px 5px 0',
|
||||
defaultType: 'textfield',
|
||||
labelWidth: 50,
|
||||
@ -496,7 +497,7 @@ Deluge.plugins.LabelPlugin = Ext.extend(Deluge.Plugin, {
|
||||
},
|
||||
|
||||
onLabelRemoveClick: function() {
|
||||
var state = this.filter.getFilter();
|
||||
var state = this.filter.getState();
|
||||
deluge.client.label.remove(state, {
|
||||
success: function() {
|
||||
deluge.ui.update();
|
||||
|
@ -46,11 +46,8 @@ import sidebar_menu
|
||||
import label_config
|
||||
import submenu
|
||||
|
||||
from deluge.configmanager import ConfigManager
|
||||
config = ConfigManager("label.conf")
|
||||
NO_LABEL = "No Label"
|
||||
|
||||
|
||||
def cell_data_label(column, cell, model, row, data):
|
||||
cell.set_property('text', str(model.get_value(row, data)))
|
||||
|
||||
|
@ -41,8 +41,6 @@ from deluge import component # for systray
|
||||
import gtk, gobject
|
||||
from deluge.ui.client import client
|
||||
|
||||
from deluge.configmanager import ConfigManager
|
||||
config = ConfigManager("label.conf")
|
||||
NO_LABEL = "No Label"
|
||||
|
||||
class LabelMenu(gtk.MenuItem):
|
||||
|
@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
mkdir temp
|
||||
export PYTHONPATH=./temp
|
||||
python setup.py develop --install-dir ./temp
|
||||
cp ./temp/Stats.egg-link ~/.config/deluge/plugins
|
||||
rm -fr ./temp
|
@ -1,81 +0,0 @@
|
||||
#
|
||||
# setup.py
|
||||
#
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
__plugin_name__ = "Stats"
|
||||
__author__ = "Martijn Voncken"
|
||||
__author_email__ = "mvoncken@gmail.com"
|
||||
__version__ = "0.1"
|
||||
__url__ = "http://deluge-torrent.org"
|
||||
__license__ = "GPLv3"
|
||||
__description__ = ""
|
||||
__long_description__ = """"""
|
||||
__pkg_data__ = {__plugin_name__.lower(): ["template/*", "data/*"]}
|
||||
|
||||
setup(
|
||||
name=__plugin_name__,
|
||||
version=__version__,
|
||||
description=__description__,
|
||||
author=__author__,
|
||||
author_email=__author_email__,
|
||||
url=__url__,
|
||||
license=__license__,
|
||||
long_description=__long_description__,
|
||||
|
||||
packages=[__plugin_name__.lower()],
|
||||
package_data = __pkg_data__,
|
||||
|
||||
entry_points="""
|
||||
[deluge.plugin.core]
|
||||
%s = %s:CorePlugin
|
||||
[deluge.plugin.gtkui]
|
||||
%s = %s:GtkUIPlugin
|
||||
[deluge.plugin.web]
|
||||
%s = %s:WebUIPlugin
|
||||
""" % ((__plugin_name__, __plugin_name__.lower())*3)
|
||||
)
|
@ -1,57 +0,0 @@
|
||||
#
|
||||
# __init__.py
|
||||
#
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
|
||||
from deluge.plugins.init import PluginInitBase
|
||||
|
||||
class CorePlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from core import Core as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(CorePlugin, self).__init__(plugin_name)
|
||||
|
||||
class GtkUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from gtkui import GtkUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(GtkUIPlugin, self).__init__(plugin_name)
|
||||
|
||||
class WebUIPlugin(PluginInitBase):
|
||||
def __init__(self, plugin_name):
|
||||
from webui import WebUI as _plugin_cls
|
||||
self._plugin_cls = _plugin_cls
|
||||
super(WebUIPlugin, self).__init__(plugin_name)
|
@ -1,39 +0,0 @@
|
||||
#
|
||||
# common.py
|
||||
#
|
||||
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
|
||||
import pkg_resources
|
||||
import os.path
|
||||
|
||||
def get_resource(filename):
|
||||
return pkg_resources.resource_filename("stats", os.path.join("data", filename))
|
@ -1,169 +0,0 @@
|
||||
#
|
||||
# core.py
|
||||
#
|
||||
# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
|
||||
# Copyright (C) 2008 Damien Churchill <damoxc@gmail.com>
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) Marcos Pinto 2007 <markybob@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
|
||||
from twisted.internet.task import LoopingCall
|
||||
import time
|
||||
|
||||
import deluge
|
||||
from deluge.log import LOG as log
|
||||
from deluge.plugins.pluginbase import CorePluginBase
|
||||
from deluge import component
|
||||
from deluge import configmanager
|
||||
from deluge.core.rpcserver import export
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
"test": "NiNiNi",
|
||||
"update_interval": 2, #2 seconds.
|
||||
"length": 150, # 2 seconds * 150 --> 5 minutes.
|
||||
}
|
||||
|
||||
DEFAULT_TOTALS = {
|
||||
"total_upload": 0,
|
||||
"total_download": 0,
|
||||
"total_payload_upload": 0,
|
||||
"total_payload_download": 0,
|
||||
"stats": {}
|
||||
}
|
||||
|
||||
class Core(CorePluginBase):
|
||||
totals = {} #class var to catch only updating this once per session in enable.
|
||||
|
||||
def enable(self):
|
||||
self.core = component.get("Core")
|
||||
self.stats ={}
|
||||
|
||||
self.config = configmanager.ConfigManager("stats.conf", DEFAULT_PREFS)
|
||||
self.saved_stats = configmanager.ConfigManager("stats.totals", DEFAULT_TOTALS)
|
||||
if self.totals == {}:
|
||||
self.totals.update(self.saved_stats.config)
|
||||
|
||||
self.stats = self.saved_stats["stats"] or {}
|
||||
|
||||
self.stats_keys = [
|
||||
"payload_download_rate",
|
||||
"payload_upload_rate"
|
||||
]
|
||||
self.update_stats()
|
||||
|
||||
self.update_timer = LoopingCall(self.update_stats)
|
||||
self.update_timer.start(self.config["update_interval"])
|
||||
|
||||
self.save_timer = LoopingCall(self.save_stats)
|
||||
self.save_timer.start(60)
|
||||
|
||||
def disable(self):
|
||||
self.save_stats()
|
||||
try:
|
||||
self.update_timer.stop()
|
||||
self.save_timer.stop()
|
||||
except:
|
||||
pass
|
||||
|
||||
def update_stats(self):
|
||||
try:
|
||||
status = self.core.get_session_status(self.stats_keys)
|
||||
for key, value in status.items():
|
||||
if key not in self.stats:
|
||||
self.stats[key] = []
|
||||
self.stats[key].insert(0, value)
|
||||
|
||||
for stat_list in self.stats.values():
|
||||
if len(stat_list) > self.config["length"]:
|
||||
stat_list.pop()
|
||||
self.last_update = time.time()
|
||||
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
|
||||
def save_stats(self):
|
||||
try:
|
||||
self.saved_stats["stats"] = self.stats
|
||||
self.saved_stats.config.update(self.get_totals())
|
||||
self.saved_stats.save()
|
||||
except Exception,e:
|
||||
log.exception(e)
|
||||
return True
|
||||
|
||||
|
||||
# export:
|
||||
@export
|
||||
def get_stats(self, keys):
|
||||
stats_dict = {}
|
||||
for key in keys:
|
||||
if key in self.stats:
|
||||
stats_dict[key] = self.stats[key]
|
||||
stats_dict["_last_update"] = self.last_update
|
||||
return stats_dict
|
||||
|
||||
@export
|
||||
def get_totals(self):
|
||||
result = {}
|
||||
session_totals = self.get_session_totals()
|
||||
for key in session_totals:
|
||||
result[key] = self.totals[key] + session_totals[key]
|
||||
return result
|
||||
|
||||
@export
|
||||
def get_session_totals(self):
|
||||
status = self.core.session.status()
|
||||
return {
|
||||
"total_upload": status.total_upload,
|
||||
"total_download": status.total_download,
|
||||
"total_payload_upload": status.total_payload_upload,
|
||||
"total_payload_download": status.total_payload_download
|
||||
}
|
||||
|
||||
@export
|
||||
def set_config(self, config):
|
||||
"sets the config dictionary"
|
||||
for key in config.keys():
|
||||
self.config[key] = config[key]
|
||||
self.config.save()
|
||||
|
||||
@export
|
||||
def get_config(self):
|
||||
"returns the config dictionary"
|
||||
return self.config.config
|
@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.5 on Fri Aug 8 23:34:44 2008 -->
|
||||
<glade-interface>
|
||||
<widget class="GtkWindow" id="window1">
|
||||
<child>
|
||||
<widget class="GtkHBox" id="prefs_box">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Test config value:</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkEntry" id="txt_test">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
Script: stats.js
|
||||
The javascript client-side code for the Stats plugin.
|
||||
|
||||
Copyright:
|
||||
(C) Damien Churchill 2009 <damoxc@gmail.com>
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, write to:
|
||||
The Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor
|
||||
Boston, MA 02110-1301, USA.
|
||||
|
||||
In addition, as a special exception, the copyright holders give
|
||||
permission to link the code of portions of this program with the OpenSSL
|
||||
library.
|
||||
You must obey the GNU General Public License in all respects for all of
|
||||
the code used other than OpenSSL. If you modify file(s) with this
|
||||
exception, you may extend this exception to your version of the file(s),
|
||||
but you are not obligated to do so. If you do not wish to do so, delete
|
||||
this exception statement from your version. If you delete this exception
|
||||
statement from all source files in the program, then also delete it here.
|
||||
*/
|
||||
|
||||
StatsPlugin = Ext.extend(Deluge.Plugin, {
|
||||
constructor: function(config) {
|
||||
config = Ext.apply({
|
||||
name: "Stats"
|
||||
}, config);
|
||||
StatsPlugin.superclass.constructor.call(this, config);
|
||||
},
|
||||
|
||||
onDisable: function() {
|
||||
},
|
||||
|
||||
onEnable: function() {
|
||||
}
|
||||
});
|
||||
new StatsPlugin();
|
@ -1,103 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.5 on Mon Oct 13 20:17:39 2008 -->
|
||||
<glade-interface>
|
||||
<widget class="GtkWindow" id="window1">
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox1">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="graph_label">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<widget class="GtkImage" id="image1">
|
||||
<property name="visible">True</property>
|
||||
<property name="stock">gtk-page-setup</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="graph_label_text">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Graphs</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="graph_tab">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<child>
|
||||
<widget class="GtkNotebook" id="graph_notebook">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tab_pos">GTK_POS_LEFT</property>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="bandwidth_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="bandwidth_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Bandwidth</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="connections_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="connections_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Connections</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkDrawingArea" id="seeds_graph">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="seeds_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Seeds/Peers</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">tab</property>
|
||||
<property name="position">2</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
@ -1,262 +0,0 @@
|
||||
#
|
||||
# graph.py
|
||||
#
|
||||
# Copyright (C) 2008 Damien Churchill <damoxc@gmail.com>
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) Marcos Pinto 2007 <markybob@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
|
||||
"""
|
||||
port of old plugin by markybob.
|
||||
"""
|
||||
import time
|
||||
import cairo
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
|
||||
black = (0, 0, 0)
|
||||
gray = (0.75, 0.75, 0.75)
|
||||
white = (1.0, 1.0, 1.0)
|
||||
darkred = (0.65, 0, 0)
|
||||
red = (1.0, 0, 0)
|
||||
green = (0, 1.0, 0)
|
||||
blue = (0, 0, 1.0)
|
||||
orange = (1.0, 0.74, 0)
|
||||
|
||||
def default_formatter(value):
|
||||
return str(value)
|
||||
|
||||
def change_opacity(color, opactiy):
|
||||
"""A method to assist in changing the opactiy of a color inorder to draw the
|
||||
fills.
|
||||
"""
|
||||
color = list(color)
|
||||
if len(color) == 4:
|
||||
color[3] = opactiy
|
||||
else:
|
||||
color.append(opactiy)
|
||||
return tuple(color)
|
||||
|
||||
class Graph:
|
||||
def __init__(self):
|
||||
self.width = 100
|
||||
self.height = 100
|
||||
self.length = 150
|
||||
self.stat_info = {}
|
||||
self.line_size = 2
|
||||
self.mean_selected = True
|
||||
self.legend_selected = True
|
||||
self.max_selected = True
|
||||
self.black = (0, 0 , 0,)
|
||||
self.interval = 2 # 2 secs
|
||||
self.text_bg = (255, 255 , 255, 128) # prototyping
|
||||
self.set_left_axis()
|
||||
|
||||
def set_left_axis(self, **kargs):
|
||||
self.left_axis = kargs
|
||||
|
||||
def add_stat(self, stat, label='', axis='left', line=True, fill=True, color=None):
|
||||
self.stat_info[stat] = {
|
||||
'axis': axis,
|
||||
'label': label,
|
||||
'line': line,
|
||||
'fill': fill,
|
||||
'color': color
|
||||
}
|
||||
|
||||
def set_stats(self, stats):
|
||||
self.last_update = stats["_last_update"]
|
||||
log.debug("Last update: %s" % self.last_update)
|
||||
del stats["_last_update"]
|
||||
self.stats = stats
|
||||
|
||||
def set_config(self, config):
|
||||
self.length = config["length"]
|
||||
self.interval = config["update_interval"]
|
||||
|
||||
def draw_to_context(self, context, width, height):
|
||||
self.ctx = context
|
||||
self.width, self.height = width, height
|
||||
try:
|
||||
self.draw_rect(white, 0, 0, self.width, self.height)
|
||||
self.draw_x_axis()
|
||||
self.draw_left_axis()
|
||||
|
||||
if self.legend_selected:
|
||||
self.draw_legend()
|
||||
except cairo.Error, e:
|
||||
log.exception(e)
|
||||
return self.ctx
|
||||
|
||||
def draw(self, width, height):
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height)
|
||||
self.ctx = cairo.Context(self.surface)
|
||||
self.draw_rect(white, 0, 0, self.width, self.height)
|
||||
self.draw_x_axis()
|
||||
self.draw_left_axis()
|
||||
|
||||
if self.legend_selected:
|
||||
self.draw_legend()
|
||||
return self.surface
|
||||
|
||||
def draw_x_axis(self):
|
||||
duration = float(self.length * self.interval)
|
||||
start = self.last_update - duration
|
||||
ratio = (self.width - 40) / duration
|
||||
seconds_to_minute = 60 - time.localtime(start)[5]
|
||||
|
||||
for i in xrange(0, 5):
|
||||
text = time.strftime('%H:%M', time.localtime(start + seconds_to_minute + (60*i)))
|
||||
x = int(ratio * (seconds_to_minute + (60*i)))
|
||||
self.draw_text(text, x + 46, self.height - 20)
|
||||
x = x + 59.5
|
||||
self.draw_dotted_line(gray, x, 20, x, self.height - 20)
|
||||
|
||||
y = self.height - 22.5
|
||||
self.draw_dotted_line(gray, 60, y, int(self.width), y)
|
||||
|
||||
def draw_left_axis(self):
|
||||
stats = {}
|
||||
max_values = []
|
||||
for stat in self.stat_info:
|
||||
if self.stat_info[stat]['axis'] == 'left':
|
||||
stats[stat] = self.stat_info[stat]
|
||||
stats[stat]['values'] = self.stats[stat]
|
||||
stats[stat]['fill_color'] = change_opacity(stats[stat]['color'], 0.5)
|
||||
stats[stat]['color'] = change_opacity(stats[stat]['color'], 0.8)
|
||||
stats[stat]['max_value'] = max(self.stats[stat])
|
||||
max_values.append(stats[stat]['max_value'])
|
||||
if len(max_values) > 1:
|
||||
max_value = max(*max_values)
|
||||
else:
|
||||
max_value = max_values[0]
|
||||
|
||||
if max_value < self.left_axis['min']:
|
||||
max_value = self.left_axis['min']
|
||||
|
||||
height = self.height - self.line_size - 22
|
||||
#max_value = float(round(max_value, len(str(max_value)) * -1))
|
||||
max_value = float(max_value)
|
||||
ratio = height / max_value
|
||||
|
||||
for i in xrange(1, 6):
|
||||
y = int(ratio * ((max_value / 5) * i)) - 0.5
|
||||
if i < 5:
|
||||
self.draw_dotted_line(gray, 60, y, self.width, y)
|
||||
text = self.left_axis['formatter']((max_value / 5) * (5 - i))
|
||||
self.draw_text(text, 0, y - 6)
|
||||
self.draw_dotted_line(gray, 60.5, 20, 60.5, self.height - 20)
|
||||
|
||||
for stat, info in stats.iteritems():
|
||||
self.draw_value_poly(info['values'], info['color'], max_value)
|
||||
self.draw_value_poly(info['values'], info['fill_color'], max_value, info['fill'])
|
||||
|
||||
def draw_legend(self):
|
||||
pass
|
||||
|
||||
def trace_path(self, values, max_value):
|
||||
height = self.height - 24
|
||||
width = self.width
|
||||
line_width = self.line_size
|
||||
|
||||
self.ctx.set_line_width(line_width)
|
||||
self.ctx.move_to(width, height)
|
||||
|
||||
self.ctx.line_to(width,
|
||||
int(height - ((height - 28) * values[0] / max_value)))
|
||||
|
||||
x = width
|
||||
step = (width - 60) / float(self.length)
|
||||
for i, value in enumerate(values):
|
||||
if i == self.length - 1:
|
||||
x = 62
|
||||
self.ctx.line_to(x,
|
||||
int(height - 1 - ((height - 28) * value / max_value))
|
||||
)
|
||||
x -= step
|
||||
|
||||
self.ctx.line_to(
|
||||
int(width + 62 - (((len(values) - 1) * width) / (self.length - 1))),
|
||||
height)
|
||||
self.ctx.close_path()
|
||||
|
||||
def draw_value_poly(self, values, color, max_value, fill=False):
|
||||
self.trace_path(values, max_value)
|
||||
self.ctx.set_source_rgba(*color)
|
||||
|
||||
if fill:
|
||||
self.ctx.fill()
|
||||
else:
|
||||
self.ctx.stroke()
|
||||
|
||||
def draw_text(self, text, x, y):
|
||||
self.ctx.set_font_size(9)
|
||||
self.ctx.move_to(x, y + 9)
|
||||
self.ctx.set_source_rgba(*self.black)
|
||||
self.ctx.show_text(text)
|
||||
|
||||
def draw_rect(self, color, x, y, height, width):
|
||||
self.ctx.set_source_rgba(*color)
|
||||
self.ctx.rectangle(x, y, height, width)
|
||||
self.ctx.fill()
|
||||
|
||||
def draw_line(self, color, x1, y1, x2, y2):
|
||||
self.ctx.set_source_rgba(*color)
|
||||
self.ctx.set_line_width(1)
|
||||
self.ctx.move_to(x1, y1)
|
||||
self.ctx.line_to(x2, y2)
|
||||
self.ctx.stroke()
|
||||
|
||||
def draw_dotted_line(self, color, x1, y1, x2, y2):
|
||||
self.ctx.set_source_rgba(*color)
|
||||
self.ctx.set_line_width(1)
|
||||
self.ctx.move_to(x1, y1)
|
||||
self.ctx.line_to(x2, y2)
|
||||
#self.ctx.stroke_preserve()
|
||||
#self.ctx.set_source_rgba(*white)
|
||||
#self.ctx.set_dash((1, 1), 4)
|
||||
self.ctx.stroke()
|
||||
#self.ctx.set_dash((1, 1), 0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import test
|
@ -1,151 +0,0 @@
|
||||
#
|
||||
# gtkui.py
|
||||
#
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
#
|
||||
# Basic plugin template created by:
|
||||
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
||||
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
|
||||
import gtk
|
||||
import gobject
|
||||
from gtk.glade import XML
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
import graph
|
||||
from deluge import component
|
||||
from deluge.log import LOG as log
|
||||
from deluge.common import fspeed
|
||||
from deluge.ui.client import client
|
||||
from deluge.ui.gtkui.torrentdetails import Tab
|
||||
from deluge.plugins.pluginbase import GtkPluginBase
|
||||
|
||||
class GraphsTab(Tab):
|
||||
def __init__(self, glade):
|
||||
Tab.__init__(self)
|
||||
self._name = 'Graphs'
|
||||
self.glade = glade
|
||||
self.window = self.glade.get_widget('graph_tab')
|
||||
self._child_widget = self.window
|
||||
self.notebook = self.glade.get_widget('graph_notebook')
|
||||
self.label = self.glade.get_widget('graph_label')
|
||||
self._tab_label = self.label
|
||||
self.bandwidth_graph = self.glade.get_widget('bandwidth_graph')
|
||||
self.bandwidth_graph.connect('expose_event', self.expose)
|
||||
self.window.unparent()
|
||||
self.label.unparent()
|
||||
|
||||
self.graph_widget = self.bandwidth_graph
|
||||
self.graph = graph.Graph()
|
||||
self.graph.add_stat('payload_download_rate', label='Download Rate', color=graph.green)
|
||||
self.graph.add_stat('payload_upload_rate', label='Upload Rate', color=graph.blue)
|
||||
self.graph.set_left_axis(formatter=fspeed, min=10240)
|
||||
|
||||
def expose(self, widget, event):
|
||||
"""Redraw"""
|
||||
context = self.graph_widget.window.cairo_create()
|
||||
# set a clip region
|
||||
context.rectangle(event.area.x, event.area.y,
|
||||
event.area.width, event.area.height)
|
||||
context.clip()
|
||||
|
||||
width, height = self.graph_widget.allocation.width, self.graph_widget.allocation.height
|
||||
self.graph.draw_to_context(context, width, height)
|
||||
#Do not propagate the event
|
||||
return False
|
||||
|
||||
def update(self):
|
||||
log.debug("getstat keys: %s", self.graph.stat_info.keys())
|
||||
d1 = client.stats.get_stats(self.graph.stat_info.keys())
|
||||
d1.addCallback(self.graph.set_stats)
|
||||
d2 = client.stats.get_config()
|
||||
d2.addCallback(self.graph.set_config)
|
||||
dl = defer.DeferredList([d1, d2])
|
||||
|
||||
def _on_update(result):
|
||||
width, height = self.graph_widget.allocation.width, self.graph_widget.allocation.height
|
||||
rect = gtk.gdk.Rectangle(0, 0, width, height)
|
||||
self.graph_widget.window.invalidate_rect(rect, True)
|
||||
|
||||
dl.addCallback(_on_update)
|
||||
|
||||
def clear(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class GtkUI(GtkPluginBase):
|
||||
def enable(self):
|
||||
log.debug("Stats plugin enable called")
|
||||
self.glade = XML(self.get_resource("config.glade"))
|
||||
component.get("Preferences").add_page("Stats", self.glade.get_widget("prefs_box"))
|
||||
component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs)
|
||||
component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs)
|
||||
self.on_show_prefs()
|
||||
|
||||
self.graphs_tab = GraphsTab(XML(self.get_resource("tabs.glade")))
|
||||
self.torrent_details = component.get('TorrentDetails')
|
||||
self.torrent_details.add_tab(self.graphs_tab)
|
||||
|
||||
def disable(self):
|
||||
component.get("Preferences").remove_page("Stats")
|
||||
component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs)
|
||||
component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs)
|
||||
self.torrent_details.remove_tab(self.graphs_tab.get_name())
|
||||
|
||||
def on_apply_prefs(self):
|
||||
log.debug("applying prefs for Stats")
|
||||
config = {
|
||||
"test":self.glade.get_widget("txt_test").get_text()
|
||||
}
|
||||
client.stats.set_config(config)
|
||||
|
||||
def on_show_prefs(self):
|
||||
client.stats.get_config().addCallback(self.cb_get_config)
|
||||
|
||||
def cb_get_config(self, config):
|
||||
"callback for on show_prefs"
|
||||
self.glade.get_widget("txt_test").set_text(config["test"])
|
||||
|
||||
def get_resource(self, filename):
|
||||
import pkg_resources, os
|
||||
return pkg_resources.resource_filename("stats", os.path.join("data", filename))
|
@ -1,9 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="2" />
|
||||
</head>
|
||||
<body>
|
||||
<img src="output_async.png" /> <br />
|
||||
<img src="output_dht.png" />
|
||||
</body>
|
||||
</html>
|
@ -1,78 +0,0 @@
|
||||
from deluge.ui.client import sclient, aclient
|
||||
sclient.set_core_uri()
|
||||
import graph
|
||||
import deluge
|
||||
|
||||
def test_sync():
|
||||
if 1:
|
||||
upload = sclient.graph_get_upload()
|
||||
download = sclient.graph_get_download()
|
||||
print upload
|
||||
print download
|
||||
else:
|
||||
upload = [66804, 66915, 66974, 67447, 67540, 67318, 67320, 67249, 66659, 66489, 67027, 66914, 66802, 67303, 67654, 67643, 67763, 67528, 67523, 67431, 67214, 66939, 67316, 67020, 66881, 67103, 67377, 67141, 67366, 67492, 67375, 67203, 67056, 67010, 67029, 66741, 66695, 66868, 66805, 66264, 66249, 66317, 66459, 66306, 66681, 66954, 66662, 66278, 65921, 65695, 65681, 65942, 66000, 66140, 66424, 66480, 66257, 66271, 66145, 65854, 65568, 65268, 65112, 65050, 65027, 64676, 64655, 64178, 64386, 63979, 63271, 62746, 62337, 62297, 62496, 62902, 63801, 64121, 62957, 62921, 63051, 62644, 63240, 64107, 63968, 63987, 63644, 63263, 63153, 62999, 62843, 62777, 63101, 63078, 63178, 63126, 63401, 62630, 62451, 62505, 62254, 61485, 61264, 60937, 60568, 61011, 61109, 60325, 60196, 59640, 59619, 59514, 60813, 60572, 61632, 61689, 63365, 64583, 66396, 67179, 68209, 68295, 67674, 67559, 67195, 66178, 65632, 66124, 66456, 66676, 67183, 67620, 66960, 66347, 65925, 65907, 65896, 66738, 66703, 67060, 67004, 67007, 66329, 65304, 52002, 38969, 25433, 12426, 0, 0]
|
||||
download = [42926, 43853, 43157, 45470, 44254, 46272, 45083, 47344, 46716, 51963, 50112, 52334, 55525, 57545, 53691, 51637, 49574, 49836, 48295, 49843, 52878, 56014, 56966, 56938, 60065, 60461, 56542, 59526, 58678, 54424, 51862, 55109, 52132, 53783, 51687, 56567, 52182, 50758, 46714, 50511, 48161, 50920, 48694, 50528, 55074, 55420, 55882, 59268, 59958, 57938, 57115, 51424, 51180, 53184, 52879, 51177, 54417, 51097, 47901, 49870, 55865, 61118, 61476, 63498, 58878, 49630, 45975, 45632, 45892, 44855, 49495, 48304, 45829, 42152, 39403, 37574, 32384, 34933, 34901, 33492, 31953, 36271, 33826, 34515, 36408, 41106, 43054, 44110, 40810, 41383, 37267, 35881, 38660, 37525, 34857, 36718, 36842, 34281, 39528, 41854, 42952, 40021, 41722, 41045, 42917, 39287, 38672, 32824, 28765, 22686, 18490, 15714, 15268, 14793, 15305, 16354, 16720, 17502, 17857, 16622, 18447, 19929, 31138, 36965, 36158, 32795, 30445, 21997, 18100, 22491, 27227, 29317, 32436, 35700, 39140, 36258, 33697, 24751, 20354, 8211, 3836, 1560, 834, 2034, 1744, 1637, 1637, 1637, 0, 0]
|
||||
|
||||
from graph import NetworkGraph
|
||||
n = NetworkGraph()
|
||||
n.savedUpSpeeds = upload
|
||||
n.savedDownSpeeds = download
|
||||
|
||||
n.draw(800,200)
|
||||
n.surface.write_to_png('output_sync.png')
|
||||
|
||||
def test_async():
|
||||
g = graph.Graph()
|
||||
g.add_stat('download_rate', color=graph.green)
|
||||
g.add_stat('upload_rate', color=graph.blue)
|
||||
g.set_left_axis(formatter=deluge.common.fspeed, min=10240)
|
||||
g.async_request()
|
||||
aclient.force_call(True)
|
||||
surface = g.draw(600, 300)
|
||||
surface.write_to_png('output_async.png')
|
||||
|
||||
def test_dht():
|
||||
"""'boring graph, but testing if it works'"""
|
||||
|
||||
g = graph.Graph()
|
||||
g.add_stat('dht_nodes', color=graph.orange)
|
||||
g.add_stat('dht_cache_nodes', color=graph.blue)
|
||||
g.add_stat('dht_torrents', color=graph.green)
|
||||
g.add_stat('num_connections', color=graph.darkred) #testing : non dht
|
||||
g.set_left_axis(formatter=str, min=10)
|
||||
g.async_request()
|
||||
aclient.force_call(True)
|
||||
surface = g.draw(600, 300)
|
||||
surface.write_to_png('output_dht.png')
|
||||
|
||||
|
||||
def test_write():
|
||||
"""
|
||||
writing to a file-like object; need this for webui.
|
||||
"""
|
||||
class fake_file:
|
||||
def __init__(self):
|
||||
self.data = []
|
||||
def write(self, str):
|
||||
self.data.append(str)
|
||||
|
||||
g = graph.Graph()
|
||||
g.add_stat('download_rate', color=graph.green)
|
||||
g.add_stat('upload_rate', color=graph.blue)
|
||||
g.set_left_axis(formatter=deluge.common.fspeed, min=10240)
|
||||
g.async_request()
|
||||
aclient.force_call(True)
|
||||
surface = g.draw(900, 150)
|
||||
|
||||
file_like = fake_file()
|
||||
surface.write_to_png(file_like)
|
||||
data = "".join(file_like.data)
|
||||
|
||||
f = open("file_like.png","wb")
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
#test_sync()
|
||||
test_async()
|
||||
test_dht()
|
||||
#test_write()
|
@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
while true; do
|
||||
python test.py
|
||||
sleep 2
|
||||
done;
|
@ -1,23 +0,0 @@
|
||||
from deluge.ui.client import sclient, aclient
|
||||
from deluge.common import fsize
|
||||
sclient.set_core_uri()
|
||||
|
||||
def print_totals(totals):
|
||||
for name, value in totals.iteritems():
|
||||
print name , fsize(value)
|
||||
|
||||
print "overhead:"
|
||||
print "up:", fsize(totals["total_upload"] - totals["total_payload_upload"] )
|
||||
print "down:", fsize(totals["total_download"] - totals["total_payload_download"] )
|
||||
|
||||
|
||||
print "==totals=="
|
||||
print_totals(sclient.stats_get_totals())
|
||||
|
||||
print "==session totals=="
|
||||
print_totals(sclient.stats_get_session_totals())
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,54 +0,0 @@
|
||||
#
|
||||
# webui.py
|
||||
#
|
||||
# Copyright (C) 2009 Damien Churchill <mvoncken@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link the code of portions of this program with the OpenSSL
|
||||
# library.
|
||||
# You must obey the GNU General Public License in all respects for all of
|
||||
# the code used other than OpenSSL. If you modify file(s) with this
|
||||
# exception, you may extend this exception to your version of the file(s),
|
||||
# but you are not obligated to do so. If you do not wish to do so, delete
|
||||
# this exception statement from your version. If you delete this exception
|
||||
# statement from all source files in the program, then also delete it here.
|
||||
#
|
||||
#
|
||||
|
||||
from deluge.log import LOG as log
|
||||
from deluge.ui.client import client
|
||||
from deluge import component
|
||||
from deluge.plugins.pluginbase import WebPluginBase
|
||||
|
||||
from common import get_resource
|
||||
|
||||
class WebUI(WebPluginBase):
|
||||
|
||||
scripts = [get_resource("stats.js")]
|
||||
|
||||
# The enable and disable methods are not scrictly required on the WebUI
|
||||
# plugins. They are only here if you need to register images/stylesheets
|
||||
# with the webserver.
|
||||
def enable(self):
|
||||
log.debug("Stats Web plugin enabled!")
|
||||
|
||||
def disable(self):
|
||||
log.debug("Stats Web plugin disabled!")
|
@ -35,7 +35,11 @@
|
||||
|
||||
from twisted.internet.protocol import Protocol, ClientFactory
|
||||
from twisted.internet import reactor, ssl, defer
|
||||
import deluge.rencode as rencode
|
||||
try:
|
||||
import rencode
|
||||
except ImportError:
|
||||
import deluge.rencode as rencode
|
||||
|
||||
import zlib
|
||||
|
||||
import deluge.common
|
||||
@ -430,7 +434,7 @@ class DaemonClassicProxy(DaemonProxy):
|
||||
self.connected = True
|
||||
self.host = "localhost"
|
||||
self.port = 58846
|
||||
self.user = "localclient"
|
||||
self.username = "localclient"
|
||||
# Register the event handlers
|
||||
for event in event_handlers:
|
||||
for handler in event_handlers[event]:
|
||||
|
@ -126,7 +126,8 @@ def get_line_length(line, encoding="UTF-8"):
|
||||
if line.count("{!") != line.count("!}"):
|
||||
raise BadColorString("Number of {! is not equal to number of !}")
|
||||
|
||||
line = line.encode(encoding, "replace")
|
||||
if isinstance(line, unicode):
|
||||
line = line.encode(encoding, "replace")
|
||||
|
||||
# Remove all the color tags
|
||||
line = strip_colors(line)
|
||||
@ -146,7 +147,8 @@ def parse_color_string(s, encoding="UTF-8"):
|
||||
if s.count("{!") != s.count("!}"):
|
||||
raise BadColorString("Number of {! is not equal to number of !}")
|
||||
|
||||
s = s.encode(encoding, "replace")
|
||||
if isinstance(s, unicode):
|
||||
s = s.encode(encoding, "replace")
|
||||
|
||||
ret = []
|
||||
# Keep track of where the strings
|
||||
|
@ -63,6 +63,9 @@ class Command(BaseCommand):
|
||||
# Keep a list of deferreds to make a DeferredList
|
||||
deferreds = []
|
||||
for arg in args:
|
||||
if not os.path.exists(arg):
|
||||
self.console.write("{!error!}%s doesn't exist!" % arg)
|
||||
continue
|
||||
if not os.path.isfile(arg):
|
||||
self.console.write("{!error!}This is a directory!")
|
||||
continue
|
||||
|
@ -109,6 +109,8 @@ class BaseCommand(object):
|
||||
return self.__doc__
|
||||
|
||||
def split(self, text):
|
||||
if deluge.common.windows_check():
|
||||
text = text.replace('\\', '\\\\')
|
||||
return shlex.split(text)
|
||||
|
||||
def create_parser(self):
|
||||
|
@ -308,7 +308,7 @@ class Screen(CursesStdIO):
|
||||
if c == curses.KEY_ENTER or c == 10:
|
||||
if self.input:
|
||||
self.add_line(">>> " + self.input)
|
||||
self.command_parser(self.input)
|
||||
self.command_parser(self.input.encode(self.encoding))
|
||||
if len(self.input_history) == INPUT_HISTORY_SIZE:
|
||||
# Remove the oldest input history if the max history size
|
||||
# is reached.
|
||||
@ -404,21 +404,13 @@ class Screen(CursesStdIO):
|
||||
if c > 31 and c < 256:
|
||||
# Emulate getwch
|
||||
stroke = chr(c)
|
||||
|
||||
uchar = None
|
||||
|
||||
while 1:
|
||||
uchar = ""
|
||||
while not uchar:
|
||||
try:
|
||||
uchar = stroke.decode(self.encoding)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
|
||||
c = self.stdscr.getch()
|
||||
|
||||
if c == -1:
|
||||
break
|
||||
|
||||
stroke += chr(c)
|
||||
c = self.stdscr.getch()
|
||||
stroke += chr(c)
|
||||
|
||||
if uchar:
|
||||
if self.input_cursor == len(self.input):
|
||||
@ -426,7 +418,7 @@ class Screen(CursesStdIO):
|
||||
else:
|
||||
# Insert into string
|
||||
self.input = self.input[:self.input_cursor] + uchar + self.input[self.input_cursor:]
|
||||
|
||||
|
||||
# Move the cursor forward
|
||||
self.input_cursor += 1
|
||||
|
||||
|
@ -214,7 +214,7 @@ class CreateTorrentDialog:
|
||||
client.core.get_path_size(result).addCallback(_on_get_path_size)
|
||||
client.force_call(True)
|
||||
|
||||
dialog.destroy()
|
||||
dialog.hide()
|
||||
|
||||
def _on_button_cancel_clicked(self, widget):
|
||||
log.debug("_on_button_cancel_clicked")
|
||||
@ -228,11 +228,14 @@ class CreateTorrentDialog:
|
||||
is_remote = self.files_treestore[0][1] == gtk.STOCK_NETWORK
|
||||
if is_remote:
|
||||
# This is a remote path
|
||||
response = self.glade.get_widget("remote_save_dialog").run()
|
||||
dialog = self.glade.get_widget("remote_save_dialog")
|
||||
response = dialog.run()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
result = self.glade.get_widget("entry_save_path").get_text()
|
||||
else:
|
||||
dialog.hide()
|
||||
return
|
||||
dialog.hide()
|
||||
else:
|
||||
# Setup the filechooserdialog
|
||||
chooser = gtk.FileChooserDialog(_("Save .torrent file"),
|
||||
@ -344,7 +347,6 @@ class CreateTorrentDialog:
|
||||
trackers,
|
||||
add_to_session).addCallback(hide_progress)
|
||||
|
||||
chooser.destroy()
|
||||
self.dialog.destroy()
|
||||
|
||||
def create_torrent(self, path, tracker, piece_length, progress, comment, target,
|
||||
|
@ -210,7 +210,7 @@ class FilterTreeView(component.Component):
|
||||
row = self.treestore.append(self.cat_nodes[cat],[cat, value, label, count , pix, True])
|
||||
self.filters[(cat, value)] = row
|
||||
|
||||
if cat == "tracker_host" and value not in ("All", "Error"):
|
||||
if cat == "tracker_host" and value not in ("All", "Error") and value:
|
||||
d = self.tracker_icons.get(value)
|
||||
d.addCallback(on_get_icon)
|
||||
|
||||
|
@ -252,12 +252,6 @@ class GtkUI(object):
|
||||
# Shutdown all components
|
||||
component.shutdown()
|
||||
|
||||
if self.started_in_classic:
|
||||
try:
|
||||
client.daemon.shutdown()
|
||||
except:
|
||||
pass
|
||||
|
||||
# Make sure the config is saved.
|
||||
self.config.save()
|
||||
|
||||
|
@ -38,7 +38,11 @@ import sys
|
||||
import os
|
||||
import base64
|
||||
|
||||
import deluge.rencode
|
||||
try:
|
||||
import rencode
|
||||
except ImportError:
|
||||
import deluge.rencode as rencode
|
||||
|
||||
import deluge.component as component
|
||||
from deluge.ui.client import client
|
||||
import deluge.common
|
||||
@ -51,12 +55,12 @@ import twisted.internet.error
|
||||
|
||||
class IPCProtocolServer(Protocol):
|
||||
def dataReceived(self, data):
|
||||
data = deluge.rencode.loads(data)
|
||||
data = rencode.loads(data)
|
||||
process_args(data)
|
||||
|
||||
class IPCProtocolClient(Protocol):
|
||||
def connectionMade(self):
|
||||
self.transport.write(deluge.rencode.dumps(self.factory.args))
|
||||
self.transport.write(rencode.dumps(self.factory.args))
|
||||
self.transport.loseConnection()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
|
@ -153,7 +153,10 @@ class MainWindow(component.Component):
|
||||
return self.main_glade
|
||||
|
||||
def quit(self):
|
||||
reactor.stop()
|
||||
if client.is_classicmode():
|
||||
gtk.main_quit()
|
||||
else:
|
||||
reactor.stop()
|
||||
|
||||
def load_window_state(self):
|
||||
x = self.config["window_x_pos"]
|
||||
|
@ -260,8 +260,6 @@ class MenuBar(component.Component):
|
||||
|
||||
def on_menuitem_quit_activate(self, data=None):
|
||||
log.debug("on_menuitem_quit_activate")
|
||||
if self.config["classic_mode"] and client.is_classicmode():
|
||||
client.daemon.shutdown()
|
||||
self.window.quit()
|
||||
|
||||
## Edit Menu ##
|
||||
|
@ -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][key] > 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, {}]
|
||||
def on_status(status):
|
||||
self.torrents[torrent_id][1].update(status)
|
||||
self.cache_times[torrent_id] = {}
|
||||
t = time.time()
|
||||
for key in status:
|
||||
self.cache_times[torrent_id][key] = t
|
||||
client.core.get_torrent_status(torrent_id, []).addCallback(on_status)
|
||||
|
||||
def on_torrent_removed(self, torrent_id):
|
||||
del self.torrents[torrent_id]
|
||||
del self.cache_times[torrent_id]
|
||||
|
@ -34,7 +34,7 @@
|
||||
#
|
||||
|
||||
import os
|
||||
from HTMLParser import HTMLParser
|
||||
from HTMLParser import HTMLParser, HTMLParseError
|
||||
from urlparse import urljoin, urlparse
|
||||
from tempfile import mkstemp
|
||||
|
||||
@ -121,37 +121,39 @@ class TrackerIcons(Component):
|
||||
"""
|
||||
A TrackerIcon factory class
|
||||
"""
|
||||
def __init__(self, dir=None, noIcon=None):
|
||||
def __init__(self, icon_dir=None, no_icon=None):
|
||||
"""
|
||||
Initialises a new TrackerIcons object
|
||||
|
||||
:param dir: the (optional) directory of where to store the icons
|
||||
:type dir: string
|
||||
:param noIcon: the (optional) path name of the icon to show when no icon
|
||||
:param icon_dir: the (optional) directory of where to store the icons
|
||||
:type icon_dir: string
|
||||
:param no_icon: the (optional) path name of the icon to show when no icon
|
||||
can be fetched
|
||||
:type noIcon: string
|
||||
:type no_icon: string
|
||||
"""
|
||||
Component.__init__(self, "TrackerIcons")
|
||||
if not dir:
|
||||
dir = get_config_dir("icons")
|
||||
self.dir = dir
|
||||
if not icon_dir:
|
||||
icon_dir = get_config_dir("icons")
|
||||
self.dir = icon_dir
|
||||
if not os.path.isdir(self.dir):
|
||||
os.makedirs(self.dir)
|
||||
|
||||
self.icons = {}
|
||||
for icon in os.listdir(self.dir):
|
||||
if icon != noIcon:
|
||||
if icon != no_icon:
|
||||
host = icon_name_to_host(icon)
|
||||
try:
|
||||
self.icons[host] = TrackerIcon(os.path.join(self.dir, icon))
|
||||
except KeyError:
|
||||
log.warning("invalid icon %s", icon)
|
||||
if noIcon:
|
||||
self.icons[None] = TrackerIcon(noIcon)
|
||||
if no_icon:
|
||||
self.icons[None] = TrackerIcon(no_icon)
|
||||
else:
|
||||
self.icons[None] = None
|
||||
self.icons[''] = self.icons[None]
|
||||
|
||||
self.pending = {}
|
||||
self.redirects = {}
|
||||
|
||||
def get(self, host):
|
||||
"""
|
||||
@ -202,7 +204,7 @@ class TrackerIcons(Component):
|
||||
:rtype: Deferred
|
||||
"""
|
||||
if not url:
|
||||
url = host_to_url(host)
|
||||
url = self.host_to_url(host)
|
||||
log.debug("Downloading %s", url)
|
||||
return download_file(url, mkstemp()[1], force_filename=True)
|
||||
|
||||
@ -235,7 +237,8 @@ class TrackerIcons(Component):
|
||||
d = f
|
||||
if f.check(error.PageRedirect):
|
||||
# Handle redirect errors
|
||||
location = urljoin(host_to_url(host), error_msg.split(" to ")[1])
|
||||
location = urljoin(self.host_to_url(host), error_msg.split(" to ")[1])
|
||||
self.redirects[host] = url_to_host(location)
|
||||
d = self.download_page(host, url=location)
|
||||
d.addCallbacks(self.on_download_page_complete, self.on_download_page_fail,
|
||||
errbackArgs=(host,))
|
||||
@ -260,7 +263,10 @@ class TrackerIcons(Component):
|
||||
break
|
||||
parser.close()
|
||||
f.close()
|
||||
os.remove(page)
|
||||
try:
|
||||
os.remove(page)
|
||||
except Exception, e:
|
||||
log.warning("Couldn't remove temp file: %s", e)
|
||||
return parser.get_icons()
|
||||
|
||||
def on_parse_complete(self, icons, host):
|
||||
@ -275,7 +281,7 @@ class TrackerIcons(Component):
|
||||
:rtype: list
|
||||
"""
|
||||
log.debug("Got icons for %s: %s", host, icons)
|
||||
url = host_to_url(host)
|
||||
url = self.host_to_url(host)
|
||||
icons = [(urljoin(url, icon), mimetype) for icon, mimetype in icons]
|
||||
log.debug("Icon urls: %s", icons)
|
||||
return icons
|
||||
@ -304,13 +310,39 @@ class TrackerIcons(Component):
|
||||
:returns: a Deferred which fires with the downloaded icon's filename
|
||||
:rtype: Deferred
|
||||
"""
|
||||
if len(icons) == 0:
|
||||
raise NoIconsError, "empty icons list"
|
||||
(url, mimetype) = icons.pop(0)
|
||||
d = download_file(url, os.path.join(self.dir, host_to_icon_name(host, mimetype)),
|
||||
force_filename=True)
|
||||
d.addCallback(self.check_icon_is_valid)
|
||||
if icons:
|
||||
d.addErrback(self.on_download_icon_fail, host, icons)
|
||||
return d
|
||||
|
||||
@proxy(threads.deferToThread)
|
||||
def check_icon_is_valid(self, icon_name):
|
||||
"""
|
||||
Performs a sanity check on icon_name
|
||||
|
||||
:param icon_name: the name of the icon to check
|
||||
:type icon_name: string
|
||||
:returns: the name of the validated icon
|
||||
:rtype: string
|
||||
:raises: InvalidIconError
|
||||
"""
|
||||
|
||||
if PIL_INSTALLED:
|
||||
try:
|
||||
Image.open(icon_name)
|
||||
except IOError, e:
|
||||
raise InvalidIconError(e)
|
||||
else:
|
||||
if os.stat(icon_name).st_size == 0L:
|
||||
raise InvalidIconError, "empty icon"
|
||||
|
||||
return icon_name
|
||||
|
||||
def on_download_icon_complete(self, icon_name, host):
|
||||
"""
|
||||
Runs any download cleanup functions
|
||||
@ -345,16 +377,16 @@ class TrackerIcons(Component):
|
||||
d = f
|
||||
if f.check(error.PageRedirect):
|
||||
# Handle redirect errors
|
||||
location = urljoin(host_to_url(host), error_msg.split(" to ")[1])
|
||||
location = urljoin(self.host_to_url(host), error_msg.split(" to ")[1])
|
||||
d = self.download_icon([(location, extension_to_mimetype(location.rpartition('.')[2]))] + icons, host)
|
||||
if not icons:
|
||||
d.addCallbacks(self.on_download_icon_complete, self.on_download_icon_fail,
|
||||
callbackArgs=(host,), errbackArgs=(host,))
|
||||
elif f.check(error.NoResource, error.ForbiddenResource) and icons:
|
||||
d = self.download_icon(icons, host)
|
||||
elif f.check(IndexError):
|
||||
elif f.check(NoIconsError, HTMLParseError):
|
||||
# No icons, try favicon.ico as an act of desperation
|
||||
d = self.download_icon([(urljoin(host_to_url(host), "favicon.ico"), extension_to_mimetype("ico"))], host)
|
||||
d = self.download_icon([(urljoin(self.host_to_url(host), "favicon.ico"), extension_to_mimetype("ico"))], host)
|
||||
d.addCallbacks(self.on_download_icon_complete, self.on_download_icon_fail,
|
||||
callbackArgs=(host,), errbackArgs=(host,))
|
||||
else:
|
||||
@ -404,6 +436,19 @@ class TrackerIcons(Component):
|
||||
del self.pending[host]
|
||||
return icon
|
||||
|
||||
def host_to_url(self, host):
|
||||
"""
|
||||
Given a host, returns the URL to fetch
|
||||
|
||||
:param host: the tracker host
|
||||
:type host: string
|
||||
:returns: the url of the tracker
|
||||
:rtype: string
|
||||
"""
|
||||
if host in self.redirects:
|
||||
host = self.redirects[host]
|
||||
return "http://%s/" % host
|
||||
|
||||
################################ HELPER CLASSES ###############################
|
||||
|
||||
class FaviconParser(HTMLParser):
|
||||
@ -450,17 +495,6 @@ class FaviconParser(HTMLParser):
|
||||
|
||||
############################### HELPER FUNCTIONS ##############################
|
||||
|
||||
def host_to_url(host):
|
||||
"""
|
||||
Given a host, returns the URL to fetch
|
||||
|
||||
:param host: the tracker host
|
||||
:type host: string
|
||||
:returns: the url of the tracker
|
||||
:rtype: string
|
||||
"""
|
||||
return "http://%s/" % host
|
||||
|
||||
def url_to_host(url):
|
||||
"""
|
||||
Given a URL, returns the host it belongs to
|
||||
@ -532,3 +566,11 @@ def extension_to_mimetype(extension):
|
||||
:raises KeyError: if given an invalid filename extension
|
||||
"""
|
||||
return MIME_MAP[extension.lower()]
|
||||
|
||||
################################## EXCEPTIONS #################################
|
||||
|
||||
class NoIconsError(Exception):
|
||||
pass
|
||||
|
||||
class InvalidIconError(Exception):
|
||||
pass
|
||||
|
@ -1,3 +1,20 @@
|
||||
Icons in this folder are copied from the fruge icon set, taken from
|
||||
http://www.pinvoke.com, licensed under the Creative Common
|
||||
Attribution-ShareAlike 3.0 License.
|
||||
|
||||
Exceptions
|
||||
==========
|
||||
apple-pre-57.png
|
||||
apple-pre-72.png
|
||||
apple-pre-114.png
|
||||
|
||||
active.png
|
||||
alert.png
|
||||
all.png
|
||||
deluge.png
|
||||
dht.png
|
||||
downloading.png
|
||||
inactive.png
|
||||
queued.png
|
||||
seeding.png
|
||||
traffic.png
|
||||
|
BIN
deluge/ui/web/icons/apple-pre-114.png
Normal file
BIN
deluge/ui/web/icons/apple-pre-114.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
deluge/ui/web/icons/apple-pre-57.png
Normal file
BIN
deluge/ui/web/icons/apple-pre-57.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
BIN
deluge/ui/web/icons/apple-pre-72.png
Normal file
BIN
deluge/ui/web/icons/apple-pre-72.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
@ -1,37 +1,40 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Deluge: Web UI ${version}</title>
|
||||
|
||||
<link rel="shortcut icon" href="${base}icons/deluge.png" type="image/png" />
|
||||
<link rel="icon" href="${base}icons/deluge.png" type="image/png" />
|
||||
|
||||
<!-- Stylesheets -->
|
||||
% for stylesheet in stylesheets:
|
||||
<link rel="stylesheet" type="text/css" href="${base}${stylesheet}" />
|
||||
% endfor
|
||||
<head>
|
||||
<title>Deluge: Web UI ${version}</title>
|
||||
|
||||
<link rel="shortcut icon" href="${base}icons/deluge.png" type="image/png" />
|
||||
<link rel="icon" href="${base}icons/deluge.png" type="image/png" />
|
||||
<link rel="apple-touch-icon-precomposed" media="screen and (resolution: 163dpi)" href="${base}icons/apple-pre-57.png" />
|
||||
<link rel="apple-touch-icon-precomposed" media="screen and (resolution: 132dpi)" href="${base}icons/apple-pre-72.png" />
|
||||
<link rel="apple-touch-icon-precomposed" media="screen and (resolution: 326dpi)" href="${base}icons/apple-pre-114.png" />
|
||||
|
||||
<!-- Stylesheets -->
|
||||
% for stylesheet in stylesheets:
|
||||
<link rel="stylesheet" type="text/css" href="${base}${stylesheet}" />
|
||||
% endfor
|
||||
|
||||
<script type="text/javascript">
|
||||
<script type="text/javascript">
|
||||
deluge = {
|
||||
author: 'Damien Churchill <damoxc@gmail.com>',
|
||||
version: '${version}',
|
||||
config: ${js_config}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<!-- Javascript -->
|
||||
% for script in scripts:
|
||||
<script type="text/javascript" src="${base}${script}"></script>
|
||||
% endfor
|
||||
<script type="text/javascript">
|
||||
Deluge.debug = ${str(debug).lower()};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-image: url('${base}themes/default/tree/loading.gif');"></div>
|
||||
<!-- Javascript -->
|
||||
% for script in scripts:
|
||||
<script type="text/javascript" src="${base}${script}"></script>
|
||||
% endfor
|
||||
<script type="text/javascript">
|
||||
Deluge.debug = ${str(debug).lower()};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-image: url('${base}themes/default/tree/loading.gif');"></div>
|
||||
|
||||
<!-- Preload icon classes -->
|
||||
<div class="ext-mb-error"></div>
|
||||
<div class="icon-ok"></div>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,45 +1,59 @@
|
||||
#!/bin/sh
|
||||
|
||||
add_file "data/SortTypes.js"
|
||||
add_file "data/PeerRecord.js"
|
||||
add_file "data/TorrentRecord.js"
|
||||
add_file "details/DetailsPanel.js"
|
||||
add_file "details/DetailsTab.js"
|
||||
add_file "details/FilesTab.js"
|
||||
add_file "details/OptionsTab.js"
|
||||
add_file "details/PeersTab.js"
|
||||
add_file "details/StatusTab.js"
|
||||
add_file "add/Window.js"
|
||||
add_file "add/AddWindow.js"
|
||||
add_file "add/FileWindow.js"
|
||||
add_file "add/FilesTab.js"
|
||||
add_file "add/Infohash.js"
|
||||
add_file "add/OptionsPanel.js"
|
||||
add_file "add/OptionsTab.js"
|
||||
add_file "add/UrlWindow.js"
|
||||
add_file "preferences/BandwidthPage.js"
|
||||
add_file "preferences/CachePage.js"
|
||||
add_file "preferences/DaemonPage.js"
|
||||
add_file "preferences/DownloadsPage.js"
|
||||
add_file "preferences/EncryptionPage.js"
|
||||
add_file "preferences/InstallPluginWindow.js"
|
||||
add_file "preferences/InterfacePage.js"
|
||||
add_file "preferences/NetworkPage.js"
|
||||
add_file "preferences/OtherPage.js"
|
||||
add_file "preferences/PluginsPage.js"
|
||||
add_file "preferences/PreferencesWindow.js"
|
||||
add_file "preferences/ProxyField.js"
|
||||
add_file "preferences/ProxyPage.js"
|
||||
add_file "preferences/QueuePage.js"
|
||||
add_file "StatusbarMenu.js"
|
||||
add_file "OptionsManager.js"
|
||||
add_file "AddConnectionWindow.js"
|
||||
add_file "AddTrackerWindow.js"
|
||||
add_file "Client.js"
|
||||
add_file "ConnectionManager.js"
|
||||
add_file "Deluge.js"
|
||||
add_file "Deluge.Formatters.js"
|
||||
add_file "Deluge.Keys.js"
|
||||
add_file "Deluge.Menus.js"
|
||||
add_file "Deluge.data.SortTypes.js"
|
||||
add_file "Deluge.EventsManager.js"
|
||||
add_file "Deluge.OptionsManager.js"
|
||||
add_file "Deluge.MultiOptionsManager.js"
|
||||
add_file "Deluge.Add.js"
|
||||
add_file "Deluge.Add.File.js"
|
||||
add_file "Deluge.Add.Url.js"
|
||||
add_file "Deluge.Client.js"
|
||||
add_file "Deluge.ConnectionManager.js"
|
||||
add_file "Deluge.Details.js"
|
||||
add_file "Deluge.Details.Status.js"
|
||||
add_file "Deluge.Details.Details.js"
|
||||
add_file "Deluge.Details.Files.js"
|
||||
add_file "Deluge.Details.Peers.js"
|
||||
add_file "Deluge.Details.Options.js"
|
||||
add_file "Deluge.EditTrackers.js"
|
||||
add_file "Deluge.FileBrowser.js"
|
||||
add_file "Deluge.Login.js"
|
||||
add_file "Deluge.MoveStorage.js"
|
||||
add_file "Deluge.Plugin.js"
|
||||
add_file "Deluge.Preferences.js"
|
||||
add_file "Deluge.Preferences.Downloads.js"
|
||||
add_file "Deluge.Preferences.Network.js"
|
||||
add_file "Deluge.Preferences.Encryption.js"
|
||||
add_file "Deluge.Preferences.Bandwidth.js"
|
||||
add_file "Deluge.Preferences.Interface.js"
|
||||
add_file "Deluge.Preferences.Other.js"
|
||||
add_file "Deluge.Preferences.Daemon.js"
|
||||
add_file "Deluge.Preferences.Queue.js"
|
||||
add_file "Deluge.Preferences.Proxy.js"
|
||||
add_file "Deluge.Preferences.Cache.js"
|
||||
add_file "Deluge.Preferences.Plugins.js"
|
||||
add_file "Deluge.Remove.js"
|
||||
add_file "Deluge.Sidebar.js"
|
||||
add_file "Deluge.Statusbar.js"
|
||||
add_file "Deluge.Toolbar.js"
|
||||
add_file "Deluge.Torrent.js"
|
||||
add_file "Deluge.Torrents.js"
|
||||
add_file "Deluge.UI.js"
|
||||
add_file "EditTrackerWindow.js"
|
||||
add_file "EditTrackersWindow.js"
|
||||
add_file "EventsManager.js"
|
||||
add_file "FileBrowser.js"
|
||||
add_file "FilterPanel.js"
|
||||
add_file "Formatters.js"
|
||||
add_file "Keys.js"
|
||||
add_file "LoginWindow.js"
|
||||
add_file "Menus.js"
|
||||
add_file "MoveStorage.js"
|
||||
add_file "MultiOptionsManager.js"
|
||||
add_file "OtherLimitWindow.js"
|
||||
add_file "Plugin.js"
|
||||
add_file "RemoveWindow.js"
|
||||
add_file "Sidebar.js"
|
||||
add_file "Statusbar.js"
|
||||
add_file "Toolbar.js"
|
||||
add_file "TorrentGrid.js"
|
||||
add_file "UI.js"
|
||||
|
@ -84,12 +84,12 @@ Deluge.Formatters = {
|
||||
/**
|
||||
* Formats a string to display a transfer speed utilizing {@link #size}
|
||||
*
|
||||
* @param {Number} bits the number of bits per second
|
||||
* @param {Number} bytes the number of bytes per second
|
||||
* @param {Boolean} showZero pass in true to displays 0 values
|
||||
* @return {String} formatted string with KiB, MiB or GiB units.
|
||||
*/
|
||||
speed: function(bits, showZero) {
|
||||
return (!bits && !showZero) ? '' : fsize(bits, showZero) + '/s';
|
||||
speed: function(bytes, showZero) {
|
||||
return (!bytes && !showZero) ? '' : fsize(bytes, showZero) + '/s';
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -107,7 +107,7 @@ Deluge.Sidebar = Ext.extend(Ext.Panel, {
|
||||
// Grab the filters from each of the filter panels
|
||||
this.items.each(function(panel) {
|
||||
var state = panel.getState();
|
||||
if (!state == null) return;
|
||||
if (state == null) return;
|
||||
states[panel.filterType] = state;
|
||||
}, this);
|
||||
} else {
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,16 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
add_file "BufferView.js"
|
||||
add_file "FileUploadField.js"
|
||||
add_file "layout/FormLayoutFix.js"
|
||||
add_file "tree/TreeGrid.js"
|
||||
add_file "tree/TreeGridColumnResizer.js"
|
||||
add_file "tree/TreeGridColumns.js"
|
||||
add_file "tree/TreeGridLoader.js"
|
||||
add_file "tree/TreeGridNodeUI.js"
|
||||
add_file "tree/TreeGridNodeUIFix.js"
|
||||
add_file "tree/TreeGridRenderColumn.js"
|
||||
add_file "tree/TreeGridSorter.js"
|
||||
add_file "grid/BufferView.js"
|
||||
add_file "form/FileUploadField.js"
|
||||
add_file "form/RadioGroupFix.js"
|
||||
add_file "form/SpinnerField.js"
|
||||
add_file "form/SpinnerGroup.js"
|
||||
add_file "form/ToggleField.js"
|
||||
add_file "JSLoader.js"
|
||||
add_file "Spinner.js"
|
||||
add_file "SpinnerField.js"
|
||||
add_file "StatusBar.js"
|
||||
add_file "ToggleField.js"
|
||||
add_file "TreeGridSorter.js"
|
||||
add_file "TreeGridColumnResizer.js"
|
||||
add_file "TreeGridNodeUI.js"
|
||||
add_file "TreeGridLoader.js"
|
||||
add_file "TreeGridColumns.js"
|
||||
add_file "TreeGridRenderColumn.js"
|
||||
add_file "TreeGrid.js"
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*!
|
||||
* Ext.ux.tree.MultiSelectionModelFix.js
|
||||
*
|
||||
* Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, write to:
|
||||
* The Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link the code of portions of this program with the OpenSSL
|
||||
* library.
|
||||
* You must obey the GNU General Public License in all respects for all of
|
||||
* the code used other than OpenSSL. If you modify file(s) with this
|
||||
* exception, you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete
|
||||
* this exception statement from your version. If you delete this exception
|
||||
* statement from all source files in the program, then also delete it here.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This enhances the MSM to allow for shift selecting in tree grids etc.
|
||||
* @author Damien Churchill <damoxc@gmail.com>
|
||||
*/
|
||||
Ext.override(Ext.tree.MultiSelectionModel, {
|
||||
|
||||
onNodeClick: function (node, e) {
|
||||
if (e.ctrlKey && this.isSelected(node)) {
|
||||
this.unselect(node);
|
||||
} else if (e.shiftKey && !this.isSelected(node)) {
|
||||
var parentNode = node.parentNode;
|
||||
// We can only shift select files in the same node
|
||||
if (this.lastSelNode.parentNode.id != parentNode.id) return;
|
||||
|
||||
// Get the node indexes
|
||||
var fi = parentNode.indexOf(node),
|
||||
li = parentNode.indexOf(this.lastSelNode);
|
||||
|
||||
// Select the last clicked node and wipe old selections
|
||||
this.select(this.lastSelNode, e, false, true);
|
||||
|
||||
// Swap the values if required
|
||||
if (fi > li) {
|
||||
fi = fi + li, li = fi - li, fi = fi - li;
|
||||
}
|
||||
|
||||
// Select all the nodes
|
||||
parentNode.eachChild(function(n) {
|
||||
var i = parentNode.indexOf(n);
|
||||
if (fi < i && i < li) {
|
||||
this.select(n, e, true, true);
|
||||
}
|
||||
}, this);
|
||||
|
||||
// Select the clicked node
|
||||
this.select(node, e, true);
|
||||
} else {
|
||||
this.select(node, e, e.ctrlKey);
|
||||
}
|
||||
},
|
||||
|
||||
select: function(node, e, keepExisting, suppressEvent) {
|
||||
if(keepExisting !== true){
|
||||
this.clearSelections(true);
|
||||
}
|
||||
if(this.isSelected(node)){
|
||||
this.lastSelNode = node;
|
||||
return node;
|
||||
}
|
||||
this.selNodes.push(node);
|
||||
this.selMap[node.id] = node;
|
||||
this.lastSelNode = node;
|
||||
node.ui.onSelectedChange(true);
|
||||
if (suppressEvent !== true) {
|
||||
this.fireEvent('selectionchange', this, this.selNodes);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
})
|
@ -110,7 +110,16 @@ class JSON(resource.Resource, component.Component):
|
||||
component.Component.__init__(self, "JSON")
|
||||
self._remote_methods = []
|
||||
self._local_methods = {}
|
||||
client.disconnect_callback = self._on_client_disconnect
|
||||
if client.is_classicmode():
|
||||
def on_got_methods(methods):
|
||||
"""
|
||||
Handles receiving the method names
|
||||
"""
|
||||
self._remote_methods = methods
|
||||
|
||||
client.daemon.get_method_list().addCallback(on_got_methods)
|
||||
else:
|
||||
client.disconnect_callback = self._on_client_disconnect
|
||||
|
||||
def connect(self, host="localhost", port=58846, username="", password=""):
|
||||
"""
|
||||
@ -410,7 +419,10 @@ class WebApi(JSONComponent):
|
||||
self.host_list = ConfigManager("hostlist.conf.1.2", DEFAULT_HOSTS)
|
||||
self.core_config = CoreConfig()
|
||||
self.event_queue = EventQueue()
|
||||
self.sessionproxy = SessionProxy()
|
||||
try:
|
||||
self.sessionproxy = component.get("SessionProxy")
|
||||
except KeyError:
|
||||
self.sessionproxy = SessionProxy()
|
||||
|
||||
def get_host(self, host_id):
|
||||
"""
|
||||
|
@ -188,7 +188,13 @@ class Render(resource.Resource):
|
||||
return compress(template.render(), request)
|
||||
|
||||
class Tracker(resource.Resource):
|
||||
tracker_icons = TrackerIcons()
|
||||
|
||||
def __init__(self):
|
||||
resource.Resource.__init__(self)
|
||||
try:
|
||||
self.tracker_icons = component.get("TrackerIcons")
|
||||
except KeyError:
|
||||
self.tracker_icons = TrackerIcons()
|
||||
|
||||
def getChild(self, path, request):
|
||||
request.tracker_name = path
|
||||
@ -405,7 +411,7 @@ class ScriptResource(resource.Resource, component.Component):
|
||||
|
||||
def getChild(self, path, request):
|
||||
if hasattr(request, "lookup_path"):
|
||||
request.lookup_path = os.path.join(request.lookup_path, path)
|
||||
request.lookup_path += '/' + path
|
||||
else:
|
||||
request.lookup_path = path
|
||||
return self
|
||||
|
@ -27,8 +27,10 @@ Port daemon will listen on, default is 58846
|
||||
.TP
|
||||
.I -i INTERFACE, --interface=INTERFACE
|
||||
Interface daemon will listen for bittorrent connections on, this should be an IP address
|
||||
.TP
|
||||
.I -u UI_INTERFACE, --ui-interface=UI_INTERFACE
|
||||
Interface daemon will listen for UI connections on, this should be an IP address
|
||||
.TP
|
||||
.I -d, --do-not-daemonize
|
||||
Do not daemonize
|
||||
.TP
|
||||
|
16
get_libtorrent.sh
Executable file
16
get_libtorrent.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script checks out libtorrent from subversion
|
||||
#
|
||||
|
||||
SVN=$(which svn)
|
||||
LT_URL=https://libtorrent.svn.sourceforge.net/svnroot/libtorrent
|
||||
VERSION=14
|
||||
[ "$1" != "" ] && VERSION=$1
|
||||
BRANCH=branches/RC_0_$VERSION
|
||||
|
||||
if [ -d libtorrent ]; then
|
||||
$SVN up libtorrent
|
||||
else
|
||||
$SVN co $LT_URL/$BRANCH libtorrent
|
||||
fi
|
Submodule libtorrent deleted from e792b0189d
39
setup.py
39
setup.py
@ -173,7 +173,7 @@ else:
|
||||
if osx_check():
|
||||
dynamic_lib_extension = ".dylib"
|
||||
|
||||
_lib_extensions = ['-mt_1_39', '-mt-1_38', '-mt-1_37', '-mt-1_36', '-mt-1_35', '-mt']
|
||||
_lib_extensions = ['-mt', '-mt_1_39', '-mt-1_38', '-mt-1_37', '-mt-1_36', '-mt-1_35']
|
||||
|
||||
# Modify the libs if necessary for systems with only -mt boost libs
|
||||
for lib in _libraries:
|
||||
@ -213,18 +213,29 @@ except ImportError:
|
||||
else:
|
||||
build_libtorrent = False
|
||||
|
||||
if build_libtorrent and os.path.exists("libtorrent") and os.listdir("libtorrent"):
|
||||
# There isn't a system libtorrent library, so let's build the one included with deluge
|
||||
libtorrent = Extension(
|
||||
'libtorrent',
|
||||
extra_compile_args = _extra_compile_args,
|
||||
include_dirs = _include_dirs,
|
||||
libraries = _libraries,
|
||||
library_dirs = _library_dirs,
|
||||
sources = _sources
|
||||
)
|
||||
if build_libtorrent:
|
||||
got_libtorrent = False
|
||||
if not os.path.exists("libtorrent"):
|
||||
import subprocess
|
||||
if subprocess.call(['./get_libtorrent.sh']) > 0:
|
||||
got_libtorrent = False
|
||||
else:
|
||||
got_libtorrent = True
|
||||
else:
|
||||
got_libtorrent = True
|
||||
|
||||
_ext_modules = [libtorrent]
|
||||
if got_libtorrent:
|
||||
# There isn't a system libtorrent library, so let's build the one included with deluge
|
||||
libtorrent = Extension(
|
||||
'libtorrent',
|
||||
extra_compile_args = _extra_compile_args,
|
||||
include_dirs = _include_dirs,
|
||||
libraries = _libraries,
|
||||
library_dirs = _library_dirs,
|
||||
sources = _sources
|
||||
)
|
||||
|
||||
_ext_modules = [libtorrent]
|
||||
|
||||
class build_trans(cmd.Command):
|
||||
description = 'Compile .po files into .mo files'
|
||||
@ -318,7 +329,7 @@ class build_debug(build):
|
||||
sub_commands = [x for x in build.sub_commands if x[0] != 'build_ext'] + [('build_ext_debug', None)]
|
||||
|
||||
class build_ext_debug(_build_ext):
|
||||
|
||||
|
||||
def run(self):
|
||||
if not self.distribution.ext_modules:
|
||||
return _build_ext.run(self)
|
||||
@ -424,7 +435,7 @@ _data_files = [
|
||||
# Main setup
|
||||
setup(
|
||||
name = "deluge",
|
||||
version = "1.2.900",
|
||||
version = "1.3.0-rc2",
|
||||
fullname = "Deluge Bittorrent Client",
|
||||
description = "Bittorrent Client",
|
||||
author = "Andrew Resch, Damien Churchill",
|
||||
|
BIN
tests/publicbt.ico
Normal file
BIN
tests/publicbt.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
@ -6,7 +6,7 @@ import os
|
||||
|
||||
from deluge.config import Config
|
||||
|
||||
DEFAULTS = {"string": "foobar", "int": 1, "float": 0.435, "bool": True, "tuple": (1, 2)}
|
||||
DEFAULTS = {"string": "foobar", "int": 1, "float": 0.435, "bool": True}
|
||||
|
||||
class ConfigTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -15,10 +15,10 @@ class ConfigTestCase(unittest.TestCase):
|
||||
def test_init(self):
|
||||
config = Config("test.conf", defaults=DEFAULTS, config_dir=self.config_dir)
|
||||
self.assertEquals(DEFAULTS, config.config)
|
||||
|
||||
|
||||
config = Config("test.conf", config_dir=self.config_dir)
|
||||
self.assertEquals({}, config.config)
|
||||
|
||||
|
||||
def test_set_get_item(self):
|
||||
config = Config("test.conf", config_dir=self.config_dir)
|
||||
config["foo"] = 1
|
||||
@ -26,28 +26,28 @@ class ConfigTestCase(unittest.TestCase):
|
||||
self.assertRaises(ValueError, config.set_item, "foo", "bar")
|
||||
config["foo"] = 2
|
||||
self.assertEquals(config.get_item("foo"), 2)
|
||||
|
||||
|
||||
config._save_timer.cancel()
|
||||
|
||||
def test_load(self):
|
||||
def check_config():
|
||||
config = Config("test.conf", config_dir=self.config_dir)
|
||||
|
||||
|
||||
self.assertEquals(config["string"], "foobar")
|
||||
self.assertEquals(config["float"], 0.435)
|
||||
|
||||
|
||||
# Test loading an old config from 1.1.x
|
||||
import pickle
|
||||
pickle.dump(DEFAULTS, open(os.path.join(self.config_dir, "test.conf"), "wb"))
|
||||
|
||||
|
||||
check_config()
|
||||
|
||||
|
||||
# Test opening a previous 1.2 config file of just a json object
|
||||
import json
|
||||
json.dump(DEFAULTS, open(os.path.join(self.config_dir, "test.conf"), "wb"), indent=2)
|
||||
|
||||
check_config()
|
||||
|
||||
|
||||
# Test opening a previous 1.2 config file of having the format versions
|
||||
# as ints
|
||||
f = open(os.path.join(self.config_dir, "test.conf"), "wb")
|
||||
@ -55,26 +55,33 @@ class ConfigTestCase(unittest.TestCase):
|
||||
f.write(str(1) + "\n")
|
||||
json.dump(DEFAULTS, f, indent=2)
|
||||
f.close()
|
||||
|
||||
|
||||
check_config()
|
||||
|
||||
|
||||
# Test the 1.2 config format
|
||||
v = {"format": 1, "file": 1}
|
||||
f = open(os.path.join(self.config_dir, "test.conf"), "wb")
|
||||
json.dump(v, f, indent=2)
|
||||
json.dump(DEFAULTS, f, indent=2)
|
||||
f.close()
|
||||
|
||||
|
||||
check_config()
|
||||
|
||||
def test_save(self):
|
||||
config = Config("test.conf", defaults=DEFAULTS, config_dir=self.config_dir)
|
||||
# We do this twice because the first time we need to save the file to disk
|
||||
# and the second time we do a compare and we should not write
|
||||
ret = config.save()
|
||||
self.assertTrue(ret)
|
||||
ret = config.save()
|
||||
self.assertTrue(ret)
|
||||
|
||||
config["string"] = "baz"
|
||||
config["int"] = 2
|
||||
ret = config.save()
|
||||
self.assertTrue(ret)
|
||||
del config
|
||||
|
||||
|
||||
config = Config("test.conf", defaults=DEFAULTS, config_dir=self.config_dir)
|
||||
self.assertEquals(config["string"], "baz")
|
||||
self.assertEquals(config["int"], 2)
|
||||
@ -84,14 +91,14 @@ class ConfigTestCase(unittest.TestCase):
|
||||
config["string"] = "baz"
|
||||
config["int"] = 2
|
||||
self.assertTrue(config._save_timer.active())
|
||||
|
||||
|
||||
def check_config(config):
|
||||
self.assertTrue(not config._save_timer.active())
|
||||
del config
|
||||
config = Config("test.conf", defaults=DEFAULTS, config_dir=self.config_dir)
|
||||
self.assertEquals(config["string"], "baz")
|
||||
self.assertEquals(config["int"], 2)
|
||||
|
||||
|
||||
from twisted.internet.task import deferLater
|
||||
from twisted.internet import reactor
|
||||
d = deferLater(reactor, 7, check_config, config)
|
||||
@ -99,16 +106,15 @@ class ConfigTestCase(unittest.TestCase):
|
||||
|
||||
def test_find_json_objects(self):
|
||||
s = """{
|
||||
"file": 1,
|
||||
"file": 1,
|
||||
"format": 1
|
||||
}{
|
||||
"ssl": true,
|
||||
"enabled": false,
|
||||
"ssl": true,
|
||||
"enabled": false,
|
||||
"port": 8115
|
||||
}\n"""
|
||||
|
||||
|
||||
from deluge.config import find_json_objects
|
||||
|
||||
|
||||
objects = find_json_objects(s)
|
||||
self.assertEquals(len(objects), 2)
|
||||
|
||||
|
128
tests/test_sessionproxy.py
Normal file
128
tests/test_sessionproxy.py
Normal file
@ -0,0 +1,128 @@
|
||||
import time
|
||||
import sys
|
||||
from twisted.trial import unittest
|
||||
from twisted.internet.defer import maybeDeferred, succeed, DeferredList
|
||||
import deluge.ui.sessionproxy
|
||||
import deluge.component as component
|
||||
|
||||
class Core(object):
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.torrents = {}
|
||||
self.torrents["a"] = {"key1": 1, "key2": 2, "key3": 3}
|
||||
self.torrents["b"] = {"key1": 1, "key2": 2, "key3": 3}
|
||||
self.torrents["c"] = {"key1": 1, "key2": 2, "key3": 3}
|
||||
self.prev_status = {}
|
||||
|
||||
def get_torrent_status(self, torrent_id, keys, diff=False):
|
||||
if not keys:
|
||||
keys = self.torrents[torrent_id].keys()
|
||||
|
||||
if not diff:
|
||||
ret = {}
|
||||
for key in keys:
|
||||
ret[key] = self.torrents[torrent_id][key]
|
||||
|
||||
return succeed(ret)
|
||||
|
||||
else:
|
||||
ret = {}
|
||||
if torrent_id in self.prev_status:
|
||||
for key in keys:
|
||||
if self.prev_status[torrent_id][key] != self.torrents[torrent_id][key]:
|
||||
ret[key] = self.torrents[torrent_id][key]
|
||||
else:
|
||||
ret = self.torrents[torrent_id]
|
||||
self.prev_status[torrent_id] = dict(self.torrents[torrent_id])
|
||||
return succeed(ret)
|
||||
|
||||
def get_torrents_status(self, filter_dict, keys, diff=False):
|
||||
if not filter_dict:
|
||||
filter_dict["id"] = self.torrents.keys()
|
||||
if not keys:
|
||||
keys = self.torrents["a"].keys()
|
||||
if not diff:
|
||||
if "id" in filter_dict:
|
||||
torrents = filter_dict["id"]
|
||||
ret = {}
|
||||
for torrent in torrents:
|
||||
ret[torrent] = {}
|
||||
for key in keys:
|
||||
ret[torrent][key] = self.torrents[torrent][key]
|
||||
return succeed(ret)
|
||||
else:
|
||||
if "id" in filter_dict:
|
||||
torrents = filter_dict["id"]
|
||||
ret = {}
|
||||
for torrent in torrents:
|
||||
ret[torrent] = {}
|
||||
if torrent in self.prev_status:
|
||||
for key in self.prev_status[torrent]:
|
||||
if self.prev_status[torrent][key] != self.torrents[torrent][key]:
|
||||
ret[torrent][key] = self.torrents[torrent][key]
|
||||
else:
|
||||
ret[torrent] = dict(self.torrents[torrent])
|
||||
|
||||
self.prev_status[torrent] = dict(self.torrents[torrent])
|
||||
return succeed(ret)
|
||||
|
||||
class Client(object):
|
||||
def __init__(self):
|
||||
self.core = Core()
|
||||
|
||||
def __noop__(self, *args, **kwargs):
|
||||
return None
|
||||
def __getattr__(self, *args, **kwargs):
|
||||
return self.__noop__
|
||||
|
||||
client = Client()
|
||||
|
||||
deluge.ui.sessionproxy.client = client
|
||||
class SessionProxyTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.sp = deluge.ui.sessionproxy.SessionProxy()
|
||||
client.core.reset()
|
||||
d = self.sp.start()
|
||||
return d
|
||||
|
||||
def tearDown(self):
|
||||
return component.deregister("SessionProxy")
|
||||
|
||||
def test_startup(self):
|
||||
self.assertEquals(client.core.torrents["a"], self.sp.torrents["a"][1])
|
||||
|
||||
def test_get_torrent_status_no_change(self):
|
||||
d = self.sp.get_torrent_status("a", [])
|
||||
d.addCallback(self.assertEquals, client.core.torrents["a"])
|
||||
return d
|
||||
|
||||
def test_get_torrent_status_change_with_cache(self):
|
||||
client.core.torrents["a"]["key1"] = 2
|
||||
d = self.sp.get_torrent_status("a", ["key1"])
|
||||
d.addCallback(self.assertEquals, {"key1": 1})
|
||||
return d
|
||||
|
||||
def test_get_torrent_status_change_without_cache(self):
|
||||
client.core.torrents["a"]["key1"] = 2
|
||||
time.sleep(self.sp.cache_time + 0.1)
|
||||
d = self.sp.get_torrent_status("a", [])
|
||||
d.addCallback(self.assertEquals, client.core.torrents["a"])
|
||||
return d
|
||||
|
||||
def test_get_torrent_status_key_not_updated(self):
|
||||
time.sleep(self.sp.cache_time + 0.1)
|
||||
self.sp.get_torrent_status("a", ["key1"])
|
||||
client.core.torrents["a"]["key2"] = 99
|
||||
d = self.sp.get_torrent_status("a", ["key2"])
|
||||
d.addCallback(self.assertEquals, {"key2": 99})
|
||||
return d
|
||||
|
||||
def test_get_torrents_status_key_not_updated(self):
|
||||
time.sleep(self.sp.cache_time + 0.1)
|
||||
self.sp.get_torrents_status({"id": ["a"]}, ["key1"])
|
||||
client.core.torrents["a"]["key2"] = 99
|
||||
d = self.sp.get_torrents_status({"id": ["a"]}, ["key2"])
|
||||
d.addCallback(self.assertEquals, {"a": {"key2": 99}})
|
||||
return d
|
@ -52,3 +52,15 @@ class TrackerIconsTestCase(unittest.TestCase):
|
||||
d.addCallback(self.assertNotIdentical, None)
|
||||
d.addCallback(self.assertEquals, icon)
|
||||
return d
|
||||
|
||||
def test_get_publicbt_ico(self):
|
||||
icon = TrackerIcon("../publicbt.ico")
|
||||
d = icons.get("publicbt.org")
|
||||
d.addCallback(self.assertNotIdentical, None)
|
||||
d.addCallback(self.assertEquals, icon)
|
||||
return d
|
||||
|
||||
def test_get_empty_string_tracker(self):
|
||||
d = icons.get("")
|
||||
d.addCallback(self.assertIdentical, None)
|
||||
return d
|
||||
|
@ -1,4 +1,4 @@
|
||||
build_version = "1.2.2"
|
||||
build_version = "1.3.0-rc2"
|
||||
python_path = "C:\\Python26\\"
|
||||
|
||||
import shutil
|
||||
@ -7,7 +7,7 @@ shutil.copy(python_path + "Scripts\deluged-script.py", python_path + "Scripts\de
|
||||
shutil.copy(python_path + "Scripts\deluge-web-script.py", python_path + "Scripts\deluge-web.py")
|
||||
shutil.copy(python_path + "Scripts\deluge-gtk-script.py", python_path + "Scripts\deluge-gtk.py")
|
||||
shutil.copy(python_path + "Scripts\deluge-console-script.py", python_path + "Scripts\deluge-console.py")
|
||||
|
||||
|
||||
|
||||
from bbfreeze import Freezer
|
||||
f = Freezer("..\\build-win32\\deluge-bbfreeze-" + build_version, includes=("libtorrent", "gzip", "zipfile", "re", "socket", "struct", "cairo", "pangocairo", "atk", "pango", "wsgiref.handlers", "twisted.internet.utils", "gio", "gtk.glade"))
|
||||
@ -16,4 +16,4 @@ f.addScript(python_path + "Scripts\deluged.py", gui_only=False)
|
||||
f.addScript(python_path + "Scripts\deluge-web.py", gui_only=False)
|
||||
f.addScript(python_path + "Scripts\deluge-gtk.py", gui_only=False)
|
||||
f.addScript(python_path + "Scripts\deluge-console.py", gui_only=False)
|
||||
f() # starts the freezing process
|
||||
f() # starts the freezing process
|
||||
|
@ -37,14 +37,14 @@ SetCompressor lzma
|
||||
|
||||
# Deluge program information
|
||||
!define PROGRAM_NAME "Deluge"
|
||||
!define PROGRAM_VERSION "1.2.2"
|
||||
!define PROGRAM_VERSION "1.3.0-rc2"
|
||||
!define PROGRAM_WEB_SITE "http://deluge-torrent.org"
|
||||
|
||||
# Python files generated with bbfreeze (without DLLs from GTK+ runtime)
|
||||
!define DELUGE_PYTHON_BBFREEZE_OUTPUT_DIR "..\build-win32\deluge-bbfreeze-${PROGRAM_VERSION}"
|
||||
|
||||
# Installer for GTK+ 2.12 runtime; will be downloaded from deluge-torrent.org
|
||||
!define DELUGE_GTK_DEPENDENCY "gtk2-runtime-2.16.6-2010-02-24-ash.exe"
|
||||
!define DELUGE_GTK_DEPENDENCY "gtk2-runtime-2.16.6-2010-05-12-ash.exe"
|
||||
|
||||
|
||||
# --- Interface settings ---
|
||||
|
Reference in New Issue
Block a user