From 0723a772147c21d25e249c045790471cedd98dc3 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Sun, 22 Nov 2009 02:34:23 +0000 Subject: [PATCH] First notifications plugin commit. Working when not in classic mode. In classic mode, needs some more coding. --- deluge/component.py | 21 +- deluge/core/rpcserver.py | 2 +- deluge/core/torrentmanager.py | 7 +- deluge/event.py | 23 +- deluge/pluginmanagerbase.py | 3 +- deluge/plugins/blocklist/blocklist/common.py | 15 - deluge/plugins/blocklist/blocklist/core.py | 14 +- deluge/plugins/blocklist/blocklist/detect.py | 1 + deluge/plugins/blocklist/blocklist/readers.py | 29 +- .../plugins/notifications/create_dev_link.sh | 7 + .../notifications/notifications/__init__.py | 58 +++ .../notifications/notifications/common.py | 42 ++ .../notifications/notifications/core.py | 205 +++++++++ .../notifications/data/config.glade | 398 ++++++++++++++++++ .../notifications/data/notifications.js | 50 +++ .../notifications/notifications/events.py | 73 ++++ .../notifications/notifications/gtkui.py | 282 +++++++++++++ .../notifications/notifications/manager.py | 149 +++++++ .../notifications/notifications/webui.py | 57 +++ deluge/plugins/notifications/setup.py | 73 ++++ deluge/ui/common.py | 33 +- deluge/ui/console/main.py | 2 +- deluge/ui/gtkui/addtorrentdialog.py | 33 +- deluge/ui/gtkui/common.py | 2 - .../ui/gtkui/glade/add_torrent_dialog.glade | 23 +- deluge/ui/gtkui/gtkui.py | 17 +- deluge/ui/gtkui/ipcinterface.py | 20 +- deluge/ui/gtkui/systemtray.py | 5 +- deluge/ui/web/index.html | 2 +- 29 files changed, 1482 insertions(+), 164 deletions(-) create mode 100755 deluge/plugins/notifications/create_dev_link.sh create mode 100644 deluge/plugins/notifications/notifications/__init__.py create mode 100644 deluge/plugins/notifications/notifications/common.py create mode 100644 deluge/plugins/notifications/notifications/core.py create mode 100644 deluge/plugins/notifications/notifications/data/config.glade create mode 100644 deluge/plugins/notifications/notifications/data/notifications.js create mode 100644 deluge/plugins/notifications/notifications/events.py create mode 100644 deluge/plugins/notifications/notifications/gtkui.py create mode 100644 deluge/plugins/notifications/notifications/manager.py create mode 100644 deluge/plugins/notifications/notifications/webui.py create mode 100755 deluge/plugins/notifications/setup.py diff --git a/deluge/component.py b/deluge/component.py index f433ad6e0..764c38609 100644 --- a/deluge/component.py +++ b/deluge/component.py @@ -101,13 +101,6 @@ class ComponentRegistry: if depend != None: self.depend[name] = depend - def deregister(self, name): - """Deregisters a component""" - if name in self.components: - log.debug("Deregistering Component: %s", name) - self.stop_component(name) - del self.components[name] - def get(self, name): """Returns a reference to the component 'name'""" return self.components[name] @@ -133,14 +126,8 @@ class ComponentRegistry: def stop(self): """Stops all components""" - # We create a separate list of the keys and do an additional check to - # make sure the key still exists in the components dict. - # This is because components could be deregistered during a stop and - # the dictionary would get modified while iterating through it. - components = self.components.keys() - for component in components: - if component in self.components: - self.stop_component(component) + for component in self.components.keys(): + self.stop_component(component) def stop_component(self, component): if self.components[component].get_state() != \ @@ -200,10 +187,6 @@ def register(name, obj, depend=None): """Registers a component with the registry""" _ComponentRegistry.register(name, obj, depend) -def deregister(name): - """Deregisters a component""" - _ComponentRegistry.deregister(name) - def start(component=None): """Starts all components""" if component == None: diff --git a/deluge/core/rpcserver.py b/deluge/core/rpcserver.py index 425ccb6c8..7525fdb5c 100644 --- a/deluge/core/rpcserver.py +++ b/deluge/core/rpcserver.py @@ -131,7 +131,7 @@ class DelugeRPCProtocol(Protocol): try: request = rencode.loads(dobj.decompress(data)) except Exception, e: - #log.debug("Received possible invalid message (%r): %s", data, e) + log.debug("Received possible invalid message (%r): %s", data, e) # This could be cut-off data, so we'll save this in the buffer # and try to prepend it on the next dataReceived() self.__buffer = data diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index c9d69f487..0bfe1c1d8 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -210,11 +210,8 @@ class TorrentManager(component.Component): def stop(self): # Stop timers - if self.save_state_timer.running: - self.save_state_timer.stop() - - if self.save_resume_data_timer.running: - self.save_resume_data_timer.stop() + self.save_state_timer.stop() + self.save_resume_data_timer.stop() # Save state on shutdown self.save_state() diff --git a/deluge/event.py b/deluge/event.py index 2acaf8d72..118a76e59 100644 --- a/deluge/event.py +++ b/deluge/event.py @@ -17,9 +17,9 @@ # # 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. +# 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 @@ -41,21 +41,6 @@ and subsequently emitted to the clients. """ -known_events = {} - -class DelugeEventMetaClass(type): - """ - This metaclass simply keeps a list of all events classes created. - """ - def __init__(cls, name, bases, dct): - super(DelugeEventMetaClass, cls).__init__(name, bases, dct) - if name != "DelugeEvent": - classdoc = cls.__doc__.splitlines() - if classdoc[0].strip(): - known_events[name] = classdoc[0].strip() - else: - known_events[name] = classdoc[1].strip() - class DelugeEvent(object): """ The base class for all events. @@ -64,8 +49,6 @@ class DelugeEvent(object): :prop args: a list of the attribute values """ - __metaclass__ = DelugeEventMetaClass - def _get_name(self): return self.__class__.__name__ diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index 35c551d25..7a899475e 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -87,7 +87,7 @@ class PluginManagerBase: def disable_plugins(self): # Disable all plugins that are enabled for key in self.plugins.keys(): - self.disable_plugin(key) + self.plugins[key].disable() def __getitem__(self, key): return self.plugins[key] @@ -153,7 +153,6 @@ class PluginManagerBase: """Disables a plugin""" try: self.plugins[name].disable() - component.deregister(self.plugins[name].plugin.get_component_name()) del self.plugins[name] self.config["enabled_plugins"].remove(name) except KeyError: diff --git a/deluge/plugins/blocklist/blocklist/common.py b/deluge/plugins/blocklist/blocklist/common.py index d1181ddf5..a8d6d93b9 100644 --- a/deluge/plugins/blocklist/blocklist/common.py +++ b/deluge/plugins/blocklist/blocklist/common.py @@ -49,18 +49,3 @@ def raiseError(error): raise error return new return safer - -def remove_zeros(ip): - """ - Removes unneeded zeros from ip addresses. - - Example: 000.000.000.003 -> 0.0.0.3 - - :param ip: the ip address - :type ip: string - - :returns: the ip address without the unneeded zeros - :rtype: string - - """ - return ".".join([part.lstrip("0").zfill(1) for part in ip.split(".")]) diff --git a/deluge/plugins/blocklist/blocklist/core.py b/deluge/plugins/blocklist/blocklist/core.py index 0d772a2ad..c734ca0fc 100644 --- a/deluge/plugins/blocklist/blocklist/core.py +++ b/deluge/plugins/blocklist/blocklist/core.py @@ -128,8 +128,6 @@ class Core(CorePluginBase): self.use_cache = False self.failed_attempts = 0 self.auto_detected = False - if force: - self.reader = None # Start callback chain d = self.download_list() @@ -220,8 +218,8 @@ class Core(CorePluginBase): if self.config["last_update"] and not self.force_download: headers['If-Modified-Since'] = self.config["last_update"] - log.debug("Attempting to download blocklist %s", url) - log.debug("Sending headers: %s", headers) + log.debug("Attempting to download blocklist %s" % url) + log.debug("Sending headers: %s" % headers) self.up_to_date = False self.is_downloading = True return download_file(url, deluge.configmanager.get_config_dir("blocklist.download"), on_retrieve_data, headers) @@ -241,7 +239,7 @@ class Core(CorePluginBase): # Handle redirect errors location = error_msg.split(" to ")[1] if "Moved Permanently" in error_msg: - log.debug("Setting blocklist url to %s", location) + log.debug("Setting blocklist url to %s" % location) self.config["url"] = location f.trap(f.type) d = self.download_list(url=location) @@ -293,7 +291,7 @@ class Core(CorePluginBase): self.auto_detect(blocklist) self.auto_detected = True - log.debug("Importing using reader: %s", self.reader) + log.debug("Importing using reader: %s",self.reader) log.debug("Reader type: %s compression: %s", self.config["list_type"], self.config["list_compression"]) d = threads.deferToThread(self.reader(blocklist).read, on_read_ip_range) d.addCallback(on_finish_read) @@ -329,7 +327,7 @@ class Core(CorePluginBase): elif os.path.exists(blocklist) and not self.use_cache: # If we have a backup and we haven't already used it e = f.trap(Exception) - log.warning("Error reading blocklist: %s", e) + log.warning("Error reading blocklist: ", e) self.use_cache = True try_again = True @@ -349,7 +347,7 @@ class Core(CorePluginBase): """ self.config["list_compression"] = detect_compression(blocklist) self.config["list_type"] = detect_format(blocklist, self.config["list_compression"]) - log.debug("Auto-detected type: %s compression: %s", self.config["list_type"], self.config["list_compression"]) + log.debug("Auto-detected type: %s compression: %s", self.config["list_type"], self.config["list_compression"]) if not self.config["list_type"]: self.config["list_compression"] = "" raise UnknownFormatError diff --git a/deluge/plugins/blocklist/blocklist/detect.py b/deluge/plugins/blocklist/blocklist/detect.py index f429e1e9f..af1450424 100644 --- a/deluge/plugins/blocklist/blocklist/detect.py +++ b/deluge/plugins/blocklist/blocklist/detect.py @@ -77,4 +77,5 @@ def create_reader(format, compression=""): decompressor = DECOMPRESSERS.get(compression) if decompressor: reader = decompressor(reader) + return reader diff --git a/deluge/plugins/blocklist/blocklist/readers.py b/deluge/plugins/blocklist/blocklist/readers.py index a520fad3c..ddfea004c 100644 --- a/deluge/plugins/blocklist/blocklist/readers.py +++ b/deluge/plugins/blocklist/blocklist/readers.py @@ -33,9 +33,29 @@ # # -from common import raiseError, remove_zeros -import re +from deluge.log import LOG as log +from common import raiseError +def remove_zeros(ip): + """ + Removes unneeded zeros from ip addresses. + + Example: 000.000.000.003 -> 0.0.0.3 + + :param ip: the ip address + :type ip: string + + :returns: the ip address without the unneeded zeros + :rtype: string + + """ + new_ip = [] + for part in ip.split("."): + while part[0] == "0" and len(part) > 1: + part = part[1:] + new_ip.append(part) + return ".".join(new_ip) + class ReaderParseError(Exception): pass @@ -70,9 +90,6 @@ class BaseReader(object): if not self.is_ignored(line): try: (start, end) = self.parse(line) - if not re.match("^(\d{1,3}\.){4}$", start + ".") or \ - not re.match("^(\d{1,3}\.){4}$", end + "."): - valid = False except: valid = False finally: @@ -98,7 +115,7 @@ class SafePeerReader(BaseReader): """Blocklist reader for SafePeer style blocklists""" @raiseError(ReaderParseError) def parse(self, line): - return line.strip().split(":")[-1].split("-") + return line.strip().split(":")[1].split("-") class PeerGuardianReader(SafePeerReader): """Blocklist reader for PeerGuardian style blocklists""" diff --git a/deluge/plugins/notifications/create_dev_link.sh b/deluge/plugins/notifications/create_dev_link.sh new file mode 100755 index 000000000..fddef345f --- /dev/null +++ b/deluge/plugins/notifications/create_dev_link.sh @@ -0,0 +1,7 @@ +#!/bin/bash +cd /home/vampas/projects/DelugeNotify/deluge/plugins/notifications +mkdir temp +export PYTHONPATH=./temp +python setup.py build develop --install-dir ./temp +cp ./temp/Notifications.egg-link .config//plugins +rm -fr ./temp diff --git a/deluge/plugins/notifications/notifications/__init__.py b/deluge/plugins/notifications/notifications/__init__.py new file mode 100644 index 000000000..7f5b5704c --- /dev/null +++ b/deluge/plugins/notifications/notifications/__init__.py @@ -0,0 +1,58 @@ +# +# __init__.py +# +# Copyright (C) 2009 Pedro Algarvio +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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) diff --git a/deluge/plugins/notifications/notifications/common.py b/deluge/plugins/notifications/notifications/common.py new file mode 100644 index 000000000..39695fab0 --- /dev/null +++ b/deluge/plugins/notifications/notifications/common.py @@ -0,0 +1,42 @@ +# +# common.py +# +# Copyright (C) 2009 Pedro Algarvio +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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("notifications", os.path.join("data", filename)) diff --git a/deluge/plugins/notifications/notifications/core.py b/deluge/plugins/notifications/notifications/core.py new file mode 100644 index 000000000..5280b7246 --- /dev/null +++ b/deluge/plugins/notifications/notifications/core.py @@ -0,0 +1,205 @@ +# +# core.py +# +# Copyright (C) 2009 Pedro Algarvio +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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 smtplib +from twisted.internet import defer, threads +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 + +# Relative imports +from manager import Notifications +import events + +DEFAULT_PREFS = { + # BLINK + "blink_enabled": False, + # EMAIL + "smtp_enabled": False, + "smtp_host": "", + "smtp_port": 25, + "smtp_user": "", + "smtp_pass": "", + "smtp_from": "", + "smtp_tls": False, # SSL or TLS + "smtp_recipients": [], + # FLASH + "flash_enabled": False, + # POPUP + "popup_enabled": False, + # SOUND + "sound_enabled": False, + "sound_path": "" +} + + +class Core(CorePluginBase, Notifications): + def enable(self): + Notifications.enable(self) + self.config = deluge.configmanager.ConfigManager("notifications.conf", + DEFAULT_PREFS) + component.get("EventManager").register_event_handler( + "TorrentFinishedEvent", self._on_torrent_finished_event + ) + log.debug("\n\nENABLING CORE NOTIFICATIONS\n\n") + + def disable(self): + Notifications.disable(self) + log.debug("\n\nDISABLING CORE NOTIFICATIONS\n\n") + + def update(self): + pass + + @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 + + # Notification methods + @export + def notify_blink(self): + if not self.config["blink_enabled"]: + return defer.succeed("Blink notification not enabled") + return defer.maybeDeferred( + component.get("EventManager").emit, events.NotificationBlinkEvent()) + + @export + def notify_email(self, title='', message='', smtp_from='', recipients=[]): + if not self.config['smtp_enabled']: + return defer.succeed("SMTP notification not enabled") + d = threads.deferToThread(self._notify_email, title, message, smtp_from, + recipients) + d.addCallback(self._on_notify_sucess, 'email') + d.addErrback(self._on_notify_failure, 'email') + return d + + @export + def notify_flash(self, title='', message=''): + if not self.config["flash_enabled"]: + return defer.succeed("Flash notification not enabled") + return defer.maybeDeferred( + component.get("EventManager").emit, + events.NotificationFlashEvent(title, message) + ) + + @export + def notify_popup(self, title='', message=''): + if not self.config["popup_enabled"]: + return defer.succeed("Popup notification not enabled") + return defer.maybeDeferred( + component.get("EventManager").emit, + events.NotificationPopupEvent(title, message) + ) + + @export + def notify_sound(self, sound_path=''): + if not self.config["sound_enabled"]: + return defer.succeed("Sound notification not enabled") + return defer.maybeDeferred( + component.get("EventManager").emit, + events.NotificationSoundEvent(sound_path)) + + def _notify_email(self, title='', message='', smtp_from='', recipients=[]): + config = self.config + to_addrs = '; '.join(config['smtp_recipients']+recipients) + headers = """\ +From: %(smtp_from)s +To: %(smtp_recipients)s +Subject: %(title)s + + +""" % {'smtp_from': smtp_from and smtp_from or config['smtp_from'], + 'title': title, + 'smtp_recipients': to_addrs} + + message = '\r\n'.join((headers + message).splitlines()) + + try: + server = smtplib.SMTP(config["smtp_host"], config["smtp_port"]) + except Exception, err: + log.error("There was an error sending the notification email: %s", + err) + + security_enabled = config['smtp_tls'] + + if security_enabled: + server.ehlo() + if not server.esmtp_features.has_key('starttls'): + log.warning("TLS/SSL enabled but server does not support it") + else: + server.starttls() + server.ehlo() + + if config['smtp_user'] and config['smtp_pass']: + try: + server.login(config['smtp_user'], config['smtp_pass']) + except smtplib.SMTPHeloError: + log.warning("The server didn't reply properly to the helo " + "greeting") + except smtplib.SMTPAuthenticationError: + log.warning("The server didn't accept the username/password " + "combination") + + try: + try: + server.sendmail(config['smtp_from'], to_addrs, message) + except smtplib.SMTPException, err: + log.error("There was an error sending the notification email: " + "%s", err) + finally: + if security_enabled: + # avoid false failure detection when the server closes + # the SMTP connection with TLS enabled + import socket + try: + server.quit() + except socket.sslerror: + pass + else: + server.quit() + return "Notification email sent." diff --git a/deluge/plugins/notifications/notifications/data/config.glade b/deluge/plugins/notifications/notifications/data/config.glade new file mode 100644 index 000000000..1bdd923d3 --- /dev/null +++ b/deluge/plugins/notifications/notifications/data/config.glade @@ -0,0 +1,398 @@ + + + + + + + + True + vertical + + + True + 0 + none + + + True + 12 + + + True + vertical + + + Blink tray icon + True + True + False + True + + + 0 + + + + + Show popup + True + True + False + True + + + 1 + + + + + True + + + Play sound + True + True + False + True + + + + False + 0 + + + + + True + False + + + 2 + 1 + + + + + 2 + + + + + + + + + True + <b>UI Notifications</b> + True + + + label_item + + + + + 0 + + + + + True + 0 + none + + + True + 12 + + + True + 7 + 4 + 2 + 2 + + + True + Hostname: + right + + + 1 + 2 + + + + + True + True + + + + 1 + 2 + 1 + 2 + + + + + True + Port: + right + + + 2 + 3 + 1 + 2 + + + + + True + True + 65535 + + 5 + 25 1 100 1 10 10 + 1 + True + True + + + 3 + 4 + 1 + 2 + + + + + True + Username: + right + + + 2 + 3 + + + + + True + True + + + + 1 + 4 + 2 + 3 + + + + + True + Password: + + + 3 + 4 + + + + + True + True + False + + + + 1 + 4 + 3 + 4 + + + + + True + 0 + none + + + True + 12 + + + True + 2 + + + True + True + automatic + automatic + + + True + True + False + horizontal + + + + + 0 + + + + + True + vertical + 5 + start + + + gtk-add + True + True + True + True + + + + False + False + 0 + + + + + gtk-delete + True + False + True + True + True + + + + False + False + 1 + + + + + False + 3 + 1 + + + + + + + + + True + <b>Recipients</b> + True + + + label_item + + + + + 4 + 6 + 7 + + + + + True + True + False + True + + + True + Server requires TLS/SSL + + + + + 1 + 4 + 5 + 6 + 10 + + + + + True + From: + right + + + 4 + 5 + + + + + True + True + + + + 1 + 4 + 4 + 5 + + + + + Enabled + True + True + False + True + + + + 4 + + + + + + + + + + + + True + <b>Email Notifications</b> + True + + + label_item + + + + + 1 + + + + + + diff --git a/deluge/plugins/notifications/notifications/data/notifications.js b/deluge/plugins/notifications/notifications/data/notifications.js new file mode 100644 index 000000000..46384d0bc --- /dev/null +++ b/deluge/plugins/notifications/notifications/data/notifications.js @@ -0,0 +1,50 @@ +/* +Script: notifications.js + The client-side javascript code for the Notifications plugin. + +Copyright: + (C) Pedro Algarvio 2009 + 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. +*/ + +NotificationsPlugin = Ext.extend(Deluge.Plugin, { + constructor: function(config) { + config = Ext.apply({ + name: "Notifications" + }, config); + NotificationsPlugin.superclass.constructor.call(this, config); + }, + + onDisable: function() { + + }, + + onEnable: function() { + + } +}); +new NotificationsPlugin(); diff --git a/deluge/plugins/notifications/notifications/events.py b/deluge/plugins/notifications/notifications/events.py new file mode 100644 index 000000000..76bbd02a3 --- /dev/null +++ b/deluge/plugins/notifications/notifications/events.py @@ -0,0 +1,73 @@ +# +# events.py +# +# Copyright (C) 2009 Pedro Algarvio +# +# 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.event import DelugeEvent + +class NotificationEvent(DelugeEvent): + """Emitted when a notification is suposed to happen.""" + def __init__(self, title=None, message=None): + """ + :param title: the notification title + :type title: string + :param message: the notification message + :type message: string + """ + self._args = [title, message] + +class NotificationBlinkEvent(DelugeEvent): + """Emitted when a tray icon blink should occur.""" + +class NotificationPopupEvent(DelugeEvent): + """Emitted when a popup notification is required""" + def __init__(self, title="", message=""): + """ + :param title: the notification title + :type title: string + :param message: the notification message + :type message: string + """ + self._args = [title, message] + +class NotificationFlashEvent(NotificationPopupEvent): + """Emmited when a flash on the web front-end should occur.""" + +class NotificationSoundEvent(DelugeEvent): + """Emitted when a sound notification is required""" + def __init__(self, sound_path=""): + """ + :param sound_path: the path to the notification sound + :type title: string + """ + self._args = [sound_path] diff --git a/deluge/plugins/notifications/notifications/gtkui.py b/deluge/plugins/notifications/notifications/gtkui.py new file mode 100644 index 000000000..9d21b02bf --- /dev/null +++ b/deluge/plugins/notifications/notifications/gtkui.py @@ -0,0 +1,282 @@ +# +# gtkui.py +# +# Copyright (C) 2009 Pedro Algarvio +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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 twisted.internet import defer +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 + +try: + import pygame + SOUND_AVAILABLE = True +except ImportError: + SOUND_AVAILABLE = False + +try: + import pynotify + POPUP_ENABLED = True +except ImportError: + POPUP_ENABLED = False + +# Relative imports +from common import get_resource +from manager import Notifications + +RECIPIENT_FIELD, RECIPIENT_EDIT = range(2) + +class GtkUI(GtkPluginBase, Notifications): + def enable(self): + Notifications.enable(self) + self.glade = gtk.glade.XML(get_resource("config.glade")) + self.glade.get_widget("smtp_port").set_value(25) + self.prefs = self.glade.get_widget("prefs_box") + self.prefs.show_all() + self.treeview = self.glade.get_widget("smtp_recipients") + treeview_selection = self.treeview.get_selection() + treeview_selection.connect("changed", self.on_treeview_selection_changed) + self.model = gtk.ListStore(str, bool) + + renderer = gtk.CellRendererText() + renderer.connect("edited", self.on_cell_edited, self.model) + renderer.set_data("recipient", RECIPIENT_FIELD) + column = gtk.TreeViewColumn("Recipients", renderer, + text=RECIPIENT_FIELD, + editable=RECIPIENT_EDIT) + column.set_expand(True) + self.treeview.append_column(column) + self.treeview.set_model(self.model) + + deluge.common.get_default_download_dir() + + self.glade.signal_autoconnect({ + 'on_add_button_clicked': (self.on_add_button_clicked, + self.treeview), + 'on_delete_button_clicked': (self.on_delete_button_clicked, + self.treeview), + 'on_enabled_toggled': self.on_enabled_toggled, + 'on_sound_enabled_toggled': self.on_sound_enabled_toggled + }) + + component.get("Preferences").add_page("Notifications", self.prefs) + component.get("PluginManager").register_hook("on_apply_prefs", + self.on_apply_prefs) + component.get("PluginManager").register_hook("on_show_prefs", + self.on_show_prefs) + + if not POPUP_ENABLED: + self.glade.get_widget("popup_enabled").set_property('sensitive', + False) + else: + client.register_event_handler("NotificationPopupEvent", + self.notify_popup) + + client.register_event_handler("NotificationBlinkEvent", + self.notify_blink) + + self.tray = component.get("SystemTray") + if not SOUND_AVAILABLE: + self.glade.get_widget("sound_enabled").set_property('sensitive', + False) + self.glade.get_widget('sound_path').set_property('sensitive', False) + else: + client.register_event_handler("NotificationSoundEvent", + self.notify_sound) + # Force config populate + client.notifications.get_config().addCallback(self.cb_get_config) + + def disable(self): + Notifications.disable(self) + component.get("Preferences").remove_page("Notifications") + component.get("PluginManager").deregister_hook("on_apply_prefs", + self.on_apply_prefs) + component.get("PluginManager").deregister_hook("on_show_prefs", + self.on_show_prefs) + + def on_apply_prefs(self): + log.debug("applying prefs for Notifications") + config = { + "smtp_enabled": self.glade.get_widget("smtp_enabled").get_active(), + "smtp_host": self.glade.get_widget("smtp_host").get_text(), + "smtp_port": self.glade.get_widget("smtp_port").get_value(), + "smtp_user": self.glade.get_widget("smtp_user").get_text(), + "smtp_pass": self.glade.get_widget("smtp_pass").get_text(), + "smtp_from": self.glade.get_widget("smtp_from").get_text(), + "smtp_tls": self.glade.get_widget("smtp_tls").get_active(), + "smtp_recipients": [dest[0] for dest in self.model if + dest[0]!='USER@HOST'], + "blink_enabled": self.glade.get_widget("blink_enabled").get_active(), + "sound_enabled": self.glade.get_widget("sound_enabled").get_active(), + "sound_path": self.glade.get_widget("sound_path").get_filename(), + "popup_enabled": self.glade.get_widget("popup_enabled").get_active() + } + + client.notifications.set_config(config) + + def on_show_prefs(self): + client.notifications.get_config().addCallback(self.cb_get_config) + + def cb_get_config(self, config): + "callback for on show_prefs" + self.config = config + self.glade.get_widget("smtp_host").set_text(config["smtp_host"]) + self.glade.get_widget("smtp_port").set_value(config["smtp_port"]) + self.glade.get_widget("smtp_user").set_text(config["smtp_user"]) + self.glade.get_widget("smtp_pass").set_text(config["smtp_pass"]) + self.glade.get_widget("smtp_from").set_text(config["smtp_from"]) + self.glade.get_widget("smtp_tls").set_active(config["smtp_tls"]) + self.model.clear() + for recipient in config['smtp_recipients']: + self.model.set(self.model.append(), + RECIPIENT_FIELD, recipient, + RECIPIENT_EDIT, False) + self.glade.get_widget("smtp_enabled").set_active(config['smtp_enabled']) + self.glade.get_widget("sound_enabled").set_active( + config['sound_enabled'] + ) + self.glade.get_widget("popup_enabled").set_active( + config['popup_enabled'] + ) + self.glade.get_widget("blink_enabled").set_active( + config['blink_enabled'] + ) + if config['sound_path']: + sound_path = config['sound_path'] + else: + sound_path = deluge.common.get_default_download_dir() + self.glade.get_widget("sound_path").set_filename(sound_path) + # Force toggle + self.on_enabled_toggled(self.glade.get_widget("smtp_enabled")) + self.on_sound_enabled_toggled(self.glade.get_widget('sound_enabled')) + + def on_add_button_clicked(self, widget, treeview): + model = treeview.get_model() + model.set(model.append(), + RECIPIENT_FIELD, "USER@HOST", + RECIPIENT_EDIT, True) + + def on_delete_button_clicked(self, widget, treeview): + selection = treeview.get_selection() + model, iter = selection.get_selected() + if iter: + path = model.get_path(iter)[0] + model.remove(iter) + + def on_cell_edited(self, cell, path_string, new_text, model): + log.debug("%s %s %s %s", cell, path_string, new_text, model) + iter = model.get_iter_from_string(path_string) + path = model.get_path(iter)[0] + model.set(iter, RECIPIENT_FIELD, new_text) + + def on_treeview_selection_changed(self, selection): + model, selected_connection_iter = selection.get_selected() + if selected_connection_iter: + self.glade.get_widget("delete_button").set_property('sensitive', + True) + else: + self.glade.get_widget("delete_button").set_property('sensitive', + False) + def on_enabled_toggled(self, widget): + if widget.get_active(): + for widget in ('smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', + 'smtp_pass', 'smtp_tls', 'smtp_from', + 'smtp_recipients'): + self.glade.get_widget(widget).set_property('sensitive', True) + else: + for widget in ('smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', + 'smtp_pass', 'smtp_tls', 'smtp_from', + 'smtp_recipients'): + self.glade.get_widget(widget).set_property('sensitive', False) + + + def on_sound_enabled_toggled(self, widget): + if widget.get_active(): + self.glade.get_widget('sound_path').set_property('sensitive', True) + else: + self.glade.get_widget('sound_path').set_property('sensitive', False) + + def notify_blink(self): + return defer.maybeDeferred(self.tray.blink, True) + + def notify_email(self, title='', message='', smtp_from='', recipients=[]): + client.notifications.notify_email(title, message, smtp_from, recipients) + + def notify_flash(self, title='', message=''): + client.notifications.notify_flash(title, message) + + def notify_popup(self, title='', message=''): + if not self.config['popup_enabled']: + return defer.succeed("Popup notification is not enabled.") + if not POPUP_ENABLED: + return defer.fail("pynotify is not installed") + + if pynotify.init("Deluge"): + icon = gtk.gdk.pixbuf_new_from_file_at_size( + deluge.common.get_pixmap("deluge.svg"), 48, 48) + self.note = pynotify.Notification(title, message) + self.note.set_icon_from_pixbuf(icon) + if not self.note.show(): + log.warning("pynotify failed to show notification") + return defer.fail("pynotify failed to show notification") + return defer.succeed("Notification popup shown") + + def notify_sound(self, sound_path=''): + if not self.config['sound_enabled']: + return defer.succeed("Sound notification not enabled") + if not SOUND_AVAILABLE: + log.warning("pygame is not installed") + return defer.fail("pygame is not installed") + + pygame.init() + try: + if not sound_path: + sound_path = self.config['sound_path'] + alert_sound = pygame.mixer.music + alert_sound.load(sound_path) + alert_sound.play() + except pygame.error, message: + log.warning("pygame failed to play because %s" % (message)) + return defer.fail("Sound notification failed %s" % (message)) + else: + log.info("sound notification played successfully") + return defer.succeed("Sound notification Success") + diff --git a/deluge/plugins/notifications/notifications/manager.py b/deluge/plugins/notifications/notifications/manager.py new file mode 100644 index 000000000..b95658d01 --- /dev/null +++ b/deluge/plugins/notifications/notifications/manager.py @@ -0,0 +1,149 @@ +# +# notifications.py +# +# Copyright (C) 2009 Pedro Algarvio +# +# 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 twisted.internet import defer, threads +from deluge import component +from deluge.core.rpcserver import export +from deluge.log import LOG as log +from deluge.ui.client import client + + +class Notifications(component.Component): + def __init__(self, name): + component.Component.__init__(self, "Notifications") + log.debug("\n\nSTARTING NOTIFICATIONS\n\n") + + def enable(self): + log.debug("\n\nENABLING NOTIFICATIONS\n\n") + + def disable(self): + log.debug("\n\nDISABLING NOTIFICATIONS\n\n") + + + def notify_blink(self): + raise NotImplementedError("%s has not implemented this method" % + self.__class__.__name__) + + def notify_email(self, title='', message='', smtp_from='', recipients=[]): + raise NotImplementedError("%s has not implemented this method" % + self.__class__.__name__) + + def notify_flash(self, title='', message=''): + raise NotImplementedError("%s has not implemented this method" % + self.__class__.__name__) + + def notify_popup(self, title='', message=''): + raise NotImplementedError("%s has not implemented this method" % + self.__class__.__name__) + + def notify_sound(self, sound_path=''): + raise NotImplementedError("%s has not implemented this method" % + self.__class__.__name__) + + def notify(self, + # COMMON + title = '', message='', + # EMAIL + smtp_from='', recipients=[], + # SOUND + sound_path=''): + self.notify_blink() + self.notify_email(title, message, smtp_from, recipients) + self.notify_flash(title, message) + self.notify_popup(title, message) + self.notify_sound(sound_path) + + def _on_notify_sucess(self, result, kind): + log.debug("Notification success using %s: %s", kind, result) + + def _on_notify_failure(self, failure, kind): + log.debug("Notification failure using %s: %s", kind, failure) + +# def _on_torrent_finished_event(self, torrent_id): +# log.debug("\n\nhandler for TorrentFinishedEvent called") +# torrent = component.get("TorrentManager")[torrent_id] +# torrent_status = torrent.get_status({}) +# # Email +# title = _("Finished Torrent %(name)s") % torrent_status +# message = _( +# "This email is to inform you that Deluge has finished " +# "downloading \"%(name)s\", which includes %(num_files)i files." +# "\nTo stop receiving these alerts, simply turn off email " +# "notification in Deluge's preferences.\n\n" +# "Thank you,\nDeluge." +# ) % torrent_status +# +# d0 = defer.maybeDeferred(self.notify_blink) +# d0.addCallback(self._on_notify_sucess, 'blink') +# d0.addErrback(self._on_notify_failure, 'blink') +# log.debug("Blink notification callback yielded") +# +## self.notify_email(title, message) +# d1 = defer.maybeDeferred(self.notify_email, title, message) +# d1.addCallback(self._on_notify_sucess, 'email') +# d1.addErrback(self._on_notify_failure, 'email') +## d. +## yield d +# log.debug("Email notification callback yielded") +# +# d2 = defer.maybeDeferred(self.notify_flash, title, message) +# d2.addCallback(self._on_notify_sucess, 'flash') +# d2.addErrback(self._on_notify_failure, 'flash') +## d. +## yield d +# log.debug("Flash notification callback yielded") +# # Sound +## self.notify_sound() +# d3 = defer.maybeDeferred(self.notify_sound) +# d3.addCallback(self._on_notify_sucess, 'sound') +# d3.addErrback(self._on_notify_failure, 'sound') +## yield d +# log.debug("Sound notification callback yielded") +# +# # Popup +# title = _("Finished Torrent") +# message = _("The torrent \"%(name)s\" including %(num_files)i " +# "has finished downloading.") % torrent_status +## self.notify_popup(title, message) +# d4 = defer.maybeDeferred(self.notify_popup, title, message) +# d4.addCallback(self._on_notify_sucess, 'popup') +# d4.addErrback(self._on_notify_failure, 'popup') +## yield d +# log.debug("Popup notification callback yielded") +# +# d5 = defer.maybeDeferred(self.notify_sound) +# d5.addCallback(self._on_notify_sucess, 'sound') +# d5.addErrback(self._on_notify_failure, 'sound') diff --git a/deluge/plugins/notifications/notifications/webui.py b/deluge/plugins/notifications/notifications/webui.py new file mode 100644 index 000000000..3464ece8f --- /dev/null +++ b/deluge/plugins/notifications/notifications/webui.py @@ -0,0 +1,57 @@ +# +# webui.py +# +# Copyright (C) 2009 Pedro Algarvio +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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 + +# Relative imports +from common import get_resource +from manager import Notifications + +class WebUI(WebPluginBase, Notifications): + + scripts = [get_resource("notifications.js")] + + def enable(self): + Notifications.enable(self) + + def disable(self): + Notifications.disable(self) diff --git a/deluge/plugins/notifications/setup.py b/deluge/plugins/notifications/setup.py new file mode 100755 index 000000000..88056e15f --- /dev/null +++ b/deluge/plugins/notifications/setup.py @@ -0,0 +1,73 @@ +# +# setup.py +# +# Copyright (C) 2009 Pedro Algarvio +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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__ = "Notifications" +__author__ = "Pedro Algarvio" +__author_email__ = "ufs@ufsoft.org" +__version__ = "0.1" +__url__ = "http://dev.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__ 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 + [deluge.plugin.webui] + %s = %s:WebUIPlugin + """ % ((__plugin_name__, __plugin_name__.lower())*3) +) diff --git a/deluge/ui/common.py b/deluge/ui/common.py index 4ea999da8..1748f201a 100644 --- a/deluge/ui/common.py +++ b/deluge/ui/common.py @@ -75,17 +75,16 @@ def decode_string(s, encoding="utf8"): class TorrentInfo(object): """ Collects information about a torrent file. - + :param filename: The path to the torrent :type filename: string - + """ def __init__(self, filename): # Get the torrent data from the torrent file try: log.debug("Attempting to open %s.", filename) - self.__m_filedata = open(filename, "rb").read() - self.__m_metadata = bencode.bdecode(self.__m_filedata) + self.__m_metadata = bencode.bdecode(open(filename, "rb").read()) except Exception, e: log.warning("Unable to open %s: %s", filename, e) raise e @@ -164,7 +163,7 @@ class TorrentInfo(object): def name(self): """ The name of the torrent. - + :rtype: string """ return self.__m_name @@ -173,7 +172,7 @@ class TorrentInfo(object): def info_hash(self): """ The torrents info_hash - + :rtype: string """ return self.__m_info_hash @@ -182,7 +181,7 @@ class TorrentInfo(object): def files(self): """ A list of the files that the torrent contains. - + :rtype: list """ return self.__m_files @@ -191,15 +190,15 @@ class TorrentInfo(object): def files_tree(self): """ A dictionary based tree of the files. - + :: - + { "some_directory": { "some_file": (index, size, download) } } - + :rtype: dictionary """ return self.__m_files_tree @@ -208,21 +207,11 @@ class TorrentInfo(object): def metadata(self): """ The torrents metadata. - + :rtype: dictionary """ return self.__m_metadata - @property - def filedata(self): - """ - The torrents file data. This will be the bencoded dictionary read - from the torrent file. - - :rtype: string - """ - return self.__m_filedata - class FileTree(object): """ Convert a list of paths in a file tree. @@ -230,7 +219,7 @@ class FileTree(object): :param paths: The paths to be converted. :type paths: list """ - + def __init__(self, paths): self.tree = {} diff --git a/deluge/ui/console/main.py b/deluge/ui/console/main.py index afbe6a7ac..749f8d826 100644 --- a/deluge/ui/console/main.py +++ b/deluge/ui/console/main.py @@ -249,7 +249,7 @@ class ConsoleUI(component.Component): """ self.batch_write = batch - if not batch and self.interactive: + if not batch: self.screen.refresh() def write(self, line): diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index fb253c6bf..a9de36a2e 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -211,7 +211,7 @@ class AddTorrentDialog(component.Component): new_row = self.torrent_liststore.append( [info.info_hash, info.name, filename]) self.files[info.info_hash] = info.files - self.infos[info.info_hash] = info.filedata + self.infos[info.info_hash] = info.metadata self.listview_torrents.get_selection().select_iter(new_row) self.set_default_options() @@ -226,11 +226,7 @@ class AddTorrentDialog(component.Component): new_row = None for uri in uris: - s = uri.split("&")[0][20:] - if len(s) == 32: - info_hash = base64.b32decode(s).encode("hex") - elif len(s) == 40: - info_hash = s + info_hash = base64.b32decode(uri.split("&")[0][20:]).encode("hex") if info_hash in self.infos: log.debug("Torrent already in list!") continue @@ -720,6 +716,11 @@ class AddTorrentDialog(component.Component): if row is not None: self.save_torrent_options(row) + torrent_filenames = [] + torrent_magnets = [] + torrent_magnet_options = [] + torrent_options = [] + row = self.torrent_liststore.get_iter_first() while row != None: torrent_id = self.torrent_liststore.get_value(row, 0) @@ -734,16 +735,26 @@ class AddTorrentDialog(component.Component): options["file_priorities"] = file_priorities if deluge.common.is_magnet(filename): + torrent_magnets.append(filename) del options["file_priorities"] - client.core.add_torrent_magnet(filename, options) + torrent_magnet_options.append(options) else: - client.core.add_torrent_file( - os.path.split(filename)[-1], - base64.encodestring(self.infos[torrent_id]), - options) + torrent_filenames.append(filename) + torrent_options.append(options) row = self.torrent_liststore.iter_next(row) + if torrent_filenames: + for i, f in enumerate(torrent_filenames): + client.core.add_torrent_file( + os.path.split(f)[-1], + base64.encodestring(open(f, "rb").read()), + torrent_options[i]) + if torrent_magnets: + for i, m in enumerate(torrent_magnets): + client.core.add_torrent_magnet(m, torrent_magnet_options[i]) + + client.force_call(False) self.hide() def _on_button_apply_clicked(self, widget): diff --git a/deluge/ui/gtkui/common.py b/deluge/ui/gtkui/common.py index aceee3679..72bbbb139 100644 --- a/deluge/ui/gtkui/common.py +++ b/deluge/ui/gtkui/common.py @@ -35,8 +35,6 @@ """Common functions for various parts of gtkui to use.""" -import os - import pygtk pygtk.require('2.0') import gtk, gtk.glade diff --git a/deluge/ui/gtkui/glade/add_torrent_dialog.glade b/deluge/ui/gtkui/glade/add_torrent_dialog.glade index d59c24d20..00aa52cd4 100644 --- a/deluge/ui/gtkui/glade/add_torrent_dialog.glade +++ b/deluge/ui/gtkui/glade/add_torrent_dialog.glade @@ -3,6 +3,7 @@ + 560 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 Add Torrents @@ -13,14 +14,12 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical 2 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -38,7 +37,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -347,7 +345,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 - vertical 5 @@ -428,7 +425,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical Full @@ -661,7 +657,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical 5 @@ -906,8 +901,6 @@ - False - False 0 @@ -922,8 +915,6 @@ - False - False 1 @@ -951,13 +942,11 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical 5 @@ -1064,8 +1053,6 @@ True - False - False 0 @@ -1082,8 +1069,6 @@ True - False - False 1 @@ -1111,13 +1096,11 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical 5 @@ -1263,8 +1246,6 @@ True - False - False 0 @@ -1281,8 +1262,6 @@ True - False - False 1 diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index b8f435716..37f44861c 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -309,7 +309,10 @@ Please see the details below for more information."), details=traceback.format_e dialogs.ErrorDialog( _("Error Starting Daemon"), _("There was an error starting the daemon process. Try running it from a console to see if there is an error.")).run() - + + # We'll try 30 reconnects at 500ms intervals + try_counter = 30 + def on_connect(connector): component.start() def on_connect_fail(result, try_counter): @@ -320,15 +323,15 @@ Please see the details below for more information."), details=traceback.format_e try_counter -= 1 import time time.sleep(0.5) - do_connect(try_counter) + do_connect() return result - - def do_connect(try_counter): + + def do_connect(): client.connect(*host[1:]).addCallback(on_connect).addErrback(on_connect_fail, try_counter) - + if try_connect: - do_connect(6) - break + do_connect() + if self.config["show_connection_manager_on_start"]: # XXX: We need to call a simulate() here, but this could be a bug in twisted diff --git a/deluge/ui/gtkui/ipcinterface.py b/deluge/ui/gtkui/ipcinterface.py index e86c4501a..2ee48fd21 100644 --- a/deluge/ui/gtkui/ipcinterface.py +++ b/deluge/ui/gtkui/ipcinterface.py @@ -35,7 +35,7 @@ import sys -import os +import os.path import base64 import deluge.rencode @@ -71,9 +71,7 @@ class IPCInterface(component.Component): _args = [] for arg in args: if arg.strip(): - if not deluge.common.is_magnet(arg) and not deluge.common.is_url(arg): - arg = os.path.abspath(arg) - _args.append(arg) + _args.append(os.path.abspath(arg)) args = _args socket = os.path.join(deluge.configmanager.get_config_dir("ipc"), "deluge-gtk") @@ -106,20 +104,6 @@ class IPCInterface(component.Component): reactor.run() sys.exit(0) else: - lockfile = socket + ".lock" - log.debug("Checking if lockfile exists: %s", lockfile) - if os.path.lexists(lockfile): - try: - os.kill(int(os.readlink(lockfile)), 0) - except OSError: - log.debug("Removing lockfile since it's stale.") - try: - os.remove(lockfile) - os.remove(socket) - except Exception, e: - log.error("Problem deleting lockfile or socket file!") - log.exception(e) - try: self.factory = Factory() self.factory.protocol = IPCProtocolServer diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 6ccfeb332..70f8bd64c 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -165,7 +165,7 @@ class SystemTray(component.Component): self.tray.set_tooltip(_("Deluge\nNot Connected..")) def shutdown(self): - if self.config["enable_system_tray"]: + if self.config["enable_system_tray"]: self.tray.set_visible(False) def send_status_request(self): @@ -196,9 +196,6 @@ class SystemTray(component.Component): self.upload_rate = deluge.common.fsize(upload_rate) def update(self): - if not self.config["enable_system_tray"]: - return - # Set the tool tip text max_download_speed = self.max_download_speed max_upload_speed = self.max_upload_speed diff --git a/deluge/ui/web/index.html b/deluge/ui/web/index.html index d0fed9d63..0eb3b7cef 100644 --- a/deluge/ui/web/index.html +++ b/deluge/ui/web/index.html @@ -1,7 +1,7 @@ - Deluge: Web UI ${version} + Deluge: Web UI (alpha) ${version}