Update BUGS for 1.1 and upcoming 1.2.
[pwl6.git] / src / yuu / storage.js
1 /* Copyright 2014 Yukkuri Games
2 Licensed under the terms of the GNU GPL v2 or later
3 @license http://www.gnu.org/licenses/gpl-2.0.html
4 @source: http://yukkurigames.com/yuu/
5 */
6
7 (function (exports) {
8 "use strict";
9
10 var yT = this.yT || require('./yT');
11
12 var FakeStorage = exports.FakeStorage = yT({
13 /** Fake, ephemeral storage roughly like Web Storage
14
15 This is the fallback storage when permission is denied or
16 otherwise busted. It just stores a dictionary for as long as
17 the object survives.
18 */
19 constructor: function () {
20 this._storage = {};
21 },
22
23 getItem: function (key) {
24 return (key in this._storage) ? this._storage[key] : null;
25 },
26
27 setItem: function (key, value) {
28 this._storage[key] = value.toString();
29 },
30
31 removeItem: function (key) {
32 delete this._storage[key];
33 },
34
35 clear: function () {
36 this._storage = {};
37 },
38
39 length: {
40 get: function () { return Object.keys(this._storage).length; }
41 },
42
43 key: function (n) {
44 // Object.keys isn't guaranteed to have a consistent order
45 // even when nothing changes, so normalize it by sorting.
46 var keys = Object.keys(this._storage).sort();
47 return (n >= 0 && n < keys.length) ? keys[n] : null;
48 }
49 });
50
51 var PrefixedStorage = exports.PrefixedStorage = yT({
52 /** Per-application storage roughly like Web Storage
53
54 This storage prefixes all keys with a special token, so you
55 can run multiple applications on the same origin without
56 the risk of conflicting keys.
57
58 A caveat of this approach is clear() is not atomic.
59 */
60
61 constructor: function (storage, prefix) {
62 this._storage = storage;
63 this._prefix = prefix + " -- ";
64 },
65
66 _key: function (key) {
67 return this._prefix + key;
68 },
69
70 _unkey: function (key) {
71 return key.substring(this._prefix.length);
72 },
73
74 _iskey: function (key) {
75 return key.startsWith(this._prefix);
76 },
77
78 _keys: function () {
79 var keys = [];
80 var key;
81 var i = 0;
82 while ((key = this._storage.key(i++)) !== null)
83 if (this._iskey(key))
84 keys.push(this._unkey(key));
85 return keys;
86 },
87
88 getItem: function (key) {
89 return this._storage.getItem(this._key(key));
90 },
91
92 setItem: function (key, value) {
93 return this._storage.setItem(this._key(key), value);
94 },
95
96 removeItem: function (key) {
97 return this._storage.removeItem(this._key(key));
98 },
99
100 clear: function () {
101 this._keys().forEach(this.removeItem, this);
102 },
103
104 length: {
105 get: function () { return this._keys().length; }
106 },
107
108 key: function (n) {
109 var keys = this._keys().sort();
110 return (n >= 0 && n < keys.length) ? keys[n] : null;
111 }
112 });
113
114 var Storage = exports.Storage = yT({
115 /** Higher-level access to Web Storage-esque things
116
117 Storage lets you store and retrieve JSON-serializable
118 objects inside a Web Storage container.
119
120 You can specify default values. If you retrieve an object
121 that hasn't been set, you get its default value.
122
123 Storage automatically falls back to an ephemeral storage
124 backend if a SecurityException occurs during startup.
125 */
126
127 constructor: function (storage, defaults) {
128 this._storage = storage || new FakeStorage();
129 this._defaults = defaults || {};
130
131 try {
132 this.setFlag('__ystorage__');
133 } catch (exc) {
134 this._storage = new FakeStorage();
135 console.error("Unable to use provided storage:", exc);
136 }
137 },
138
139 getObject: function (key, fallbackValue) {
140 var v = this._storage.getItem(key);
141 if (v === null) {
142 return (key in this._defaults)
143 ? this._defaults[key]
144 : fallbackValue;
145 }
146 try {
147 return JSON.parse(v);
148 } catch (exc) {
149 console.error("Malformed storage value:", key, v, exc);
150 return (key in this._defaults)
151 ? this._defaults[key]
152 : fallbackValue;
153 }
154 },
155
156 setObject: function (key, value) {
157 this._storage.setItem(key, JSON.stringify(value));
158 },
159
160 removeObject: { proxy: '_storage.removeItem' },
161
162 getFlag: function (key) {
163 return !!this.getObject(key, false);
164 },
165
166 setFlag: function (key) {
167 return this.setObject(key, true);
168 },
169
170 clearFlag: function (key) {
171 return this.setObject(key, false);
172 },
173
174 clear: { proxy: '_storage.clear' }
175 });
176
177 exports.getStorage = function (prefix, defaults, backend) {
178 /** Create a Storage with prefixed access to localStorage. */
179 prefix = prefix
180 || (document &&
181 (document.documentElement.getAttribute('data-appid')
182 || document.title));
183 backend = backend || localStorage;
184 return new Storage(new PrefixedStorage(backend, prefix), defaults);
185 };
186
187 }).call(typeof exports === 'undefined' ? this : exports,
188 typeof exports === 'undefined' ? (this.ystorage = {}) : exports);