diff --git a/src/jio.storage/conflictmanagerstorage.js b/src/jio.storage/conflictmanagerstorage.js deleted file mode 100644 index e8f1ef226a57d2244ad989e543217ffdb7f90e78..0000000000000000000000000000000000000000 --- a/src/jio.storage/conflictmanagerstorage.js +++ /dev/null @@ -1,1085 +0,0 @@ -/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ -/*global jIO: true, hex_sha256: true, setTimeout: true */ -jIO.addStorageType('conflictmanager', function (spec, my) { - var that, priv, storage_exists, local_namespace, empty_fun, - super_serialized; - - spec = spec || {}; - that = my.basicStorage(spec, my); - priv = {}; - storage_exists = (spec.storage ? true : false); - priv.sub_storage_spec = spec.storage || { - type: 'base' - }; - priv.sub_storage_string = JSON.stringify(priv.sub_storage_spec); - - local_namespace = 'jio/conflictmanager/' + priv.sub_storage_string + '/'; - - empty_fun = function () {}; - - super_serialized = that.serialized; - that.serialized = function () { - var o = super_serialized(); - o.storage = priv.sub_storage_spec; - return o; - }; - - that.validateState = function () { - if (storage_exists) { - return ''; - } - return 'Need at least one parameter: "storage".'; - }; - - priv.getDistantMetadata = function (command, path, success, error) { - var cloned_option = command.cloneOption(); - cloned_option.metadata_only = false; - that.addJob('get', priv.sub_storage_spec, path, cloned_option, - success, error); - }; - - priv.saveMetadataToDistant = function (command, path, content, success, - error) { - that.addJob('put', priv.sub_storage_spec, { - _id: path, - content: JSON.stringify(content) - }, - command.cloneOption(), success, error); - }; - - priv.saveNewRevision = function (command, path, content, success, error) { - that.addJob('post', priv.sub_storage_spec, { - _id: path, - content: content - }, - command.cloneOption(), success, error); - }; - - priv.loadRevision = function (command, path, success, error) { - that.addJob('get', priv.sub_storage_spec, path, command.cloneOption(), - success, error); - }; - - priv.deleteAFile = function (command, path, success, error) { - that.addJob('remove', priv.sub_storage_spec, { - _id: path - }, - command.cloneOption(), success, error); - }; - - priv.chooseARevision = function (metadata) { - var tmp_last_modified = 0, - ret_rev = '', - rev; - for (rev in metadata) { - if (metadata.hasOwnProperty(rev)) { - if (tmp_last_modified < metadata[rev]._last_modified) { - tmp_last_modified = metadata[rev]._last_modified; - ret_rev = rev; - } - } - } - return ret_rev; - }; - - priv._revs = function (metadata, revision) { - if (!(metadata && revision)) { - return null; - } - if (metadata[revision]) { - return { - start: metadata[revision]._revisions.length, - ids: metadata[revision]._revisions - }; - } - return null; - }; - - priv._revs_info = function (metadata) { - if (!metadata) { - return null; - } - var k, l = []; - for (k in metadata) { - if (metadata.hasOwnProperty(k)) { - l.push({ - rev: k, - status: ( - metadata[k] ? ( - metadata[k]._deleted ? 'deleted' : 'available' - ) : 'missing' - ) - }); - } - } - return l; - }; - - priv.solveConflict = function (doc, option, param) { - var o = {}, am = priv.newAsyncModule(), - - command = param.command, - metadata_file_path = param.docid + '.metadata', - current_revision = '', - current_revision_file_path = '', - metadata_file_content = null, - on_conflict = false, - conflict_object = { - total_rows: 0, - rows: [] - }, - on_remove = param._deleted, - previous_revision = param.previous_revision, - previous_revision_content_object = null, - now = new Date(), - failerror; - - o.getDistantMetadata = function () { - priv.getDistantMetadata( - command, - metadata_file_path, - function (result) { - var previous_revision_number = - parseInt(previous_revision.split('-')[0], 10); - metadata_file_content = JSON.parse(result.content); - // set current revision - // jslint: removed '' in hex_sha256(''... - current_revision = (previous_revision_number + 1) + '-' + - hex_sha256(doc.content + previous_revision + - JSON.stringify(metadata_file_content)); - current_revision_file_path = param.docid + '.' + current_revision; - previous_revision_content_object = metadata_file_content[ - previous_revision - ] || {}; - if (!on_remove) { - am.wait(o, 'saveMetadataOnDistant', 1); - am.call(o, 'saveNewRevision'); - } - am.call(o, 'previousUpdateMetadata'); - }, - function (error) { - am.call(o, 'error', [error]); - } - ); - }; - o.saveNewRevision = function () { - priv.saveNewRevision( - command, - current_revision_file_path, - doc.content, - function () { - am.call(o, 'saveMetadataOnDistant'); - }, - function (error) { - am.call(o, 'error', [error]); - } - ); - }; - o.previousUpdateMetadata = function () { - var i; - for (i = 0; i < param.key.length; i += 1) { - delete metadata_file_content[param.key[i]]; - } - am.call(o, 'checkForConflicts'); - }; - o.checkForConflicts = function () { - var rev; - for (rev in metadata_file_content) { - if (metadata_file_content.hasOwnProperty(rev)) { - on_conflict = true; - failerror = { - status: 409, - error: 'conflict', - statusText: 'Conflict', - reason: 'document update conflict', - message: 'There is one or more conflicts' - }; - break; - } - } - am.call(o, 'updateMetadata'); - }; - o.updateMetadata = function () { - var revision_history, id = ''; - id = current_revision.split('-'); - id.shift(); - id = id.join('-'); - revision_history = previous_revision_content_object._revisions; - revision_history.unshift(id); - metadata_file_content[current_revision] = { - _creation_date: previous_revision_content_object._creation_date || - now.getTime(), - _last_modified: now.getTime(), - _revisions: revision_history, - _conflict: on_conflict, - _deleted: on_remove - }; - if (on_conflict) { - conflict_object = priv.createConflictObject( - command, - metadata_file_content, - current_revision - ); - } - am.call(o, 'saveMetadataOnDistant'); - }; - o.saveMetadataOnDistant = function () { - priv.saveMetadataToDistant( - command, - metadata_file_path, - metadata_file_content, - function () { - am.call(o, 'deleteAllConflictingRevision'); - if (on_conflict) { - am.call(o, 'error'); - } else { - am.call(o, 'success'); - } - }, - function (error) { - am.call(o, 'error', [error]); - } - ); - }; - o.deleteAllConflictingRevision = function () { - var i; - for (i = 0; i < param.key.length; i += 1) { - priv.deleteAFile( - command, - param.docid + '.' + param.key[i], - empty_fun, - empty_fun - ); - } - }; - o.success = function () { - var a = { - ok: true, - id: param.docid, - rev: current_revision - }; - am.neverCall(o, 'error'); - am.neverCall(o, 'success'); - if (option.revs) { - a.revisions = priv._revs( - metadata_file_content, - current_revision - ); - } - if (option.revs_info) { - a.revs_info = priv._revs_info(metadata_file_content); - } - if (option.conflicts) { - a.conflicts = conflict_object; - } - param.success(a); - }; - o.error = function (error) { - var err = error || failerror || { - status: 0, - statusText: 'Unknown', - error: 'unknown_error', - message: 'Unknown error.', - reason: 'unknown error' - }; - if (current_revision) { - err.rev = current_revision; - } - if (option.revs) { - err.revisions = priv._revs( - metadata_file_content, - current_revision - ); - } - if (option.revs_info) { - err.revs_info = priv._revs_info( - metadata_file_content - ); - } - if (option.conflicts) { - err.conflicts = conflict_object; - } - am.neverCall(o, 'error'); - am.neverCall(o, 'success'); - param.error(err); - }; - am.call(o, 'getDistantMetadata'); - }; - - priv.createConflictObject = function (command, metadata, revision) { - return { - total_rows: 1, - rows: [priv.createConflictRow( - command, - command.getDocId(), - metadata, - revision - )] - }; - }; - - priv.getParam = function (list) { - var param = {}, i = 0; - if (typeof list[i] === 'string') { - param.content = list[i]; - i += 1; - } - if (typeof list[i] === 'object') { - param.options = list[i]; - i += 1; - } else { - param.options = {}; - } - param.callback = function () {}; - param.success = function (val) { - param.callback(undefined, val); - }; - param.error = function (err) { - param.callback(err, undefined); - }; - if (typeof list[i] === 'function') { - if (typeof list[i + 1] === 'function') { - param.success = list[i]; - param.error = list[i + 1]; - } else { - param.callback = list[i]; - } - } - return param; - }; - - priv.createConflictRow = function (command, docid, metadata, revision) { - var row = { - id: docid, - key: [], - value: { - // jslint: removed params /* content, option, success, error */ - _solveConflict: function () { - var param = {}, got = priv.getParam(arguments); - if (got.content === undefined) { - param._deleted = true; - } else { - param._deleted = false; - } - param.success = got.success; - param.error = got.error; - param.previous_revision = revision; - param.docid = docid; - param.key = row.key; - param.command = command.clone(); - return priv.solveConflict({ - _id: docid, - content: got.content, - _rev: revision - }, - got.options, param); - } - } - }, k; - for (k in metadata) { - if (metadata.hasOwnProperty(k)) { - row.key.push(k); - } - } - return row; - }; - - priv.newAsyncModule = function () { - var async = {}; - async.call = function (obj, function_name, arglist) { - obj._wait = obj._wait || {}; - if (obj._wait[function_name]) { - obj._wait[function_name] -= 1; - return empty_fun; - } - // ok if undef or 0 - arglist = arglist || []; - setTimeout(function () { - obj[function_name].apply(obj[function_name], arglist); - }); - }; - async.neverCall = function (obj, function_name) { - obj._wait = obj._wait || {}; - obj._wait[function_name] = -1; - }; - async.wait = function (obj, function_name, times) { - obj._wait = obj._wait || {}; - obj._wait[function_name] = times; - }; - async.end = function () { - async.call = empty_fun; - }; - return async; - }; - - that.post = function (command) { - that.put(command); - }; - - /** - * Save a document and can manage conflicts. - * @method put - */ - that.put = function (command) { - var o = {}, am = priv.newAsyncModule(), - - metadata_file_path = command.getDocId() + '.metadata', - current_revision = '', - current_revision_file_path = '', - metadata_file_content = null, - on_conflict = false, - conflict_object = { - total_rows: 0, - rows: [] - }, - previous_revision = command.getDocInfo('_rev') || '0', - previous_revision_file_path = command.getDocId() + '.' + - previous_revision, - now = new Date(), - failerror; - - o.getDistantMetadata = function () { - priv.getDistantMetadata( - command, - metadata_file_path, - function (result) { - var previous_revision_number = - parseInt(previous_revision.split('-')[0], 10); - metadata_file_content = JSON.parse(result.content); - // set current revision - current_revision = (previous_revision_number + 1) + '-' + - // jslint: removed hex_sha256(''+... - hex_sha256(command.getDocContent() + previous_revision + - JSON.stringify(metadata_file_content)); - current_revision_file_path = command.getDocId() + '.' + - current_revision; - am.wait(o, 'saveMetadataOnDistant', 1); - am.call(o, 'saveNewRevision'); - am.call(o, 'checkForConflicts'); - }, - function (error) { - if (error.status === 404) { - current_revision = '1-' + hex_sha256(command.getDocContent()); - current_revision_file_path = command.getDocId() + '.' + - current_revision; - am.wait(o, 'saveMetadataOnDistant', 1); - am.call(o, 'saveNewRevision'); - am.call(o, 'createMetadata'); - } else { - am.call(o, 'error', [error]); - } - } - ); - }; - o.saveNewRevision = function () { - priv.saveNewRevision( - command, - current_revision_file_path, - command.getDocContent(), - function () { - am.call(o, 'saveMetadataOnDistant'); - }, - function (error) { - am.call(o, 'error', [error]); - } - ); - }; - o.checkForConflicts = function () { - var rev; - for (rev in metadata_file_content) { - if (metadata_file_content.hasOwnProperty(rev) && - rev !== previous_revision) { - on_conflict = true; - failerror = { - status: 409, - error: 'conflict', - statusText: 'Conflict', - reason: 'document update conflict', - message: 'Document update conflict.' - }; - break; - } - } - am.call(o, 'updateMetadata'); - }; - o.createMetadata = function () { - var id = current_revision; - id = id.split('-'); - id.shift(); - id = id.join('-'); - metadata_file_content = {}; - metadata_file_content[current_revision] = { - _creation_date: now.getTime(), - _last_modified: now.getTime(), - _revisions: [id], - _conflict: false, - _deleted: false - }; - am.call(o, 'saveMetadataOnDistant'); - }; - o.updateMetadata = function () { - var previous_creation_date, revision_history = [], - id = ''; - if (metadata_file_content[previous_revision]) { - previous_creation_date = metadata_file_content[ - previous_revision - ]._creation_date; - revision_history = metadata_file_content[ - previous_revision - ]._revisions; - delete metadata_file_content[previous_revision]; - } - id = current_revision.split('-'); - id.shift(); - id = id.join('-'); - revision_history.unshift(id); - metadata_file_content[current_revision] = { - _creation_date: previous_creation_date || now.getTime(), - _last_modified: now.getTime(), - _revisions: revision_history, - _conflict: on_conflict, - _deleted: false - }; - if (on_conflict) { - conflict_object = priv.createConflictObject( - command, - metadata_file_content, - current_revision - ); - } - am.call(o, 'saveMetadataOnDistant'); - }; - o.saveMetadataOnDistant = function () { - priv.saveMetadataToDistant( - command, - metadata_file_path, - metadata_file_content, - function () { - am.call(o, 'deletePreviousRevision'); - if (on_conflict) { - am.call(o, 'error'); - } else { - am.call(o, 'success'); - } - }, - function (error) { - am.call(o, 'error', [error]); - } - ); - }; - o.deletePreviousRevision = function () { - // jslint: removed /*&& !on_conflict*/ - if (previous_revision !== '0') { - priv.deleteAFile( - command, - previous_revision_file_path, - empty_fun, - empty_fun - ); - } - }; - o.success = function () { - var a = { - ok: true, - id: command.getDocId(), - rev: current_revision - }; - am.neverCall(o, 'error'); - am.neverCall(o, 'success'); - if (command.getOption('revs')) { - a.revisions = priv._revs( - metadata_file_content, - current_revision - ); - } - if (command.getOption('revs_info')) { - a.revs_info = priv._revs_info(metadata_file_content); - } - if (command.getOption('conflicts')) { - a.conflicts = conflict_object; - } - that.success(a); - }; - o.error = function (error) { - var err = error || failerror || { - status: 0, - statusText: 'Unknown', - error: 'unknown_error', - message: 'Unknown error.', - reason: 'unknown error' - }; - if (current_revision) { - err.rev = current_revision; - } - if (command.getOption('revs')) { - err.revisions = priv._revs( - metadata_file_content, - current_revision - ); - } - if (command.getOption('revs_info')) { - err.revs_info = priv._revs_info(metadata_file_content); - } - if (command.getOption('conflicts')) { - err.conflicts = conflict_object; - } - am.neverCall(o, 'error'); - am.neverCall(o, 'success'); - that.error(err); - }; - am.call(o, 'getDistantMetadata'); - }; // end put - - /** - * Load a document from several storages, and send the first retreived - * document. - * @method get - */ - that.get = function (command) { - var o = {}, am = priv.newAsyncModule(), - metadata_file_path = command.getDocId() + '.metadata', - current_revision = command.getOption('rev') || '', - metadata_file_content = null, - metadata_only = command.getOption('metadata_only'), - on_conflict = false, - conflict_object = { - total_rows: 0, - rows: [] - }, - doc = { - _id: command.getDocId() - }, - call404 = function (message) { - am.call(o, 'error', [{ - status: 404, - statusText: 'Not Found', - error: 'not_found', - message: message, - reason: message - }]); - }; - - o.getDistantMetadata = function () { - priv.getDistantMetadata( - command, - metadata_file_path, - function (result) { - metadata_file_content = JSON.parse(result.content); - if (!metadata_only) { - am.wait(o, 'success', 1); - } - am.call(o, 'affectMetadata'); - am.call(o, 'checkForConflicts'); - }, - function (error) { - am.call(o, 'error', [error]); - } - ); - }; - o.affectMetadata = function () { - if (current_revision) { - if (!metadata_file_content[current_revision]) { - return call404('Document revision does not exists.'); - } - } else { - current_revision = priv.chooseARevision(metadata_file_content); - } - doc._last_modified = - metadata_file_content[current_revision]._last_modified; - doc._creation_date = - metadata_file_content[current_revision]._creation_date; - doc._rev = current_revision; - if (metadata_only) { - am.call(o, 'success'); - } else { - am.call(o, 'loadRevision'); - } - }; - o.loadRevision = function () { - if (!current_revision || - metadata_file_content[current_revision]._deleted) { - return call404('Document has been removed.'); - } - priv.loadRevision( - command, - doc._id + '.' + current_revision, - function (result) { - doc.content = result.content; - am.call(o, 'success'); - }, - function (error) { - am.call(o, 'error', [error]); - } - ); - }; - o.checkForConflicts = function () { - if (metadata_file_content[current_revision]._conflict) { - on_conflict = true; - conflict_object = priv.createConflictObject( - command, - metadata_file_content, - current_revision - ); - } - am.call(o, 'success'); - }; - o.success = function () { - am.neverCall(o, 'error'); - am.neverCall(o, 'success'); - if (command.getOption('revs')) { - doc._revisions = priv._revs( - metadata_file_content, - current_revision - ); - } - if (command.getOption('revs_info')) { - doc._revs_info = priv._revs_info(metadata_file_content); - } - if (command.getOption('conflicts')) { - doc._conflicts = conflict_object; - } - that.success(doc); - }; - o.error = function (error) { - var err = error || { - status: 0, - statusText: 'Unknown', - message: 'Unknown error.' - }; - if (command.getOption('revs')) { - err._revisions = priv._revs( - metadata_file_content, - current_revision - ); - } - if (command.getOption('revs_info')) { - err._revs_info = priv._revs_info(metadata_file_content); - } - if (command.getOption('conflicts')) { - err._conflicts = conflict_object; - } - am.neverCall(o, 'error'); - am.neverCall(o, 'success'); - that.error(err); - }; - am.call(o, 'getDistantMetadata'); - }; - - /** - * Get a document list from several storages, and returns the first - * retreived document list. - * @method allDocs - */ - that.allDocs = function (command) { - var o = {}, am = priv.newAsyncModule(), - metadata_only = command.getOption('metadata_only'), - result_list = [], - conflict_object = { - total_rows: 0, - rows: [] - }, - success_count = 0, - success_max = 0; - o.retreiveList = function () { - var cloned_option = command.cloneOption(), - success = function (result) { - am.call(o, 'filterTheList', [result]); - }, - error = function (error) { - am.call(o, 'error', [error]); - }; - cloned_option.metadata_only = true; - that.addJob('allDocs', priv.sub_storage_spec, null, cloned_option, - success, error - ); - }; - o.filterTheList = function (result) { - var i, splitname; - success_max += 1; - for (i = 0; i < result.total_rows; i += 1) { - splitname = result.rows[i].id.split('.') || []; - if (splitname.length > 0 && splitname[splitname.length - 1] === - 'metadata') { - success_max += 1; - splitname.length -= 1; - am.call(o, 'loadMetadataFile', [splitname.join('.')]); - } - } - am.call(o, 'success'); - }; - o.loadMetadataFile = function (path) { - priv.getDistantMetadata( - command, - path + '.metadata', - function (data) { - data = JSON.parse(data.content); - var revision = priv.chooseARevision(data); - if (!data[revision]._deleted) { - am.call(o, 'loadFile', [path, revision, data]); - } else { - am.call(o, 'success'); - } - }, - function (error) { - am.call(o, 'error', [error]); - } - ); - }; - o.loadFile = function (path, revision, data) { - var doc = { - id: path, - key: path, - value: { - _last_modified: data[revision]._last_modified, - _creation_date: data[revision]._creation_date, - _rev: revision - } - }; - if (command.getOption('revs')) { - doc.value._revisions = priv._revs(data, revision); - } - if (command.getOption('revs_info')) { - doc.value._revs_info = priv._revs_info(data, revision); - } - if (command.getOption('conflicts')) { - if (data[revision]._conflict) { - conflict_object.total_rows += 1; - conflict_object.rows.push( - priv.createConflictRow(command, path, data, revision) - ); - } - } - if (!metadata_only) { - priv.loadRevision( - command, - path + '.' + revision, - function (data) { - doc.content = data.content; - result_list.push(doc); - am.call(o, 'success'); - }, - function (error) { - am.call(o, 'error', [error]); - } - ); - } else { - result_list.push(doc); - am.call(o, 'success'); - } - }; - o.success = function () { - var obj; - success_count += 1; - if (success_count >= success_max) { - am.end(); - obj = { - total_rows: result_list.length, - rows: result_list - }; - if (command.getOption('conflicts')) { - obj.conflicts = conflict_object; - } - that.success(obj); - } - }; - o.error = function (error) { - am.end(); - that.error(error); - }; - am.call(o, 'retreiveList'); - }; // end allDocs - - /** - * Remove a document from several storages. - * @method remove - */ - that.remove = function (command) { - var o = {}, am = priv.newAsyncModule(), - - metadata_file_path = command.getDocId() + '.metadata', - current_revision = '', - current_revision_file_path = '', - metadata_file_content = null, - on_conflict = false, - conflict_object = { - total_rows: 0, - rows: [] - }, - previous_revision = command.getOption('rev') || '0', - previous_revision_file_path = command.getDocId() + '.' + - previous_revision, - now = new Date(), - failerror; - - o.getDistantMetadata = function () { - priv.getDistantMetadata( - command, - metadata_file_path, - function (result) { - metadata_file_content = JSON.parse(result.content); - if (previous_revision === 'last') { - previous_revision = priv.chooseARevision(metadata_file_content); - previous_revision_file_path = command.getDocId() + '.' + - previous_revision; - } - var previous_revision_number = - parseInt(previous_revision.split('-')[0], 10) || 0; - // set current revision - current_revision = (previous_revision_number + 1) + '-' + - //jslint: removed hex_sha256(''... - hex_sha256(previous_revision + - JSON.stringify(metadata_file_content) - ); - current_revision_file_path = command.getDocId() + '.' + - current_revision; - am.call(o, 'checkForConflicts'); - }, - function (error) { - if (error.status === 404) { - am.call(o, 'error', [{ - status: 404, - statusText: 'Not Found', - error: 'not_found', - reason: 'missing', - message: 'Document not found.' - }]); - } else { - am.call(o, 'error', [error]); - } - } - ); - }; - o.checkForConflicts = function () { - var rev; - for (rev in metadata_file_content) { - if (metadata_file_content.hasOwnProperty(rev) && - rev !== previous_revision) { - on_conflict = true; - failerror = { - status: 409, - error: 'conflict', - statusText: 'Conflict', - reason: 'document update conflict', - message: 'There is one or more conflicts' - }; - break; - } - } - am.call(o, 'updateMetadata'); - }; - o.updateMetadata = function () { - var previous_creation_date, revision_history = [], - id = ''; - if (metadata_file_content[previous_revision]) { - previous_creation_date = metadata_file_content[ - previous_revision - ]._creation_date; - revision_history = metadata_file_content[ - previous_revision - ]._revisions; - delete metadata_file_content[previous_revision]; - } - id = current_revision; - id = id.split('-'); - id.shift(); - id = id.join('-'); - revision_history.unshift(id); - metadata_file_content[current_revision] = { - _creation_date: previous_creation_date || now.getTime(), - _last_modified: now.getTime(), - _revisions: revision_history, - _conflict: on_conflict, - _deleted: true - }; - if (on_conflict) { - conflict_object = priv.createConflictObject( - command, - metadata_file_content, - current_revision - ); - } - am.call(o, 'saveMetadataOnDistant'); - }; - o.saveMetadataOnDistant = function () { - priv.saveMetadataToDistant( - command, - metadata_file_path, - metadata_file_content, - function () { - am.call(o, 'deletePreviousRevision'); - if (on_conflict) { - am.call(o, 'error'); - } else { - am.call(o, 'success'); - } - }, - function (error) { - am.call(o, 'error', [error]); - } - ); - }; - o.deletePreviousRevision = function () { - // jslint: removed /*&& !on_conflict*/ - if (previous_revision !== '0') { - priv.deleteAFile( - command, - previous_revision_file_path, - empty_fun, - empty_fun - ); - } - }; - o.success = function (revision) { - var a = { - ok: true, - id: command.getDocId(), - rev: revision || current_revision - }; - am.neverCall(o, 'error'); - am.neverCall(o, 'success'); - if (command.getOption('revs')) { - a.revisions = priv._revs( - metadata_file_content, - current_revision - ); - } - if (command.getOption('revs_info')) { - a.revs_info = priv._revs_info(metadata_file_content); - } - if (command.getOption('conflicts')) { - a.conflicts = conflict_object; - } - that.success(a); - }; - o.error = function (error) { - var err = error || failerror || { - status: 0, - statusText: 'Unknown', - error: 'unknown_error', - message: 'Unknown error.', - reason: 'unknown error' - }; - if (current_revision) { - err.rev = current_revision; - } - if (command.getOption('revs')) { - err.revisions = priv._revs( - metadata_file_content, - current_revision - ); - } - if (command.getOption('revs_info')) { - err.revs_info = priv._revs_info(metadata_file_content); - } - if (command.getOption('conflicts')) { - err.conflicts = conflict_object; - } - am.neverCall(o, 'error'); - am.neverCall(o, 'success'); - that.error(err); - }; - am.call(o, 'getDistantMetadata'); - }; // end remove - - return that; -}); diff --git a/src/jio.storage/cryptstorage.js b/src/jio.storage/cryptstorage.js deleted file mode 100644 index d0ca3895f46d4ec462d03dffc72c7841c1a7b9b6..0000000000000000000000000000000000000000 --- a/src/jio.storage/cryptstorage.js +++ /dev/null @@ -1,277 +0,0 @@ -/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ -/*global jIO: true, sjcl: true, $: true, setTimeout: true */ -jIO.addStorageType('crypt', function (spec, my) { - spec = spec || {}; - var that = my.basicStorage(spec, my), - priv = {}, - is_valid_storage = (spec.storage ? true : false), - super_serialized = that.serialized; - - priv.username = spec.username || ''; - priv.password = spec.password || ''; - priv.sub_storage_spec = spec.storage || { - type: 'base' - }; - priv.sub_storage_string = JSON.stringify(priv.sub_storage_string); - - that.serialized = function () { - var o = super_serialized(); - o.username = priv.username; - o.password = priv.password; // TODO : unsecured !!! - o.storage = priv.sub_storage_string; - return o; - }; - - that.validateState = function () { - if (priv.username && is_valid_storage) { - return ''; - } - return 'Need at least two parameters: "username" and "storage".'; - }; - - // TODO : IT IS NOT SECURE AT ALL! - // WE MUST REWORK CRYPTED STORAGE! - priv.encrypt_param_object = { - "iv": "kaprWwY/Ucr7pumXoTHbpA", - "v": 1, - "iter": 1000, - "ks": 256, - "ts": 128, - "mode": "ccm", - "adata": "", - "cipher": "aes", - "salt": "K4bmZG9d704" - }; - priv.decrypt_param_object = { - "iv": "kaprWwY/Ucr7pumXoTHbpA", - "ks": 256, - "ts": 128, - "salt": "K4bmZG9d704" - }; - priv.encrypt = function (data, callback) { - // end with a callback in order to improve encrypt to an - // asynchronous encryption. - var tmp = sjcl.encrypt(priv.username + ':' + priv.password, data, - priv.encrypt_param_object); - callback(JSON.parse(tmp).ct); - }; - priv.decrypt = function (data, callback) { - var tmp, param = $.extend(true, {}, priv.decrypt_param_object); - param.ct = data || ''; - param = JSON.stringify(param); - try { - tmp = sjcl.decrypt(priv.username + ':' + priv.password, param); - } catch (e) { - callback({ - status: 403, - statusText: 'Forbidden', - error: 'forbidden', - message: 'Unable to decrypt.', - reason: 'unable to decrypt' - }); - return; - } - callback(undefined, tmp); - }; - - priv.newAsyncModule = function () { - var async = {}; - async.call = function (obj, function_name, arglist) { - obj._wait = obj._wait || {}; - if (obj._wait[function_name]) { - obj._wait[function_name] -= 1; - return function () {}; - } - // ok if undef or 0 - arglist = arglist || []; - setTimeout(function () { - obj[function_name].apply(obj[function_name], arglist); - }); - }; - async.neverCall = function (obj, function_name) { - obj._wait = obj._wait || {}; - obj._wait[function_name] = -1; - }; - async.wait = function (obj, function_name, times) { - obj._wait = obj._wait || {}; - obj._wait[function_name] = times; - }; - async.end = function () { - async.call = function () {}; - }; - return async; - }; - - that.post = function (command) { - that.put(command); - }; - - /** - * Saves a document. - * @method put - */ - that.put = function (command) { - var new_file_name, new_file_content, am = priv.newAsyncModule(), - o = {}; - o.encryptFilePath = function () { - priv.encrypt(command.getDocId(), function (res) { - new_file_name = res; - am.call(o, 'save'); - }); - }; - o.encryptFileContent = function () { - priv.encrypt(command.getDocContent(), function (res) { - new_file_content = res; - am.call(o, 'save'); - }); - }; - o.save = function () { - var success = function (val) { - val.id = command.getDocId(); - that.success(val); - }, - error = function (err) { - that.error(err); - }, - cloned_doc = command.cloneDoc(); - - cloned_doc._id = new_file_name; - cloned_doc.content = new_file_content; - that.addJob('put', priv.sub_storage_spec, cloned_doc, - command.cloneOption(), success, error); - }; - am.wait(o, 'save', 1); - am.call(o, 'encryptFilePath'); - am.call(o, 'encryptFileContent'); - }; // end put - - /** - * Loads a document. - * @method get - */ - that.get = function (command) { - var new_file_name, am = priv.newAsyncModule(), - o = {}; - o.encryptFilePath = function () { - priv.encrypt(command.getDocId(), function (res) { - new_file_name = res; - am.call(o, 'get'); - }); - }; - o.get = function () { - that.addJob('get', priv.sub_storage_spec, new_file_name, - command.cloneOption(), o.success, o.error); - }; - o.success = function (val) { - val._id = command.getDocId(); - if (command.getOption('metadata_only')) { - that.success(val); - } else { - priv.decrypt(val.content, function (err, res) { - if (err) { - that.error(err); - } else { - val.content = res; - that.success(val); - } - }); - } - }; - o.error = function (error) { - that.error(error); - }; - am.call(o, 'encryptFilePath'); - }; // end get - - /** - * Gets a document list. - * @method allDocs - */ - that.allDocs = function (command) { - var result_array = [], - am = priv.newAsyncModule(), - o = {}; - o.allDocs = function () { - that.addJob('allDocs', priv.sub_storage_spec, null, - command.cloneOption(), o.onSuccess, o.error); - }; - o.onSuccess = function (val) { - if (val.total_rows === 0) { - return am.call(o, 'success'); - } - result_array = val.rows; - var i, decrypt = function (c) { - priv.decrypt(result_array[c].id, function (err, res) { - if (err) { - am.call(o, 'error', [err]); - } else { - result_array[c].id = res; - result_array[c].key = res; - am.call(o, 'success'); - } - }); - if (!command.getOption('metadata_only')) { - priv.decrypt( - result_array[c].value.content, - - function (err, res) { - if (err) { - am.call(o, 'error', [err]); - } else { - result_array[c].value.content = res; - am.call(o, 'success'); - } - } - ); - } - }; - if (command.getOption('metadata_only')) { - am.wait(o, 'success', val.total_rows - 1); - } else { - am.wait(o, 'success', val.total_rows * 2 - 1); - } - for (i = 0; i < result_array.length; i += 1) { - decrypt(i); - } - }; - o.error = function (error) { - am.end(); - that.error(error); - }; - o.success = function () { - am.end(); - that.success({ - total_rows: result_array.length, - rows: result_array - }); - }; - am.call(o, 'allDocs'); - }; // end allDocs - - /** - * Removes a document. - * @method remove - */ - that.remove = function (command) { - var new_file_name, o = {}; - o.encryptDocId = function () { - priv.encrypt(command.getDocId(), function (res) { - new_file_name = res; - o.removeDocument(); - }); - }; - o.removeDocument = function () { - var cloned_doc = command.cloneDoc(); - cloned_doc._id = new_file_name; - that.addJob('remove', priv.sub_storage_spec, cloned_doc, - command.cloneOption(), o.success, that.error); - }; - o.success = function (val) { - val.id = command.getDocId(); - that.success(val); - }; - o.encryptDocId(); - }; // end remove - - return that; -}); diff --git a/src/jio.storage/davstorage.js b/src/jio.storage/davstorage.js deleted file mode 100644 index f451a2ee98480ea61e3a94caaf9ee6202d318b78..0000000000000000000000000000000000000000 --- a/src/jio.storage/davstorage.js +++ /dev/null @@ -1,943 +0,0 @@ -/* - * Copyright 2013, Nexedi SA - * Released under the LGPL license. - * http://www.gnu.org/licenses/lgpl.html - */ - -/*jslint indent: 2, maxlen: 80, nomen: true */ -/*global define, jIO, jQuery, btoa */ - -// JIO Dav Storage Description : -// { -// type: "dav", -// url: {string} -// } - -// { -// type: "dav", -// url: {string}, -// auth_type: {string}, (optional) -// - "auto" (default) (not implemented) -// - "basic" -// - "digest" (not implemented) -// realm: {string}, (optional) -// - undefined (default) (not implemented) -// - "<string>" realm name (not implemented) -// username: {string}, -// password: {string} (optional) -// } - -// { -// type: "dav", -// url: {string}, -// encoded_login: {string} -// } - -// { -// type: "dav", -// url: {string}, -// secured_login: {string} (not implemented) -// } - -// NOTE: to get the authentication type -> -// curl --verbose -X OPTION http://domain/ -// In the headers: "WWW-Authenticate: Basic realm="DAV-upload" - -// URL Characters convertion: -// If I want to retrieve the file which id is -> http://100%.json -// http://domain/collection/http://100%.json cannot be applied -// - '/' is col separator, -// - '?' is url/parameter separator -// - '%' is special char -// - '.' document and attachment separator -// http://100%.json will become -// - http:%2F%2F100%25.json to avoid bad request ('/', '%' -> '%2F', '%25') -// - http:%2F%2F100%25_.json to avoid ids conflicts ('.' -> '_.') -// - http:%252F%252F100%2525_.json to avoid request bad interpretation -// ('%', '%25') -// The file will be saved as http:%2F%2F100%25_.json - -// define([module_name], [dependencies], module); -(function (dependencies, module) { - "use strict"; - if (typeof define === 'function' && define.amd) { - return define(dependencies, module); - } - module(jIO, jQuery); -}(['jio', 'jquery'], function (jIO, $) { - "use strict"; - jIO.addStorageType("dav", function (spec, my) { - var priv = {}, that = my.basicStorage(spec, my), dav = {}; - - // ATTRIBUTES // - priv.url = null; - priv.username = null; - priv.password = null; - priv.encoded_login = null; - - // CONSTRUCTOR // - /** - * Init the dav storage connector thanks to the description - * @method __init__ - * @param {object} description The description object - */ - priv.__init__ = function (description) { - priv.url = description.url || ""; - priv.url = priv.removeSlashIfLast(priv.url); - // if (description.secured_login) { - // not implemented - // } else - if (description.encoded_login) { - priv.encoded_login = description.encoded_login; - } else if (description.auth_type) { - if (description.auth_type === "basic") { - priv.encoded_login = "Basic " + - btoa((description.username || "") + ":" + - (description.password || "")); - } - } else { - priv.encoded_login = ""; - } - }; - - // OVERRIDES // - that.specToStore = function () { - // TODO: secured password - // The encoded_login can be seen by anyone, - // we must find a way to secure it! - // secured_login = encrypt(encoded_login) - // encoded_login = decrypt(secured_login) - return { - "url": priv.url, - "encoded_login": priv.encoded_login - }; - }; - - that.validateState = function () { - if (typeof priv.url !== "string" || priv.url === "") { - return "The webDav server URL is not provided"; - } - if (priv.encoded_login === null) { - return "Impossible to create the authorization"; - } - return ""; - }; - - // TOOLS // - /** - * Generate a new uuid - * @method generateUuid - * @return {string} The new uuid - */ - priv.generateUuid = function () { - var S4 = function () { - /* 65536 */ - var i, string = Math.floor( - Math.random() * 0x10000 - ).toString(16); - for (i = string.length; i < 4; i += 1) { - string = "0" + string; - } - return string; - }; - return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + - S4() + S4(); - }; - - // /** - // * Clones an object in deep - // * @method clone - // * @param {object} object The object to clone - // * @return {object} The cloned object - // */ - // priv.clone = function (object) { - // var tmp = JSON.stringify(object); - // if (tmp === undefined) { - // return undefined; - // } - // return JSON.parse(tmp); - // }; - - /** - * Replace substrings to another strings - * @method recursiveReplace - * @param {string} string The string to do replacement - * @param {array} list_of_replacement An array of couple - * ["substring to select", "selected substring replaced by this string"]. - * @return {string} The replaced string - */ - priv.recursiveReplace = function (string, list_of_replacement) { - var i, split_string = string.split(list_of_replacement[0][0]); - if (list_of_replacement[1]) { - for (i = 0; i < split_string.length; i += 1) { - split_string[i] = priv.recursiveReplace( - split_string[i], - list_of_replacement.slice(1) - ); - } - } - return split_string.join(list_of_replacement[0][1]); - }; - - /** - * Changes spaces to %20, / to %2f, % to %25 and ? to %3f - * @method secureName - * @param {string} name The name to secure - * @return {string} The secured name - */ - priv.secureName = function (name) { - return priv.recursiveReplace(name, [ - [" ", "%20"], - ["/", "%2F"], - ["%", "%25"], - ["?", "%3F"] - ]); - }; - - /** - * Restores the original name from a secured name - * @method restoreName - * @param {string} secured_name The secured name to restore - * @return {string} The original name - */ - priv.restoreName = function (secured_name) { - return priv.recursiveReplace(secured_name, [ - ["%20", " "], - ["%2F", "/"], - ["%25", "%"], - ["%3F", "?"] - ]); - }; - - /** - * Convert document id and attachment id to a file name - * @method idsToFileName - * @param {string} doc_id The document id - * @param {string} attachment_id The attachment id (optional) - * @return {string} The file name - */ - priv.idsToFileName = function (doc_id, attachment_id) { - doc_id = priv.secureName(doc_id).split(".").join("_."); - if (typeof attachment_id === "string") { - attachment_id = priv.secureName(attachment_id).split(".").join("_."); - return doc_id + "." + attachment_id; - } - return doc_id; - }; - - /** - * Convert a file name to a document id (and attachment id if there) - * @method fileNameToIds - * @param {string} file_name The file name to convert - * @return {array} ["document id", "attachment id"] or ["document id"] - */ - priv.fileNameToIds = function (file_name) { - var separator_index = -1, split = file_name.split("."); - split.slice(0, -1).forEach(function (file_name_part, index) { - if (file_name_part.slice(-1) !== "_") { - if (separator_index !== -1) { - separator_index = new TypeError("Corrupted file name"); - separator_index.status = 24; - throw separator_index; - } - separator_index = index; - } - }); - if (separator_index === -1) { - return [priv.restoreName(priv.restoreName( - file_name - ).split("_.").join("."))]; - } - return [ - priv.restoreName(priv.restoreName( - split.slice(0, separator_index + 1).join(".") - ).split("_.").join(".")), - priv.restoreName(priv.restoreName( - split.slice(separator_index + 1).join(".") - ).split("_.").join(".")) - ]; - }; - - /** - * Removes the last character if it is a "/". "/a/b/c/" become "/a/b/c" - * @method removeSlashIfLast - * @param {string} string The string to modify - * @return {string} The modified string - */ - priv.removeSlashIfLast = function (string) { - if (string[string.length - 1] === "/") { - return string.slice(0, -1); - } - return string; - }; - - /** - * Modify an ajax object to add default values - * @method makeAjaxObject - * @param {string} file_name The file name to add to the url - * @param {object} ajax_object The ajax object to override - * @return {object} A new ajax object with default values - */ - priv.makeAjaxObject = function (file_name, method, ajax_object) { - ajax_object.type = method || ajax_object.type || "GET"; - ajax_object.url = priv.url + "/" + priv.secureName(file_name) + - "?_=" + Date.now(); - ajax_object.async = ajax_object.async === false ? false : true; - ajax_object.crossdomain = - ajax_object.crossdomain === false ? false : true; - ajax_object.headers = ajax_object.headers || {}; - ajax_object.headers.Authorization = ajax_object.headers.Authorization || - priv.encoded_login; - return ajax_object; - }; - - /** - * Runs all ajax requests for davStorage - * @method ajax - * @param {string} doc_id The document id - * @param {string} attachment_id The attachment id, can be undefined - * @param {string} method The request method - * @param {object} ajax_object The request parameters (optional) - */ - priv.ajax = function (doc_id, attachment_id, method, ajax_object) { - var new_ajax_object = JSON.parse(JSON.stringify(ajax_object) || "{}"); - return $.ajax(priv.makeAjaxObject( - priv.idsToFileName(doc_id || '', attachment_id), - method, - new_ajax_object - ));//.always(then || function () {}); - }; - - /** - * Creates error objects for this storage - * @method createError - * @param {string} url url to clean up - * @return {object} error The error object - */ - priv.createError = function (status, message, reason) { - var error = { - "status": status, - "message": message, - "reason": reason - }; - switch (status) { - case 404: - error.statusText = "Not found"; - break; - case 405: - error.statusText = "Method Not Allowed"; - break; - case 409: - error.statusText = "Conflicts"; - break; - case 24: - error.statusText = "Corrupted Document"; - break; - } - error.error = error.statusText.toLowerCase().split(" ").join("_"); - return error; - }; - - /** - * Converts ajax error object to a JIO error object - * @method ajaxErrorToJioError - * @param {object} ajax_error_object The ajax error object - * @param {string} message The error message - * @param {string} reason The error reason - * @return {object} The JIO error object - */ - priv.ajaxErrorToJioError = function (ajax_error_object, message, reason) { - var jio_error_object = {}; - jio_error_object.status = ajax_error_object.status; - jio_error_object.statusText = ajax_error_object.statusText; - jio_error_object.error = - ajax_error_object.statusText.toLowerCase().split(" ").join("_"); - jio_error_object.message = message; - jio_error_object.reason = reason; - return jio_error_object; - }; - - /** - * Function that create an object containing jQuery like callbacks - * @method makeJQLikeCallback - * @return {object} jQuery like callback methods - */ - priv.makeJQLikeCallback = function () { - var result = null, emptyFun = function () {}, jql = { - "respond": function () { - result = arguments; - }, - "to_return": { - "always": function (func) { - if (result) { - func.apply(func, result); - jql.to_return.always = emptyFun; - } else { - jql.respond = func; - } - return jql.to_return; - }, - "then": function (func) { - if (result) { - func(result[1]); - jql.to_return.then = emptyFun; - } else { - jql.respond = function (err, response) { - func(response); - }; - } - return jql.to_return; - } - } - }; - return jql; - }; - - // DAV REQUESTS // - /** - * Retrieve a document file - * @method dav.getDocument - * @param {string} doc_id The document id - */ - dav.getDocument = function (doc_id) { - var doc, jql = priv.makeJQLikeCallback(), error = null; - priv.ajax(doc_id, undefined, "GET").always(function (one, state, three) { - if (state !== "success") { - error = priv.ajaxErrorToJioError( - one, - "Cannot retrieve document", - "Unknown" - ); - if (one.status === 404) { - error.reason = "Not Found"; - } - return jql.respond(error, undefined); - } - try { - doc = JSON.parse(one); - } catch (e) { - return jql.respond(priv.createError( - 24, - "Cannot parse document", - "Document is corrupted" - ), undefined); - } - // document health is good - return jql.respond(undefined, doc); - }); - return jql.to_return; - }; - - /** - * Retrieve an attachment file - * @method dav.getAttachment - * @param {string} doc_id The document id - * @param {string} attachment_id The attachment id - */ - dav.getAttachment = function (doc_id, attachment_id) { - var jql = priv.makeJQLikeCallback(), error = null; - priv.ajax( - doc_id, - attachment_id, - "GET" - ).always(function (one, state, three) { - if (state !== "success") { - error = priv.ajaxErrorToJioError( - one, - "Cannot retrieve attachment", - "Unknown" - ); - if (one.status === 404) { - error.reason = "Not Found"; - } - return jql.respond(error, undefined); - } - return jql.respond(undefined, one); - }); - return jql.to_return; - }; - - /** - * Uploads a document file - * @method dav.putDocument - * @param {object} doc The document object - */ - dav.putDocument = function (doc) { - var jql = priv.makeJQLikeCallback(); - priv.ajax(doc._id, undefined, "PUT", { - "dataType": "text", - "data": JSON.stringify(doc) - }).always(function (one, state, three) { - if (state !== "success") { - return jql.respond(priv.ajaxErrorToJioError( - one, - "Cannot upload document", - "Unknown" - ), undefined); - } - jql.respond(undefined, {"ok": true, "id": doc._id}); - }); - return jql.to_return; - }; - - /** - * Uploads an attachment file - * @method dav.putAttachment - * @param {string} doc_id The document id - * @param {string} attachment_id The attachment id - * @param {string} data The attachment data - */ - dav.putAttachment = function (doc_id, attachment_id, data) { - var jql = priv.makeJQLikeCallback(); - priv.ajax(doc_id, attachment_id, "PUT", { - "dataType": "text", - "data": data - }).always(function (one, state, three) { - if (state !== "success") { - return jql.respond(priv.ajaxErrorToJioError( - one, - "Cannot upload attachment", - "Unknown" - ), undefined); - } - return jql.respond(undefined, { - "ok": true, - "id": doc_id, - "attachment": attachment_id - }); - }); - return jql.to_return; - }; - - /** - * Deletes a document file - * @method dav.removeDocument - * @param {string} doc_id The document id - */ - dav.removeDocument = function (doc_id) { - var jql = priv.makeJQLikeCallback(), error = null; - priv.ajax( - doc_id, - undefined, - "DELETE" - ).always(function (one, state, three) { - if (state !== "success") { - error = priv.ajaxErrorToJioError( - one, - "Cannot delete document", - "Unknown" - ); - if (one.status === 404) { - error.reason = "Not Found"; - } - return jql.respond(error, undefined); - } - jql.respond(undefined, {"ok": true, "id": doc_id}); - }); - return jql.to_return; - }; - - /** - * Deletes an attachment file - * @method dav.removeAttachment - * @param {string} doc_id The document id - * @param {string} attachment_id The attachment id - */ - dav.removeAttachment = function (doc_id, attachment_id) { - var jql = priv.makeJQLikeCallback(), error = null; - priv.ajax( - doc_id, - attachment_id, - "DELETE" - ).always(function (one, state, three) { - if (state !== "success") { - error = priv.ajaxErrorToJioError( - one, - "Cannot delete attachment", - "Unknown" - ); - if (one.status === 404) { - error.reason = "Not Found"; - } - return jql.respond(error, undefined); - } - jql.respond(undefined, {"ok": true, "id": doc_id}); - }); - return jql.to_return; - }; - - /** - * Get a list of document file - * @method dav.allDocs - */ - dav.allDocs = function () { - var jql = priv.makeJQLikeCallback(), rows = []; - priv.ajax(undefined, undefined, "PROPFIND", { - "dataType": "xml", - "headers": {"Depth": 1} - }).always(function (one, state, three) { - var response, len; - if (state !== "success") { - return jql.respond(priv.ajaxErrorToJioError( - one, - "Cannot get the document list", - "Unknown" - ), undefined); - } - response = $(one).find("D\\:response, response"); - len = response.length; - if (len === 1) { - return jql.respond({"total_rows": 0, "rows": []}); - } - response.each(function (i, data) { - var row; - if (i > 0) { // exclude parent folder - row = { - "id": "", - "key": "", - "value": {} - }; - $(data).find("D\\:href, href").each(function () { - row.id = $(this).text().split('/').slice(-1)[0]; - try { - row.id = priv.fileNameToIds(row.id); - } catch (e) { - if (e.name === "TypeError" && e.status === 24) { - return; - } - throw e; - } - if (row.id.length !== 1) { - row = undefined; - } else { - row.id = row.id[0]; - row.key = row.id; - } - }); - if (row !== undefined) { - rows.push(row); - } - } - }); - jql.respond(undefined, { - "total_rows": rows.length, - "rows": rows - }); - }); - return jql.to_return; - }; - - // JIO COMMANDS // - - // wedDav methods rfc4918 (short summary) - // COPY Reproduces single resources (files) and collections (directory - // trees). Will overwrite files (if specified by request) but will - // respond 209 (Conflict) if it would overwrite a tree - // DELETE deletes files and directory trees - // GET just the vanilla HTTP/1.1 behaviour - // HEAD ditto - // LOCK locks a resources - // MKCOL creates a directory - // MOVE Moves (rename or copy) a file or a directory tree. Will - // 'overwrite' files (if specified by the request) but will respond - // 209 (Conflict) if it would overwrite a tree. - // OPTIONS If WebDAV is enabled and available for the path this reports the - // WebDAV extension methods - // PROPFIND Retrieves the requested file characteristics, DAV lock status - // and 'dead' properties for individual files, a directory and its - // child files, or a directory tree - // PROPPATCHset and remove 'dead' meta-data properties - // PUT Update or create resource or collections - // UNLOCK unlocks a resource - - // Notes: all Ajax requests should be CORS (cross-domain) - // adding custom headers triggers preflight OPTIONS request! - // http://remysharp.com/2011/04/21/getting-cors-working/ - - /** - * Creates a new document - * @method post - * @param {object} command The JIO command - */ - that.post = function (command) { - var doc_id = command.getDocId() || priv.generateUuid(); - dav.getDocument(doc_id).always(function (err, response) { - if (err) { - if (err.status === 404) { - // the document does not already exist - // updating document - var doc = command.cloneDoc(); - doc._id = doc_id; - return dav.putDocument(doc).always(function (err, response) { - if (err) { - return that.retry(err); - } - return that.success(response); - }); - } - if (err.status === 24) { - return that.error(err); - } - // an error occured - return that.retry(err); - } - // the document already exists - return that.error(priv.createError( - 405, - "Cannot create document", - "Document already exists" - )); - }); - }; - - /** - * Creates or updates a document - * @method put - * @param {object} command The JIO command - */ - that.put = function (command) { - dav.putDocument(command.cloneDoc()).always(function (err, response) { - if (err) { - // an error occured - return that.retry(err); - } - // document updated - return that.success(response); - }); - }; - - /** - * Add an attachment to a document - * @method putAttachment - * @param {object} command The JIO command - */ - that.putAttachment = function (command) { - var doc = null, doc_id = command.getDocId(), attachment_id, tmp; - attachment_id = command.getAttachmentId(); - dav.getDocument(doc_id).always(function (err, response) { - if (err) { - // document not found or error - tmp = that.retry; - if (err.status === 404 || - err.status === 24) { - tmp = that.error; - } - return tmp(err); - } - doc = response; - doc._attachments = doc._attachments || {}; - doc._attachments[attachment_id] = { - "length": command.getAttachmentLength(), - "digest": "md5-" + command.md5SumAttachmentData(), - "content_type": command.getAttachmentMimeType() - }; - // put the attachment - dav.putAttachment( - doc_id, - attachment_id, - command.getAttachmentData() - ).always(function (err, response) { - if (err) { - // an error occured - return that.retry(err); - } - // update the document - dav.putDocument(doc).always(function (err, response) { - if (err) { - return that.retry(err); - } - response.attachment = attachment_id; - return that.success(response); - }); - }); - }); - }; - - /** - * Get a document - * @method get - * @param {object} command The JIO command - */ - that.get = function (command) { - dav.getDocument(command.getDocId()).always(function (err, response) { - if (err) { - if (err.status === 404 || - err.status === 24) { - return that.error(err); - } - return that.retry(err); - } - return that.success(response); - }); - }; - - /** - * Get an attachment - * @method getAttachment - * @param {object} command The JIO command - */ - that.getAttachment = function (command) { - dav.getAttachment( - command.getDocId(), - command.getAttachmentId() - ).always(function (err, response) { - if (err) { - if (err.status === 404) { - return that.error(err); - } - return that.retry(err); - } - return that.success(response); - }); - }; - - /** - * Remove a document - * @method remove - * @param {object} command The JIO command - */ - that.remove = function (command) { - var doc_id = command.getDocId(), count = 0, end; - end = function () { - count -= 1; - if (count === 0) { - that.success({"ok": true, "id": doc_id}); - } - }; - dav.getDocument(doc_id).always(function (err, response) { - var attachment_id = null; - if (err) { - if (err.status === 404) { - return that.error(err); - } - if (err.status !== 24) { // 24 -> corrupted document - return that.retry(err); - } - response = {}; - } - count += 2; - dav.removeDocument(doc_id).always(function (err, response) { - if (err) { - if (err.status === 404) { - return that.error(err); - } - return that.retry(err); - } - return end(); - }); - for (attachment_id in response._attachments) { - if (response._attachments.hasOwnProperty(attachment_id)) { - count += 1; - dav.removeAttachment( - doc_id, - attachment_id - ).always(end); - } - } - end(); - }); - }; - - /** - * Remove an attachment - * @method removeAttachment - * @param {object} command The JIO command - */ - that.removeAttachment = function (command) { - var doc_id = command.getDocId(), doc, attachment_id; - attachment_id = command.getAttachmentId(); - dav.getDocument(doc_id).always(function (err, response) { - var still_has_attachments; - if (err) { - if (err.status === 404 || - err.status === 24) { - return that.error(err); - } - return that.retry(err); - } - doc = response; - if (typeof (doc._attachments || {})[attachment_id] !== "object") { - return that.error(priv.createError( - 404, - "Cannot remove attachment", - "Not Found" - )); - } - delete doc._attachments[attachment_id]; - // check if there is still attachments - for (still_has_attachments in doc._attachments) { - if (doc._attachments.hasOwnProperty(still_has_attachments)) { - break; - } - } - if (still_has_attachments === undefined) { - delete doc._attachments; - } - doc._id = doc_id; - dav.putDocument(doc).always(function (err, response) { - if (err) { - return that.retry(err); - } - dav.removeAttachment( - doc_id, - attachment_id - ).always(function (err, response) { - that.success({ - "ok": true, - "id": doc_id, - "attachment": attachment_id - }); - }); - }); - }); - }; - - /** - * Gets a document list from a distant dav storage - * Options: - * - {boolean} include_docs Also retrieve the actual document content. - * @method allDocs - * @param {object} command The JIO command - */ - that.allDocs = function (command) { - var count = 0, end, rows; - end = function () { - count -= 1; - if (count === 0) { - that.success(rows); - } - }; - dav.allDocs().always(function (err, response) { - if (err) { - return that.retry(err); - } - if (command.getOption("include_docs") === true) { - count += 1; - rows = response; - rows.rows.forEach(function (row) { - count += 1; - dav.getDocument( - row.id - ).always(function (err, response) { - if (err) { - if (err.status === 404 || err.status === 24) { - return that.error(err); - } - return that.retry(err); - } - row.doc = response; - end(); - }); - }); - end(); - } else { - that.success(response); - } - }); - }; - - priv.__init__(spec); - return that; - }); - -})); diff --git a/src/jio.storage/erp5storage.js b/src/jio.storage/erp5storage.js deleted file mode 100644 index 8a55a7008db251e48d07726fc55e70cf8dd508b8..0000000000000000000000000000000000000000 --- a/src/jio.storage/erp5storage.js +++ /dev/null @@ -1,457 +0,0 @@ -/* - * Copyright 2013, Nexedi SA - * Released under the LGPL license. - * http://www.gnu.org/licenses/lgpl.html - */ -/*jslint indent: 2, maxlen: 80, nomen: true */ -/*global jIO: true, $: true, complex_queries: true */ - -// JIO Erp5 Storage Description : -// { -// type: "erp5" -// url: {string} -// mode: {string} (optional) -// - "generic" (default) -// - "erp5_only" -// -// with -// -// auth_type: {string} (optional) -// - "none" (default) -// - "basic" (not implemented) -// - "digest" (not implemented) -// username: {string} -// password: {string} (optional) -// - no password (default) -// -// or -// -// encoded_login: {string} (not implemented) -// -// or -// -// secured_login: {string} (not implemented) -// } -jIO.addStorageType("erp5", function (spec, my) { - "use strict"; - var priv = {}, that = my.basicStorage(spec, my), erp5 = {}; - - // ATTRIBUTES // - priv.url = null; - priv.mode = "generic"; - priv.auth_type = "none"; - priv.encoded_login = null; - - // CONSTRUCTOR // - /** - * Init the erp5 storage connector thanks to the description - * @method __init__ - * @param {object} description The description object - */ - priv.__init__ = function (description) { - priv.url = description.url || ""; - priv.url = priv.removeSlashIfLast(priv.url); - if (description.mode === "erp5_only") { - priv.mode = "erp5_only"; - } - if (description.encoded_login) { - priv.encoded_login = description.encoded_login; - } else { - if (description.username) { - priv.encoded_login = - "__ac_name=" + priv.convertToUrlParameter(description.username) + - "&" + (typeof description.password === "string" ? - "__ac_password=" + - priv.convertToUrlParameter(description.password) + "&" : ""); - } else { - priv.encoded_login = ""; - } - } - }; - - // OVERRIDES // - that.specToStore = function () { - // TODO: secured password - // The encoded_login can be seen by anyone, we must find a way to secure it! - // secured_login = encrypt(encoded_login) - // encoded_login = decrypt(secured_login) - return { - "url": priv.url, - "mode": priv.mode, - "encoded_login": priv.encoded_login - }; - }; - - that.validateState = function () { - if (typeof priv.url !== "string" || priv.url === "") { - return "The erp5 server URL is not provided"; - } - if (priv.encoded_login === null) { - return "Impossible to create the authorization"; - } - return ""; - }; - - // TOOLS // - /** - * Replace substrings to another strings - * @method recursiveReplace - * @param {string} string The string to do replacement - * @param {array} list_of_replacement An array of couple - * ["substring to select", "selected substring replaced by this string"]. - * @return {string} The replaced string - */ - priv.recursiveReplace = function (string, list_of_replacement) { - var i, split_string = string.split(list_of_replacement[0][0]); - if (list_of_replacement[1]) { - for (i = 0; i < split_string.length; i += 1) { - split_string[i] = priv.recursiveReplace( - split_string[i], - list_of_replacement.slice(1) - ); - } - } - return split_string.join(list_of_replacement[0][1]); - }; - - /** - * Changes & to %26 - * @method convertToUrlParameter - * @param {string} parameter The parameter to convert - * @return {string} The converted parameter - */ - priv.convertToUrlParameter = function (parameter) { - return priv.recursiveReplace(parameter, [[" ", "%20"], ["&", "%26"]]); - }; - - /** - * Removes the last character if it is a "/". "/a/b/c/" become "/a/b/c" - * @method removeSlashIfLast - * @param {string} string The string to modify - * @return {string} The modified string - */ - priv.removeSlashIfLast = function (string) { - if (string[string.length - 1] === "/") { - return string.slice(0, -1); - } - return string; - }; - - /** - * Modify an ajax object to add default values - * @method makeAjaxObject - * @param {object} json The JSON object - * @param {object} option The option object - * @param {string} method The erp5 request method - * @param {object} ajax_object The ajax object to override - * @return {object} A new ajax object with default values - */ - priv.makeAjaxObject = function (json, option, method, ajax_object) { - ajax_object.type = "POST"; - ajax_object.dataType = "json"; - ajax_object.data = [ - {"name": "doc", "value": JSON.stringify(json)}, - {"name": "option", "value": JSON.stringify(option)}, - {"name": "mode", "value": priv.mode} - ]; - ajax_object.url = priv.url + "/JIO_" + method + - "?" + priv.encoded_login + "_=" + Date.now(); - ajax_object.async = ajax_object.async === false ? false : true; - ajax_object.crossdomain = ajax_object.crossdomain === false ? false : true; - ajax_object.headers = ajax_object.headers || {}; - return ajax_object; - }; - - /** - * Runs all ajax requests for erp5Storage - * @method ajax - * @param {object} json The JSON object - * @param {object} option The option object - * @param {string} method The erp5 request method - * @param {object} ajax_object The request parameters (optional) - */ - priv.ajax = function (json, option, method, ajax_object) { - return $.ajax(priv.makeAjaxObject(json, option, method, ajax_object || {})); - //.always(then || function () {}); - }; - - /** - * Creates error objects for this storage - * @method createError - * @param {string} url url to clean up - * @return {object} error The error object - */ - priv.createError = function (status, message, reason) { - var error = { - "status": status, - "message": message, - "reason": reason - }; - switch (status) { - case 404: - error.statusText = "Not found"; - break; - case 405: - error.statusText = "Method Not Allowed"; - break; - case 409: - error.statusText = "Conflicts"; - break; - case 24: - error.statusText = "Corrupted Document"; - break; - } - error.error = error.statusText.toLowerCase().split(" ").join("_"); - return error; - }; - - /** - * Converts ajax error object to a JIO error object - * @method ajaxErrorToJioError - * @param {object} ajax_error_object The ajax error object - * @param {string} message The error message - * @param {string} reason The error reason - * @return {object} The JIO error object - */ - priv.ajaxErrorToJioError = function (ajax_error_object, message, reason) { - var jio_error_object = {}; - jio_error_object.status = ajax_error_object.status; - jio_error_object.statusText = ajax_error_object.statusText; - jio_error_object.error = - ajax_error_object.statusText.toLowerCase().split(" ").join("_"); - jio_error_object.message = message; - jio_error_object.reason = reason; - return jio_error_object; - }; - - /** - * Function that create an object containing jQuery like callbacks - * @method makeJQLikeCallback - * @return {object} jQuery like callback methods - */ - priv.makeJQLikeCallback = function () { - var result = null, emptyFun = function () {}, jql = { - "respond": function () { - result = arguments; - }, - "to_return": { - "always": function (func) { - if (result) { - func.apply(func, result); - jql.to_return.always = emptyFun; - } else { - jql.respond = func; - } - return jql.to_return; - } - } - }; - return jql; - }; - - /** - * Use option object and converts a query to a compatible ERP5 Query. - * - * @param {Object} option The command options - */ - priv.convertToErp5Query = function (option) { - option.query = complex_queries.QueryFactory.create(option.query || ""); - if (option.wildcard_character === undefined || - (option.wildcard_character !== null && - typeof option.wildcard_character !== 'string')) { - option.wildcard_character = '%'; - } else { - option.wildcard_character = option.wildcard_character || ''; - } - option.query.onParseSimpleQuery = function (object) { - if (option.wildcard_character.length === 1 && - object.parsed.operator === '=') { - object.parsed.operator = 'like'; - if (option.wildcard_character === '%') { - object.parsed.value = - object.parsed.value.replace(/_/g, '\\_'); - } else if (option.wildcard_character === '_') { - object.parsed.value = - object.parsed.value.replace(/%/g, '\\%').replace(/_/g, '%'); - } else { - object.parsed.value = - object.parsed.value.replace( - /([%_])/g, - '\\$1' - ).replace( - new RegExp(complex_queries.stringEscapeRegexpCharacters( - option.wildcard_character - ), 'g'), - '%' - ); - } - } - }; - option.query = option.query.parse(); - }; - - // ERP5 REQUESTS // - /** - * Sends a request to ERP5 - * @method erp5.genericRequest - * @param {object} doc The document object - * @param {object} option The option object - * @param {string} method The ERP5 request method - */ - erp5.genericRequest = function (json, option, method) { - var jql = priv.makeJQLikeCallback(), error = null; - priv.ajax(json, option, method).always(function (one, state, three) { - if (state === "parsererror") { - return jql.respond(priv.createError( - 24, - "Cannot parse data", - "Corrupted data" - ), undefined); - } - if (state !== "success") { - error = priv.ajaxErrorToJioError( - one, - "An error occured on " + method, - "Unknown" - ); - if (one.status === 404) { - error.reason = "Not Found"; - } - return jql.respond(error, undefined); - } - if (one.err !== null) { - return jql.respond(one.err, undefined); - } - if (one.response !== null) { - return jql.respond(undefined, one.response); - } - return jql.respond(priv.createError( - 24, - "Cannot parse data", - "Corrupted data" - ), undefined); - }); - return jql.to_return; - }; - - // JIO COMMANDS // - /** - * The ERP5 storage generic command - * @method genericCommand - * @param {object} command The JIO command object - * @param {string} method The ERP5 request method - */ - priv.genericCommand = function (command, method) { - var option = command.cloneOption(); - if (complex_queries !== undefined && - method === 'allDocs' && - option.query) { - priv.convertToErp5Query(option); - } - erp5.genericRequest( - command.cloneDoc(), - option, - method - ).always(function (err, response) { - if (err) { - return that.error(err); - } - return that.success(response); - }); - }; - - /** - * Creates a new document - * @method post - * @param {object} command The JIO command - */ - that.post = function (command) { - priv.genericCommand(command, "post"); - }; - - /** - * Creates or updates a document - * @method put - * @param {object} command The JIO command - */ - that.put = function (command) { - priv.genericCommand(command, "put"); - }; - - /** - * Add an attachment to a document - * @method putAttachment - * @param {object} command The JIO command - */ - that.putAttachment = function (command) { - priv.genericCommand(command, "putAttachment"); - }; - - /** - * Get a document - * @method get - * @param {object} command The JIO command - */ - that.get = function (command) { - priv.genericCommand(command, "get"); - }; - - /** - * Get an attachment - * @method getAttachment - * @param {object} command The JIO command - */ - that.getAttachment = function (command) { - priv.genericCommand(command, "getAttachment"); - }; - - /** - * Remove a document - * @method remove - * @param {object} command The JIO command - */ - that.remove = function (command) { - priv.genericCommand(command, "remove"); - }; - - /** - * Remove an attachment - * @method removeAttachment - * @param {object} command The JIO command - */ - that.removeAttachment = function (command) { - priv.genericCommand(command, "removeAttachment"); - }; - - /** - * Gets a document list from a distant erp5 storage - * Options: - * - {boolean} include_docs Also retrieve the actual document content. - * @method allDocs - * @param {object} command The JIO command - */ - that.allDocs = function (command) { - priv.genericCommand(command, "allDocs"); - }; - - /** - * Checks a document state - * @method check - * @param {object} command The JIO command - */ - that.check = function (command) { - priv.genericCommand(command, "check"); - }; - - /** - * Restore a document state to a coherent state - * @method repair - * @param {object} command The JIO command - */ - that.repair = function (command) { - priv.genericCommand(command, "repair"); - }; - - priv.__init__(spec); - return that; -}); diff --git a/src/jio.storage/gidstorage.js b/src/jio.storage/gidstorage.js deleted file mode 100644 index 74b2d62e3d2e4fbad2a0a9cf2bef5c6a228d9bf8..0000000000000000000000000000000000000000 --- a/src/jio.storage/gidstorage.js +++ /dev/null @@ -1,615 +0,0 @@ -/* - * JIO extension for resource global identifier management. - * Copyright (C) 2013 Nexedi SA - * - * This library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ -/*global define, jIO, setTimeout */ - -/** - * JIO GID Storage. Type = 'gid'. - * Identifies document with their global identifier representation - * - * Sub storages must support complex queries and include_docs options. - * - * Storage Description: - * - * { - * "type": "gid", - * "sub_storage": {<storage description>}, - * "constraints": { - * "default": { - * "identifier": "list", // ['a', 1] - * "type": "DCMIType", // 'Text' - * "title": "string" // 'something blue' - * }, - * "Text": { - * "format": "contentType" // contains 'text/plain;charset=utf-8' - * }, - * "Image": { - * "version": "json" // value as is - * } - * } - * } - */ -// define([module_name], [dependencies], module); -(function (dependencies, module) { - "use strict"; - if (typeof define === 'function' && define.amd) { - return define(dependencies, module); - } - module(jIO); -}(['jio'], function (jIO) { - "use strict"; - - var dcmi_types, metadata_actions, content_type_re; - dcmi_types = { - 'Collection': 'Collection', - 'Dataset': 'Dataset', - 'Event': 'Event', - 'Image': 'Image', - 'InteractiveResource': 'InteractiveResource', - 'MovingImage': 'MovingImage', - 'PhysicalObject': 'PhysicalObject', - 'Service': 'Service', - 'Software': 'Software', - 'Sound': 'Sound', - 'StillImage': 'StillImage', - 'Text': 'Text' - }; - metadata_actions = { - /** - * Returns the metadata value - */ - json: function (value) { - return value; - }, - /** - * Returns the metadata if there is a string - */ - string: function (value) { - if (!Array.isArray(value)) { - if (typeof value === 'object') { - return value.content; - } - return value; - } - }, - /** - * Returns the metadata in a array format - */ - list: function (value) { - var i, new_value = []; - if (Array.isArray(value)) { - for (i = 0; i < value.length; i += 1) { - if (typeof value[i] === 'object') { - new_value[new_value.length] = value[i].content; - } else { - new_value[new_value.length] = value[i]; - } - } - } else if (value !== undefined) { - value = [value]; - } - return value; - }, - /** - * Returns the metadata if there is a string equal to a DCMIType - */ - DCMIType: function (value) { - var i; - if (!Array.isArray(value)) { - value = [value]; - } - for (i = 0; i < value.length; i += 1) { - if (typeof value[i] === 'object' && dcmi_types[value[i].content]) { - return value[i].content; - } - if (dcmi_types[value[i]]) { - return value[i]; - } - } - }, - /** - * Returns the metadata content type if exist - */ - contentType: function (value) { - var i; - if (!Array.isArray(value)) { - value = [value]; - } - for (i = 0; i < value.length; i += 1) { - if (value[i] === 'object') { - if (content_type_re.test(value[i].content)) { - return value[i].content; - } - } else { - if (content_type_re.test(value[i])) { - return value[i]; - } - } - } - }, - /** - * Returns the metadata if it is a date - */ - date: function (value) { - var d; - if (!Array.isArray(value)) { - if (typeof value === 'object') { - d = new Date(value.content); - value = value.content; - } else { - d = new Date(value); - } - } - if (Object.prototype.toString.call(d) === "[object Date]") { - if (!isNaN(d.getTime())) { - return value; - } - } - } - }; - content_type_re = - /^([a-z]+\/[a-zA-Z0-9\+\-\.]+)(?:\s*;\s*charset\s*=\s*([a-zA-Z0-9\-]+))?$/; - - /** - * Creates a gid from metadata and constraints. - * - * @param {Object} metadata The metadata to use - * @param {Object} constraints The constraints - * @return {String} The gid or undefined if metadata doesn't respect the - * constraints - */ - function gidFormat(metadata, constraints) { - var types, i, j, meta_key, result = [], tmp, constraint, actions; - types = (metadata_actions.list(metadata.type) || []).slice(); - types.unshift('default'); - for (i = 0; i < types.length; i += 1) { - constraint = constraints[types[i]]; - for (meta_key in constraint) { - if (constraint.hasOwnProperty(meta_key)) { - actions = constraint[meta_key]; - if (!Array.isArray(actions)) { - actions = [actions]; - } - for (j = 0; j < actions.length; j += 1) { - tmp = metadata_actions[ - actions[j] - ](metadata[meta_key]); - if (tmp === undefined) { - return; - } - } - result[result.length] = [meta_key, tmp]; - } - } - } - // sort dict keys to make gid universal - result.sort(function (a, b) { - return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0; - }); - tmp = {}; - for (i = 0; i < result.length; i += 1) { - tmp[result[i][0]] = result[i][1]; - } - return JSON.stringify(tmp); - } - - /** - * Convert a gid to a complex query. - * - * @param {Object,String} gid The gid - * @param {Object} constraints The constraints - * @return {Object} A complex serialized object - */ - function gidToComplexQuery(gid, contraints) { - var k, i, result = [], meta, content; - if (typeof gid === 'string') { - gid = JSON.parse(gid); - } - for (k in gid) { - if (gid.hasOwnProperty(k)) { - meta = gid[k]; - if (!Array.isArray(meta)) { - meta = [meta]; - } - for (i = 0; i < meta.length; i += 1) { - content = meta[i]; - if (typeof content === 'object') { - content = content.content; - } - result[result.length] = { - "type": "simple", - "operator": "=", - "key": k, - "value": content - }; - } - } - } - return { - "type": "complex", - "operator": "AND", - "query_list": result - }; - } - - /** - * Parse the gid and returns a metadata object containing gid keys and values. - * - * @param {String} gid The gid to convert - * @param {Object} constraints The constraints - * @return {Object} The gid metadata - */ - function gidParse(gid, constraints) { - var object; - try { - object = JSON.parse(gid); - } catch (e) { - return; - } - if (gid !== gidFormat(object, constraints)) { - return; - } - return object; - } - - /** - * The gid storage used by JIO. - * - * This storage change the id of a document with its global id. A global id - * is representation of a document metadata used to define it as uniq. The way - * to generate global ids can be define in the storage description. It allows - * us use duplicating storage with different sub storage kind. - * - * @class gidStorage - */ - function gidStorage(spec, my) { - var that = my.basicStorage(spec, my), priv = {}; - - priv.sub_storage = spec.sub_storage; - priv.constraints = spec.constraints || { - "default": { - "type": "DCMIType", - "title": "string" - } - }; - - // Overrides - - that.specToStore = function () { - return { - "sub_storage": priv.sub_storage, - "constraints": priv.constraints - }; - }; - - // JIO Commands - - /** - * Generic command for post or put one. - * - * This command will check if the document already exist with an allDocs - * and a complex query. If exist, then post will fail. Put will update the - * retrieved document thanks to its real id. If no documents are found, post - * and put will create a new document with the sub storage id generator. - * - * @method putOrPost - * @private - * @param {Command} command The JIO command - * @param {String} method The command method - */ - priv.putOrPost = function (command, method) { - setTimeout(function () { - var gid, complex_query, doc = command.cloneDoc(); - gid = gidFormat(doc, priv.constraints); - if (gid === undefined || (doc._id && gid !== doc._id)) { - return that.error({ - "status": 400, - "statusText": "Bad Request", - "error": "bad_request", - "message": "Cannot " + method + " document", - "reason": "metadata should respect constraints" - }); - } - complex_query = gidToComplexQuery(gid); - that.addJob('allDocs', priv.sub_storage, {}, { - "query": complex_query, - "wildcard_character": null - }, function (response) { - var update_method = method; - if (response.total_rows !== 0) { - if (method === 'post') { - return that.error({ - "status": 409, - "statusText": "Conflict", - "error": "conflict", - "message": "Cannot " + method + " document", - "reason": "Document already exist" - }); - } - doc = command.cloneDoc(); - doc._id = response.rows[0].id; - } else { - doc = command.cloneDoc(); - delete doc._id; - update_method = 'post'; - } - that.addJob(update_method, priv.sub_storage, doc, { - }, function (response) { - response.id = gid; - that.success(response); - }, function (err) { - err.message = "Cannot " + method + " document"; - that.error(err); - }); - }, function (err) { - err.message = "Cannot " + method + " document"; - that.error(err); - }); - }); - }; - - /** - * Generic command for putAttachment, getAttachment or removeAttachment. - * - * This command will check if the document exist with an allDocs and a - * complex query. If not exist, then it returns 404. Otherwise the - * action will be done on the attachment of the found document. - * - * @method putGetOrRemoveAttachment - * @private - * @param {Command} command The JIO command - * @param {String} method The command method - */ - priv.putGetOrRemoveAttachment = function (command, method) { - setTimeout(function () { - var gid_object, complex_query, doc = command.cloneDoc(); - gid_object = gidParse(doc._id, priv.constraints); - if (gid_object === undefined) { - return that.error({ - "status": 400, - "statusText": "Bad Request", - "error": "bad_request", - "message": "Cannot " + method + " attachment", - "reason": "metadata should respect constraints" - }); - } - complex_query = gidToComplexQuery(gid_object); - that.addJob('allDocs', priv.sub_storage, {}, { - "query": complex_query, - "wildcard_character": null - }, function (response) { - if (response.total_rows === 0) { - return that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": "Cannot " + method + " attachment", - "reason": "Document already exist" - }); - } - gid_object = doc._id; - doc._id = response.rows[0].id; - that.addJob(method + "Attachment", priv.sub_storage, doc, { - }, function (response) { - if (method !== 'get') { - response.id = gid_object; - } - that.success(response); - }, function (err) { - err.message = "Cannot " + method + " attachment"; - that.error(err); - }); - }, function (err) { - err.message = "Cannot " + method + " attachment"; - that.error(err); - }); - }); - }; - - /** - * See {{#crossLink "gidStorage/putOrPost:method"}}{{/#crossLink}}. - * - * @method post - * @param {Command} command The JIO command - */ - that.post = function (command) { - priv.putOrPost(command, 'post'); - }; - - /** - * See {{#crossLink "gidStorage/putOrPost:method"}}{{/#crossLink}}. - * - * @method put - * @param {Command} command The JIO command - */ - that.put = function (command) { - priv.putOrPost(command, 'put'); - }; - - /** - * Puts an attachment to a document thank to its gid, a sub allDocs and a - * complex query. - * - * @method putAttachment - * @param {Command} command The JIO command - */ - that.putAttachment = function (command) { - priv.putGetOrRemoveAttachment(command, 'put'); - }; - - /** - * Gets a document thank to its gid, a sub allDocs and a complex query. - * - * @method get - * @param {Command} command The JIO command - */ - that.get = function (command) { - setTimeout(function () { - var gid_object, complex_query; - gid_object = gidParse(command.getDocId(), priv.constraints); - if (gid_object === undefined) { - return that.error({ - "status": 400, - "statusText": "Bad Request", - "error": "bad_request", - "message": "Cannot get document", - "reason": "metadata should respect constraints" - }); - } - complex_query = gidToComplexQuery(gid_object); - that.addJob('allDocs', priv.sub_storage, {}, { - "query": complex_query, - "wildcard_character": null, - "include_docs": true - }, function (response) { - if (response.total_rows === 0) { - return that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": "Cannot get document", - "reason": "missing" - }); - } - response.rows[0].doc._id = command.getDocId(); - return that.success(response.rows[0].doc); - }, function (err) { - err.message = "Cannot get document"; - return that.error(err); - }); - }); - }; - - /** - * Gets an attachment from a document thank to its gid, a sub allDocs and a - * complex query. - * - * @method getAttachment - * @param {Command} command The JIO command - */ - that.getAttachment = function (command) { - priv.putGetOrRemoveAttachment(command, 'get'); - }; - - /** - * Remove a document thank to its gid, sub allDocs and a complex query. - * - * @method remove - * @param {Command} command The JIO command. - */ - that.remove = function (command) { - setTimeout(function () { - var gid_object, complex_query, doc = command.cloneDoc(); - gid_object = gidParse(doc._id, priv.constraints); - if (gid_object === undefined) { - return that.error({ - "status": 400, - "statusText": "Bad Request", - "error": "bad_request", - "message": "Cannot remove document", - "reason": "metadata should respect constraints" - }); - } - complex_query = gidToComplexQuery(gid_object); - that.addJob('allDocs', priv.sub_storage, {}, { - "query": complex_query, - "wildcard_character": null - }, function (response) { - if (response.total_rows === 0) { - return that.error({ - "status": 404, - "statusText": "Not found", - "error": "not_found", - "message": "Cannot remove document", - "reason": "missing" - }); - } - gid_object = doc._id; - doc = {"_id": response.rows[0].id}; - that.addJob('remove', priv.sub_storage, doc, { - }, function (response) { - response.id = gid_object; - that.success(response); - }, function (err) { - err.message = "Cannot remove document"; - that.error(err); - }); - }, function (err) { - err.message = "Cannot remove document"; - that.error(err); - }); - }); - }; - - /** - * Removes an attachment to a document thank to its gid, a sub allDocs and a - * complex query. - * - * @method removeAttachment - * @param {Command} command The JIO command - */ - that.removeAttachment = function (command) { - priv.putGetOrRemoveAttachment(command, 'remove'); - }; - - /** - * Retrieve a list of document which respect gid constraints. - * - * @method allDocs - * @param {Command} command The JIO command - */ - that.allDocs = function (command) { - setTimeout(function () { - var options = command.cloneOption(), include_docs; - include_docs = options.include_docs; - options.include_docs = true; - that.addJob('allDocs', priv.sub_storage, { - }, options, function (response) { - var result = [], doc_gids = {}, i, row, gid; - while ((row = response.rows.shift()) !== undefined) { - if ((gid = gidFormat(row.doc, priv.constraints)) !== undefined) { - if (!doc_gids[gid]) { - doc_gids[gid] = true; - row.id = gid; - delete row.key; - result[result.length] = row; - if (include_docs === true) { - row.doc._id = gid; - } else { - delete row.doc; - } - } - } - } - doc_gids = undefined; // free memory - row = undefined; - that.success({"total_rows": result.length, "rows": result}); - }, function (err) { - err.message = "Cannot get all documents"; - return that.error(err); - }); - }); - }; - - return that; - } - - jIO.addStorageType('gid', gidStorage); - -})); diff --git a/src/jio.storage/indexstorage.js b/src/jio.storage/indexstorage.js deleted file mode 100644 index 07a51feb9a0e4130436c2c496d13ac746e12a40b..0000000000000000000000000000000000000000 --- a/src/jio.storage/indexstorage.js +++ /dev/null @@ -1,982 +0,0 @@ -/* - * JIO extension for resource indexing. - * Copyright (C) 2013 Nexedi SA - * - * This library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true, regexp: true */ -/*global jIO, define, complex_queries */ - -/** - * JIO Index Storage. - * Manages indexes for specified storages. - * Description: - * { - * "type": "index", - * "indices": [{ - * "id": "index_title_subject.json", // doc id where to store indices - * "index": ["title", "subject"], // metadata to index - * "attachment": "youhou", // default "body" - * "metadata": { // default {} - * "type": "Dataset", - * "format": "application/json", - * "date": "yyyy-mm-ddTHH:MM:SS+HH:MM", - * "title": "My index database", - * "creator": "Me" - * }, - * "sub_storage": <sub storage where to store index> - * (default equal to parent sub_storage field) - * }, { - * "id": "index_year.json", - * "index": "year" - * ... - * }], - * "sub_storage": <sub storage description> - * } - * - * Sent document metadata will be: - * index_titre_subject.json - * { - * "_id": "index_title_subject.json", - * "type": "Dataset", - * "format": "application/json", - * "date": "yyyy-mm-ddTHH:MM:SS+HH:MM", - * "title": "My index database", - * "creator": "Me", - * "_attachments": { - * "youhou": { - * "length": Num, - * "digest": "XXX", - * "content_type": "application/json" - * } - * } - * } - * Attachment "youhou" - * { - * "indexing": ["title", "subject"], - * "free": [0], - * "location": { - * "foo": 1, - * "bar": 2, - * ... - * }, - * "database": [ - * {}, - * {"_id": "foo", "title": "...", "subject": ...}, - * {"_id": "bar", "title": "...", "subject": ...}, - * ... - * ] - * } - * - * index_year.json - * { - * "_id": "index_year.json", - * "_attachments": { - * "body": {..} - * } - * } - * Attachment "body" - * { - * "indexing": ["year"], - * "free": [1], - * "location": { - * "foo": 0, - * "bar": 2, - * ... - * }, - * "database": [ - * {"_id": "foo", "year": "..."}, - * {}, - * {"_id": "bar", "year": "..."}, - * ... - * ] - * } - * - * A put document will be indexed to the free location if exist, else it will be - * indexed at the end of the database. The document id will be indexed, also, in - * 'location' to quickly replace metadata. - * - * Only one or two loops are executed: - * - one to filter retrieved document list (no query -> no loop) - * - one to format the result to a JIO response - */ -// define([module_name], [dependencies], module); -(function (dependencies, module) { - "use strict"; - if (typeof define === 'function' && define.amd) { - return define(dependencies, module); - } - module(jIO, complex_queries); -}(['jio', 'complex_queries'], function (jIO, complex_queries) { - "use strict"; - - var error_dict = { - "Corrupted Index": { - "status": 24, - "statusText": "Corrupt", - "error": "corrupt", - "reason": "corrupted index database" - }, - "Corrupted Metadata": { - "status": 24, - "statusText": "Corrupt", - "error": "corrupt", - "reason": "corrupted document" - }, - "Not Found": { - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "reason": "missing document" - }, - "Conflict": { - "status": 409, - "statusText": "Conflicts", - "error": "conflicts", - "reason": "already exist" - }, - "Different Index": { - "status": 40, - "statusText": "Check failed", - "error": "check_failed", - "reason": "incomplete database" - } - }; - - /** - * Generate a JIO Error Object - * - * @method generateErrorObject - * @param {String} name The error name - * @param {String} message The error message - * @param {String} [reason] The error reason - * @return {Object} A jIO error object - */ - function generateErrorObject(name, message, reason) { - if (!error_dict[name]) { - return { - "status": 0, - "statusText": "Unknown", - "error": "unknown", - "message": message, - "reason": reason || "unknown" - }; - } - return { - "status": error_dict[name].status, - "statusText": error_dict[name].statusText, - "error": error_dict[name].error, - "message": message, - "reason": reason || error_dict[name].reason - }; - } - - /** - * Get the real type of an object - * @method type - * @param {Any} value The value to check - * @return {String} The value type - */ - function type(value) { - // returns "String", "Object", "Array", "RegExp", ... - return (/^\[object ([a-zA-Z]+)\]$/).exec( - Object.prototype.toString.call(value) - )[1]; - } - - /** - * Generate a new uuid - * @method generateUuid - * @return {string} The new uuid - */ - function generateUuid() { - var S4 = function () { - var i, string = Math.floor( - Math.random() * 0x10000 /* 65536 */ - ).toString(16); - for (i = string.length; i < 4; i += 1) { - string = "0" + string; - } - return string; - }; - return S4() + S4() + "-" + - S4() + "-" + - S4() + "-" + - S4() + "-" + - S4() + S4() + S4(); - } - - /** - * Tool to get the date in W3C date format "2011-12-13T14:15:16+01:00" - * - * @param {Any} date The new Date() parameter - * @return {String} The date in W3C date format - */ - function w3cDate(date) { - var d = new Date(date), offset = -d.getTimezoneOffset(); - return ( - d.getFullYear() + "-" + - (d.getMonth() + 1) + "-" + - d.getDate() + "T" + - d.getHours() + ":" + - d.getMinutes() + ":" + - d.getSeconds() + - (offset < 0 ? "-" : "+") + - (offset / 60) + ":" + - (offset % 60) - ).replace(/[0-9]+/g, function (found) { - if (found.length < 2) { - return '0' + found; - } - return found; - }); - } - - /** - * A JSON Index manipulator - * - * @class JSONIndex - * @constructor - */ - function JSONIndex(spec) { - var that = this; - spec = spec || {}; - - /** - * The document id - * - * @property _id - * @type String - */ - that._id = spec._id; - - /** - * The attachment id - * - * @property _attachment - * @type String - */ - that._attachment = spec._attachment; - - /** - * The array with metadata key to index - * - * @property _indexing - * @type Array - */ - that._indexing = spec.indexing || []; - - /** - * The array of free location index - * - * @property _free - * @type Array - * @default [] - */ - that._free = spec.free || []; - - /** - * The dictionnary document id -> database index - * - * @property _location - * @type Object - * @default {} - */ - that._location = spec.location || {}; - - /** - * The database array containing document metadata - * - * @property _database - * @type Array - * @default [] - */ - that._database = spec.database || []; - - /** - * Adds a metadata object in the database, replace if already exist - * - * @method put - * @param {Object} meta The metadata to add - * @return {Boolean} true if added, false otherwise - */ - that.put = function (meta) { - var k, needed_meta = {}, ok = false; - if (typeof meta._id !== "string" && meta._id !== "") { - throw new TypeError("Corrupted Metadata"); - } - for (k in meta) { - if (meta.hasOwnProperty(k)) { - if (k[0] === "_") { - if (k === "_id") { - needed_meta[k] = meta[k]; - } - } else if (that._indexing_object[k]) { - needed_meta[k] = meta[k]; - ok = true; - } - } - } - if (ok) { - if (typeof that._location[meta._id] === "number") { - that._database[that._location[meta._id]] = needed_meta; - } else if (that._free.length > 0) { - k = that._free.shift(); - that._database[k] = needed_meta; - that._location[meta._id] = k; - } else { - that._database.push(needed_meta); - that._location[meta._id] = that._database.length - 1; - } - return true; - } - if (typeof that._location[meta._id] === "number") { - that.remove(meta); - } - return false; - }; - - /** - * Removes a metadata object from the database if exist - * - * @method remove - * @param {Object} meta The metadata to remove - */ - that.remove = function (meta) { - if (typeof meta._id !== "string") { - throw new TypeError("Corrupted Metadata"); - } - if (typeof that._location[meta._id] !== "number") { - throw new ReferenceError("Not Found"); - } - that._database[that._location[meta._id]] = null; - that._free.push(that._location[meta._id]); - delete that._location[meta._id]; - }; - - /** - * Checks if the index database document is correct - * - * @method check - */ - that.check = function () { - var id, database_meta; - if (typeof that._id !== "string" || - that._id === "" || - typeof that._attachment !== "string" || - that._attachment === "" || - !Array.isArray(that._free) || - !Array.isArray(that._indexing) || - typeof that._location !== 'object' || - Array.isArray(that._location) || - !Array.isArray(that._database) || - that._indexing.length === 0) { - throw new TypeError("Corrupted Index"); - } - for (id in that._location) { - if (that._location.hasOwnProperty(id)) { - database_meta = that._database[that._location[id]]; - if (type(database_meta) !== "Object" || - database_meta._id !== id) { - throw new TypeError("Corrupted Index"); - } - } - } - }; - - that.equals = function (json_index) { - function equalsDirection(a, b) { - var k; - for (k in a._location) { - if (a._location.hasOwnProperty(k)) { - if (b._location[k] === undefined || - JSON.stringify(b._database[b._location[k]]) !== - JSON.stringify(a._database[a._location[k]])) { - return false; - } - } - } - return true; - } - if (!equalsDirection(that, json_index)) { - return false; - } - if (!equalsDirection(json_index, that)) { - return false; - } - return true; - }; - - that.checkDocument = function (doc) { - var i, key, db_doc; - if (typeof that._location[doc._id] !== "number" || - (db_doc = that._database(that._location[doc._id])._id) !== doc._id) { - throw new TypeError("Different Index"); - } - for (i = 0; i < that._indexing.length; i += 1) { - key = that._indexing[i]; - if (doc[key] !== db_doc[key]) { - throw new TypeError("Different Index"); - } - } - }; - - /** - * Recreates database indices and remove free space - * - * @method repair - */ - that.repair = function () { - var i = 0, meta; - that._free = []; - that._location = {}; - if (type(that._database) !== "Array") { - that._database = []; - } - while (i < that._database.length) { - meta = that._database[i]; - if (type(meta) === "Object" && - typeof meta._id === "string" && meta._id !== "" && - !that._location[meta._id]) { - that._location[meta._id] = i; - i += 1; - } else { - that._database.splice(i, 1); - } - } - }; - - /** - * Returns the serialized version of this object (not cloned) - * - * @method serialized - * @return {Object} The serialized version - */ - that.serialized = function () { - return { - "indexing": that._indexing, - "free": that._free, - "location": that._location, - "database": that._database - }; - }; - - that.check(); - that._indexing_object = {}; - that._indexing.forEach(function (meta_key) { - that._indexing_object[meta_key] = true; - }); - } - - /** - * The JIO index storage constructor - */ - function indexStorage(spec, my) { - var that, priv = {}; - - that = my.basicStorage(spec, my); - - priv.indices = spec.indices; - priv.sub_storage = spec.sub_storage; - - // Overrides - - that.specToStore = function () { - return { - "indices": priv.indices, - "sub_storage": priv.sub_storage - }; - }; - - /** - * Return the similarity percentage (1 >= p >= 0) between two index lists. - * - * @method similarityPercentage - * @param {Array} list_a An index list - * @param {Array} list_b Another index list - * @return {Number} The similarity percentage - */ - priv.similarityPercentage = function (list_a, list_b) { - var ai, bi, count = 0; - for (ai = 0; ai < list_a.length; ai += 1) { - for (bi = 0; bi < list_b.length; bi += 1) { - if (list_a[ai] === list_b[bi]) { - count += 1; - } - } - } - return count / (list_a.length > list_b.length ? - list_a.length : list_b.length); - }; - - /** - * Select the good index to use according to a select list. - * - * @method selectIndex - * @param {Array} select_list An array of strings - * @return {Number} The index index - */ - priv.selectIndex = function (select_list) { - var i, tmp, selector = {"index": 0, "similarity": 0}; - for (i = 0; i < priv.indices.length; i += 1) { - tmp = priv.similarityPercentage(select_list, - priv.indices[i].index); - if (tmp > selector.similarity) { - selector.index = i; - selector.similarity = tmp; - } - } - return selector.index; - }; - - /** - * Get a database - * - * @method getIndexDatabase - * @param {Object} option The command option - * @param {Number} number The location in priv.indices - * @param {Function} callback The callback - */ - priv.getIndexDatabase = function (option, number, callback) { - that.addJob( - "getAttachment", - priv.indices[number].sub_storage || priv.sub_storage, - { - "_id": priv.indices[number].id, - "_attachment": priv.indices[number].attachment || "body" - }, - option, - function (response) { - try { - response = JSON.parse(response); - response._id = priv.indices[number].id; - response._attachment = priv.indices[number].attachment || "body"; - callback(new JSONIndex(response)); - } catch (e) { - return that.error(generateErrorObject( - e.message, - "Repair is necessary", - "corrupt" - )); - } - }, - function (err) { - if (err.status === 404) { - callback(new JSONIndex({ - "_id": priv.indices[number].id, - "_attachment": priv.indices[number].attachment || "body", - "indexing": priv.indices[number].index - })); - return; - } - err.message = "Unable to get index database."; - that.error(err); - } - ); - }; - - /** - * Gets a list containing all the databases set in the storage description. - * - * @method getIndexDatabaseList - * @param {Object} option The command option - * @param {Function} callback The result callback(database_list) - */ - priv.getIndexDatabaseList = function (option, callback) { - var i, count = 0, callbacks = {}, response_list = []; - callbacks.error = function (index) { - return function (err) { - if (err.status === 404) { - response_list[index] = new JSONIndex({ - "_id": priv.indices[index].id, - "_attachment": priv.indices[index].attachment || "body", - "indexing": priv.indices[index].index - }); - count += 1; - if (count === priv.indices.length) { - callback(response_list); - } - return; - } - err.message = "Unable to get index database."; - that.error(err); - }; - }; - callbacks.success = function (index) { - return function (response) { - try { - response = JSON.parse(response); - response._id = priv.indices[index].id; - response._attachment = priv.indices[index].attachment || "body"; - response_list[index] = new JSONIndex(response); - } catch (e) { - return that.error(generateErrorObject( - e.message, - "Repair is necessary", - "corrupt" - )); - } - count += 1; - if (count === priv.indices.length) { - callback(response_list); - } - }; - }; - for (i = 0; i < priv.indices.length; i += 1) { - that.addJob( - "getAttachment", - priv.indices[i].sub_storage || priv.sub_storage, - { - "_id": priv.indices[i].id, - "_attachment": priv.indices[i].attachment || "body" - }, - option, - callbacks.success(i), - callbacks.error(i) - ); - } - }; - - /** - * Saves all the databases to the remote(s). - * - * @method storeIndexDatabaseList - * @param {Array} database_list The database list - * @param {Object} option The command option - * @param {Function} callback The result callback(err, response) - */ - priv.storeIndexDatabaseList = function (database_list, option, callback) { - var i, count = 0, count_max = 0; - function onAttachmentResponse(response) { - count += 1; - if (count === count_max) { - callback({"ok": true}); - } - } - function onAttachmentError(err) { - err.message = "Unable to store index database."; - that.error(err); - } - function putAttachment(i) { - that.addJob( - "putAttachment", - priv.indices[i].sub_storage || priv.sub_storage, - { - "_id": database_list[i]._id, - "_attachment": database_list[i]._attachment, - "_data": JSON.stringify(database_list[i].serialized()), - "_mimetype": "application/json" - }, - option, - onAttachmentResponse, - onAttachmentError - ); - } - function post(i) { - var doc = priv.indices[i].metadata || {}; - doc._id = database_list[i]._id; - that.addJob( - "post", // with id - priv.indices[i].sub_storage || priv.sub_storage, - doc, - option, - function (response) { - putAttachment(i); - }, - function (err) { - if (err.status === 409) { - return putAttachment(i); - } - err.message = "Unable to store index database."; - that.error(err); - } - ); - } - for (i = 0; i < priv.indices.length; i += 1) { - if (database_list[i] !== undefined) { - count_max += 1; - post(i); - } - } - }; - - /** - * A generic request method which delegates the request to the sub storage. - * On response, it will index the document from the request and update all - * the databases. - * - * @method genericRequest - * @param {Command} command The JIO command - * @param {Function} method The request method - */ - priv.genericRequest = function (command, method) { - var doc = command.cloneDoc(), option = command.cloneOption(); - that.addJob( - method, - priv.sub_storage, - doc, - option, - function (response) { - switch (method) { - case "post": - case "put": - case "remove": - doc._id = response.id; - priv.getIndexDatabaseList(option, function (database_list) { - var i; - switch (method) { - case "post": - case "put": - for (i = 0; i < database_list.length; i += 1) { - database_list[i].put(doc); - } - break; - case "remove": - for (i = 0; i < database_list.length; i += 1) { - database_list[i].remove(doc); - } - break; - default: - break; - } - priv.storeIndexDatabaseList(database_list, option, function () { - that.success({"ok": true, "id": doc._id}); - }); - }); - break; - default: - that.success(response); - break; - } - }, - function (err) { - return that.error(err); - } - ); - }; - - /** - * Post the document metadata and update the index - * @method post - * @param {object} command The JIO command - */ - that.post = function (command) { - priv.genericRequest(command, 'post'); - }; - - /** - * Update the document metadata and update the index - * @method put - * @param {object} command The JIO command - */ - that.put = function (command) { - priv.genericRequest(command, 'put'); - }; - - /** - * Add an attachment to a document (no index modification) - * @method putAttachment - * @param {object} command The JIO command - */ - that.putAttachment = function (command) { - priv.genericRequest(command, 'putAttachment'); - }; - - /** - * Get the document metadata - * @method get - * @param {object} command The JIO command - */ - that.get = function (command) { - priv.genericRequest(command, 'get'); - }; - - /** - * Get the attachment. - * @method getAttachment - * @param {object} command The JIO command - */ - that.getAttachment = function (command) { - priv.genericRequest(command, 'getAttachment'); - }; - - /** - * Remove document - removing documents updates index!. - * @method remove - * @param {object} command The JIO command - */ - that.remove = function (command) { - priv.genericRequest(command, 'remove'); - }; - - /** - * Remove attachment - * @method removeAttachment - * @param {object} command The JIO command - */ - that.removeAttachment = function (command) { - priv.genericRequest(command, 'removeAttachment'); - }; - - /** - * Gets a document list from the substorage - * Options: - * - {boolean} include_docs Also retrieve the actual document content. - * @method allDocs - * @param {object} command The JIO command - */ - that.allDocs = function (command) { - var option = command.cloneOption(), - index = priv.selectIndex(option.select_list || []); - // Include docs option is ignored, if you want to get all the document, - // don't use index storage! - - option.select_list = option.select_list || []; - option.select_list.push("_id"); - priv.getIndexDatabase(option, index, function (db) { - var i, id; - db = db._database; - complex_queries.QueryFactory.create(option.query || ''). - exec(db, option); - for (i = 0; i < db.length; i += 1) { - id = db[i]._id; - delete db[i]._id; - db[i] = { - "id": id, - "key": id, - "value": db[i], - }; - } - that.success({"total_rows": db.length, "rows": db}); - }); - }; - - that.check = function (command) { - that.repair(command, true); - }; - - priv.repairIndexDatabase = function (command, index, just_check) { - var i, option = command.cloneOption(); - that.addJob( - 'allDocs', - priv.sub_storage, - {}, - {'include_docs': true}, - function (response) { - var db_list = [], db = new JSONIndex({ - "_id": command.getDocId(), - "_attachment": priv.indices[index].attachment || "body", - "indexing": priv.indices[index].index - }); - for (i = 0; i < response.rows.length; i += 1) { - db.put(response.rows[i].doc); - } - db_list[index] = db; - if (just_check) { - priv.getIndexDatabase(option, index, function (current_db) { - if (db.equals(current_db)) { - return that.success({"ok": true, "id": command.getDocId()}); - } - return that.error(generateErrorObject( - "Different Index", - "Check failed", - "corrupt index database" - )); - }); - } else { - priv.storeIndexDatabaseList(db_list, {}, function () { - that.success({"ok": true, "id": command.getDocId()}); - }); - } - }, - function (err) { - err.message = "Unable to repair the index database"; - that.error(err); - } - ); - }; - - priv.repairDocument = function (command, just_check) { - var i, option = command.cloneOption(); - that.addJob( - "get", - priv.sub_storage, - command.cloneDoc(), - {}, - function (response) { - response._id = command.getDocId(); - priv.getIndexDatabaseList(option, function (database_list) { - if (just_check) { - for (i = 0; i < database_list.length; i += 1) { - try { - database_list[i].checkDocument(response); - } catch (e) { - return that.error(generateErrorObject( - e.message, - "Check failed", - "corrupt index database" - )); - } - } - that.success({"_id": command.getDocId(), "ok": true}); - } else { - for (i = 0; i < database_list.length; i += 1) { - database_list[i].put(response); - } - priv.storeIndexDatabaseList(database_list, option, function () { - that.success({"ok": true, "id": command.getDocId()}); - }); - } - }); - }, - function (err) { - err.message = "Unable to repair document"; - return that.error(err); - } - ); - }; - - that.repair = function (command, just_check) { - var database_index = -1, i; - for (i = 0; i < priv.indices.length; i += 1) { - if (priv.indices[i].id === command.getDocId()) { - database_index = i; - break; - } - } - that.addJob( - "repair", - priv.sub_storage, - command.cloneDoc(), - command.cloneOption(), - function (response) { - if (database_index !== -1) { - priv.repairIndexDatabase(command, database_index, just_check); - } else { - priv.repairDocument(command, just_check); - } - }, - function (err) { - err.message = "Could not repair sub storage"; - that.error(err); - } - ); - }; - - return that; - } - - jIO.addStorageType("indexed", indexStorage); -})); diff --git a/src/jio.storage/ramstorage.js b/src/jio.storage/ramstorage.js deleted file mode 100644 index 9a03c9bd7b52c267fa716e85a66453d289eab48b..0000000000000000000000000000000000000000 --- a/src/jio.storage/ramstorage.js +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright 2013, Nexedi SA - * Released under the LGPL license. - * http://www.gnu.org/licenses/lgpl.html - */ - -/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ -/*global define, jIO, setTimeout, complex_queries */ - -// define([module_name], [dependencies], module); -(function (dependencies, module) { - "use strict"; - if (typeof define === 'function' && define.amd) { - return define(dependencies, module); - } - module(jIO); -}(['jio'], function (jIO) { - - var storage = {}; - - /** - * Returns 4 hexadecimal random characters. - * - * @return {String} The characters - */ - function S4() { - return ('0000' + Math.floor( - Math.random() * 0x10000 /* 65536 */ - ).toString(16)).slice(-4); - } - - /** - * An Universal Unique ID generator - * - * @return {String} The new UUID. - */ - function generateUuid() { - return S4() + S4() + "-" + - S4() + "-" + - S4() + "-" + - S4() + "-" + - S4() + S4() + S4(); - } - - /** - * Checks if an object has no enumerable keys - * - * @param {Object} obj The object - * @return {Boolean} true if empty, else false - */ - function objectIsEmpty(obj) { - var k; - for (k in obj) { - if (obj.hasOwnProperty(k)) { - return false; - } - } - return true; - } - - /** - * JIO Ram Storage. Type = 'ram'. - * Memory "database" storage. - * - * Storage Description: - * - * { - * "type": "ram", - * "namespace": <string>, // default 'default' - * } - * - * Document are stored in path - * 'namespace/document_id' like this: - * - * { - * "_id": "document_id", - * "_attachments": { - * "attachment_name": { - * "length": data_length, - * "digest": "md5-XXX", - * "content_type": "mime/type" - * }, - * "attachment_name2": {..}, ... - * }, - * "metadata_name": "metadata_value" - * "metadata_name2": ... - * ... - * } - * - * Only "_id" and "_attachments" are specific metadata keys, other one can be - * added without loss. - * - * @class RamStorage - */ - function ramStorage(spec, my) { - var that, priv = {}, ramstorage; - that = my.basicStorage(spec, my); - - /* - * Wrapper for the localStorage used to simplify instion of any kind of - * values - */ - ramstorage = { - getItem: function (item) { - var value = storage[item]; - return value === undefined ? null : JSON.parse(value); - }, - setItem: function (item, value) { - storage[item] = JSON.stringify(value); - }, - removeItem: function (item) { - delete storage[item]; - } - }; - - // attributes - if (typeof spec.namespace !== 'string') { - priv.namespace = 'default'; - } else { - priv.namespace = spec.namespace; - } - - // ===================== overrides ====================== - that.specToStore = function () { - return { - "namespace": priv.namespace - }; - }; - - that.validateState = function () { - return ''; - }; - - // ==================== commands ==================== - /** - * Create a document in local storage. - * @method post - * @param {object} command The JIO command - */ - that.post = function (command) { - setTimeout(function () { - var doc, doc_id = command.getDocId(); - if (!doc_id) { - doc_id = generateUuid(); - } - doc = ramstorage.getItem(priv.namespace + "/" + doc_id); - if (doc === null) { - // the document does not exist - doc = command.cloneDoc(); - doc._id = doc_id; - delete doc._attachments; - ramstorage.setItem(priv.namespace + "/" + doc_id, doc); - that.success({ - "ok": true, - "id": doc_id - }); - } else { - // the document already exists - that.error({ - "status": 409, - "statusText": "Conflicts", - "error": "conflicts", - "message": "Cannot create a new document", - "reason": "Document already exists" - }); - } - }); - }; - - /** - * Create or update a document in local storage. - * @method put - * @param {object} command The JIO command - */ - that.put = function (command) { - setTimeout(function () { - var doc, tmp; - doc = ramstorage.getItem(priv.namespace + "/" + command.getDocId()); - if (doc === null) { - // the document does not exist - doc = command.cloneDoc(); - delete doc._attachments; - } else { - // the document already exists - tmp = command.cloneDoc(); - tmp._attachments = doc._attachments; - doc = tmp; - } - // write - ramstorage.setItem(priv.namespace + "/" + command.getDocId(), doc); - that.success({ - "ok": true, - "id": command.getDocId() - }); - }); - }; - - /** - * Add an attachment to a document - * @method putAttachment - * @param {object} command The JIO command - */ - that.putAttachment = function (command) { - setTimeout(function () { - var doc; - doc = ramstorage.getItem(priv.namespace + "/" + command.getDocId()); - if (doc === null) { - // the document does not exist - that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": "Impossible to add attachment", - "reason": "Document not found" - }); - return; - } - - // the document already exists - doc._attachments = doc._attachments || {}; - doc._attachments[command.getAttachmentId()] = { - "content_type": command.getAttachmentMimeType(), - "digest": "md5-" + command.md5SumAttachmentData(), - "length": command.getAttachmentLength() - }; - - // upload data - ramstorage.setItem(priv.namespace + "/" + command.getDocId() + "/" + - command.getAttachmentId(), - command.getAttachmentData()); - // write document - ramstorage.setItem(priv.namespace + "/" + command.getDocId(), doc); - that.success({ - "ok": true, - "id": command.getDocId(), - "attachment": command.getAttachmentId() - }); - }); - }; - - /** - * Get a document - * @method get - * @param {object} command The JIO command - */ - that.get = function (command) { - setTimeout(function () { - var doc = ramstorage.getItem(priv.namespace + "/" + command.getDocId()); - if (doc !== null) { - that.success(doc); - } else { - that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": "Cannot find the document", - "reason": "Document does not exist" - }); - } - }); - }; - - /** - * Get a attachment - * @method getAttachment - * @param {object} command The JIO command - */ - that.getAttachment = function (command) { - setTimeout(function () { - var doc = ramstorage.getItem(priv.namespace + "/" + command.getDocId() + - "/" + command.getAttachmentId()); - if (doc !== null) { - that.success(doc); - } else { - that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": "Cannot find the attachment", - "reason": "Attachment does not exist" - }); - } - }); - }; - - /** - * Remove a document - * @method remove - * @param {object} command The JIO command - */ - that.remove = function (command) { - setTimeout(function () { - var doc, i, attachment_list; - doc = ramstorage.getItem(priv.namespace + "/" + command.getDocId()); - attachment_list = []; - if (doc !== null && typeof doc === "object") { - if (typeof doc._attachments === "object") { - // prepare list of attachments - for (i in doc._attachments) { - if (doc._attachments.hasOwnProperty(i)) { - attachment_list.push(i); - } - } - } - } else { - return that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": "Document not found", - "reason": "missing" - }); - } - ramstorage.removeItem(priv.namespace + "/" + command.getDocId()); - // delete all attachments - for (i = 0; i < attachment_list.length; i += 1) { - ramstorage.removeItem(priv.namespace + "/" + command.getDocId() + - "/" + attachment_list[i]); - } - that.success({ - "ok": true, - "id": command.getDocId() - }); - }); - }; - - /** - * Remove an attachment - * @method removeAttachment - * @param {object} command The JIO command - */ - that.removeAttachment = function (command) { - setTimeout(function () { - var doc, error, i, attachment_list; - error = function (word) { - that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": word + " not found", - "reason": "missing" - }); - }; - doc = ramstorage.getItem(priv.namespace + "/" + command.getDocId()); - // remove attachment from document - if (doc !== null && typeof doc === "object" && - typeof doc._attachments === "object") { - if (typeof doc._attachments[command.getAttachmentId()] === - "object") { - delete doc._attachments[command.getAttachmentId()]; - if (priv.objectIsEmpty(doc._attachments)) { - delete doc._attachments; - } - ramstorage.setItem(priv.namespace + "/" + command.getDocId(), - doc); - ramstorage.removeItem(priv.namespace + "/" + command.getDocId() + - "/" + command.getAttachmentId()); - that.success({ - "ok": true, - "id": command.getDocId(), - "attachment": command.getAttachmentId() - }); - } else { - error("Attachment"); - } - } else { - error("Document"); - } - }); - }; - - /** - * Get all filenames belonging to a user from the document index - * @method allDocs - * @param {object} command The JIO command - */ - that.allDocs = function (command) { - var i, row, path_re, rows = [], document_list, option, document_object; - document_list = []; - path_re = new RegExp( - "^" + complex_queries.stringEscapeRegexpCharacters(priv.namespace) + - "/[^/]+$" - ); - option = command.cloneOption(); - if (typeof complex_queries !== "object" || - (option.query === undefined && option.sort_on === undefined && - option.select_list === undefined && - option.include_docs === undefined)) { - rows = []; - for (i in storage) { - if (storage.hasOwnProperty(i)) { - // filter non-documents - if (path_re.test(i)) { - row = {"value": {}}; - row.id = i.split('/').slice(-1)[0]; - row.key = row.id; - if (command.getOption('include_docs')) { - row.doc = ramstorage.getItem(i); - } - rows.push(row); - } - } - } - that.success({"rows": rows, "total_rows": rows.length}); - } else { - // create complex query object from returned results - for (i in storage) { - if (storage.hasOwnProperty(i)) { - if (path_re.test(i)) { - document_list.push(ramstorage.getItem(i)); - } - } - } - option.select_list = option.select_list || []; - option.select_list.push("_id"); - if (option.include_docs === true) { - document_object = {}; - document_list.forEach(function (meta) { - document_object[meta._id] = meta; - }); - } - complex_queries.QueryFactory.create(option.query || ""). - exec(document_list, option); - document_list = document_list.map(function (value) { - var o = { - "id": value._id, - "key": value._id - }; - if (option.include_docs === true) { - o.doc = document_object[value._id]; - delete document_object[value._id]; - } - delete value._id; - o.value = value; - return o; - }); - that.success({"total_rows": document_list.length, - "rows": document_list}); - } - }; - - return that; - } - - jIO.addStorageType('ram', ramStorage); -})); diff --git a/src/jio.storage/replicaterevisionstorage.js b/src/jio.storage/replicaterevisionstorage.js deleted file mode 100644 index b20f70124ddc559bee41c808afbc1bd4f4355617..0000000000000000000000000000000000000000 --- a/src/jio.storage/replicaterevisionstorage.js +++ /dev/null @@ -1,692 +0,0 @@ -/*jslint indent: 2, maxlen: 80, nomen: true */ -/*global jIO, define */ - -/** - * JIO Replicate Revision Storage. - * It manages storages that manage revisions and conflicts. - * Description: - * { - * "type": "replicaterevision", - * "storage_list": [ - * <sub storage description>, - * ... - * ] - * } - */ -// define([module_name], [dependencies], module); -(function (dependencies, module) { - "use strict"; - if (typeof define === 'function' && define.amd) { - return define(dependencies, module); - } - module(jIO); -}(['jio'], function (jIO) { - "use strict"; - jIO.addStorageType('replicaterevision', function (spec, my) { - var that, priv = {}; - spec = spec || {}; - that = my.basicStorage(spec, my); - - priv.storage_list_key = "storage_list"; - priv.storage_list = spec[priv.storage_list_key]; - priv.emptyFunction = function () {}; - - that.specToStore = function () { - var o = {}; - o[priv.storage_list_key] = priv.storage_list; - return o; - }; - - /** - * Generate a new uuid - * @method generateUuid - * @return {string} The new uuid - */ - priv.generateUuid = function () { - var S4 = function () { - var i, string = Math.floor( - Math.random() * 0x10000 /* 65536 */ - ).toString(16); - for (i = string.length; i < 4; i += 1) { - string = "0" + string; - } - return string; - }; - return S4() + S4() + "-" + - S4() + "-" + - S4() + "-" + - S4() + "-" + - S4() + S4() + S4(); - }; - - /** - * Create an array containing dictionnary keys - * @method dictKeys2Array - * @param {object} dict The object to convert - * @return {array} The array of keys - */ - priv.dictKeys2Array = function (dict) { - var k, newlist = []; - for (k in dict) { - if (dict.hasOwnProperty(k)) { - newlist.push(k); - } - } - return newlist; - }; - - /** - * Checks a revision format - * @method checkRevisionFormat - * @param {string} revision The revision string - * @return {boolean} True if ok, else false - */ - priv.checkRevisionFormat = function (revision) { - return (/^[0-9]+-[0-9a-zA-Z_]+$/.test(revision)); - }; - - /** - * Clones an object in deep (without functions) - * @method clone - * @param {any} object The object to clone - * @return {any} The cloned object - */ - priv.clone = function (object) { - var tmp = JSON.stringify(object); - if (tmp === undefined) { - return undefined; - } - return JSON.parse(tmp); - }; - - /** - * Like addJob but also return the method and the index of the storage - * @method send - * @param {string} method The request method - * @param {number} index The storage index - * @param {object} doc The document object - * @param {object} option The request object - * @param {function} callback The callback. Parameters: - * - {string} The request method - * - {number} The storage index - * - {object} The error object - * - {object} The response object - */ - priv.send = function (method, index, doc, option, callback) { - var wrapped_callback_success, wrapped_callback_error; - callback = callback || priv.emptyFunction; - wrapped_callback_success = function (response) { - callback(method, index, undefined, response); - }; - wrapped_callback_error = function (err) { - callback(method, index, err, undefined); - }; - that.addJob( - method, - priv.storage_list[index], - doc, - option, - wrapped_callback_success, - wrapped_callback_error - ); - }; - - /** - * Use "send" method to all sub storages. - * Calling "callback" for each storage response. - * @method sendToAll - * @param {string} method The request method - * @param {object} doc The document object - * @param {object} option The request option - * @param {function} callback The callback. Parameters: - * - {string} The request method - * - {number} The storage index - * - {object} The error object - * - {object} The response object - */ - priv.sendToAll = function (method, doc, option, callback) { - var i; - for (i = 0; i < priv.storage_list.length; i += 1) { - priv.send(method, i, doc, option, callback); - } - }; - - /** - * Use "send" method to all sub storages. - * Calling "callback" only with the first response - * @method sendToAllFastestResponseOnly - * @param {string} method The request method - * @param {object} doc The document object - * @param {object} option The request option - * @param {function} callback The callback. Parameters: - * - {string} The request method - * - {object} The error object - * - {object} The response object - */ - priv.sendToAllFastestResponseOnly = function ( - method, - doc, - option, - callback - ) { - var i, callbackWrapper, error_count, last_error; - error_count = 0; - callbackWrapper = function (method, index, err, response) { - if (err) { - error_count += 1; - last_error = err; - if (error_count === priv.storage_list.length) { - return callback(method, err, response); - } - } - callback(method, err, response); - }; - for (i = 0; i < priv.storage_list.length; i += 1) { - priv.send(method, i, doc, option, callbackWrapper); - } - }; - - /** - * Use "sendToAll" method, calling "callback" at the last response with - * the response list - * @method sendToAllGetResponseList - * @param {string} method The request method - * @param {object} doc The document object - * @param {object} option The request option - * @return {function} callback The callback. Parameters: - * - {string} The request method - * - {object} The error object - * - {object} The response object - */ - priv.sendToAllGetResponseList = function (method, doc, option, callback) { - var wrapper, callback_count = 0, response_list = [], error_list = []; - response_list.length = priv.storage_list.length; - wrapper = function (method, index, err, response) { - error_list[index] = err; - response_list[index] = response; - callback_count += 1; - if (callback_count === priv.storage_list.length) { - callback(error_list, response_list); - } - }; - priv.sendToAll(method, doc, option, wrapper); - }; - - /** - * Checks if the sub storage are identical - * @method check - * @param {object} command The JIO command - */ - that.check = function (command) { - function callback(err, response) { - if (err) { - return that.error(err); - } - that.success(response); - } - priv.check( - command.cloneDoc(), - command.cloneOption(), - callback - ); - }; - - /** - * Repair the sub storages to make them identical - * @method repair - * @param {object} command The JIO command - */ - that.repair = function (command) { - function callback(err, response) { - if (err) { - return that.error(err); - } - that.success(response); - } - priv.repair( - command.cloneDoc(), - command.cloneOption(), - true, - callback - ); - }; - - priv.check = function (doc, option, success, error) { - priv.repair(doc, option, false, success, error); - }; - - priv.repair = function (doc, option, repair, callback) { - var functions = {}; - callback = callback || priv.emptyFunction; - option = option || {}; - functions.begin = function () { - // }; - // functions.repairAllSubStorages = function () { - var i; - for (i = 0; i < priv.storage_list.length; i += 1) { - priv.send( - repair ? "repair" : "check", - i, - doc, - option, - functions.repairAllSubStoragesCallback - ); - } - }; - functions.repair_sub_storages_count = 0; - functions.repairAllSubStoragesCallback = function (method, - index, err, response) { - if (err) { - return that.error(err); - } - functions.repair_sub_storages_count += 1; - if (functions.repair_sub_storages_count === priv.storage_list.length) { - functions.getAllDocuments(functions.newParam( - doc, - option, - repair - )); - } - }; - functions.newParam = function (doc, option, repair) { - var param = { - "doc": doc, // the document to repair - "option": option, - "repair": repair, - "responses": { - "count": 0, - "list": [ - // 0: response0 - // 1: response1 - // 2: response2 - ], - "stats": { - // responseA: [0, 1] - // responseB: [2] - }, - "stats_items": [ - // 0: [responseA, [0, 1]] - // 1: [responseB, [2]] - ], - "attachments": { - // attachmentA : {_id: attachmentA, _revs_info, _mimetype: ..} - // attachmentB : {_id: attachmentB, _revs_info, _mimetype: ..} - } - }, - "conflicts": { - // revC: true - // revD: true - }, - "deal_result_state": "ok", - "my_rev": undefined - }; - param.responses.list.length = priv.storage_list.length; - return param; - }; - functions.getAllDocuments = function (param) { - var i, doc = priv.clone(param.doc), option = priv.clone(param.option); - option.conflicts = true; - option.revs = true; - option.revs_info = true; - for (i = 0; i < priv.storage_list.length; i += 1) { - // if the document is not loaded - priv.send("get", i, doc, option, functions.dealResults(param)); - } - functions.finished_count += 1; - }; - functions.dealResults = function (param) { - return function (method, index, err, response) { - var response_object = {}; - if (param.deal_result_state !== "ok") { - // deal result is in a wrong state, exit - return; - } - if (err) { - if (err.status !== 404) { - // get document failed, exit - param.deal_result_state = "error"; - callback({ - "status": 40, - "statusText": "Check Failed", - "error": "check_failed", - "message": "An error occured on the sub storage", - "reason": err.reason - }, undefined); - return; - } - } - // success to get the document - // add the response in memory - param.responses.count += 1; - param.responses.list[index] = response; - - // add the conflicting revision for other synchronizations - functions.addConflicts(param, (response || {})._conflicts); - if (param.responses.count !== param.responses.list.length) { - // this is not the last response, wait for the next response - return; - } - - // this is now the last response - functions.makeResponsesStats(param.responses); - if (param.responses.stats_items.length === 1) { - // the responses are equals! - response_object.ok = true; - response_object.id = param.doc._id; - if (doc._rev) { - response_object.rev = doc._rev; - // "rev": (typeof param.responses.list[0] === "object" ? - // param.responses.list[0]._rev : undefined) - } - callback(undefined, response_object); - return; - } - // the responses are different - if (param.repair === false) { - // do not repair - callback({ - "status": 41, - "statusText": "Check Not Ok", - "error": "check_not_ok", - "message": "Some documents are different in the sub storages", - "reason": "Storage contents differ" - }, undefined); - return; - } - // repair - functions.getAttachments(param); - }; - }; - functions.addConflicts = function (param, list) { - var i; - list = list || []; - for (i = 0; i < list.length; i += 1) { - param.conflicts[list[i]] = true; - } - }; - functions.makeResponsesStats = function (responses) { - var i, str_response; - for (i = 0; i < responses.count; i += 1) { - str_response = JSON.stringify(responses.list[i]); - if (responses.stats[str_response] === undefined) { - responses.stats[str_response] = []; - responses.stats_items.push([ - str_response, - responses.stats[str_response] - ]); - } - responses.stats[str_response].push(i); - } - }; - functions.getAttachments = function (param) { - var response, parsed_response, attachment; - for (response in param.responses.stats) { - if (param.responses.stats.hasOwnProperty(response)) { - parsed_response = JSON.parse(response); - for (attachment in parsed_response._attachments) { - if ((parsed_response._attachments).hasOwnProperty(attachment)) { - functions.get_attachment_count += 1; - priv.send( - "getAttachment", - param.responses.stats[response][0], - { - "_id": param.doc._id, - "_attachment": attachment, - "_rev": JSON.parse(response)._rev - }, - param.option, - functions.getAttachmentsCallback( - param, - attachment, - param.responses.stats[response] - ) - ); - } - } - } - } - }; - functions.get_attachment_count = 0; - functions.getAttachmentsCallback = function ( - param, - attachment_id, - index_list - ) { - return function (method, index, err, response) { - if (err) { - callback({ - "status": 40, - "statusText": "Check Failed", - "error": "check_failed", - "message": "Unable to retreive attachments", - "reason": err.reason - }, undefined); - return; - } - functions.get_attachment_count -= 1; - param.responses.attachments[attachment_id] = response; - if (functions.get_attachment_count === 0) { - functions.synchronizeAllSubStorage(param); - if (param.option.synchronize_conflicts !== false) { - functions.synchronizeConflicts(param); - } - } - }; - }; - functions.synchronizeAllSubStorage = function (param) { - var i, j, len = param.responses.stats_items.length; - for (i = 0; i < len; i += 1) { - // browsing responses - for (j = 0; j < len; j += 1) { - // browsing storage list - if (i !== j) { - functions.synchronizeResponseToSubStorage( - param, - param.responses.stats_items[i][0], - param.responses.stats_items[j][1] - ); - } - } - } - functions.finished_count -= 1; - }; - functions.synchronizeResponseToSubStorage = function ( - param, - response, - storage_list - ) { - var i, new_doc, attachment_to_put = []; - if (response === undefined) { - // no response to sync - return; - } - new_doc = JSON.parse(response); - new_doc._revs = new_doc._revisions; - delete new_doc._rev; - delete new_doc._revisions; - delete new_doc._conflicts; - for (i in new_doc._attachments) { - if (new_doc._attachments.hasOwnProperty(i)) { - attachment_to_put.push({ - "_id": i, - "_mimetype": new_doc._attachments[i].content_type, - "_revs_info": new_doc._revs_info - }); - } - } - for (i = 0; i < storage_list.length; i += 1) { - functions.finished_count += attachment_to_put.length || 1; - priv.send( - "put", - storage_list[i], - new_doc, - param.option, - functions.putAttachments(param, attachment_to_put) - ); - } - functions.finished_count += 1; - functions.finished(); - }; - functions.synchronizeConflicts = function (param) { - var rev, new_doc, new_option; - new_option = priv.clone(param.option); - new_option.synchronize_conflict = false; - for (rev in param.conflicts) { - if (param.conflicts.hasOwnProperty(rev)) { - new_doc = priv.clone(param.doc); - new_doc._rev = rev; - // no need to synchronize all the conflicts again, do it once - functions.getAllDocuments(functions.newParam( - new_doc, - new_option, - param.repair - )); - } - } - }; - functions.putAttachments = function (param, attachment_to_put) { - return function (method, index, err, response) { - var i, attachment; - if (err) { - return callback({ - "status": 40, - "statusText": "Check Failed", - "error": "check_failed", - "message": "Unable to copy attachments", - "reason": err.reason - }, undefined); - } - for (i = 0; i < attachment_to_put.length; i += 1) { - attachment = { - "_id": param.doc._id, - "_attachment": attachment_to_put[i]._id, - "_mimetype": attachment_to_put[i]._mimetype, - "_revs_info": attachment_to_put[i]._revs_info, - // "_revs_info": param.responses.list[index]._revs_info, - "_data": param.responses.attachments[attachment_to_put[i]._id] - }; - priv.send( - "putAttachment", - index, - attachment, - option, - functions.putAttachmentCallback(param) - ); - } - if (attachment_to_put.length === 0) { - functions.finished(); - } - }; - }; - functions.putAttachmentCallback = function (param) { - return function (method, index, err, response) { - if (err) { - return callback(err, undefined); - } - functions.finished(); - }; - }; - functions.finished_count = 0; - functions.finished = function () { - var response_object = {}; - functions.finished_count -= 1; - if (functions.finished_count === 0) { - response_object.ok = true; - response_object.id = doc._id; - if (doc._rev) { - response_object.rev = doc._rev; - } - callback(undefined, response_object); - } - }; - functions.begin(); - }; - - /** - * The generic method to use - * @method genericRequest - * @param {object} command The JIO command - * @param {string} method The method to use - */ - that.genericRequest = function (command, method) { - var doc = command.cloneDoc(); - doc._id = doc._id || priv.generateUuid(); - priv.sendToAllFastestResponseOnly( - method, - doc, - command.cloneOption(), - function (method, err, response) { - if (err) { - return that.error(err); - } - that.success(response); - } - ); - }; - - /** - * Post the document metadata to all sub storages - * @method post - * @param {object} command The JIO command - */ - that.post = function (command) { - that.genericRequest(command, "put"); - }; - - /** - * Put the document metadata to all sub storages - * @method put - * @param {object} command The JIO command - */ - that.put = function (command) { - that.genericRequest(command, "post"); - }; - - /** - * Put an attachment to a document to all sub storages - * @method putAttachment - * @param {object} command The JIO command - */ - that.putAttachment = function (command) { - that.genericRequest(command, "putAttachment"); - }; - - /** - * Get the document from all sub storages, get the fastest. - * @method get - * @param {object} command The JIO command - */ - that.get = function (command) { - that.genericRequest(command, "get"); - }; - - /** - * Get the attachment from all sub storages, get the fastest. - * @method getAttachment - * @param {object} command The JIO command - */ - that.getAttachment = function (command) { - that.genericRequest(command, "getAttachment"); - }; - - /** - * Remove the document from all sub storages. - * @method remove - * @param {object} command The JIO command - */ - that.remove = function (command) { - that.genericRequest(command, "remove"); - }; - - /** - * Remove the attachment from all sub storages. - * @method remove - * @param {object} command The JIO command - */ - that.removeAttachment = function (command) { - that.genericRequest(command, "removeAttachment"); - }; - - return that; - }); -})); diff --git a/src/jio.storage/replicatestorage.js b/src/jio.storage/replicatestorage.js deleted file mode 100644 index 50583e158d0b5bcf785ee63e49901ecd5d594f3b..0000000000000000000000000000000000000000 --- a/src/jio.storage/replicatestorage.js +++ /dev/null @@ -1,110 +0,0 @@ -/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ -/*global jIO: true */ -jIO.addStorageType('replicate', function (spec, my) { - - var that, cloned_option, priv = {}, - super_serialized = that.serialized; - - spec = spec || {}; - that = my.basicStorage(spec, my); - - priv.return_value_array = []; - priv.storagelist = spec.storagelist || []; - priv.nb_storage = priv.storagelist.length; - - that.serialized = function () { - var o = super_serialized(); - o.storagelist = priv.storagelist; - return o; - }; - - that.validateState = function () { - if (priv.storagelist.length === 0) { - return 'Need at least one parameter: "storagelist" ' + - 'containing at least one storage.'; - } - return ''; - }; - - priv.isTheLast = function (error_array) { - return (error_array.length === priv.nb_storage); - }; - - priv.doJob = function (command, errormessage, nodocid) { - var done = false, - error_array = [], - i, - error = function (err) { - if (!done) { - error_array.push(err); - if (priv.isTheLast(error_array)) { - that.error({ - status: 207, - statusText: 'Multi-Status', - error: 'multi_status', - message: 'All ' + errormessage + (!nodocid ? ' "' + - command.getDocId() + '"' : ' ') + ' requests have failed.', - reason: 'requests fail', - array: error_array - }); - } - } - }, - success = function (val) { - if (!done) { - done = true; - that.success(val); - } - }; - for (i = 0; i < priv.nb_storage; i += 1) { - cloned_option = command.cloneOption(); - that.addJob(command.getLabel(), priv.storagelist[i], - command.cloneDoc(), cloned_option, success, error); - } - }; - - that.post = function (command) { - priv.doJob(command, 'post'); - that.end(); - }; - - /** - * Save a document in several storages. - * @method put - */ - that.put = function (command) { - priv.doJob(command, 'put'); - that.end(); - }; - - /** - * Load a document from several storages, and send the first retreived - * document. - * @method get - */ - that.get = function (command) { - priv.doJob(command, 'get'); - that.end(); - }; - - /** - * Get a document list from several storages, and returns the first - * retreived document list. - * @method allDocs - */ - that.allDocs = function (command) { - priv.doJob(command, 'allDocs', true); - that.end(); - }; - - /** - * Remove a document from several storages. - * @method remove - */ - that.remove = function (command) { - priv.doJob(command, 'remove'); - that.end(); - }; - - return that; -}); diff --git a/src/jio.storage/revisionstorage.js b/src/jio.storage/revisionstorage.js deleted file mode 100644 index 12075087ab1a5f9a094dd8ad10135a334b1d8692..0000000000000000000000000000000000000000 --- a/src/jio.storage/revisionstorage.js +++ /dev/null @@ -1,1034 +0,0 @@ -/*jslint indent: 2, maxlen: 80, nomen: true */ -/*global jIO, hex_sha256, setTimeout, define */ - -/** - * JIO Revision Storage. - * It manages document version and can generate conflicts. - * Description: - * { - * "type": "revision", - * "sub_storage": <sub storage description> - * } - */ -// define([module_name], [dependencies], module); -(function (dependencies, module) { - "use strict"; - if (typeof define === 'function' && define.amd) { - return define(dependencies, module); - } - module(jIO, {hex_sha256: hex_sha256}); -}(['jio', 'sha256'], function (jIO, sha256) { - "use strict"; - - jIO.addStorageType("revision", function (spec, my) { - - var that = {}, priv = {}; - spec = spec || {}; - that = my.basicStorage(spec, my); - // ATTRIBUTES // - priv.doc_tree_suffix = ".revision_tree.json"; - priv.sub_storage = spec.sub_storage; - // METHODS // - /** - * Description to store in order to be restored later - * @method specToStore - * @return {object} Descriptions to store - */ - that.specToStore = function () { - return { - "sub_storage": priv.sub_storage - }; - }; - - /** - * Clones an object in deep (without functions) - * @method clone - * @param {any} object The object to clone - * @return {any} The cloned object - */ - priv.clone = function (object) { - var tmp = JSON.stringify(object); - if (tmp === undefined) { - return undefined; - } - return JSON.parse(tmp); - }; - - /** - * Generate a new uuid - * @method generateUuid - * @return {string} The new uuid - */ - priv.generateUuid = function () { - var S4 = function () { - /* 65536 */ - var i, string = Math.floor( - Math.random() * 0x10000 - ).toString(16); - for (i = string.length; i < 4; i += 1) { - string = '0' + string; - } - return string; - }; - return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + - S4() + S4(); - }; - - /** - * Generates a hash code of a string - * @method hashCode - * @param {string} string The string to hash - * @return {string} The string hash code - */ - priv.hashCode = function (string) { - return sha256.hex_sha256(string); - }; - - /** - * Checks a revision format - * @method checkDocumentRevisionFormat - * @param {object} doc The document object - * @return {object} null if ok, else error object - */ - priv.checkDocumentRevisionFormat = function (doc) { - var send_error = function (message) { - return { - "status": 31, - "statusText": "Wrong Revision Format", - "error": "wrong_revision_format", - "message": message, - "reason": "Revision is wrong" - }; - }; - if (typeof doc._rev === "string") { - if (/^[0-9]+-[0-9a-zA-Z]+$/.test(doc._rev) === false) { - return send_error("The document revision does not match " + - "^[0-9]+-[0-9a-zA-Z]+$"); - } - } - if (typeof doc._revs === "object") { - if (typeof doc._revs.start !== "number" || - typeof doc._revs.ids !== "object" || - typeof doc._revs.ids.length !== "number") { - return send_error( - "The document revision history is not well formated" - ); - } - } - if (typeof doc._revs_info === "object") { - if (typeof doc._revs_info.length !== "number") { - return send_error("The document revision information " + - "is not well formated"); - } - } - }; - - /** - * Creates a new document tree - * @method newDocTree - * @return {object} The new document tree - */ - priv.newDocTree = function () { - return {"children": []}; - }; - - /** - * Convert revs_info to a simple revisions history - * @method revsInfoToHistory - * @param {array} revs_info The revs info - * @return {object} The revisions history - */ - priv.revsInfoToHistory = function (revs_info) { - var i, revisions = { - "start": 0, - "ids": [] - }; - revs_info = revs_info || []; - if (revs_info.length > 0) { - revisions.start = parseInt(revs_info[0].rev.split('-')[0], 10); - } - for (i = 0; i < revs_info.length; i += 1) { - revisions.ids.push(revs_info[i].rev.split('-')[1]); - } - return revisions; - }; - - /** - * Convert the revision history object to an array of revisions. - * @method revisionHistoryToList - * @param {object} revs The revision history - * @return {array} The revision array - */ - priv.revisionHistoryToList = function (revs) { - var i, start = revs.start, new_list = []; - for (i = 0; i < revs.ids.length; i += 1, start -= 1) { - new_list.push(start + "-" + revs.ids[i]); - } - return new_list; - }; - - /** - * Convert revision list to revs info. - * @method revisionListToRevsInfo - * @param {array} revision_list The revision list - * @param {object} doc_tree The document tree - * @return {array} The document revs info - */ - priv.revisionListToRevsInfo = function (revision_list, doc_tree) { - var revisionListToRevsInfoRec, revs_info = [], j; - for (j = 0; j < revision_list.length; j += 1) { - revs_info.push({"rev": revision_list[j], "status": "missing"}); - } - revisionListToRevsInfoRec = function (index, doc_tree) { - var child, i; - if (index < 0) { - return; - } - for (i = 0; i < doc_tree.children.length; i += 1) { - child = doc_tree.children[i]; - if (child.rev === revision_list[index]) { - revs_info[index].status = child.status; - revisionListToRevsInfoRec(index - 1, child); - } - } - }; - revisionListToRevsInfoRec(revision_list.length - 1, doc_tree); - return revs_info; - }; - - /** - * Update a document metadata revision properties - * @method fillDocumentRevisionProperties - * @param {object} doc The document object - * @param {object} doc_tree The document tree - */ - priv.fillDocumentRevisionProperties = function (doc, doc_tree) { - if (doc._revs_info) { - doc._revs = priv.revsInfoToHistory(doc._revs_info); - } else if (doc._revs) { - doc._revs_info = priv.revisionListToRevsInfo( - priv.revisionHistoryToList(doc._revs), - doc_tree - ); - } else if (doc._rev) { - doc._revs_info = priv.getRevisionInfo(doc._rev, doc_tree); - doc._revs = priv.revsInfoToHistory(doc._revs_info); - } else { - doc._revs_info = []; - doc._revs = {"start": 0, "ids": []}; - } - if (doc._revs.start > 0) { - doc._rev = doc._revs.start + "-" + doc._revs.ids[0]; - } else { - delete doc._rev; - } - }; - - /** - * Generates the next revision of a document. - * @methode generateNextRevision - * @param {object} doc The document metadata - * @param {boolean} deleted_flag The deleted flag - * @return {array} 0:The next revision number and 1:the hash code - */ - priv.generateNextRevision = function (doc, deleted_flag) { - var string, revision_history, revs_info, pseudo_revision; - doc = priv.clone(doc) || {}; - revision_history = doc._revs; - revs_info = doc._revs_info; - delete doc._rev; - delete doc._revs; - delete doc._revs_info; - string = JSON.stringify(doc) + JSON.stringify(revision_history) + - JSON.stringify(deleted_flag ? true : false); - revision_history.start += 1; - revision_history.ids.unshift(priv.hashCode(string)); - doc._revs = revision_history; - doc._rev = revision_history.start + "-" + revision_history.ids[0]; - revs_info.unshift({ - "rev": doc._rev, - "status": deleted_flag ? "deleted" : "available" - }); - doc._revs_info = revs_info; - return doc; - }; - - /** - * Gets the revs info from the document tree - * @method getRevisionInfo - * @param {string} revision The revision to search for - * @param {object} doc_tree The document tree - * @return {array} The revs info - */ - priv.getRevisionInfo = function (revision, doc_tree) { - var getRevisionInfoRec; - getRevisionInfoRec = function (doc_tree) { - var i, child, revs_info; - for (i = 0; i < doc_tree.children.length; i += 1) { - child = doc_tree.children[i]; - if (child.rev === revision) { - return [{"rev": child.rev, "status": child.status}]; - } - revs_info = getRevisionInfoRec(child); - if (revs_info.length > 0 || revision === undefined) { - revs_info.push({"rev": child.rev, "status": child.status}); - return revs_info; - } - } - return []; - }; - return getRevisionInfoRec(doc_tree); - }; - - priv.updateDocumentTree = function (doc, doc_tree) { - var revs_info, updateDocumentTreeRec, next_rev; - doc = priv.clone(doc); - revs_info = doc._revs_info; - updateDocumentTreeRec = function (doc_tree, revs_info) { - var i, child, info; - if (revs_info.length === 0) { - return; - } - info = revs_info.pop(); - for (i = 0; i < doc_tree.children.length; i += 1) { - child = doc_tree.children[i]; - if (child.rev === info.rev) { - return updateDocumentTreeRec(child, revs_info); - } - } - doc_tree.children.unshift({ - "rev": info.rev, - "status": info.status, - "children": [] - }); - updateDocumentTreeRec(doc_tree.children[0], revs_info); - }; - updateDocumentTreeRec(doc_tree, priv.clone(revs_info)); - }; - - priv.send = function (method, doc, option, callback) { - that.addJob( - method, - priv.sub_storage, - doc, - option, - function (success) { - callback(undefined, success); - }, - function (err) { - callback(err, undefined); - } - ); - }; - - priv.getWinnerRevsInfo = function (doc_tree) { - var revs_info = [], getWinnerRevsInfoRec; - getWinnerRevsInfoRec = function (doc_tree, tmp_revs_info) { - var i; - if (doc_tree.rev) { - tmp_revs_info.unshift({ - "rev": doc_tree.rev, - "status": doc_tree.status - }); - } - if (doc_tree.children.length === 0) { - if (revs_info.length === 0 || - (revs_info[0].status !== "available" && - tmp_revs_info[0].status === "available") || - (tmp_revs_info[0].status === "available" && - revs_info.length < tmp_revs_info.length)) { - revs_info = priv.clone(tmp_revs_info); - } - } - for (i = 0; i < doc_tree.children.length; i += 1) { - getWinnerRevsInfoRec(doc_tree.children[i], tmp_revs_info); - } - tmp_revs_info.shift(); - }; - getWinnerRevsInfoRec(doc_tree, []); - return revs_info; - }; - - priv.getConflicts = function (revision, doc_tree) { - var conflicts = [], getConflictsRec; - getConflictsRec = function (doc_tree) { - var i; - if (doc_tree.rev === revision) { - return; - } - if (doc_tree.children.length === 0) { - if (doc_tree.status !== "deleted") { - conflicts.push(doc_tree.rev); - } - } - for (i = 0; i < doc_tree.children.length; i += 1) { - getConflictsRec(doc_tree.children[i]); - } - }; - getConflictsRec(doc_tree); - return conflicts.length === 0 ? undefined : conflicts; - }; - - priv.get = function (doc, option, callback) { - priv.send("get", doc, option, callback); - }; - priv.put = function (doc, option, callback) { - priv.send("put", doc, option, callback); - }; - priv.remove = function (doc, option, callback) { - priv.send("remove", doc, option, callback); - }; - priv.getAttachment = function (attachment, option, callback) { - priv.send("getAttachment", attachment, option, callback); - }; - priv.putAttachment = function (attachment, option, callback) { - priv.send("putAttachment", attachment, option, callback); - }; - priv.removeAttachment = function (attachment, option, callback) { - priv.send("removeAttachment", attachment, option, callback); - }; - - priv.getDocument = function (doc, option, callback) { - doc = priv.clone(doc); - doc._id = doc._id + "." + doc._rev; - delete doc._attachment; - delete doc._rev; - delete doc._revs; - delete doc._revs_info; - priv.get(doc, option, callback); - }; - priv.putDocument = function (doc, option, callback) { - doc = priv.clone(doc); - doc._id = doc._id + "." + doc._rev; - delete doc._attachment; - delete doc._data; - delete doc._mimetype; - delete doc._rev; - delete doc._revs; - delete doc._revs_info; - priv.put(doc, option, callback); - }; - - priv.getRevisionTree = function (doc, option, callback) { - doc = priv.clone(doc); - doc._id = doc._id + priv.doc_tree_suffix; - priv.get(doc, option, callback); - }; - - priv.getAttachmentList = function (doc, option, callback) { - var attachment_id, dealResults, state = "ok", result_list = [], count = 0; - dealResults = function (attachment_id, attachment_meta) { - return function (err, attachment) { - if (state !== "ok") { - return; - } - count -= 1; - if (err) { - if (err.status === 404) { - result_list.push(undefined); - } else { - state = "error"; - return callback(err, undefined); - } - } - result_list.push({ - "_attachment": attachment_id, - "_data": attachment, - "_mimetype": attachment_meta.content_type - }); - if (count === 0) { - state = "finished"; - callback(undefined, result_list); - } - }; - }; - for (attachment_id in doc._attachments) { - if (doc._attachments.hasOwnProperty(attachment_id)) { - count += 1; - priv.getAttachment( - {"_id": doc._id, "_attachment": attachment_id}, - option, - dealResults(attachment_id, doc._attachments[attachment_id]) - ); - } - } - if (count === 0) { - callback(undefined, []); - } - }; - - priv.putAttachmentList = function (doc, option, attachment_list, callback) { - var i, dealResults, state = "ok", count = 0, attachment; - attachment_list = attachment_list || []; - dealResults = function (index) { - return function (err, response) { - if (state !== "ok") { - return; - } - count -= 1; - if (err) { - state = "error"; - return callback(err, undefined); - } - if (count === 0) { - state = "finished"; - callback(undefined, {"id": doc._id, "ok": true}); - } - }; - }; - for (i = 0; i < attachment_list.length; i += 1) { - attachment = attachment_list[i]; - if (attachment !== undefined) { - count += 1; - attachment._id = doc._id + "." + doc._rev; - priv.putAttachment(attachment, option, dealResults(i)); - } - } - if (count === 0) { - return callback(undefined, {"id": doc._id, "ok": true}); - } - }; - - priv.putDocumentTree = function (doc, option, doc_tree, callback) { - doc_tree = priv.clone(doc_tree); - doc_tree._id = doc._id + priv.doc_tree_suffix; - priv.put(doc_tree, option, callback); - }; - - priv.notFoundError = function (message, reason) { - return { - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": message, - "reason": reason - }; - }; - - priv.conflictError = function (message, reason) { - return { - "status": 409, - "statusText": "Conflict", - "error": "conflict", - "message": message, - "reason": reason - }; - }; - - priv.revisionGenericRequest = function (doc, option, - specific_parameter, onEnd) { - var prev_doc, doc_tree, attachment_list, callback = {}; - if (specific_parameter.doc_id) { - doc._id = specific_parameter.doc_id; - } - if (specific_parameter.attachment_id) { - doc._attachment = specific_parameter.attachment_id; - } - callback.begin = function () { - var check_error; - doc._id = doc._id || priv.generateUuid(); - if (specific_parameter.revision_needed && !doc._rev) { - return onEnd(priv.conflictError( - "Document update conflict", - "No document revision was provided" - ), undefined); - } - // check revision format - check_error = priv.checkDocumentRevisionFormat(doc); - if (check_error !== undefined) { - return onEnd(check_error, undefined); - } - priv.getRevisionTree(doc, option, callback.getRevisionTree); - }; - callback.getRevisionTree = function (err, response) { - var winner_info, previous_revision, generate_new_revision; - previous_revision = doc._rev; - generate_new_revision = doc._revs || doc._revs_info ? false : true; - if (err) { - if (err.status !== 404) { - err.message = "Cannot get document revision tree"; - return onEnd(err, undefined); - } - } - doc_tree = response || priv.newDocTree(); - if (specific_parameter.get || specific_parameter.getAttachment) { - if (!doc._rev) { - winner_info = priv.getWinnerRevsInfo(doc_tree); - if (winner_info.length === 0) { - return onEnd(priv.notFoundError( - "Document not found", - "missing" - ), undefined); - } - if (winner_info[0].status === "deleted") { - return onEnd(priv.notFoundError( - "Document not found", - "deleted" - ), undefined); - } - doc._rev = winner_info[0].rev; - } - priv.fillDocumentRevisionProperties(doc, doc_tree); - return priv.getDocument(doc, option, callback.getDocument); - } - priv.fillDocumentRevisionProperties(doc, doc_tree); - if (generate_new_revision) { - if (previous_revision && doc._revs_info.length === 0) { - // the document history has changed, it means that the document - // revision was wrong. Add a pseudo history to the document - doc._rev = previous_revision; - doc._revs = { - "start": parseInt(previous_revision.split("-")[0], 10), - "ids": [previous_revision.split("-")[1]] - }; - doc._revs_info = [{"rev": previous_revision, "status": "missing"}]; - } - doc = priv.generateNextRevision( - doc, - specific_parameter.remove - ); - } - if (doc._revs_info.length > 1) { - prev_doc = { - "_id": doc._id, - "_rev": doc._revs_info[1].rev - }; - if (!generate_new_revision && specific_parameter.putAttachment) { - prev_doc._rev = doc._revs_info[0].rev; - } - } - // force revs_info status - doc._revs_info[0].status = (specific_parameter.remove ? - "deleted" : "available"); - priv.updateDocumentTree(doc, doc_tree); - if (prev_doc) { - return priv.getDocument(prev_doc, option, callback.getDocument); - } - if (specific_parameter.remove || specific_parameter.removeAttachment) { - return onEnd(priv.notFoundError( - "Unable to remove an inexistent document", - "missing" - ), undefined); - } - priv.putDocument(doc, option, callback.putDocument); - }; - callback.getDocument = function (err, res_doc) { - var k, conflicts; - if (err) { - if (err.status === 404) { - if (specific_parameter.remove || - specific_parameter.removeAttachment) { - return onEnd(priv.conflictError( - "Document update conflict", - "Document is missing" - ), undefined); - } - if (specific_parameter.get) { - return onEnd(priv.notFoundError( - "Unable to find the document", - "missing" - ), undefined); - } - res_doc = {}; - } else { - err.message = "Cannot get document"; - return onEnd(err, undefined); - } - } - if (specific_parameter.get) { - res_doc._id = doc._id; - res_doc._rev = doc._rev; - if (option.conflicts === true) { - conflicts = priv.getConflicts(doc._rev, doc_tree); - if (conflicts) { - res_doc._conflicts = conflicts; - } - } - if (option.revs === true) { - res_doc._revisions = doc._revs; - } - if (option.revs_info === true) { - res_doc._revs_info = doc._revs_info; - } - return onEnd(undefined, res_doc); - } - if (specific_parameter.putAttachment || - specific_parameter.removeAttachment) { - // copy metadata (not beginning by "_" to document - for (k in res_doc) { - if (res_doc.hasOwnProperty(k) && !k.match("^_")) { - doc[k] = res_doc[k]; - } - } - } - if (specific_parameter.remove) { - priv.putDocumentTree(doc, option, doc_tree, callback.putDocumentTree); - } else { - priv.getAttachmentList(res_doc, option, callback.getAttachmentList); - } - }; - callback.getAttachmentList = function (err, res_list) { - var i, attachment_found = false; - if (err) { - err.message = "Cannot get attachment"; - return onEnd(err, undefined); - } - attachment_list = res_list || []; - if (specific_parameter.getAttachment) { - // getting specific attachment - for (i = 0; i < attachment_list.length; i += 1) { - if (attachment_list[i] && - doc._attachment === - attachment_list[i]._attachment) { - return onEnd(undefined, attachment_list[i]._data); - } - } - return onEnd(priv.notFoundError( - "Unable to get an inexistent attachment", - "missing" - ), undefined); - } - if (specific_parameter.remove_from_attachment_list) { - // removing specific attachment - for (i = 0; i < attachment_list.length; i += 1) { - if (attachment_list[i] && - specific_parameter.remove_from_attachment_list._attachment === - attachment_list[i]._attachment) { - attachment_found = true; - attachment_list[i] = undefined; - break; - } - } - if (!attachment_found) { - return onEnd(priv.notFoundError( - "Unable to remove an inexistent attachment", - "missing" - ), undefined); - } - } - priv.putDocument(doc, option, callback.putDocument); - }; - callback.putDocument = function (err, response) { - var i, attachment_found = false; - if (err) { - err.message = "Cannot post the document"; - return onEnd(err, undefined); - } - if (specific_parameter.add_to_attachment_list) { - // adding specific attachment - attachment_list = attachment_list || []; - for (i = 0; i < attachment_list.length; i += 1) { - if (attachment_list[i] && - specific_parameter.add_to_attachment_list._attachment === - attachment_list[i]._attachment) { - attachment_found = true; - attachment_list[i] = specific_parameter.add_to_attachment_list; - break; - } - } - if (!attachment_found) { - attachment_list.unshift(specific_parameter.add_to_attachment_list); - } - } - priv.putAttachmentList( - doc, - option, - attachment_list, - callback.putAttachmentList - ); - }; - callback.putAttachmentList = function (err, response) { - if (err) { - err.message = "Cannot copy attacments to the document"; - return onEnd(err, undefined); - } - priv.putDocumentTree(doc, option, doc_tree, callback.putDocumentTree); - }; - callback.putDocumentTree = function (err, response) { - var response_object; - if (err) { - err.message = "Cannot update the document history"; - return onEnd(err, undefined); - } - response_object = { - "ok": true, - "id": doc._id, - "rev": doc._rev - }; - if (specific_parameter.putAttachment || - specific_parameter.removeAttachment || - specific_parameter.getAttachment) { - response_object.attachment = doc._attachment; - } - onEnd(undefined, response_object); - // if (option.keep_revision_history !== true) { - // // priv.remove(prev_doc, option, function () { - // // - change "available" status to "deleted" - // // - remove attachments - // // - done, no callback - // // }); - // } - }; - callback.begin(); - }; - - /** - * Post the document metadata and create or update a document tree. - * Options: - * - {boolean} keep_revision_history To keep the previous revisions - * (false by default) (NYI). - * @method post - * @param {object} command The JIO command - */ - that.post = function (command) { - priv.revisionGenericRequest( - command.cloneDoc(), - command.cloneOption(), - {}, - function (err, response) { - if (err) { - return that.error(err); - } - that.success(response); - } - ); - }; - - /** - * Put the document metadata and create or update a document tree. - * Options: - * - {boolean} keep_revision_history To keep the previous revisions - * (false by default) (NYI). - * @method put - * @param {object} command The JIO command - */ - that.put = function (command) { - priv.revisionGenericRequest( - command.cloneDoc(), - command.cloneOption(), - {}, - function (err, response) { - if (err) { - return that.error(err); - } - that.success(response); - } - ); - }; - - - that.putAttachment = function (command) { - priv.revisionGenericRequest( - command.cloneDoc(), - command.cloneOption(), - { - "doc_id": command.getDocId(), - "attachment_id": command.getAttachmentId(), - "add_to_attachment_list": { - "_attachment": command.getAttachmentId(), - "_mimetype": command.getAttachmentMimeType(), - "_data": command.getAttachmentData() - }, - "putAttachment": true - }, - function (err, response) { - if (err) { - return that.error(err); - } - that.success(response); - } - ); - }; - - that.remove = function (command) { - if (command.getAttachmentId()) { - return that.removeAttachment(command); - } - priv.revisionGenericRequest( - command.cloneDoc(), - command.cloneOption(), - { - "revision_needed": true, - "remove": true - }, - function (err, response) { - if (err) { - return that.error(err); - } - that.success(response); - } - ); - }; - - that.removeAttachment = function (command) { - priv.revisionGenericRequest( - command.cloneDoc(), - command.cloneOption(), - { - "doc_id": command.getDocId(), - "attachment_id": command.getAttachmentId(), - "revision_needed": true, - "removeAttachment": true, - "remove_from_attachment_list": { - "_attachment": command.getAttachmentId() - } - }, - function (err, response) { - if (err) { - return that.error(err); - } - that.success(response); - } - ); - }; - - that.get = function (command) { - if (command.getAttachmentId()) { - return that.getAttachment(command); - } - priv.revisionGenericRequest( - command.cloneDoc(), - command.cloneOption(), - { - "get": true - }, - function (err, response) { - if (err) { - return that.error(err); - } - that.success(response); - } - ); - }; - - that.getAttachment = function (command) { - priv.revisionGenericRequest( - command.cloneDoc(), - command.cloneOption(), - { - "doc_id": command.getDocId(), - "attachment_id": command.getAttachmentId(), - "getAttachment": true - }, - function (err, response) { - if (err) { - return that.error(err); - } - that.success(response); - } - ); - }; - - that.allDocs = function (command) { - var rows, result = {"total_rows": 0, "rows": []}, functions = {}; - functions.finished = 0; - functions.falseResponseGenerator = function (response, callback) { - callback(undefined, response); - }; - functions.fillResultGenerator = function (doc_id) { - return function (err, doc_tree) { - var document_revision, row, revs_info; - if (err) { - return that.error(err); - } - revs_info = priv.getWinnerRevsInfo(doc_tree); - document_revision = - rows.document_revisions[doc_id + "." + revs_info[0].rev]; - if (document_revision) { - row = { - "id": doc_id, - "key": doc_id, - "value": { - "rev": revs_info[0].rev - } - }; - if (document_revision.doc && command.getOption("include_docs")) { - document_revision.doc._id = doc_id; - document_revision.doc._rev = revs_info[0].rev; - row.doc = document_revision.doc; - } - result.rows.push(row); - result.total_rows += 1; - } - functions.success(); - }; - }; - functions.success = function () { - functions.finished -= 1; - if (functions.finished === 0) { - that.success(result); - } - }; - priv.send("allDocs", null, command.cloneOption( - ), function (err, response) { - var i, j, row, selector, selected; - if (err) { - return that.error(err); - } - selector = /\.revision_tree\.json$/; - rows = { - "revision_trees": { - // id.revision_tree.json: { - // id: blabla - // doc: {...} - // } - }, - "document_revisions": { - // id.rev: { - // id: blabla - // rev: 1-1 - // doc: {...} - // } - } - }; - while (response.rows.length > 0) { - // filling rows - row = response.rows.shift(); - selected = selector.exec(row.id); - if (selected) { - selected = selected.input.substring(0, selected.index); - // this is a revision tree - rows.revision_trees[row.id] = { - "id": selected - }; - if (row.doc) { - rows.revision_trees[row.id].doc = row.doc; - } - } else { - // this is a simple revision - rows.document_revisions[row.id] = { - "id": row.id.split(".").slice(0, -1), - "rev": row.id.split(".").slice(-1) - }; - if (row.doc) { - rows.document_revisions[row.id].doc = row.doc; - } - } - } - functions.finished += 1; - for (i in rows.revision_trees) { - if (rows.revision_trees.hasOwnProperty(i)) { - functions.finished += 1; - if (rows.revision_trees[i].doc) { - functions.falseResponseGenerator( - rows.revision_trees[i].doc, - functions.fillResultGenerator(rows.revision_trees[i].id) - ); - } else { - priv.getRevisionTree( - {"_id": rows.revision_trees[i].id}, - command.cloneOption(), - functions.fillResultGenerator(rows.revision_trees[i].id) - ); - } - } - } - functions.success(); - }); - }; - - // END // - return that; - }); // end RevisionStorage - -})); diff --git a/src/jio.storage/s3storage.js b/src/jio.storage/s3storage.js deleted file mode 100644 index ee3de1db911c13f5417de547f25649b4845d0706..0000000000000000000000000000000000000000 --- a/src/jio.storage/s3storage.js +++ /dev/null @@ -1,1005 +0,0 @@ -/*jslint indent: 2, maxlen: 80, nomen: true */ -/*global define, jIO, btoa, b64_hmac_sha1, jQuery, XMLHttpRequest, XHRwrapper, - FormData*/ -/** - * JIO S3 Storage. Type = "s3". - * Amazon S3 "database" storage. - */ -// define([module_name], [dependencies], module); -(function (dependencies, module) { - "use strict"; - if (typeof define === 'function' && define.amd) { - return define(dependencies, module); - } - module(jIO, jQuery, {b64_hmac_sha1: b64_hmac_sha1}); -}(['jio', 'jquery', 'sha1'], function (jIO, $, sha1) { - "use strict"; - var b64_hmac_sha1 = sha1.b64_hmac_sha1; - - jIO.addStorageType("s3", function (spec, my) { - var evt, that, priv = {}; - spec = spec || {}; - that = my.basicStorage(spec, my); - - // attributes - priv.username = spec.username || ''; - priv.AWSIdentifier = spec.AWSIdentifier || ''; - priv.password = spec.password || ''; - priv.server = spec.server || ''; /*|| jiobucket ||*/ - priv.acl = spec.acl || ''; - - /*||> "private, - public-read, - public-read-write, - authenticated-read, - bucket-owner-read, - bucket-owner-full-control" <||*/ - - priv.actionStatus = spec.actionStatus || ''; - - priv.contenTType = spec.contenTType || ''; - - /** - * Update [doc] the document object and remove [doc] keys - * which are not in [new_doc]. It only changes [doc] keys not starting - * with an underscore. - * ex: doc: {key:value1,_key:value2} with - * new_doc: {key:value3,_key:value4} updates - * doc: {key:value3,_key:value2}. - * @param {object} doc The original document object. - * @param {object} new_doc The new document object - **/ - - priv.secureDocId = function (string) { - var split = string.split('/'), i; - if (split[0] === '') { - split = split.slice(1); - } - for (i = 0; i < split.length; i += 1) { - if (split[i] === '') { - return ''; - } - } - return split.join('%2F'); - }; - - /** - * Replace substrings to another strings - * @method recursiveReplace - * @param {string} string The string to do replacement - * @param {array} list_of_replacement An array of couple - * ["substring to select", "selected substring replaced by this string"]. - * @return {string} The replaced string - */ - priv.recursiveReplace = function (string, list_of_replacement) { - var i, split_string = string.split(list_of_replacement[0][0]); - if (list_of_replacement[1]) { - for (i = 0; i < split_string.length; i += 1) { - split_string[i] = priv.recursiveReplace( - split_string[i], - list_of_replacement.slice(1) - ); - } - } - return split_string.join(list_of_replacement[0][1]); - }; - - /** - * Changes / to %2F, % to %25 and . to _. - * @method secureName - * @param {string} name The name to secure - * @return {string} The secured name - */ - priv.secureName = function (name) { - return priv.recursiveReplace(name, [["/", "%2F"], ["%", "%25"]]); - }; - - /** - * Restores the original name from a secured name - * @method restoreName - * @param {string} secured_name The secured name to restore - * @return {string} The original name - */ - priv.restoreName = function (secured_name) { - return priv.recursiveReplace(secured_name, [["%2F", "/"], ["%25", "%"]]); - }; - - /** - * Convert document id and attachment id to a file name - * @method idsToFileName - * @param {string} doc_id The document id - * @param {string} attachment_id The attachment id (optional) - * @return {string} The file name - */ - priv.idsToFileName = function (doc_id, attachment_id) { - doc_id = priv.secureName(doc_id).split(".").join("_."); - if (typeof attachment_id === "string") { - attachment_id = priv.secureName(attachment_id).split(".").join("_."); - return doc_id + "." + attachment_id; - } - return doc_id; - }; - - /** - * Convert a file name to a document id (and attachment id if there) - * @method fileNameToIds - * @param {string} file_name The file name to convert - * @return {array} ["document id", "attachment id"] or ["document id"] - */ - priv.fileNameToIds = function (file_name) { - var separator_index = -1, split = file_name.split("."); - split.slice(0, -1).forEach(function (file_name_part, index) { - if (file_name_part.slice(-1) !== "_") { - separator_index = index; - } - }); - if (separator_index === -1) { - return [priv.restoreName(priv.restoreName( - file_name - ).split("_.").join("."))]; - } - return [ - priv.restoreName(priv.restoreName( - split.slice(0, separator_index + 1).join(".") - ).split("_.").join(".")), - priv.restoreName(priv.restoreName( - split.slice(separator_index + 1).join(".") - ).split("_.").join(".")) - ]; - }; - - /** - * Removes the last character if it is a "/". "/a/b/c/" become "/a/b/c" - * @method removeSlashIfLast - * @param {string} string The string to modify - * @return {string} The modified string - */ - priv.removeSlashIfLast = function (string) { - if (string[string.length - 1] === "/") { - return string.slice(0, -1); - } - return string; - }; - - - - that.documentObjectUpdate = function (doc, new_doc) { - var k; - for (k in doc) { - if (doc.hasOwnProperty(k)) { - if (k[0] !== '_') { - delete doc[k]; - } - } - } - for (k in new_doc) { - if (new_doc.hasOwnProperty(k)) { - if (k[0] !== '_') { - doc[k] = new_doc[k]; - } - } - } - }; - - /** - * Checks if an object has no enumerable keys - * @method objectIsEmpty - * @param {object} obj The object - * @return {boolean} true if empty, else false - */ - - that.objectIsEmpty = function (obj) { - var k; - for (k in obj) { - if (obj.hasOwnProperty(k)) { - return false; - } - } - return true; - }; - - // ===================== overrides ====================== - that.specToStore = function () { - return { - "username": priv.username, - "password": priv.password, - "server": priv.server, - "acl": priv.acl - }; - }; - - that.validateState = function () { - // xxx complete error message - // jjj completion below - - if (typeof priv.AWSIdentifier === "string" && priv.AWSIdentifier === '') { - return 'Need at least one parameter "Aws login".'; - } - if (typeof priv.password === "string" && priv.password === '') { - return 'Need at least one parameter "password".'; - } - if (typeof priv.server === "string" && priv.server === '') { - return 'Need at least one parameter "server".'; - } - return ''; - }; - - // =================== S3 Specifics ================= - /** - * Encoding the signature using a stringToSign - * Encoding the policy - * @method buildStringToSign - * @param {string} http_verb The HTTP method - * @param {string} content_md5 The md5 content - * @param {string} content_type The content type - * @param {number} expires The expires time - * @param {string} x_amz_headers The specific amazon headers - * @param {string} path_key The path of the document - * @return {string} The generated signature - */ - - // xxx no need to make it public, use private -> "priv" (not "that") - priv.buildStringToSign = function (http_verb, content_md5, content_type, - expires, x_amz_headers, path_key) { - //example : - // var StringToSign = S3.buildStringToSign(S3.httpVerb,'','','', - // 'x-amz-date:'+S3.requestUTC,'/jio1st/prive.json'); - - var StringToSign = http_verb + '\n' - + content_md5 + '\n'//content-md5 - + content_type + '\n'//content-type - + expires + '\n'//expires - + x_amz_headers + '\n'//x-amz headers - + path_key;//path key - - return StringToSign; - }; - - - - - that.encodePolicy = function (form) { - //generates the policy - //enables the choice for the http response code - var http_code, s3_policy, Signature = ''; - s3_policy = { - "expiration": "2020-01-01T00:00:00Z", - "conditions": [ - {"bucket": priv.server }, - ["starts-with", "$key", ""], - {"acl": priv.acl }, - {"success_action_redirect": ""}, - {"success_action_status": http_code }, - ["starts-with", "$Content-Type", ""], - ["content-length-range", 0, 524288000] - ] - }; - - //base64 encoding of the policy (native base64 js >> - // .btoa() = encode, .atob() = decode) - priv.b64_policy = btoa(JSON.stringify(s3_policy)); - //generates the signature value using the policy and the secret access key - //use of sha1.js to generate the signature - Signature = that.signature(priv.b64_policy); - - }; - - that.signature = function (string) { - var Signature = b64_hmac_sha1(priv.password, string); - return Signature; - }; - - function xhr_onreadystatechange(docId, - command, - obj, - http, - jio, - isAttachment, - callback) { - obj.onreadystatechange = function () { - var response, err = ''; - if (obj.readyState === 4) { - if (this.status === 204 || this.status === 201 || - this.status === 200) { - switch (http) { - case "POST": - that.success({ - ok: true, - id: docId - }); - break; - case 'PUT': - if (jio === true) { - that.success({ - ok: true, - id: command.getDocId() - }); - } else { - callback(this.responseText); - } - break; - case 'GET': - if (jio === true) { - if (typeof this.responseText !== 'string') { - response = JSON.parse(this.responseText); - response._attachments = response._attachments || {}; - delete response._attachments; - that.success(JSON.stringify(response)); - } else { - if (isAttachment === true) { - that.success(this.responseText); - } else { - that.success(JSON.parse(this.responseText)); - } - } - } else { - callback(this.responseText); - } - break; - case 'DELETE': - if (jio === true) { - if (isAttachment === false) { - that.success({ - ok: true, - id: command.getDocId() - }); - } else { - that.success({ - ok: true, - id: command.getDocId(), - attachment: command.getAttachmentId() - }); - } - } else { - callback(this.responseText); - } - break; - } - } else { - err = this; - if (this.status === 405) { - //status - //statustext "Not Found" - //error - //reason "reason" - //message "did not work" - err.error = "not_allowed"; - that.error(err); - } - if (this.status === 404) { - if (http === 'GET') { - if (jio === true) { - //status - //statustext "Not Found" - //error - //reason "reason" - //message "did not work" - err.statustext = "not_foud"; - err.reason = "file does not exist"; - err.error = "not_found"; - that.error(err); - } else { - - callback('404'); - } - } else { - //status - //statustext "Not Found" - //error - //reason "reason" - //message "did not work" - err.error = "not_found"; - that.error(err); - } - } - if (this.status === 409) { - //status - //statustext "Not Found" - //error - //reason "reason" - //message "did not work" - err.error = "already_exists"; - that.error(err); - } - } - } - }; - } - - priv.updateMeta = function (doc, docid, attachid, action, data) { - doc._attachments = doc._attachments || {}; - switch (action) { - case "add": - doc._attachments[attachid] = data; - //nothing happens - doc = JSON.stringify(doc); - break; - case "remove": - if (doc._attachments !== undefined) { - delete doc._attachments[attachid]; - } - doc = JSON.stringify(doc); - break; - case "update": - doc._attachments[attachid] = data; - //update happened in the put request - doc = JSON.stringify(doc); - break; - } - return doc; - }; - - priv.createError = function (status, message, reason) { - var error = { - "status": status, - "message": message, - "reason": reason - }; - switch (status) { - case 404: - error.statusText = "Not found"; - break; - case 405: - error.statusText = "Method Not Allowed"; - break; - case 409: - error.statusText = "Conflicts"; - break; - case 24: - error.statusText = "Corrupted Document"; - break; - } - error.error = error.statusText.toLowerCase().split(" ").join("_"); - return error; - }; - - that.encodeAuthorization = function (key, mime) { - //GET oriented method - var requestUTC, httpVerb, StringToSign, Signature; - requestUTC = new Date().toUTCString(); - httpVerb = "GET"; - StringToSign = priv.buildStringToSign( - httpVerb, - '', - 'application/json', - '', - 'x-amz-date:' + requestUTC, - '/' + priv.server + '/' + key - ); - Signature = b64_hmac_sha1(priv.password, StringToSign); - return Signature; - }; - - that.XHRwrapper = function (command, - docId, - attachId, - http, - mime, - data, - jio, - is_attachment, - callback) { - - var docFile, requestUTC, StringToSign, url, Signature, xhr; - docFile = priv.secureName(priv.idsToFileName(docId, - attachId || undefined)); - - requestUTC = new Date().toUTCString(); - - StringToSign = priv.buildStringToSign( - http, - '', - mime, - '', - 'x-amz-date:' + requestUTC, - '/' + priv.server + '/' + docFile - ); - - url = 'http://s3.amazonaws.com/' + priv.server + '/' + docFile; - - Signature = b64_hmac_sha1(priv.password, StringToSign); - xhr = new XMLHttpRequest(); - - xhr.open(http, url, true); - xhr.setRequestHeader("HTTP-status-code", "100"); - xhr.setRequestHeader("x-amz-date", requestUTC); - xhr.setRequestHeader("Authorization", "AWS " - + priv.AWSIdentifier - + ":" - + Signature); - xhr.setRequestHeader("Content-Type", mime); - xhr.responseType = 'text'; - - xhr_onreadystatechange(docId, - command, - xhr, - http, - jio, - is_attachment, - callback); - - if (http === 'PUT') { - xhr.send(data); - } else { - xhr.send(null); - } - }; - - // ==================== commands ==================== - /** - * Create a document in local storage. - * @method post - * @param {object} command The JIO command - **/ - - that.post = function (command) { - //as S3 encoding key are directly inserted within the FormData(), - //use of XHRwrapper function ain't pertinent - - var doc, doc_id, mime; - doc = command.cloneDoc(); - doc_id = command.getDocId(); - - function postDocument() { - var http_response, fd, Signature, xhr; - doc_id = priv.secureName(priv.idsToFileName(doc_id)); - //Meant to deep-serialize in order to avoid - //conflicts due to the multipart enctype - doc = JSON.stringify(doc); - http_response = ''; - fd = new FormData(); - //virtually builds the form fields - //filename - fd.append('key', doc_id); - //file access authorizations - priv.acl = ""; - fd.append('acl', priv.acl); - //content-type - priv.contenTType = "text/plain"; - fd.append('Content-Type', priv.contenTType); - //allows specification of a success url redirection - fd.append('success_action_redirect', ''); - //allows to specify the http code response if the request is successful - fd.append('success_action_status', http_response); - //login AWS - fd.append('AWSAccessKeyId', priv.AWSIdentifier); - //exchange policy with the amazon s3 service - //can be common to all uploads or specific - that.encodePolicy(fd); - //priv.b64_policy = that.encodePolicy(fd); - fd.append('policy', priv.b64_policy); - //signature through the base64.hmac.sha1(secret key, policy) method - Signature = b64_hmac_sha1(priv.password, priv.b64_policy); - fd.append('signature', Signature); - //uploaded content !!may must be a string rather than an object - fd.append('file', doc); - xhr = new XMLHttpRequest(); - xhr_onreadystatechange(doc_id, command, xhr, 'POST', true, false, ''); - xhr.open('POST', 'https://' + priv.server + '.s3.amazonaws.com/', true); - xhr.send(fd); - } - - if (doc_id === '' || doc_id === undefined) { - doc_id = 'no_document_id_' - + ((Math.random() * 10).toString().split('.'))[1]; - doc._id = doc_id; - } - - mime = 'text/plain; charset=UTF-8'; - that.XHRwrapper(command, doc_id, '', 'GET', mime, '', false, false, - function (response) { - if (response === '404') { - postDocument(); - } else { - //si ce n'est pas une 404, - //alors on renvoit une erreur 405 - return that.error(priv.createError( - 409, - "Cannot create document", - "Document already exists" - )); - } - } - ); - }; - - /** - * Get a document or attachment - * @method get - * @param {object} command The JIO command - **/ - - that.get = function (command) { - var docId, attachId, isJIO, mime; - docId = command.getDocId(); - attachId = command.getAttachmentId() || ''; - isJIO = true; - mime = 'text/plain; charset=UTF-8'; - that.XHRwrapper(command, docId, attachId, 'GET', mime, '', isJIO, false); - }; - - that.getAttachment = function (command) { - var docId, attachId, isJIO, mime; - docId = command.getDocId(); - attachId = command.getAttachmentId(); - isJIO = true; - mime = 'text/plain; charset=UTF-8'; - that.XHRwrapper(command, docId, attachId, 'GET', mime, '', isJIO, true); - }; - - /** - * Create or update a document in local storage. - * @method put - * @param {object} command The JIO command - **/ - - that.put = function (command) { - var doc, docId, mime; - doc = command.cloneDoc(); - docId = command.getDocId(); - mime = 'text/plain; charset=UTF-8'; - //pas d'attachment dans un put simple - function putDocument() { - var attachId, data, isJIO; - attachId = ''; - data = JSON.stringify(doc); - isJIO = true; - that.XHRwrapper(command, - docId, - attachId, - 'PUT', - mime, - data, - isJIO, - false); - } - - that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, - function (response) { - //if (response === '404') {} - if (response._attachments !== undefined) { - doc._attachments = response._attachments; - } - putDocument(); - } - ); - }; - - that.putAttachment = function (command) { - var mon_document, - docId, - attachId, - mime, - attachment_id, - attachment_data, - attachment_md5, - attachment_mimetype, - attachment_length; - - mon_document = null; - docId = command.getDocId(); - attachId = command.getAttachmentId() || ''; - mime = 'text/plain; charset=UTF-8'; - //récupération des variables de l'attachement - - attachment_id = command.getAttachmentId(); - attachment_data = command.getAttachmentData(); - attachment_md5 = command.md5SumAttachmentData(); - attachment_mimetype = command.getAttachmentMimeType(); - attachment_length = command.getAttachmentLength(); - - function putAttachment() { - that.XHRwrapper(command, - docId, - attachId, - 'PUT', - mime, - attachment_data, - false, - true, - function (reponse) { - that.success({ - // response - "ok": true, - "id": docId, - "attachment": attachId - //"rev": current_revision - }); - } - ); - } - - function putDocument() { - var attachment_obj, data, doc; - attachment_obj = { - //"revpos": 3, // optional - "digest": attachment_md5, - "content_type": attachment_mimetype, - "length": attachment_length - }; - data = JSON.parse(mon_document); - - doc = priv.updateMeta(data, docId, attachId, "add", attachment_obj); - - that.XHRwrapper(command, docId, '', 'PUT', mime, doc, false, false, - function (reponse) { - putAttachment(); - } - ); - } - - function getDocument() { - //XHRwrapper(command,'PUT','text/plain; charset=UTF-8',true); - that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, - function (reponse) { - if (reponse === '404') { - return that.error(priv.createError( - 404, - "Cannot find document", - "Document does not exist" - )); - } - mon_document = reponse; - putDocument(); - } - ); - } - getDocument(); - }; - - /** - * Remove a document or attachment - * @method remove - * @param {object} command The JIO command - */ - - that.remove = function (command) { - var docId, mime; - docId = command.getDocId(); - mime = 'text/plain; charset=UTF-8'; - - function deleteDocument() { - that.XHRwrapper(command, docId, '', 'DELETE', mime, '', true, false, - function (reponse) { - that.success({ - // response - "ok": true, - "id": docId - //"rev": current_revision - }); - } - ); - } - - function myCallback(response) { - } - - that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, - function (response) { - var attachKeys, keys; - attachKeys = (JSON.parse(response))._attachments; - for (keys in attachKeys) { - if (attachKeys.hasOwnProperty(keys)) { - that.XHRwrapper(command, - docId, - keys, - 'DELETE', - mime, - '', - false, - false, - myCallback - ); - } - } - deleteDocument(); - } - ); - }; - - that.removeAttachment = function (command) { - var mon_document, - docId, - attachId, - mime, - attachment_id, - attachment_data, - attachment_md5, - attachment_mimetype, - attachment_length; - - mon_document = null; - docId = command.getDocId(); - attachId = command.getAttachmentId() || ''; - mime = 'text/plain; charset=UTF-8'; - //récupération des variables de l'attachement - - attachment_id = command.getAttachmentId(); - attachment_data = command.getAttachmentData(); - attachment_md5 = command.md5SumAttachmentData(); - attachment_mimetype = command.getAttachmentMimeType(); - attachment_length = command.getAttachmentLength(); - - function removeAttachment() { - that.XHRwrapper(command, docId, attachId, 'DELETE', mime, '', true, - true, function (reponse) { - } - ); - } - - function putDocument() { - var data, doc; - data = JSON.parse(mon_document); - doc = priv.updateMeta(data, docId, attachId, "remove", ''); - that.XHRwrapper(command, docId, '', 'PUT', mime, doc, - false, false, function (reponse) { - removeAttachment(); - } - ); - } - - function getDocument() { - that.XHRwrapper(command, docId, '', 'GET', mime, '', false, false, - function (reponse) { - mon_document = reponse; - putDocument(); - } - ); - } - getDocument(); - }; - - /** - * Get all filenames belonging to a user from the document index - * @method allDocs - * @param {object} command The JIO command - **/ - - that.allDocs = function (command) { - var mon_document, mime; - mon_document = null; - mime = 'text/plain; charset=UTF-8'; - - function makeJSON() { - var keys, - resultTable, - counter, - allDocResponse, - count, - countB, - dealCallback, - errCallback, - i, - keyId, - Signature, - callURL, - requestUTC, - parse, - checkCounter; - - keys = $(mon_document).find('Key'); - - resultTable = []; - counter = 0; - - keys.each(function (index) { - var that, filename, docId; - that = $(this); - filename = that.context.textContent; - docId = priv.idsToFileName(priv.fileNameToIds(filename)[0]); - if (counter === 0) { - counter += 1; - resultTable.push(docId); - } else if (docId !== resultTable[counter - 1]) { - counter += 1; - resultTable.push(docId); - } - }); - - allDocResponse = { - // document content will be added to response - "total_rows": resultTable.length, - "offset": 0, - "rows": [] - }; - - //needed to save the index within the $.ajax.success() callback - count = resultTable.length - 1; - countB = 0; - - dealCallback = function (i, countB, allDoc) { - return function (doc, statustext, response) { - allDoc.rows[i].doc = response.responseText; - if (count === 0) { - that.success(allDoc); - } else { - count -= 1; - } - }; - }; - - errCallback = function (err) { - if (err.status === 404) { - //status - //statustext "Not Found" - //error - //reason "reason" - //message "did not work" - err.error = "not_found"; - that.error(err); - } else { - return that.retry(err); - } - }; - - i = resultTable.length - 1; - - if (command.getOption("include_docs") === true) { - - for (i; i >= 0; i -= 1) { - keyId = resultTable[i]; - Signature = that.encodeAuthorization(keyId); - callURL = 'http://' + priv.server + '.s3.amazonaws.com/' + keyId; - requestUTC = new Date().toUTCString(); - parse = true; - - allDocResponse.rows[i] = { - "id": priv.fileNameToIds(keyId).join(), - "key": keyId, - "value": {} - }; - checkCounter = i; - - $.ajax({ - contentType : '', - crossdomain : true, - url : callURL, - type : 'GET', - headers : { - 'Authorization' : "AWS" - + " " - + priv.AWSIdentifier - + ":" - + Signature, - 'x-amz-date' : requestUTC, - 'Content-Type' : 'application/json' - //'Content-MD5' : '' - //'Content-Length' : , - //'Expect' : , - //'x-amz-security-token' : , - }, - success : dealCallback(i, countB, allDocResponse), - error : errCallback(that.error) - }); - countB += 1; - } - } else { - for (i; i >= 0; i -= 1) { - keyId = resultTable[i]; - allDocResponse.rows[i] = { - "id": priv.fileNameToIds(keyId).join(), - "key": keyId, - "value": {} - }; - } - that.success(allDocResponse); - } - } - - function getXML() { - //XHRwrapper(command,'PUT','text/plain; charset=UTF-8',true); - that.XHRwrapper(command, '', '', 'GET', mime, '', false, false, - function (reponse) { - mon_document = reponse; - makeJSON(); - } - ); - } - - getXML(); - //fin alldocs - }; - return that; - }); - -})); diff --git a/src/jio.storage/splitstorage.js b/src/jio.storage/splitstorage.js deleted file mode 100644 index 6a0e2cbba3a47387159be10eeda24e9b7a043850..0000000000000000000000000000000000000000 --- a/src/jio.storage/splitstorage.js +++ /dev/null @@ -1,588 +0,0 @@ -/* - * Copyright 2013, Nexedi SA - * Released under the LGPL license. - * http://www.gnu.org/licenses/lgpl.html - */ - -/*jslint indent:2, maxlen: 80, nomen: true */ -/*global jIO: true, exports: true, define: true */ - -/** - * Provides a split storage for JIO. This storage splits data - * and store them in the sub storages defined on the description. - * - * { - * "type": "split", - * "storage_list": [<storage description>, ...] - * } - */ -// define([module_name], [dependencies], module); -(function (dependencies, module) { - "use strict"; - if (typeof define === 'function' && define.amd) { - return define(dependencies, module); - } - module(jIO); -}(['jio'], function (jIO) { - "use strict"; - - /** - * Generate a new uuid - * - * @method generateUuid - * @private - * @return {String} The new uuid - */ - function generateUuid() { - function S4() { - /* 65536 */ - var i, string = Math.floor( - Math.random() * 0x10000 - ).toString(16); - for (i = string.length; i < 4; i += 1) { - string = '0' + string; - } - return string; - } - return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + - S4() + S4(); - } - - - /** - * Class to merge allDocs responses from several sub storages. - * - * @class AllDocsResponseMerger - * @constructor - */ - function AllDocsResponseMerger() { - - /** - * A list of allDocs response. - * - * @attribute response_list - * @type {Array} Contains allDocs responses - * @default [] - */ - this.response_list = []; - } - AllDocsResponseMerger.prototype.constructor = AllDocsResponseMerger; - - /** - * Add an allDocs response to the response list. - * - * @method addResponse - * @param {Object} response The allDocs response. - * @return {AllDocsResponseMerger} This - */ - AllDocsResponseMerger.prototype.addResponse = function (response) { - this.response_list.push(response); - return this; - }; - - /** - * Add several allDocs responses to the response list. - * - * @method addResponseList - * @param {Array} response_list An array of allDocs responses. - * @return {AllDocsResponseMerger} This - */ - AllDocsResponseMerger.prototype.addResponseList = function (response_list) { - var i; - for (i = 0; i < response_list.length; i += 1) { - this.response_list.push(response_list[i]); - } - return this; - }; - - /** - * Merge the response_list to one allDocs response. - * - * The merger will find rows with the same id in order to merge them, thanks - * to the onRowToMerge method. If no row correspond to an id, rows with the - * same id will be ignored. - * - * @method merge - * @param {Object} [option={}] The merge options - * @param {Boolean} [option.include_docs=false] Tell the merger to also - * merge metadata if true. - * @return {Object} The merged allDocs response. - */ - AllDocsResponseMerger.prototype.merge = function (option) { - var result = [], row, to_merge = [], tmp, i; - if (this.response_list.length === 0) { - return []; - } - while ((row = this.response_list[0].rows.shift()) !== undefined) { - to_merge[0] = row; - for (i = 1; i < this.response_list.length; i += 1) { - to_merge[i] = AllDocsResponseMerger.listPopFromRowId( - this.response_list[i].rows, - row.id - ); - if (to_merge[i] === undefined) { - break; - } - } - tmp = this.onRowToMerge(to_merge, option || {}); - if (tmp !== undefined) { - result[result.length] = tmp; - } - } - this.response_list = []; - return {"total_rows": result.length, "rows": result}; - }; - - /** - * This method is called when the merger want to merge several rows with the - * same id. - * - * @method onRowToMerge - * @param {Array} row_list An array of rows. - * @param {Object} [option={}] The merge option. - * @param {Boolean} [option.include_docs=false] Also merge the metadata if - * true - * @return {Object} The merged row - */ - AllDocsResponseMerger.prototype.onRowToMerge = function (row_list, option) { - var i, k, new_row = {"value": {}}, data = ""; - option = option || {}; - for (i = 0; i < row_list.length; i += 1) { - new_row.id = row_list[i].id; - if (row_list[i].key) { - new_row.key = row_list[i].key; - } - if (option.include_docs) { - new_row.doc = new_row.doc || {}; - for (k in row_list[i].doc) { - if (row_list[i].doc.hasOwnProperty(k)) { - if (k[0] === "_") { - new_row.doc[k] = row_list[i].doc[k]; - } - } - } - data += row_list[i].doc.data; - } - } - if (option.include_docs) { - try { - data = JSON.parse(data); - } catch (e) { return undefined; } - for (k in data) { - if (data.hasOwnProperty(k)) { - new_row.doc[k] = data[k]; - } - } - } - return new_row; - }; - - /** - * Search for a specific row and pop it. During the search operation, all - * parsed rows are stored on a dictionnary in order to be found instantly - * later. - * - * @method listPopFromRowId - * @param {Array} rows The row list - * @param {String} doc_id The document/row id - * @return {Object/undefined} The poped row - */ - AllDocsResponseMerger.listPopFromRowId = function (rows, doc_id) { - var row; - if (!rows.dict) { - rows.dict = {}; - } - if (rows.dict[doc_id]) { - row = rows.dict[doc_id]; - delete rows.dict[doc_id]; - return row; - } - while ((row = rows.shift()) !== undefined) { - if (row.id === doc_id) { - return row; - } - rows.dict[row.id] = row; - } - }; - - - /** - * The split storage class used by JIO. - * - * A split storage instance is able to i/o on several sub storages with - * split documents. - * - * @class splitStorage - */ - function splitStorage(spec, my) { - var that = my.basicStorage(spec, my), priv = {}; - - /** - * The list of sub storages we want to use to store part of documents. - * - * @attribute storage_list - * @private - * @type {Array} Array of storage descriptions - */ - priv.storage_list = spec.storage_list; - - ////////////////////////////////////////////////////////////////////// - // Overrides - - /** - * Overrides the original {{#crossLink "storage/specToStore:method"}} - * specToStore method{{/crossLink}}. - * - * @method specToStore - * @return {Object} The specificities to store - */ - that.specToStore = function () { - return {"storage_list": priv.storage_list}; - }; - - /** - * TODO validateState - */ - - ////////////////////////////////////////////////////////////////////// - // Tools - - /** - * Send a command to all sub storages. All the response are returned - * in a list. The index of the response correspond to the storage_list - * index. If an error occurs during operation, the callback is called with - * `callback(err, undefined)`. The response is given with - * `callback(undefined, response_list)`. - * - * `doc` is the document informations but can also be a list of dedicated - * document informations. In this case, each document is associated to one - * sub storage. - * - * @method send - * @private - * @param {String} method The command method - * @param {Object,Array} doc The document information to send to each sub - * storages or a list of dedicated document - * @param {Object} option The command option - * @param {Function} callback Called at the end - */ - priv.send = function (method, doc, option, callback) { - var i, answer_list = [], failed = false; - function onEnd() { - i += 1; - if (i === priv.storage_list.length) { - callback(undefined, answer_list); - } - } - function onSuccess(i) { - return function (response) { - if (!failed) { - answer_list[i] = response; - } - onEnd(); - }; - } - function onError(i) { - return function (err) { - if (!failed) { - failed = true; - err.index = i; - callback(err, undefined); - } - }; - } - if (!Array.isArray(doc)) { - for (i = 0; i < priv.storage_list.length; i += 1) { - that.addJob( - method, - priv.storage_list[i], - doc, - option, - onSuccess(i), - onError(i) - ); - } - } else { - for (i = 0; i < priv.storage_list.length; i += 1) { - that.addJob( - method, - priv.storage_list[i], - doc[i], - option, - onSuccess(i), - onError(i) - ); - } - } - i = 0; - }; - - /** - * Split document metadata then store them to the sub storages. - * - * @method postOrPut - * @private - * @param {Object} doc A serialized document object - * @param {Object} option Command option properties - * @param {String} method The command method ('post' or 'put') - */ - priv.postOrPut = function (doc, option, method) { - var i, data, doc_list = [], doc_underscores = {}; - if (!doc._id) { - doc._id = generateUuid(); - } - for (i in doc) { - if (doc.hasOwnProperty(i)) { - if (i[0] === "_") { - doc_underscores[i] = doc[i]; - delete doc[i]; - } - } - } - data = JSON.stringify(doc); - for (i = 0; i < priv.storage_list.length; i += 1) { - doc_list[i] = JSON.parse(JSON.stringify(doc_underscores)); - doc_list[i].data = data.slice( - (data.length / priv.storage_list.length) * i, - (data.length / priv.storage_list.length) * (i + 1) - ); - } - priv.send(method, doc_list, option, function (err, response) { - if (err) { - err.message = "Unable to " + method + " document"; - delete err.index; - return that.error(err); - } - that.success({"ok": true, "id": doc_underscores._id}); - }); - }; - - ////////////////////////////////////////////////////////////////////// - // JIO commands - - /** - * Split document metadata then store them to the sub storages. - * - * @method post - * @param {Command} command The JIO command - */ - that.post = function (command) { - priv.postOrPut(command.cloneDoc(), command.cloneOption(), 'post'); - }; - - /** - * Split document metadata then store them to the sub storages. - * - * @method put - * @param {Command} command The JIO command - */ - that.put = function (command) { - priv.postOrPut(command.cloneDoc(), command.cloneOption(), 'put'); - }; - - /** - * Puts an attachment to the sub storages. - * - * @method putAttachment - * @param {Command} command The JIO command - */ - that.putAttachment = function (command) { - var i, attachment_list = [], data = command.getAttachmentData(); - for (i = 0; i < priv.storage_list.length; i += 1) { - attachment_list[i] = command.cloneDoc(); - attachment_list[i]._data = data.slice( - (data.length / priv.storage_list.length) * i, - (data.length / priv.storage_list.length) * (i + 1) - ); - } - priv.send( - 'putAttachment', - attachment_list, - command.cloneOption(), - function (err, response) { - if (err) { - err.message = "Unable to put attachment"; - delete err.index; - return that.error(err); - } - that.success({ - "ok": true, - "id": command.getDocId(), - "attachment": command.getAttachmentId() - }); - } - ); - }; - - /** - * Gets splited document metadata then returns real document. - * - * @method get - * @param {Command} command The JIO command - */ - that.get = function (command) { - var doc, option, data, attachments; - doc = command.cloneDoc(); - option = command.cloneOption(); - priv.send('get', doc, option, function (err, response) { - var i, k; - if (err) { - err.message = "Unable to get document"; - delete err.index; - return that.error(err); - } - doc = ''; - for (i = 0; i < response.length; i += 1) { - doc += response[i].data; - } - doc = JSON.parse(doc); - for (i = 0; i < response.length; i += 1) { - for (k in response[i]) { - if (response[i].hasOwnProperty(k)) { - if (k[0] === "_") { - doc[k] = response[i][k]; - } - } - } - } - delete doc._attachments; - for (i = 0; i < response.length; i += 1) { - if (response[i]._attachments) { - for (k in response[i]._attachments) { - if (response[i]._attachments.hasOwnProperty(k)) { - doc._attachments = doc._attachments || {}; - doc._attachments[k] = doc._attachments[k] || { - "length": 0, - "content_type": "" - }; - doc._attachments[k].length += response[i]._attachments[k]. - length; - // if (response[i]._attachments[k].digest) { - // if (doc._attachments[k].digest) { - // doc._attachments[k].digest += " " + response[i]. - // _attachments[k].digest; - // } else { - // doc._attachments[k].digest = response[i]. - // _attachments[k].digest; - // } - // } - doc._attachments[k].content_type = response[i]._attachments[k]. - content_type; - } - } - } - } - doc._id = command.getDocId(); - that.success(doc); - }); - }; - - /** - * Gets splited document attachment then returns real attachment data. - * - * @method getAttachment - * @param {Command} command The JIO command - */ - that.getAttachment = function (command) { - var doc, option; - doc = command.cloneDoc(); - option = command.cloneOption(); - priv.send('getAttachment', doc, option, function (err, response) { - var i, k; - if (err) { - err.message = "Unable to get attachment"; - delete err.index; - return that.error(err); - } - doc = ''; - for (i = 0; i < response.length; i += 1) { - doc += response[i]; - } - that.success(doc); - }); - }; - - /** - * Removes a document from the sub storages. - * - * @method remove - * @param {Command} command The JIO command - */ - that.remove = function (command) { - priv.send( - 'remove', - command.cloneDoc(), - command.cloneOption(), - function (err, response_list) { - if (err) { - err.message = "Unable to remove document"; - delete err.index; - return that.error(err); - } - that.success({"id": command.getDocId(), "ok": true}); - } - ); - }; - - /** - * Removes an attachment from the sub storages. - * - * @method removeAttachment - * @param {Command} command The JIO command - */ - that.removeAttachment = function (command) { - var doc = command.cloneDoc(); - priv.send( - 'removeAttachment', - doc, - command.cloneOption(), - function (err, response_list) { - if (err) { - err.message = "Unable to remove attachment"; - delete err.index; - return that.error(err); - } - that.success({ - "id": doc._id, - "attachment": doc._attachment, - "ok": true - }); - } - ); - }; - - /** - * Retreive a list of all document in the sub storages. - * - * If include_docs option is false, then it returns the document list from - * the first sub storage. Else, it will merge results and return. - * - * @method allDocs - * @param {Command} command The JIO command - */ - that.allDocs = function (command) { - var option = command.cloneOption(); - option = {"include_docs": option.include_docs}; - priv.send( - 'allDocs', - command.cloneDoc(), - option, - function (err, response_list) { - var all_docs_merger; - if (err) { - err.message = "Unable to retrieve document list"; - delete err.index; - return that.error(err); - } - all_docs_merger = new AllDocsResponseMerger(); - all_docs_merger.addResponseList(response_list); - return that.success(all_docs_merger.merge(option)); - } - ); - }; - - return that; - } // end of splitStorage - - jIO.addStorageType('split', splitStorage); -})); diff --git a/src/jio.storage/xwikistorage.js b/src/jio.storage/xwikistorage.js deleted file mode 100644 index c282f5176346df473eadcfcee60f26fcd4d32767..0000000000000000000000000000000000000000 --- a/src/jio.storage/xwikistorage.js +++ /dev/null @@ -1,731 +0,0 @@ -/*jslint indent: 2, - maxlen: 80, - nomen: true -*/ -/*global - define: true, - jIO: true, - jQuery: true, - XMLHttpRequest: true, - Blob: true, - FormData: true, - window: true -*/ -/** - * JIO XWiki Storage. Type = 'xwiki'. - * XWiki Document/Attachment storage. - */ -(function () { - "use strict"; - - var $, store; - store = function (spec, my) { - - spec = spec || {}; - var that, priv, xwikistorage; - - that = my.basicStorage(spec, my); - priv = {}; - - /** - * Get the Space and Page components of a documkent ID. - * - * @param id the document id. - * @return a map of { 'space':<Space>, 'page':<Page> } - */ - priv.getParts = function (id) { - if (id.indexOf('/') === -1) { - return { - space: 'Main', - page: id - }; - } - return { - space: id.substring(0, id.indexOf('/')), - page: id.substring(id.indexOf('/') + 1) - }; - }; - - /** - * Get the Anti-CSRF token and do something with it. - * - * @param andThen function which is called with (formToken, err) - * as parameters. - */ - priv.doWithFormToken = function (andThen) { - $.ajax({ - url: priv.formTokenPath, - type: "GET", - async: true, - dataType: 'text', - success: function (html) { - var m, token; - // this is unreliable - //var token = $('meta[name=form_token]', html).attr("content"); - m = html.match(/<meta name="form_token" content="(\w*)"\/>/); - token = (m && m[1]) || null; - if (!token) { - andThen(null, { - "status": 404, - "statusText": "Not Found", - "error": "err_form_token_not_found", - "message": "Anti-CSRF form token was not found in page", - "reason": "XWiki main page did not contain expected " + - "Anti-CSRF form token" - }); - } else { - andThen(token, null); - } - }, - error: function (jqxhr, err, cause) { - andThen(null, { - "status": jqxhr.status, - "statusText": jqxhr.statusText, - "error": err, - "message": "Could not get Anti-CSRF form token from [" + - priv.xwikiurl + "]", - "reason": cause - }); - }, - }); - }; - - /** - * Get the REST read URL for a document. - * - * @param docId the id of the document. - * @return the REST URL for accessing this document. - */ - priv.getDocRestURL = function (docId) { - var parts = priv.getParts(docId); - return priv.xwikiurl + '/rest/wikis/' - + priv.wiki + '/spaces/' + parts.space + '/pages/' + parts.page; - }; - - /** - * Make an HTML5 Blob object. - * Equivilant to the `new Blob()` constructor. - * Will fall back on deprecated BlobBuilder if necessary. - */ - priv.makeBlob = function (contentArray, options) { - var i, bb, BB; - try { - // use the constructor if possible. - return new Blob(contentArray, options); - } catch (err) { - // fall back on the blob builder. - BB = (window.MozBlobBuilder || window.WebKitBlobBuilder - || window.BlobBuilder); - bb = new BB(); - for (i = 0; i < contentArray.length; i += 1) { - bb.append(contentArray[i]); - } - return bb.getBlob(options ? options.type : undefined); - } - }; - - priv.isBlob = function (potentialBlob) { - return typeof (potentialBlob) !== 'undefined' && - potentialBlob.toString() === "[object Blob]"; - }; - - /* - * Wrapper for the xwikistorage based on localstorage JiO store. - */ - xwikistorage = { - /** - * Get content of an XWikiDocument. - * - * @param docId the document ID. - * @param andThen a callback taking (doc, err), doc being the document - * json object and err being the error if any. - */ - getItem: function (docId, andThen) { - - var success = function (jqxhr) { - var out, xd; - out = {}; - try { - xd = $(jqxhr.responseText); - xd.find('modified').each(function () { - out._last_modified = Date.parse($(this).text()); - }); - xd.find('created').each(function () { - out._creation_date = Date.parse($(this).text()); - }); - xd.find('title').each(function () { out.title = $(this).text(); }); - xd.find('parent').each(function () { - out.parent = $(this).text(); - }); - xd.find('syntax').each(function () { - out.syntax = $(this).text(); - }); - xd.find('content').each(function () { - out.content = $(this).text(); - }); - out._id = docId; - andThen(out, null); - } catch (err) { - andThen(null, { - status: 500, - statusText: "internal error", - error: err, - message: err.message, - reason: "" - }); - } - }; - - $.ajax({ - url: priv.getDocRestURL(docId), - type: "GET", - async: true, - dataType: 'xml', - - // Use complete instead of success and error because phantomjs - // sometimes causes error to be called with html return code 200. - complete: function (jqxhr) { - if (jqxhr.status === 404) { - andThen(null, null); - return; - } - if (jqxhr.status !== 200) { - andThen(null, { - "status": jqxhr.status, - "statusText": jqxhr.statusText, - "error": "", - "message": "Failed to get document [" + docId + "]", - "reason": "" - }); - return; - } - success(jqxhr); - } - }); - }, - - /** - * Get content of an XWikiAttachment. - * - * @param attachId the attachment ID. - * @param andThen a callback taking (attach, err), attach being the - * attachment blob and err being the error if any. - */ - getAttachment: function (docId, fileName, andThen) { - var xhr, parts, url; - // need to do this manually, jquery doesn't support returning blobs. - xhr = new XMLHttpRequest(); - parts = priv.getParts(docId); - url = priv.xwikiurl + '/bin/download/' + parts.space + - "/" + parts.page + "/" + fileName + '?cb=' + Math.random(); - xhr.open('GET', url, true); - if (priv.useBlobs) { - xhr.responseType = 'blob'; - } else { - xhr.responseType = 'text'; - } - - xhr.onload = function (e) { - if (xhr.status === 200) { - var contentType = xhr.getResponseHeader("Content-Type"); - if (contentType.indexOf(';') > -1) { - contentType = contentType.substring(0, contentType.indexOf(';')); - } - andThen(xhr.response); - } else { - andThen(null, { - "status": xhr.status, - "statusText": xhr.statusText, - "error": "err_network_error", - "message": "Failed to get attachment [" - + docId + "/" + fileName + "]", - "reason": "Error getting data from network" - }); - } - }; - - xhr.send(); - }, - - /** - * Store an XWikiDocument. - * - * @param id the document identifier. - * @param doc the document JSON object containing - * "parent", "title", "content", and/or "syntax" keys. - * @param andThen a callback taking (err), err being the error if any. - */ - setItem: function (id, doc, andThen) { - priv.doWithFormToken(function (formToken, err) { - if (err) { - that.error(err); - return; - } - var parts = priv.getParts(id); - $.ajax({ - url: priv.xwikiurl + "/bin/preview/" + - parts.space + '/' + parts.page, - type: "POST", - async: true, - dataType: 'text', - data: { - parent: doc.parent || '', - title: doc.title || '', - xredirect: '', - language: 'en', - // RequiresHTMLConversion: 'content', - // content_syntax: doc.syntax || 'xwiki/2.1', - content: doc.content || '', - xeditaction: 'edit', - comment: 'Saved by JiO', - action_saveandcontinue: 'Save & Continue', - syntaxId: doc.syntax || 'xwiki/2.1', - xhidden: 0, - minorEdit: 0, - ajax: true, - form_token: formToken - }, - success: function () { - andThen(null); - }, - error: function (jqxhr, err, cause) { - andThen({ - "status": jqxhr.status, - "statusText": jqxhr.statusText, - "error": err, - "message": "Failed to store document [" + id + "]", - "reason": cause - }); - } - }); - }); - }, - - /** - * Store an XWikiAttachment. - * - * @param docId the ID of the document to attach to. - * @param fileName the attachment file name. - * @param mimeType the MIME type of the attachment content. - * @param content the attachment content. - * @param andThen a callback taking one parameter, the error if any. - */ - setAttachment: function (docId, fileName, mimeType, content, andThen) { - priv.doWithFormToken(function (formToken, err) { - var parts, blob, fd, xhr; - if (err) { - that.error(err); - return; - } - parts = priv.getParts(docId); - blob = priv.isBlob(content) - ? content - : priv.makeBlob([content], {type: mimeType}); - fd = new FormData(); - fd.append("filepath", blob, fileName); - fd.append("form_token", formToken); - xhr = new XMLHttpRequest(); - xhr.open('POST', priv.xwikiurl + "/bin/upload/" + - parts.space + '/' + parts.page, true); - xhr.onload = function (e) { - if (xhr.status === 302 || xhr.status === 200) { - andThen(null); - } else { - andThen({ - "status": xhr.status, - "statusText": xhr.statusText, - "error": "err_network_error", - "message": "Failed to store attachment [" - + docId + "/" + fileName + "]", - "reason": "Error posting data" - }); - } - }; - xhr.send(fd); - }); - }, - - removeItem: function (id, andThen) { - priv.doWithFormToken(function (formToken, err) { - if (err) { - that.error(err); - return; - } - var parts = priv.getParts(id); - $.ajax({ - url: priv.xwikiurl + "/bin/delete/" + - parts.space + '/' + parts.page, - type: "POST", - async: true, - dataType: 'text', - data: { - confirm: '1', - form_token: formToken - }, - success: function () { - andThen(null); - }, - error: function (jqxhr, err, cause) { - andThen({ - "status": jqxhr.status, - "statusText": jqxhr.statusText, - "error": err, - "message": "Failed to delete document [" + id + "]", - "reason": cause - }); - } - }); - }); - }, - - removeAttachment: function (docId, fileName, andThen) { - var parts = priv.getParts(docId); - priv.doWithFormToken(function (formToken, err) { - if (err) { - that.error(err); - return; - } - $.ajax({ - url: priv.xwikiurl + "/bin/delattachment/" + parts.space + '/' + - parts.page + '/' + fileName, - type: "POST", - async: true, - dataType: 'text', - data: { - ajax: '1', - form_token: formToken - }, - success: function () { - andThen(null); - }, - error: function (jqxhr, err, cause) { - andThen({ - "status": jqxhr.status, - "statusText": jqxhr.statusText, - "error": err, - "message": "Failed to delete attachment [" - + docId + '/' + fileName + "]", - "reason": cause - }); - } - }); - }); - } - }; - - // ==================== Tools ==================== - /** - * Update [doc] the document object and remove [doc] keys - * which are not in [new_doc]. It only changes [doc] keys not starting - * with an underscore. - * ex: doc: {key:value1,_key:value2} with - * new_doc: {key:value3,_key:value4} updates - * doc: {key:value3,_key:value2}. - * @param {object} doc The original document object. - * @param {object} new_doc The new document object - */ - priv.documentObjectUpdate = function (doc, new_doc) { - var k; - for (k in doc) { - if (doc.hasOwnProperty(k)) { - if (k[0] !== '_') { - delete doc[k]; - } - } - } - for (k in new_doc) { - if (new_doc.hasOwnProperty(k)) { - if (k[0] !== '_') { - doc[k] = new_doc[k]; - } - } - } - }; - - /** - * Checks if an object has no enumerable keys - * @method objectIsEmpty - * @param {object} obj The object - * @return {boolean} true if empty, else false - */ - priv.objectIsEmpty = function (obj) { - var k; - for (k in obj) { - if (obj.hasOwnProperty(k)) { - return false; - } - } - return true; - }; - - // ==================== attributes ==================== - // the wiki to store stuff in - priv.wiki = spec.wiki || 'xwiki'; - - // unused - priv.username = spec.username; - priv.language = spec.language; - - // URL location of the wiki, unused since - // XWiki doesn't currently allow cross-domain requests. - priv.xwikiurl = spec.xwikiurl || - window.location.href.replace(/\/xwiki\/bin\//, '/xwiki\n') - .split('\n')[0]; - // should be: s@/xwiki/bin/.*$@/xwiki@ - // but jslint gets in the way. - - // Which URL to load for getting the Anti-CSRF form token, used for testing. - priv.formTokenPath = spec.formTokenPath || priv.xwikiurl; - - // If true then Blob objects will be returned by - // getAttachment() rather than strings. - priv.useBlobs = spec.useBlobs || false; - - // If true then Blob objects will be returned by - // getAttachment() rather than strings. - priv.useBlobs = spec.useBlobs || false; - - - that.specToStore = function () { - return { - "username": priv.username, - "language": priv.language, - "xwikiurl": priv.xwikiurl, - }; - }; - - // can't fo wrong since no parameters are required. - that.validateState = function () { - return ''; - }; - - // ==================== commands ==================== - /** - * Create a document in local storage. - * @method post - * @param {object} command The JIO command - */ - that.post = function (command) { - var docId = command.getDocId(); - if (!(typeof docId === "string" && docId !== "")) { - setTimeout(function () { - that.error({ - "status": 405, - "statusText": "Method Not Allowed", - "error": "method_not_allowed", - "message": "Cannot create document which id is undefined", - "reason": "Document id is undefined" - }); - }); - return; - } - xwikistorage.getItem(docId, function (doc, err) { - if (err) { - that.error(err); - } else if (doc === null) { - // the document does not exist - xwikistorage.setItem(command.getDocId(), - command.cloneDoc(), - function (err) { - if (err) { - that.error(err); - } else { - that.success({ - "ok": true, - "id": command.getDocId() - }); - } - }); - } else { - // the document already exists - that.error({ - "status": 409, - "statusText": "Conflicts", - "error": "conflicts", - "message": "Cannot create a new document", - "reason": "Document already exists (use 'put' to modify it)" - }); - } - }); - }; - - /** - * Create or update a document in local storage. - * @method put - * @param {object} command The JIO command - */ - that.put = function (command) { - xwikistorage.getItem(command.getDocId(), function (doc, err) { - if (err) { - that.error(err); - } else if (doc === null) { - doc = command.cloneDoc(); - } else { - priv.documentObjectUpdate(doc, command.cloneDoc()); - } - // write - xwikistorage.setItem(command.getDocId(), doc, function (err) { - if (err) { - that.error(err); - } else { - that.success({ - "ok": true, - "id": command.getDocId() - }); - } - }); - }); - }; - - /** - * Add an attachment to a document - * @method putAttachment - * @param {object} command The JIO command - */ - that.putAttachment = function (command) { - xwikistorage.getItem(command.getDocId(), function (doc, err) { - if (err) { - that.error(err); - } else if (doc === null) { - // the document does not exist - that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": "Impossible to add attachment", - "reason": "Document not found" - }); - } else { - // Document exists, upload attachment. - xwikistorage.setAttachment(command.getDocId(), - command.getAttachmentId(), - command.getAttachmentMimeType(), - command.getAttachmentData(), - function (err) { - if (err) { - that.error(err); - } else { - that.success({ - "ok": true, - "id": command.getDocId() + "/" + command.getAttachmentId() - }); - } - }); - } - }); - }; - - /** - * Get a document or attachment - * @method get - * @param {object} command The JIO command - */ - that.get = that.getAttachment = function (command) { - if (typeof command.getAttachmentId() === "string") { - // seeking for an attachment - xwikistorage.getAttachment(command.getDocId(), - command.getAttachmentId(), - function (attach, err) { - if (err) { - that.error(err); - } else if (attach !== null) { - that.success(attach); - } else { - that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": "Cannot find the attachment", - "reason": "Attachment does not exist" - }); - } - }); - } else { - // seeking for a document - xwikistorage.getItem(command.getDocId(), function (doc, err) { - if (err) { - that.error(err); - } else if (doc !== null) { - that.success(doc); - } else { - that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": "Cannot find the document", - "reason": "Document does not exist" - }); - } - }); - } - }; - - /** - * Remove a document or attachment - * @method remove - * @param {object} command The JIO command - */ - that.remove = that.removeAttachment = function (command) { - var notFoundError, objId, complete; - notFoundError = function (word) { - that.error({ - "status": 404, - "statusText": "Not Found", - "error": "not_found", - "message": word + " not found", - "reason": "missing" - }); - }; - - objId = command.getDocId(); - complete = function (err) { - if (err) { - that.error(err); - } else { - that.success({ - "ok": true, - "id": objId - }); - } - }; - if (typeof command.getAttachmentId() === "string") { - objId += '/' + command.getAttachmentId(); - xwikistorage.removeAttachment(command.getDocId(), - command.getAttachmentId(), - complete); - } else { - xwikistorage.removeItem(objId, complete); - } - }; - - /** - * Get all filenames belonging to a user from the document index - * @method allDocs - * @param {object} command The JIO command - */ - that.allDocs = function () { - setTimeout(function () { - that.error({ - "status": 405, - "statusText": "Method Not Allowed", - "error": "method_not_allowed", - "message": "Your are not allowed to use this command", - "reason": "xwikistorage forbids AllDocs command executions" - }); - }); - }; - - return that; - }; - - if (typeof (define) === 'function' && define.amd) { - define(['jquery', 'jio'], function (jquery, jIO) { - $ = jquery; - jIO.addStorageType('xwiki', store); - }); - } else { - jIO.addStorageType('xwiki', store); - $ = jQuery; - } - -}());