[sushi] evince: load EvinceDocument from JS



commit be8ef096e8a9e38652432b5707c74de0285f2a19
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Mon Jun 17 21:27:56 2019 -0700

    evince: load EvinceDocument from JS
    
    Instead of having SushiPdfLoader, a class to load the EvinceDocument
    and do conversion from LibreOffice when required, implement loading
    natively in the viewer, and factor out an async method for the LO
    conversion.
    
    The goal here is to simplify the viewer state and slim down libsushi
    as much as possible.

 src/libsushi/meson.build        |   3 +-
 src/libsushi/sushi-pdf-loader.c | 500 ----------------------------------------
 src/libsushi/sushi-pdf-loader.h |  62 -----
 src/libsushi/sushi-utils.c      | 292 +++++++++++++++++++++++
 src/libsushi/sushi-utils.h      |   9 +
 src/viewers/evince.js           |  35 ++-
 6 files changed, 327 insertions(+), 574 deletions(-)
---
diff --git a/src/libsushi/meson.build b/src/libsushi/meson.build
index c43d4d6..1bd7e65 100644
--- a/src/libsushi/meson.build
+++ b/src/libsushi/meson.build
@@ -4,7 +4,6 @@ libsushi_c = [
   'sushi-font-loader.c',
   'sushi-font-widget.c',
   'sushi-media-bin.c',
-  'sushi-pdf-loader.c',
   'sushi-sound-player.c',
   'sushi-utils.c',
 ]
@@ -15,7 +14,6 @@ libsushi_headers = [
   'sushi-font-loader.h',
   'sushi-font-widget.h',
   'sushi-media-bin.h',
-  'sushi-pdf-loader.h',
   'sushi-sound-player.h',
   'sushi-utils.h',
 ]
@@ -64,6 +62,7 @@ gnome.generate_gir(
     'Gtk-3.0',
     'GtkSource-4',
     'EvinceDocument-3.0',
+    'EvinceView-3.0',
   ],
   install: true,
   install_dir_gir: join_paths(pkgdatadir, 'gir-1.0'),
diff --git a/src/libsushi/sushi-utils.c b/src/libsushi/sushi-utils.c
index ede75e8..dc69278 100644
--- a/src/libsushi/sushi-utils.c
+++ b/src/libsushi/sushi-utils.c
@@ -25,6 +25,7 @@
 
 #include "sushi-utils.h"
 
+#include <glib/gstdio.h>
 #include <gtk/gtk.h>
 
 #ifdef GDK_WINDOWING_X11
@@ -50,6 +51,18 @@ sushi_create_foreign_window (guint xid)
   return retval;
 }
 
