Merge pull request #512 from galfert/node_binary_fix
[remotestorage.js] / src / inmemorystorage.js
1 (function(global) {
2   function makeNode(path) {
3     var node = { path: path };
4     if (path[path.length - 1] === '/') {
5       node.body = {};
6       node.contentType = 'application/json';
7     }
8     return node;
9   }
10
11   function applyRecursive(path, cb) {
12     var parts = path.match(/^(.*\/)([^\/]+\/?)$/);
13     if (parts) {
14       var dirname = parts[1];
15       var basename = parts[2];
16
17       if (cb(dirname, basename) && dirname !== '/') {
18         applyRecursive(dirname, cb);
19       }
20     } else {
21       throw new Error('inMemoryStorage encountered invalid path : ' + path);
22     }
23   }
24
25   RemoteStorage.InMemoryStorage = function(rs) {
26     this.rs = rs;
27     RemoteStorage.cachingLayer(this);
28     RemoteStorage.eventHandling(this, 'change', 'conflict');
29     this._storage = {};
30     this._changes = {};
31   };
32
33   RemoteStorage.InMemoryStorage.prototype = {
34     get: function(path) {
35       var node = this._storage[path];
36       if (node) {
37         return promising().fulfill(200, node.body, node.contentType, node.revision);
38       } else {
39         return promising().fulfill(404);
40       }
41     },
42
43     put: function(path, body, contentType, incoming) {
44       var oldNode = this._storage[path];
45       var node = {
46         path: path,
47         contentType: contentType,
48         body: body
49       };
50       this._storage[path] = node;
51       this._addToParent(path);
52       if (!incoming) {
53         this._recordChange(path, { action: 'PUT' });
54       }
55
56       this._emit('change', {
57         path: path,
58         origin: incoming ? 'remote' : 'window',
59         oldValue: oldNode ? oldNode.body : undefined,
60         newValue: body
61       });
62       return promising().fulfill(200);
63     },
64
65     'delete': function(path, incoming) {
66       var oldNode = this._storage[path];
67       delete this._storage[path];
68       this._removeFromParent(path);
69       if (!incoming) {
70         this._recordChange(path, { action: 'DELETE' });
71       }
72
73       if (oldNode) {
74         this._emit('change', {
75           path: path,
76           origin: incoming ? 'remote' : 'window',
77           oldValue: oldNode.body,
78           newValue: undefined
79         });
80       }
81       return promising().fulfill(200);
82     },
83
84     _addToParent: function(path) {
85       var storage = this._storage;
86       applyRecursive(path, function(dirname, basename) {
87         var node = storage[dirname] || makeNode(dirname);
88         node.body[basename] = true;
89         storage[dirname] = node;
90         return true;
91       });
92     },
93
94     _removeFromParent: function(path) {
95       var storage = this._storage;
96       var self = this;
97       applyRecursive(path, function(dirname, basename) {
98         var node = storage[dirname];
99         if (node) {
100           delete node.body[basename];
101           if (Object.keys(node.body).length === 0) {
102             delete storage[dirname];
103             return true;
104           } else {
105             self._addToParent(dirname);
106           }
107         }
108       });
109     },
110
111     _recordChange: function(path, attributes) {
112       var change = this._changes[path] || {};
113       for(var key in attributes) {
114         change[key] = attributes[key];
115       }
116       change.path = path;
117       this._changes[path] = change;
118     },
119
120     clearChange: function(path) {
121       delete this._changes[path];
122       return promising().fulfill();
123     },
124
125     changesBelow: function(path) {
126       var changes = [];
127       var l = path.length;
128       for(var key in this._changes) {
129         if (key.substr(0,l) === path) {
130           changes.push(this._changes[key]);
131         }
132       }
133       return promising().fulfill(changes);
134     },
135
136     setConflict: function(path, attributes) {
137       var event = this._createConflictEvent(path, attributes);
138       this._recordChange(path, { conflict: attributes });
139       this._emit('conflict', event);
140     },
141
142     setRevision: function(path, revision) {
143       var node = this._storage[path] || makeNode(path);
144       node.revision = revision;
145       this._storage[path] = node;
146       return promising().fulfill();
147     },
148
149     getRevision: function(path) {
150       var rev;
151       if (this._storage[path]) {
152         rev = this._storage[path].revision;
153       }
154       return promising().fulfill(rev);
155     },
156
157     fireInitial: function() {
158       // fireInital fires a change event for each item in the store
159       // inMemoryStorage is always empty on pageLoad
160     }
161   };
162
163   RemoteStorage.InMemoryStorage._rs_init = function() {};
164
165   RemoteStorage.InMemoryStorage._rs_supported = function() {
166     return true;
167   };
168
169   RemoteStorage.InMemoryStorage._rs_cleanup = function() {};
170 })(typeof(window) !== 'undefined' ? window : global);