[gnome-documents] Add support for eBooks



commit 17322bb4b627ebfef0b8c36ba22d390d0910faa6
Author: Bastien Nocera <hadess hadess net>
Date:   Tue Aug 26 18:25:31 2014 +0200

    Add support for eBooks
    
    Add a new application, "Books" (aka gnome-books) to the
    gnome-documents git repository. This application shares most
    of its code with Documents, and it supports:
    - Categorising e-Books vs. Comics
    - Searches
    - Preview of CBZ/CBR/etc. comic formats (supported through the
      libevince backend) with "fit page" zoom by default
    
    https://bugzilla.gnome.org/show_bug.cgi?id=704316

 data/Makefile.am                 |    7 ++--
 data/org.gnome.Books.desktop.in  |   13 +++++++
 data/org.gnome.books.gschema.xml |   24 ++++++++++++++
 src/Makefile-js.am               |    1 +
 src/Makefile.am                  |   16 ++++++++-
 src/application.js               |   27 ++++++++++++---
 src/documents.js                 |    4 ++
 src/embed.js                     |    5 ++-
 src/gnome-books.in               |   20 +++++++++++
 src/lib/gd-utils.c               |   31 +++++++++++++++++-
 src/main.js                      |    2 +-
 src/mainBooks.js                 |   31 ++++++++++++++++++
 src/org.gnome.Books.service.in   |    3 ++
 src/query.js                     |   11 +++++-
 src/search.js                    |   65 ++++++++++++++++++++++++++-----------
 src/utils.js                     |    2 +
 16 files changed, 226 insertions(+), 36 deletions(-)
---
diff --git a/data/Makefile.am b/data/Makefile.am
index 772cc66..9b560f5 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -8,21 +8,22 @@ appdata_in_files = org.gnome.Documents.appdata.xml.in
 
 desktopdir = $(datadir)/applications
 desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
-desktop_in_files = org.gnome.Documents.desktop.in
+desktop_in_files = org.gnome.Documents.desktop.in org.gnome.Books.desktop.in
 
 searchproviderdir = $(datadir)/gnome-shell/search-providers
 searchprovider_DATA = org.gnome.Documents.search-provider.ini
 
 @INTLTOOL_DESKTOP_RULE@
 
-check-local: org.gnome.Documents.desktop
+check-local: org.gnome.Documents.desktop org.gnome.Books.desktop
        $(DESKTOP_FILE_VALIDATE) org.gnome.Documents.desktop
+       $(DESKTOP_FILE_VALIDATE) org.gnome.Books.desktop
 
 gsettings_ENUM_NAMESPACE = org.gnome.Documents
 gsettings_ENUM_FILES = \
     $(top_srcdir)/libgd/libgd/gd-main-view.h
 
-gsettings_SCHEMAS = org.gnome.documents.gschema.xml
+gsettings_SCHEMAS = org.gnome.documents.gschema.xml org.gnome.books.gschema.xml
 .PRECIOUS: $(gsettings_SCHEMAS)
 
 @GSETTINGS_RULES@
