(function(glob) {
var undefined = {}.a;
function definition(Q) {
/**
@author Matt Crinklaw-Vogt
*/
function PipeContext(handlers, nextMehod, end) {
this._handlers = handlers;
this._next = nextMehod;
this._end = end;
this._i = 0;
}
PipeContext.prototype = {
next: function() {
// var args = Array.prototype.slice.call(arguments, 0);
// args.unshift(this);
this.__pipectx = this;
return this._next.apply(this, arguments);
},
_nextHandler: function() {
if (this._i >= this._handlers.length) return this._end;
var handler = this._handlers[this._i].handler;
this._i += 1;
return handler;
},
length: function() {
return this._handlers.length;
}
};
function indexOfHandler(handlers, len, target) {
for (var i = 0; i < len; ++i) {
var handler = handlers[i];
if (handler.name === target || handler.handler === target) {
return i;
}
}
return -1;
}
function forward(ctx) {
return ctx.next.apply(ctx, Array.prototype.slice.call(arguments, 1));
}
function coerce(methodNames, handler) {
methodNames.forEach(function(meth) {
if (!handler[meth])
handler[meth] = forward;
});
}
var abstractPipeline = {
addFirst: function(name, handler) {
coerce(this._pipedMethodNames, handler);
this._handlers.unshift({name: name, handler: handler});
},
addLast: function(name, handler) {
coerce(this._pipedMethodNames, handler);
this._handlers.push({name: name, handler: handler});
},
/**
Add the handler with the given name after the
handler specified by target. Target can be a handler
name or a handler instance.
*/
addAfter: function(target, name, handler) {
coerce(this._pipedMethodNames, handler);
var handlers = this._handlers;
var len = handlers.length;
var i = indexOfHandler(handlers, len, target);
if (i >= 0) {
handlers.splice(i+1, 0, {name: name, handler: handler});
}
},
/**
Add the handler with the given name after the handler
specified by target. Target can be a handler name or
a handler instance.
*/
addBefore: function(target, name, handler) {
coerce(this._pipedMethodNames, handler);
var handlers = this._handlers;
var len = handlers.length;
var i = indexOfHandler(handlers, len, target);
if (i >= 0) {
handlers.splice(i, 0, {name: name, handler: handler});
}
},
/**
Replace the handler specified by target.
*/
replace: function(target, newName, handler) {
coerce(this._pipedMethodNames, handler);
var handlers = this._handlers;
var len = handlers.length;
var i = indexOfHandler(handlers, len, target);
if (i >= 0) {
handlers.splice(i, 1, {name: newName, handler: handler});
}
},
removeFirst: function() {
return this._handlers.shift();
},
removeLast: function() {
return this._handlers.pop();
},
remove: function(target) {
var handlers = this._handlers;
var len = handlers.length;
var i = indexOfHandler(handlers, len, target);
if (i >= 0)
handlers.splice(i, 1);
},
getHandler: function(name) {
var i = indexOfHandler(this._handlers, this._handlers.length, name);
if (i >= 0)
return this._handlers[i].handler;
return null;
}
};
function createPipeline(pipedMethodNames) {
var end = {};
var endStubFunc = function() { return end; };
var nextMethods = {};
function Pipeline(pipedMethodNames) {
this.pipe = {
_handlers: [],
_contextCtor: PipeContext,
_nextMethods: nextMethods,
end: end,
_pipedMethodNames: pipedMethodNames
};
}
var pipeline = new Pipeline(pipedMethodNames);
for (var k in abstractPipeline) {
pipeline.pipe[k] = abstractPipeline[k];
}
pipedMethodNames.forEach(function(name) {
end[name] = endStubFunc;
nextMethods[name] = new Function(
"var handler = this._nextHandler();" +
"handler.__pipectx = this.__pipectx;" +
"return handler." + name + ".apply(handler, arguments);");
pipeline[name] = new Function(
"var ctx = new this.pipe._contextCtor(this.pipe._handlers, this.pipe._nextMethods." + name + ", this.pipe.end);"
+ "return ctx.next.apply(ctx, arguments);");
});
return pipeline;
}
createPipeline.isPipeline = function(obj) {
return obj instanceof Pipeline;
}
var utils = (function() {
return {
convertToBase64: function(blob, cb) {
var fr = new FileReader();
fr.onload = function(e) {
cb(e.target.result);
};
fr.onerror = function(e) {
};
fr.onabort = function(e) {
};
fr.readAsDataURL(blob);
},
dataURLToBlob: function(dataURL) {
var BASE64_MARKER = ';base64,';
if (dataURL.indexOf(BASE64_MARKER) == -1) {
var parts = dataURL.split(',');
var contentType = parts[0].split(':')[1];
var raw = parts[1];
return new Blob([raw], {type: contentType});
}
var parts = dataURL.split(BASE64_MARKER);
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array.buffer], {type: contentType});
},
splitAttachmentPath: function(path) {
var parts = path.split('/');
if (parts.length == 1)
parts.unshift('__nodoc__');
return parts;
},
mapAsync: function(fn, promise) {
var deferred = Q.defer();
promise.then(function(data) {
_mapAsync(fn, data, [], deferred);
}, function(e) {
deferred.reject(e);
});
return deferred.promise;
},
countdown: function(n, cb) {
var args = [];
return function() {
for (var i = 0; i < arguments.length; ++i)
args.push(arguments[i]);
n -= 1;
if (n == 0)
cb.apply(this, args);
}
}
};
function _mapAsync(fn, data, result, deferred) {
fn(data[result.length], function(v) {
result.push(v);
if (result.length == data.length)
deferred.resolve(result);
else
_mapAsync(fn, data, result, deferred);
}, function(err) {
deferred.reject(err);
})
}
})();
var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
var persistentStorage = navigator.persistentStorage || navigator.webkitPersistentStorage;
var FilesystemAPIProvider = (function(Q) {
function makeErrorHandler(deferred, finalDeferred) {
// TODO: normalize the error so
// we can handle it upstream
return function(e) {
if (e.code == 1) {
deferred.resolve(undefined);
} else {
if (finalDeferred)
finalDeferred.reject(e);
else
deferred.reject(e);
}
}
}
function getAttachmentPath(docKey, attachKey) {
docKey = docKey.replace(/\//g, '--');
var attachmentsDir = docKey + "-attachments";
return {
dir: attachmentsDir,
path: attachmentsDir + "/" + attachKey
};
}
function readDirEntries(reader, result) {
var deferred = Q.defer();
_readDirEntries(reader, result, deferred);
return deferred.promise;
}
function _readDirEntries(reader, result, deferred) {
reader.readEntries(function(entries) {
if (entries.length == 0) {
deferred.resolve(result);
} else {
result = result.concat(entries);
_readDirEntries(reader, result, deferred);
}
}, function(err) {
deferred.reject(err);
});
}
function entryToFile(entry, cb, eb) {
entry.file(cb, eb);
}
function entryToURL(entry) {
return entry.toURL();
}
function FSAPI(fs, numBytes, prefix) {
this._fs = fs;
this._capacity = numBytes;
this._prefix = prefix;
this.type = "FileSystemAPI";
}
FSAPI.prototype = {
getContents: function(path, options) {
var deferred = Q.defer();
path = this._prefix + path;
this._fs.root.getFile(path, {}, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
var data = e.target.result;
var err;
if (options && options.json) {
try {
data = JSON.parse(data);
} catch(e) {
err = new Error('unable to parse JSON for ' + path);
}
}
if (err) {
deferred.reject(err);
} else {
deferred.resolve(data);
}
};
reader.readAsText(file);
}, makeErrorHandler(deferred));
}, makeErrorHandler(deferred));
return deferred.promise;
},
// create a file at path
// and write `data` to it
setContents: function(path, data, options) {
var deferred = Q.defer();
if (options && options.json)
data = JSON.stringify(data);
path = this._prefix + path;
this._fs.root.getFile(path, {create:true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
var blob;
fileWriter.onwriteend = function(e) {
fileWriter.onwriteend = function() {
deferred.resolve();
};
fileWriter.truncate(blob.size);
}
fileWriter.onerror = makeErrorHandler(deferred);
if (data instanceof Blob) {
blob = data;
} else {
blob = new Blob([data], {type: 'text/plain'});
}
fileWriter.write(blob);
}, makeErrorHandler(deferred));
}, makeErrorHandler(deferred));
return deferred.promise;
},
ls: function(docKey) {
var isRoot = false;
if (!docKey) {docKey = this._prefix; isRoot = true;}
else docKey = this._prefix + docKey + "-attachments";
var deferred = Q.defer();
this._fs.root.getDirectory(docKey, {create:false},
function(entry) {
var reader = entry.createReader();
readDirEntries(reader, []).then(function(entries) {
var listing = [];
entries.forEach(function(entry) {
if (!entry.isDirectory) {
listing.push(entry.name);
}
});
deferred.resolve(listing);
});
}, function(error) {
deferred.reject(error);
});
return deferred.promise;
},
clear: function() {
var deferred = Q.defer();
var failed = false;
var ecb = function(err) {
failed = true;
deferred.reject(err);
}
this._fs.root.getDirectory(this._prefix, {},
function(entry) {
var reader = entry.createReader();
reader.readEntries(function(entries) {
var latch =
utils.countdown(entries.length, function() {
if (!failed)
deferred.resolve();
});
entries.forEach(function(entry) {
if (entry.isDirectory) {
entry.removeRecursively(latch, ecb);
} else {
entry.remove(latch, ecb);
}
});
if (entries.length == 0)
deferred.resolve();
}, ecb);
}, ecb);
return deferred.promise;
},
rm: function(path) {
var deferred = Q.defer();
var finalDeferred = Q.defer();
// remove attachments that go along with the path
path = this._prefix + path;
var attachmentsDir = path + "-attachments";
this._fs.root.getFile(path, {create:false},
function(entry) {
entry.remove(function() {
deferred.promise.then(finalDeferred.resolve);
}, function(err) {
finalDeferred.reject(err);
});
},
makeErrorHandler(finalDeferred));
this._fs.root.getDirectory(attachmentsDir, {},
function(entry) {
entry.removeRecursively(function() {
deferred.resolve();
}, function(err) {
finalDeferred.reject(err);
});
},
makeErrorHandler(deferred, finalDeferred));
return finalDeferred.promise;
},
getAttachment: function(docKey, attachKey) {
var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path;
var deferred = Q.defer();
this._fs.root.getFile(attachmentPath, {}, function(fileEntry) {
fileEntry.file(function(file) {
if (file.size == 0)
deferred.resolve(undefined);
else
deferred.resolve(file);
}, makeErrorHandler(deferred));
}, function(err) {
if (err.code == 1) {
deferred.resolve(undefined);
} else {
deferred.reject(err);
}
});
return deferred.promise;
},
getAttachmentURL: function(docKey, attachKey) {
var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path;
var deferred = Q.defer();
var url = 'filesystem:' + window.location.protocol + '//' + window.location.host + '/persistent/' + attachmentPath;
deferred.resolve(url);
// this._fs.root.getFile(attachmentPath, {}, function(fileEntry) {
// deferred.resolve(fileEntry.toURL());
// }, makeErrorHandler(deferred, "getting attachment file entry"));
return deferred.promise;
},
getAllAttachments: function(docKey) {
var deferred = Q.defer();
var attachmentsDir = this._prefix + docKey + "-attachments";
this._fs.root.getDirectory(attachmentsDir, {},
function(entry) {
var reader = entry.createReader();
deferred.resolve(
utils.mapAsync(function(entry, cb, eb) {
entry.file(function(file) {
cb({
data: file,
docKey: docKey,
attachKey: entry.name
});
}, eb);
}, readDirEntries(reader, [])));
}, function(err) {
deferred.resolve([]);
});
return deferred.promise;
},
getAllAttachmentURLs: function(docKey) {
var deferred = Q.defer();
var attachmentsDir = this._prefix + docKey + "-attachments";
this._fs.root.getDirectory(attachmentsDir, {},
function(entry) {
var reader = entry.createReader();
readDirEntries(reader, []).then(function(entries) {
deferred.resolve(entries.map(
function(entry) {
return {
url: entry.toURL(),
docKey: docKey,
attachKey: entry.name
};
}));
});
}, function(err) {
deferred.reject(err);
});
return deferred.promise;
},
revokeAttachmentURL: function(url) {
// we return FS urls so this is a no-op
// unless someone is being silly and doing
// createObjectURL(getAttachment()) ......
},
// Create a folder at dirname(path)+"-attachments"
// add attachment under that folder as basename(path)
setAttachment: function(docKey, attachKey, data) {
var attachInfo = getAttachmentPath(docKey, attachKey);
var deferred = Q.defer();
var self = this;
this._fs.root.getDirectory(this._prefix + attachInfo.dir,
{create:true}, function(dirEntry) {
deferred.resolve(self.setContents(attachInfo.path, data));
}, makeErrorHandler(deferred));
return deferred.promise;
},
// rm the thing at dirname(path)+"-attachments/"+basename(path)
rmAttachment: function(docKey, attachKey) {
var attachmentPath = getAttachmentPath(docKey, attachKey).path;
var deferred = Q.defer();
this._fs.root.getFile(this._prefix + attachmentPath, {create:false},
function(entry) {
entry.remove(function() {
deferred.resolve();
}, makeErrorHandler(deferred));
}, makeErrorHandler(deferred));
return deferred.promise;
},
getCapacity: function() {
return this._capacity;
}
};
return {
init: function(config) {
var deferred = Q.defer();
if (!requestFileSystem) {
deferred.reject("No FS API");
return deferred.promise;
}
var prefix = config.name + '/';
persistentStorage.requestQuota(config.size,
function(numBytes) {
requestFileSystem(window.PERSISTENT, numBytes,
function(fs) {
fs.root.getDirectory(config.name, {create: true},
function() {
deferred.resolve(new FSAPI(fs, numBytes, prefix));
}, function(err) {
console.error(err);
deferred.reject(err);
});
}, function(err) {
// TODO: implement various error messages.
console.error(err);
deferred.reject(err);
});
}, function(err) {
// TODO: implement various error messages.
console.error(err);
deferred.reject(err);
});
return deferred.promise;
},
isAvailable: function() {
return requestFileSystem != null;
}
}
})(Q);
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB;
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction;
var IndexedDBProvider = (function(Q) {
var URL = window.URL || window.webkitURL;
var convertToBase64 = utils.convertToBase64;
var dataURLToBlob = utils.dataURLToBlob;
function IDB(db) {
this._db = db;
this.type = 'IndexedDB';
var transaction = this._db.transaction(['attachments'], 'readwrite');
this._supportsBlobs = true;
try {
transaction.objectStore('attachments')
.put(Blob(["sdf"], {type: "text/plain"}), "featurecheck");
} catch (e) {
this._supportsBlobs = false;
}
}
// TODO: normalize returns and errors.
IDB.prototype = {
getContents: function(docKey) {
var deferred = Q.defer();
var transaction = this._db.transaction(['files'], 'readonly');
var get = transaction.objectStore('files').get(docKey);
get.onsuccess = function(e) {
deferred.resolve(e.target.result);
};
get.onerror = function(e) {
deferred.reject(e);
};
return deferred.promise;
},
setContents: function(docKey, data) {
var deferred = Q.defer();
var transaction = this._db.transaction(['files'], 'readwrite');
var put = transaction.objectStore('files').put(data, docKey);
put.onsuccess = function(e) {
deferred.resolve(e);
};
put.onerror = function(e) {
deferred.reject(e);
};
return deferred.promise;
},
rm: function(docKey) {
var deferred = Q.defer();
var finalDeferred = Q.defer();
var transaction = this._db.transaction(['files', 'attachments'], 'readwrite');
var del = transaction.objectStore('files').delete(docKey);
del.onsuccess = function(e) {
deferred.promise.then(function() {
finalDeferred.resolve();
});
};
del.onerror = function(e) {
deferred.promise.catch(function() {
finalDeferred.reject(e);
});
};
var attachmentsStore = transaction.objectStore('attachments');
var index = attachmentsStore.index('fname');
var cursor = index.openCursor(IDBKeyRange.only(docKey));
cursor.onsuccess = function(e) {
var cursor = e.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
} else {
deferred.resolve();
}
};
cursor.onerror = function(e) {
deferred.reject(e);
}
return finalDeferred.promise;
},
getAttachment: function(docKey, attachKey) {
var deferred = Q.defer();
var transaction = this._db.transaction(['attachments'], 'readonly');
var get = transaction.objectStore('attachments').get(docKey + '/' + attachKey);
var self = this;
get.onsuccess = function(e) {
if (!e.target.result) {
deferred.resolve(undefined);
return;
}
var data = e.target.result.data;
if (!self._supportsBlobs) {
data = dataURLToBlob(data);
}
deferred.resolve(data);
};
get.onerror = function(e) {
deferred.reject(e);
};
return deferred.promise;
},
ls: function(docKey) {
var deferred = Q.defer();
if (!docKey) {
// list docs
var store = 'files';
} else {
// list attachments
var store = 'attachments';
}
var transaction = this._db.transaction([store], 'readonly');
var cursor = transaction.objectStore(store).openCursor();
var listing = [];
cursor.onsuccess = function(e) {
var cursor = e.target.result;
if (cursor) {
listing.push(!docKey ? cursor.key : cursor.key.split('/')[1]);
cursor.continue();
} else {
deferred.resolve(listing);
}
};
cursor.onerror = function(e) {
deferred.reject(e);
};
return deferred.promise;
},
clear: function() {
var deferred = Q.defer();
var finalDeferred = Q.defer();
var t = this._db.transaction(['attachments', 'files'], 'readwrite');
var req1 = t.objectStore('attachments').clear();
var req2 = t.objectStore('files').clear();
req1.onsuccess = function() {
deferred.promise.then(finalDeferred.resolve);
};
req2.onsuccess = function() {
deferred.resolve();
};
req1.onerror = function(err) {
finalDeferred.reject(err);
};
req2.onerror = function(err) {
finalDeferred.reject(err);
};
return finalDeferred.promise;
},
getAllAttachments: function(docKey) {
var deferred = Q.defer();
var self = this;
var transaction = this._db.transaction(['attachments'], 'readonly');
var index = transaction.objectStore('attachments').index('fname');
var cursor = index.openCursor(IDBKeyRange.only(docKey));
var values = [];
cursor.onsuccess = function(e) {
var cursor = e.target.result;
if (cursor) {
var data;
if (!self._supportsBlobs) {
data = dataURLToBlob(cursor.value.data)
} else {
data = cursor.value.data;
}
values.push({
data: data,
docKey: docKey,
attachKey: cursor.primaryKey.split('/')[1] // TODO
});
cursor.continue();
} else {
deferred.resolve(values);
}
};
cursor.onerror = function(e) {
deferred.reject(e);
};
return deferred.promise;
},
getAllAttachmentURLs: function(docKey) {
var deferred = Q.defer();
this.getAllAttachments(docKey).then(function(attachments) {
var urls = attachments.map(function(a) {
a.url = URL.createObjectURL(a.data);
delete a.data;
return a;
});
deferred.resolve(urls);
}, function(e) {
deferred.reject(e);
});
return deferred.promise;
},
getAttachmentURL: function(docKey, attachKey) {
var deferred = Q.defer();
this.getAttachment(docKey, attachKey).then(function(attachment) {
deferred.resolve(URL.createObjectURL(attachment));
}, function(e) {
deferred.reject(e);
});
return deferred.promise;
},
revokeAttachmentURL: function(url) {
URL.revokeObjectURL(url);
},
setAttachment: function(docKey, attachKey, data) {
var deferred = Q.defer();
if (data instanceof Blob && !this._supportsBlobs) {
var self = this;
convertToBase64(data, function(data) {
continuation.call(self, data);
});
} else {
continuation.call(this, data);
}
function continuation(data) {
var obj = {
path: docKey + '/' + attachKey,
fname: docKey,
data: data
};
var transaction = this._db.transaction(['attachments'], 'readwrite');
var put = transaction.objectStore('attachments').put(obj);
put.onsuccess = function(e) {
deferred.resolve(e);
};
put.onerror = function(e) {
deferred.reject(e);
};
}
return deferred.promise;
},
rmAttachment: function(docKey, attachKey) {
var deferred = Q.defer();
var transaction = this._db.transaction(['attachments'], 'readwrite');
var del = transaction.objectStore('attachments').delete(docKey + '/' + attachKey);
del.onsuccess = function(e) {
deferred.resolve(e);
};
del.onerror = function(e) {
deferred.reject(e);
};
return deferred.promise;
}
};
return {
init: function(config) {
var deferred = Q.defer();
var dbVersion = 2;
if (!indexedDB || !IDBTransaction) {
deferred.reject("No IndexedDB");
return deferred.promise;
}
var request = indexedDB.open(config.name, dbVersion);
function createObjectStore(db) {
db.createObjectStore("files");
var attachStore = db.createObjectStore("attachments", {keyPath: 'path'});
attachStore.createIndex('fname', 'fname', {unique: false})
}
// TODO: normalize errors
request.onerror = function (event) {
deferred.reject(event);
};
request.onsuccess = function (event) {
var db = request.result;
db.onerror = function (event) {
console.log(event);
};
// Chrome workaround
if (db.setVersion) {
if (db.version != dbVersion) {
var setVersion = db.setVersion(dbVersion);
setVersion.onsuccess = function () {
createObjectStore(db);
deferred.resolve();
};
}
else {
deferred.resolve(new IDB(db));
}
} else {
deferred.resolve(new IDB(db));
}
}
request.onupgradeneeded = function (event) {
createObjectStore(event.target.result);
};
return deferred.promise;
},
isAvailable: function() {
return indexedDB != null && IDBTransaction != null;
}
}
})(Q);
var LocalStorageProvider = (function(Q) {
return {
init: function() {
return Q({type: 'LocalStorage'});
}
}
})(Q);
var openDb = window.openDatabase;
var WebSQLProvider = (function(Q) {
var URL = window.URL || window.webkitURL;
var convertToBase64 = utils.convertToBase64;
var dataURLToBlob = utils.dataURLToBlob;
function WSQL(db) {
this._db = db;
this.type = 'WebSQL';
}
WSQL.prototype = {
getContents: function(docKey, options) {
var deferred = Q.defer();
this._db.transaction(function(tx) {
tx.executeSql('SELECT value FROM files WHERE fname = ?', [docKey],
function(tx, res) {
if (res.rows.length == 0) {
deferred.resolve(undefined);
} else {
var data = res.rows.item(0).value;
if (options && options.json)
data = JSON.parse(data);
deferred.resolve(data);
}
});
}, function(err) {
consol.log(err);
deferred.reject(err);
});
return deferred.promise;
},
setContents: function(docKey, data, options) {
var deferred = Q.defer();
if (options && options.json)
data = JSON.stringify(data);
this._db.transaction(function(tx) {
tx.executeSql(
'INSERT OR REPLACE INTO files (fname, value) VALUES(?, ?)', [docKey, data]);
}, function(err) {
console.log(err);
deferred.reject(err);
}, function() {
deferred.resolve();
});
return deferred.promise;
},
rm: function(docKey) {
var deferred = Q.defer();
this._db.transaction(function(tx) {
tx.executeSql('DELETE FROM files WHERE fname = ?', [docKey]);
tx.executeSql('DELETE FROM attachments WHERE fname = ?', [docKey]);
}, function(err) {
console.log(err);
deferred.reject(err);
}, function() {
deferred.resolve();
});
return deferred.promise;
},
getAttachment: function(fname, akey) {
var deferred = Q.defer();
this._db.transaction(function(tx){
tx.executeSql('SELECT value FROM attachments WHERE fname = ? AND akey = ?',
[fname, akey],
function(tx, res) {
if (res.rows.length == 0) {
deferred.resolve(undefined);
} else {
deferred.resolve(dataURLToBlob(res.rows.item(0).value));
}
});
}, function(err) {
deferred.reject(err);
});
return deferred.promise;
},
getAttachmentURL: function(docKey, attachKey) {
var deferred = Q.defer();
this.getAttachment(docKey, attachKey).then(function(blob) {
deferred.resolve(URL.createObjectURL(blob));
}, function() {
deferred.reject();
});
return deferred.promise;
},
ls: function(docKey) {
var deferred = Q.defer();
var select;
var field;
if (!docKey) {
select = 'SELECT fname FROM files';
field = 'fname';
} else {
select = 'SELECT akey FROM attachments WHERE fname = ?';
field = 'akey';
}
this._db.transaction(function(tx) {
tx.executeSql(select, docKey ? [docKey] : [],
function(tx, res) {
var listing = [];
for (var i = 0; i < res.rows.length; ++i) {
listing.push(res.rows.item(i)[field]);
}
deferred.resolve(listing);
}, function(err) {
deferred.reject(err);
});
});
return deferred.promise;
},
clear: function() {
var deffered1 = Q.defer();
var deffered2 = Q.defer();
this._db.transaction(function(tx) {
tx.executeSql('DELETE FROM files', function() {
deffered1.resolve();
});
tx.executeSql('DELETE FROM attachments', function() {
deffered2.resolve();
});
}, function(err) {
deffered1.reject(err);
deffered2.reject(err);
});
return Q.all([deffered1, deffered2]);
},
getAllAttachments: function(fname) {
var deferred = Q.defer();
this._db.transaction(function(tx) {
tx.executeSql('SELECT value, akey FROM attachments WHERE fname = ?',
[fname],
function(tx, res) {
// TODO: ship this work off to a webworker
// since there could be many of these conversions?
var result = [];
for (var i = 0; i < res.rows.length; ++i) {
var item = res.rows.item(i);
result.push({
docKey: fname,
attachKey: item.akey,
data: dataURLToBlob(item.value)
});
}
deferred.resolve(result);
});
}, function(err) {
deferred.reject(err);
});
return deferred.promise;
},
getAllAttachmentURLs: function(fname) {
var deferred = Q.defer();
this.getAllAttachments(fname).then(function(attachments) {
var urls = attachments.map(function(a) {
a.url = URL.createObjectURL(a.data);
delete a.data;
return a;
});
deferred.resolve(urls);
}, function(e) {
deferred.reject(e);
});
return deferred.promise;
},
revokeAttachmentURL: function(url) {
URL.revokeObjectURL(url);
},
setAttachment: function(fname, akey, data) {
var deferred = Q.defer();
var self = this;
convertToBase64(data, function(data) {
self._db.transaction(function(tx) {
tx.executeSql(
'INSERT OR REPLACE INTO attachments (fname, akey, value) VALUES(?, ?, ?)',
[fname, akey, data]);
}, function(err) {
deferred.reject(err);
}, function() {
deferred.resolve();
});
});
return deferred.promise;
},
rmAttachment: function(fname, akey) {
var deferred = Q.defer();
this._db.transaction(function(tx) {
tx.executeSql('DELETE FROM attachments WHERE fname = ? AND akey = ?',
[fname, akey]);
}, function(err) {
deferred.reject(err);
}, function() {
deferred.resolve();
});
return deferred.promise;
}
};
return {
init: function(config) {
var deferred = Q.defer();
if (!openDb) {
deferred.reject("No WebSQL");
return deferred.promise;
}
var db = openDb(config.name, '1.0', 'large local storage', config.size);
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS files (fname unique, value)');
tx.executeSql('CREATE TABLE IF NOT EXISTS attachments (fname, akey, value)');
tx.executeSql('CREATE INDEX IF NOT EXISTS fname_index ON attachments (fname)');
tx.executeSql('CREATE INDEX IF NOT EXISTS akey_index ON attachments (akey)');
tx.executeSql('CREATE UNIQUE INDEX IF NOT EXISTS uniq_attach ON attachments (fname, akey)')
}, function(err) {
deferred.reject(err);
}, function() {
deferred.resolve(new WSQL(db));
});
return deferred.promise;
},
isAvailable: function() {
return openDb != null;
}
}
})(Q);
var LargeLocalStorage = (function(Q) {
var sessionMeta = localStorage.getItem('LargeLocalStorage-meta');
if (sessionMeta)
sessionMeta = JSON.parse(sessionMeta);
else
sessionMeta = {};
window.addEventListener('beforeunload', function() {
localStorage.setItem('LargeLocalStorage-meta', JSON.stringify(sessionMeta));
});
function defaults(options, defaultOptions) {
for (var k in defaultOptions) {
if (options[k] === undefined)
options[k] = defaultOptions[k];
}
return options;
}
var providers = {
FileSystemAPI: FilesystemAPIProvider,
IndexedDB: IndexedDBProvider,
WebSQL: WebSQLProvider
// LocalStorage: LocalStorageProvider
}
var defaultConfig = {
size: 10 * 1024 * 1024,
name: 'lls'
};
function selectImplementation(config) {
if (!config) config = {};
config = defaults(config, defaultConfig);
if (config.forceProvider) {
return providers[config.forceProvider].init(config);
}
return FilesystemAPIProvider.init(config).then(function(impl) {
return Q(impl);
}, function() {
return IndexedDBProvider.init(config);
}).then(function(impl) {
return Q(impl);
}, function() {
return WebSQLProvider.init(config);
}).then(function(impl) {
return Q(impl);
}, function() {
console.error('Unable to create any storage implementations. Using LocalStorage');
return LocalStorageProvider.init(config);
});
}
function copy(obj) {
var result = {};
Object.keys(obj).forEach(function(key) {
result[key] = obj[key];
});
return result;
}
function handleDataMigration(storageInstance, config, previousProviderType, currentProivderType) {
var previousProviderType =
sessionMeta[config.name] && sessionMeta[config.name].lastStorageImpl;
if (config.migrate) {
if (previousProviderType != currentProivderType
&& previousProviderType in providers) {
config = copy(config);
config.forceProvider = previousProviderType;
selectImplementation(config).then(function(prevImpl) {
config.migrate(null, prevImpl, storageInstance, config);
}, function(e) {
config.migrate(e);
});
} else {
if (config.migrationComplete)
config.migrationComplete();
}
}
}
/**
*
* LargeLocalStorage (or LLS) gives you a large capacity
* (up to several gig with permission from the user)
* key-value store in the browser.
*
* For storage, LLS uses the [FilesystemAPI](https://developer.mozilla.org/en-US/docs/WebGuide/API/File_System)
* when running in Chrome and Opera,
* [IndexedDB](https://developer.mozilla.org/en-US/docs/IndexedDB) in Firefox and IE
* and [WebSQL](http://www.w3.org/TR/webdatabase/) in Safari.
*
* When IndexedDB becomes available in Safari, LLS will
* update to take advantage of that storage implementation.
*
*
* Upon construction a LargeLocalStorage (LLS) object will be
* immediately returned but not necessarily immediately ready for use.
*
* A LLS object has an `initialized` property which is a promise
* that is resolved when the LLS object is ready for us.
*
* Usage of LLS would typically be:
* ```
* var storage = new LargeLocalStorage({size: 75*1024*1024});
* storage.initialized.then(function(grantedCapacity) {
* // storage ready to be used.
* });
* ```
*
* The reason that LLS may not be immediately ready for
* use is that some browsers require confirmation from the
* user before a storage area may be created. Also,
* the browser's native storage APIs are asynchronous.
*
* If an LLS instance is used before the storage
* area is ready then any
* calls to it will throw an exception with code: "NO_IMPLEMENTATION"
*
* This behavior is useful when you want the application
* to continue to function--regardless of whether or
* not the user has allowed it to store data--and would
* like to know when your storage calls fail at the point
* of those calls.
*
* LLS-contrib has utilities to queue storage calls until
* the implementation is ready. If an implementation
* is never ready this could obviously lead to memory issues
* which is why it is not the default behavior.
*
* @example
* var desiredCapacity = 50 * 1024 * 1024; // 50MB
* var storage = new LargeLocalStorage({
* // desired capacity, in bytes.
* size: desiredCapacity,
*
* // optional name for your LLS database. Defaults to lls.
* // This is the name given to the underlying
* // IndexedDB or WebSQL DB or FSAPI Folder.
* // LLS's with different names are independent.
* name: 'myStorage'
*
* // the following is an optional param
* // that is useful for debugging.
* // force LLS to use a specific storage implementation
* // forceProvider: 'IndexedDB' or 'WebSQL' or 'FilesystemAPI'
*
* // These parameters can be used to migrate data from one
* // storage implementation to another
* // migrate: LargeLocalStorage.copyOldData,
* // migrationComplete: function(err) {
* // db is initialized and old data has been copied.
* // }
* });
* storage.initialized.then(function(capacity) {
* if (capacity != -1 && capacity != desiredCapacity) {
* // the user didn't authorize your storage request
* // so instead you have some limitation on your storage
* }
* })
*
* @class LargeLocalStorage
* @constructor
* @param {object} config {size: sizeInByes, [forceProvider: force a specific implementation]}
* @return {LargeLocalStorage}
*/
function LargeLocalStorage(config) {
var deferred = Q.defer();
/**
* @property {promise} initialized
*/
this.initialized = deferred.promise;
var piped = createPipeline([
'ready',
'ls',
'rm',
'clear',
'getContents',
'setContents',
'getAttachment',
'setAttachment',
'getAttachmentURL',
'getAllAttachments',
'getAllAttachmentURLs',
'revokeAttachmentURL',
'rmAttachment',
'getCapacity',
'initialized']);
piped.pipe.addLast('lls', this);
piped.initialized = this.initialized;
var self = this;
selectImplementation(config).then(function(impl) {
self._impl = impl;
handleDataMigration(piped, config, self._impl.type);
sessionMeta[config.name] = sessionMeta[config.name] || {};
sessionMeta[config.name].lastStorageImpl = impl.type;
deferred.resolve(piped);
}).catch(function(e) {
// This should be impossible
console.log(e);
deferred.reject('No storage provider found');
});
return piped;
}
LargeLocalStorage.prototype = {
/**
* Whether or not LLS is ready to store data.
* The `initialized` property can be used to
* await initialization.
* @example
* // may or may not be true
* storage.ready();
*
* storage.initialized.then(function() {
* // always true
* storage.ready();
* })
* @method ready
*/
ready: function() {
return this._impl != null;
},
/**
* List all attachments under a given key.
*
* List all documents if no key is provided.
*
* Returns a promise that is fulfilled with
* the listing.
*
* @example
* storage.ls().then(function(docKeys) {
* console.log(docKeys);
* })
*
* @method ls
* @param {string} [docKey]
* @returns {promise} resolved with the listing, rejected if the listing fails.
*/
ls: function(docKey) {
this._checkAvailability();
return this._impl.ls(docKey);
},
/**
* Remove the specified document and all
* of its attachments.
*
* Returns a promise that is fulfilled when the
* removal completes.
*
* If no docKey is specified, this throws an error.
*
* To remove all files in LargeLocalStorage call
* `lls.clear();`
*
* To remove all attachments that were written without
* a docKey, call `lls.rm('__emptydoc__');`
*
* rm works this way to ensure you don't lose
* data due to an accidently undefined variable.
*
* @example
* stoarge.rm('exampleDoc').then(function() {
* alert('doc and all attachments were removed');
* })
*
* @method rm
* @param {string} docKey
* @returns {promise} resolved when removal completes, rejected if the removal fails.
*/
rm: function(docKey) {
this._checkAvailability();
return this._impl.rm(docKey);
},
/**
* An explicit way to remove all documents and
* attachments from LargeLocalStorage.
*
* @example
* storage.clear().then(function() {
* alert('all data has been removed');
* });
*
* @returns {promise} resolve when clear completes, rejected if clear fails.
*/
clear: function() {
this._checkAvailability();
return this._impl.clear();
},
/**
* Get the contents of a document identified by `docKey`
* TODO: normalize all implementations to allow storage
* and retrieval of JS objects?
*
* @example
* storage.getContents('exampleDoc').then(function(contents) {
* alert(contents);
* });
*
* @method getContents
* @param {string} docKey
* @returns {promise} resolved with the contents when the get completes
*/
getContents: function(docKey, options) {
this._checkAvailability();
return this._impl.getContents(docKey, options);
},
/**
* Set the contents identified by `docKey` to `data`.
* The document will be created if it does not exist.
*
* @example
* storage.setContents('exampleDoc', 'some data...').then(function() {
* alert('doc written');
* });
*
* @method setContents
* @param {string} docKey
* @param {any} data
* @returns {promise} fulfilled when set completes
*/
setContents: function(docKey, data, options) {
this._checkAvailability();
return this._impl.setContents(docKey, data, options);
},
/**
* Get the attachment identified by `docKey` and `attachKey`
*
* @example
* storage.getAttachment('exampleDoc', 'examplePic').then(function(attachment) {
* var url = URL.createObjectURL(attachment);
* var image = new Image(url);
* document.body.appendChild(image);
* URL.revokeObjectURL(url);
* })
*
* @method getAttachment
* @param {string} [docKey] Defaults to `__emptydoc__`
* @param {string} attachKey key of the attachment
* @returns {promise} fulfilled with the attachment or
* rejected if it could not be found. code: 1
*/
getAttachment: function(docKey, attachKey) {
if (!docKey) docKey = '__emptydoc__';
this._checkAvailability();
return this._impl.getAttachment(docKey, attachKey);
},
/**
* Set an attachment for a given document. Identified
* by `docKey` and `attachKey`.
*
* @example
* storage.setAttachment('myDoc', 'myPic', blob).then(function() {
* alert('Attachment written');
* })
*
* @method setAttachment
* @param {string} [docKey] Defaults to `__emptydoc__`
* @param {string} attachKey key for the attachment
* @param {any} attachment data
* @returns {promise} resolved when the write completes. Rejected
* if an error occurs.
*/
setAttachment: function(docKey, attachKey, data) {
if (!docKey) docKey = '__emptydoc__';
this._checkAvailability();
return this._impl.setAttachment(docKey, attachKey, data);
},
/**
* Get the URL for a given attachment.
*
* @example
* storage.getAttachmentURL('myDoc', 'myPic').then(function(url) {
* var image = new Image();
* image.src = url;
* document.body.appendChild(image);
* storage.revokeAttachmentURL(url);
* })
*
* This is preferrable to getting the attachment and then getting the
* URL via `createObjectURL` (on some systems) as LLS can take advantage of
* lower level details to improve performance.
*
* @method getAttachmentURL
* @param {string} [docKey] Identifies the document. Defaults to `__emptydoc__`
* @param {string} attachKey Identifies the attachment.
* @returns {promose} promise that is resolved with the attachment url.
*/
getAttachmentURL: function(docKey, attachKey) {
if (!docKey) docKey = '__emptydoc__';
this._checkAvailability();
return this._impl.getAttachmentURL(docKey, attachKey);
},
/**
* Gets all of the attachments for a document.
*
* @example
* storage.getAllAttachments('exampleDoc').then(function(attachEntries) {
* attachEntries.map(function(entry) {
* var a = entry.data;
* // do something with it...
* if (a.type.indexOf('image') == 0) {
* // show image...
* } else if (a.type.indexOf('audio') == 0) {
* // play audio...
* } else ...
* })
* })
*
* @method getAllAttachments
* @param {string} [docKey] Identifies the document. Defaults to `__emptydoc__`
* @returns {promise} Promise that is resolved with all of the attachments for
* the given document.
*/
getAllAttachments: function(docKey) {
if (!docKey) docKey = '__emptydoc__';
this._checkAvailability();
return this._impl.getAllAttachments(docKey);
},
/**
* Gets all attachments URLs for a document.
*
* @example
* storage.getAllAttachmentURLs('exampleDoc').then(function(urlEntries) {
* urlEntries.map(function(entry) {
* var url = entry.url;
* // do something with the url...
* })
* })
*
* @method getAllAttachmentURLs
* @param {string} [docKey] Identifies the document. Defaults to the `__emptydoc__` document.
* @returns {promise} Promise that is resolved with all of the attachment
* urls for the given doc.
*/
getAllAttachmentURLs: function(docKey) {
if (!docKey) docKey = '__emptydoc__';
this._checkAvailability();
return this._impl.getAllAttachmentURLs(docKey);
},
/**
* Revoke the attachment URL as required by the underlying
* storage system.
*
* This is akin to `URL.revokeObjectURL(url)`
* URLs that come from `getAttachmentURL` or `getAllAttachmentURLs`
* should be revoked by LLS and not `URL.revokeObjectURL`
*
* @example
* storage.getAttachmentURL('doc', 'attach').then(function(url) {
* // do something with the URL
* storage.revokeAttachmentURL(url);
* })
*
* @method revokeAttachmentURL
* @param {string} url The URL as returned by `getAttachmentURL` or `getAttachmentURLs`
* @returns {void}
*/
revokeAttachmentURL: function(url) {
this._checkAvailability();
return this._impl.revokeAttachmentURL(url);
},
/**
* Remove an attachment from a document.
*
* @example
* storage.rmAttachment('exampleDoc', 'someAttachment').then(function() {
* alert('exampleDoc/someAttachment removed');
* }).catch(function(e) {
* alert('Attachment removal failed: ' + e);
* });
*
* @method rmAttachment
* @param {string} docKey
* @param {string} attachKey
* @returns {promise} Promise that is resolved once the remove completes
*/
rmAttachment: function(docKey, attachKey) {
if (!docKey) docKey = '__emptydoc__';
this._checkAvailability();
return this._impl.rmAttachment(docKey, attachKey);
},
/**
* Returns the actual capacity of the storage or -1
* if it is unknown. If the user denies your request for
* storage you'll get back some smaller amount of storage than what you
* actually requested.
*
* TODO: return an estimated capacity if actual capacity is unknown?
* -Firefox is 50MB until authorized to go above,
* -Chrome is some % of available disk space,
* -Safari unlimited as long as the user keeps authorizing size increases
* -Opera same as safari?
*
* @example
* // the initialized property will call you back with the capacity
* storage.initialized.then(function(capacity) {
* console.log('Authorized to store: ' + capacity + ' bytes');
* });
* // or if you know your storage is already available
* // you can call getCapacity directly
* storage.getCapacity()
*
* @method getCapacity
* @returns {number} Capacity, in bytes, of the storage. -1 if unknown.
*/
getCapacity: function() {
this._checkAvailability();
if (this._impl.getCapacity)
return this._impl.getCapacity();
else
return -1;
},
_checkAvailability: function() {
if (!this._impl) {
throw {
msg: "No storage implementation is available yet. The user most likely has not granted you app access to FileSystemAPI or IndexedDB",
code: "NO_IMPLEMENTATION"
};
}
}
};
LargeLocalStorage.contrib = {};
function writeAttachments(docKey, attachments, storage) {
var promises = [];
attachments.forEach(function(attachment) {
promises.push(storage.setAttachment(docKey, attachment.attachKey, attachment.data));
});
return Q.all(promises);
}
function copyDocs(docKeys, oldStorage, newStorage) {
var promises = [];
docKeys.forEach(function(key) {
promises.push(oldStorage.getContents(key).then(function(contents) {
return newStorage.setContents(key, contents);
}));
});
docKeys.forEach(function(key) {
promises.push(oldStorage.getAllAttachments(key).then(function(attachments) {
return writeAttachments(key, attachments, newStorage);
}));
});
return Q.all(promises);
}
LargeLocalStorage.copyOldData = function(err, oldStorage, newStorage, config) {
if (err) {
throw err;
}
oldStorage.ls().then(function(docKeys) {
return copyDocs(docKeys, oldStorage, newStorage)
}).then(function() {
if (config.migrationComplete)
config.migrationComplete();
}, function(e) {
config.migrationComplete(e);
});
};
LargeLocalStorage._sessionMeta = sessionMeta;
var availableProviders = [];
Object.keys(providers).forEach(function(potentialProvider) {
if (providers[potentialProvider].isAvailable())
availableProviders.push(potentialProvider);
});
LargeLocalStorage.availableProviders = availableProviders;
return LargeLocalStorage;
})(Q);
return LargeLocalStorage;
}
if (typeof define === 'function' && define.amd) {
define(['Q'], definition);
} else {
glob.LargeLocalStorage = definition.call(glob, Q);
}
}).call(this, this);From http://tantaman.github.io/LargeLocalStorage/dist/LargeLocalStorage.js
Travelled to 12 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, gwrvuhgaqvyk, irmadwmeruwu, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt
No comments. add comment
| Snippet ID: | #1018990 |
| Snippet name: | LargeLocalStorage.js |
| Eternal ID of this version: | #1018990/2 |
| Text MD5: | 9e71c922ae158003f28ddc3f96b577d3 |
| Author: | stefan |
| Category: | javax / web |
| Type: | Document |
| Public (visible to everyone): | Yes |
| Archived (hidden from active list): | No |
| Created/modified: | 2018-10-19 16:43:38 |
| Source code size: | 50103 bytes / 1889 lines |
| Pitched / IR pitched: | No / No |
| Views / Downloads: | 535 / 153 |
| Version history: | 1 change(s) |
| Referenced in: | -