diff options
Diffstat (limited to 'deluge/plugins/Execute/deluge_execute/core.py')
-rw-r--r-- | deluge/plugins/Execute/deluge_execute/core.py | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/deluge/plugins/Execute/deluge_execute/core.py b/deluge/plugins/Execute/deluge_execute/core.py new file mode 100644 index 0000000..9dcd97a --- /dev/null +++ b/deluge/plugins/Execute/deluge_execute/core.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> +# +# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with +# the additional special exception to link portions of this program with the OpenSSL library. +# See LICENSE for more details. +# + +from __future__ import unicode_literals + +import hashlib +import logging +import os +import time + +from twisted.internet.utils import getProcessOutputAndValue + +import deluge.component as component +from deluge.common import windows_check +from deluge.configmanager import ConfigManager +from deluge.core.rpcserver import export +from deluge.event import DelugeEvent +from deluge.plugins.pluginbase import CorePluginBase + +log = logging.getLogger(__name__) + +DEFAULT_CONFIG = {'commands': []} + +EXECUTE_ID = 0 +EXECUTE_EVENT = 1 +EXECUTE_COMMAND = 2 + +EVENT_MAP = { + 'complete': 'TorrentFinishedEvent', + 'added': 'TorrentAddedEvent', + 'removed': 'TorrentRemovedEvent', +} + + +class ExecuteCommandAddedEvent(DelugeEvent): + """ + Emitted when a new command is added. + """ + + def __init__(self, command_id, event, command): + self._args = [command_id, event, command] + + +class ExecuteCommandRemovedEvent(DelugeEvent): + """ + Emitted when a command is removed. + """ + + def __init__(self, command_id): + self._args = [command_id] + + +class Core(CorePluginBase): + def enable(self): + self.config = ConfigManager('execute.conf', DEFAULT_CONFIG) + event_manager = component.get('EventManager') + self.registered_events = {} + self.preremoved_cache = {} + + # Go through the commands list and register event handlers + for command in self.config['commands']: + event = command[EXECUTE_EVENT] + if event in self.registered_events: + continue + + def create_event_handler(event): + def event_handler(torrent_id, *arg): + self.execute_commands(torrent_id, event, *arg) + + return event_handler + + event_handler = create_event_handler(event) + event_manager.register_event_handler(EVENT_MAP[event], event_handler) + if event == 'removed': + event_manager.register_event_handler( + 'PreTorrentRemovedEvent', self.on_preremoved + ) + self.registered_events[event] = event_handler + + log.debug('Execute core plugin enabled!') + + def on_preremoved(self, torrent_id): + # Get and store the torrent info before it is removed + torrent = component.get('TorrentManager').torrents[torrent_id] + info = torrent.get_status(['name', 'download_location']) + self.preremoved_cache[torrent_id] = [ + torrent_id, + info['name'], + info['download_location'], + ] + + def execute_commands(self, torrent_id, event, *arg): + if event == 'added' and arg[0]: + # No futher action as from_state (arg[0]) is True + return + elif event == 'removed': + torrent_id, torrent_name, download_location = self.preremoved_cache.pop( + torrent_id + ) + else: + torrent = component.get('TorrentManager').torrents[torrent_id] + info = torrent.get_status(['name', 'download_location']) + # Grab the torrent name and download location + # getProcessOutputAndValue requires args to be str + torrent_name = info['name'] + download_location = info['download_location'] + + log.debug('Running commands for %s', event) + + def log_error(result, command): + (stdout, stderr, exit_code) = result + if exit_code: + log.warning('Command "%s" failed with exit code %d', command, exit_code) + if stdout: + log.warning('stdout: %s', stdout) + if stderr: + log.warning('stderr: %s', stderr) + + # Go through and execute all the commands + for command in self.config['commands']: + if command[EXECUTE_EVENT] == event: + command = os.path.expandvars(command[EXECUTE_COMMAND]) + command = os.path.expanduser(command) + + cmd_args = [ + torrent_id.encode('utf8'), + torrent_name.encode('utf8'), + download_location.encode('utf8'), + ] + if windows_check(): + # Escape ampersand on windows (see #2784) + cmd_args = [cmd_arg.replace('&', '^^^&') for cmd_arg in cmd_args] + + if os.path.isfile(command) and os.access(command, os.X_OK): + log.debug('Running %s with args: %s', command, cmd_args) + d = getProcessOutputAndValue(command, cmd_args, env=os.environ) + d.addCallback(log_error, command) + else: + log.error('Execute script not found or not executable') + + def disable(self): + self.config.save() + event_manager = component.get('EventManager') + for event, handler in self.registered_events.items(): + event_manager.deregister_event_handler(event, handler) + log.debug('Execute core plugin disabled!') + + # Exported RPC methods # + @export + def add_command(self, event, command): + command_id = hashlib.sha1(str(time.time()).encode()).hexdigest() + self.config['commands'].append((command_id, event, command)) + self.config.save() + component.get('EventManager').emit( + ExecuteCommandAddedEvent(command_id, event, command) + ) + + @export + def get_commands(self): + return self.config['commands'] + + @export + def remove_command(self, command_id): + for command in self.config['commands']: + if command[EXECUTE_ID] == command_id: + self.config['commands'].remove(command) + component.get('EventManager').emit( + ExecuteCommandRemovedEvent(command_id) + ) + break + self.config.save() + + @export + def save_command(self, command_id, event, cmd): + for i, command in enumerate(self.config['commands']): + if command[EXECUTE_ID] == command_id: + self.config['commands'][i] = (command_id, event, cmd) + break + self.config.save() |