[gnome-documents] Support previewing of password protected PDFs



commit cde99212000c2f2b0beb29d66956aad7b791b44e
Author: Debarshi Ray <debarshir gnome org>
Date:   Mon May 27 16:48:49 2013 +0200

    Support previewing of password protected PDFs
    
    Delay changing the window mode when loading a document, so that we are
    still in the overview when presenting the dialog to enter the password.
    We switch the mode to preview when load-finished or load-error has
    been received.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=700716

 src/Makefile-js.am      |    1 +
 src/documents.js        |   24 +++++++----
 src/embed.js            |   28 ++++++++++--
 src/lib/gd-pdf-loader.c |   40 +++++++++++++++---
 src/lib/gd-pdf-loader.h |    1 +
 src/mainToolbar.js      |    3 +-
 src/password.js         |  105 +++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 181 insertions(+), 21 deletions(-)
---
diff --git a/src/Makefile-js.am b/src/Makefile-js.am
index 9387e0f..764981e 100644
--- a/src/Makefile-js.am
+++ b/src/Makefile-js.am
@@ -11,6 +11,7 @@ dist_js_DATA = \
     manager.js \
     miners.js \
     notifications.js \
+    password.js \
     places.js \
     presentation.js \
     preview.js \
diff --git a/src/documents.js b/src/documents.js
index 96516a2..8b37c17 100644
--- a/src/documents.js
+++ b/src/documents.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2012 Red Hat, Inc.
+ * Copyright (c) 2011, 2012, 2013 Red Hat, Inc.
  *
  * Gnome Documents is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by the
@@ -19,6 +19,7 @@
  *
  */
 
+const EvDocument = imports.gi.EvinceDocument;
 const EvView = imports.gi.EvinceView;
 const GdkPixbuf = imports.gi.GdkPixbuf;
 const Gio = imports.gi.Gio;
