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