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/>.
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
39 from gpodder
.liblogger
import log
43 """Decorator to create handler functions in HookManager
45 Calls the specified function in all user extensions that define it.
47 method_name
= func
.__name
__
49 @functools.wraps(func
)
50 def handler(self
, *args
, **kwargs
):
52 for filename
, module
in self
.modules
:
54 callback
= getattr(module
, method_name
, None)
55 if callback
is not None:
56 result
= callback(*args
, **kwargs
)
58 log('Error in %s, function %s: %s', filename
, method_name
, \
59 e
, traceback
=True, sender
=self
)
60 func(self
, *args
, **kwargs
)
66 class HookManager(object):
67 # The class name that has to appear in a hook module
68 HOOK_CLASS
= 'gPodderHooks'
71 """Create a new hook manager"""
74 for filename
in glob
.glob(os
.path
.join(gpodder
.home
, 'Hooks', '*.py')):
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
)
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).
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
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:
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.
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
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
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
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
155 # FIXME: When multiple hooks are used, concatenate the resulting lists
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