Compare commits

...

78 Commits

Author SHA1 Message Date
b9336889f5 Update version 2010-08-20 14:20:51 -07:00
995f5387eb Update windows files 2010-08-20 14:19:42 -07:00
38958d3c4f Update ChangeLog 2010-08-20 14:15:21 -07:00
b45e019f08 Fix man deluged not showing '-u' on its own line 2010-08-20 01:16:09 +10:00
d93fcf6eea Fix #1341 issue where Config would try to cancel the save_timer when it is None. 2010-08-18 12:32:11 -07:00
a2d75a5274 Add cache expiry check by key update times to fix issue where some status updates would not return
correctly if they were done < cache_time from the previous status request
2010-08-18 12:17:31 -07:00
8797c3ce1b Add additional test for get_torrents_status and fix the other one to properly invalidate the cache
time from startup before proceeding
2010-08-18 12:17:23 -07:00
79749cca03 Add test to demonstrate flaw in SessionProxy design. Need to keep track of update times for each
status key individually to fix this.
2010-08-18 11:05:21 -07:00
5fd8628761 fix the script resource on windows 2010-08-14 17:38:31 +01:00
0e80b3ea0a add the apple iOS bookmark icons from #1339 2010-08-14 16:15:18 +01:00
aa61d33ee2 Merge branch '1.3-stable' of deluge-torrent.org:deluge into 1.3-stable 2010-08-14 16:06:00 +01:00
13f29a77dd fix the system.listMethods json call when running in classic mode 2010-08-14 16:05:41 +01:00
97453d1411 Add test suite for SessionProxy 2010-08-10 09:58:28 -07:00
62d02091b3 Fix getting a torrent's status with an empty key list to return all the
torrent's status keys instead of an empty dict
2010-08-06 17:26:23 -07:00
161ad0ff0d use the get_libtorrent.sh script to get libtorrent if it is missing 2010-07-22 21:13:27 +01:00
7f323ec0fc add libtorrent fetch script 2010-07-22 18:17:51 +01:00
05aebbb575 remove the libtorrent submodule 2010-07-22 18:04:49 +01:00
de85e1dcdc a couple of fixes to stop the webui crashing when running within the gtkui 2010-07-18 23:11:02 +01:00
1ce480ff23 Only use an icon if it passes some sanity checks 2010-07-17 17:13:15 +10:00
007a9912d2 Use a blank icon when the tracker icon downloaded isn't a proper image 2010-07-15 19:17:48 -07:00
d793b9e6b8 Attempt to create a move_storage destination path if it doesn't exist 2010-07-15 10:50:15 -07:00
72ec926c1a Do not attempt to move a torrents storage if the destination path does
not exist
2010-07-12 14:45:51 -07:00
0d431ae7db Try to import system rencode before deluge.rencode to allow the use of the new rencode library at: http://code.google.com/p/rencode/ 2010-07-08 16:39:53 -07:00
f1e0e3be15 Add logging the user when a torrent is added or removed 2010-07-05 21:10:16 -07:00
e8bf5eb592 Fix ChangeLog 2010-07-04 02:13:31 +10:00
d9a2c4db72 Fix uncaught exception when closing deluge in classic mode 2010-07-02 02:43:07 +10:00
8fb7277a82 Fix typo 2010-07-02 02:41:30 +10:00
35128cf18f more improvements to the shift select 2010-07-01 14:21:37 +01:00
6ff1da2391 fix select 'upwards' 2010-07-01 14:08:20 +01:00
4614188c62 update changelog 2010-07-01 13:46:27 +01:00
80297b8e45 allow for shift selecting in tree grids 2010-07-01 13:45:22 +01:00
9173a9cfdd Update Changelog 2010-06-22 18:26:43 -07:00
571d1079f6 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.
2010-06-22 18:25:50 -07:00
0497c407e1 Always look for -mt boost libraries first 2010-06-18 09:51:50 -07:00
8b58c960f3 fix typo 2010-06-12 22:48:00 +01:00
b615ebe1b8 change bits to bytes, thanks to charles 2010-06-11 15:59:33 +01:00
3ed8279219 Fix typo in label plugin - thanks konti 2010-06-11 00:51:46 +10:00
f2944bdeef Handle os.remove failing on windows 2010-06-08 03:20:48 +10:00
0fcd90ee2c Python independent version of previous commit 2010-06-08 01:57:13 +10:00
26460808e7 Fix console ui not liking paths with backslashes on windows (#1293) 2010-06-08 01:30:10 +10:00
7aba1af0b2 Print a more informative error message if the torrent file doesn't exist 2010-06-08 01:26:49 +10:00
4d2b7df49d Fix execute plugin only executing last event (#1306) 2010-06-08 00:20:07 +10:00
bd775d0d40 Only encode if necessary 2010-06-07 20:18:37 +10:00
7fb3c3c04c Fix unicode support in console ui (#1307) 2010-06-07 20:18:35 +10:00
19c27ee8c5 Add some debug logging statements 2010-06-04 18:05:27 +01:00
d69b8e1099 Fix an error in the key 2010-06-04 17:37:45 +01:00
88daf82cb0 Fix saving the correct event name 2010-06-04 16:37:50 +01:00
99c1a61383 Save the execute config after adding/removing/saving commands 2010-06-04 16:30:51 +01:00
2e55769c18 Fix typo in execute plugin 2010-05-20 00:06:18 +10:00
259d2633e7 Fix man deluged not showing '-d' on its own line 2010-05-16 22:32:59 +10:00
8e5aab660c Fix remote save path dialog not disappearing after creating a torrent 2010-05-16 18:13:34 +10:00
fc96e9d02c Fix only being able to click "remote path" once when creating a torrent 2010-05-16 18:13:31 +10:00
821d403a6c Fix deluged crashing on windows when logfile's directory doesn't exist 2010-05-16 13:12:51 +10:00
5e0d988ef0 Revert "Fix trac wiki turning CamelCase words into broken links"
This reverts commit 925ac42f7c.
2010-05-11 23:46:48 +10:00
925ac42f7c Fix trac wiki turning CamelCase words into broken links 2010-05-11 23:29:02 +10:00
1ac72b81b6 Update email address and copyright 2010-05-11 03:52:17 +10:00
3417caf1d2 Fix label plugin not remembering newly created labels 2010-05-09 17:52:29 +10:00
1bcfc91c35 Remove unused code from label plugin 2010-05-09 17:52:25 +10:00
6ee0e5b6be Update docstrings to use names from previous commit 2010-05-09 17:07:11 +10:00
58a74202e1 Use better names for TrackerIcons' args 2010-05-09 17:01:47 +10:00
a4c6f4e8c9 Return the noIcon for empty strings as well 2010-05-09 16:44:12 +10:00
60f3d32de7 Raise IconsError instead of IndexError (fixes infinite looping) 2010-05-09 16:36:54 +10:00
b3eed8a1f0 Add test for tracker_icons for when requesting an icon for host that is "". This test results in an infinite loop. 2010-05-08 20:10:12 -07:00
37137d9b54 Return 0 in get_free_space if the download_location is invalid 2010-05-08 20:09:57 -07:00
4fb14b581d Use previously defined host variable instead of getting the tracker host from the TreeModel 2010-05-08 20:09:49 -07:00
98da4d0291 Do not request a tracker icon if the host is "" 2010-05-08 20:09:37 -07:00
f0c06f4bc5 Update Changelog 2010-05-08 11:50:19 -07:00
63d701305c rebuild deluge-all and ext-extensions 2010-05-08 16:19:40 +01:00
99396afa0c update the build files for deluge-all and ext-extensions 2010-05-08 16:19:40 +01:00
6231dbd1ca fix the null comparison 2010-05-08 16:19:40 +01:00
8f021c7f06 set the baseCls for the add label form panel to x-plain 2010-05-08 16:19:40 +01:00
6bb4559d18 Make host_to_url support redirection and add another test 2010-05-08 16:26:08 +10:00
7c9eea0361 Try favicon.ico if there's a HTMLParseError 2010-05-08 15:50:57 +10:00
15247507d4 Fix relative redirecting in blocklist plugin 2010-05-06 23:31:25 +10:00
10de8d5475 Remove plugins that aren't release ready 2010-05-05 17:48:04 -07:00
e304c1f719 update the ChangeLog 2010-05-05 23:03:19 +01:00
48d3e89d84 Update windows build files 2010-05-05 15:01:52 -07:00
44f9e17a09 Update version
Update ChangeLog
2010-05-05 14:52:18 -07:00
97 changed files with 11426 additions and 10941 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "libtorrent"]
path = libtorrent
url = git://deluge-torrent.org/libtorrent

View File

@ -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 ====

View File

@ -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

View File

@ -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):

View File

@ -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.

View File

@ -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:

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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.
#

View File

@ -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.
#

View File

@ -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.
#

View File

@ -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"

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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!")

View File

@ -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)
)

View File

@ -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()

View File

@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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>

View File

@ -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()

View File

@ -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"))

View File

@ -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)
)

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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">&#x25CF;</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">&lt;b&gt;Free Space Checking&lt;/b&gt;</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>

View File

@ -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();

View File

@ -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"
# )

View File

@ -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

View File

@ -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)
)

View File

@ -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():

View File

@ -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();

View File

@ -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)))

View File

@ -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):

View File

@ -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

View File

@ -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)
)

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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>

View File

@ -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();

View File

@ -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>

View File

@ -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

View File

@ -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))

View File

@ -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>

View File

@ -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()

View File

@ -1,6 +0,0 @@
#!/bin/sh
while true; do
python test.py
sleep 2
done;

View File

@ -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())

View File

@ -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!")

View File

@ -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]:

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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()

View File

@ -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):

View File

@ -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"]

View File

@ -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 ##

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -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

View File

@ -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"

View File

@ -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';
},
/**

View File

@ -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

View File

@ -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"

View File

@ -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;
}
})

View File

@ -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):
"""

View File

@ -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

View File

@ -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
View 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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -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
View 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

View File

@ -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

View File

@ -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

View File

@ -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 ---