Add episode context menu hook, refactor playback code
[gpodder.git] / src / gpodder / hooks.py
blobcfd5310f9069d3aa7b5f9a8ad7401fe080724aa3
1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2009 Thomas Perl and the gPodder Team
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 """
20 Loads and executes user hooks.
22 Hooks are python scripts in the "Hooks" folder of $GPODDER_HOME. Each script
23 must define a class named "gPodderHooks", otherwise it will be ignored.
25 The hooks class defines several callbacks that will be called by the
26 gPodder application at certain points. See the methods defined below
27 for a list on what these callbacks are and the parameters they take.
29 For an example extension see examples/hooks.py
30 """
32 import glob
33 import imp
34 import os
35 import functools
37 import gpodder
39 from gpodder.liblogger import log
42 def call_hooks(func):
43 """Decorator to create handler functions in HookManager
45 Calls the specified function in all user extensions that define it.
46 """
47 method_name = func.__name__
49 @functools.wraps(func)
50 def handler(self, *args, **kwargs):
51 result = None
52 for filename, module in self.modules:
53 try:
54 callback = getattr(module, method_name, None)
55 if callback is not None:
56 result = callback(*args, **kwargs)
57 except Exception, e:
58 log('Error in %s, function %s: %s', filename, method_name, \
59 e, traceback=True, sender=self)
60 func(self, *args, **kwargs)
61 return result
63 return handler
66 class HookManager(object):
67 # The class name that has to appear in a hook module
68 HOOK_CLASS = 'gPodderHooks'
70 def __init__(self):
71 """Create a new hook manager"""
72 self.modules = []
74 for filename in glob.glob(os.path.join(gpodder.home, 'Hooks', '*.py')):
75 try:
76 module = self._load_module(filename)
77 if module is not None:
78 self.modules.append((filename, module))
79 log('Module loaded: %s', filename, sender=self)
80 except Exception, e:
81 log('Error loading %s: %s', filename, e, sender=self)
83 def has_modules(self):
84 """Check whether this manager manages any modules
86 Returns True if there is at least one module that is
87 managed by this manager, or False if no modules are
88 loaded (in this case, the hook manager can be deactivated).
89 """
90 return bool(self.modules)
92 def _load_module(self, filepath):
93 """Load a Python module by filename
95 Returns an instance of the HOOK_CLASS class defined
96 in the module, or None if the module does not contain
97 such a class.
98 """
99 basename, extension = os.path.splitext(os.path.basename(filepath))
100 module = imp.load_module(basename, file(filepath, 'r'), filepath, (extension, 'r', imp.PY_SOURCE))
101 hook_class = getattr(module, HookManager.HOOK_CLASS, None)
103 if hook_class is None:
104 return None
105 else:
106 return hook_class()
108 # Define all known handler functions here, decorate them with the
109 # "call_hooks" decorator to forward all calls to hook scripts that have
110 # the same function defined in them. If the handler functions here contain
111 # any code, it will be called after all the hooks have been called.
113 @call_hooks
114 def on_podcast_updated(self, podcast):
115 """Called when a podcast feed was updated
117 This hook will be called even if there were no new episodes.
119 @param podcast: A gpodder.model.PodcastChannel instance
121 pass
123 @call_hooks
124 def on_podcast_save(self, podcast):
125 """Called when a podcast is saved to the database
127 This hooks will be called when the user edits the metadata of
128 the podcast or when the feed was updated.
130 @param podcast: A gpodder.model.PodcastChannel instance
132 pass
134 @call_hooks
135 def on_episode_save(self, episode):
136 """Called when an episode is saved to the database
138 This hook will be called when a new episode is added to the
139 database or when the state of an existing episode is changed.
141 @param episode: A gpodder.model.PodcastEpisode instance
143 pass
145 @call_hooks
146 def on_episode_downloaded(self, episode):
147 """Called when an episode has been downloaded
149 You can retrieve the filename via episode.local_filename(False)
151 @param episode: A gpodder.model.PodcastEpisode instance
153 pass
155 # FIXME: When multiple hooks are used, concatenate the resulting lists
156 @call_hooks
157 def on_episodes_context_menu(self, episodes):
158 """Called when the episode list context menu is opened
160 You can add additional context menu entries here. You have to
161 return a list of tuples, where the first item is a label and
162 the second item is a callable that will get the episode as its
163 first and only parameter.
165 Example return value:
167 [('Mark as new', lambda episodes: ...)]
169 @param episode: A list of gpodder.model.PodcastEpisode instances