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