summaryrefslogtreecommitdiff
path: root/lib/epub.js/src/store.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/epub.js/src/store.js')
-rw-r--r--lib/epub.js/src/store.js384
1 files changed, 384 insertions, 0 deletions
diff --git a/lib/epub.js/src/store.js b/lib/epub.js/src/store.js
new file mode 100644
index 0000000..0d12103
--- /dev/null
+++ b/lib/epub.js/src/store.js
@@ -0,0 +1,384 @@
+import {defer, isXml, parse} from "./utils/core";
+import httpRequest from "./utils/request";
+import mime from "./utils/mime";
+import Path from "./utils/path";
+import EventEmitter from "event-emitter";
+import localforage from "localforage";
+
+/**
+ * Handles saving and requesting files from local storage
+ * @class
+ * @param {string} name This should be the name of the application for modals
+ * @param {function} [requester]
+ * @param {function} [resolver]
+ */
+class Store {
+
+ constructor(name, requester, resolver) {
+ this.urlCache = {};
+
+ this.storage = undefined;
+
+ this.name = name;
+ this.requester = requester || httpRequest;
+ this.resolver = resolver;
+
+ this.online = true;
+
+ this.checkRequirements();
+
+ this.addListeners();
+ }
+
+ /**
+ * Checks to see if localForage exists in global namspace,
+ * Requires localForage if it isn't there
+ * @private
+ */
+ checkRequirements(){
+ try {
+ let store;
+ if (typeof localforage === "undefined") {
+ store = localforage;
+ }
+ this.storage = store.createInstance({
+ name: this.name
+ });
+ } catch (e) {
+ throw new Error("localForage lib not loaded");
+ }
+ }
+
+ /**
+ * Add online and offline event listeners
+ * @private
+ */
+ addListeners() {
+ this._status = this.status.bind(this);
+ window.addEventListener('online', this._status);
+ window.addEventListener('offline', this._status);
+ }
+
+ /**
+ * Remove online and offline event listeners
+ * @private
+ */
+ removeListeners() {
+ window.removeEventListener('online', this._status);
+ window.removeEventListener('offline', this._status);
+ this._status = undefined;
+ }
+
+ /**
+ * Update the online / offline status
+ * @private
+ */
+ status(event) {
+ let online = navigator.onLine;
+ this.online = online;
+ if (online) {
+ this.emit("online", this);
+ } else {
+ this.emit("offline", this);
+ }
+ }
+
+ /**
+ * Add all of a book resources to the store
+ * @param {Resources} resources book resources
+ * @param {boolean} [force] force resaving resources
+ * @return {Promise<object>} store objects
+ */
+ add(resources, force) {
+ let mapped = resources.resources.map((item) => {
+ let { href } = item;
+ let url = this.resolver(href);
+ let encodedUrl = window.encodeURIComponent(url);
+
+ return this.storage.getItem(encodedUrl).then((item) => {
+ if (!item || force) {
+ return this.requester(url, "binary")
+ .then((data) => {
+ return this.storage.setItem(encodedUrl, data);
+ });
+ } else {
+ return item;
+ }
+ });
+
+ });
+ return Promise.all(mapped);
+ }
+
+ /**
+ * Put binary data from a url to storage
+ * @param {string} url a url to request from storage
+ * @param {boolean} [withCredentials]
+ * @param {object} [headers]
+ * @return {Promise<Blob>}
+ */
+ put(url, withCredentials, headers) {
+ let encodedUrl = window.encodeURIComponent(url);
+
+ return this.storage.getItem(encodedUrl).then((result) => {
+ if (!result) {
+ return this.requester(url, "binary", withCredentials, headers).then((data) => {
+ return this.storage.setItem(encodedUrl, data);
+ });
+ }
+ return result;
+ });
+ }
+
+ /**
+ * Request a url
+ * @param {string} url a url to request from storage
+ * @param {string} [type] specify the type of the returned result
+ * @param {boolean} [withCredentials]
+ * @param {object} [headers]
+ * @return {Promise<Blob | string | JSON | Document | XMLDocument>}
+ */
+ request(url, type, withCredentials, headers){
+ if (this.online) {
+ // From network
+ return this.requester(url, type, withCredentials, headers).then((data) => {
+ // save to store if not present
+ this.put(url);
+ return data;
+ })
+ } else {
+ // From store
+ return this.retrieve(url, type);
+ }
+
+ }
+
+ /**
+ * Request a url from storage
+ * @param {string} url a url to request from storage
+ * @param {string} [type] specify the type of the returned result
+ * @return {Promise<Blob | string | JSON | Document | XMLDocument>}
+ */
+ retrieve(url, type) {
+ var deferred = new defer();
+ var response;
+ var path = new Path(url);
+
+ // If type isn't set, determine it from the file extension
+ if(!type) {
+ type = path.extension;
+ }
+
+ if(type == "blob"){
+ response = this.getBlob(url);
+ } else {
+ response = this.getText(url);
+ }
+
+
+ return response.then((r) => {
+ var deferred = new defer();
+ var result;
+ if (r) {
+ result = this.handleResponse(r, type);
+ deferred.resolve(result);
+ } else {
+ deferred.reject({
+ message : "File not found in storage: " + url,
+ stack : new Error().stack
+ });
+ }
+ return deferred.promise;
+ });
+ }
+
+ /**
+ * Handle the response from request
+ * @private
+ * @param {any} response
+ * @param {string} [type]
+ * @return {any} the parsed result
+ */
+ handleResponse(response, type){
+ var r;
+
+ if(type == "json") {
+ r = JSON.parse(response);
+ }
+ else
+ if(isXml(type)) {
+ r = parse(response, "text/xml");
+ }
+ else
+ if(type == "xhtml") {
+ r = parse(response, "application/xhtml+xml");
+ }
+ else
+ if(type == "html" || type == "htm") {
+ r = parse(response, "text/html");
+ } else {
+ r = response;
+ }
+
+ return r;
+ }
+
+ /**
+ * Get a Blob from Storage by Url
+ * @param {string} url
+ * @param {string} [mimeType]
+ * @return {Blob}
+ */
+ getBlob(url, mimeType){
+ let encodedUrl = window.encodeURIComponent(url);
+
+ return this.storage.getItem(encodedUrl).then(function(uint8array) {
+ if(!uint8array) return;
+
+ mimeType = mimeType || mime.lookup(url);
+
+ return new Blob([uint8array], {type : mimeType});
+ });
+
+ }
+
+ /**
+ * Get Text from Storage by Url
+ * @param {string} url
+ * @param {string} [mimeType]
+ * @return {string}
+ */
+ getText(url, mimeType){
+ let encodedUrl = window.encodeURIComponent(url);
+
+ mimeType = mimeType || mime.lookup(url);
+
+ return this.storage.getItem(encodedUrl).then(function(uint8array) {
+ var deferred = new defer();
+ var reader = new FileReader();
+ var blob;
+
+ if(!uint8array) return;
+
+ blob = new Blob([uint8array], {type : mimeType});
+
+ reader.addEventListener("loadend", () => {
+ deferred.resolve(reader.result);
+ });
+
+ reader.readAsText(blob, mimeType);
+
+ return deferred.promise;
+ });
+ }
+
+ /**
+ * Get a base64 encoded result from Storage by Url
+ * @param {string} url
+ * @param {string} [mimeType]
+ * @return {string} base64 encoded
+ */
+ getBase64(url, mimeType){
+ let encodedUrl = window.encodeURIComponent(url);
+
+ mimeType = mimeType || mime.lookup(url);
+
+ return this.storage.getItem(encodedUrl).then((uint8array) => {
+ var deferred = new defer();
+ var reader = new FileReader();
+ var blob;
+
+ if(!uint8array) return;
+
+ blob = new Blob([uint8array], {type : mimeType});
+
+ reader.addEventListener("loadend", () => {
+ deferred.resolve(reader.result);
+ });
+ reader.readAsDataURL(blob, mimeType);
+
+ return deferred.promise;
+ });
+ }
+
+ /**
+ * Create a Url from a stored item
+ * @param {string} url
+ * @param {object} [options.base64] use base64 encoding or blob url
+ * @return {Promise} url promise with Url string
+ */
+ createUrl(url, options){
+ var deferred = new defer();
+ var _URL = window.URL || window.webkitURL || window.mozURL;
+ var tempUrl;
+ var response;
+ var useBase64 = options && options.base64;
+
+ if(url in this.urlCache) {
+ deferred.resolve(this.urlCache[url]);
+ return deferred.promise;
+ }
+
+ if (useBase64) {
+ response = this.getBase64(url);
+
+ if (response) {
+ response.then(function(tempUrl) {
+
+ this.urlCache[url] = tempUrl;
+ deferred.resolve(tempUrl);
+
+ }.bind(this));
+
+ }
+
+ } else {
+
+ response = this.getBlob(url);
+
+ if (response) {
+ response.then(function(blob) {
+
+ tempUrl = _URL.createObjectURL(blob);
+ this.urlCache[url] = tempUrl;
+ deferred.resolve(tempUrl);
+
+ }.bind(this));
+
+ }
+ }
+
+
+ if (!response) {
+ deferred.reject({
+ message : "File not found in storage: " + url,
+ stack : new Error().stack
+ });
+ }
+
+ return deferred.promise;
+ }
+
+ /**
+ * Revoke Temp Url for a achive item
+ * @param {string} url url of the item in the store
+ */
+ revokeUrl(url){
+ var _URL = window.URL || window.webkitURL || window.mozURL;
+ var fromCache = this.urlCache[url];
+ if(fromCache) _URL.revokeObjectURL(fromCache);
+ }
+
+ destroy() {
+ var _URL = window.URL || window.webkitURL || window.mozURL;
+ for (let fromCache in this.urlCache) {
+ _URL.revokeObjectURL(fromCache);
+ }
+ this.urlCache = {};
+ this.removeListeners();
+ }
+}
+
+EventEmitter(Store.prototype);
+
+export default Store;