UNPKG

11.5 kBJavaScriptView Raw
1var helpers = require("./helpers.js");
2var Item = require("./item.js");
3
4
5var mockCallback = helpers.mockCallback;
6
7var validKeyType = function(mockInstance, key, callback) {
8 return helpers.validKeyType(mockInstance, key, 'list', callback)
9};
10
11var initKey = function(mockInstance, key) {
12 return helpers.initKey(mockInstance, key, Item.createList);
13};
14
15/**
16 * Llen
17 */
18exports.llen = function (mockInstance, key, callback) {
19 var length = mockInstance.storage[key] ? mockInstance.storage[key].value.length : 0;
20 mockInstance._callCallback(callback, null, length);
21};
22
23var push = function (fn, args) {
24 var len = args.length;
25 if (len < 2) {
26 return
27 }
28 var mockInstance = args[0];
29 var key = args[1];
30 var callback = helpers.parseCallback(args);
31 if (callback == undefined) {
32 callback = mockCallback;
33 }
34 if (!validKeyType(mockInstance, key, callback)) {
35 return
36 }
37 // init key
38 initKey(mockInstance, key);
39
40 // parse only the values from the args;
41 var values = [];
42 for (var i=2, val; i < len; i++) {
43 val = args[i];
44 if ('function' == typeof val) {
45 break;
46 }
47 values.push(val);
48 }
49 fn.call(mockInstance.storage[key], values);
50 var length = mockInstance.storage[key].value.length;
51 pushListWatcher.pushed(key);
52 mockInstance._callCallback(callback, null, length);
53};
54
55/**
56 * Lpush
57 */
58exports.lpush = function () {
59 push(Item._list.prototype.lpush, arguments);
60};
61
62/**
63 * Rpush
64 */
65exports.rpush = function () {
66 push(Item._list.prototype.rpush, arguments);
67};
68
69var pushx = function (fn, mockInstance, key, value, callback) {
70 var length = 0;
71 if (mockInstance.storage[key]) {
72 if (mockInstance.storage[key].type !== "list") {
73 return mockInstance._callCallback(callback,
74 new Error("ERR Operation against a key holding the wrong kind of value"));
75 }
76 fn.call(mockInstance.storage[key], [value]);
77 length = mockInstance.storage[key].value.length;
78 pushListWatcher.pushed(key);
79 }
80 mockInstance._callCallback(callback, null, length);
81};
82
83/**
84 * Rpushx
85 */
86exports.rpushx = function (mockInstance, key, value, callback) {
87 pushx(Item._list.prototype.rpush, mockInstance, key, value, callback);
88};
89
90/**
91 * Lpushx
92 */
93exports.lpushx = function (mockInstance, key, value, callback) {
94 pushx(Item._list.prototype.lpush, mockInstance, key, value, callback);
95};
96
97var pop = function (fn, mockInstance, key, callback) {
98 var val = null;
99 if (mockInstance.storage[key] && mockInstance.storage[key].type !== "list") {
100 return mockInstance._callCallback(callback,
101 new Error("ERR Operation against a key holding the wrong kind of value"));
102 }
103 if (mockInstance.storage[key] && mockInstance.storage[key].value.length > 0) {
104 val = fn.call(mockInstance.storage[key]);
105 }
106 mockInstance._callCallback(callback, null, val);
107};
108
109/**
110 * Lpop
111 */
112exports.lpop = function (mockInstance, key, callback) {
113 pop.call(this, Item._list.prototype.lpop, mockInstance, key, callback);
114};
115
116/**
117 * Rpop
118 */
119exports.rpop = function (mockInstance, key, callback) {
120 pop.call(this, Item._list.prototype.rpop, mockInstance, key, callback);
121};
122
123/**
124 * Rpoplpush
125 */
126exports.rpoplpush = function(mockInstance, sourceKey, destinationKey, callback) {
127 pop.call(this, Item._list.prototype.rpop, mockInstance, sourceKey, function (err, reply) {
128 if (err) {
129 return mockInstance._callCallback(callback, err, null);
130 }
131 if (reply === null || reply === undefined) {
132 return mockInstance._callCallback(callback, null);
133 }
134 push(Item._list.prototype.lpush, [
135 mockInstance,
136 destinationKey,
137 reply,
138 function (err) {
139 if (err) {
140 return mockInstance._callCallback(callback, err, null);
141 }
142 return mockInstance._callCallback(callback, null, reply);
143 }
144 ]);
145 });
146};
147
148/**
149 * Listen to all the list identified by keys and set a timeout if timeout != 0
150 */
151var listenToPushOnLists = function (mockInstance, keys, timeout, callback) {
152 var listenedTo = [];
153 var expire = null;
154 var listener = function (key) {
155 // We remove all the other listeners.
156 pushListWatcher.removeListeners(listenedTo, listener);
157 if (expire) {
158 clearTimeout(expire);
159 }
160 callback(key);
161 };
162
163 for (var i = 0; i < keys.length; i++) {
164 listenedTo.push(keys[i]);
165 pushListWatcher.suscribe(keys[i], listener);
166 }
167 if (timeout > 0) {
168 expire = setTimeout(function () {
169 pushListWatcher.removeListeners(listenedTo, listener);
170 callback(null);
171 }, timeout * 1000);
172 if (expire.unref) {
173 expire.unref();
174 }
175 }
176};
177
178/**
179 * Helper function to build blpop and brpop
180 */
181var bpop = function (fn, mockInstance, keys, timeout, callback) {
182 var val = null;
183 // Look if any element can be returned
184 for (var i = 0; i < keys.length; i++) {
185 if (mockInstance.storage[keys[i]] && mockInstance.storage[keys[i]].value.length > 0) {
186 var key = keys[i];
187 val = fn.call(mockInstance.storage[key]);
188 mockInstance._callCallback(callback, null, [key, val]);
189 return;
190 }
191 }
192 // We listen to all the list we asked for
193 listenToPushOnLists(mockInstance, keys, timeout, function (key) {
194 if (key !== null) {
195 val = fn.call(mockInstance.storage[key]);
196 mockInstance._callCallback(callback, null, [key, val]);
197 } else {
198 mockInstance._callCallback(callback, null, null);
199 }
200
201 });
202};
203
204/**
205 * BLpop
206 */
207exports.blpop = function (mockInstance, keys, timeout, callback) {
208 bpop.call(this, Item._list.prototype.lpop, mockInstance, keys, timeout, callback);
209};
210
211/**
212 * BRpop
213 */
214exports.brpop = function (mockInstance, keys, timeout, callback) {
215 bpop.call(this, Item._list.prototype.rpop, mockInstance, keys, timeout, callback);
216};
217
218/**
219 * Lindex
220 */
221exports.lindex = function (mockInstance, key, index, callback) {
222 var val = null;
223 if (mockInstance.storage[key]) {
224 if (mockInstance.storage[key].type !== "list") {
225 return mockInstance._callCallback(callback,
226 new Error("ERR Operation against a key holding the wrong kind of value"));
227 }
228
229 if (index < 0 && -mockInstance.storage[key].value.length <= index) {
230 val = mockInstance.storage[key].value[mockInstance.storage[key].value.length + index];
231 } else if (mockInstance.storage[key].value.length > index) {
232 val = mockInstance.storage[key].value[index];
233 }
234 }
235 mockInstance._callCallback(callback, null, val);
236};
237
238/**
239 * Lrange
240 */
241exports.lrange = function (mockInstance, key, startIndex, stopIndex, callback) {
242 var val = [];
243 var index1 = startIndex;
244 var index2 = stopIndex;
245
246 if (mockInstance.storage[key]) {
247 if (mockInstance.storage[key].type !== "list") {
248 return mockInstance._callCallback(callback,
249 new Error("ERR Operation against a key holding the wrong kind of value"));
250 }
251
252 index1 = index1 >= 0 ? index1 : Math.max(mockInstance.storage[key].value.length + index1, 0);
253 index2 = index2 >= 0 ? index2 : Math.max(mockInstance.storage[key].value.length + index2, 0);
254 val = mockInstance.storage[key].value.slice(index1, index2 + 1);
255 }
256 mockInstance._callCallback(callback, null, val);
257};
258
259/**
260 * Lrem
261 */
262exports.lrem = function (mockInstance, key, count, value, callback) {
263 var removedCount = 0;
264
265 if (mockInstance.storage[key]) {
266 if (mockInstance.storage[key].type !== "list") {
267 return mockInstance._callCallback(callback,
268 new Error("ERR Operation against a key holding the wrong kind of value"));
269 }
270
271 var list = mockInstance.storage[key].value;
272
273 var strValue = Item._stringify(value);
274
275 var filteredList = [];
276 if (count > 0) {
277 // count > 0: Remove elements equal to value moving from head to tail
278 for (var i = 0; i < list.length; ++i) {
279 if (list[i] == strValue && count > 0) {
280 --count;
281 ++removedCount;
282 } else {
283 filteredList.push(list[i]);
284 }
285 }
286 } else if (count < 0) {
287 // count < 0: Remove elements equal to value moving from tail to head.
288 for (i = list.length; i > 0; --i) {
289 if (list[i-1] == strValue && count < 0) {
290 ++count;
291 ++removedCount;
292 } else {
293 filteredList.unshift(list[i-1]);
294 }
295 }
296 } else {
297 // count = 0: Remove all elements equal to value.
298 for (i = 0; i < list.length; ++i) {
299 if (list[i] === strValue) {
300 ++removedCount;
301 } else {
302 filteredList.push(list[i]);
303 }
304 }
305 }
306
307 mockInstance.storage[key].value = filteredList;
308 }
309 mockInstance._callCallback(callback, null, removedCount);
310};
311
312/**
313 * Lset
314 */
315exports.lset = function (mockInstance, key, index, value, callback) {
316 var res = "OK";
317 var len = -1;
318 if (!mockInstance.storage[key]) {
319 return mockInstance._callCallback(callback,
320 new Error("ERR no such key"));
321 }
322 if (mockInstance.storage[key].type !== "list") {
323 return mockInstance._callCallback(callback,
324 new Error("ERR Operation against a key holding the wrong kind of value"));
325 }
326 len = mockInstance.storage[key].value.length;
327 if (len <= index || -len > index) {
328 return mockInstance._callCallback(callback,
329 new Error("ERR index out of range"));
330 }
331 if (index < 0) {
332 mockInstance.storage[key].value[len + index] = Item._stringify(value);
333 } else {
334 mockInstance.storage[key].value[index] = Item._stringify(value);
335 }
336 mockInstance._callCallback(callback, null, res);
337};
338
339/**
340 * ltrim
341 */
342exports.ltrim = function(mockInstance, key, start, end, callback) {
343 var res = "OK";
344 var len = -1;
345 if (!mockInstance.storage[key]) {
346 return mockInstance._callCallback(callback, null, res);
347 }
348
349 if (mockInstance.storage[key].type !== "list") {
350 return mockInstance._callCallback(callback,
351 new Error("WRONGTYPE Operation against a key holding the wrong kind of value"));
352 }
353
354 len = mockInstance.storage[key].value.length;
355
356 if (start < 0) {
357 start = len + start;
358 }
359 if (end < 0) {
360 end = len + end;
361 }
362 if (end >= len) {
363 end = len - 1;
364 }
365 if (start >= len || start > end) {
366 // trim whole list
367 delete mockInstance.storage[key];
368 } else {
369 mockInstance.storage[key].value = mockInstance.storage[key].value.slice(start, end + 1);
370 }
371 mockInstance._callCallback(callback, null, res);
372};
373
374/**
375 * Used to follow a list depending on its key (used by blpop and brpop mainly)
376 */
377var PushListWatcher = function () {
378 this.listeners = {};
379};
380
381/**
382 * Watch for the next push in the list key
383 */
384PushListWatcher.prototype.suscribe = function (key, listener) {
385 if (this.listeners[key]) {
386 this.listeners[key].push(listener);
387 } else {
388 this.listeners[key] = [listener];
389 }
390};
391
392/**
393 * Calls the first listener which was waiting for an element
394 * to call when we push to a list
395 */
396PushListWatcher.prototype.pushed = function (key) {
397 if (this.listeners[key] && this.listeners[key].length > 0) {
398 var listener = this.listeners[key].shift();
399 listener(key);
400 }
401};
402
403/**
404 * Remove all the listener from all the keys it was listening to
405 */
406PushListWatcher.prototype.removeListeners = function (listenedTo, listener) {
407 for (var i = 0; i < listenedTo.length; i++) {
408 for (var j = 0; j < this.listeners[listenedTo[i]].length; j++) {
409 if (this.listeners[listenedTo[i]][j] === listener) {
410 this.listeners[listenedTo[i]].splice(j, 1);
411 j = this.listeners[listenedTo[i]];
412 }
413 }
414 }
415};
416
417var pushListWatcher = new PushListWatcher();