1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-03 14:59:18 +02:00

added queue for loading and offline storage of parsed info

This commit is contained in:
Fred Chasen 2013-01-16 12:20:36 -08:00
parent ca3e2c24c2
commit d90fd14ad2
12 changed files with 10017 additions and 47 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
fpjs/.DS_Store vendored

Binary file not shown.

9555
fpjs/libs/jquery-1.9.0.js vendored Normal file

File diff suppressed because it is too large Load diff

4
fpjs/libs/jquery-1.9.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -11,16 +11,6 @@ FP.app.init = (function($){
//-- Setup the browser prefixes //-- Setup the browser prefixes
FP.core.crossBrowserColumnCss(); FP.core.crossBrowserColumnCss();
//-- Temp set the previos position to section 6,
// since moby-dick has lots of crap before the test
// Might want to make a way to skip to first chapter
if (localStorage.getItem("bookURL") === null ||
localStorage.getItem("bookURL") != bookURL) {
localStorage.setItem("bookURL", bookURL);
localStorage.setItem("spinePos", 0);
}
//-- Set up our sidebar //-- Set up our sidebar
$("#main").width($(window).width()-40); $("#main").width($(window).width()-40);
@ -150,6 +140,6 @@ FP.app.init = (function($){
} }
return init; return init;
})(jQuery); })(jQuery);

View file

