(function(window) { // retry infinite is hard coded atm var Gitana = window.Gitana; var OBJECTS_PER_REQUEST = 100; var STATUS_POLL_INTERVAL = 2 * 1000; // 2 seconds var TRANSACTION_STATUS_FINISHED = 'FINISHED'; var chunk = function(array, size) { var chunks = []; for (var i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; }; /** * Given a transaction add all of the tasks and then commit. */ var commit = function(transaction) { var allObjects = transaction.objects; var requests = []; var q = new Gitana.Queue(); // split up into chunks of objects var chunks = chunk(allObjects, OBJECTS_PER_REQUEST); for (var i = 0; i < chunks.length; i++) { var objects = chunks[i]; q.add(function(index, objects, transaction) { return function() { var def = new Gitana.Defer(); //console.log("CHUNK " + index + ", size: " + objects.length); // TRANSACTION_TEST if (Gitana.Transaction.testMode) { console.log("POST /transactions/" + transaction.getId() + "/add"); def.resolve(objects); } else { var payload = { "objects": objects }; transaction.getDriver().gitanaPost('/transactions/' + transaction.getId() + '/add', {}, payload, function(res) { def.resolve(objects); }, function(err) { // when things fail, we don't retry, to fail the entire transaction before committing def.reject(err); }); } return def.promise; }; }(i, objects, transaction)); } var def2 = new Gitana.Defer(); q.go().then(function(reses) { // TRANSACTION_TEST if (Gitana.Transaction.testMode) { console.log("POST /transaction/" + transaction.getId() + "/commit"); def2.resolve(); } else { transaction.getDriver().gitanaPost('/transactions/' + transaction.getId() + '/commit', {}, {}, function(res) { def2.resolve(res); }, function(err) { def2.reject(err); }); } }, def2.reject); return def2.promise; }; /** * Tell the server to cancel this transaction */ var cancel = function(transaction) { var def = new Gitana.Defer(); // TRANSACTION_TEST if (Gitana.Transaction.testMode) { console.log("DELETE /transactions/" + transaction.getId()); def.resolve(); } else { transaction.getDriver().gitanaDelete('/transactions/' + transaction.getId(), {}, function(res) { def.resolve(res); }, function(err) { def.reject(err); }); } return def.promise; }; /** * Add an object to a transaction */ var addObject = function(transaction, object) { if (object.data && Gitana.isString(object.data)) { object.data = { "_doc": object.data }; } transaction.objects.push(object); }; /** * Transaction constructor * * Options doesn't really do anything ATM * * transaction.promise is a promise that gets resolved/rejected once the http * request completes which creates the transaction on the server side. */ var Transaction = function(container, options) { // object queue this.objects = []; this.getContainer = function() { return container; }; if (container) { this['for'](container); } }; Transaction.prototype['for'] = function(container) { if (this.promise) { throw new Error('Container for transaction has already been set'); } var self = this; var def = new Gitana.Defer(); this.promise = def.promise; this.getDriver().gitanaPost(this.getUri(), {}, {}, function(res) { self.getId = function() { return res._doc; }; self.getContainerReference = function() { return res['container-reference']; }; def.resolve(self); }, function(err) { def.reject(err); }); }; /** * Cloud CMS */ /** * Return the driver instance of this transaction's container */ Transaction.prototype.getDriver = function() { return this.getContainer().getDriver(); }; /** * Returns the uri used to create this transaction */ Transaction.prototype.getUri = function() { return '/transactions?reference=' + this.getContainer().ref(); }; /** * Transaction API */ /** * Add a write action to the transaction */ Transaction.prototype.write = function(data) { if (typeof this.promise === 'undefined') { throw new Error('You must set the transaction\'s container with the "for" method before calling this method' ); } this.promise.then(function(self) { if (Gitana.isArray(data)) { for (var i = 0; i < data.length; i++) { var d = data[i]; addObject(self, { header: { type: 'node', operation: 'write' }, data: d }); } } else { addObject(self, { header: { type: 'node', operation: 'write' }, data: data }); } }); return this; }; Transaction.prototype.create = Transaction.prototype.update = Transaction.prototype.write; /** * Add a delete action to the transaction */ Transaction.prototype.del = function(data) { if (typeof this.promise === 'undefined') { throw new Error('You must set the transaction\'s container with the "for" method before calling this method' ); } if (typeof(data) === "string") { data = { "_doc": data }; } this.promise.then(function(self) { if (Gitana.isArray(data)) { for (var i = 0; i < data.length; i++) { var d = data[i]; addObject(self, { header: { type: 'node', operation: 'delete' }, data: d }); } } else { addObject(self, { header: { type: 'node', operation: 'delete' }, data: data }); } }); return this; }; /** * Commit this transaction */ Transaction.prototype.commit = function() { var def = new Gitana.Defer(); var self = this; if (typeof this.promise === 'undefined') { throw new Error('You must set the transaction\'s container with the "for" method before calling this method' ); } this.promise.then(function(self) { commit(self).then(function() { (function pollLoop() { // TRANSACTION_TEST if (Transaction.testMode) { console.log("GET /transactions/" + self.getId() + "/status"); def.resolve(); } else { self.getDriver().gitanaGet('/transactions/' + self.getId() + '/status', {}, {}, function(res) { if (res.status === TRANSACTION_STATUS_FINISHED) { def.resolve(res.results); } else { setTimeout(pollLoop, STATUS_POLL_INTERVAL); } }, function(err) { def.reject(err); }); } })(); }, def.reject); }); return def.promise; }; /** * Cancel this transaction */ Transaction.prototype.cancel = function() { var def = new Gitana.Defer(); if (typeof this.promise === 'undefined') { throw new Error('You must set the transaction\'s container with the "for" method before calling this method' ); } this.promise.then(function(self) { cancel(self).then(def.resolve, def.reject); }); return def.promise; }; /** * Exports */ Gitana.Transaction = Transaction; Gitana.TypedIDConstants.TYPE_TRANSACTION = 'Transaction'; Gitana.ObjectFactory.prototype.transaction = function(container, object) { return this.create(Gitana.Transaction, container, object); }; var createTransaction = function(container) { return new Transaction(container, { }); }; Gitana.transactions = function() { var r = {}; r.create = function(container) { return container ? createTransaction(container) : { "for": createTransaction }; }; return r; }; })(window);