diff --git a/data/org.gnome.Books.desktop.in b/data/org.gnome.Books.desktop.in
new file mode 100644
index 0000000..7c98d6a
--- /dev/null
+++ b/data/org.gnome.Books.desktop.in
@@ -0,0 +1,13 @@
+[Desktop Entry]
+_Name=Books
+_Comment=Access, manage and share books
+Exec=gnome-books
+# FIXME icon
+Icon=gnome-books
+Terminal=false
+Type=Application
+StartupNotify=true
+DBusActivatable=true
+OnlyShowIn=GNOME;
+Categories=GNOME;GTK;Utility;Core;
+_Keywords=Books;Comics;ePub;PDF;
diff --git a/data/org.gnome.books.gschema.xml b/data/org.gnome.books.gschema.xml
new file mode 100644
index 0000000..80f0736
--- /dev/null
+++ b/data/org.gnome.books.gschema.xml
@@ -0,0 +1,24 @@
+<schemalist gettext-domain="gnome-documents">
+  <schema id="org.gnome.books" path="/org/gnome/books/">
+    <key name="view-as" enum="org.gnome.Documents.GdMainViewType">
+      <default>'icon'</default>
+      <summary>View as</summary>
+      <description>View as type</description>
+    </key>
+    <key name="window-size" type="ai">
+      <default>[768, 600]</default>
+      <summary>Window size</summary>
+      <description>Window size (width and height).</description>
+    </key>
+    <key name="window-position" type="ai">
+      <default>[]</default>
+      <summary>Window position</summary>
+      <description>Window position (x and y).</description>
+    </key>
+    <key name="window-maximized" type="b">
+      <default>true</default>
+      <summary>Window maximized</summary>
+      <description>Window maximized state</description>
+    </key>
+  </schema>
+</schemalist>
diff --git a/src/Makefile-js.am b/src/Makefile-js.am
index 764981e..88cf7a1 100644
--- a/src/Makefile-js.am
+++ b/src/Makefile-js.am
@@ -6,6 +6,7 @@ dist_js_DATA = \
     edit.js \
     embed.js \
     main.js \
+    mainBooks.js \
     mainToolbar.js \
     mainWindow.js \
     manager.js \
diff --git a/src/Makefile.am b/src/Makefile.am
index a40df05..c11023f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -56,12 +56,16 @@ typelib_DATA += $(gir_DATA:.gir=.typelib)
 
 CLEANFILES += $(gir_DATA) $(typelib_DATA)
 
-service_in_files = org.gnome.Documents.service.in
+service_in_files = org.gnome.Documents.service.in org.gnome.Books.service.in
 servicedir = $(datadir)/dbus-1/services
-service_DATA = org.gnome.Documents.service
+service_DATA = org.gnome.Documents.service org.gnome.Books.service
+
 org.gnome.Documents.service: org.gnome.Documents.service.in
        $(AM_V_GEN) $(do_subst) $< > $@
 
+org.gnome.Books.service: org.gnome.Books.service.in
+       $(AM_V_GEN) $(do_subst) $< > $@
+
 CLEANFILES += $(service_DATA)
 EXTRA_DIST += $(service_in_files)
 
@@ -73,4 +77,12 @@ gnome-documents: gnome-documents.in
 CLEANFILES += gnome-documents
 EXTRA_DIST += gnome-documents.in
 
+bin_SCRIPTS += gnome-books
+gnome-books: gnome-books.in
+       $(AM_V_GEN) $(do_subst) $< > $@
+       chmod +x $@
+
+CLEANFILES += gnome-books
+EXTRA_DIST += gnome-books.in
+
 -include $(top_srcdir)/git.mk
