summaryrefslogtreecommitdiffstats
path: root/deluge/plugins/Execute/deluge_execute/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/plugins/Execute/deluge_execute/core.py')
-rw-r--r--deluge/plugins/Execute/deluge_execute/core.py182
1 files changed, 182 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..6d33e54
--- /dev/null
+++ b/deluge/plugins/Execute/deluge_execute/core.py
@@ -0,0 +1,182 @@
+#
+# 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.
+#
+
+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(b'&', b'^^^&') 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()