+/**
+ * sushi_get_evince_document_from_job:
+ * @job:
+ *
+ * Returns: (transfer none):
+ */
+EvDocument *
+sushi_get_evince_document_from_job (EvJob *job)
+{
+  return job->document;
+}
+
 /**
  * sushi_query_supported_document_types:
  *
@@ -83,3 +96,282 @@ sushi_query_supported_document_types (void)
 
   return retval;
 }
+
+static void load_libreoffice (GTask *task);
+
+typedef struct {
+  GFile *file;
+  gchar *pdf_path;
+
+  gboolean checked_libreoffice_flatpak;
+  gboolean have_libreoffice_flatpak;
+  GPid libreoffice_pid;
+} TaskData;
+
+static void
+task_data_free (TaskData *data)
+{
+  if (data->pdf_path) {
+    g_unlink (data->pdf_path);
+    g_free (data->pdf_path);
+  }
+
+  if (data->libreoffice_pid != -1) {
+    kill (data->libreoffice_pid, SIGKILL);
+    data->libreoffice_pid = -1;
+  }
+
+  g_clear_object (&data->file);
+  g_free (data);
+}
+
+static void
+libreoffice_missing_ready_cb (GObject *source,
+                              GAsyncResult *res,
+                              gpointer user_data)
+{
+  GTask *task = user_data;
+  GError *error = NULL;
+
+  g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);
+  if (error != NULL) {
+    /* can't install libreoffice with packagekit - nothing else we can do */
+    g_task_return_error (task, error);
+    g_object_unref (task);
+    return;
+  }
+
+  /* now that we have libreoffice installed, try again loading the document */
+  load_libreoffice (task);
+}
+
+static void
+libreoffice_missing (GTask *task)
+{
+  GApplication *app = g_application_get_default ();
+  GtkWidget *widget = GTK_WIDGET (gtk_application_get_active_window (GTK_APPLICATION (app)));
+  GDBusConnection *connection = g_application_get_dbus_connection (app);
+  guint xid = 0;
+  GdkWindow *gdk_window;
+  const gchar *libreoffice_path[2];
+
+  gdk_window = gtk_widget_get_window (widget);
+  if (gdk_window != NULL)
+    xid = GDK_WINDOW_XID (gdk_window);
+
+  libreoffice_path[0] = "/usr/bin/libreoffice";
+  libreoffice_path[1] = NULL;
+
+  g_dbus_connection_call (connection,
+                          "org.freedesktop.PackageKit",
+                          "/org/freedesktop/PackageKit",
+                          "org.freedesktop.PackageKit.Modify",
+                          "InstallProvideFiles",
+                          g_variant_new ("(u^ass)",
+                                         xid,
+                                         libreoffice_path,
+                                         "hide-confirm-deps"),
+                          NULL, G_DBUS_CALL_FLAGS_NONE,
+                          G_MAXINT, NULL,
+                          libreoffice_missing_ready_cb,
+                          task);
+}
+
+static void
+libreoffice_child_watch_cb (GPid pid,
+                            gint status,
+                            gpointer user_data)
+{
+  GTask *task = user_data;
+  TaskData *data = g_task_get_task_data (task);
+  GFile *file;
+
+  g_spawn_close_pid (pid);
+  data->libreoffice_pid = -1;
+
+  file = g_file_new_for_path (data->pdf_path);
+  g_task_return_pointer (task, file, g_object_unref);
+  g_object_unref (task);
+}
+
+#define LIBREOFFICE_FLATPAK "org.libreoffice.LibreOffice"
+
+static gboolean
+check_libreoffice_flatpak (GTask       *task,
+                           const gchar *flatpak_path)
+{
+  const gchar *check_argv[] = { flatpak_path, "info", LIBREOFFICE_FLATPAK, NULL };
+  gboolean ret;
+  gint exit_status = -1;
+  GError *error = NULL;
+  TaskData *data = g_task_get_task_data (task);
+
+  if (data->checked_libreoffice_flatpak)
+    return data->have_libreoffice_flatpak;
+
+  data->checked_libreoffice_flatpak = TRUE;
+
+  ret = g_spawn_sync (NULL, (gchar **) check_argv, NULL,
+                      G_SPAWN_DEFAULT |
+                      G_SPAWN_STDERR_TO_DEV_NULL |
+                      G_SPAWN_STDOUT_TO_DEV_NULL,
+                      NULL, NULL,
+                      NULL, NULL,
+                      &exit_status, &error);
+
+  if (ret) {
+    GError *child_error = NULL;
+    if (g_spawn_check_exit_status (exit_status, &child_error)) {
+      g_debug ("Found LibreOffice flatpak!");
+      data->have_libreoffice_flatpak = TRUE;
+    } else {
+      g_debug ("LibreOffice flatpak not found, flatpak info returned %i (%s)",
+               exit_status, child_error->message);
+      g_clear_error (&child_error);
+    }
+  } else {
+    g_warning ("Error while checking for LibreOffice flatpak: %s",
+               error->message);
+    g_clear_error (&error);
+  }
+
+  return data->have_libreoffice_flatpak;
+}
+
+static void
+load_libreoffice (GTask *task)
+{
+  gchar *flatpak_path, *libreoffice_path = NULL;
+  gboolean use_flatpak = FALSE;
+  gchar *doc_path, *doc_name, *tmp_name, *pdf_dir;
+  gchar *flatpak_doc = NULL, *flatpak_dir = NULL;
+  gboolean res;
+  GPid pid;
+  GError *error = NULL;
+  gchar **argv = NULL;
+  TaskData *data = g_task_get_task_data (task);
+
+  flatpak_path = g_find_program_in_path ("flatpak");
+  if (flatpak_path != NULL)
+    use_flatpak = check_libreoffice_flatpak (task, flatpak_path);
+
+  if (!use_flatpak) {
+    libreoffice_path = g_find_program_in_path ("libreoffice");
+    if (libreoffice_path == NULL) {
+      libreoffice_missing (task);
+      g_free (flatpak_path);
+      return;
+    }
+  }
+
+  doc_path = g_file_get_path (data->file);
+  doc_name = g_file_get_basename (data->file);
+
+  /* libreoffice --convert-to replaces the extension with .pdf */
+  tmp_name = g_strrstr (doc_name, ".");
+  if (tmp_name)
+    *tmp_name = '\0';
+  tmp_name = g_strdup_printf ("%s.pdf", doc_name);
+  g_free (doc_name);
+
+  pdf_dir = g_build_filename (g_get_user_cache_dir (), "sushi", NULL);
+  data->pdf_path = g_build_filename (pdf_dir, tmp_name, NULL);
+  g_mkdir_with_parents (pdf_dir, 0700);
+
+  g_free (tmp_name);
+
+  if (use_flatpak) {
+    flatpak_doc = g_strdup_printf ("--filesystem=%s:ro", doc_path);
+    flatpak_dir = g_strdup_printf ("--filesystem=%s", pdf_dir);
+
+    const gchar *flatpak_argv[] = {
+      NULL, /* to be replaced with flatpak binary */
+      "run", "--command=/app/libreoffice/program/soffice",
+      "--nofilesystem=host",
+      NULL, /* to be replaced with filesystem permissions to read document */
+      NULL, /* to be replaced with filesystem permissions to write output */
+      LIBREOFFICE_FLATPAK,
+      "--convert-to", "pdf",
+      "--outdir", NULL, /* to be replaced with output dir */
+      NULL, /* to be replaced with input file */
+      NULL
+    };
+
+    flatpak_argv[0] = flatpak_path;
+    flatpak_argv[4] = flatpak_doc;
+    flatpak_argv[5] = flatpak_dir;
+    flatpak_argv[10] = pdf_dir;
+    flatpak_argv[11] = doc_path;
+
+    argv = g_strdupv ((gchar **) flatpak_argv);
+  } else {
+    const gchar *libreoffice_argv[] = {
+      NULL, /* to be replaced with binary */
+      "--convert-to", "pdf",
+      "--outdir", NULL, /* to be replaced with output dir */
+      NULL, /* to be replaced with input file */
+      NULL
+    };
+
+    libreoffice_argv[0] = libreoffice_path;
+    libreoffice_argv[4] = pdf_dir;
+    libreoffice_argv[5] = doc_path;
+
+    argv = g_strdupv ((gchar **) libreoffice_argv);
+  }
+
+  tmp_name = g_strjoinv (" ", (gchar **) argv);
+  g_debug ("Executing LibreOffice command: %s", tmp_name);
+  g_free (tmp_name);
+
+  res = g_spawn_async (NULL, (gchar **) argv, NULL,
+                       G_SPAWN_DO_NOT_REAP_CHILD,
+                       NULL, NULL,
+                       &pid, &error);
+
+  g_free (pdf_dir);
+  g_free (doc_path);
+  g_free (libreoffice_path);
+  g_free (flatpak_path);
+  g_free (flatpak_doc);
+  g_free (flatpak_dir);
+  g_strfreev (argv);
+
+  if (!res) {
+    g_warning ("Error while spawning libreoffice: %s",
+               error->message);
+    g_error_free (error);
+
+    return;
+  }
+
+  g_child_watch_add (pid, libreoffice_child_watch_cb, task);
+  data->libreoffice_pid = pid;
+}
+
+void
+sushi_convert_libreoffice (GFile *file,
+                           GAsyncReadyCallback callback,
+                           gpointer user_data)
+{
+  GTask *task = g_task_new (NULL, NULL, callback, user_data);
+  TaskData *data = g_new0 (TaskData, 1);
+  data->file = g_object_ref (file);
+
+  g_task_set_task_data (task, data, (GDestroyNotify) task_data_free);
+  load_libreoffice (task);
+}
+
+/**
+ * sushi_convert_libreoffice_finish:
+ * @result:
+ * @error:
+ *
+ * Returns: (transfer full):
+ */
+GFile *
+sushi_convert_libreoffice_finish (GAsyncResult *result,
+                                  GError **error)
+{
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
diff --git a/src/libsushi/sushi-utils.h b/src/libsushi/sushi-utils.h
index 8be1b92..c2bb721 100644
--- a/src/libsushi/sushi-utils.h
+++ b/src/libsushi/sushi-utils.h
@@ -27,12 +27,21 @@
 #define __SUSHI_UTILS_H__
 
 #include <evince-document.h>
+#include <evince-view.h>
 #include <gdk/gdk.h>
+#include <gio/gio.h>
 
 G_BEGIN_DECLS
 
 GdkWindow *    sushi_create_foreign_window (guint xid);
 gchar **       sushi_query_supported_document_types (void);
+EvDocument *   sushi_get_evince_document_from_job (EvJob *job);
+
+void           sushi_convert_libreoffice (GFile *file,
+                                          GAsyncReadyCallback callback,
+                                          gpointer user_data);
+GFile *        sushi_convert_libreoffice_finish (GAsyncResult *result,
+                                                 GError **error);
 
 G_END_DECLS
 
diff --git a/src/viewers/evince.js b/src/viewers/evince.js
index 195bbc1..e40ea7a 100644
--- a/src/viewers/evince.js
+++ b/src/viewers/evince.js
@@ -42,14 +42,26 @@ var Klass = GObject.registerClass({
                                          false)
     },
 }, class EvinceRenderer extends Gtk.ScrolledWindow {
-    _init(file) {
+    _init(file, fileInfo) {
         super._init({ visible: true,
                       min_content_height: Constants.VIEW_MIN,
                       min_content_width: Constants.VIEW_MIN });
 
-        this._pdfLoader = new Sushi.PdfLoader();
-        this._pdfLoader.connect('notify::document', this._onDocumentLoaded.bind(this));
-        this._pdfLoader.uri = file.get_uri();
+        if (evinceTypes.includes(fileInfo.get_content_type())) {
+            this._loadFile(file);
+        } else {
+            Sushi.convert_libreoffice(file, (o, res) => {
+                let convertedFile;
+                try {
+                    convertedFile = Sushi.convert_libreoffice_finish(res);
+                } catch (e) {
+                    logError(e, 'Unable to convert Libreoffice document to PDF');
+                    return;
+                }
+
+                this._loadFile(convertedFile);
+            });
+        }
 
         this._view = EvinceView.View.new();
         this._view.show();
@@ -59,6 +71,12 @@ var Klass = GObject.registerClass({
         this.isReady();
     }
 
+    _loadFile(file) {
+        let job = EvinceView.JobLoad.new(file.get_uri());
+        job.connect('finished', this._onLoadJobFinished.bind(this));
+        job.scheduler_push_job(EvinceView.JobPriority.PRIORITY_NONE);
+    }
+
     _updatePageLabel() {
         let curPage = this._model.get_page();
         let totPages = this._model.document.get_n_pages();
@@ -69,8 +87,9 @@ var Klass = GObject.registerClass({
         this._pageLabel.set_text(_("%d of %d").format(curPage + 1, totPages));
     }
 
-    _onDocumentLoaded(pdfLoader) {
-        this._model = EvinceView.DocumentModel.new_with_document(pdfLoader.document);
+    _onLoadJobFinished(job) {
+        let document = Sushi.get_evince_document_from_job(job);
+        this._model = EvinceView.DocumentModel.new_with_document(document);
         this._model.set_sizing_mode(EvinceView.SizingMode.FIT_WIDTH);
         this._model.set_continuous(true);
 
@@ -106,10 +125,6 @@ var Klass = GObject.registerClass({
         let toolbarZoom = Utils.createFullscreenButton(this);
         toolbar.add(toolbarZoom);
     }
-
-    _onDestroy() {
-        this._pdfLoader = null;
-    }
 });
 
 EvinceDocument.init();


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]