diff --git a/src/application.js b/src/application.js
index a2fd080..88c67ed 100644
--- a/src/application.js
+++ b/src/application.js
@@ -110,17 +110,27 @@ const Application = new Lang.Class({
     Name: 'Application',
     Extends: Gtk.Application,
 
-    _init: function() {
+    _init: function(isBooks) {
         this.minersRunning = [];
         this._activationTimestamp = Gdk.CURRENT_TIME;
         this._extractPriority = null;
 
+        this.isBooks = isBooks;
+
         Gettext.bindtextdomain('gnome-documents', Path.LOCALE_DIR);
         Gettext.textdomain('gnome-documents');
-        GLib.set_prgname('gnome-documents');
-        GLib.set_application_name(_("Documents"));
+        let appid;
+        if (!this.isBooks) {
+            GLib.set_prgname('gnome-documents');
+            GLib.set_application_name(_("Documents"));
+            appid = 'org.gnome.Documents';
+        } else {
+            GLib.set_prgname('gnome-books');
+            GLib.set_application_name(_("Books"));
+            appid = 'org.gnome.Books';
+        }
 
-        this.parent({ application_id: 'org.gnome.Documents',
+        this.parent({ application_id: appid,
                       inactivity_timeout: 12000 });
 
         this._searchProvider = new ShellSearchProvider.ShellSearchProvider();
@@ -409,7 +419,10 @@ const Application = new Lang.Class({
         resource._register();
 
         application = this;
-        settings = new Gio.Settings({ schema_id: 'org.gnome.documents' });
+        if (!application.isBooks)
+            settings = new Gio.Settings({ schema: 'org.gnome.documents' });
+        else
+            settings = new Gio.Settings({ schema: 'org.gnome.books' });
 
         let gtkSettings = Gtk.Settings.get_default();
         gtkSettings.connect('notify::gtk-theme-name', Lang.bind(this, this._themeChanged));
@@ -542,7 +555,9 @@ const Application = new Lang.Class({
 
         this._initActions();
         this._initAppMenu();
-        this._initGettingStarted();
+
+        if (!this.isBooks)
+            this._initGettingStarted();
     },
 
     vfunc_shutdown: function() {
diff --git a/src/documents.js b/src/documents.js
index 138b560..9b93a94 100644
--- a/src/documents.js
+++ b/src/documents.js
@@ -879,6 +879,8 @@ const GoogleDocument = new Lang.Class({
             description = _("Spreadsheet");
         else if (this.rdfType.indexOf('nfo#Presentation') != -1)
             description = _("Presentation");
+        else if (this.rdfType.indexOf('nfo#EBook') != -1)
+            description = _("e-Book");
         else
             description = _("Document");
 
@@ -1049,6 +1051,8 @@ const SkydriveDocument = new Lang.Class({
             description = _("Spreadsheet");
         else if (this.rdfType.indexOf('nfo#Presentation') != -1)
             description = _("Presentation");
+        else if (this.rdfType.indexOf('nfo#EBook') != -1)
+            description = _("e-Book");
         else
             description = _("Document");
 
diff --git a/src/embed.js b/src/embed.js
index 82c3fea..2db07b6 100644
--- a/src/embed.js
+++ b/src/embed.js
@@ -399,7 +399,10 @@ const Embed = new Lang.Class({
     },
 
     _onLoadFinished: function(manager, doc, docModel) {
-        docModel.set_sizing_mode(EvView.SizingMode.AUTOMATIC);
+        if (!Application.application.isBooks)
+            docModel.set_sizing_mode(EvView.SizingMode.AUTOMATIC);
+        else
+            docModel.set_sizing_mode(EvView.SizingMode.FIT_PAGE);
         docModel.set_page_layout(EvView.PageLayout.AUTOMATIC);
         this._toolbar.setModel(docModel);
         this._preview.setModel(docModel);
diff --git a/src/gnome-books.in b/src/gnome-books.in
new file mode 100644
index 0000000..36957cc
--- /dev/null
+++ b/src/gnome-books.in
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+export GJS_PATH="@pkgdatadir@/js${GJS_PATH:+:$GJS_PATH}"
+export GI_TYPELIB_PATH="@pkglibdir@/girepository-1.0${GI_TYPELIB_PATH:+:$GI_TYPELIB_PATH}"
+export LD_LIBRARY_PATH="@pkglibdir ${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
+
+if test x"$GJS_DEBUG_OUTPUT" = x ; then
+    export GJS_DEBUG_OUTPUT=stderr
+fi
+
+if test x"$GJS_DEBUG_TOPICS" = x ; then
+    export GJS_DEBUG_TOPICS="JS ERROR;JS LOG"
+fi
+
+DEBUG_COMMAND=""
+if test x"$DOCUMENTS_RUN_DEBUG" != x; then
+    DEBUG_COMMAND="gdb --args"
+fi
+
+exec $DEBUG_COMMAND @GJS_CONSOLE@ -I @pkgdatadir@/js -c "const Main = imports.mainBooks; Main.start();" "$@"
diff --git a/src/lib/gd-utils.c b/src/lib/gd-utils.c
index 23b59d3..bbf10a7 100644
--- a/src/lib/gd-utils.c
+++ b/src/lib/gd-utils.c
@@ -124,6 +124,7 @@ gd_filename_get_extension_offset (const char *filename)
                if (strcmp (end, ".gz") == 0 ||
                    strcmp (end, ".bz2") == 0 ||
                    strcmp (end, ".sit") == 0 ||
+                   strcmp (end, ".zip") == 0 ||
                    strcmp (end, ".Z") == 0) {
                        end2 = end - 1;
                        while (end2 > filename &&
@@ -182,6 +183,24 @@ gd_filename_to_mime_type (const gchar *filename_with_extension)
 
   if (g_strcmp0 (extension, ".pdf") == 0)
     type = "application/pdf";
+  else if (g_strcmp0 (extension, ".epub") == 0)
+    type = "application/epub+zip";
+  else if (g_strcmp0 (extension, ".cbr") == 0)
+    type = "application/x-cbr";
+  else if (g_strcmp0 (extension, ".cbz") == 0)
+    type = "application/x-cbz";
+  else if (g_strcmp0 (extension, ".cbt") == 0)
+    type = "application/x-cbt";
+  else if (g_strcmp0 (extension, ".cb7") == 0)
+    type = "application/x-cb7";
+  else if (g_strcmp0 (extension, ".fb2.zip") == 0)
+    type = "application/x-zip-compressed-fb2";
+  else if (g_strcmp0 (extension, ".fb2") == 0)
+    type = "application/x-fictionbook+xml";
+  else if (g_strcmp0 (extension, ".mobi") == 0)
+    type = "application/x-mobipocket-ebook";
+  else if (g_strcmp0 (extension, ".prc") == 0)
+    type = "application/x-mobipocket-ebook";
 
   return type;
 }
@@ -210,10 +229,20 @@ gd_filename_to_rdf_type (const gchar *filename_with_extension)
       || g_strcmp0 (extension, ".docx") == 0
       || g_strcmp0 (extension, ".dot") == 0
       || g_strcmp0 (extension, ".dotx") == 0
-      || g_strcmp0 (extension, ".epub") == 0
       || g_strcmp0 (extension, ".pdf") == 0)
     type = "nfo:PaginatedTextDocument";
 
+  else if (g_strcmp0 (extension, ".epub") == 0
+           || g_strcmp0 (extension, ".cbr") == 0
+           || g_strcmp0 (extension, ".cbz") == 0
+           || g_strcmp0 (extension, ".cbt") == 0
+           || g_strcmp0 (extension, ".cb7") == 0
+           || g_strcmp0 (extension, ".fb2") == 0
+           || g_strcmp0 (extension, ".fb2.zip") == 0
+           || g_strcmp0 (extension, ".mobi") == 0
+           || g_strcmp0 (extension, ".prc") == 0)
+    type = "nfo:EBook";
+
   else if (g_strcmp0 (extension, ".pot") == 0
            || g_strcmp0 (extension, ".potm") == 0
            || g_strcmp0 (extension, ".potx") == 0
diff --git a/src/main.js b/src/main.js
index 40af051..ec08fb3 100644
--- a/src/main.js
+++ b/src/main.js
@@ -24,7 +24,7 @@ const GLib = imports.gi.GLib;
 const System = imports.system;
 
 function start() {
-    let application = new Application.Application();
+    let application = new Application.Application(false);
     if (GLib.getenv('DOCUMENTS_PERSIST'))
         application.hold();
     return application.run([System.programInvocationName].concat(ARGV));
diff --git a/src/mainBooks.js b/src/mainBooks.js
new file mode 100644
index 0000000..4b4c6a7
--- /dev/null
+++ b/src/mainBooks.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2011 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: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+const Application = imports.application;
+const GLib = imports.gi.GLib;
+const System = imports.system;
+
+function start() {
+    let application = new Application.Application(true);
+    if (GLib.getenv('DOCUMENTS_PERSIST'))
+        application.hold();
+    return application.run([System.programInvocationName].concat(ARGV));
+}
diff --git a/src/org.gnome.Books.service.in b/src/org.gnome.Books.service.in
new file mode 100644
index 0000000..b88fe37
--- /dev/null
+++ b/src/org.gnome.Books.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.Books
+Exec= bindir@/gnome-books --gapplication-service
diff --git a/src/query.js b/src/query.js
index 771f367..0566fce 100644
--- a/src/query.js
+++ b/src/query.js
@@ -44,7 +44,8 @@ const QueryFlags = {
     UNFILTERED: 1 << 0
 };
 
-const LOCAL_COLLECTIONS_IDENTIFIER = 'gd:collection:local:';
+const LOCAL_DOCUMENTS_COLLECTIONS_IDENTIFIER = 'gd:collection:local:';
+const LOCAL_BOOKS_COLLECTIONS_IDENTIFIER = 'gb:collection:local:';
 
 const QueryBuilder = new Lang.Class({
     Name: 'QueryBuilder',
@@ -204,11 +205,17 @@ const QueryBuilder = new Lang.Class({
     },
 
     buildCreateCollectionQuery: function(name) {
+        let application = Gio.Application.get_default();
+        let collectionsIdentifier;
+        if (!application.isBooks)
+            collectionsIdentifier = LOCAL_DOCUMENTS_COLLECTIONS_IDENTIFIER;
+        else
+            collectionsIdentifier = LOCAL_BOOKS_COLLECTIONS_IDENTIFIER;
         let time = GdPrivate.iso8601_from_timestamp(GLib.get_real_time() / GLib.USEC_PER_SEC);
         let sparql = ('INSERT { _:res a nfo:DataContainer ; a nie:DataObject ; ' +
                       'nie:contentLastModified \"' + time + '\" ; ' +
                       'nie:title \"' + name + '\" ; ' +
-                      'nao:identifier \"' + LOCAL_COLLECTIONS_IDENTIFIER + name + '\" }');
+                      'nao:identifier \"' + collectionsIdentifier + name + '\" }');
 
         return this._createQuery(sparql);
     },
diff --git a/src/search.js b/src/search.js
index 96fa2f4..bb93efc 100644
--- a/src/search.js
+++ b/src/search.js
@@ -164,7 +164,9 @@ const SearchTypeStock = {
     PDF: 'pdf',
     PRESENTATIONS: 'presentations',
     SPREADSHEETS: 'spreadsheets',
-    TEXTDOCS: 'textdocs'
+    TEXTDOCS: 'textdocs',
+    EBOOKS: 'ebooks',
+    COMICS: 'comics'
 };
 
 const SearchTypeManager = new Lang.Class({
@@ -178,23 +180,44 @@ const SearchTypeManager = new Lang.Class({
 
         this.addItem(new SearchType({ id: SearchTypeStock.ALL,
                                       name: _("All") }));
-        this.addItem(new SearchType({ id: SearchTypeStock.COLLECTIONS,
-                                      name: _("Collections"),
-                                      filter: 'fn:starts-with(nao:identifier(?urn), \"gd:collection\")',
-                                      where: '?urn rdf:type nfo:DataContainer .' }));
-        this.addItem(new SearchType({ id: SearchTypeStock.PDF,
-                                      name: _("PDF Documents"),
-                                      filter: 'fn:contains(nie:mimeType(?urn), \"application/pdf\")',
-                                      where: '?urn rdf:type nfo:PaginatedTextDocument .' }));
-        this.addItem(new SearchType({ id: SearchTypeStock.PRESENTATIONS,
-                                      name: _("Presentations"),
-                                      where: '?urn rdf:type nfo:Presentation .' }));
-        this.addItem(new SearchType({ id: SearchTypeStock.SPREADSHEETS,
-                                      name: _("Spreadsheets"),
-                                      where: '?urn rdf:type nfo:Spreadsheet .' }));
-        this.addItem(new SearchType({ id: SearchTypeStock.TEXTDOCS,
-                                      name: _("Text Documents"),
-                                      where: '?urn rdf:type nfo:PaginatedTextDocument .' }));
+        if (!Application.application.isBooks) {
+            this.addItem(new SearchType({ id: SearchTypeStock.COLLECTIONS,
+                                          name: _("Collections"),
+                                          filter: 'fn:starts-with(nao:identifier(?urn), \"gd:collection\")',
+                                          where: '?urn rdf:type nfo:DataContainer .' }));
+            this.addItem(new SearchType({ id: SearchTypeStock.PDF,
+                                          name: _("PDF Documents"),
+                                          filter: 'fn:contains(nie:mimeType(?urn), \"application/pdf\")',
+                                          where: '?urn rdf:type nfo:PaginatedTextDocument .' }));
+        } else {
+            this.addItem(new SearchType({ id: SearchTypeStock.COLLECTIONS,
+                                          name: _("Collections"),
+                                          filter: 'fn:starts-with(nao:identifier(?urn), \"gb:collection\")',
+                                          where: '?urn rdf:type nfo:DataContainer .' }));
+            //FIXME we need to remove all the non-Comics PDFs here
+        }
+
+        if (!Application.application.isBooks) {
+            this.addItem(new SearchType({ id: SearchTypeStock.PRESENTATIONS,
+                                          name: _("Presentations"),
+                                          where: '?urn rdf:type nfo:Presentation .' }));
+            this.addItem(new SearchType({ id: SearchTypeStock.SPREADSHEETS,
+                                          name: _("Spreadsheets"),
+                                          where: '?urn rdf:type nfo:Spreadsheet .' }));
+            this.addItem(new SearchType({ id: SearchTypeStock.TEXTDOCS,
+                                          name: _("Text Documents"),
+                                          where: '?urn rdf:type nfo:PaginatedTextDocument .' }));
+        } else {
+          this.addItem(new SearchType({ id: SearchTypeStock.EBOOKS,
+                                        name: _("e-Books"),
+                                        filter: '(nie:mimeType(?urn) IN (\"application/epub+zip\", 
\"application/x-mobipocket-ebook\", \"application/x-fictionbook+xml\", 
\"application/x-zip-compressed-fb2\"))',
+                                        where: '?urn rdf:type nfo:EBook .' }));
+          this.addItem(new SearchType({ id: SearchTypeStock.COMICS,
+                                        name: _("Comics"),
+                                        filter: '(nie:mimeType(?urn) IN (\"application/x-cbr\", 
\"application/x-cbz\", \"application/x-cbt\", \"application/x-cb7\"))',
+                                        where: '?urn rdf:type nfo:EBook .' }));
+        }
+
 
         this.setActiveItemById(SearchTypeStock.ALL);
     },
@@ -379,8 +402,10 @@ const Source = new Lang.Class({
                 filters.push('(fn:contains (nie:url(?urn), "%s"))'.format(location.get_uri()));
             }));
 
-        filters.push('(fn:starts-with (nao:identifier(?urn), "gd:collection:local:"))');
-
+        if (!Application.application.isBooks)
+            filters.push('(fn:starts-with (nao:identifier(?urn), "gd:collection:local:"))');
+        else
+            filters.push('(fn:starts-with (nao:identifier(?urn), "gb:collection:local:"))');
         return '(' + filters.join(' || ') + ')';
     },
 
diff --git a/src/utils.js b/src/utils.js
index 7a358ed..ec64923 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -77,6 +77,8 @@ function iconFromRdfType(type) {
         iconName = 'x-office-spreadsheet';
     else if (type.indexOf('nfo#Presentation') != -1)
         iconName = 'x-office-presentation';
+    else if (type.indexOf('nfo#EBook') != -1)
+        iconName = 'x-office-document'; //FIXME should be a real icon
     else if (type.indexOf('nfo#DataContainer') != -1)
         return GdPrivate.create_collection_icon(
             getIconSize() * Application.application.getScaleFactor(),


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