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