Storage abstraction.

This commit is contained in:
Peter 2014-03-11 02:01:22 +01:00
parent 44e4903598
commit a4ea0c8048
4 changed files with 260 additions and 160 deletions

View File

@ -34,6 +34,7 @@ define(function(require) {
var util = require('util');
var cookies = require('cookies');
var geometry = require('geometry');
var osabstraction = require('osabstraction');
// Maximum number of titles to display in a search
var MAX_SEARCH_RESULT_SIZE = 50;
@ -155,12 +156,15 @@ define(function(require) {
// We have to scan all the DeviceStorages, because getDeviceStorage
// only returns the default Device Storage.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=885753
storages = navigator.getDeviceStorages("sdcard");
storages = $.map(navigator.getDeviceStorages("sdcard"), function(s) {
return new osabstraction.StorageFirefoxOS(s);
});
}
else {
// The method getDeviceStorages is not available (FxOS 1.0)
// The fallback is to use getDeviceStorage
storages[0] = navigator.getDeviceStorage("sdcard");
storages[0] = new osabstraction.StorageFirefoxOS(
navigator.getDeviceStorage("sdcard"));
}
}

View File

@ -28,6 +28,7 @@ define(function(require) {
var evopediaTitle = require('title');
var util = require('util');
var geometry = require('geometry');
var jQuery = require('jquery');
// Declare the webworker that can uncompress with bzip2 algorithm
var webworkerBzip2 = new Worker("js/lib/webworker_bzip2.js");
@ -69,20 +70,16 @@ define(function(require) {
*/
LocalArchive.prototype.readTitleFilesFromStorage = function(storage, directory) {
var currentLocalArchiveInstance = this;
var filerequest = storage.get(directory + 'titles.idx');
filerequest.onsuccess = function() {
currentLocalArchiveInstance.titleFile = filerequest.result;
};
filerequest.onerror = function(event) {
alert("Error reading title file in directory " + directory + " : " + event.target.error.name);
};
var filerequestSearch = storage.get(directory + 'titles_search.idx');
filerequestSearch.onsuccess = function() {
currentLocalArchiveInstance.titleSearchFile = filerequest.result;
};
filerequest.onerror = function(event) {
storage.get(directory + 'titles.idx').then(function(file) {
currentLocalArchiveInstance.titleFile = file;
}, function(error) {
alert("Error reading title file in directory " + directory + " : " + error);
});
storage.get(directory + 'titles_search.idx').then(function(file) {
currentLocalArchiveInstance.titleSearchFile = file;
}, function(error) {
// Do nothing : this file is not mandatory in an archive
};
});
};
/**
@ -102,20 +99,18 @@ define(function(require) {
} else {
prefixedFileNumber = index;
}
var filerequest = storage.get(directory + 'wikipedia_' + prefixedFileNumber
+ '.dat');
filerequest.onsuccess = function() {
currentLocalArchiveInstance.dataFiles[index] = filerequest.result;
currentLocalArchiveInstance.readDataFilesFromStorage(storage, directory,
index + 1);
};
filerequest.onerror = function(event) {
// TODO there must be a better to way to detect a FileNotFound
if (event.target.error.name != "NotFoundError") {
alert("Error reading data file " + index + " in directory "
+ directory + " : " + event.target.error.name);
}
};
storage.get(directory + 'wikipedia_' + prefixedFileNumber + '.dat')
.then(function(file) {
currentLocalArchiveInstance.dataFiles[index] = file;
currentLocalArchiveInstance.readDataFilesFromStorage(storage, directory,
index + 1);
}, function(error) {
// TODO there must be a better to way to detect a FileNotFound
if (error != "NotFoundError") {
alert("Error reading data file " + index + " in directory "
+ directory + " : " + error);
}
});
};
/**
@ -135,20 +130,18 @@ define(function(require) {
} else {
prefixedFileNumber = index;
}
var filerequest = storage.get(directory + 'coordinates_' + prefixedFileNumber
+ '.idx');
filerequest.onsuccess = function() {
currentLocalArchiveInstance.coordinateFiles[index - 1] = filerequest.result;
storage.get(directory + 'coordinates_' + prefixedFileNumber
+ '.idx').then(function(file) {
currentLocalArchiveInstance.coordinateFiles[index] = file;
currentLocalArchiveInstance.readCoordinateFilesFromStorage(storage, directory,
index + 1);
};
filerequest.onerror = function(event) {
}, function(error) {
// TODO there must be a better to way to detect a FileNotFound
if (event.target.error.name != "NotFoundError") {
if (error != "NotFoundError") {
alert("Error reading coordinates file " + index + " in directory "
+ directory + " : " + event.target.error.name);
+ directory + " : " + error);
}
};
});
};
/**
@ -161,15 +154,13 @@ define(function(require) {
LocalArchive.prototype.readMetadataFileFromStorage = function(storage, directory) {
var currentLocalArchiveInstance = this;
var filerequest = storage.get(directory + 'metadata.txt');
filerequest.onsuccess = function() {
var metadataFile = filerequest.result;
storage.get(directory + 'metadata.txt').then(function(file) {
var metadataFile = file;
currentLocalArchiveInstance.readMetadataFile(metadataFile);
};
filerequest.onerror = function(event) {
}, function(error) {
alert("Error reading metadata.txt file in directory "
+ directory + " : " + event.target.error.name);
};
+ directory + " : " + error);
});
};
/**
@ -300,20 +291,16 @@ define(function(require) {
*/
LocalArchive.prototype.readMathFilesFromStorage = function(storage, directory) {
var currentLocalArchiveInstance = this;
var filerequest1 = storage.get(directory + 'math.idx');
filerequest1.onsuccess = function() {
currentLocalArchiveInstance.mathIndexFile = filerequest1.result;
};
filerequest1.onerror = function(event) {
alert("Error reading math index file in directory " + directory + " : " + event.target.error.name);
};
var filerequest2 = storage.get(directory + 'math.dat');
filerequest2.onsuccess = function() {
currentLocalArchiveInstance.mathDataFile = filerequest2.result;
};
filerequest2.onerror = function(event) {
alert("Error reading math data file in directory " + directory + " : " + event.target.error.name);
};
storage.get(directory + 'math.idx').then(function(file) {
currentLocalArchiveInstance.mathIndexFile = file;
}, function(error) {
alert("Error reading math index file in directory " + directory + " : " + error);
});
storage.get(directory + 'math.dat').then(function(file) {
currentLocalArchiveInstance.mathDataFile = file;
}, function(error) {
alert("Error reading math data file in directory " + directory + " : " + error);
});
};
/**
@ -928,40 +915,23 @@ define(function(require) {
*/
LocalArchive.scanForArchives = function(storages, callbackFunction) {
var directories = [];
var cursor = util.enumerateAll(storages);
cursor.onerror = function() {
alert("Error scanning your SD card : " + cursor.error
+". If you're using the Firefox OS Simulator, please put the archives in a 'fake-sdcard' directory inside your Firefox profile (ex : ~/.mozilla/firefox/xxxx.default/extensions/r2d2b2g@mozilla.org/profile/fake-sdcard/wikipedia_small_2010-08-14)");
var promises = jQuery.map(storages, function(storage) {
return storage.scanForDirectoriesContainingFile('titles.idx')
.then(function(dirs) {
jQuery.merge(directories, dirs);
return true
});
});
jQuery.when.apply(null, promises).then(function() {
callbackFunction(directories);
}, function(error) {
alert("Error scanning your SD card : " + error
+ ". If you're using the Firefox OS Simulator, please put the archives in "
+ "a 'fake-sdcard' directory inside your Firefox profile "
+ "(ex : ~/.mozilla/firefox/xxxx.default/extensions/r2d2b2g@mozilla.org/"
+ "profile/fake-sdcard/wikipedia_small_2010-08-14)");
callbackFunction(null);
};
cursor.onsuccess = function() {
if (cursor.result) {
var file = cursor.result;
var fileName = file.name;
// We look for files "titles.idx"
if (!util.endsWith(fileName, "titles.idx")) {
cursor.continue();
return;
}
// Handle the case of archive files at the root of the sd-card
// (without a subdirectory)
var directory = "/";
if (fileName.lastIndexOf('/')!==-1) {
// We want to return the directory where titles.idx is stored
// We also keep the trailing slash
directory = fileName.substring(0, fileName.lastIndexOf('/') + 1);
}
directories.push(directory);
cursor.continue();
}
else {
callbackFunction(directories);
}
};
});
};
/**

193
www/js/lib/osabstraction.js Normal file
View File

@ -0,0 +1,193 @@
/**
* osabstraction.js: Abstraction layer for file access
*
* Copyright 2014 Evopedia developers
* License GPL v3:
*
* This file is part of Evopedia.
*
* Evopedia 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 3 of the License, or
* (at your option) any later version.
*
* Evopedia 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 Evopedia (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
*/
define(['util', 'jquery'], function(util, jQuery) {
/**
* Creates an abstraction layer around the FirefoxOS storage.
* @see StoragePhoneGap
* @param storage FirefoxOS DeviceStorage object
*/
function StorageFirefoxOS(storage) {
this._storage = storage;
this.storageName = storage.storageName;
}
/**
* Access the given file.
* @param path absolute path to the file
* @return jQuery promise which is resolved with a HTML5 file object and
* rejected with an error message.
*/
StorageFirefoxOS.prototype.get = function(path) {
var deferred = jQuery.Deferred();
var request = this._storage.get(path);
request.onsuccess = function() { deferred.resolve(this.result); }
request.onerror = function() { deferred.reject(this.error.name); }
return deferred.promise();
}
/**
* Searches for directories containing a file with the given name.
* @param fileName file name to search
* @return jQuery promise which is resolved with an array of directory
* paths and rejected with an error message.
*/
StorageFirefoxOS.prototype.scanForDirectoriesContainingFile
= function(fileName) {
var deferred = jQuery.Deferred();
var directories = [];
var cursor = this._storage.enumerate();
cursor.onerror = function() {
deferred.reject(cursor.error);
};
cursor.onsuccess = function() {
if (!cursor.result) {
deferred.resolve(directories);
return;
}
var file = cursor.result;
if (!util.endsWith(file.name, fileName)) {
cursor.continue();
return;
}
// Handle the case of archive files at the root of the sd-card
// (without a subdirectory)
var directory = "/";
if (file.name.lastIndexOf('/') !== -1) {
// We want to return the directory where the file is stored
// We also keep the trailing slash
directory = file.name.substring(0,
file.name.lastIndexOf('/') + 1);
}
directories.push(directory);
cursor.continue();
};
return deferred.promise();
}
/**
* Creates an abstraction layour around the PhoneGap storage.
* @see StorageFirefoxOS
* @param storage PhoneGap FileSystem object
*/
function StoragePhoneGap(storage) {
this._storage = storage;
this.storageName = 'PhoneGapStorage'; // TODO
}
/**
* Access the given file.
* @param path absolute path to the file
* @return jQuery promise which is resolved with a HTML5 file object and
* rejected with an error message.
*/
StoragePhoneGap.prototype.get = function(path) {
console.log("Trying to access " + path);
var deferred = jQuery.Deferred();
var that = this;
var onSuccess = function(file) {
deferred.resolve(file);
};
var onError = function(error) {
console.log("Error code: " + error.code);
deferred.reject(that._errorCodeToString(error.code));
};
var onSuccessInt = function(fileEntry) {
fileEntry.file(onSuccess, onError);
};
var options = {create: false, exclusive: false};
if (path.substr(0, 7) == 'file://')
path = path.substr(7);
this._storage.root.getFile(path, options, onSuccessInt, onError);
return deferred.promise();
}
/**
* Searches for directories containing a file with the given name.
* @param fileName file name to search
* @return jQuery promise which is resolved with an array of directory
* paths and rejected with an error message.
*/
StoragePhoneGap.prototype.scanForDirectoriesContainingFile
= function(fileName) {
var that = this;
var deferred = jQuery.Deferred();
var directories = [];
var stack = [this._storage.root];
var dirReaderSuccess = function(entries) {
var dir = stack[stack.length - 1];
stack.pop();
for (var i = 0; i < entries.length; i ++) {
var entry = entries[i];
if (entry.isDirectory) {
stack.push(entry);
} else if (util.endsWith(entry.name, fileName)) {
var path = dir.fullPath;
if (path.length == 0 || path[path.length - 1] != '/')
path += '/';
directories.push(path);
}
}
iteration();
}
var dirReaderFail = function(error) {
deferred.reject(that._errorCodeToString(error.code));
}
var iteration = function() {
if (stack.length == 0) {
deferred.resolve(directories);
return;
}
var reader = stack[stack.length - 1].createReader();
reader.readEntries(dirReaderSuccess, dirReaderFail);
}
iteration();
return deferred.promise();
}
/**
* Convert HTML5 FileError codes to strings.
* @param code FileError code
* @return string message corresponding to the error code
*/
StoragePhoneGap.prototype._errorCodeToString = function(code) {
switch (code) {
case FileError.QUOTA_EXCEEDED_ERR:
return 'QUOTA_EXCEEDED_ERR';
case FileError.NOT_FOUND_ERR:
return 'NOT_FOUND_ERR';
case FileError.SECURITY_ERR:
return 'SECURITY_ERR';
case FileError.INVALID_MODIFICATION_ERR:
return 'INVALID_MODIFICATION_ERR';
case FileError.INVALID_STATE_ERR:
return 'INVALID_STATE_ERR';
default:
return 'Unknown Error';
}
}
return {
StorageFirefoxOS: StorageFirefoxOS,
StoragePhoneGap: StoragePhoneGap
};
});

View File

@ -113,72 +113,6 @@ define(function(require) {
return (r > 0 ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3);
}
/**
* This function emulates a "composite" storage enumeration
* (i.e. returns files from all the storage areas)
* This is needed since the removal of composite storage :
* see https://bugzilla.mozilla.org/show_bug.cgi?id=885753
*
* This code was copied (with only slight modifications) from Gaia source code :
* https://bug893282.bugzilla.mozilla.org/attachment.cgi?id=785076
*
* @param {type} storages List of DeviceStorage instances
* @returns {_L22.enumerateAll.cursor} Cursor of files found in device storages
*/
function enumerateAll(storages) {
var storageIndex = 0;
var ds_cursor = null;
var cursor = {
continue: function cursor_continue() {
ds_cursor.continue();
}
};
function enumerateNextStorage() {
ds_cursor = storages[storageIndex].enumerate();
ds_cursor.onsuccess = onsuccess;
ds_cursor.onerror = onerror;
};
function onsuccess(e) {
cursor.result = e.target.result;
if (!cursor.result) {
storageIndex++;
if (storageIndex < storages.length) {
enumerateNextStorage();
return;
}
// If we've run out of storages, then we fall through and call
// onsuccess with the null result.
}
if (cursor.onsuccess) {
try {
cursor.onsuccess(e);
} catch (err) {
console.warn('enumerateAll onsuccess threw', err);
}
}
};
function onerror(e) {
cursor.error = e.target.error;
if (cursor.onerror) {
try {
cursor.onerror(e);
} catch (err) {
console.warn('enumerateAll onerror threw', err);
}
}
};
enumerateNextStorage();
return cursor;
}
/**
* Functions and classes exposed by this module
*/
@ -188,7 +122,6 @@ define(function(require) {
readIntegerFrom2Bytes : readIntegerFrom2Bytes,
readFloatFrom4Bytes : readFloatFrom4Bytes,
uint8ArrayToHex : uint8ArrayToHex,
uint8ArrayToBase64 : uint8ArrayToBase64,
enumerateAll : enumerateAll
uint8ArrayToBase64 : uint8ArrayToBase64
};
});