]> www.fi.muni.cz Git - evince.git/blob - shell/ev-daemon.c
[ev-daemon] Add spawn option to FindDocument
[evince.git] / shell / ev-daemon.c
1 /* ev-metadata.c
2  *  this file is part of evince, a gnome document viewer
3  *
4  * Copyright (C) 2009 Carlos Garcia Campos  <carlosgc@gnome.org>
5  * Copyright © 2010 Christian Persch
6  *
7  * Evince is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * Evince is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21
22 #include "config.h"
23
24 #include <glib.h>
25 #include <glib/gstdio.h>
26 #include <gio/gio.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/wait.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33
34 #define EV_DBUS_DAEMON_NAME             "org.gnome.evince.Daemon"
35 #define EV_DBUS_DAEMON_INTERFACE_NAME   "org.gnome.evince.Daemon"
36 #define EV_DBUS_DAEMON_OBJECT_PATH      "/org/gnome/evince/Daemon"
37
38 #define DAEMON_TIMEOUT (30) /* seconds */
39
40 #define LOG g_printerr
41
42 static GList *ev_daemon_docs = NULL;
43 static guint kill_timer_id;
44 static GHashTable *pending_invocations = NULL;
45
46 typedef struct {
47         gchar *dbus_name;
48         gchar *uri;
49         guint  watch_id;
50 } EvDoc;
51
52 static void
53 ev_doc_free (EvDoc *doc)
54 {
55         if (!doc)
56                 return;
57
58         g_free (doc->dbus_name);
59         g_free (doc->uri);
60
61         g_bus_unwatch_name (doc->watch_id);
62
63         g_free (doc);
64 }
65
66 static EvDoc *
67 ev_daemon_find_doc (const gchar *uri)
68 {
69         GList *l;
70
71         for (l = ev_daemon_docs; l != NULL; l = l->next) {
72                 EvDoc *doc = (EvDoc *)l->data;
73
74                 if (strcmp (doc->uri, uri) == 0)
75                         return doc;
76         }
77
78         return NULL;
79 }
80
81 static void
82 ev_daemon_stop_killtimer (void)
83 {
84         if (kill_timer_id != 0)
85                 g_source_remove (kill_timer_id);
86         kill_timer_id = 0;
87 }
88
89 static gboolean
90 ev_daemon_shutdown (gpointer user_data)
91 {
92         GMainLoop *loop = (GMainLoop *) user_data;
93
94         LOG ("Timeout; exiting daemon.\n");
95
96         if (g_main_loop_is_running (loop))
97                 g_main_loop_quit (loop);
98
99         return FALSE;
100 }
101
102 static void
103 ev_daemon_maybe_start_killtimer (gpointer data)
104 {
105         ev_daemon_stop_killtimer ();
106         if (ev_daemon_docs != NULL)
107                 return;
108
109         kill_timer_id = g_timeout_add_seconds (DAEMON_TIMEOUT,
110                                                (GSourceFunc) ev_daemon_shutdown,
111                                                data);
112 }
113
114 static gboolean
115 convert_metadata (const gchar *metadata)
116 {
117         GFile   *file;
118         char    *argv[3];
119         gint     exit_status;
120         GFileAttributeInfoList *namespaces;
121         gboolean supported = FALSE;
122         GError  *error = NULL;
123         gboolean retval;
124
125         /* If metadata is not supported for a local file
126          * is likely because and old gvfs version is running.
127          */
128         file = g_file_new_for_path (metadata);
129         namespaces = g_file_query_writable_namespaces (file, NULL, NULL);
130         if (namespaces) {
131                 gint i;
132
133                 for (i = 0; i < namespaces->n_infos; i++) {
134                         if (strcmp (namespaces->infos[i].name, "metadata") == 0) {
135                                 supported = TRUE;
136                                 break;
137                         }
138                 }
139                 g_file_attribute_info_list_unref (namespaces);
140         }
141         if (!supported) {
142                 g_warning ("GVFS metadata not supported. "
143                            "Evince will run without metadata support.\n");
144                 g_object_unref (file);
145                 return FALSE;
146         }
147         g_object_unref (file);
148
149         argv[0] = g_build_filename (LIBEXECDIR, "evince-convert-metadata", NULL);
150         argv[1] = (char *) metadata;
151         argv[2] = NULL;
152
153         retval = g_spawn_sync (NULL /* wd */, argv, NULL /* env */,
154                                0, NULL, NULL, NULL, NULL,
155                                &exit_status, &error);
156         g_free (argv[0]);
157
158         if (!retval) {
159                 g_printerr ("Error migrating metadata: %s\n", error->message);
160                 g_error_free (error);
161         }
162
163         return retval && WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0;
164 }
165
166 static void
167 ev_migrate_metadata (void)
168 {
169         gchar       *updated;
170         gchar       *metadata;
171         gchar       *dot_dir;
172         const gchar *userdir;
173
174         userdir = g_getenv ("GNOME22_USER_DIR");
175         if (userdir) {
176                 dot_dir = g_build_filename (userdir, "evince", NULL);
177         } else {
178                 dot_dir = g_build_filename (g_get_home_dir (),
179                                             ".gnome2",
180                                             "evince",
181                                             NULL);
182         }
183
184         updated = g_build_filename (dot_dir, "migrated-to-gvfs", NULL);
185         if (g_file_test (updated, G_FILE_TEST_EXISTS)) {
186                 /* Already migrated */
187                 g_free (updated);
188                 g_free (dot_dir);
189                 return;
190         }
191
192         metadata = g_build_filename (dot_dir, "ev-metadata.xml", NULL);
193         if (g_file_test (metadata, G_FILE_TEST_EXISTS)) {
194                 if (convert_metadata (metadata)) {
195                         gint fd;
196
197                         fd = g_creat (updated, 0600);
198                         if (fd != -1) {
199                                 close (fd);
200                         }
201                 }
202         }
203
204         g_free (dot_dir);
205         g_free (updated);
206         g_free (metadata);
207 }
208
209 static gboolean
210 spawn_evince (const gchar *uri)
211 {
212         gchar   *argv[3];
213         gboolean retval;
214         GError  *error = NULL;
215
216         /* TODO Check that the uri exists */
217         argv[0] = g_build_filename (BINDIR, "evince", NULL);
218         argv[1] = (gchar *) uri;
219         argv[2] = NULL;
220
221         retval = g_spawn_async (NULL /* wd */, argv, NULL /* env */,
222                                 0, NULL, NULL, NULL, &error);
223         if (!retval) {
224                 g_printerr ("Error spawning evince for uri %s: %s\n", uri, error->message);
225                 g_error_free (error);
226         }
227         g_free (argv[0]);
228
229         return retval;
230 }
231
232 static void
233 name_appeared_cb (GDBusConnection *connection,
234                   const gchar     *name,
235                   const gchar     *name_owner,
236                   gpointer         user_data)
237 {
238         LOG ("Watch name'%s' appeared with owner '%s'\n", name, name_owner);
239 }
240
241 static void
242 name_vanished_cb (GDBusConnection *connection,
243                   const gchar     *name,
244                   gpointer         user_data)
245 {
246         GList *l;
247
248         LOG ("Watch name'%s' disappeared\n", name);
249
250         for (l = ev_daemon_docs; l != NULL; l = l->next) {
251                 EvDoc *doc = (EvDoc *) l->data;
252
253                 if (strcmp (doc->dbus_name, name) != 0)
254                         continue;
255
256                 LOG ("Watch found URI '%s' for name; removing\n", doc->uri);
257
258                 ev_daemon_docs = g_list_delete_link (ev_daemon_docs, l);
259                 ev_doc_free (doc);
260                 
261                 ev_daemon_maybe_start_killtimer (user_data);
262                 return;
263         }
264 }
265
266 static void
267 process_pending_invocations (const gchar *uri,
268                              const gchar *dbus_name)
269 {
270         GList *l;
271         GList *uri_invocations;
272
273         LOG ("RegisterDocument process pending invocations for URI %s\n", uri);
274         uri_invocations = g_hash_table_lookup (pending_invocations, uri);
275
276         for (l = uri_invocations; l != NULL; l = l->next) {
277                 GDBusMethodInvocation *invocation;
278
279                 invocation = (GDBusMethodInvocation *)l->data;
280                 g_dbus_method_invocation_return_value (invocation,
281                                                        g_variant_new ("(s)", dbus_name));
282         }
283
284         g_list_free (uri_invocations);
285         g_hash_table_remove (pending_invocations, uri);
286 }
287
288 static void
289 method_call_cb (GDBusConnection       *connection,
290                 const gchar           *sender,
291                 const gchar           *object_path,
292                 const gchar           *interface_name,
293                 const gchar           *method_name,
294                 GVariant              *parameters,
295                 GDBusMethodInvocation *invocation,
296                 gpointer               user_data)
297 {
298         if (g_strcmp0 (interface_name, EV_DBUS_DAEMON_INTERFACE_NAME) != 0)
299                 return;
300
301         if (g_strcmp0 (method_name, "RegisterDocument") == 0) {
302                 EvDoc       *doc;
303                 const gchar *uri;
304
305                 g_variant_get (parameters, "(&s)", &uri);
306
307                 doc = ev_daemon_find_doc (uri);
308                 if (doc != NULL) {
309                         LOG ("RegisterDocument found owner '%s' for URI '%s'\n", doc->dbus_name, uri);
310                         g_dbus_method_invocation_return_value (invocation,
311                                                                g_variant_new ("(s)", doc->dbus_name));
312                         return;
313                 }
314
315                 ev_daemon_stop_killtimer ();
316
317                 doc = g_new (EvDoc, 1);
318                 doc->dbus_name = g_strdup (sender);
319                 doc->uri = g_strdup (uri);
320                 process_pending_invocations (doc->uri, doc->dbus_name);
321
322                 doc->watch_id = g_bus_watch_name_on_connection (connection,
323                                                                 sender,
324                                                                 G_BUS_NAME_WATCHER_FLAGS_NONE,
325                                                                 name_appeared_cb,
326                                                                 name_vanished_cb,
327                                                                 user_data, NULL);
328
329                 LOG ("RegisterDocument registered owner '%s' for URI '%s'\n", doc->dbus_name, uri);
330                 ev_daemon_docs = g_list_prepend (ev_daemon_docs, doc);
331
332                 g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", ""));
333         } else if (g_strcmp0 (method_name, "UnregisterDocument") == 0) {
334                 EvDoc *doc;
335                 const gchar *uri;
336
337                 g_variant_get (parameters, "(&s)", &uri);
338
339                 LOG ("UnregisterDocument URI '%s'\n", uri);
340
341                 doc = ev_daemon_find_doc (uri);
342                 if (doc == NULL) {
343                         LOG ("UnregisterDocument URI was not registered!\n");
344                         g_dbus_method_invocation_return_error_literal (invocation,
345                                                                        G_DBUS_ERROR,
346                                                                        G_DBUS_ERROR_INVALID_ARGS,
347                                                                        "URI not registered");
348                         return;
349                 }
350
351                 if (strcmp (doc->dbus_name, sender) != 0) {
352                         LOG ("UnregisterDocument called by non-owner (owner '%s' sender '%s')\n",
353                              doc->dbus_name, sender);
354
355                         g_dbus_method_invocation_return_error_literal (invocation,
356                                                                        G_DBUS_ERROR,
357                                                                        G_DBUS_ERROR_BAD_ADDRESS,
358                                                                        "Only owner can call this method");
359                         return;
360                 }
361
362                 ev_daemon_docs = g_list_remove (ev_daemon_docs, doc);
363                 ev_doc_free (doc);
364                 ev_daemon_maybe_start_killtimer (user_data);
365
366                 g_dbus_method_invocation_return_value (invocation, g_variant_new ("()"));
367         } else if (g_strcmp0 (method_name, "FindDocument") == 0) {
368                 EvDoc *doc;
369                 const gchar *uri;
370                 gboolean spawn;
371
372                 g_variant_get (parameters, "(&sb)",  &uri, &spawn);
373
374                 LOG ("FindDocument URI '%s' \n", uri);
375
376                 doc = ev_daemon_find_doc (uri);
377                 if (doc != NULL) {
378                         g_dbus_method_invocation_return_value (invocation,
379                                                                g_variant_new ("(s)", doc->dbus_name));
380                         return;
381                 }
382
383                 if (spawn) {
384                         GList *uri_invocations;
385                         gboolean ret_val = TRUE;
386
387                         uri_invocations = g_hash_table_lookup (pending_invocations, uri);
388
389                         if (uri_invocations == NULL) {
390                                 /* Only spawn once. */
391                                 ret_val = spawn_evince (uri);
392                         }
393
394                         if (ret_val) {
395                                 /* Only defer DBUS answer if evince was succesfully spawned */
396                                 uri_invocations = g_list_prepend (uri_invocations, invocation);
397                                 g_hash_table_insert (pending_invocations,
398                                                      g_strdup (uri),
399                                                      uri_invocations);
400                                 return;
401                         }
402                 }
403
404                 LOG ("FindDocument URI '%s' was not registered!\n", uri);
405                 g_dbus_method_invocation_return_value (invocation,
406                                                        g_variant_new ("(s)",""));
407         }
408 }
409
410 static const char introspection_xml[] =
411   "<node>"
412     "<interface name='org.gnome.evince.Daemon'>"
413       "<method name='RegisterDocument'>"
414         "<arg type='s' name='uri' direction='in'/>"
415         "<arg type='s' name='owner' direction='out'/>"
416       "</method>"
417       "<method name='UnregisterDocument'>"
418         "<arg type='s' name='uri' direction='in'/>"
419       "</method>"
420       "<method name='FindDocument'>"
421         "<arg type='s' name='uri' direction='in'/>"
422         "<arg type='b' name='spawn' direction='in'/>"
423         "<arg type='s' name='owner' direction='out'/>"
424       "</method>"
425     "</interface>"
426   "</node>";
427
428 static const GDBusInterfaceVTable interface_vtable = {
429   method_call_cb,
430   NULL,
431   NULL
432 };
433
434 static GDBusNodeInfo *introspection_data;
435
436 static void
437 bus_acquired_cb (GDBusConnection *connection,
438                  const gchar     *name,
439                  gpointer         user_data)
440 {
441         GMainLoop *loop = (GMainLoop *) user_data;
442         guint      registration_id;
443         GError    *error = NULL;
444
445         if (!introspection_data)
446                 introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
447
448         registration_id = g_dbus_connection_register_object (connection,
449                                                              EV_DBUS_DAEMON_OBJECT_PATH,
450                                                              introspection_data->interfaces[0],
451                                                              &interface_vtable,
452                                                              g_main_loop_ref (loop),
453                                                              (GDestroyNotify) g_main_loop_unref,
454                                                              &error);
455         if (registration_id == 0) {
456                 g_printerr ("Failed to register object: %s\n", error->message);
457                 g_error_free (error);
458
459                 if (g_main_loop_is_running (loop))
460                         g_main_loop_quit (loop);
461         }
462 }
463
464 static void
465 name_acquired_cb (GDBusConnection *connection,
466                   const gchar     *name,
467                   gpointer         user_data)
468 {
469         ev_migrate_metadata ();
470
471         ev_daemon_maybe_start_killtimer (user_data);
472 }
473
474 static void
475 name_lost_cb (GDBusConnection *connection,
476               const gchar     *name,
477               gpointer         user_data)
478 {
479           GMainLoop *loop = (GMainLoop *) user_data;
480
481           /* Failed to acquire the name; exit daemon */
482           if (g_main_loop_is_running (loop))
483                   g_main_loop_quit (loop);
484 }
485
486 gint
487 main (gint argc, gchar **argv)
488 {
489         GMainLoop *loop;
490         guint owner_id;
491
492         g_set_prgname ("evince-daemon");
493
494         g_type_init ();
495
496         loop = g_main_loop_new (NULL, FALSE);
497
498         pending_invocations = g_hash_table_new_full (g_str_hash,
499                                                      g_str_equal,
500                                                      (GDestroyNotify)g_free,
501                                                      NULL);
502
503         owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
504                                    EV_DBUS_DAEMON_NAME,
505                                    G_BUS_NAME_OWNER_FLAGS_NONE,
506                                    bus_acquired_cb,
507                                    name_acquired_cb,
508                                    name_lost_cb,
509                                    g_main_loop_ref (loop),
510                                    (GDestroyNotify) g_main_loop_unref);
511
512         g_main_loop_run (loop);
513
514         g_bus_unown_name (owner_id);
515
516         g_main_loop_unref (loop);
517         if (introspection_data)
518                 g_dbus_node_info_unref (introspection_data);
519         g_list_foreach (ev_daemon_docs, (GFunc)ev_doc_free, NULL);
520         g_list_free (ev_daemon_docs);
521         g_hash_table_destroy (pending_invocations);
522
523         return 0;
524 }