@@ -548,7 +549,7 @@ const DocCommon = new Lang.Class({
     },
 
     print: function(toplevel) {
-        this.load(null, Lang.bind(this,
+        this.load(null, null, Lang.bind(this,
             function(doc, docModel, error) {
                 if (error) {
                     log('Unable to print document ' + this.uri + ': ' + error);
@@ -621,8 +622,8 @@ const LocalDocument = new Lang.Class({
             this.typeDescription = Gio.content_type_get_description(this.mimeType);
     },
 
-    load: function(cancellable, callback) {
-        GdPrivate.pdf_loader_load_uri_async(this.uri, cancellable, Lang.bind(this,
+    load: function(passwd, cancellable, callback) {
+        GdPrivate.pdf_loader_load_uri_async(this.uri, passwd, cancellable, Lang.bind(this,
             function(source, res) {
                 try {
                     let docModel = GdPrivate.pdf_loader_load_uri_finish(res);
@@ -687,7 +688,7 @@ const GoogleDocument = new Lang.Class({
                  }));
     },
 
-    load: function(cancellable, callback) {
+    load: function(passwd, cancellable, callback) {
         this._createGDataEntry(cancellable, Lang.bind(this,
             function(entry, service, exception) {
                 if (exception) {
@@ -839,7 +840,7 @@ const SkydriveDocument = new Lang.Class({
                  }));
     },
 
-    load: function(cancellable, callback) {
+    load: function(passwd, cancellable, callback) {
         this._createZpjEntry(cancellable, Lang.bind(this,
             function(entry, service, exception) {
                 if (exception) {
@@ -1018,6 +1019,11 @@ const DocumentManager = new Lang.Class({
         if (error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
             return;
 
+        if (error.matches(EvDocument.DocumentError, EvDocument.DocumentError.ENCRYPTED)) {
+            this.emit('password-needed', doc);
+            return;
+        }
+
         // Translators: %s is the title of a document
         let message = _("Oops! Unable to load ā€œ%sā€").format(doc.name);
         let exception = this._humanizeError(error);
@@ -1042,7 +1048,7 @@ const DocumentManager = new Lang.Class({
         this.emit('load-finished', doc, docModel);
     },
 
-    reloadActiveItem: function() {
+    reloadActiveItem: function(passwd) {
         let doc = this.getActiveItem();
 
         if (!doc)
@@ -1055,7 +1061,7 @@ const DocumentManager = new Lang.Class({
         this._clearActiveDocModel();
 
         this._loaderCancellable = new Gio.Cancellable();
-        doc.load(this._loaderCancellable, Lang.bind(this, this._onDocumentLoaded));
+        doc.load(passwd, this._loaderCancellable, Lang.bind(this, this._onDocumentLoaded));
         this.emit('load-started', doc);
     },
 
@@ -1079,7 +1085,7 @@ const DocumentManager = new Lang.Class({
         recentManager.add_item(doc.uri);
 
         this._loaderCancellable = new Gio.Cancellable();
-        doc.load(this._loaderCancellable, Lang.bind(this, this._onDocumentLoaded));
+        doc.load(null, this._loaderCancellable, Lang.bind(this, this._onDocumentLoaded));
         this.emit('load-started', doc);
     },
 
diff --git a/src/embed.js b/src/embed.js
index 668de42..7bb747e 100644
--- a/src/embed.js
+++ b/src/embed.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Red Hat, Inc.
+ * Copyright (c) 2011, 2013 Red Hat, Inc.
  *
  * Gnome Documents is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by the
@@ -25,6 +25,7 @@ const Mainloop = imports.mainloop;
 const Application = imports.application;
 const MainToolbar = imports.mainToolbar;
 const Notifications = imports.notifications;
+const Password = imports.password;
 const Preview = imports.preview;
 const Edit = imports.edit;
 const Selections = imports.selections;
@@ -268,6 +269,8 @@ const Embed = new Lang.Class({
                                             Lang.bind(this, this._onLoadFinished));
         Application.documentManager.connect('load-error',
                                             Lang.bind(this, this._onLoadError));
+        Application.documentManager.connect('password-needed',
+                                            Lang.bind(this, this._onPasswordNeeded));
 
         this._onQueryStatusChanged();
 
@@ -353,15 +356,13 @@ const Embed = new Lang.Class({
     },
 
     _onActiveItemChanged: function(manager, doc) {
-        let newMode = WindowMode.WindowMode.OVERVIEW;
-
         if (doc) {
             let collection = Application.collectionManager.getItemById(doc.id);
             if (!collection)
-                newMode = WindowMode.WindowMode.PREVIEW;
+                return;
         }
 
-        Application.modeController.setWindowMode(newMode);
+        Application.modeController.setWindowMode(WindowMode.WindowMode.OVERVIEW);
     },
 
     _clearLoadTimer: function() {
@@ -384,6 +385,8 @@ const Embed = new Lang.Class({
     },
 
     _onLoadFinished: function(manager, doc, docModel) {
+        Application.modeController.setWindowMode(WindowMode.WindowMode.PREVIEW);
+
         docModel.set_sizing_mode(EvView.SizingMode.AUTOMATIC);
         docModel.set_page_layout(EvView.PageLayout.AUTOMATIC);
         this._toolbar.setModel(docModel);
@@ -396,11 +399,26 @@ const Embed = new Lang.Class({
     },
 
     _onLoadError: function(manager, doc, message, exception) {
+        Application.modeController.setWindowMode(WindowMode.WindowMode.PREVIEW);
+
         this._clearLoadTimer();
         this._spinnerBox.stop();
         this._setError(message, exception.message);
     },
 
+    _onPasswordNeeded: function(manager, doc) {
+        this._clearLoadTimer();
+        this._spinnerBox.stop();
+
+        let dialog = new Password.PasswordDialog(doc);
+        dialog.widget.connect('response', Lang.bind(this,
+            function(widget, response) {
+                dialog.widget.destroy();
+                if (response == Gtk.ResponseType.CANCEL)
+                    Application.documentManager.setActiveItem(null);
+            }));
+    },
+
     _prepareForOverview: function() {
         if (this._preview)
             this._preview.setModel(null);
diff --git a/src/lib/gd-pdf-loader.c b/src/lib/gd-pdf-loader.c
index f98fed9..5d3437f 100644
--- a/src/lib/gd-pdf-loader.c
+++ b/src/lib/gd-pdf-loader.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2012 Red Hat, Inc.
+ * Copyright (c) 2011, 2012, 2013 Red Hat, Inc.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by 
@@ -41,6 +41,9 @@ typedef struct {
   gchar *pdf_path;
   GPid unoconv_pid;
 
+  gchar *passwd;
+  gboolean passwd_tried;
+
   GFile *download_file;
   GInputStream *stream;
 
@@ -135,6 +138,7 @@ pdf_load_job_free (PdfLoadJob *job)
   g_clear_object (&job->zpj_entry);
 
   g_free (job->uri);
+  g_free (job->passwd);
   g_free (job->resource_id);
 
   if (job->pdf_path != NULL) {
@@ -157,6 +161,7 @@ pdf_load_job_new (GSimpleAsyncResult *result,
                   const gchar *uri,
                   GDataEntry *gdata_entry,
                   ZpjSkydriveEntry *zpj_entry,
+                  const gchar *passwd,
                   GCancellable *cancellable)
 {
   PdfLoadJob *retval;
@@ -169,6 +174,8 @@ pdf_load_job_new (GSimpleAsyncResult *result,
 
   if (uri != NULL)
     retval->uri = g_strdup (uri);
+  if (passwd != NULL)
+    retval->passwd = g_strdup (passwd);
   if (gdata_entry != NULL)
     retval->gdata_entry = g_object_ref (gdata_entry);
   if (zpj_entry != NULL)
@@ -222,14 +229,23 @@ ev_load_job_done (EvJob *ev_job,
   PdfLoadJob *job = user_data;
 
   if (ev_job_is_failed (ev_job) || (ev_job->document == NULL)) {
-    if (job->from_old_cache)
+    if (job->from_old_cache) {
       pdf_load_job_force_refresh_cache (job);
-    else
+    } else if (g_error_matches (ev_job->error, EV_DOCUMENT_ERROR, EV_DOCUMENT_ERROR_ENCRYPTED)
+               && job->passwd != NULL
+               && !job->passwd_tried) {
+      /* EvJobLoad tries using the password only after the job has
+       * failed once.
+       */
+      ev_job_scheduler_push_job (ev_job, EV_JOB_PRIORITY_NONE);
+      job->passwd_tried = TRUE;
+    } else {
       pdf_load_job_complete_error (job, (ev_job->error != NULL) ? 
                                    g_error_copy (ev_job->error) :
                                    g_error_new_literal (G_IO_ERROR,
                                                         G_IO_ERROR_FAILED,
                                                         _("Unable to load the document")));
+    }
 
     return;
   }
@@ -252,6 +268,9 @@ pdf_load_job_from_pdf (PdfLoadJob *job)
   }
 
   ev_job = ev_job_load_new ((uri != NULL) ? (uri) : (job->uri));
+  if (job->passwd != NULL)
+    ev_job_load_set_password (EV_JOB_LOAD (ev_job), job->passwd);
+
   g_signal_connect (ev_job, "finished",
                     G_CALLBACK (ev_load_job_done), job);
 
@@ -1013,8 +1032,17 @@ pdf_load_job_start (PdfLoadJob *job)
     pdf_load_job_from_regular_file (job);
 }
 
+/**
+ * gd_pdf_loader_load_uri_async:
+ * @uri:
+ * @passwd: (allow-none):
+ * @cancellable: (allow-none):
+ * @callback:
+ * @user_data:
+ */
 void
 gd_pdf_loader_load_uri_async (const gchar *uri,
+                              const gchar *passwd,
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
@@ -1025,7 +1053,7 @@ gd_pdf_loader_load_uri_async (const gchar *uri,
   result = g_simple_async_result_new (NULL, callback, user_data,
                                       gd_pdf_loader_load_uri_async);
 
-  job = pdf_load_job_new (result, uri, NULL, NULL, cancellable);
+  job = pdf_load_job_new (result, uri, NULL, NULL, passwd, cancellable);
 
   pdf_load_job_start (job);
 
@@ -1066,7 +1094,7 @@ gd_pdf_loader_load_gdata_entry_async (GDataEntry *entry,
   result = g_simple_async_result_new (NULL, callback, user_data,
                                       gd_pdf_loader_load_gdata_entry_async);
 
-  job = pdf_load_job_new (result, NULL, entry, NULL, cancellable);
+  job = pdf_load_job_new (result, NULL, entry, NULL, NULL, cancellable);
   job->gdata_service = g_object_ref (service);
 
   pdf_load_job_start (job);
@@ -1108,7 +1136,7 @@ gd_pdf_loader_load_zpj_entry_async (ZpjSkydriveEntry *entry,
   result = g_simple_async_result_new (NULL, callback, user_data,
                                       gd_pdf_loader_load_zpj_entry_async);
 
-  job = pdf_load_job_new (result, NULL, NULL, entry, cancellable);
+  job = pdf_load_job_new (result, NULL, NULL, entry, NULL, cancellable);
   job->zpj_service = g_object_ref (service);
 
   pdf_load_job_start (job);
diff --git a/src/lib/gd-pdf-loader.h b/src/lib/gd-pdf-loader.h
index 22b05b4..9e5ffb7 100644
--- a/src/lib/gd-pdf-loader.h
+++ b/src/lib/gd-pdf-loader.h
@@ -33,6 +33,7 @@
 G_BEGIN_DECLS
 
 void gd_pdf_loader_load_uri_async (const gchar *uri,
+                                   const gchar *passwd,
                                    GCancellable *cancellable,
                                    GAsyncReadyCallback callback,
                                    gpointer user_data);
diff --git a/src/mainToolbar.js b/src/mainToolbar.js
index c83d399..c14f80d 100644
--- a/src/mainToolbar.js
+++ b/src/mainToolbar.js
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 Red Hat, Inc.
+ * Copyright (c) 2011, 2013 Red Hat, Inc.
  *
  * Gnome Documents is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by the
@@ -58,6 +58,7 @@ const MainToolbar = new Lang.Class({
             }));
 
         Application.documentManager.connect('load-error', Lang.bind(this, this._onLoadErrorOrPassword));
+        Application.documentManager.connect('password-needed', Lang.bind(this, this._onLoadErrorOrPassword));
     },
 
     _onLoadErrorOrPassword: function() {
diff --git a/src/password.js b/src/password.js
new file mode 100644
index 0000000..3a09b10
--- /dev/null
+++ b/src/password.js
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * Gnome Documents 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.
+ *
+ * Gnome Documents 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 Gnome Documents; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Debarshi Ray <debarshir gnome org>
+ *
+ */
+
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const Gtk = imports.gi.Gtk;
+const _ = imports.gettext.gettext;
+const C_ = imports.gettext.pgettext;
+
+const Application = imports.application;
+const Documents = imports.documents;
+const Mainloop = imports.mainloop;
+
+const Lang = imports.lang;
+
+const PasswordDialog = new Lang.Class({
+    Name: 'PasswordDialog',
+
+    _init: function(doc) {
+        let toplevel = Application.application.get_windows()[0];
+        this.widget = new Gtk.Dialog({ resizable: false,
+                                       transient_for: toplevel,
+                                       modal: true,
+                                       destroy_with_parent: true,
+                                       default_width: 400,
+                                       border_width: 6,
+                                       title: _("Password Required"),
+                                       hexpand: true });
+        this.widget.add_button('gtk-cancel', Gtk.ResponseType.CANCEL);
+        this.widget.add_button(_("_Unlock"), Gtk.ResponseType.OK);
+        this.widget.set_default_response(Gtk.ResponseType.OK);
+        this.widget.set_response_sensitive(Gtk.ResponseType.OK, false);
+
+        let grid = new Gtk.Grid({ column_spacing: 12,
+                                  row_spacing: 18,
+                                  border_width: 5,
+                                  margin_bottom: 6,
+                                  hexpand: true,
+                                  vexpand: true });
+
+        let contentArea = this.widget.get_content_area();
+        contentArea.pack_start(grid, true, true, 2);
+
+        let label;
+
+        let msg = _("Document %s is locked and requires a password to be opened."
+                   ).format(doc.name);
+        // Doesn't respect halign and hexpand.
+        label = new Gtk.Label({ label: msg,
+                                max_width_chars: 56,
+                                use_markup: true,
+                                wrap: true });
+        label.set_alignment(0.0, 0.5);
+        grid.attach(label, 0, 0, 2, 1);
+
+        let entry = new Gtk.Entry({ activates_default: true,
+                                    can_focus: true,
+                                    visibility: false,
+                                    hexpand: true });
+        label = new Gtk.Label({ label: _("_Password"),
+                                mnemonic_widget: entry,
+                                use_underline: true });
+        label.get_style_context().add_class('dim-label');
+        grid.attach(label, 0, 1, 1, 1);
+        grid.attach(entry, 1, 1, 1, 1);
+
+        entry.connect('realize', Lang.bind(this,
+            function() {
+                entry.grab_focus();
+            }));
+        entry.connect('changed', Lang.bind(this,
+            function() {
+                let length = entry.get_text_length();
+                this.widget.set_response_sensitive(Gtk.ResponseType.OK, (length != 0));
+            }));
+
+        this.widget.connect('response', Lang.bind(this,
+            function(widget, response) {
+                if (response != Gtk.ResponseType.OK)
+                    return;
+                let passwd = entry.get_text();
+                Application.documentManager.reloadActiveItem(passwd);
+            }));
+
+        this.widget.show_all();
+    }
+});


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