summaryrefslogtreecommitdiffstats
path: root/web/transaction.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/transaction.js')
-rw-r--r--web/transaction.js723
1 files changed, 376 insertions, 347 deletions
diff --git a/web/transaction.js b/web/transaction.js
index 3d4a953..fc00550 100644
--- a/web/transaction.js
+++ b/web/transaction.js
@@ -3,9 +3,9 @@
* See LICENSE file for license details
*/
-define(
- ["aconf/blocking", "aconf/path", "aconf/type", "jquery", "underscore"],
- function(blocking, pth, type, $, _) {
+angular.module("aconf").factory(
+ "aconfTxn",
+ function($http, $q, aconfExclusive, aconfPath, aconfType) {
return function(token, saveRequired) {
var txnMgr = {};
@@ -22,15 +22,16 @@ define(
function request(url, options) {
options = options || {};
+ options.url = url;
options.headers = {"X-AConf-Auth-Token": token};
if (txn) options.headers["X-AConf-Transaction-ID"] = txn;
- if (options.data != undefined)
+ if (options.data != null)
options.data = JSON.stringify(options.data);
- return $.ajax(encodeURI(url), options);
+ return $http(options);
}
txnMgr.abort = function() {
- request("/transaction", {type: "DELETE"});
+ request("/transaction", {method: "DELETE"});
reset();
}
@@ -39,408 +40,436 @@ define(
}
function exclusive(task) {
- blocking.enable();
-
- var def = $.Deferred();
- function resolve(txnValid) { def.resolve(txnValid); }
- function reject() { def.reject(); }
+ function attempt() {
+ return $q(function(resolve, reject) {
+ function postpone() {
+ attempt().then(resolve, reject);
+ }
- var tasks = _.filter(
- _.pluck(_.values(invalid), 1), function(d) {
- return d && d.state() == "pending";
- }
- );
+ var tasks = _.filter(
+ _.pluck(_.values(invalid), 1),
+ function(task) { return task; }
+ );
- if (tasks.length)
- tasks[0].always(function() {
- exclusive(task).done(resolve).fail(reject);
+ if (tasks.length) tasks[0].then(postpone, postpone);
+ else task().then(resolve, reject);
});
- else task().always(blocking.disable).done(resolve).fail(reject);
+ }
- return def;
+ return aconfExclusive(attempt);
}
txnMgr.query = function(path) {
- var def = $.Deferred();
-
- objRequest(path).done(function(data) {
- data.txnMgr = txnMgr;
-
- if (type.isTreeNode(data.meta) && !_.size(data.data))
- data.data = type.isList(data.meta) ? [] : {};
-
- if (!_.isArray(data.meta.removable))
- data.meta.removable = [];
-
- function index(name) {
- return _.isArray(data.data) ? name - 1 : name;
- }
-
- function ensureKeyPresence(name) {
- var key = index(name);
- if (!(key in data.data)) data.data[key] = null;
- if (data.data[key] == null)
- data.meta.removable.push(name);
- }
-
- function findSubordinateKeys(objmap, path) {
- return _.filter(_.keys(objmap), function(p) {
- return pth.isSubordinate(p, path);
- });
- }
-
- function invalidSubordinates() {
- return findSubordinateKeys(invalid, path);
- }
-
- var set = data.meta.type == "set";
-
- if (type.isCollection(data.meta) && !set) {
- var level = pth.split(path).length;
- _.each(invalidSubordinates(), function(p) {
- ensureKeyPresence(pth.split(p)[level]);
- });
- }
-
- data.get = function(name, valid) {
- var p = pth.join(path, name);
- if (!valid && p in invalid) return invalid[p][0];
-
- if (data.meta.type == "set")
- return _.contains(data.data, name) ? name : null;
-
- return data.data[index(name)];
- };
-
- data.metaRequest = function(name) {
- return request("/meta" + pth.join(path, name));
- };
-
- data.match = function(filter) {
- if (!filter) return true;
- return _.every(_.map(filter, function(values, key) {
- return _.contains(values, data.data[key]);
- }));
- };
-
- data.status = function(name) {
- var p = name ? pth.join(path, name) : path;
- function scan(objmap) {
- return _.size(findSubordinateKeys(objmap, p));
- }
-
- if (scan(invalid)) return "invalid";
- if (scan(changed)) return "changed";
- return null;
- };
-
- data.isSubtreeValid = function() {
- return !_.size(invalidSubordinates(invalid, path));
- };
-
- data.validate = function() {
- var valid = true;
- if (data.meta.required) {
- valid = _.size(data.data);
- if (valid) delete invalid[path];
- else invalid[path] = [path];
- }
- return valid && isValid();
- };
+ return $q(function(resolve, reject) {
+ objRequest(path).success(function(data) {
+ data.txnMgr = txnMgr;
- data.set = function(name, newValue) {
- var def = $.Deferred();
- function reject(xhr) { def.reject(xhr); }
+ if (aconfType.isTreeNode(data.meta) &&
+ !_.size(data.data))
+ data.data = aconfType.isList(data.meta) ? [] : {};
- var mpath = pth.join(path, name);
+ if (!_.isArray(data.meta.removable))
+ data.meta.removable = [];
- ensureKeyPresence(name);
- var value = data.get(name, true);
-
- var empty = newValue === null &&
- type.isCollection(data.meta);
- if (newValue === undefined) newValue = null;
- var tn = _.isObject(newValue);
- var npv = tn ? mpath : newValue;
-
- var prevTask = mpath in invalid &&
- invalid[mpath][1] || $.Deferred().resolve();
+ function index(name) {
+ return _.isArray(data.data) ? name - 1 : name;
+ }
- invalid[mpath] = [npv, def];
+ function ensureKeyPresence(name) {
+ var key = index(name);
+ if (!(key in data.data)) data.data[key] = null;
+ if (data.data[key] == null)
+ data.meta.removable.push(name);
+ }
- function ignore(path) {
- _.each(_.keys(invalid), function(p) {
- if (pth.isSubordinate(p, path))
- delete invalid[p];
+ function findSubordinateKeys(objmap, path) {
+ return _.filter(_.keys(objmap), function(p) {
+ return aconfPath.isSubordinate(p, path);
});
}
- function resolve() {
- if (mpath in invalid &&
- invalid[mpath][1] == def) {
-
- var del = invalid[mpath][0] == null;
+ function invalidSubordinates() {
+ return findSubordinateKeys(invalid, path);
+ }
- delete invalid[mpath];
- if (del) ignore(mpath);
- }
+ var set = data.meta.type == "set";
- def.resolve(isValid());
+ if (aconfType.isCollection(data.meta) && !set) {
+ var level = aconfPath.split(path).length;
+ _.each(invalidSubordinates(), function(p) {
+ ensureKeyPresence(aconfPath.split(p)[level]);
+ });
}
- prevTask.always(function() {
- if (empty) {
- def.reject("Value not set");
- return;
+ data.get = function(name, valid) {
+ var p = aconfPath.join(path, name);
+ if (!valid && p in invalid) return invalid[p][0];
+
+ if (data.meta.type == "set")
+ return _.contains(data.data, name) ?
+ name : null;
+
+ var res = data.data[index(name)];
+ return res === undefined ? null : res;
+ };
+
+ data.metaRequest = function(name) {
+ return request("/meta" + aconfPath.join(path, name));
+ };
+
+ data.match = function(filter) {
+ if (!filter) return true;
+ return _.every(
+ _.map(filter, function(values, key) {
+ return _.contains(values, data.data[key]);
+ })
+ );
+ };
+
+ data.status = function(name) {
+ var p = aconfPath.join(path, name);
+ function scan(objmap) {
+ return _.size(findSubordinateKeys(objmap, p));
}
- var del = newValue == null;
-
- var options;
- if (!del)
- options = {
- type: set ? "POST" : "PUT", data: newValue
- };
- else if (data.get(name, true) != null)
- options = {type: "DELETE"};
-
- if (!options) {
- if (data.meta.type == "model" &&
- _.findWhere(
- data.meta.fields, {name: name}
- ).required)
- def.reject("Required value not set");
- else resolve();
- return;
+ if (scan(invalid)) return "invalid";
+ if (scan(changed)) return "changed";
+ return null;
+ };
+
+ data.isNodeValid = function() {
+ return !(path in invalid);
+ };
+
+ data.isSubtreeValid = function() {
+ return !_.size(invalidSubordinates(invalid, path));
+ };
+
+ data.validate = function() {
+ var valid = true;
+ if (data.meta.required) {
+ valid = _.size(data.data);
+ if (valid) delete invalid[path];
+ else invalid[path] = [path];
+ }
+ return valid && isValid();
+ };
+
+ data.set = function(name, newValue) {
+ var def = $q.defer();
+ function reject(resp) {
+ if (mpath in invalid &&
+ invalid[mpath][1] == def.promise)
+ invalid[mpath][1] = null;
+ def.reject(resp);
}
-
- objRequest(
- set && !del ? path : mpath, options
- ).done(function() {
- if (!(mpath in changed))
- changed[mpath] = value;
- if (!tn && newValue == changed[mpath])
- delete changed[mpath];
-
- if (npv == null)
- _.each(
- _.keys(changed),
- function(p) {
- if (pth.isSubordinate(
- p, mpath, true
- ))
- delete changed[p];
- }
- );
- if (data.meta.type == "list" && del)
- data.data.splice(name - 1, 1);
- else if (!set) data.data[index(name)] = npv;
- else if (del) data.data = _.without(data.data, name);
- else data.data.push(name);
+ var mpath = aconfPath.join(path, name);
- data.validate();
+ ensureKeyPresence(name);
+ var value = data.get(name, true);
- if (data.meta.type == "model")
- _.each(
- data.meta.fields, function(field) {
- if (field.condition &&
- field.condition[name] &&
- !_.contains(
- field.condition[name],
- newValue
- ))
- ignore(
- pth.join(path, field.name)
- );
- }
- );
-
- if (tn && !set)
- txnMgr.query(mpath).done(function(data) {
-
- if (mpath in invalid) {
- if (data.meta.type == "model")
- _.each(
- data.meta.fields,
- function(field) {
- var mmpath = pth.join(
- mpath,
- field.name
- );
- if (field.required &&
- data.match(
- field.condition
- ) &&
- !(mmpath in invalid) &&
- (type.isCollection(
- field
- ) || data.get(
- field.name
- ) == null)) {
- invalid[mmpath] = [
- null
- ];
- }
- });
-
- else if (
- type.isCollection(data.meta) &&
- data.meta.required
- )
- invalid[mpath] = [mpath];
- }
+ var empty = newValue === null &&
+ aconfType.isCollection(data.meta);
+ if (newValue === undefined) newValue = null;
+ var tn = _.isObject(newValue);
+ var npv = tn ? mpath : newValue;
- resolve();
- }).fail(reject);
-
- else resolve();
-
- }).fail(reject);
- });
-
- return def;
- };
-
- data.add = function(name) {
- return data.set(name, name);
- };
-
- function adjustListIndex(oldIndex, newIndex) {
- var opath = pth.join(path, oldIndex);
- var npath = pth.join(path, newIndex);
- _.each(
- [changed, invalid],
- function(map) {
- _.each(
- _.keys(map),
- function(p) {
- if (pth.isSubordinate(
- p, opath
- )) {
- map[npath + p.substring(
- opath.length
- )] = map[p];
- delete map[p];
- }
- });
+ var prevTask = mpath in invalid &&
+ invalid[mpath][1] || $q.when();
+
+ invalid[mpath] = [npv, def.promise];
+
+ function ignore(path) {
+ _.each(_.keys(invalid), function(p) {
+ if (aconfPath.isSubordinate(p, path))
+ delete invalid[p];
+ });
}
- );
- }
- function adjustListIndices(start, end) {
- var offset = start < end ? 1 : -1;
- for (var i = start; i != end; i += offset)
- adjustListIndex(i + offset, i);
- }
+ function resolve() {
+ if (mpath in invalid &&
+ invalid[mpath][1] == def.promise) {
- function _delete(name) {
- var def = $.Deferred();
- var length = data.data.length;
+ var del = invalid[mpath][0] == null;
- data.set(name).done(function(txnValid) {
- if (type.isTreeNode(data.meta) &&
- data.meta.type != "set") {
+ delete invalid[mpath];
+ if (del) ignore(mpath);
+ }
- delete changed[pth.join(path, name)];
- changed[path] = path;
+ def.resolve(isValid());
+ }
- if (data.meta.type == "list")
- adjustListIndices(name, length);
+ function newTask() {
+ if (empty) {
+ reject("Value not set");
+ return;
+ }
+
+ var del = newValue == null;
+
+ var options;
+ if (!del)
+ options = {
+ method: set ? "POST" : "PUT",
+ data: newValue
+ };
+ else if (data.get(name, true) != null)
+ options = {method: "DELETE"};
+
+ if (!options) {
+ if (data.meta.type == "model" &&
+ _.findWhere(
+ data.meta.fields, {name: name}
+ ).required)
+ reject("Required value not set");
+ else resolve();
+ return;
+ }
+
+ objRequest(
+ set && !del ? path : mpath, options
+ ).then(function() {
+ if (!(mpath in changed))
+ changed[mpath] = value;
+ if (!tn && newValue == changed[mpath])
+ delete changed[mpath];
+
+ if (del)
+ _.each(
+ _.keys(changed),
+ function(p) {
+ if (aconfPath.isSubordinate(
+ p, mpath, true
+ ))
+ delete changed[p];
+ }
+ );
+
+ if (!set) data.data[index(name)] = npv;
+ else if (del)
+ data.data = _.without(data.data, name);
+ else data.data.push(name);
+
+ data.validate();
+
+ if (data.meta.type == "model")
+ _.each(
+ data.meta.fields, function(field) {
+ if (field.condition &&
+ field.condition[name] &&
+ !_.contains(
+ field.condition[name],
+ newValue
+ ))
+ ignore(
+ aconfPath.join(
+ path, field.name
+ )
+ );
+ }
+ );
+
+ if (tn && !set)
+ txnMgr.query(mpath).then(function(data) {
+
+ if (mpath in invalid) {
+ if (data.meta.type == "model")
+ _.each(
+ data.meta.fields,
+ function(field) {
+ var mmpath = aconfPath.join(
+ mpath,
+ field.name
+ );
+ if (field.required &&
+ data.match(
+ field.condition
+ ) &&
+ !(mmpath in invalid) &&
+ (aconfType.isCollection(
+ field
+ ) || data.get(
+ field.name
+ ) == null)) {
+ invalid[mmpath] = [
+ null
+ ];
+ }
+ });
+
+ else if (aconfType.isCollection(meta) &&
+ data.meta.required)
+ invalid[mpath] = [mpath];
+ }
+
+ resolve();
+ }, reject);
+
+ else resolve();
+
+ }, reject);
}
- def.resolve(txnValid);
- }).fail(function() { def.reject(); });
+ prevTask.then(newTask, newTask);
- return def;
- }
+ return def.promise;
+ };
- data.delete = function(name) {
- return exclusive(function() { return _delete(name); });
- };
+ data.add = function(name) {
+ return data.set(name, name);
+ };
- data.move = function(oldIndex, newIndex) {
- if (oldIndex == newIndex)
- return $.Deferred().resolve(isValid());
+ function adjustListIndex(oldIndex, newIndex) {
+ var opath = aconfPath.join(path, oldIndex);
+ var npath = aconfPath.join(path, newIndex);
+ _.each(
+ [changed, invalid],
+ function(map) {
+ _.each(
+ _.keys(map),
+ function(p) {
+ if (aconfPath.isSubordinate(
+ p, opath
+ )) {
+ map[npath + p.substring(
+ opath.length
+ )] = map[p];
+ delete map[p];
+ }
+ });
+ }
+ );
+ }
- var value = data.get(oldIndex);
- var length = data.data.length;
+ function adjustListIndices(start, end) {
+ var offset = start < end ? 1 : -1;
+ for (var i = start; i != end; i += offset)
+ adjustListIndex(i + offset, i);
+ }
- return exclusive(function() {
- var def = $.Deferred();
- function reject() { def.reject(); }
+ function _delete(name) {
+ return $q(function(resolve, reject) {
+ var length = data.data.length;
- if (oldIndex > newIndex) oldIndex++;
- else newIndex++;
+ data.set(name).then(function(txnValid) {
+ if (aconfType.isTreeNode(data.meta) &&
+ data.meta.type != "set") {
- objRequest(path, {type: "POST", data: {
- index: newIndex,
- data: type.isTreeNode(data.meta.members) ?
- pth.join(path, oldIndex) : value
- }}).done(function() {
+ var mpath = aconfPath.join(path, name);
+ var key = index(name);
- data.data.splice(newIndex - 1, 0, value);
+ delete changed[mpath];
+ changed[path] = path;
- adjustListIndices(length + 1, newIndex);
- adjustListIndex(oldIndex, newIndex);
+ if (data.meta.type == "list") {
+ if (aconfType.isTreeNode(
+ data.meta.members
+ )) {
+ data.data[key] = mpath;
+ data.data.pop();
+ }
+ else data.data.splice(name - 1, 1);
- _delete(oldIndex)
- .done(function(txnValid) {
- def.resolve(txnValid);
- })
- .fail(reject);
+ adjustListIndices(name, length);
+ }
+ else delete data.data[key];
+ }
- }).fail(reject);
+ resolve(txnValid);
+ }, reject);
+ });
+ }
- return def;
- });
- };
+ data.delete = function(name) {
+ return exclusive(function() {
+ return _delete(name);
+ });
+ };
+
+ data.move = function(oldIndex, newIndex) {
+ if (oldIndex == newIndex)
+ return $q.when(isValid());
+
+ var value = data.get(oldIndex);
+ var length = data.data.length;
+
+ return exclusive(function() {
+ return $q(function(resolve, reject) {
+ if (oldIndex > newIndex) oldIndex++;
+ else newIndex++;
+
+ objRequest(path, {method: "POST", data: {
+ index: newIndex,
+ data: aconfType.isTreeNode(
+ data.meta.members
+ ) ? aconfPath.join(
+ path, oldIndex
+ ) : value
+ }}).then(function() {
+
+ if (aconfType.isTreeNode(
+ data.meta.members
+ ))
+ data.data.push(value);
+ else data.data.splice(
+ newIndex - 1, 0, value
+ );
+
+ adjustListIndices(length + 1, newIndex);
+ adjustListIndex(oldIndex, newIndex);
+
+ _delete(oldIndex).then(resolve, reject);
+
+ }, reject);
+ });
+ });
+ };
- data.invoke = function(name) {
- return objRequest(pth.join(path, name), {type: "POST"});
- };
+ data.invoke = function(name) {
+ return objRequest(
+ aconfPath.join(path, name), {method: "POST"}
+ );
+ };
- def.resolve(data);
- }).fail(function() { def.reject(); });
+ resolve(data);
+ }).error(reject);
+ });
+ };
- return def;
- }
+ txnMgr.isPristine = function() { return !(_.size(changed)); };
txnMgr.start = function() {
- var def = $.Deferred();
- if (txn && isValid() && !(_.size(changed))) txnMgr.abort();
-
- if (txn) def.resolve();
+ return $q(function(resolve, reject) {
+ if (txn && isValid() && txnMgr.isPristine()) txnMgr.abort();
- else request("/transaction", {type: "POST"})
- .done(function(data, status, xhr) {
- txn = xhr.getResponseHeader("X-AConf-Transaction-ID");
- def.resolve();
- })
- .fail(function() { def.reject(); });
+ if (txn) resolve();
- return def;
+ else request("/transaction", {method: "POST"})
+ .success(function(data, status, headers) {
+ txn = headers("X-AConf-Transaction-ID");
+ resolve();
+ }).error(reject);
+ });
};
txnMgr.commit = function() {
- var def = $.Deferred();
- function reject(xhr) { def.reject(xhr); }
- request("/transaction", {type: "PUT"}).done(function() {
- reset();
- if (saveRequired)
- request("/save", {type: "POST"}).done(function() {
- def.resolve();
- }).fail(reject);
- else def.resolve();
- }).fail(reject);
- return def;
+ return $q(function(resolve, reject) {
+ request("/transaction", {method: "PUT"}).then(function() {
+ reset();
+ if (saveRequired)
+ request("/save", {method: "POST"})
+ .then(resolve, reject);
+ else resolve();
+ }, reject);
+ });
};
txnMgr.logout = function() {
- return request("/login", {type: "DELETE"});
+ return request("/login", {method: "DELETE"});
};
return txnMgr;
- }
+ };
}
);