First notifications plugin commit.

Working when not in classic mode. In classic mode, needs some more coding.
This commit is contained in:
Pedro Algarvio 2009-11-22 02:34:23 +00:00
parent 1f58910a38
commit 0723a77214
29 changed files with 1482 additions and 164 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(".")])

View File

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

View File

@ -77,4 +77,5 @@ def create_reader(format, compression=""):
decompressor = DECOMPRESSERS.get(compression)
if decompressor:
reader = decompressor(reader)
return reader

View File

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

View File

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

View File

@ -0,0 +1,58 @@
#
# __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

@ -0,0 +1,42 @@
#
# 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("notifications", os.path.join("data", filename))

View File

@ -0,0 +1,205 @@
#
# 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 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."

View File

@ -0,0 +1,398 @@
<?xml version="1.0"?>
<glade-interface>
<!-- interface-requires gtk+ 2.6 -->
<!-- interface-naming-policy toplevel-contextual -->
<widget class="GtkWindow" id="window">
<child>
<widget class="GtkVBox" id="prefs_box">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkFrame" id="frame2">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<widget class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="left_padding">12</property>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkCheckButton" id="blink_enabled">
<property name="label" translatable="yes">Blink tray icon</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="position">0</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="popup_enabled">
<property name="label" translatable="yes">Show popup</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="position">1</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<child>
<widget class="GtkCheckButton" id="sound_enabled">
<property name="label" translatable="yes">Play sound</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_sound_enabled_toggled"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkFileChooserButton" id="sound_path">
<property name="visible">True</property>
<property name="create_folders">False</property>
</widget>
<packing>
<property name="padding">2</property>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="position">2</property>
</packing>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;UI Notifications&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>
<child>
<widget class="GtkFrame" id="frame3">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<widget class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<property name="left_padding">12</property>
<child>
<widget class="GtkTable" id="prefs_table">
<property name="visible">True</property>
<property name="n_rows">7</property>
<property name="n_columns">4</property>
<property name="column_spacing">2</property>
<property name="row_spacing">2</property>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">Hostname:</property>
<property name="justify">right</property>
</widget>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="smtp_host">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">Port:</property>
<property name="justify">right</property>
</widget>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<widget class="GtkSpinButton" id="smtp_port">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">65535</property>
<property name="invisible_char">&#x25CF;</property>
<property name="width_chars">5</property>
<property name="adjustment">25 1 100 1 10 10</property>
<property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</widget>
<packing>
<property name="left_attach">3</property>
<property name="right_attach">4</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="label" translatable="yes">Username:</property>
<property name="justify">right</property>
</widget>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="smtp_user">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="label" translatable="yes">Password:</property>
</widget>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="smtp_pass">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">&#x25CF;</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<widget class="GtkFrame" id="frame1">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<widget class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="left_padding">12</property>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="spacing">2</property>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<widget class="GtkTreeView" id="smtp_recipients">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
<property name="enable_grid_lines">horizontal</property>
</widget>
</child>
</widget>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkVButtonBox" id="vbuttonbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<property name="layout_style">start</property>
<child>
<widget class="GtkButton" id="add_button">
<property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_add_button_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="delete_button">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_delete_button_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="padding">3</property>
<property name="position">1</property>
</packing>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Recipients&lt;/b&gt;</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="type">label_item</property>
</packing>
</child>
</widget>
<packing>
<property name="right_attach">4</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="smtp_tls">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<child>
<widget class="GtkLabel" id="label9">
<property name="visible">True</property>
<property name="label" translatable="yes">Server requires TLS/SSL</property>
</widget>
</child>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="x_padding">10</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="label" translatable="yes">From:</property>
<property name="justify">right</property>
</widget>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="smtp_from">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="smtp_enabled">
<property name="label" translatable="yes">Enabled</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_enabled_toggled"/>
</widget>
<packing>
<property name="right_attach">4</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label7">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Email Notifications&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">1</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View File

@ -0,0 +1,50 @@
/*
Script: notifications.js
The client-side javascript code for the Notifications 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.
*/
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();

View File

@ -0,0 +1,73 @@
#
# events.py
#
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
#
# 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]

View File

@ -0,0 +1,282 @@
#
# 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 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")

View File

@ -0,0 +1,149 @@
#
# notifications.py
#
# Copyright (C) 2009 Pedro Algarvio <ufs@ufsoft.org>
#
# 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')

View File

@ -0,0 +1,57 @@
#
# 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
# 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)

View File

@ -0,0 +1,73 @@
#
# 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__ = "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)
)

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy toplevel-contextual -->
<widget class="GtkDialog" id="dialog_add_torrent">
<property name="height_request">560</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Add Torrents</property>
@ -13,14 +14,12 @@
<widget class="GtkVBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<widget class="GtkVPaned" id="vpaned1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkFrame" id="frame2">
<property name="visible">True</property>
@ -38,7 +37,6 @@
<widget class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
@ -347,7 +345,6 @@
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<widget class="GtkFrame" id="frame7">
@ -428,7 +425,6 @@
<widget class="GtkVBox" id="vbox4">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkRadioButton" id="radio_full">
<property name="label" translatable="yes">Full</property>
@ -661,7 +657,6 @@
<widget class="GtkVBox" id="vbox5">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<widget class="GtkCheckButton" id="chk_paused">
@ -906,8 +901,6 @@
<signal name="clicked" handler="on_button_cancel_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
@ -922,8 +915,6 @@
<signal name="clicked" handler="on_button_add_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
@ -951,13 +942,11 @@
<widget class="GtkVBox" id="dialog-vbox4">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<widget class="GtkVBox" id="vbox6">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<widget class="GtkHBox" id="hbox7">
@ -1064,8 +1053,6 @@
<property name="use_stock">True</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
@ -1082,8 +1069,6 @@
<property name="use_stock">True</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
@ -1111,13 +1096,11 @@
<widget class="GtkVBox" id="dialog-vbox5">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<widget class="GtkVBox" id="vbox7">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<widget class="GtkHBox" id="hbox8">
@ -1263,8 +1246,6 @@
<property name="use_stock">True</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
@ -1281,8 +1262,6 @@
<property name="use_stock">True</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<!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>
<title>Deluge: Web UI (alpha) ${version}</title>
<link rel="shortcut icon" href="/icons/deluge.png" type="image/png" />
<link rel="icon" href="/icons/deluge.png" type="image/png" />