@ -17,11 +17,13 @@ FP.Book = function(elem, bookUrl){
this.createEvent("book:resized"); this.createEvent("book:resized");
this.initialize(this.el); this.initialize(this.el);
this.online = navigator.onLine;
this.listeners(); this.listeners();
//-- Determine storage type //-- Determine storage type
// options: none | ram // options: none | ram
FP.storage.storageMethod("none"); FP.storage.storageMethod("ram");
// BookUrl is optional, but if present start loading process // BookUrl is optional, but if present start loading process
if(bookUrl) { if(bookUrl) {
@ -31,6 +33,7 @@ FP.Book = function(elem, bookUrl){
} }
//-- Build up any html needed //-- Build up any html needed
FP.Book.prototype.initialize = function(el){ FP.Book.prototype.initialize = function(el){
this.iframe = document.createElement('iframe'); this.iframe = document.createElement('iframe');
@ -38,17 +41,23 @@ FP.Book.prototype.initialize = function(el){
this.listen("book:resized", this.resizeIframe, this); this.listen("book:resized", this.resizeIframe, this);
//this.listen("book:bookReady", function(){console.log("rready")});
this.el.appendChild(this.iframe); this.el.appendChild(this.iframe);
} }
FP.Book.prototype.listeners = function(){ FP.Book.prototype.listeners = function(){
var that = this; var that = this;
window.addEventListener("resize", that.onResized.bind(this), false); window.addEventListener("resize", that.onResized.bind(this), false);
window.addEventListener("offline", function(e) {
that.online = false;
}, false);
window.addEventListener("online", function(e) {
that.online = true;
}, false);
//-- TODO: listener for offline
} }
@ -58,16 +67,59 @@ FP.Book.prototype.loadEpub = function(bookUrl){
//-- TODO: Check what storage types are available //-- TODO: Check what storage types are available
//-- TODO: Checks if the url is a zip file and unpack //-- TODO: Checks if the url is a zip file and unpack
if(this.isContained(bookUrl)){ if(this.isContained(bookUrl)){
console.log("Zipped!"); console.error("Zipped!");
} }
//-- Gets the root of the book and url of the opf if(!this.isSaved()){
this.parseContainer(function(){ //-- Gets the root of the book and url of the opf
//-- Gets all setup of the book from xml file this.parseContainer(function(){
//-- TODO: add promise for this instead of callback? //-- Gets all setup of the book from xml file
this.parseContents(); //-- TODO: add promise for this instead of callback?
}); this.parseContents();
});
}else{
this.tell("book:tocReady");
this.tell("book:metadataReady");
this.tell("book:spineReady");
console.log(this.metadata)
//-- Info is saved, start display
this.startDisplay();
}
}
FP.Book.prototype.isSaved = function(force) {
if (localStorage.getItem("bookUrl") === null ||
localStorage.getItem("bookUrl") != this.bookUrl ||
force == true) {
localStorage.setItem("bookUrl", this.bookUrl);
localStorage.setItem("spinePos", 0);
localStorage.setItem("stored", 0);
this.spinePos = 0;
this.stored = 0;
return false;
}else{
//-- get previous saved positions
this.spinePos = parseInt(localStorage.getItem("spinePos")) || 0;
this.stored = parseInt(localStorage.getItem("stored")) || 0;
//-- get previous saved paths
this.basePath = localStorage.getItem("basePath");
this.contentsPath = localStorage.getItem("contentsPath");
//-- get previous saved content
this.metadata = JSON.parse(localStorage.getItem("metadata"));
this.assets = JSON.parse(localStorage.getItem("assets"));
this.spine = JSON.parse(localStorage.getItem("spine"));
this.toc = JSON.parse(localStorage.getItem("toc"));
return true;
}
} }
@ -128,6 +180,10 @@ FP.Book.prototype.parseContainer = function(callback){
that.basePath = that.bookUrl + fullpath[0] + "/"; that.basePath = that.bookUrl + fullpath[0] + "/";
that.contentsPath = fullpath[1]; that.contentsPath = fullpath[1];
localStorage.setItem("basePath", that.basePath);
localStorage.setItem("contentsPath", that.contentsPath);
//-- Now that we have the path we can parse the contents //-- Now that we have the path we can parse the contents
//-- TODO: move this //-- TODO: move this
that.parseContents(that.contentsPath); that.parseContents(that.contentsPath);
@ -143,6 +199,7 @@ FP.Book.prototype.parseContents = function(){
var metadata = contents.getElementsByTagName("metadata")[0], var metadata = contents.getElementsByTagName("metadata")[0],
manifest = contents.getElementsByTagName("manifest")[0], manifest = contents.getElementsByTagName("manifest")[0],
spine = contents.getElementsByTagName("spine")[0]; spine = contents.getElementsByTagName("spine")[0];
that.parseMetadata(metadata); that.parseMetadata(metadata);
that.parseManifest(manifest); that.parseManifest(manifest);
that.parseSpine(spine); that.parseSpine(spine);
@ -161,6 +218,8 @@ FP.Book.prototype.parseMetadata = function(metadata){
this.metadata["bookTitle"] = title ? title.childNodes[0].nodeValue : ""; this.metadata["bookTitle"] = title ? title.childNodes[0].nodeValue : "";
this.metadata["creator"] = creator ? creator.childNodes[0].nodeValue : ""; this.metadata["creator"] = creator ? creator.childNodes[0].nodeValue : "";
localStorage.setItem("metadata", JSON.stringify(this.metadata));
this.tell("book:metadataReady"); this.tell("book:metadataReady");
} }
@ -181,6 +240,8 @@ FP.Book.prototype.parseManifest = function(manifest){
that.parseTOC(href); that.parseTOC(href);
} }
}); });
localStorage.setItem("assets", JSON.stringify(this.assets));
} }
FP.Book.prototype.parseSpine = function(spine){ FP.Book.prototype.parseSpine = function(spine){
@ -201,6 +262,9 @@ FP.Book.prototype.parseSpine = function(spine){
that.spine.push({"id": id, "href": href}); that.spine.push({"id": id, "href": href});
that.spineIndex[id] = index; that.spineIndex[id] = index;
}); });
localStorage.setItem("spine", JSON.stringify(this.spine));
this.tell("book:spineReady"); this.tell("book:spineReady");
} }
@ -253,6 +317,7 @@ FP.Book.prototype.parseTOC = function(path){
that.toc = getTOC(navMap.getElementsByTagName("navPoint"), navMap); that.toc = getTOC(navMap.getElementsByTagName("navPoint"), navMap);
localStorage.setItem("toc", JSON.stringify(that.toc));
that.tell("book:tocReady"); that.tell("book:tocReady");
/* /*
@ -283,16 +348,22 @@ FP.Book.prototype.chapterTitle = function(){
} }
FP.Book.prototype.startDisplay = function(){ FP.Book.prototype.startDisplay = function(){
//-- get previous saved positions
var spinePos = parseInt(localStorage.getItem("spinePos")) || 0;
this.tell("book:bookReady"); this.tell("book:bookReady");
this.displayChapter(this.spinePos, false, function(){
//-- What happens if the cache expires / is cleared / changed?
//if(!this.stored){
this.storeOffline();
//}
}.bind(this));
this.displayChapter(spinePos);
} }
FP.Book.prototype.displayChapter = function(pos, end){ FP.Book.prototype.displayChapter = function(pos, end, callback){
var that = this; var that = this;
if(pos >= this.spine.length){ if(pos >= this.spine.length){
@ -321,7 +392,12 @@ FP.Book.prototype.displayChapter = function(pos, end){
that.tell("book:chapterReady"); that.tell("book:chapterReady");
that.preloadNextChapter(); if(callback){
callback();
}
//that.preloadNextChapter();
} }
} }
@ -365,10 +441,15 @@ FP.Book.prototype.preloadNextChapter = function(){
file = FP.storage.preload(path); file = FP.storage.preload(path);
} }
FP.Book.prototype.preloadAll = function(){ FP.Book.prototype.storeOffline = function(callback){
var assets = FP.core.toArray(this.assets);
FP.storage.batch(assets, function(){
this.stored = 1;
localStorage.setItem("stored", 1);
if(callback) callback();
}.bind(this));
} }
FP.Book.prototype.preloadResources = function(){ FP.Book.prototype.availableOffline = function(){
return this.stored > 0 ? true : false;
} }

View file

@ -9,24 +9,34 @@ FP.Chapter = function(book){
this.chapterPos = 1; this.chapterPos = 1;
this.leftPos = 0; this.leftPos = 0;
this.loadFromStorage(); this.load();
return this; return this;
} }
FP.Chapter.prototype.loadFromStorage = function(){ FP.Chapter.prototype.load = function(){
var path = this.path, var path = this.path;
file = FP.storage.get(path, this.postLoad.bind(this));
if(this.book.online){
this.setIframeSrc(path);
}else{
this.loadFromStorage(path);
}
} }
FP.Chapter.prototype.postLoad = function(file){ FP.Chapter.prototype.loadFromStorage = function(path){
var file = FP.storage.get(path, this.setIframeSrc.bind(this));
}
FP.Chapter.prototype.setIframeSrc = function(url){
var that = this; var that = this;
//-- Not sure if this is the best time to do this, but hide current text //-- Not sure if this is the best time to do this, but hide current text
if(this.bodyEl) this.bodyEl.style.visibility = "hidden"; if(this.bodyEl) this.bodyEl.style.visibility = "hidden";
this.iframe.src = file; this.iframe.src = url;
this.iframe.onload = function() { this.iframe.onload = function() {
//that.bodyEl = that.iframe.contentDocument.documentElement.getElementsByTagName('body')[0]; //that.bodyEl = that.iframe.contentDocument.documentElement.getElementsByTagName('body')[0];

View file

@ -121,3 +121,18 @@ FP.core.crossBrowserColumnCss = function(){
// FP.core.columnWidth = cssIfy(FP.core.columnWidth); // FP.core.columnWidth = cssIfy(FP.core.columnWidth);
} }
FP.core.toArray = function(obj) {
var arr = [];
for (member in obj) {
var newitm;
if ( obj.hasOwnProperty(member) ) {
newitm = obj[member];
newitm.ident = member;
arr.push(newitm);
}
}
return arr;
};

View file

167
fpjs/render/queue.js Normal file
View file

@ -0,0 +1,167 @@
FP.Queue = function(worker, concurrency){
this._q = [];
this._tasks = {};
this.idCount = 0;
this.concurrency = 0;
this.workers = [];
this.available = [];
if(typeof(worker) === "string") {
this.workerStr = worker;
this.addWorkers(concurrency || 1);
}
if(typeof(worker) === "function") {
this.workerFunction = worker;
this.addFakeWorkers(concurrency || 1);
}
}
FP.Queue.prototype.addWorkers = function(concurrency){
var min = this.concurrency,
max = min + concurrency;
//-- Stop running jobs or something?
for(var i=min; i < concurrency; i++){
var worker = new Worker(this.workerStr);
this.workers.push(worker); //-- Add new work
this.available.push(i); //-- Make available to start tasks
}
this.concurrency = concurrency;
}
FP.Queue.prototype.addFakeWorkers = function(concurrency){
var min = this.concurrency,
max = min + concurrency;
//-- Stop running jobs or something?
for(var i=min; i < concurrency; i++){
var worker = new FP.FakeWorker(this.workerFunction);
this.workers.push(worker); //-- Add new work
this.available.push(i); //-- Make available to start tasks
}
this.concurrency = concurrency;
}
FP.Queue.prototype.add = function(msg, callback, priority){
var ID = this.idCount;
//-- Add to task object : maybe check for dups
this._tasks[ID] = {
"msg": msg,
"callback": callback || function(){}
}
//-- Add id to queue
if(!priority){
this._q.push(ID);
}else{
this._q.unshift(ID);
if(!this.running) this.run();
}
//-- Increment ID for next task
this.idCount++;
return ID;
}
FP.Queue.prototype.addGroup = function(group, callback){
var that = this,
counter = group.length,
after = function(){
counter--;
if(counter <= 0) callback();
};
group.forEach(function(msg){
that.add(msg, after);
});
if(!this.running) this.run();
return after;
}
FP.Queue.prototype.run = function(id){
if(this.running) return;
this.running = true;
while(this.available.length) {
var next = this.next();
if(!next) break; //-- no tasks left or error
}
}
FP.Queue.prototype.find = function(msg){
}
FP.Queue.prototype.next = function(){
var that = this,
curr = this._q.shift(),
task,
workerID,
worker;
if(typeof(curr) === "undefined"){
//-- Nothing left on queue
this.running = false;
return false;
}
task = this._tasks[curr];
workerID = this.available.pop();
worker = this.workers[workerID];
//-- give worker new task
worker.postMessage(task.msg);
//-- listen for worker response
worker.onmessage = function(e){
var data = e.data;
task.callback(data);
delete that._tasks[curr]; //-- Remove task
that.available.push(workerID);
that.next();
}
return worker;
}
FP.Queue.prototype.empty = function(){
this._q = [];
this._tasks = {};
//-- TODO: close workers
}
//-- A super simplistic fake worker, is passed a function instead of a script
FP.FakeWorker = function(func){
this.func = func;
}
FP.FakeWorker.prototype.postMessage = function(msg){
setTimeout(function(){
this.func(msg, this.onmessage);
}.bind(this), 1);
}
FP.FakeWorker.prototype.onmessage = function(e){
}
FP.FakeWorker.prototype.close = function(e){
}

View file

@ -16,6 +16,7 @@ FP.storage = function(){
//-- Handle load errors //-- Handle load errors
this._store.failed = _error; this._store.failed = _error;
} }
function get(path, callback) { function get(path, callback) {
@ -26,6 +27,14 @@ FP.storage = function(){
return this._store.preload(path, callback); return this._store.preload(path, callback);
} }
function batch(group, callback) {
return this._store.batch(group, callback);
}
function replace(path, content, callback) {
//return this._store.batch(group, callback);
}
function _error(err){ function _error(err){
console.log("error", err); console.log("error", err);
} }
@ -33,15 +42,18 @@ FP.storage = function(){
return { return {
"get" : get, "get" : get,
"preload" : preload, "preload" : preload,
"batch" : batch,
"storageMethod": storageMethod "storageMethod": storageMethod
} }
}(); }();
/*
FP.storage.ram = function() { FP.storage.ram = function() {
var _store = {}, var _store = {},
_blobs = {}; _blobs = {},
_queue = new FP.Queue("loader_ram.js", 3);
//-- TODO: this should be prototypes? //-- TODO: this should be prototypes?
@ -54,6 +66,10 @@ FP.storage.ram = function() {
} }
} }
function batch(group, callback){
_queue.addGroup(group)
}
//-- Fetches url //-- Fetches url
function get(path, callback) { function get(path, callback) {
var fromCache = check(path), var fromCache = check(path),
@ -148,6 +164,7 @@ FP.storage.ram = function() {
"preload" : preload "preload" : preload
} }
} }
*/
FP.storage.none = function() { FP.storage.none = function() {
var _store = {}; var _store = {};
@ -222,3 +239,132 @@ FP.storage.none = function() {
"preload" : preload "preload" : preload
} }
} }
FP.storage.ram = function() {
var _store = {},
_blobs = {},
_queue = new FP.Queue(loader, 6);
//-- max of 6 concurrent requests: http://www.browserscope.org/?category=network
function loader(msg, callback){
var e = {"data":null},
fromCache = check(msg);
if(fromCache){
e.data = fromCache;
callback(e);
}else{
request(msg, function(file){
e.data = file;
callback(e);
});
}
}
function preload(path) {
var fromCache = check(path);
if(!fromCache){
_queue.add(path);
}
}
function batch(group, callback){
_queue.addGroup(group, callback);
}
//-- Fetches url
function get(path, callback) {
var fromCache = check(path),
url;
if(fromCache){
url = getURL(path, fromCache);
if(typeof(callback) != "undefined"){
callback(url);
}
}else{
_queue.add(path, function(file){
url = getURL(path, file);
if(typeof(callback) != "undefined"){
callback(url);
}
}, true);
}
}
function check(path) {
var file = _store[path];
if(typeof(file) != "undefined"){
return file;
}
return false;
}
function request(path, callback) {
var xhr = new FP.core.loadFile(path);
xhr.succeeded = function(file) {
//console.log("file", file)
cache(path, file);
if(typeof(callback) != "undefined"){
callback(file);
}
}
xhr.failed = _error;
xhr.start();
}
function cache(path, file) {
if(_store[path]) return;
_store[path] = file;
}
function getURL(path, file){
var url;
if(typeof(_blobs[path]) != "undefined"){
return _blobs[path];
}
url = this._URL.createObjectURL(file);
//-- need to revokeObjectURL previous urls, but only when cleaning cache
// this.createdURLs.forEach(function(url){
// this._URL.revokeObjectURL(url);
// });
_blobs[path] = url;
return url;
}
// this.succeeded = function(){
// console.log("loaded");
// }
//
// this.failed = function(){
// console.log("loaded");
// }
function _error(err){
if(typeof(this.failed) == "undefined"){
console.log("Error: ", err);
}else{
this.failed(err);
}
}
return {
"get" : get,
"preload" : preload,
"batch" : batch
}
}

View file

@ -10,7 +10,8 @@
<link rel="stylesheet" href="css/normalize.css"> <link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css"> <link rel="stylesheet" href="css/main.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="fpjs/libs/jquery-1.9.0.min.js"><\/script>')</script>
<script src="fpjs/libs/modernizr-2.6.2.min.js"></script> <script src="fpjs/libs/modernizr-2.6.2.min.js"></script>
<script> <script>
//-- Define Global //-- Define Global
@ -35,6 +36,7 @@
<script async src="fpjs/render/core.js"></script> <script async src="fpjs/render/core.js"></script>
<script async src="fpjs/render/queue.js"></script>
<script async src="fpjs/render/storage.js"></script> <script async src="fpjs/render/storage.js"></script>
<script async src="fpjs/render/events.js"></script> <script async src="fpjs/render/events.js"></script>
<script async src="fpjs/render/book.js"></script> <script async src="fpjs/render/book.js"></script>