[gnome-documents] Add support for eBooks
- From: Bastien Nocera <hadess src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-documents] Add support for eBooks
- Date: Mon, 1 Dec 2014 13:27:05 +0000 (UTC)
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]