1
0
mirror of https://gitlab.gnome.org/GNOME/evince synced 2024-07-05 00:59:07 +00:00
evince/shell/ev-daemon.c
Pablo Correa Gómez 84a2648215
shell: simplify freeing memory by better exploiting glib functions
Mostly g_clear_object and g_clear_pointer. But also some small
simplifications depending on the context.
2023-03-24 09:58:48 +01:00

483 lines
17 KiB
C

/* ev-daemon.c
* this file is part of evince, a gnome document viewer
*
* Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
* Copyright © 2010, 2012 Christian Persch
*
* Evince 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.
*
* Evince 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "config.h"
#define G_LOG_DOMAIN "EvinceDaemon"
#include <glib.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "ev-daemon-gdbus-generated.h"
#define EV_DBUS_DAEMON_NAME "org.gnome.evince.Daemon"
#define EV_DBUS_DAEMON_INTERFACE_NAME "org.gnome.evince.Daemon"
#define EV_DBUS_DAEMON_OBJECT_PATH "/org/gnome/evince/Daemon"
#define EV_DBUS_WINDOW_INTERFACE_NAME "org.gnome.evince.Window"
#define DAEMON_TIMEOUT (30) /* seconds */
#define LOG g_debug
#define EV_TYPE_DAEMON_APPLICATION (ev_daemon_application_get_type ())
#define EV_DAEMON_APPLICATION(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), EV_TYPE_DAEMON_APPLICATION, EvDaemonApplication))
#define EV_DAEMON_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EV_TYPE_DAEMON_APPLICATION, EvDaemonApplicationClass))
#define EV_IS_DAEMON_APPLICATION(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), EV_TYPE_DAEMON_APPLICATION))
#define EV_IS_DAEMON_APPLICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EV_TYPE_DAEMON_APPLICATION))
#define EV_DAEMON_APPLICATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EV_TYPE_DAEMON_APPLICATION, EvDaemonApplicationClass))
typedef struct _EvDaemonApplication EvDaemonApplication;
typedef struct _EvDaemonApplicationClass EvDaemonApplicationClass;
struct _EvDaemonApplicationClass {
GApplicationClass parent_class;
};
struct _EvDaemonApplication
{
GApplication parent_instance;
EvDaemon *daemon;
GHashTable *pending_invocations;
GList *docs;
};
static GType ev_daemon_application_get_type (void);
G_DEFINE_TYPE (EvDaemonApplication, ev_daemon_application, G_TYPE_APPLICATION)
typedef struct {
gchar *dbus_name;
gchar *uri;
guint watch_id;
guint loaded_id;
} EvDoc;
static void
ev_doc_free (EvDoc *doc)
{
if (!doc)
return;
g_free (doc->dbus_name);
g_free (doc->uri);
g_bus_unwatch_name (doc->watch_id);
g_free (doc);
}
static EvDoc *
ev_daemon_application_find_doc (EvDaemonApplication *application,
const gchar *uri)
{
GList *l;
for (l = application->docs; l != NULL; l = l->next) {
EvDoc *doc = (EvDoc *)l->data;
if (strcmp (doc->uri, uri) == 0)
return doc;
}
return NULL;
}
static gboolean
spawn_evince (const gchar *uri)
{
gchar *argv[3];
gboolean retval;
GError *error = NULL;
/* TODO Check that the uri exists */
argv[0] = g_build_filename (BINDIR, "evince", NULL);
argv[1] = (gchar *) uri;
argv[2] = NULL;
retval = g_spawn_async (NULL /* wd */, argv, NULL /* env */,
0, NULL, NULL, NULL, &error);
if (!retval) {
g_printerr ("Error spawning evince for uri %s: %s\n", uri, error->message);
g_error_free (error);
}
g_free (argv[0]);
return retval;
}
static void
name_appeared_cb (GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
LOG ("Watch name'%s' appeared with owner '%s'", name, name_owner);
}
static void
name_vanished_cb (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
EvDaemonApplication *application = EV_DAEMON_APPLICATION (user_data);
GList *l;
LOG ("Watch name'%s' disappeared", name);
for (l = application->docs; l != NULL; l = l->next) {
EvDoc *doc = (EvDoc *) l->data;
if (strcmp (doc->dbus_name, name) != 0)
continue;
LOG ("Watch found URI '%s' for name; removing", doc->uri);
application->docs = g_list_delete_link (application->docs, l);
ev_doc_free (doc);
g_application_release (G_APPLICATION (application));
return;
}
}
static void
process_pending_invocations (EvDaemonApplication *application,
const gchar *uri,
const gchar *dbus_name)
{
GList *l;
GList *uri_invocations;
LOG ("RegisterDocument process pending invocations for URI %s", uri);
uri_invocations = g_hash_table_lookup (application->pending_invocations, uri);
for (l = uri_invocations; l != NULL; l = l->next) {
GDBusMethodInvocation *invocation;
invocation = (GDBusMethodInvocation *)l->data;
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(s)", dbus_name));
}
g_list_free (uri_invocations);
g_hash_table_remove (application->pending_invocations, uri);
}
static void
document_loaded_cb (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
EvDaemonApplication *application = EV_DAEMON_APPLICATION (user_data);
const gchar *uri;
EvDoc *doc;
g_variant_get (parameters, "(&s)", &uri);
doc = ev_daemon_application_find_doc (application, uri);
if (doc == NULL)
return;
if (strcmp (uri, doc->uri) == 0) {
process_pending_invocations (application, uri, sender_name);
}
g_dbus_connection_signal_unsubscribe (connection, doc->loaded_id);
doc->loaded_id = 0;
}
static gboolean
handle_register_document_cb (EvDaemon *object,
GDBusMethodInvocation *invocation,
const gchar *uri,
EvDaemonApplication *application)
{
GDBusConnection *connection;
const char *sender;
EvDoc *doc;
doc = ev_daemon_application_find_doc (application, uri);
if (doc != NULL) {
LOG ("RegisterDocument found owner '%s' for URI '%s'", doc->dbus_name, uri);
ev_daemon_complete_register_document (object, invocation, doc->dbus_name);
return TRUE;
}
sender = g_dbus_method_invocation_get_sender (invocation);
connection = g_dbus_method_invocation_get_connection (invocation);
LOG ("RegisterDocument registered owner '%s' for URI '%s'", sender, uri);
doc = g_new (EvDoc, 1);
doc->dbus_name = g_strdup (sender);
doc->uri = g_strdup (uri);
application->docs = g_list_prepend (application->docs, doc);
doc->loaded_id = g_dbus_connection_signal_subscribe (connection,
doc->dbus_name,
EV_DBUS_WINDOW_INTERFACE_NAME,
"DocumentLoaded",
NULL,
NULL,
0,
document_loaded_cb,
application, NULL);
doc->watch_id = g_bus_watch_name_on_connection (connection,
sender,
G_BUS_NAME_WATCHER_FLAGS_NONE,
name_appeared_cb,
name_vanished_cb,
application, NULL);
ev_daemon_complete_register_document (object, invocation, "");
g_application_hold (G_APPLICATION (application));
return TRUE;
}
static gboolean
handle_unregister_document_cb (EvDaemon *object,
GDBusMethodInvocation *invocation,
const gchar *uri,
EvDaemonApplication *application)
{
EvDoc *doc;
const char *sender;
LOG ("UnregisterDocument URI '%s'", uri);
doc = ev_daemon_application_find_doc (application, uri);
if (doc == NULL) {
LOG ("UnregisterDocument URI was not registered!");
g_dbus_method_invocation_return_error_literal (invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"URI not registered");
return TRUE;
}
sender = g_dbus_method_invocation_get_sender (invocation);
if (strcmp (doc->dbus_name, sender) != 0) {
LOG ("UnregisterDocument called by non-owner (owner '%s' sender '%s')",
doc->dbus_name, sender);
g_dbus_method_invocation_return_error_literal (invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_BAD_ADDRESS,
"Only owner can call this method");
return TRUE;
}
application->docs = g_list_remove (application->docs, doc);
if (doc->loaded_id != 0) {
g_dbus_connection_signal_unsubscribe (g_dbus_method_invocation_get_connection (invocation),
doc->loaded_id);
doc->loaded_id = 0;
}
ev_doc_free (doc);
ev_daemon_complete_unregister_document (object, invocation);
g_application_release (G_APPLICATION (application));
return TRUE;
}
static gboolean
handle_find_document_cb (EvDaemon *object,
GDBusMethodInvocation *invocation,
const gchar *uri,
gboolean spawn,
EvDaemonApplication *application)
{
EvDoc *doc;
LOG ("FindDocument URI '%s'", uri);
doc = ev_daemon_application_find_doc (application, uri);
if (doc != NULL) {
ev_daemon_complete_find_document (object, invocation, doc->dbus_name);
return TRUE;
}
if (spawn) {
GList *uri_invocations;
gboolean ret_val = TRUE;
uri_invocations = g_hash_table_lookup (application->pending_invocations, uri);
if (uri_invocations == NULL) {
/* Only spawn once. */
ret_val = spawn_evince (uri);
}
if (ret_val) {
/* Only defer DBUS answer if evince was succesfully spawned */
uri_invocations = g_list_prepend (uri_invocations, invocation);
g_hash_table_insert (application->pending_invocations,
g_strdup (uri),
uri_invocations);
return TRUE;
}
}
LOG ("FindDocument URI '%s' was not registered!", uri);
// FIXME: shouldn't this return an error then?
ev_daemon_complete_find_document (object, invocation, "");
return TRUE;
}
/* ------------------------------------------------------------------------- */
static gboolean
ev_daemon_application_dbus_register (GApplication *gapplication,
GDBusConnection *connection,
const gchar *object_path,
GError **error)
{
EvDaemonApplication *application = EV_DAEMON_APPLICATION (gapplication);
EvDaemon *skeleton;
if (!G_APPLICATION_CLASS (ev_daemon_application_parent_class)->dbus_register (gapplication,
connection,
object_path,
error))
return FALSE;
skeleton = ev_daemon_skeleton_new ();
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton),
connection,
EV_DBUS_DAEMON_OBJECT_PATH,
error)) {
g_object_unref (skeleton);
return FALSE;
}
application->daemon = skeleton;
g_signal_connect (skeleton, "handle-register-document",
G_CALLBACK (handle_register_document_cb), application);
g_signal_connect (skeleton, "handle-unregister-document",
G_CALLBACK (handle_unregister_document_cb), application);
g_signal_connect (skeleton, "handle-find-document",
G_CALLBACK (handle_find_document_cb), application);
return TRUE;
}
static void
ev_daemon_application_dbus_unregister (GApplication *gapplication,
GDBusConnection *connection,
const gchar *object_path)
{
EvDaemonApplication *application = EV_DAEMON_APPLICATION (gapplication);
if (application->daemon) {
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (application->daemon));
g_clear_object (&application->daemon);
}
G_APPLICATION_CLASS (ev_daemon_application_parent_class)->dbus_unregister (gapplication,
connection,
object_path);
}
static void
ev_daemon_application_init (EvDaemonApplication *application)
{
application->pending_invocations = g_hash_table_new_full (g_str_hash,
g_str_equal,
(GDestroyNotify) g_free,
NULL);
}
static void
ev_daemon_application_finalize (GObject *object)
{
EvDaemonApplication *application = EV_DAEMON_APPLICATION (object);
g_warn_if_fail (g_hash_table_size (application->pending_invocations) == 0);
g_hash_table_destroy (application->pending_invocations);
g_list_free_full (application->docs, (GDestroyNotify) ev_doc_free);
G_OBJECT_CLASS (ev_daemon_application_parent_class)->finalize (object);
}
static void
ev_daemon_application_class_init (EvDaemonApplicationClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass);
object_class->finalize = ev_daemon_application_finalize;
g_application_class->dbus_register = ev_daemon_application_dbus_register;
g_application_class->dbus_unregister = ev_daemon_application_dbus_unregister;
}
/* ------------------------------------------------------------------------- */
gint
main (gint argc, gchar **argv)
{
GApplication *application;
const GApplicationFlags flags = G_APPLICATION_IS_SERVICE;
GError *error = NULL;
int status;
g_set_prgname ("evince-daemon");
application = g_object_new (EV_TYPE_DAEMON_APPLICATION,
"application-id", EV_DBUS_DAEMON_NAME,
"flags", flags,
NULL);
g_application_set_inactivity_timeout (application, DAEMON_TIMEOUT);
if (!g_application_register (application, NULL, &error)) {
g_printerr ("Failed to register: %s\n", error->message);
g_error_free (error);
g_object_unref (application);
return 1;
}
status = g_application_run (application, 0, NULL);
g_object_unref (application);
return status;
}