First notifications plugin commit.
Working when not in classic mode. In classic mode, needs some more coding.
This commit is contained in:
parent
1f58910a38
commit
0723a77214
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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__
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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(".")])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -77,4 +77,5 @@ def create_reader(format, compression=""):
|
|||
decompressor = DECOMPRESSERS.get(compression)
|
||||
if decompressor:
|
||||
reader = decompressor(reader)
|
||||
|
||||
return reader
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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))
|
|
@ -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."
|
|
@ -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"><b>UI Notifications</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">label_item</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<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">●</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">●</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">●</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">●</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"><b>Recipients</b></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">●</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"><b>Email Notifications</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="type">label_item</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</glade-interface>
|
|
@ -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();
|
|
@ -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]
|
|
@ -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")
|
||||
|
|
@ -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')
|
|
@ -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)
|
|
@ -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)
|
||||
)
|
|
@ -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 = {}
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in New Issue