summaryrefslogtreecommitdiff
path: root/src/extension.vala
blob: 377b7871301ea80ac9400bc17dde7eb37a723198 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
/* extension.vala
 *
 * Copyright © 2011 Manish Sinha <manishsinha@ubuntu.com>
 * Copyright © 2011 Michal Hruby <michal.mhr@gmail.com>
 * Copyright © 2011 Collabora Ltd.
 *             By Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

namespace Zeitgeist
{
    /**
     * Base class for all extensions
     *
     * The constructor of an Extension object takes the Engine object
     * it extends as the only argument.
     *
     * Extensions have a set of hooks with which they can control how
     * events are inserted and retrieved from the log.
     *
     * Additionally, extensions may create their own D-Bus interface
     * over which they can expose their own methods.
     */
    public abstract class Extension : Object
    {
        public unowned Engine engine { get; construct set; }

        /**
         * This method gets called before Zeitgeist stops.
         *
         * Execution of this method isn't guaranteed, and you shouldn't do
         * anything slow in there.
         */
        public virtual void unload ()
        {
        }

        /**
         * Hook applied to all events before they are inserted into the
         * log. The returned event is progressively passed through all
         * extensions before the final result is inserted.
         *
         * To block an event completely simply replace it with NULL.
         * The event may also be modified or completely substituted for
         * another event.
         *
         * @param events: A GenericArray of Event instances
         * @param sender: The D-Bus bus name of the client or NULL
         */
        public virtual void pre_insert_events (GenericArray<Event?> events,
            BusName? sender)
        {
        }

        /**
         * Hook applied to all events after they are inserted into the log.
         *
         * The inserted events will have been updated to include their new
         * ID.
         *
         * @param events: A GenericArray of Event instances
         * @param sender: The D-Bus bus name of the client or NULL
         */
        public virtual void post_insert_events (GenericArray<Event?> events,
            BusName? sender)
        {
        }

        /**
         * Hook applied before events are deleted from the log.
         *
         * @param ids: A list with the IDs of the events whose deletion
         *     is being requested
         * @param sender: The unique DBus name for the client triggering
         *     the delete, or NULL
         * @return: The filtered list of event IDs which should be deleted,
         *     or NULL to specify no change
         */
        public virtual uint32[]? pre_delete_events (uint32[] ids,
            BusName? sender)
        {
            return null;
        }

        /**
         * Hook applied after events have been deleted from the log.
         *
         * @param ids: A list with the IDs of the events that have been deleted
         * @param sender: The unique DBus name for the client triggering the delete
         */
        public virtual void post_delete_events (uint32[] ids, BusName? sender)
        {
        }

        /**
         * Store `data' under the given (extension unique) key, overwriting any
         * previous value.
         */
        protected void store_config (string key, Variant data)
        {
            engine.extension_store.store (get_type ().name (), key, data);
        }

        /**
         * Retrieve data this extension previously stored under the given key,
         * or null if there is no such data.
         *
         * @param key: key under which the data is stored
         * @param format: type string for the resulting Variant
         */
        protected Variant? retrieve_config (string key, string format)
        {
            VariantType type = new VariantType(format);
            return engine.extension_store.retrieve (
                get_type ().name (), key, type);
        }
    }

    [CCode (has_target = false)]
    public delegate Type RegisterExtensionFunc (TypeModule module);

    public abstract class ExtensionLoader: TypeModule
    {
        public Type extension_type { get; protected set; }

        public virtual Extension? create_instance (Engine engine)
        {
            if (this.use ())
            {
                if (extension_type == Type.INVALID) return null;
                Extension? instance = Object.@new (extension_type,
                    "engine", engine) as Extension;
                debug ("Loaded extension: %s", extension_type.name ());
                this.unuse ();
                return instance;
            }

            return null;
        }
    }

    public class ModuleLoader: ExtensionLoader
    {
        public string module_path { get; construct; }

        private Module? module = null;

        public ModuleLoader (string module_path)
        {
            Object (module_path: module_path);
        }

        construct
        {
            set_name (module_path);
        }

        protected override bool load ()
        {
            module = Module.open (module_path, ModuleFlags.BIND_LOCAL);
            if (module == null)
            {
                warning ("%s", Module.error ());
                return false;
            }

            void* func_ptr;
            if (module.symbol ("zeitgeist_extension_register", out func_ptr))
            {
                RegisterExtensionFunc func = (RegisterExtensionFunc) func_ptr;
                extension_type = func (this);

                if (extension_type.is_a (typeof (Extension)) == false)
                {
                    extension_type = Type.INVALID;
                    warning ("Type implemented in \"%s\" does not subclass " +
                        "Zeitgeist.Extension!", module_path);
                    return false;
                }

                // according to docs initialized TypeModule is not supposed
                // to be unreferenced, so we do this
                this.ref ();
            }
            else
            {
                warning ("%s", Module.error ());
                return false;
            }

            return true;
        }

        protected override void unload ()
        {
            module = null;
        }
    }

    public class BuiltinExtension: ExtensionLoader
    {
        private RegisterExtensionFunc reg_func;

        public BuiltinExtension (RegisterExtensionFunc func)
        {
            Object ();
            reg_func = func;
        }

        construct
        {
            set_name ("builtin");
        }

        protected override bool load ()
        {
            if (extension_type == Type.INVALID)
            {
                extension_type = reg_func (this);

                if (extension_type.is_a (typeof (Extension)) == false)
                {
                    warning ("Type \"%s\" implemented by [%p] does not " +
                        "subclass Zeitgeist.Extension!",
                        extension_type.name (), this.reg_func);
                    extension_type = Type.INVALID;
                    return false;
                }

                // according to docs initialized TypeModule is not supposed
                // to be unreferenced, so we do this
                this.ref ();
            }
            else
            {
                // this is still needed
                extension_type = reg_func (this);
            }

            return true;
        }

        protected override void unload ()
        {
        }

    }

}

// vim:expandtab:ts=4:sw=4