summaryrefslogtreecommitdiff
path: root/extensions/fts++/zeitgeist-fts.vala
blob: 0f8ecbc6d11bc85738cfe3bce20e9ee904aa1e84 (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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/* zeitgeist-fts.vala
 *
 * Copyright © 2012 Canonical Ltd.
 * Copyright © 2012 Michal Hruby <michal.mhr@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * 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 General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

namespace Zeitgeist
{

    [DBus (name = "org.freedesktop.DBus")]
    public interface RemoteDBus : Object
    {
        public abstract bool name_has_owner (string name) throws IOError;
    }

    public class FtsDaemon : Object, RemoteSimpleIndexer, RemoteMonitor
    {
        //const string DBUS_NAME = "org.gnome.zeitgeist.Fts";
        const string DBUS_NAME = "org.gnome.zeitgeist.SimpleIndexer";
        const string ZEITGEIST_DBUS_NAME = "org.gnome.zeitgeist.Engine";
        private static bool show_version_info = false;
        private static string log_level = "";

        const OptionEntry[] options =
        {
            {
                "version", 'v', 0, OptionArg.NONE, out show_version_info,
                "Print program's version number and exit", null
            },
            {
                "log-level", 0, 0, OptionArg.STRING, out log_level,
                "How much information should be printed; possible values: " +
                "DEBUG, INFO, WARNING, ERROR, CRITICAL", "LEVEL"
            },
            {
                null
            }
        };

        private static FtsDaemon? instance;
        private static MainLoop mainloop;
        private static bool name_acquired = false;
        private static bool zeitgeist_up = false;

        private DbReader engine;
        private Indexer indexer;

        private uint indexer_register_id;
        private uint monitor_register_id;
        private unowned DBusConnection connection;

        public FtsDaemon () throws EngineError
        {
            engine = new DbReader ();
            engine.database.set_cache_size (16);
            indexer = new Indexer (engine);
        }

        private void close ()
        {
            engine.close ();
            indexer = null; // close the index
        }

        public void register_dbus_object (DBusConnection conn) throws IOError
        {
            connection = conn;
            indexer_register_id = conn.register_object<RemoteSimpleIndexer> (
                    "/org/gnome/zeitgeist/index/activity", this);
            monitor_register_id = conn.register_object<RemoteMonitor> (
                    "/org/gnome/zeitgeist/monitor/special", this);
        }

        public void unregister_dbus_object ()
        {
            if (indexer_register_id != 0)
            {
                connection.unregister_object (indexer_register_id);
                indexer_register_id = 0;
            }

            if (monitor_register_id != 0)
            {
                connection.unregister_object (monitor_register_id);
                monitor_register_id = 0;
            }
        }

        public async void notify_insert (Variant time_range, Variant events)
            throws Error
        {
            debug ("got insertion notification");
            var events_arr = Events.from_variant (events);
            indexer.index_events (events_arr);
        }

        public async void notify_delete (Variant time_range, uint32[] event_ids)
            throws IOError
        {
            debug ("got deletion notification");
            indexer.delete_events (event_ids);
        }

        public async void search (string query_string, Variant time_range,
                                  Variant filter_templates,
                                  uint offset, uint count, uint result_type,
                                  Cancellable? cancellable,
                                  out Variant events, out uint matches)
            throws Error
        {
            var tr = new TimeRange.from_variant (time_range);
            var templates = Events.from_variant (filter_templates);
            var results = instance.indexer.search (query_string,
                                                   tr,
                                                   templates,
                                                   offset,
                                                   count,
                                                   (ResultType) result_type,
                                                   out matches);

            events = Events.to_variant (results);
        }

        public async void search_with_relevancies (
                                  string query_string, Variant time_range,
                                  Variant filter_templates,
                                  uint storage_state, uint offset,
                                  uint count, uint result_type,
                                  Cancellable? cancellable,
                                  out Variant events, out double[] relevancies,
                                  out uint matches)
            throws Error
        {
            var tr = new TimeRange.from_variant (time_range);
            var templates = Events.from_variant (filter_templates);
            var results = instance.indexer.search_with_relevancies (
                    query_string, tr, templates, (StorageState) storage_state,
                    offset, count, (ResultType) result_type,
                    out relevancies, out matches);

            events = Events.to_variant (results);
        }

        private static void name_acquired_callback (DBusConnection conn)
        {
            name_acquired = true;
        }

        private static void name_lost_callback (DBusConnection? conn)
        {
            if (conn == null)
            {
                // something happened to our bus connection
                mainloop.quit ();
            }
            else if (instance != null && name_acquired)
            {
                // we owned the name and we lost it... what to do?
                mainloop.quit ();
            }
        }

        private static void zeitgeist_vanished ()
        {
            if (zeitgeist_up)
            {
                // client APIs query us via zeitgeist, so quit if ZG goes away
                mainloop.quit ();
            }
            zeitgeist_up = false;
        }

        static void run ()
            throws Error
        {
            DBusConnection connection = Bus.get_sync (BusType.SESSION);
            var proxy = connection.get_proxy_sync<RemoteDBus> (
                "org.freedesktop.DBus", "/org/freedesktop/DBus",
                DBusProxyFlags.DO_NOT_LOAD_PROPERTIES);
            if (proxy.name_has_owner (DBUS_NAME))
            {
                throw new EngineError.EXISTING_INSTANCE (
                    "The FTS daemon is running already.");
            }

            /* setup Engine instance and register objects on dbus */
            try
            {
                instance = new FtsDaemon ();
                instance.register_dbus_object (connection);
            }
            catch (Error err)
            {
                if (err is EngineError.DATABASE_CANTOPEN)
                {
                    warning ("Could not access the database file.\n" +
                        "Please check the permissions of file %s.",
                        Utils.get_database_file_path ());
                }
                else if (err is EngineError.DATABASE_BUSY)
                {
                    warning ("It looks like another Zeitgeist instance " +
                        "is already running (the database is locked).");
                }
                throw err;
            }

            uint owner_id = Bus.own_name_on_connection (connection,
                DBUS_NAME,
                BusNameOwnerFlags.NONE,
                name_acquired_callback,
                name_lost_callback);

            Bus.watch_name (BusType.SESSION, ZEITGEIST_DBUS_NAME, 0,
                () => { zeitgeist_up = true; },
                zeitgeist_vanished);

            mainloop = new MainLoop ();
            mainloop.run ();

            if (instance != null)
            {
                // Close any database connections
                instance.close ();

                // Release the bus name
                Bus.unown_name (owner_id);
                instance.unregister_dbus_object ();
                instance = null;

                // make sure we send quit reply
                try
                {
                    connection.flush_sync ();
                }
                catch (Error e)
                {
                    warning ("%s", e.message);
                }
            }
        }

        static void safe_exit ()
        {
            mainloop.quit ();
        }

        static int main (string[] args)
        {
            // FIXME: the cat process xapian spawns won't like this and we
            // can freeze if it dies
            Posix.signal (Posix.SIGHUP, safe_exit);
            Posix.signal (Posix.SIGINT, safe_exit);
            Posix.signal (Posix.SIGTERM, safe_exit);

            var opt_context = new OptionContext (" - Zeitgeist FTS daemon");
            opt_context.add_main_entries (options, null);

            try
            {
                opt_context.parse (ref args);

                if (show_version_info)
                {
                    stdout.printf (Config.VERSION + "\n");
                    return 0;
                }

                LogLevelFlags discarded = LogLevelFlags.LEVEL_DEBUG;
                if (log_level != null)
                {
                    var ld = LogLevelFlags.LEVEL_DEBUG;
                    var li = LogLevelFlags.LEVEL_INFO;
                    var lm = LogLevelFlags.LEVEL_MESSAGE;
                    var lw = LogLevelFlags.LEVEL_WARNING;
                    var lc = LogLevelFlags.LEVEL_CRITICAL;
                    switch (log_level.up ())
                    {
                        case "DEBUG":
                            discarded = 0;
                            break;
                        case "INFO":
                            discarded = ld;
                            break;
                        case "WARNING":
                            discarded = ld | li | lm;
                            break;
                        case "CRITICAL":
                            discarded = ld | li | lm | lw;
                            break;
                        case "ERROR":
                            discarded = ld | li | lm | lw | lc;
                            break;
                    }
                }
                if (discarded != 0)
                {
                    GLib.Log.set_handler ("", discarded, () => {});
                }
                else
                {
                    Environment.set_variable ("G_MESSAGES_DEBUG", "all", true);
                }

                run ();
            }
            catch (Error err)
            {
                if (err is EngineError.DATABASE_CANTOPEN)
                    return 21;
                if (err is EngineError.DATABASE_BUSY)
                    return 22;

                warning ("%s", err.message);
                return 1;
            }

            return 0;
        }

    }

}

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