Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

1889
LINES

< > BotCompany Repo | #1018990 // LargeLocalStorage.js

Document

1  
(function(glob) {
2  
	var undefined = {}.a;
3  
4  
	function definition(Q) {
5  
	
6  
7  
/**
8  
@author Matt Crinklaw-Vogt
9  
*/
10  
function PipeContext(handlers, nextMehod, end) {
11  
	this._handlers = handlers;
12  
	this._next = nextMehod;
13  
	this._end = end;
14  
15  
	this._i = 0;
16  
}
17  
18  
PipeContext.prototype = {
19  
	next: function() {
20  
		// var args = Array.prototype.slice.call(arguments, 0);
21  
		// args.unshift(this);
22  
		this.__pipectx = this;
23  
		return this._next.apply(this, arguments);
24  
	},
25  
26  
	_nextHandler: function() {
27  
		if (this._i >= this._handlers.length) return this._end;
28  
29  
		var handler = this._handlers[this._i].handler;
30  
		this._i += 1;
31  
		return handler;
32  
	},
33  
34  
	length: function() {
35  
		return this._handlers.length;
36  
	}
37  
};
38  
39  
function indexOfHandler(handlers, len, target) {
40  
	for (var i = 0; i < len; ++i) {
41  
		var handler = handlers[i];
42  
		if (handler.name === target || handler.handler === target) {
43  
			return i;
44  
		}
45  
	}
46  
47  
	return -1;
48  
}
49  
50  
function forward(ctx) {
51  
	return ctx.next.apply(ctx, Array.prototype.slice.call(arguments, 1));
52  
}
53  
54  
function coerce(methodNames, handler) {
55  
	methodNames.forEach(function(meth) {
56  
		if (!handler[meth])
57  
			handler[meth] = forward;
58  
	});
59  
}
60  
61  
var abstractPipeline = {
62  
	addFirst: function(name, handler) {
63  
		coerce(this._pipedMethodNames, handler);
64  
		this._handlers.unshift({name: name, handler: handler});
65  
	},
66  
67  
	addLast: function(name, handler) {
68  
		coerce(this._pipedMethodNames, handler);
69  
		this._handlers.push({name: name, handler: handler});
70  
	},
71  
72  
 	/**
73  
 	Add the handler with the given name after the 
74  
 	handler specified by target.  Target can be a handler
75  
 	name or a handler instance.
76  
 	*/
77  
	addAfter: function(target, name, handler) {
78  
		coerce(this._pipedMethodNames, handler);
79  
		var handlers = this._handlers;
80  
		var len = handlers.length;
81  
		var i = indexOfHandler(handlers, len, target);
82  
83  
		if (i >= 0) {
84  
			handlers.splice(i+1, 0, {name: name, handler: handler});
85  
		}
86  
	},
87  
88  
	/**
89  
	Add the handler with the given name after the handler
90  
	specified by target.  Target can be a handler name or
91  
	a handler instance.
92  
	*/
93  
	addBefore: function(target, name, handler) {
94  
		coerce(this._pipedMethodNames, handler);
95  
		var handlers = this._handlers;
96  
		var len = handlers.length;
97  
		var i = indexOfHandler(handlers, len, target);
98  
99  
		if (i >= 0) {
100  
			handlers.splice(i, 0, {name: name, handler: handler});
101  
		}
102  
	},
103  
104  
	/**
105  
	Replace the handler specified by target.
106  
	*/
107  
	replace: function(target, newName, handler) {
108  
		coerce(this._pipedMethodNames, handler);
109  
		var handlers = this._handlers;
110  
		var len = handlers.length;
111  
		var i = indexOfHandler(handlers, len, target);
112  
113  
		if (i >= 0) {
114  
			handlers.splice(i, 1, {name: newName, handler: handler});
115  
		}
116  
	},
117  
118  
	removeFirst: function() {
119  
		return this._handlers.shift();
120  
	},
121  
122  
	removeLast: function() {
123  
		return this._handlers.pop();
124  
	},
125  
126  
	remove: function(target) {
127  
		var handlers = this._handlers;
128  
		var len = handlers.length;
129  
		var i = indexOfHandler(handlers, len, target);
130  
131  
		if (i >= 0)
132  
			handlers.splice(i, 1);
133  
	},
134  
135  
	getHandler: function(name) {
136  
		var i = indexOfHandler(this._handlers, this._handlers.length, name);
137  
		if (i >= 0)
138  
			return this._handlers[i].handler;
139  
		return null;
140  
	}
141  
};
142  
143  
function createPipeline(pipedMethodNames) {
144  
	var end = {};
145  
	var endStubFunc = function() { return end; };
146  
	var nextMethods = {};
147  
148  
	function Pipeline(pipedMethodNames) {
149  
		this.pipe = {
150  
			_handlers: [],
151  
			_contextCtor: PipeContext,
152  
			_nextMethods: nextMethods,
153  
			end: end,
154  
			_pipedMethodNames: pipedMethodNames
155  
		};
156  
	}
157  
158  
	var pipeline = new Pipeline(pipedMethodNames);
159  
	for (var k in abstractPipeline) {
160  
		pipeline.pipe[k] = abstractPipeline[k];
161  
	}
162  
163  
	pipedMethodNames.forEach(function(name) {
164  
		end[name] = endStubFunc;
165  
166  
		nextMethods[name] = new Function(
167  
			"var handler = this._nextHandler();" +
168  
			"handler.__pipectx = this.__pipectx;" +
169  
			"return handler." + name + ".apply(handler, arguments);");
170  
171  
		pipeline[name] = new Function(
172  
			"var ctx = new this.pipe._contextCtor(this.pipe._handlers, this.pipe._nextMethods." + name + ", this.pipe.end);"
173  
			+ "return ctx.next.apply(ctx, arguments);");
174  
	});
175  
176  
	return pipeline;
177  
}
178  
179  
createPipeline.isPipeline = function(obj) {
180  
	return obj instanceof Pipeline;
181  
}
182  
var utils = (function() {
183  
	return {
184  
		convertToBase64: function(blob, cb) {
185  
			var fr = new FileReader();
186  
			fr.onload = function(e) {
187  
				cb(e.target.result);
188  
			};
189  
			fr.onerror = function(e) {
190  
			};
191  
			fr.onabort = function(e) {
192  
			};
193  
			fr.readAsDataURL(blob);
194  
		},
195  
196  
		dataURLToBlob: function(dataURL) {
197  
				var BASE64_MARKER = ';base64,';
198  
				if (dataURL.indexOf(BASE64_MARKER) == -1) {
199  
					var parts = dataURL.split(',');
200  
					var contentType = parts[0].split(':')[1];
201  
					var raw = parts[1];
202  
203  
					return new Blob([raw], {type: contentType});
204  
				}
205  
206  
				var parts = dataURL.split(BASE64_MARKER);
207  
				var contentType = parts[0].split(':')[1];
208  
				var raw = window.atob(parts[1]);
209  
				var rawLength = raw.length;
210  
211  
				var uInt8Array = new Uint8Array(rawLength);
212  
213  
				for (var i = 0; i < rawLength; ++i) {
214  
					uInt8Array[i] = raw.charCodeAt(i);
215  
				}
216  
217  
				return new Blob([uInt8Array.buffer], {type: contentType});
218  
		},
219  
220  
		splitAttachmentPath: function(path) {
221  
			var parts = path.split('/');
222  
			if (parts.length == 1) 
223  
				parts.unshift('__nodoc__');
224  
			return parts;
225  
		},
226  
227  
		mapAsync: function(fn, promise) {
228  
			var deferred = Q.defer();
229  
			promise.then(function(data) {
230  
				_mapAsync(fn, data, [], deferred);
231  
			}, function(e) {
232  
				deferred.reject(e);
233  
			});
234  
235  
			return deferred.promise;
236  
		},
237  
238  
		countdown: function(n, cb) {
239  
		    var args = [];
240  
		    return function() {
241  
		      for (var i = 0; i < arguments.length; ++i)
242  
		        args.push(arguments[i]);
243  
		      n -= 1;
244  
		      if (n == 0)
245  
		        cb.apply(this, args);
246  
		    }
247  
		}
248  
	};
249  
250  
	function _mapAsync(fn, data, result, deferred) {
251  
		fn(data[result.length], function(v) {
252  
			result.push(v);
253  
			if (result.length == data.length)
254  
				deferred.resolve(result);
255  
			else
256  
				_mapAsync(fn, data, result, deferred);
257  
		}, function(err) {
258  
			deferred.reject(err);
259  
		})
260  
	}
261  
})();
262  
var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
263  
var persistentStorage = navigator.persistentStorage || navigator.webkitPersistentStorage;
264  
var FilesystemAPIProvider = (function(Q) {
265  
	function makeErrorHandler(deferred, finalDeferred) {
266  
		// TODO: normalize the error so
267  
		// we can handle it upstream
268  
		return function(e) {
269  
			if (e.code == 1) {
270  
				deferred.resolve(undefined);
271  
			} else {
272  
				if (finalDeferred)
273  
					finalDeferred.reject(e);
274  
				else
275  
					deferred.reject(e);
276  
			}
277  
		}
278  
	}
279  
280  
	function getAttachmentPath(docKey, attachKey) {
281  
		docKey = docKey.replace(/\//g, '--');
282  
		var attachmentsDir = docKey + "-attachments";
283  
		return {
284  
			dir: attachmentsDir,
285  
			path: attachmentsDir + "/" + attachKey
286  
		};
287  
	}
288  
289  
	function readDirEntries(reader, result) {
290  
		var deferred = Q.defer();
291  
292  
		_readDirEntries(reader, result, deferred);
293  
294  
		return deferred.promise;
295  
	}
296  
297  
	function _readDirEntries(reader, result, deferred) {
298  
		reader.readEntries(function(entries) {
299  
			if (entries.length == 0) {
300  
				deferred.resolve(result);
301  
			} else {
302  
				result = result.concat(entries);
303  
				_readDirEntries(reader, result, deferred);
304  
			}
305  
		}, function(err) {
306  
			deferred.reject(err);
307  
		});
308  
	}
309  
310  
	function entryToFile(entry, cb, eb) {
311  
		entry.file(cb, eb);
312  
	}
313  
314  
	function entryToURL(entry) {
315  
		return entry.toURL();
316  
	}
317  
318  
	function FSAPI(fs, numBytes, prefix) {
319  
		this._fs = fs;
320  
		this._capacity = numBytes;
321  
		this._prefix = prefix;
322  
		this.type = "FileSystemAPI";
323  
	}
324  
325  
	FSAPI.prototype = {
326  
		getContents: function(path, options) {
327  
			var deferred = Q.defer();
328  
			path = this._prefix + path;
329  
			this._fs.root.getFile(path, {}, function(fileEntry) {
330  
				fileEntry.file(function(file) {
331  
					var reader = new FileReader();
332  
333  
					reader.onloadend = function(e) {
334  
						var data = e.target.result;
335  
						var err;
336  
						if (options && options.json) {
337  
							try {
338  
								data = JSON.parse(data);
339  
							} catch(e) {
340  
								err = new Error('unable to parse JSON for ' + path);
341  
							}
342  
						}
343  
344  
						if (err) {
345  
							deferred.reject(err);
346  
						} else {
347  
							deferred.resolve(data);
348  
						}
349  
					};
350  
351  
					reader.readAsText(file);
352  
				}, makeErrorHandler(deferred));
353  
			}, makeErrorHandler(deferred));
354  
355  
			return deferred.promise;
356  
		},
357  
358  
		// create a file at path
359  
		// and write `data` to it
360  
		setContents: function(path, data, options) {
361  
			var deferred = Q.defer();
362  
363  
			if (options && options.json)
364  
				data = JSON.stringify(data);
365  
366  
			path = this._prefix + path;
367  
			this._fs.root.getFile(path, {create:true}, function(fileEntry) {
368  
				fileEntry.createWriter(function(fileWriter) {
369  
					var blob;
370  
					fileWriter.onwriteend = function(e) {
371  
						fileWriter.onwriteend = function() {
372  
							deferred.resolve();
373  
						};
374  
						fileWriter.truncate(blob.size);
375  
					}
376  
377  
					fileWriter.onerror = makeErrorHandler(deferred);
378  
379  
					if (data instanceof Blob) {
380  
						blob = data;
381  
					} else {
382  
						blob = new Blob([data], {type: 'text/plain'});
383  
					}
384  
385  
					fileWriter.write(blob);
386  
				}, makeErrorHandler(deferred));
387  
			}, makeErrorHandler(deferred));
388  
389  
			return deferred.promise;
390  
		},
391  
392  
		ls: function(docKey) {
393  
			var isRoot = false;
394  
			if (!docKey) {docKey = this._prefix; isRoot = true;}
395  
			else docKey = this._prefix + docKey + "-attachments";
396  
397  
			var deferred = Q.defer();
398  
399  
			this._fs.root.getDirectory(docKey, {create:false},
400  
			function(entry) {
401  
				var reader = entry.createReader();
402  
				readDirEntries(reader, []).then(function(entries) {
403  
					var listing = [];
404  
					entries.forEach(function(entry) {
405  
						if (!entry.isDirectory) {
406  
							listing.push(entry.name);
407  
						}
408  
					});
409  
					deferred.resolve(listing);
410  
				});
411  
			}, function(error) {
412  
				deferred.reject(error);
413  
			});
414  
415  
			return deferred.promise;
416  
		},
417  
418  
		clear: function() {
419  
			var deferred = Q.defer();
420  
			var failed = false;
421  
			var ecb = function(err) {
422  
				failed = true;
423  
				deferred.reject(err);
424  
			}
425  
426  
			this._fs.root.getDirectory(this._prefix, {},
427  
			function(entry) {
428  
				var reader = entry.createReader();
429  
				reader.readEntries(function(entries) {
430  
					var latch = 
431  
					utils.countdown(entries.length, function() {
432  
						if (!failed)
433  
							deferred.resolve();
434  
					});
435  
436  
					entries.forEach(function(entry) {
437  
						if (entry.isDirectory) {
438  
							entry.removeRecursively(latch, ecb);
439  
						} else {
440  
							entry.remove(latch, ecb);
441  
						}
442  
					});
443  
444  
					if (entries.length == 0)
445  
						deferred.resolve();
446  
				}, ecb);
447  
			}, ecb);
448  
449  
			return deferred.promise;
450  
		},
451  
452  
		rm: function(path) {
453  
			var deferred = Q.defer();
454  
			var finalDeferred = Q.defer();
455  
456  
			// remove attachments that go along with the path
457  
			path = this._prefix + path;
458  
			var attachmentsDir = path + "-attachments";
459  
460  
			this._fs.root.getFile(path, {create:false},
461  
				function(entry) {
462  
					entry.remove(function() {
463  
						deferred.promise.then(finalDeferred.resolve);
464  
					}, function(err) {
465  
						finalDeferred.reject(err);
466  
					});
467  
				},
468  
				makeErrorHandler(finalDeferred));
469  
470  
			this._fs.root.getDirectory(attachmentsDir, {},
471  
				function(entry) {
472  
					entry.removeRecursively(function() {
473  
						deferred.resolve();
474  
					}, function(err) {
475  
						finalDeferred.reject(err);
476  
					});
477  
				},
478  
				makeErrorHandler(deferred, finalDeferred));
479  
480  
			return finalDeferred.promise;
481  
		},
482  
483  
		getAttachment: function(docKey, attachKey) {
484  
			var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path;
485  
486  
			var deferred = Q.defer();
487  
			this._fs.root.getFile(attachmentPath, {}, function(fileEntry) {
488  
				fileEntry.file(function(file) {
489  
					if (file.size == 0)
490  
						deferred.resolve(undefined);
491  
					else
492  
						deferred.resolve(file);
493  
				}, makeErrorHandler(deferred));
494  
			}, function(err) {
495  
				if (err.code == 1) {
496  
					deferred.resolve(undefined);
497  
				} else {
498  
					deferred.reject(err);
499  
				}
500  
			});
501  
502  
			return deferred.promise;
503  
		},
504  
505  
		getAttachmentURL: function(docKey, attachKey) {
506  
			var attachmentPath = this._prefix + getAttachmentPath(docKey, attachKey).path;
507  
508  
			var deferred = Q.defer();
509  
			var url = 'filesystem:' + window.location.protocol + '//' + window.location.host + '/persistent/' + attachmentPath;
510  
			deferred.resolve(url);
511  
			// this._fs.root.getFile(attachmentPath, {}, function(fileEntry) {
512  
			// 	deferred.resolve(fileEntry.toURL());
513  
			// }, makeErrorHandler(deferred, "getting attachment file entry"));
514  
515  
			return deferred.promise;
516  
		},
517  
518  
		getAllAttachments: function(docKey) {
519  
			var deferred = Q.defer();
520  
			var attachmentsDir = this._prefix + docKey + "-attachments";
521  
522  
			this._fs.root.getDirectory(attachmentsDir, {},
523  
			function(entry) {
524  
				var reader = entry.createReader();
525  
				deferred.resolve(
526  
					utils.mapAsync(function(entry, cb, eb) {
527  
						entry.file(function(file) {
528  
							cb({
529  
								data: file,
530  
								docKey: docKey,
531  
								attachKey: entry.name
532  
							});
533  
						}, eb);
534  
					}, readDirEntries(reader, [])));
535  
			}, function(err) {
536  
				deferred.resolve([]);
537  
			});
538  
539  
			return deferred.promise;
540  
		},
541  
542  
		getAllAttachmentURLs: function(docKey) {
543  
			var deferred = Q.defer();
544  
			var attachmentsDir = this._prefix + docKey + "-attachments";
545  
546  
			this._fs.root.getDirectory(attachmentsDir, {},
547  
			function(entry) {
548  
				var reader = entry.createReader();
549  
				readDirEntries(reader, []).then(function(entries) {
550  
					deferred.resolve(entries.map(
551  
					function(entry) {
552  
						return {
553  
							url: entry.toURL(),
554  
							docKey: docKey,
555  
							attachKey: entry.name
556  
						};
557  
					}));
558  
				});
559  
			}, function(err) {
560  
				deferred.reject(err);
561  
			});
562  
563  
			return deferred.promise;
564  
		},
565  
566  
		revokeAttachmentURL: function(url) {
567  
			// we return FS urls so this is a no-op
568  
			// unless someone is being silly and doing
569  
			// createObjectURL(getAttachment()) ......
570  
		},
571  
572  
		// Create a folder at dirname(path)+"-attachments"
573  
		// add attachment under that folder as basename(path)
574  
		setAttachment: function(docKey, attachKey, data) {
575  
			var attachInfo = getAttachmentPath(docKey, attachKey);
576  
577  
			var deferred = Q.defer();
578  
579  
			var self = this;
580  
			this._fs.root.getDirectory(this._prefix + attachInfo.dir,
581  
			{create:true}, function(dirEntry) {
582  
				deferred.resolve(self.setContents(attachInfo.path, data));
583  
			}, makeErrorHandler(deferred));
584  
585  
			return deferred.promise;
586  
		},
587  
588  
		// rm the thing at dirname(path)+"-attachments/"+basename(path)
589  
		rmAttachment: function(docKey, attachKey) {
590  
			var attachmentPath = getAttachmentPath(docKey, attachKey).path;
591  
592  
			var deferred = Q.defer();
593  
			this._fs.root.getFile(this._prefix + attachmentPath, {create:false},
594  
				function(entry) {
595  
					entry.remove(function() {
596  
						deferred.resolve();
597  
					}, makeErrorHandler(deferred));
598  
			}, makeErrorHandler(deferred));
599  
600  
			return deferred.promise;
601  
		},
602  
603  
		getCapacity: function() {
604  
			return this._capacity;
605  
		}
606  
	};
607  
608  
	return {
609  
		init: function(config) {
610  
			var deferred = Q.defer();
611  
612  
			if (!requestFileSystem) {
613  
				deferred.reject("No FS API");
614  
				return deferred.promise;
615  
			}
616  
617  
			var prefix = config.name + '/';
618  
619  
			persistentStorage.requestQuota(config.size,
620  
			function(numBytes) {
621  
				requestFileSystem(window.PERSISTENT, numBytes,
622  
				function(fs) {
623  
					fs.root.getDirectory(config.name, {create: true},
624  
					function() {
625  
						deferred.resolve(new FSAPI(fs, numBytes, prefix));
626  
					}, function(err) {
627  
						console.error(err);
628  
						deferred.reject(err);
629  
					});
630  
				}, function(err) {
631  
					// TODO: implement various error messages.
632  
					console.error(err);
633  
					deferred.reject(err);
634  
				});
635  
			}, function(err) {
636  
				// TODO: implement various error messages.
637  
				console.error(err);
638  
				deferred.reject(err);
639  
			});
640  
641  
			return deferred.promise;
642  
		},
643  
644  
		isAvailable: function() {
645  
			return requestFileSystem != null;
646  
		}
647  
	}
648  
})(Q);
649  
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB;
650  
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction;
651  
var IndexedDBProvider = (function(Q) {
652  
	var URL = window.URL || window.webkitURL;
653  
654  
	var convertToBase64 = utils.convertToBase64;
655  
	var dataURLToBlob = utils.dataURLToBlob;
656  
657  
	function IDB(db) {
658  
		this._db = db;
659  
		this.type = 'IndexedDB';
660  
661  
		var transaction = this._db.transaction(['attachments'], 'readwrite');
662  
		this._supportsBlobs = true;
663  
		try {
664  
			transaction.objectStore('attachments')
665  
			.put(Blob(["sdf"], {type: "text/plain"}), "featurecheck");
666  
		} catch (e) {
667  
			this._supportsBlobs = false;
668  
		}
669  
	}
670  
671  
	// TODO: normalize returns and errors.
672  
	IDB.prototype = {
673  
		getContents: function(docKey) {
674  
			var deferred = Q.defer();
675  
			var transaction = this._db.transaction(['files'], 'readonly');
676  
677  
			var get = transaction.objectStore('files').get(docKey);
678  
			get.onsuccess = function(e) {
679  
				deferred.resolve(e.target.result);
680  
			};
681  
682  
			get.onerror = function(e) {
683  
				deferred.reject(e);
684  
			};
685  
686  
			return deferred.promise;
687  
		},
688  
689  
		setContents: function(docKey, data) {
690  
			var deferred = Q.defer();
691  
			var transaction = this._db.transaction(['files'], 'readwrite');
692  
693  
			var put = transaction.objectStore('files').put(data, docKey);
694  
			put.onsuccess = function(e) {
695  
				deferred.resolve(e);
696  
			};
697  
698  
			put.onerror = function(e) {
699  
				deferred.reject(e);
700  
			};
701  
702  
			return deferred.promise;
703  
		},
704  
705  
		rm: function(docKey) {
706  
			var deferred = Q.defer();
707  
			var finalDeferred = Q.defer();
708  
709  
			var transaction = this._db.transaction(['files', 'attachments'], 'readwrite');
710  
			
711  
			var del = transaction.objectStore('files').delete(docKey);
712  
713  
			del.onsuccess = function(e) {
714  
				deferred.promise.then(function() {
715  
					finalDeferred.resolve();
716  
				});
717  
			};
718  
719  
			del.onerror = function(e) {
720  
				deferred.promise.catch(function() {
721  
					finalDeferred.reject(e);
722  
				});
723  
			};
724  
725  
			var attachmentsStore = transaction.objectStore('attachments');
726  
			var index = attachmentsStore.index('fname');
727  
			var cursor = index.openCursor(IDBKeyRange.only(docKey));
728  
			cursor.onsuccess = function(e) {
729  
				var cursor = e.target.result;
730  
				if (cursor) {
731  
					cursor.delete();
732  
					cursor.continue();
733  
				} else {
734  
					deferred.resolve();
735  
				}
736  
			};
737  
738  
			cursor.onerror = function(e) {
739  
				deferred.reject(e);
740  
			}
741  
742  
			return finalDeferred.promise;
743  
		},
744  
745  
		getAttachment: function(docKey, attachKey) {
746  
			var deferred = Q.defer();
747  
748  
			var transaction = this._db.transaction(['attachments'], 'readonly');
749  
			var get = transaction.objectStore('attachments').get(docKey + '/' + attachKey);
750  
751  
			var self = this;
752  
			get.onsuccess = function(e) {
753  
				if (!e.target.result) {
754  
					deferred.resolve(undefined);
755  
					return;
756  
				}
757  
758  
				var data = e.target.result.data;
759  
				if (!self._supportsBlobs) {
760  
					data = dataURLToBlob(data);
761  
				}
762  
				deferred.resolve(data);
763  
			};
764  
765  
			get.onerror = function(e) {
766  
				deferred.reject(e);
767  
			};
768  
769  
			return deferred.promise;
770  
		},
771  
772  
		ls: function(docKey) {
773  
			var deferred = Q.defer();
774  
775  
			if (!docKey) {
776  
				// list docs
777  
				var store = 'files';
778  
			} else {
779  
				// list attachments
780  
				var store = 'attachments';
781  
			}
782  
783  
			var transaction = this._db.transaction([store], 'readonly');
784  
			var cursor = transaction.objectStore(store).openCursor();
785  
			var listing = [];
786  
787  
			cursor.onsuccess = function(e) {
788  
				var cursor = e.target.result;
789  
				if (cursor) {
790  
					listing.push(!docKey ? cursor.key : cursor.key.split('/')[1]);
791  
					cursor.continue();
792  
				} else {
793  
					deferred.resolve(listing);
794  
				}
795  
			};
796  
797  
			cursor.onerror = function(e) {
798  
				deferred.reject(e);
799  
			};
800  
801  
			return deferred.promise;
802  
		},
803  
804  
		clear: function() {
805  
			var deferred = Q.defer();
806  
			var finalDeferred = Q.defer();
807  
808  
			var t = this._db.transaction(['attachments', 'files'], 'readwrite');
809  
810  
811  
			var req1 = t.objectStore('attachments').clear();
812  
			var req2 = t.objectStore('files').clear();
813  
814  
			req1.onsuccess = function() {
815  
				deferred.promise.then(finalDeferred.resolve);
816  
			};
817  
818  
			req2.onsuccess = function() {
819  
				deferred.resolve();
820  
			};
821  
822  
			req1.onerror = function(err) {
823  
				finalDeferred.reject(err);
824  
			};
825  
826  
			req2.onerror = function(err) {
827  
				finalDeferred.reject(err);
828  
			};
829  
830  
			return finalDeferred.promise;
831  
		},
832  
833  
		getAllAttachments: function(docKey) {
834  
			var deferred = Q.defer();
835  
			var self = this;
836  
837  
			var transaction = this._db.transaction(['attachments'], 'readonly');
838  
			var index = transaction.objectStore('attachments').index('fname');
839  
840  
			var cursor = index.openCursor(IDBKeyRange.only(docKey));
841  
			var values = [];
842  
			cursor.onsuccess = function(e) {
843  
				var cursor = e.target.result;
844  
				if (cursor) {
845  
					var data;
846  
					if (!self._supportsBlobs) {
847  
						data = dataURLToBlob(cursor.value.data)
848  
					} else {
849  
						data = cursor.value.data;
850  
					}
851  
					values.push({
852  
						data: data,
853  
						docKey: docKey,
854  
						attachKey: cursor.primaryKey.split('/')[1] // TODO
855  
					});
856  
					cursor.continue();
857  
				} else {
858  
					deferred.resolve(values);
859  
				}
860  
			};
861  
862  
			cursor.onerror = function(e) {
863  
				deferred.reject(e);
864  
			};
865  
866  
			return deferred.promise;
867  
		},
868  
869  
		getAllAttachmentURLs: function(docKey) {
870  
			var deferred = Q.defer();
871  
			this.getAllAttachments(docKey).then(function(attachments) {
872  
				var urls = attachments.map(function(a) {
873  
					a.url = URL.createObjectURL(a.data);
874  
					delete a.data;
875  
					return a;
876  
				});
877  
878  
				deferred.resolve(urls);
879  
			}, function(e) {
880  
				deferred.reject(e);
881  
			});
882  
883  
			return deferred.promise;
884  
		},
885  
886  
		getAttachmentURL: function(docKey, attachKey) {
887  
			var deferred = Q.defer();
888  
			this.getAttachment(docKey, attachKey).then(function(attachment) {
889  
				deferred.resolve(URL.createObjectURL(attachment));
890  
			}, function(e) {
891  
				deferred.reject(e);
892  
			});
893  
894  
			return deferred.promise;
895  
		},
896  
897  
		revokeAttachmentURL: function(url) {
898  
			URL.revokeObjectURL(url);
899  
		},
900  
901  
		setAttachment: function(docKey, attachKey, data) {
902  
			var deferred = Q.defer();
903  
904  
			if (data instanceof Blob && !this._supportsBlobs) {
905  
				var self = this;
906  
				convertToBase64(data, function(data) {
907  
					continuation.call(self, data);
908  
				});
909  
			} else {
910  
				continuation.call(this, data);
911  
			}
912  
913  
			function continuation(data) {
914  
				var obj = {
915  
					path: docKey + '/' + attachKey,
916  
					fname: docKey,
917  
					data: data
918  
				};
919  
				var transaction = this._db.transaction(['attachments'], 'readwrite');
920  
				var put = transaction.objectStore('attachments').put(obj);
921  
922  
				put.onsuccess = function(e) {
923  
					deferred.resolve(e);
924  
				};
925  
926  
				put.onerror = function(e) {
927  
					deferred.reject(e);
928  
				};
929  
			}
930  
931  
			return deferred.promise;
932  
		},
933  
934  
		rmAttachment: function(docKey, attachKey) {
935  
			var deferred = Q.defer();
936  
			var transaction = this._db.transaction(['attachments'], 'readwrite');
937  
			var del = transaction.objectStore('attachments').delete(docKey + '/' + attachKey);
938  
939  
			del.onsuccess = function(e) {
940  
				deferred.resolve(e);
941  
			};
942  
943  
			del.onerror = function(e) {
944  
				deferred.reject(e);
945  
			};
946  
947  
			return deferred.promise;
948  
		}
949  
	};
950  
951  
	return {
952  
		init: function(config) {
953  
			var deferred = Q.defer();
954  
			var dbVersion = 2;
955  
956  
			if (!indexedDB || !IDBTransaction) {
957  
				deferred.reject("No IndexedDB");
958  
				return deferred.promise;
959  
			}
960  
961  
			var request = indexedDB.open(config.name, dbVersion);
962  
963  
			function createObjectStore(db) {
964  
				db.createObjectStore("files");
965  
				var attachStore = db.createObjectStore("attachments", {keyPath: 'path'});
966  
				attachStore.createIndex('fname', 'fname', {unique: false})
967  
			}
968  
969  
			// TODO: normalize errors
970  
			request.onerror = function (event) {
971  
				deferred.reject(event);
972  
			};
973  
		 
974  
			request.onsuccess = function (event) {
975  
				var db = request.result;
976  
		 
977  
				db.onerror = function (event) {
978  
					console.log(event);
979  
				};
980  
				
981  
				// Chrome workaround
982  
				if (db.setVersion) {
983  
					if (db.version != dbVersion) {
984  
						var setVersion = db.setVersion(dbVersion);
985  
						setVersion.onsuccess = function () {
986  
							createObjectStore(db);
987  
							deferred.resolve();
988  
						};
989  
					}
990  
					else {
991  
						deferred.resolve(new IDB(db));
992  
					}
993  
				} else {
994  
					deferred.resolve(new IDB(db));
995  
				}
996  
			}
997  
			
998  
			request.onupgradeneeded = function (event) {
999  
				createObjectStore(event.target.result);
1000  
			};
1001  
1002  
			return deferred.promise;
1003  
		},
1004  
1005  
		isAvailable: function() {
1006  
			return indexedDB != null && IDBTransaction != null;
1007  
		}
1008  
	}
1009  
})(Q);
1010  
var LocalStorageProvider = (function(Q) {
1011  
	return {
1012  
		init: function() {
1013  
			return Q({type: 'LocalStorage'});
1014  
		}
1015  
	}
1016  
})(Q);
1017  
var openDb = window.openDatabase;
1018  
var WebSQLProvider = (function(Q) {
1019  
	var URL = window.URL || window.webkitURL;
1020  
	var convertToBase64 = utils.convertToBase64;
1021  
	var dataURLToBlob = utils.dataURLToBlob;
1022  
1023  
	function WSQL(db) {
1024  
		this._db = db;
1025  
		this.type = 'WebSQL';
1026  
	}
1027  
1028  
	WSQL.prototype = {
1029  
		getContents: function(docKey, options) {
1030  
			var deferred = Q.defer();
1031  
			this._db.transaction(function(tx) {
1032  
				tx.executeSql('SELECT value FROM files WHERE fname = ?', [docKey],
1033  
				function(tx, res) {
1034  
					if (res.rows.length == 0) {
1035  
						deferred.resolve(undefined);
1036  
					} else {
1037  
						var data = res.rows.item(0).value;
1038  
						if (options && options.json)
1039  
							data = JSON.parse(data);
1040  
						deferred.resolve(data);
1041  
					}
1042  
				});
1043  
			}, function(err) {
1044  
				consol.log(err);
1045  
				deferred.reject(err);
1046  
			});
1047  
1048  
			return deferred.promise;
1049  
		},
1050  
1051  
		setContents: function(docKey, data, options) {
1052  
			var deferred = Q.defer();
1053  
			if (options && options.json)
1054  
				data = JSON.stringify(data);
1055  
1056  
			this._db.transaction(function(tx) {
1057  
				tx.executeSql(
1058  
				'INSERT OR REPLACE INTO files (fname, value) VALUES(?, ?)', [docKey, data]);
1059  
			}, function(err) {
1060  
				console.log(err);
1061  
				deferred.reject(err);
1062  
			}, function() {
1063  
				deferred.resolve();
1064  
			});
1065  
1066  
			return deferred.promise;
1067  
		},
1068  
1069  
		rm: function(docKey) {
1070  
			var deferred = Q.defer();
1071  
1072  
			this._db.transaction(function(tx) {
1073  
				tx.executeSql('DELETE FROM files WHERE fname = ?', [docKey]);
1074  
				tx.executeSql('DELETE FROM attachments WHERE fname = ?', [docKey]);
1075  
			}, function(err) {
1076  
				console.log(err);
1077  
				deferred.reject(err);
1078  
			}, function() {
1079  
				deferred.resolve();
1080  
			});
1081  
1082  
			return deferred.promise;
1083  
		},
1084  
1085  
		getAttachment: function(fname, akey) {
1086  
			var deferred = Q.defer();
1087  
1088  
			this._db.transaction(function(tx){ 
1089  
				tx.executeSql('SELECT value FROM attachments WHERE fname = ? AND akey = ?',
1090  
				[fname, akey],
1091  
				function(tx, res) {
1092  
					if (res.rows.length == 0) {
1093  
						deferred.resolve(undefined);
1094  
					} else {
1095  
						deferred.resolve(dataURLToBlob(res.rows.item(0).value));
1096  
					}
1097  
				});
1098  
			}, function(err) {
1099  
				deferred.reject(err);
1100  
			});
1101  
1102  
			return deferred.promise;
1103  
		},
1104  
1105  
		getAttachmentURL: function(docKey, attachKey) {
1106  
			var deferred = Q.defer();
1107  
			this.getAttachment(docKey, attachKey).then(function(blob) {
1108  
				deferred.resolve(URL.createObjectURL(blob));
1109  
			}, function() {
1110  
				deferred.reject();
1111  
			});
1112  
1113  
			return deferred.promise;
1114  
		},
1115  
1116  
		ls: function(docKey) {
1117  
			var deferred = Q.defer();
1118  
1119  
			var select;
1120  
			var field;
1121  
			if (!docKey) {
1122  
				select = 'SELECT fname FROM files';
1123  
				field = 'fname';
1124  
			} else {
1125  
				select = 'SELECT akey FROM attachments WHERE fname = ?';
1126  
				field = 'akey';
1127  
			}
1128  
1129  
			this._db.transaction(function(tx) {
1130  
				tx.executeSql(select, docKey ? [docKey] : [],
1131  
				function(tx, res) {
1132  
					var listing = [];
1133  
					for (var i = 0; i < res.rows.length; ++i) {
1134  
						listing.push(res.rows.item(i)[field]);
1135  
					}
1136  
1137  
					deferred.resolve(listing);
1138  
				}, function(err) {
1139  
					deferred.reject(err);
1140  
				});
1141  
			});
1142  
1143  
			return deferred.promise;
1144  
		},
1145  
1146  
		clear: function() {
1147  
			var deffered1 = Q.defer();
1148  
			var deffered2 = Q.defer();
1149  
1150  
			this._db.transaction(function(tx) {
1151  
				tx.executeSql('DELETE FROM files', function() {
1152  
					deffered1.resolve();
1153  
				});
1154  
				tx.executeSql('DELETE FROM attachments', function() {
1155  
					deffered2.resolve();
1156  
				});
1157  
			}, function(err) {
1158  
				deffered1.reject(err);
1159  
				deffered2.reject(err);
1160  
			});
1161  
1162  
			return Q.all([deffered1, deffered2]);
1163  
		},
1164  
1165  
		getAllAttachments: function(fname) {
1166  
			var deferred = Q.defer();
1167  
1168  
			this._db.transaction(function(tx) {
1169  
				tx.executeSql('SELECT value, akey FROM attachments WHERE fname = ?',
1170  
				[fname],
1171  
				function(tx, res) {
1172  
					// TODO: ship this work off to a webworker
1173  
					// since there could be many of these conversions?
1174  
					var result = [];
1175  
					for (var i = 0; i < res.rows.length; ++i) {
1176  
						var item = res.rows.item(i);
1177  
						result.push({
1178  
							docKey: fname,
1179  
							attachKey: item.akey,
1180  
							data: dataURLToBlob(item.value)
1181  
						});
1182  
					}
1183  
1184  
					deferred.resolve(result);
1185  
				});
1186  
			}, function(err) {
1187  
				deferred.reject(err);
1188  
			});
1189  
1190  
			return deferred.promise;
1191  
		},
1192  
1193  
		getAllAttachmentURLs: function(fname) {
1194  
			var deferred = Q.defer();
1195  
			this.getAllAttachments(fname).then(function(attachments) {
1196  
				var urls = attachments.map(function(a) {
1197  
					a.url = URL.createObjectURL(a.data);
1198  
					delete a.data;
1199  
					return a;
1200  
				});
1201  
1202  
				deferred.resolve(urls);
1203  
			}, function(e) {
1204  
				deferred.reject(e);
1205  
			});
1206  
1207  
			return deferred.promise;
1208  
		},
1209  
1210  
		revokeAttachmentURL: function(url) {
1211  
			URL.revokeObjectURL(url);
1212  
		},
1213  
1214  
		setAttachment: function(fname, akey, data) {
1215  
			var deferred = Q.defer();
1216  
1217  
			var self = this;
1218  
			convertToBase64(data, function(data) {
1219  
				self._db.transaction(function(tx) {
1220  
					tx.executeSql(
1221  
					'INSERT OR REPLACE INTO attachments (fname, akey, value) VALUES(?, ?, ?)',
1222  
					[fname, akey, data]);
1223  
				}, function(err) {
1224  
					deferred.reject(err);
1225  
				}, function() {
1226  
					deferred.resolve();
1227  
				});
1228  
			});
1229  
1230  
			return deferred.promise;
1231  
		},
1232  
1233  
		rmAttachment: function(fname, akey) {
1234  
			var deferred = Q.defer();
1235  
			this._db.transaction(function(tx) {
1236  
				tx.executeSql('DELETE FROM attachments WHERE fname = ? AND akey = ?',
1237  
				[fname, akey]);
1238  
			}, function(err) {
1239  
				deferred.reject(err);
1240  
			}, function() {
1241  
				deferred.resolve();
1242  
			});
1243  
1244  
			return deferred.promise;
1245  
		}
1246  
	};
1247  
1248  
	return {
1249  
		init: function(config) {
1250  
			var deferred = Q.defer();
1251  
			if (!openDb) {
1252  
				deferred.reject("No WebSQL");
1253  
				return deferred.promise;
1254  
			}
1255  
1256  
			var db = openDb(config.name, '1.0', 'large local storage', config.size);
1257  
1258  
			db.transaction(function(tx) {
1259  
				tx.executeSql('CREATE TABLE IF NOT EXISTS files (fname unique, value)');
1260  
				tx.executeSql('CREATE TABLE IF NOT EXISTS attachments (fname, akey, value)');
1261  
				tx.executeSql('CREATE INDEX IF NOT EXISTS fname_index ON attachments (fname)');
1262  
				tx.executeSql('CREATE INDEX IF NOT EXISTS akey_index ON attachments (akey)');
1263  
				tx.executeSql('CREATE UNIQUE INDEX IF NOT EXISTS uniq_attach ON attachments (fname, akey)')
1264  
			}, function(err) {
1265  
				deferred.reject(err);
1266  
			}, function() {
1267  
				deferred.resolve(new WSQL(db));
1268  
			});
1269  
1270  
			return deferred.promise;
1271  
		},
1272  
1273  
		isAvailable: function() {
1274  
			return openDb != null;
1275  
		}
1276  
	}
1277  
})(Q);
1278  
var LargeLocalStorage = (function(Q) {
1279  
	var sessionMeta = localStorage.getItem('LargeLocalStorage-meta');
1280  
	if (sessionMeta)
1281  
		sessionMeta = JSON.parse(sessionMeta);
1282  
	else
1283  
		sessionMeta = {};
1284  
1285  
	window.addEventListener('beforeunload', function() {
1286  
		localStorage.setItem('LargeLocalStorage-meta', JSON.stringify(sessionMeta));
1287  
	});
1288  
1289  
	function defaults(options, defaultOptions) {
1290  
		for (var k in defaultOptions) {
1291  
			if (options[k] === undefined)
1292  
				options[k] = defaultOptions[k];
1293  
		}
1294  
1295  
		return options;
1296  
	}
1297  
1298  
	var providers = {
1299  
		FileSystemAPI: FilesystemAPIProvider,
1300  
		IndexedDB: IndexedDBProvider,
1301  
		WebSQL: WebSQLProvider
1302  
		// LocalStorage: LocalStorageProvider
1303  
	}
1304  
1305  
	var defaultConfig = {
1306  
		size: 10 * 1024 * 1024,
1307  
		name: 'lls'
1308  
	};
1309  
1310  
	function selectImplementation(config) {
1311  
		if (!config) config = {};
1312  
		config = defaults(config, defaultConfig);
1313  
1314  
		if (config.forceProvider) {
1315  
			return providers[config.forceProvider].init(config);
1316  
		}
1317  
1318  
		return FilesystemAPIProvider.init(config).then(function(impl) {
1319  
			return Q(impl);
1320  
		}, function() {
1321  
			return IndexedDBProvider.init(config);
1322  
		}).then(function(impl) {
1323  
			return Q(impl);
1324  
		}, function() {
1325  
			return WebSQLProvider.init(config);
1326  
		}).then(function(impl) {
1327  
			return Q(impl);
1328  
		}, function() {
1329  
			console.error('Unable to create any storage implementations.  Using LocalStorage');
1330  
			return LocalStorageProvider.init(config);
1331  
		});
1332  
	}
1333  
1334  
	function copy(obj) {
1335  
		var result = {};
1336  
		Object.keys(obj).forEach(function(key) {
1337  
			result[key] = obj[key];
1338  
		});
1339  
1340  
		return result;
1341  
	}
1342  
1343  
	function handleDataMigration(storageInstance, config, previousProviderType, currentProivderType) {
1344  
		var previousProviderType = 
1345  
			sessionMeta[config.name] && sessionMeta[config.name].lastStorageImpl;
1346  
		if (config.migrate) {
1347  
			if (previousProviderType != currentProivderType
1348  
				&& previousProviderType in providers) {
1349  
				config = copy(config);
1350  
				config.forceProvider = previousProviderType;
1351  
				selectImplementation(config).then(function(prevImpl) {
1352  
					config.migrate(null, prevImpl, storageInstance, config);
1353  
				}, function(e) {
1354  
					config.migrate(e);
1355  
				});
1356  
			} else {
1357  
				if (config.migrationComplete)
1358  
					config.migrationComplete();
1359  
			}
1360  
		}
1361  
	}
1362  
1363  
	/**
1364  
	 * 
1365  
	 * LargeLocalStorage (or LLS) gives you a large capacity 
1366  
	 * (up to several gig with permission from the user)
1367  
	 * key-value store in the browser.
1368  
	 *
1369  
	 * For storage, LLS uses the [FilesystemAPI](https://developer.mozilla.org/en-US/docs/WebGuide/API/File_System)
1370  
	 * when running in Chrome and Opera, 
1371  
	 * [IndexedDB](https://developer.mozilla.org/en-US/docs/IndexedDB) in Firefox and IE
1372  
	 * and [WebSQL](http://www.w3.org/TR/webdatabase/) in Safari.
1373  
	 *
1374  
	 * When IndexedDB becomes available in Safari, LLS will
1375  
	 * update to take advantage of that storage implementation.
1376  
	 *
1377  
	 *
1378  
	 * Upon construction a LargeLocalStorage (LLS) object will be 
1379  
	 * immediately returned but not necessarily immediately ready for use.
1380  
	 *
1381  
	 * A LLS object has an `initialized` property which is a promise
1382  
	 * that is resolved when the LLS object is ready for us.
1383  
	 *
1384  
	 * Usage of LLS would typically be:
1385  
	 * ```
1386  
	 * var storage = new LargeLocalStorage({size: 75*1024*1024});
1387  
	 * storage.initialized.then(function(grantedCapacity) {
1388  
	 *   // storage ready to be used.
1389  
	 * });
1390  
	 * ```
1391  
	 *
1392  
	 * The reason that LLS may not be immediately ready for
1393  
	 * use is that some browsers require confirmation from the
1394  
	 * user before a storage area may be created.  Also,
1395  
	 * the browser's native storage APIs are asynchronous.
1396  
	 *
1397  
	 * If an LLS instance is used before the storage
1398  
	 * area is ready then any
1399  
	 * calls to it will throw an exception with code: "NO_IMPLEMENTATION"
1400  
	 *
1401  
	 * This behavior is useful when you want the application
1402  
	 * to continue to function--regardless of whether or
1403  
	 * not the user has allowed it to store data--and would
1404  
	 * like to know when your storage calls fail at the point
1405  
	 * of those calls.
1406  
	 *
1407  
	 * LLS-contrib has utilities to queue storage calls until
1408  
	 * the implementation is ready.  If an implementation
1409  
	 * is never ready this could obviously lead to memory issues
1410  
	 * which is why it is not the default behavior.
1411  
	 *
1412  
	 * @example
1413  
	 *	var desiredCapacity = 50 * 1024 * 1024; // 50MB
1414  
	 *	var storage = new LargeLocalStorage({
1415  
	 *		// desired capacity, in bytes.
1416  
	 *		size: desiredCapacity,
1417  
	 *
1418  
	 * 		// optional name for your LLS database. Defaults to lls.
1419  
	 *		// This is the name given to the underlying
1420  
	 *		// IndexedDB or WebSQL DB or FSAPI Folder.
1421  
	 *		// LLS's with different names are independent.
1422  
	 *		name: 'myStorage'
1423  
	 *
1424  
	 *		// the following is an optional param 
1425  
	 *		// that is useful for debugging.
1426  
	 *		// force LLS to use a specific storage implementation
1427  
	 *		// forceProvider: 'IndexedDB' or 'WebSQL' or 'FilesystemAPI'
1428  
	 *		
1429  
	 *		// These parameters can be used to migrate data from one
1430  
	 *		// storage implementation to another
1431  
	 *		// migrate: LargeLocalStorage.copyOldData,
1432  
	 *		// migrationComplete: function(err) {
1433  
	 *		//   db is initialized and old data has been copied.
1434  
	 *		// }
1435  
	 *	});
1436  
	 *	storage.initialized.then(function(capacity) {
1437  
	 *		if (capacity != -1 && capacity != desiredCapacity) {
1438  
	 *			// the user didn't authorize your storage request
1439  
	 *			// so instead you have some limitation on your storage
1440  
	 *		}
1441  
	 *	})
1442  
	 *
1443  
	 * @class LargeLocalStorage
1444  
	 * @constructor
1445  
	 * @param {object} config {size: sizeInByes, [forceProvider: force a specific implementation]}
1446  
	 * @return {LargeLocalStorage}
1447  
	 */
1448  
	function LargeLocalStorage(config) {
1449  
		var deferred = Q.defer();
1450  
		/**
1451  
		* @property {promise} initialized
1452  
		*/
1453  
		this.initialized = deferred.promise;
1454  
1455  
		var piped = createPipeline([
1456  
			'ready',
1457  
			'ls',
1458  
			'rm',
1459  
			'clear',
1460  
			'getContents',
1461  
			'setContents',
1462  
			'getAttachment',
1463  
			'setAttachment',
1464  
			'getAttachmentURL',
1465  
			'getAllAttachments',
1466  
			'getAllAttachmentURLs',
1467  
			'revokeAttachmentURL',
1468  
			'rmAttachment',
1469  
			'getCapacity',
1470  
			'initialized']);
1471  
1472  
		piped.pipe.addLast('lls', this);
1473  
		piped.initialized = this.initialized;
1474  
1475  
		var self = this;
1476  
		selectImplementation(config).then(function(impl) {
1477  
			self._impl = impl;
1478  
			handleDataMigration(piped, config, self._impl.type);
1479  
			sessionMeta[config.name] = sessionMeta[config.name] || {};
1480  
			sessionMeta[config.name].lastStorageImpl = impl.type;
1481  
			deferred.resolve(piped);
1482  
		}).catch(function(e) {
1483  
			// This should be impossible
1484  
			console.log(e);
1485  
			deferred.reject('No storage provider found');
1486  
		});
1487  
1488  
		return piped;
1489  
	}
1490  
1491  
	LargeLocalStorage.prototype = {
1492  
		/**
1493  
		* Whether or not LLS is ready to store data.
1494  
		* The `initialized` property can be used to
1495  
		* await initialization.
1496  
		* @example
1497  
		*	// may or may not be true
1498  
		*	storage.ready();
1499  
		*	
1500  
		*	storage.initialized.then(function() {
1501  
		*		// always true
1502  
		*		storage.ready();
1503  
		*	})
1504  
		* @method ready
1505  
		*/
1506  
		ready: function() {
1507  
			return this._impl != null;
1508  
		},
1509  
1510  
		/**
1511  
		* List all attachments under a given key.
1512  
		*
1513  
		* List all documents if no key is provided.
1514  
		*
1515  
		* Returns a promise that is fulfilled with
1516  
		* the listing.
1517  
		*
1518  
		* @example
1519  
		*	storage.ls().then(function(docKeys) {
1520  
		*		console.log(docKeys);
1521  
		*	})
1522  
		*
1523  
		* @method ls
1524  
		* @param {string} [docKey]
1525  
		* @returns {promise} resolved with the listing, rejected if the listing fails.
1526  
		*/
1527  
		ls: function(docKey) {
1528  
			this._checkAvailability();
1529  
			return this._impl.ls(docKey);
1530  
		},
1531  
1532  
		/**
1533  
		* Remove the specified document and all
1534  
		* of its attachments.
1535  
		*
1536  
		* Returns a promise that is fulfilled when the
1537  
		* removal completes.
1538  
		*
1539  
		* If no docKey is specified, this throws an error.
1540  
		*
1541  
		* To remove all files in LargeLocalStorage call
1542  
		* `lls.clear();`
1543  
		*
1544  
		* To remove all attachments that were written without
1545  
		* a docKey, call `lls.rm('__emptydoc__');`
1546  
		*
1547  
		* rm works this way to ensure you don't lose
1548  
		* data due to an accidently undefined variable.
1549  
		*
1550  
		* @example
1551  
		* 	stoarge.rm('exampleDoc').then(function() {
1552  
		*		alert('doc and all attachments were removed');
1553  
		* 	})
1554  
		*
1555  
		* @method rm
1556  
		* @param {string} docKey
1557  
		* @returns {promise} resolved when removal completes, rejected if the removal fails.
1558  
		*/
1559  
		rm: function(docKey) {
1560  
			this._checkAvailability();
1561  
			return this._impl.rm(docKey);
1562  
		},
1563  
1564  
		/**
1565  
		* An explicit way to remove all documents and
1566  
		* attachments from LargeLocalStorage.
1567  
		*
1568  
		* @example
1569  
		*	storage.clear().then(function() {
1570  
		*		alert('all data has been removed');
1571  
		*	});
1572  
		* 
1573  
		* @returns {promise} resolve when clear completes, rejected if clear fails.
1574  
		*/
1575  
		clear: function() {
1576  
			this._checkAvailability();
1577  
			return this._impl.clear();
1578  
		},
1579  
1580  
		/**
1581  
		* Get the contents of a document identified by `docKey`
1582  
		* TODO: normalize all implementations to allow storage
1583  
		* and retrieval of JS objects?
1584  
		*
1585  
		* @example
1586  
		* 	storage.getContents('exampleDoc').then(function(contents) {
1587  
		* 		alert(contents);
1588  
		* 	});
1589  
		*
1590  
		* @method getContents
1591  
		* @param {string} docKey
1592  
		* @returns {promise} resolved with the contents when the get completes
1593  
		*/
1594  
		getContents: function(docKey, options) {
1595  
			this._checkAvailability();
1596  
			return this._impl.getContents(docKey, options);
1597  
		},
1598  
1599  
		/**
1600  
		* Set the contents identified by `docKey` to `data`.
1601  
		* The document will be created if it does not exist.
1602  
		*
1603  
		* @example
1604  
		* 	storage.setContents('exampleDoc', 'some data...').then(function() {
1605  
		*		alert('doc written');
1606  
		* 	});
1607  
		*
1608  
		* @method setContents
1609  
		* @param {string} docKey
1610  
		* @param {any} data
1611  
		* @returns {promise} fulfilled when set completes
1612  
		*/
1613  
		setContents: function(docKey, data, options) {
1614  
			this._checkAvailability();
1615  
			return this._impl.setContents(docKey, data, options);
1616  
		},
1617  
1618  
		/**
1619  
		* Get the attachment identified by `docKey` and `attachKey`
1620  
		*
1621  
		* @example
1622  
		* 	storage.getAttachment('exampleDoc', 'examplePic').then(function(attachment) {
1623  
		*    	var url = URL.createObjectURL(attachment);
1624  
		*    	var image = new Image(url);
1625  
		*    	document.body.appendChild(image);
1626  
		*    	URL.revokeObjectURL(url);
1627  
		* 	})
1628  
		*
1629  
		* @method getAttachment
1630  
		* @param {string} [docKey] Defaults to `__emptydoc__`
1631  
		* @param {string} attachKey key of the attachment
1632  
		* @returns {promise} fulfilled with the attachment or
1633  
		* rejected if it could not be found.  code: 1
1634  
		*/
1635  
		getAttachment: function(docKey, attachKey) {
1636  
			if (!docKey) docKey = '__emptydoc__';
1637  
			this._checkAvailability();
1638  
			return this._impl.getAttachment(docKey, attachKey);
1639  
		},
1640  
1641  
		/**
1642  
		* Set an attachment for a given document.  Identified
1643  
		* by `docKey` and `attachKey`.
1644  
		*
1645  
		* @example
1646  
		* 	storage.setAttachment('myDoc', 'myPic', blob).then(function() {
1647  
		*    	alert('Attachment written');
1648  
		* 	})
1649  
		*
1650  
		* @method setAttachment
1651  
		* @param {string} [docKey] Defaults to `__emptydoc__`
1652  
		* @param {string} attachKey key for the attachment
1653  
		* @param {any} attachment data
1654  
		* @returns {promise} resolved when the write completes.  Rejected
1655  
		* if an error occurs.
1656  
		*/
1657  
		setAttachment: function(docKey, attachKey, data) {
1658  
			if (!docKey) docKey = '__emptydoc__';
1659  
			this._checkAvailability();
1660  
			return this._impl.setAttachment(docKey, attachKey, data);
1661  
		},
1662  
1663  
		/**
1664  
		* Get the URL for a given attachment.
1665  
		*
1666  
		* @example
1667  
		* 	storage.getAttachmentURL('myDoc', 'myPic').then(function(url) {
1668  
	 	*   	var image = new Image();
1669  
	 	*   	image.src = url;
1670  
	 	*   	document.body.appendChild(image);
1671  
	 	*   	storage.revokeAttachmentURL(url);
1672  
		* 	})
1673  
		*
1674  
		* This is preferrable to getting the attachment and then getting the
1675  
		* URL via `createObjectURL` (on some systems) as LLS can take advantage of 
1676  
		* lower level details to improve performance.
1677  
		*
1678  
		* @method getAttachmentURL
1679  
		* @param {string} [docKey] Identifies the document.  Defaults to `__emptydoc__`
1680  
		* @param {string} attachKey Identifies the attachment.
1681  
		* @returns {promose} promise that is resolved with the attachment url.
1682  
		*/
1683  
		getAttachmentURL: function(docKey, attachKey) {
1684  
			if (!docKey) docKey = '__emptydoc__';
1685  
			this._checkAvailability();
1686  
			return this._impl.getAttachmentURL(docKey, attachKey);
1687  
		},
1688  
1689  
		/**
1690  
		* Gets all of the attachments for a document.
1691  
		*
1692  
		* @example
1693  
		* 	storage.getAllAttachments('exampleDoc').then(function(attachEntries) {
1694  
		* 		attachEntries.map(function(entry) {
1695  
		*			var a = entry.data;
1696  
		*			// do something with it...
1697  
		* 			if (a.type.indexOf('image') == 0) {
1698  
		*				// show image...
1699  
		*			} else if (a.type.indexOf('audio') == 0) {
1700  
		*				// play audio...
1701  
		*			} else ...
1702  
		*		})
1703  
		* 	})
1704  
		*
1705  
		* @method getAllAttachments
1706  
		* @param {string} [docKey] Identifies the document.  Defaults to `__emptydoc__`
1707  
		* @returns {promise} Promise that is resolved with all of the attachments for
1708  
		* the given document.
1709  
		*/
1710  
		getAllAttachments: function(docKey) {
1711  
			if (!docKey) docKey = '__emptydoc__';
1712  
			this._checkAvailability();
1713  
			return this._impl.getAllAttachments(docKey);
1714  
		},
1715  
1716  
		/**
1717  
		* Gets all attachments URLs for a document.
1718  
		*
1719  
		* @example
1720  
		* 	storage.getAllAttachmentURLs('exampleDoc').then(function(urlEntries) {
1721  
		*		urlEntries.map(function(entry) {
1722  
		*			var url = entry.url;
1723  
		* 			// do something with the url...
1724  
		* 		})
1725  
		* 	})
1726  
		*
1727  
		* @method getAllAttachmentURLs
1728  
		* @param {string} [docKey] Identifies the document.  Defaults to the `__emptydoc__` document.
1729  
		* @returns {promise} Promise that is resolved with all of the attachment
1730  
		* urls for the given doc.
1731  
		*/
1732  
		getAllAttachmentURLs: function(docKey) {
1733  
			if (!docKey) docKey = '__emptydoc__';
1734  
			this._checkAvailability();
1735  
			return this._impl.getAllAttachmentURLs(docKey);
1736  
		},
1737  
1738  
		/**
1739  
		* Revoke the attachment URL as required by the underlying
1740  
		* storage system.
1741  
		*
1742  
		* This is akin to `URL.revokeObjectURL(url)`
1743  
		* URLs that come from `getAttachmentURL` or `getAllAttachmentURLs` 
1744  
		* should be revoked by LLS and not `URL.revokeObjectURL`
1745  
		*
1746  
		* @example
1747  
		* 	storage.getAttachmentURL('doc', 'attach').then(function(url) {
1748  
		*		// do something with the URL
1749  
		*		storage.revokeAttachmentURL(url);
1750  
		* 	})
1751  
		*
1752  
		* @method revokeAttachmentURL
1753  
		* @param {string} url The URL as returned by `getAttachmentURL` or `getAttachmentURLs`
1754  
		* @returns {void}
1755  
		*/
1756  
		revokeAttachmentURL: function(url) {
1757  
			this._checkAvailability();
1758  
			return this._impl.revokeAttachmentURL(url);
1759  
		},
1760  
1761  
		/**
1762  
		* Remove an attachment from a document.
1763  
		*
1764  
		* @example
1765  
		* 	storage.rmAttachment('exampleDoc', 'someAttachment').then(function() {
1766  
		* 		alert('exampleDoc/someAttachment removed');
1767  
		* 	}).catch(function(e) {
1768  
		*		alert('Attachment removal failed: ' + e);
1769  
		* 	});
1770  
		*
1771  
		* @method rmAttachment
1772  
		* @param {string} docKey
1773  
		* @param {string} attachKey
1774  
		* @returns {promise} Promise that is resolved once the remove completes
1775  
		*/
1776  
		rmAttachment: function(docKey, attachKey) {
1777  
			if (!docKey) docKey = '__emptydoc__';
1778  
			this._checkAvailability();
1779  
			return this._impl.rmAttachment(docKey, attachKey);
1780  
		},
1781  
1782  
		/**
1783  
		* Returns the actual capacity of the storage or -1
1784  
		* if it is unknown.  If the user denies your request for
1785  
		* storage you'll get back some smaller amount of storage than what you
1786  
		* actually requested.
1787  
		*
1788  
		* TODO: return an estimated capacity if actual capacity is unknown?
1789  
		* -Firefox is 50MB until authorized to go above,
1790  
		* -Chrome is some % of available disk space,
1791  
		* -Safari unlimited as long as the user keeps authorizing size increases
1792  
		* -Opera same as safari?
1793  
		*
1794  
		* @example
1795  
		*	// the initialized property will call you back with the capacity
1796  
		* 	storage.initialized.then(function(capacity) {
1797  
		*		console.log('Authorized to store: ' + capacity + ' bytes');
1798  
		* 	});
1799  
		*	// or if you know your storage is already available
1800  
		*	// you can call getCapacity directly
1801  
		*	storage.getCapacity()
1802  
		*
1803  
		* @method getCapacity
1804  
		* @returns {number} Capacity, in bytes, of the storage.  -1 if unknown.
1805  
		*/
1806  
		getCapacity: function() {
1807  
			this._checkAvailability();
1808  
			if (this._impl.getCapacity)
1809  
				return this._impl.getCapacity();
1810  
			else
1811  
				return -1;
1812  
		},
1813  
1814  
		_checkAvailability: function() {
1815  
			if (!this._impl) {
1816  
				throw {
1817  
					msg: "No storage implementation is available yet.  The user most likely has not granted you app access to FileSystemAPI or IndexedDB",
1818  
					code: "NO_IMPLEMENTATION"
1819  
				};
1820  
			}
1821  
		}
1822  
	};
1823  
1824  
	LargeLocalStorage.contrib = {};
1825  
1826  
	function writeAttachments(docKey, attachments, storage) {
1827  
		var promises = [];
1828  
		attachments.forEach(function(attachment) {
1829  
			promises.push(storage.setAttachment(docKey, attachment.attachKey, attachment.data));
1830  
		});
1831  
1832  
		return Q.all(promises);
1833  
	}
1834  
1835  
	function copyDocs(docKeys, oldStorage, newStorage) {
1836  
		var promises = [];
1837  
		docKeys.forEach(function(key) {
1838  
			promises.push(oldStorage.getContents(key).then(function(contents) {
1839  
				return newStorage.setContents(key, contents);
1840  
			}));
1841  
		});
1842  
1843  
		docKeys.forEach(function(key) {
1844  
			promises.push(oldStorage.getAllAttachments(key).then(function(attachments) {
1845  
				return writeAttachments(key, attachments, newStorage);
1846  
			}));
1847  
		});
1848  
1849  
		return Q.all(promises);
1850  
	}
1851  
1852  
	LargeLocalStorage.copyOldData = function(err, oldStorage, newStorage, config) {
1853  
		if (err) {
1854  
			throw err;
1855  
		}
1856  
1857  
		oldStorage.ls().then(function(docKeys) {
1858  
			return copyDocs(docKeys, oldStorage, newStorage)
1859  
		}).then(function() {
1860  
			if (config.migrationComplete)
1861  
				config.migrationComplete();
1862  
		}, function(e) {
1863  
			config.migrationComplete(e);
1864  
		});
1865  
	};
1866  
1867  
	LargeLocalStorage._sessionMeta = sessionMeta;
1868  
	
1869  
	var availableProviders = [];
1870  
	Object.keys(providers).forEach(function(potentialProvider) {
1871  
		if (providers[potentialProvider].isAvailable())
1872  
			availableProviders.push(potentialProvider);
1873  
	});
1874  
1875  
	LargeLocalStorage.availableProviders = availableProviders;
1876  
1877  
	return LargeLocalStorage;
1878  
})(Q);
1879  
1880  
	return LargeLocalStorage;
1881  
}
1882  
1883  
if (typeof define === 'function' && define.amd) {
1884  
	define(['Q'], definition);
1885  
} else {
1886  
	glob.LargeLocalStorage = definition.call(glob, Q);
1887  
}
1888  
1889  
}).call(this, this);

Author comment

From http://tantaman.github.io/LargeLocalStorage/dist/LargeLocalStorage.js

download  show line numbers   

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: 235 / 75
Version history: 1 change(s)
Referenced in: [show references]