Share the code for on-demand resources
[gregoa/zavai.git] / zavai / registry.py
1 # registry - zavai resource registry
2 #
3 # Copyright (C) 2009  Enrico Zini <enrico@enricozini.org>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
19 import zavai
20 import gtk
21
22 def get_parent(s):
23     "Get the parent name for s"
24     pos = s.rfind(".")
25     if pos == -1: return None
26     res = s[:pos]
27     if res == "menu": return None
28     return res
29
30 def default_label(s):
31     "Compute a default label given the last element of a path"
32     pos = s.rfind(".")
33     if pos == -1: return s.capitalize()
34     return s[pos+1:].capitalize()
35
36
37 class Registry(object):
38     """Collection of resources.
39
40     Various factories can be registered by name on the registry. Then when an
41     object is requested for the first time, it is created using the factory.
42     When it is requested again, the existing object is reused.
43     """
44
45     def __init__(self):
46         self.factories = dict()
47         self.objects = dict()
48         self.labels = dict()
49
50     def register(self, obj, name=None):
51         """Register an object at the given path.
52
53         Name the path to this object, like "menu.gps.monitor".
54         """
55         if name is None:
56             name = obj.props.name
57
58         if name in self.objects:
59             return KeyError("%s is already registered", name)
60         zavai.info("Registering", name)
61         self.objects[name] = obj
62
63         if name.startswith("menu."):
64             self.add_to_menu(name)
65
66     def register_factory(self, fac, name, label = None):
67         """Register an object factory at the given path.
68
69         Name the path to this object, like "menu.gps.monitor".
70         """
71         if name in self.factories:
72             return KeyError("Factory %s is already registered", name)
73         zavai.info("Registering factory", name)
74         self.factories[name] = fac
75         if label is not None: self.labels[name] = label
76
77     def add_to_menu(self, name):
78         "Add the applet with the given name to the menu structure"
79         parent = get_parent(name)
80         if parent is not None:
81             zavai.info("Add to menu", name, parent)
82             menu = self.menu(parent)
83
84             obj = self.resource(name)
85             if isinstance(obj, gtk.ToggleAction):
86                 menu.add_child(zavai.ToggleButton(self, name, action=obj))
87             elif isinstance(obj, gtk.Action):
88                 menu.add_child(zavai.LinkButton(self, name, action=obj))
89             else:
90                 menu.add_child(zavai.LinkButton(self, name, self.label(name)))
91
92     def label(self, name):
93         "Return the label for the object with the given name"
94         res = self.labels.get(name)
95         if res is not None:
96             return res
97         try:
98             obj = self.resource(name)
99             return obj.props.label
100         except:
101             return default_label(name)
102
103     def resource(self, name):
104         """Get a resource from the registry.
105
106         If no resource exists at `name` but there is a factory, instantiate the
107         object using the factory.
108
109         If not even a factory exists at `name`, returns None.
110         """
111         res = self.objects.get(name, None)
112         if res is None:
113             fac = self.factories.get(name, None)
114             if fac is not None:
115                 res = self.objects[name] = fac(self, name)
116         return res
117
118     def menu(self, name):
119         """Get a menu resource, automatically creating it if it is missing.
120
121         Menus are created automatically linked to a parent menu, according to
122         the hierarchy in `name`.
123         """
124         res = self.resource(name)
125         if res is None:
126             # Check if it is a toplevel menu
127             if name.startswith("menu."):
128                 parent = get_parent(name[5:])
129                 if parent is not None:
130                     parent = "menu." + parent
131             else:
132                 parent = get_parent(name)
133
134             res = zavai.Menu(self, name, parent)
135             self.register(res, name)
136         return res
137
138     def shutdown(self):
139         """Shut down all objects in this Registry.
140
141         After shutting down, all objects cannot be used anymore"""
142         for o in self.objects.itervalues():
143             if isinstance(o, Resource):
144                 o.shutdown()
145         self.objects.clear()
146
147 class Resource(object):
148     def __init__(self):
149         super(Resource, self).__init__()
150
151     def shutdown(self):
152         """Shut down this resource.
153
154         Normally one does nothing here, but it is important to give resources a
155         chance to do cleanup when the program quits.
156
157         This can be used for tasks like closing the tags on a GPX track,
158         releasing a FSO resource, restoring mixer settings and so on.
159         """
160         pass
161
162 class Service(Resource):
163     "Service that is activated only when someone is listening"
164     def __init__(self):
165         super(Service, self).__init__()
166         self.callbacks = set()
167
168     def shutdown(self):
169         self.stop()
170
171     def start(self):
172         "Activate the service"
173         pass
174
175     def stop(self):
176         "Deactivate the service"
177         pass
178
179     def notify(self, *args, **kw):
180         "Call all callbacks with the given parameters"
181         for cb in self.callbacks:
182             cb(*args, **kw)
183
184     def connect(self, callback):
185         "Connect a callback to this resource, activating it if needed"
186         do_start = not self.callbacks
187         self.callbacks.add(callback)
188         if do_start:
189             self.start()
190
191     def disconnect(self, callback):
192         "Disconnect a callback to this resource, activating it if needed"
193         if not self.callbacks: return
194         self.callbacks.discard(callback)
195         if not self.callbacks:
196             self.stop()