Localisation updates from https://translatewiki.net.
[lhc/web/wiklou.git] / resources / lib / jquery / jquery.jStorage.js
1 /*
2 * ----------------------------- JSTORAGE -------------------------------------
3 * Simple local storage wrapper to save data on the browser side, supporting
4 * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
5 *
6 * Author: Andris Reinman, andris.reinman@gmail.com
7 * Project homepage: www.jstorage.info
8 *
9 * Licensed under Unlicense:
10 *
11 * This is free and unencumbered software released into the public domain.
12 *
13 * Anyone is free to copy, modify, publish, use, compile, sell, or
14 * distribute this software, either in source code form or as a compiled
15 * binary, for any purpose, commercial or non-commercial, and by any
16 * means.
17 *
18 * In jurisdictions that recognize copyright laws, the author or authors
19 * of this software dedicate any and all copyright interest in the
20 * software to the public domain. We make this dedication for the benefit
21 * of the public at large and to the detriment of our heirs and
22 * successors. We intend this dedication to be an overt act of
23 * relinquishment in perpetuity of all present and future rights to this
24 * software under copyright law.
25 *
26 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
29 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
30 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
31 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
32 * OTHER DEALINGS IN THE SOFTWARE.
33 *
34 * For more information, please refer to <http://unlicense.org/>
35 */
36
37 (function(){
38 var
39 /* jStorage version */
40 JSTORAGE_VERSION = "0.4.8",
41
42 /* detect a dollar object or create one if not found */
43 $ = window.jQuery || window.$ || (window.$ = {}),
44
45 /* check for a JSON handling support */
46 JSON = {
47 parse:
48 window.JSON && (window.JSON.parse || window.JSON.decode) ||
49 String.prototype.evalJSON && function(str){return String(str).evalJSON();} ||
50 $.parseJSON ||
51 $.evalJSON,
52 stringify:
53 Object.toJSON ||
54 window.JSON && (window.JSON.stringify || window.JSON.encode) ||
55 $.toJSON
56 };
57
58 // Break if no JSON support was found
59 if(!("parse" in JSON) || !("stringify" in JSON)){
60 throw new Error("No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page");
61 }
62
63 var
64 /* This is the object, that holds the cached values */
65 _storage = {__jstorage_meta:{CRC32:{}}},
66
67 /* Actual browser storage (localStorage or globalStorage["domain"]) */
68 _storage_service = {jStorage:"{}"},
69
70 /* DOM element for older IE versions, holds userData behavior */
71 _storage_elm = null,
72
73 /* How much space does the storage take */
74 _storage_size = 0,
75
76 /* which backend is currently used */
77 _backend = false,
78
79 /* onchange observers */
80 _observers = {},
81
82 /* timeout to wait after onchange event */
83 _observer_timeout = false,
84
85 /* last update time */
86 _observer_update = 0,
87
88 /* pubsub observers */
89 _pubsub_observers = {},
90
91 /* skip published items older than current timestamp */
92 _pubsub_last = +new Date(),
93
94 /* Next check for TTL */
95 _ttl_timeout,
96
97 /**
98 * XML encoding and decoding as XML nodes can't be JSON'ized
99 * XML nodes are encoded and decoded if the node is the value to be saved
100 * but not if it's as a property of another object
101 * Eg. -
102 * $.jStorage.set("key", xmlNode); // IS OK
103 * $.jStorage.set("key", {xml: xmlNode}); // NOT OK
104 */
105 _XMLService = {
106
107 /**
108 * Validates a XML node to be XML
109 * based on jQuery.isXML function
110 */
111 isXML: function(elm){
112 var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
113 return documentElement ? documentElement.nodeName !== "HTML" : false;
114 },
115
116 /**
117 * Encodes a XML node to string
118 * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
119 */
120 encode: function(xmlNode) {
121 if(!this.isXML(xmlNode)){
122 return false;
123 }
124 try{ // Mozilla, Webkit, Opera
125 return new XMLSerializer().serializeToString(xmlNode);
126 }catch(E1) {
127 try { // IE
128 return xmlNode.xml;
129 }catch(E2){}
130 }
131 return false;
132 },
133
134 /**
135 * Decodes a XML node from string
136 * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
137 */
138 decode: function(xmlString){
139 var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) ||
140 (window.ActiveXObject && function(_xmlString) {
141 var xml_doc = new ActiveXObject("Microsoft.XMLDOM");
142 xml_doc.async = "false";
143 xml_doc.loadXML(_xmlString);
144 return xml_doc;
145 }),
146 resultXML;
147 if(!dom_parser){
148 return false;
149 }
150 resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, "text/xml");
151 return this.isXML(resultXML)?resultXML:false;
152 }
153 };
154
155
156 ////////////////////////// PRIVATE METHODS ////////////////////////
157
158 /**
159 * Initialization function. Detects if the browser supports DOM Storage
160 * or userData behavior and behaves accordingly.
161 */
162 function _init(){
163 /* Check if browser supports localStorage */
164 var localStorageReallyWorks = false;
165 if("localStorage" in window){
166 try {
167 window.localStorage.setItem("_tmptest", "tmpval");
168 localStorageReallyWorks = true;
169 window.localStorage.removeItem("_tmptest");
170 } catch(BogusQuotaExceededErrorOnIos5) {
171 // Thanks be to iOS5 Private Browsing mode which throws
172 // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
173 }
174 }
175
176 if(localStorageReallyWorks){
177 try {
178 if(window.localStorage) {
179 _storage_service = window.localStorage;
180 _backend = "localStorage";
181 _observer_update = _storage_service.jStorage_update;
182 }
183 } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */}
184 }
185 /* Check if browser supports globalStorage */
186 else if("globalStorage" in window){
187 try {
188 if(window.globalStorage) {
189 if(window.location.hostname == "localhost"){
190 _storage_service = window.globalStorage["localhost.localdomain"];
191 }
192 else{
193 _storage_service = window.globalStorage[window.location.hostname];
194 }
195 _backend = "globalStorage";
196 _observer_update = _storage_service.jStorage_update;
197 }
198 } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */}
199 }
200 /* Check if browser supports userData behavior */
201 else {
202 _storage_elm = document.createElement("link");
203 if(_storage_elm.addBehavior){
204
205 /* Use a DOM element to act as userData storage */
206 _storage_elm.style.behavior = "url(#default#userData)";
207
208 /* userData element needs to be inserted into the DOM! */
209 document.getElementsByTagName("head")[0].appendChild(_storage_elm);
210
211 try{
212 _storage_elm.load("jStorage");
213 }catch(E){
214 // try to reset cache
215 _storage_elm.setAttribute("jStorage", "{}");
216 _storage_elm.save("jStorage");
217 _storage_elm.load("jStorage");
218 }
219
220 var data = "{}";
221 try{
222 data = _storage_elm.getAttribute("jStorage");
223 }catch(E5){}
224
225 try{
226 _observer_update = _storage_elm.getAttribute("jStorage_update");
227 }catch(E6){}
228
229 _storage_service.jStorage = data;
230 _backend = "userDataBehavior";
231 }else{
232 _storage_elm = null;
233 return;
234 }
235 }
236
237 // Load data from storage
238 _load_storage();
239
240 // remove dead keys
241 _handleTTL();
242
243 // start listening for changes
244 _setupObserver();
245
246 // initialize publish-subscribe service
247 _handlePubSub();
248
249 // handle cached navigation
250 if("addEventListener" in window){
251 window.addEventListener("pageshow", function(event){
252 if(event.persisted){
253 _storageObserver();
254 }
255 }, false);
256 }
257 }
258
259 /**
260 * Reload data from storage when needed
261 */
262 function _reloadData(){
263 var data = "{}";
264
265 if(_backend == "userDataBehavior"){
266 _storage_elm.load("jStorage");
267
268 try{
269 data = _storage_elm.getAttribute("jStorage");
270 }catch(E5){}
271
272 try{
273 _observer_update = _storage_elm.getAttribute("jStorage_update");
274 }catch(E6){}
275
276 _storage_service.jStorage = data;
277 }
278
279 _load_storage();
280
281 // remove dead keys
282 _handleTTL();
283
284 _handlePubSub();
285 }
286
287 /**
288 * Sets up a storage change observer
289 */
290 function _setupObserver(){
291 if(_backend == "localStorage" || _backend == "globalStorage"){
292 if("addEventListener" in window){
293 window.addEventListener("storage", _storageObserver, false);
294 }else{
295 document.attachEvent("onstorage", _storageObserver);
296 }
297 }else if(_backend == "userDataBehavior"){
298 setInterval(_storageObserver, 1000);
299 }
300 }
301
302 /**
303 * Fired on any kind of data change, needs to check if anything has
304 * really been changed
305 */
306 function _storageObserver(){
307 var updateTime;
308 // cumulate change notifications with timeout
309 clearTimeout(_observer_timeout);
310 _observer_timeout = setTimeout(function(){
311
312 if(_backend == "localStorage" || _backend == "globalStorage"){
313 updateTime = _storage_service.jStorage_update;
314 }else if(_backend == "userDataBehavior"){
315 _storage_elm.load("jStorage");
316 try{
317 updateTime = _storage_elm.getAttribute("jStorage_update");
318 }catch(E5){}
319 }
320
321 if(updateTime && updateTime != _observer_update){
322 _observer_update = updateTime;
323 _checkUpdatedKeys();
324 }
325
326 }, 25);
327 }
328
329 /**
330 * Reloads the data and checks if any keys are changed
331 */
332 function _checkUpdatedKeys(){
333 var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
334 newCrc32List;
335
336 _reloadData();
337 newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
338
339 var key,
340 updated = [],
341 removed = [];
342
343 for(key in oldCrc32List){
344 if(oldCrc32List.hasOwnProperty(key)){
345 if(!newCrc32List[key]){
346 removed.push(key);
347 continue;
348 }
349 if(oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0,2) == "2."){
350 updated.push(key);
351 }
352 }
353 }
354
355 for(key in newCrc32List){
356 if(newCrc32List.hasOwnProperty(key)){
357 if(!oldCrc32List[key]){
358 updated.push(key);
359 }
360 }
361 }
362
363 _fireObservers(updated, "updated");
364 _fireObservers(removed, "deleted");
365 }
366
367 /**
368 * Fires observers for updated keys
369 *
370 * @param {Array|String} keys Array of key names or a key
371 * @param {String} action What happened with the value (updated, deleted, flushed)
372 */
373 function _fireObservers(keys, action){
374 keys = [].concat(keys || []);
375 if(action == "flushed"){
376 keys = [];
377 for(var key in _observers){
378 if(_observers.hasOwnProperty(key)){
379 keys.push(key);
380 }
381 }
382 action = "deleted";
383 }
384 for(var i=0, len = keys.length; i<len; i++){
385 if(_observers[keys[i]]){
386 for(var j=0, jlen = _observers[keys[i]].length; j<jlen; j++){
387 _observers[keys[i]][j](keys[i], action);
388 }
389 }
390 if(_observers["*"]){
391 for(var j=0, jlen = _observers["*"].length; j<jlen; j++){
392 _observers["*"][j](keys[i], action);
393 }
394 }
395 }
396 }
397
398 /**
399 * Publishes key change to listeners
400 */
401 function _publishChange(){
402 var updateTime = (+new Date()).toString();
403
404 if(_backend == "localStorage" || _backend == "globalStorage"){
405 try {
406 _storage_service.jStorage_update = updateTime;
407 } catch (E8) {
408 // safari private mode has been enabled after the jStorage initialization
409 _backend = false;
410 }
411 }else if(_backend == "userDataBehavior"){
412 _storage_elm.setAttribute("jStorage_update", updateTime);
413 _storage_elm.save("jStorage");
414 }
415
416 _storageObserver();
417 }
418
419 /**
420 * Loads the data from the storage based on the supported mechanism
421 */
422 function _load_storage(){
423 /* if jStorage string is retrieved, then decode it */
424 if(_storage_service.jStorage){
425 try{
426 _storage = JSON.parse(String(_storage_service.jStorage));
427 }catch(E6){_storage_service.jStorage = "{}";}
428 }else{
429 _storage_service.jStorage = "{}";
430 }
431 _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
432
433 if(!_storage.__jstorage_meta){
434 _storage.__jstorage_meta = {};
435 }
436 if(!_storage.__jstorage_meta.CRC32){
437 _storage.__jstorage_meta.CRC32 = {};
438 }
439 }
440
441 /**
442 * This functions provides the "save" mechanism to store the jStorage object
443 */
444 function _save(){
445 _dropOldEvents(); // remove expired events
446 try{
447 _storage_service.jStorage = JSON.stringify(_storage);
448 // If userData is used as the storage engine, additional
449 if(_storage_elm) {
450 _storage_elm.setAttribute("jStorage",_storage_service.jStorage);
451 _storage_elm.save("jStorage");
452 }
453 _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
454 }catch(E7){/* probably cache is full, nothing is saved this way*/}
455 }
456
457 /**
458 * Function checks if a key is set and is string or numberic
459 *
460 * @param {String} key Key name
461 */
462 function _checkKey(key){
463 if(typeof key != "string" && typeof key != "number"){
464 throw new TypeError("Key name must be string or numeric");
465 }
466 if(key == "__jstorage_meta"){
467 throw new TypeError("Reserved key name");
468 }
469 return true;
470 }
471
472 /**
473 * Removes expired keys
474 */
475 function _handleTTL(){
476 var curtime, i, TTL, CRC32, nextExpire = Infinity, changed = false, deleted = [];
477
478 clearTimeout(_ttl_timeout);
479
480 if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != "object"){
481 // nothing to do here
482 return;
483 }
484
485 curtime = +new Date();
486 TTL = _storage.__jstorage_meta.TTL;
487
488 CRC32 = _storage.__jstorage_meta.CRC32;
489 for(i in TTL){
490 if(TTL.hasOwnProperty(i)){
491 if(TTL[i] <= curtime){
492 delete TTL[i];
493 delete CRC32[i];
494 delete _storage[i];
495 changed = true;
496 deleted.push(i);
497 }else if(TTL[i] < nextExpire){
498 nextExpire = TTL[i];
499 }
500 }
501 }
502
503 // set next check
504 if(nextExpire != Infinity){
505 _ttl_timeout = setTimeout(_handleTTL, nextExpire - curtime);
506 }
507
508 // save changes
509 if(changed){
510 _save();
511 _publishChange();
512 _fireObservers(deleted, "deleted");
513 }
514 }
515
516 /**
517 * Checks if there's any events on hold to be fired to listeners
518 */
519 function _handlePubSub(){
520 var i, len;
521 if(!_storage.__jstorage_meta.PubSub){
522 return;
523 }
524 var pubelm,
525 _pubsubCurrent = _pubsub_last;
526
527 for(i=len=_storage.__jstorage_meta.PubSub.length-1; i>=0; i--){
528 pubelm = _storage.__jstorage_meta.PubSub[i];
529 if(pubelm[0] > _pubsub_last){
530 _pubsubCurrent = pubelm[0];
531 _fireSubscribers(pubelm[1], pubelm[2]);
532 }
533 }
534
535 _pubsub_last = _pubsubCurrent;
536 }
537
538 /**
539 * Fires all subscriber listeners for a pubsub channel
540 *
541 * @param {String} channel Channel name
542 * @param {Mixed} payload Payload data to deliver
543 */
544 function _fireSubscribers(channel, payload){
545 if(_pubsub_observers[channel]){
546 for(var i=0, len = _pubsub_observers[channel].length; i<len; i++){
547 // send immutable data that can't be modified by listeners
548 try{
549 _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
550 }catch(E){};
551 }
552 }
553 }
554
555 /**
556 * Remove old events from the publish stream (at least 2sec old)
557 */
558 function _dropOldEvents(){
559 if(!_storage.__jstorage_meta.PubSub){
560 return;
561 }
562
563 var retire = +new Date() - 2000;
564
565 for(var i=0, len = _storage.__jstorage_meta.PubSub.length; i<len; i++){
566 if(_storage.__jstorage_meta.PubSub[i][0] <= retire){
567 // deleteCount is needed for IE6
568 _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
569 break;
570 }
571 }
572
573 if(!_storage.__jstorage_meta.PubSub.length){
574 delete _storage.__jstorage_meta.PubSub;
575 }
576
577 }
578
579 /**
580 * Publish payload to a channel
581 *
582 * @param {String} channel Channel name
583 * @param {Mixed} payload Payload to send to the subscribers
584 */
585 function _publish(channel, payload){
586 if(!_storage.__jstorage_meta){
587 _storage.__jstorage_meta = {};
588 }
589 if(!_storage.__jstorage_meta.PubSub){
590 _storage.__jstorage_meta.PubSub = [];
591 }
592
593 _storage.__jstorage_meta.PubSub.unshift([+new Date, channel, payload]);
594
595 _save();
596 _publishChange();
597 }
598
599
600 /**
601 * JS Implementation of MurmurHash2
602 *
603 * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
604 *
605 * @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
606 * @see http://github.com/garycourt/murmurhash-js
607 * @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
608 * @see http://sites.google.com/site/murmurhash/
609 *
610 * @param {string} str ASCII only
611 * @param {number} seed Positive integer only
612 * @return {number} 32-bit positive integer hash
613 */
614
615 function murmurhash2_32_gc(str, seed) {
616 var
617 l = str.length,
618 h = seed ^ l,
619 i = 0,
620 k;
621
622 while (l >= 4) {
623 k =
624 ((str.charCodeAt(i) & 0xff)) |
625 ((str.charCodeAt(++i) & 0xff) << 8) |
626 ((str.charCodeAt(++i) & 0xff) << 16) |
627 ((str.charCodeAt(++i) & 0xff) << 24);
628
629 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
630 k ^= k >>> 24;
631 k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
632
633 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
634
635 l -= 4;
636 ++i;
637 }
638
639 switch (l) {
640 case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
641 case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
642 case 1: h ^= (str.charCodeAt(i) & 0xff);
643 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
644 }
645
646 h ^= h >>> 13;
647 h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
648 h ^= h >>> 15;
649
650 return h >>> 0;
651 }
652
653 ////////////////////////// PUBLIC INTERFACE /////////////////////////
654
655 $.jStorage = {
656 /* Version number */
657 version: JSTORAGE_VERSION,
658
659 /**
660 * Sets a key's value.
661 *
662 * @param {String} key Key to set. If this value is not set or not
663 * a string an exception is raised.
664 * @param {Mixed} value Value to set. This can be any value that is JSON
665 * compatible (Numbers, Strings, Objects etc.).
666 * @param {Object} [options] - possible options to use
667 * @param {Number} [options.TTL] - optional TTL value
668 * @return {Mixed} the used value
669 */
670 set: function(key, value, options){
671 _checkKey(key);
672
673 options = options || {};
674
675 // undefined values are deleted automatically
676 if(typeof value == "undefined"){
677 this.deleteKey(key);
678 return value;
679 }
680
681 if(_XMLService.isXML(value)){
682 value = {_is_xml:true,xml:_XMLService.encode(value)};
683 }else if(typeof value == "function"){
684 return undefined; // functions can't be saved!
685 }else if(value && typeof value == "object"){
686 // clone the object before saving to _storage tree
687 value = JSON.parse(JSON.stringify(value));
688 }
689
690 _storage[key] = value;
691
692 _storage.__jstorage_meta.CRC32[key] = "2." + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
693
694 this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
695
696 _fireObservers(key, "updated");
697 return value;
698 },
699
700 /**
701 * Looks up a key in cache
702 *
703 * @param {String} key - Key to look up.
704 * @param {mixed} def - Default value to return, if key didn't exist.
705 * @return {Mixed} the key value, default value or null
706 */
707 get: function(key, def){
708 _checkKey(key);
709 if(key in _storage){
710 if(_storage[key] && typeof _storage[key] == "object" && _storage[key]._is_xml) {
711 return _XMLService.decode(_storage[key].xml);
712 }else{
713 return _storage[key];
714 }
715 }
716 return typeof(def) == "undefined" ? null : def;
717 },
718
719 /**
720 * Deletes a key from cache.
721 *
722 * @param {String} key - Key to delete.
723 * @return {Boolean} true if key existed or false if it didn't
724 */
725 deleteKey: function(key){
726 _checkKey(key);
727 if(key in _storage){
728 delete _storage[key];
729 // remove from TTL list
730 if(typeof _storage.__jstorage_meta.TTL == "object" &&
731 key in _storage.__jstorage_meta.TTL){
732 delete _storage.__jstorage_meta.TTL[key];
733 }
734
735 delete _storage.__jstorage_meta.CRC32[key];
736
737 _save();
738 _publishChange();
739 _fireObservers(key, "deleted");
740 return true;
741 }
742 return false;
743 },
744
745 /**
746 * Sets a TTL for a key, or remove it if ttl value is 0 or below
747 *
748 * @param {String} key - key to set the TTL for
749 * @param {Number} ttl - TTL timeout in milliseconds
750 * @return {Boolean} true if key existed or false if it didn't
751 */
752 setTTL: function(key, ttl){
753 var curtime = +new Date();
754 _checkKey(key);
755 ttl = Number(ttl) || 0;
756 if(key in _storage){
757
758 if(!_storage.__jstorage_meta.TTL){
759 _storage.__jstorage_meta.TTL = {};
760 }
761
762 // Set TTL value for the key
763 if(ttl>0){
764 _storage.__jstorage_meta.TTL[key] = curtime + ttl;
765 }else{
766 delete _storage.__jstorage_meta.TTL[key];
767 }
768
769 _save();
770
771 _handleTTL();
772
773 _publishChange();
774 return true;
775 }
776 return false;
777 },
778
779 /**
780 * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
781 *
782 * @param {String} key Key to check
783 * @return {Number} Remaining TTL in milliseconds
784 */
785 getTTL: function(key){
786 var curtime = +new Date(), ttl;
787 _checkKey(key);
788 if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){
789 ttl = _storage.__jstorage_meta.TTL[key] - curtime;
790 return ttl || 0;
791 }
792 return 0;
793 },
794
795 /**
796 * Deletes everything in cache.
797 *
798 * @return {Boolean} Always true
799 */
800 flush: function(){
801 _storage = {__jstorage_meta:{CRC32:{}}};
802 _save();
803 _publishChange();
804 _fireObservers(null, "flushed");
805 return true;
806 },
807
808 /**
809 * Returns a read-only copy of _storage
810 *
811 * @return {Object} Read-only copy of _storage
812 */
813 storageObj: function(){
814 function F() {}
815 F.prototype = _storage;
816 return new F();
817 },
818
819 /**
820 * Returns an index of all used keys as an array
821 * ["key1", "key2",.."keyN"]
822 *
823 * @return {Array} Used keys
824 */
825 index: function(){
826 var index = [], i;
827 for(i in _storage){
828 if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){
829 index.push(i);
830 }
831 }
832 return index;
833 },
834
835 /**
836 * How much space in bytes does the storage take?
837 *
838 * @return {Number} Storage size in chars (not the same as in bytes,
839 * since some chars may take several bytes)
840 */
841 storageSize: function(){
842 return _storage_size;
843 },
844
845 /**
846 * Which backend is currently in use?
847 *
848 * @return {String} Backend name
849 */
850 currentBackend: function(){
851 return _backend;
852 },
853
854 /**
855 * Test if storage is available
856 *
857 * @return {Boolean} True if storage can be used
858 */
859 storageAvailable: function(){
860 return !!_backend;
861 },
862
863 /**
864 * Register change listeners
865 *
866 * @param {String} key Key name
867 * @param {Function} callback Function to run when the key changes
868 */
869 listenKeyChange: function(key, callback){
870 _checkKey(key);
871 if(!_observers[key]){
872 _observers[key] = [];
873 }
874 _observers[key].push(callback);
875 },
876
877 /**
878 * Remove change listeners
879 *
880 * @param {String} key Key name to unregister listeners against
881 * @param {Function} [callback] If set, unregister the callback, if not - unregister all
882 */
883 stopListening: function(key, callback){
884 _checkKey(key);
885
886 if(!_observers[key]){
887 return;
888 }
889
890 if(!callback){
891 delete _observers[key];
892 return;
893 }
894
895 for(var i = _observers[key].length - 1; i>=0; i--){
896 if(_observers[key][i] == callback){
897 _observers[key].splice(i,1);
898 }
899 }
900 },
901
902 /**
903 * Subscribe to a Publish/Subscribe event stream
904 *
905 * @param {String} channel Channel name
906 * @param {Function} callback Function to run when the something is published to the channel
907 */
908 subscribe: function(channel, callback){
909 channel = (channel || "").toString();
910 if(!channel){
911 throw new TypeError("Channel not defined");
912 }
913 if(!_pubsub_observers[channel]){
914 _pubsub_observers[channel] = [];
915 }
916 _pubsub_observers[channel].push(callback);
917 },
918
919 /**
920 * Publish data to an event stream
921 *
922 * @param {String} channel Channel name
923 * @param {Mixed} payload Payload to deliver
924 */
925 publish: function(channel, payload){
926 channel = (channel || "").toString();
927 if(!channel){
928 throw new TypeError("Channel not defined");
929 }
930
931 _publish(channel, payload);
932 },
933
934 /**
935 * Reloads the data from browser storage
936 */
937 reInit: function(){
938 _reloadData();
939 },
940
941 /**
942 * Removes reference from global objects and saves it as jStorage
943 *
944 * @param {Boolean} option if needed to save object as simple "jStorage" in windows context
945 */
946 noConflict: function( saveInGlobal ) {
947 delete window.$.jStorage
948
949 if ( saveInGlobal ) {
950 window.jStorage = this;
951 }
952
953 return this;
954 }
955 };
956
957 // Initialize jStorage
958 _init();
959
960 })();