Commit bad40a1d authored by Sven Franck's avatar Sven Franck

first pass replicate storage with error handling

parent 2074b05c
......@@ -17,7 +17,7 @@
*/
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global define, module, require, jIO, RSVP */
/*global define, module, require, jIO, RSVP, JSON */
(function (root, dependencies, factory) {
"use strict";
......@@ -57,6 +57,41 @@
return false;
}
/**
* Make a clean clone of an object with clean referring to removal of all
* flags indicating missing or non-coherent elements.
* @param {Object} obj to clone
* @return {Object} clones object
*/
function cleanClone (obj) {
// NOTE: polyfill needed for IE8-
var r = Object.create(obj);
if (r._missing) {
delete r._missing;
}
if (r._conflict_list) {
delete r._conflict_list;
}
return r;
};
/**
* Generate a checksum from a passed string to compare the response returned
* by multiple storages for the same requests.
* @param {Object} obj to generate a checksum for
* @return {Integer} checksum
*/
function checksum (obj) {
var r, i, str = JSON.stringify(obj);
for(r = 0, i = 0; i < str.length; i += 1) {
r = (r << 5) - r + str.charCodeAt(i);
r &= r;
}
return r;
};
/**
* Executes a sequence of *then* callbacks. It acts like
* `smth().then(callback).then(callback)...`. The first callback is called
......@@ -202,38 +237,55 @@
}
/**
* Responds with the last modified document recieved. If all promises are
* rejected, it returns the latest rejected promise answer received. Promises
* are cancelled only by calling `lastModified(promise_list).cancel()`. USE
* THIS FUNCTION ONLY FOR GET METHOD!
*
* lastModified(promise_list): Promise
* Test a response across multiple storages. Returns first valid document
* together with the following parameters:
*
* _missing = true > when a storage returns a 404 for this document
* _version_list = [] > when a file has multiple (conflicting) versions
*
* Promises are cancelled only by calling
*
* `checkAndReturn(promise_list).cancel()`
*
* @param {Array} promise_list An array of promises
* @return {Promise} A new promise
*/
function lastModified(promise_list) {
function checkAndReturn(promise_list) {
var length = promise_list.length;
promise_list = promise_list.slice();
return new Promise(function (resolve, reject, notify) {
var index, last_good_answer, last_answer, count = 0, error_count = 0;
var index, doc, trace, version_list, count, error_count, tag;
version_list = [];
count = 0;
error_count = 0;
tag = function (reply) {
if (version_list.length) {
reply._conflict_list = version_list;
}
if (error_count) {
reply._missing = true;
}
return reply;
};
function resolver(answer) {
last_answer = answer;
if (last_good_answer === undefined) {
if (isDate(answer.data.modified)) {
last_good_answer = answer;
}
if (trace === undefined) {
trace = checksum(answer);
doc = answer;
} else {
if (isDate(answer.data.modified)) {
if (new Date(last_good_answer.data.modified) <
new Date(answer.data.modified)) {
last_good_answer = answer;
if (trace !== checksum(answer)) {
if (version.list.length === 0) {
version_list.push(doc);
}
version_list.push(answer);
}
}
count += 1;
if (count === length) {
return resolve(last_good_answer);
return resolve(tag(doc));
}
}
function rejecter(answer) {
......@@ -243,7 +295,7 @@
}
count += 1;
if (count === length) {
return resolve(last_good_answer || last_answer);
return resolve(tag(doc));
}
}
function notifier(index) {
......@@ -254,6 +306,7 @@
});
};
}
for (index = 0; index < length; index += 1) {
promise_list[index].then(resolver, rejecter, notifier(index));
}
......@@ -265,23 +318,6 @@
});
}
// /**
// * An Universal Unique ID generator
// *
// * @return {String} The new UUID.
// */
// function generateUuid() {
// function S4() {
// return ('0000' + Math.floor(
// Math.random() * 0x10000 /* 65536 */
// ).toString(16)).slice(-4);
// }
// return S4() + S4() + "-" +
// S4() + "-" +
// S4() + "-" +
// S4() + "-" +
// S4() + S4() + S4();
// }
function ReplicateStorage(spec) {
if (!Array.isArray(spec.storage_list)) {
......@@ -293,14 +329,6 @@
ReplicateStorage.prototype.post = function (command, metadata, option) {
var promise_list = [], index, length = this._storage_list.length;
// if (!isDate(metadata.modified)) {
// command.error(
// 409,
// "invalid 'modified' metadata",
// "The metadata 'modified' should be a valid date string or date object"
// );
// return;
// }
for (index = 0; index < length; index += 1) {
promise_list[index] = success(
command.storage(this._storage_list[index]).post(metadata, option)
......@@ -313,14 +341,6 @@
ReplicateStorage.prototype.put = function (command, metadata, option) {
var promise_list = [], index, length = this._storage_list.length;
// if (!isDate(metadata.modified)) {
// command.error(
// 409,
// "invalid 'modified' metadata",
// "The metadata 'modified' should be a valid date string or date object"
// );
// return;
// }
for (index = 0; index < length; index += 1) {
promise_list[index] =
command.storage(this._storage_list[index]).put(metadata, option);
......@@ -378,7 +398,7 @@
command.storage(this._storage_list[index]).get(param, option);
}
sequence([function () {
return lastModified(promise_list);
return checkAndReturn(promise_list);
}, [command.success, command.error]]);
};
......@@ -401,105 +421,137 @@
promise_list[index] =
success(command.storage(this._storage_list[index]).allDocs(option));
}
sequence([function () {
return all(promise_list);
}, function (answers) {
// merge responses
var i, j, k, found, rows;
// browsing answers
for (i = 0; i < answers.length; i += 1) {
if (answers[i].result === "success") {
if (!rows) {
rows = answers[i].data.rows;
var i, j, k, l, record, reply, rows, count, total, base, test, must_add,
len = answers.length;
// loop storage response
// NOTE: every storage may return different records!
for (i = 0; i < len; i += 1) {
reply = answers[i];
total = reply.data.total_rows;
// loop records returned in response
for (j = 0; j < total; j += 1) {
record = reply.data.rows[j];
must_add = undefined;
if (rows === undefined) {
rows = [record];
} else {
// browsing answer rows
for (j = 0; j < answers[i].data.rows.length; j += 1) {
found = false;
// browsing result rows
for (k = 0; k < rows.length; k += 1) {
if (rows[k].id === answers[i].data.rows[j].id) {
found = true;
break;
count = rows.length;
// loop records already in rows object
for (k = 0; k < count; k += 1) {
base = rows[k];
test = checksum(cleanClone(base));
// record exists, test for coherence
if (base.id === record.id) {
must_add = undefined;
if (test !== checksum(record)) {
if (!base._conflict_list) {
base._conflict_list = [base];
}
base._conflict_list.push(record);
}
// need to add record
} else {
must_add = true;
}
if (!found) {
rows.push(answers[i].data.rows[j]);
}
// add missing records
if (must_add) {
// ok on first storage iteration (i), afterwards 404
if (i > 0) {
record._missing = true;
}
rows.push(record);
}
}
}
}
// if one storage returns rows = 0, make sure all records are labelled
if (total === 0) {
rows = rows || [];
for (l = 0; l < rows.length; l += 1) {
rows[l]._missing = true;
}
}
return {"data": {"total_rows": (rows || []).length, "rows": rows || []}};
}, [command.success, command.error]]);
};
ReplicateStorage.prototype.check = function (command, param, option) {
var promise_list = [], index, length = this._storage_list.length;
for (index = 0; index < length; index += 1) {
promise_list[index] = success(
command.storage(this._storage_list[index]).check(param, option)
);
}
sequence([function () {
return all(promise_list);
}, [command.success, command.error]]);
return command.error(501);
};
ReplicateStorage.prototype.repair = function (command, param) {
var promise_list = [], index, that, length = this._storage_list.length;
that = this;
if (typeof param._id !== 'string' || !param._id) {
command.success();
return;
}
for (index = 0; index < length; index += 1) {
promise_list[index] =
success(command.storage(this._storage_list[index]).get(param));
}
sequence([function () {
return all(promise_list);
}, function (answers) {
var i, list = [], winner = null;
for (i = 0; i < answers.length; i += 1) {
if (answers[i].result === "success") {
if (isDate(answers[i].data.modified)) {
list[i] = answers[i].data;
if (winner === null ||
new Date(winner.modified) <
new Date(answers[i].data.modified)) {
winner = answers[i].data;
}
}
} else if (answers[i].status === 404) {
list[i] = 0;
}
}
for (i = 0; i < list.length; i += 1) {
if (list[i] && new Date(list[i].modified) < new Date(winner.modified)) {
list[i] = success(command.storage(that._storage_list[i]).put(winner));
} else if (list[i] === 0) {
list[i] = dictUpdate({}, winner);
delete list[i]._id;
list[i] =
success(command.storage(that._storage_list[i]).post(list[i]));
}
}
list = list.reduce(function (previous, current) {
if (current) {
previous.push(current);
}
return previous;
}, []);
return all(list);
}, function (answers) {
var i;
for (i = 0; i < answers.length; i += 1) {
if (answers[i].result !== "success") {
return command.error(409);
}
}
command.success();
}]);
console.log("repairing")
console.log(command)
console.log(param)
// var promise_list = [], index, that, length = this._storage_list.length;
// that = this;
// if (typeof param._id !== 'string' || !param._id) {
// command.success();
// return;
// }
// for (index = 0; index < length; index += 1) {
// promise_list[index] =
// success(command.storage(this._storage_list[index]).get(param));
// }
// sequence([function () {
// return all(promise_list);
// }, function (answers) {
// var i, list = [], winner = null;
// for (i = 0; i < answers.length; i += 1) {
// if (answers[i].result === "success") {
// if (isDate(answers[i].data.modified)) {
// list[i] = answers[i].data;
// if (winner === null ||
// new Date(winner.modified) <
// new Date(answers[i].data.modified)) {
// winner = answers[i].data;
// }
// }
// } else if (answers[i].status === 404) {
// list[i] = 0;
// }
// }
// for (i = 0; i < list.length; i += 1) {
// if (list[i] && new Date(list[i].modified) < new Date(winner.modified)) {
// list[i] = success(command.storage(that._storage_list[i]).put(winner));
// } else if (list[i] === 0) {
// list[i] = dictUpdate({}, winner);
// delete list[i]._id;
// list[i] =
// success(command.storage(that._storage_list[i]).post(list[i]));
// }
// }
// list = list.reduce(function (previous, current) {
// if (current) {
// previous.push(current);
// }
// return previous;
// }, []);
// return all(list);
// }, function (answers) {
// var i;
// for (i = 0; i < answers.length; i += 1) {
// if (answers[i].result !== "success") {
// return command.error(409);
// }
// }
// command.success();
// }]);
};
addStorageFunction('replicate', ReplicateStorage);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment