Commit 8143c5a8 authored by Romain Courteaud's avatar Romain Courteaud

Release 3.35.0

Fix IndexedDB transaction handling.

Ease usage of ReplicateStorage.
parent e9a130ed
...@@ -26,7 +26,7 @@ TESTDIR = test ...@@ -26,7 +26,7 @@ TESTDIR = test
EXAMPLEDIR = examples EXAMPLEDIR = examples
EXTERNALDIR = external EXTERNALDIR = external
VERSION = 3.34.0 VERSION = 3.35.0
JIOVERSION = ${DISTDIR}/jio-v${VERSION}.js JIOVERSION = ${DISTDIR}/jio-v${VERSION}.js
JIOLATEST = ${DISTDIR}/jio-latest.js JIOLATEST = ${DISTDIR}/jio-latest.js
JIONODEVERSION = ${DISTDIR}/jio-v${VERSION}-node.js JIONODEVERSION = ${DISTDIR}/jio-v${VERSION}-node.js
......
...@@ -10181,10 +10181,9 @@ var XMLHttpRequest = global.XMLHttpRequest || module.exports, ...@@ -10181,10 +10181,9 @@ var XMLHttpRequest = global.XMLHttpRequest || module.exports,
function readBlobAsText(blob, encoding) { function readBlobAsText(blob, encoding) {
var fr = new FileReader(); var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) { return new RSVP.Promise(function (resolve, reject) {
fr.addEventListener("load", resolve); fr.addEventListener("load", resolve);
fr.addEventListener("error", reject); fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsText(blob, encoding); fr.readAsText(blob, encoding);
}, function () { }, function () {
fr.abort(); fr.abort();
...@@ -10194,10 +10193,9 @@ var XMLHttpRequest = global.XMLHttpRequest || module.exports, ...@@ -10194,10 +10193,9 @@ var XMLHttpRequest = global.XMLHttpRequest || module.exports,
function readBlobAsArrayBuffer(blob) { function readBlobAsArrayBuffer(blob) {
var fr = new FileReader(); var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) { return new RSVP.Promise(function (resolve, reject) {
fr.addEventListener("load", resolve); fr.addEventListener("load", resolve);
fr.addEventListener("error", reject); fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsArrayBuffer(blob); fr.readAsArrayBuffer(blob);
}, function () { }, function () {
fr.abort(); fr.abort();
...@@ -10207,10 +10205,9 @@ var XMLHttpRequest = global.XMLHttpRequest || module.exports, ...@@ -10207,10 +10205,9 @@ var XMLHttpRequest = global.XMLHttpRequest || module.exports,
function readBlobAsDataURL(blob) { function readBlobAsDataURL(blob) {
var fr = new FileReader(); var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) { return new RSVP.Promise(function (resolve, reject) {
fr.addEventListener("load", resolve); fr.addEventListener("load", resolve);
fr.addEventListener("error", reject); fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsDataURL(blob); fr.readAsDataURL(blob);
}, function () { }, function () {
fr.abort(); fr.abort();
...@@ -10741,7 +10738,218 @@ var jIO = window.jIO, ...@@ -10741,7 +10738,218 @@ var jIO = window.jIO,
CONFLICT_THROW = 0, CONFLICT_THROW = 0,
CONFLICT_KEEP_LOCAL = 1, CONFLICT_KEEP_LOCAL = 1,
CONFLICT_KEEP_REMOTE = 2, CONFLICT_KEEP_REMOTE = 2,
CONFLICT_CONTINUE = 3; CONFLICT_CONTINUE = 3,
// 0 - 99 error
LOG_UNEXPECTED_ERROR = 0,
LOG_UNRESOLVED_CONFLICT = 74,
LOG_UNEXPECTED_LOCAL_ATTACHMENT = 49,
LOG_UNEXPECTED_REMOTE_ATTACHMENT = 47,
LOG_UNRESOLVED_ATTACHMENT_CONFLICT = 75,
// 100 - 199 solving conflict
LOG_FORCE_PUT_REMOTE = 116,
LOG_FORCE_DELETE_REMOTE = 136,
LOG_FORCE_PUT_REMOTE_ATTACHMENT = 117,
LOG_FORCE_DELETE_REMOTE_ATTACHMENT = 137,
LOG_FORCE_PUT_LOCAL = 118,
LOG_FORCE_DELETE_LOCAL = 138,
LOG_FORCE_PUT_LOCAL_ATTACHMENT = 119,
LOG_FORCE_DELETE_LOCAL_ATTACHMENT = 139,
// 200 - 299 pushing change
LOG_PUT_REMOTE = 216,
LOG_POST_REMOTE = 226,
LOG_DELETE_REMOTE = 236,
LOG_PUT_REMOTE_ATTACHMENT = 217,
LOG_DELETE_REMOTE_ATTACHMENT = 237,
LOG_PUT_LOCAL = 218,
LOG_POST_LOCAL = 228,
LOG_DELETE_LOCAL = 238,
LOG_PUT_LOCAL_ATTACHMENT = 219,
LOG_DELETE_LOCAL_ATTACHMENT = 239,
LOG_FALSE_CONFLICT = 284,
LOG_FALSE_CONFLICT_ATTACHMENT = 285,
// 300 - 399 nothing to do
LOG_SKIP_LOCAL_CREATION = 348,
LOG_SKIP_LOCAL_MODIFICATION = 358,
LOG_SKIP_LOCAL_DELETION = 368,
LOG_SKIP_REMOTE_CREATION = 346,
LOG_SKIP_REMOTE_MODIFICATION = 356,
LOG_SKIP_REMOTE_DELETION = 366,
LOG_SKIP_LOCAL_ATTACHMENT_CREATION = 349,
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION = 359,
LOG_SKIP_LOCAL_ATTACHMENT_DELETION = 369,
LOG_SKIP_REMOTE_ATTACHMENT_CREATION = 347,
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION = 357,
LOG_SKIP_REMOTE_ATTACHMENT_DELETION = 367,
LOG_SKIP_CONFLICT = 374,
LOG_SKIP_CONFLICT_ATTACHMENT = 375,
LOG_NO_CHANGE = 384,
LOG_NO_CHANGE_ATTACHMENT = 385;
function ReplicateReport(log_level, log_console) {
this._list = [];
this.name = 'ReplicateReport';
this.message = this.name;
this.has_error = false;
this._log_level = log_level;
this._log_console = log_console;
}
ReplicateReport.prototype = {
constructor: ReplicateReport,
LOG_UNEXPECTED_ERROR: LOG_UNEXPECTED_ERROR,
LOG_UNRESOLVED_CONFLICT: LOG_UNRESOLVED_CONFLICT,
LOG_UNEXPECTED_LOCAL_ATTACHMENT: LOG_UNEXPECTED_LOCAL_ATTACHMENT,
LOG_UNEXPECTED_REMOTE_ATTACHMENT: LOG_UNEXPECTED_REMOTE_ATTACHMENT,
LOG_UNRESOLVED_ATTACHMENT_CONFLICT: LOG_UNRESOLVED_ATTACHMENT_CONFLICT,
LOG_FORCE_PUT_REMOTE: LOG_FORCE_PUT_REMOTE,
LOG_FORCE_DELETE_REMOTE: LOG_FORCE_DELETE_REMOTE,
LOG_FORCE_PUT_LOCAL: LOG_FORCE_PUT_LOCAL,
LOG_FORCE_DELETE_LOCAL: LOG_FORCE_DELETE_LOCAL,
LOG_FORCE_PUT_REMOTE_ATTACHMENT: LOG_FORCE_PUT_REMOTE_ATTACHMENT,
LOG_FORCE_DELETE_REMOTE_ATTACHMENT: LOG_FORCE_DELETE_REMOTE_ATTACHMENT,
LOG_FORCE_PUT_LOCAL_ATTACHMENT: LOG_FORCE_PUT_LOCAL_ATTACHMENT,
LOG_FORCE_DELETE_LOCAL_ATTACHMENT: LOG_FORCE_DELETE_LOCAL_ATTACHMENT,
LOG_PUT_REMOTE: LOG_PUT_REMOTE,
LOG_POST_REMOTE: LOG_POST_REMOTE,
LOG_DELETE_REMOTE: LOG_DELETE_REMOTE,
LOG_PUT_REMOTE_ATTACHMENT: LOG_PUT_REMOTE_ATTACHMENT,
LOG_DELETE_REMOTE_ATTACHMENT: LOG_DELETE_REMOTE_ATTACHMENT,
LOG_PUT_LOCAL: LOG_PUT_LOCAL,
LOG_DELETE_LOCAL: LOG_DELETE_LOCAL,
LOG_PUT_LOCAL_ATTACHMENT: LOG_PUT_LOCAL_ATTACHMENT,
LOG_DELETE_LOCAL_ATTACHMENT: LOG_DELETE_LOCAL_ATTACHMENT,
LOG_FALSE_CONFLICT: LOG_FALSE_CONFLICT,
LOG_FALSE_CONFLICT_ATTACHMENT: LOG_FALSE_CONFLICT_ATTACHMENT,
LOG_SKIP_LOCAL_CREATION: LOG_SKIP_LOCAL_CREATION,
LOG_SKIP_LOCAL_MODIFICATION: LOG_SKIP_LOCAL_MODIFICATION,
LOG_SKIP_LOCAL_DELETION: LOG_SKIP_LOCAL_DELETION,
LOG_SKIP_REMOTE_CREATION: LOG_SKIP_REMOTE_CREATION,
LOG_SKIP_REMOTE_MODIFICATION: LOG_SKIP_REMOTE_MODIFICATION,
LOG_SKIP_REMOTE_DELETION: LOG_SKIP_REMOTE_DELETION,
LOG_SKIP_LOCAL_ATTACHMENT_CREATION: LOG_SKIP_LOCAL_ATTACHMENT_CREATION,
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION:
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION,
LOG_SKIP_LOCAL_ATTACHMENT_DELETION: LOG_SKIP_LOCAL_ATTACHMENT_DELETION,
LOG_SKIP_REMOTE_ATTACHMENT_CREATION: LOG_SKIP_REMOTE_ATTACHMENT_CREATION,
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION:
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION,
LOG_SKIP_REMOTE_ATTACHMENT_DELETION: LOG_SKIP_REMOTE_ATTACHMENT_DELETION,
LOG_SKIP_CONFLICT: LOG_SKIP_CONFLICT,
LOG_SKIP_CONFLICT_ATTACHMENT: LOG_SKIP_CONFLICT_ATTACHMENT,
LOG_NO_CHANGE: LOG_NO_CHANGE,
LOG_NO_CHANGE_ATTACHMENT: LOG_NO_CHANGE_ATTACHMENT,
logConsole: function (code, a, b, c) {
if (!this._log_console) {
return;
}
var txt = code,
parsed_code = code,
log;
// Check severity level
if (parsed_code >= 300) {
txt += ' SKIP ';
log = console.info;
} else if (parsed_code >= 200) {
txt += ' SOLVE ';
log = console.log;
} else if (parsed_code >= 100) {
txt += ' FORCE ';
log = console.warn;
} else {
txt += ' ERROR ';
log = console.error;
}
// Check operation
parsed_code = code % 100;
if (parsed_code >= 80) {
txt += 'idem ';
} else if (parsed_code >= 70) {
txt += 'conflict ';
} else if (parsed_code >= 60) {
txt += 'deleted ';
} else if (parsed_code >= 50) {
txt += 'modified ';
} else if (parsed_code >= 40) {
txt += 'created ';
} else if (parsed_code >= 30) {
txt += 'delete ';
} else if (parsed_code >= 20) {
txt += 'post ';
} else if (parsed_code >= 10) {
txt += 'put ';
}
// Check document
parsed_code = code % 10;
if (parsed_code >= 8) {
txt += 'local ';
} else if (parsed_code >= 6) {
txt += 'remote ';
}
if (parsed_code !== 0) {
txt += (parsed_code % 2 === 0) ? 'document' : 'attachment';
}
txt += ' ' + a;
if (b !== undefined) {
txt += ' ' + b;
if (c !== undefined) {
txt += ' ' + c;
}
}
log(txt);
},
log: function (id, type, extra) {
if (type === undefined) {
if (extra === undefined) {
extra = 'Unknown type: ' + type;
}
type = LOG_UNEXPECTED_ERROR;
}
if (type < this._log_level) {
if (extra === undefined) {
this.logConsole(type, id);
this._list.push([type, id]);
} else {
this.logConsole(type, id, extra);
this._list.push([type, id, extra]);
}
if (type < 100) {
this.has_error = true;
}
}
},
logAttachment: function (id, name, type, extra) {
if (type === undefined) {
if (extra === undefined) {
extra = 'Unknown type: ' + type;
}
type = LOG_UNEXPECTED_ERROR;
}
if (type < this._log_level) {
if (extra === undefined) {
this.logConsole(type, id, name);
this._list.push([type, id, name]);
} else {
this.logConsole(type, id, name, extra);
this._list.push([type, id, name, extra]);
}
if (type < 100) {
this.has_error = true;
}
}
},
toString: function () {
return this._list.toString();
}
};
function SkipError(message) { function SkipError(message) {
if ((message !== undefined) && (typeof message !== "string")) { if ((message !== undefined) && (typeof message !== "string")) {
...@@ -10770,6 +10978,8 @@ var jIO = window.jIO, ...@@ -10770,6 +10978,8 @@ var jIO = window.jIO,
function ReplicateStorage(spec) { function ReplicateStorage(spec) {
this._query_options = spec.query || {}; this._query_options = spec.query || {};
this._log_level = spec.report_level || 100;
this._log_console = spec.debug || false;
if (spec.signature_hash_key !== undefined) { if (spec.signature_hash_key !== undefined) {
this._query_options.select_list = [spec.signature_hash_key]; this._query_options.select_list = [spec.signature_hash_key];
} }
...@@ -10987,30 +11197,42 @@ var jIO = window.jIO, ...@@ -10987,30 +11197,42 @@ var jIO = window.jIO,
}); });
} }
function propagateAttachmentDeletion(context, skip_attachment_dict, function propagateAttachmentDeletion(context,
destination, destination,
id, name) { id, name,
conflict, from_local, report) {
if (conflict) {
report.logAttachment(id, name, from_local ?
LOG_FORCE_DELETE_REMOTE_ATTACHMENT :
LOG_FORCE_DELETE_LOCAL_ATTACHMENT);
} else {
report.logAttachment(id, name, from_local ? LOG_DELETE_REMOTE_ATTACHMENT :
LOG_DELETE_LOCAL_ATTACHMENT);
}
return destination.removeAttachment(id, name) return destination.removeAttachment(id, name)
.push(function () { .push(function () {
return context._signature_sub_storage.removeAttachment(id, name); return context._signature_sub_storage.removeAttachment(id, name);
})
.push(function () {
skip_attachment_dict[name] = null;
}); });
} }
function propagateAttachmentModification(context, skip_attachment_dict, function propagateAttachmentModification(context,
destination, destination,
blob, hash, id, name) { blob, hash, id, name,
from_local, is_conflict, report) {
if (is_conflict) {
report.logAttachment(id, name, from_local ?
LOG_FORCE_PUT_REMOTE_ATTACHMENT :
LOG_FORCE_PUT_LOCAL_ATTACHMENT);
} else {
report.logAttachment(id, name, from_local ? LOG_PUT_REMOTE_ATTACHMENT :
LOG_PUT_LOCAL_ATTACHMENT);
}
return destination.putAttachment(id, name, blob) return destination.putAttachment(id, name, blob)
.push(function () { .push(function () {
return context._signature_sub_storage.putAttachment(id, name, return context._signature_sub_storage.putAttachment(id, name,
JSON.stringify({ JSON.stringify({
hash: hash hash: hash
})); }));
})
.push(function () {
skip_attachment_dict[name] = null;
}); });
} }
...@@ -11019,7 +11241,9 @@ var jIO = window.jIO, ...@@ -11019,7 +11241,9 @@ var jIO = window.jIO,
status_hash, local_hash, blob, status_hash, local_hash, blob,
source, destination, id, name, source, destination, id, name,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore) { conflict_ignore, from_local, report) {
// No need to check twice
skip_attachment_dict[name] = null;
var remote_blob; var remote_blob;
return destination.getAttachment(id, name) return destination.getAttachment(id, name)
.push(function (result) { .push(function (result) {
...@@ -11041,39 +11265,39 @@ var jIO = window.jIO, ...@@ -11041,39 +11265,39 @@ var jIO = window.jIO,
.push(function (remote_hash) { .push(function (remote_hash) {
if (local_hash === remote_hash) { if (local_hash === remote_hash) {
// Same modifications on both side // Same modifications on both side
report.logAttachment(id, name, LOG_FALSE_CONFLICT_ATTACHMENT);
if (local_hash === null) { if (local_hash === null) {
// Deleted on both side, drop signature // Deleted on both side, drop signature
return context._signature_sub_storage.removeAttachment(id, name) return context._signature_sub_storage.removeAttachment(id, name);
.push(function () {
skip_attachment_dict[name] = null;
});
} }
return context._signature_sub_storage.putAttachment(id, name, return context._signature_sub_storage.putAttachment(id, name,
JSON.stringify({ JSON.stringify({
hash: local_hash hash: local_hash
})) }));
.push(function () {
skip_attachment_dict[name] = null;
});
} }
if ((remote_hash === status_hash) || (conflict_force === true)) { if ((remote_hash === status_hash) || (conflict_force === true)) {
// Modified only locally. No conflict or force // Modified only locally. No conflict or force
if (local_hash === null) { if (local_hash === null) {
// Deleted locally // Deleted locally
return propagateAttachmentDeletion(context, skip_attachment_dict, return propagateAttachmentDeletion(context,
destination, destination,
id, name); id, name,
(remote_hash !== status_hash),
from_local, report);
} }
return propagateAttachmentModification(context, return propagateAttachmentModification(context,
skip_attachment_dict,
destination, blob, destination, blob,
local_hash, id, name); local_hash, id, name,
from_local,
(remote_hash !== status_hash),
report);
} }
// Conflict cases // Conflict cases
if (conflict_ignore === true) { if (conflict_ignore === true) {
report.logAttachment(id, name, LOG_SKIP_CONFLICT_ATTACHMENT);
return; return;
} }
...@@ -11081,17 +11305,21 @@ var jIO = window.jIO, ...@@ -11081,17 +11305,21 @@ var jIO = window.jIO,
// Automatically resolve conflict or force revert // Automatically resolve conflict or force revert
if (remote_hash === null) { if (remote_hash === null) {
// Deleted remotely // Deleted remotely
return propagateAttachmentDeletion(context, skip_attachment_dict, return propagateAttachmentDeletion(context,
source, id, name); source, id, name,
(local_hash !== status_hash),
!from_local, report);
} }
return propagateAttachmentModification( return propagateAttachmentModification(
context, context,
skip_attachment_dict,
source, source,
remote_blob, remote_blob,
remote_hash, remote_hash,
id, id,
name name,
!from_local,
(local_hash !== status_hash),
report
); );
} }
...@@ -11099,14 +11327,15 @@ var jIO = window.jIO, ...@@ -11099,14 +11327,15 @@ var jIO = window.jIO,
if (remote_hash === null) { if (remote_hash === null) {
// Copy remote modification remotely // Copy remote modification remotely
return propagateAttachmentModification(context, return propagateAttachmentModification(context,
skip_attachment_dict,
destination, blob, destination, blob,
local_hash, id, name); local_hash, id, name, from_local,
false,
report);
} }
throw new jIO.util.jIOError("Conflict on '" + id + report.logAttachment(id, name, LOG_UNRESOLVED_ATTACHMENT_CONFLICT);
"' with attachment '" + })
name + "'", .push(undefined, function (error) {
409); report.logAttachment(id, name, LOG_UNEXPECTED_ERROR, error);
}); });
} }
...@@ -11117,7 +11346,9 @@ var jIO = window.jIO, ...@@ -11117,7 +11346,9 @@ var jIO = window.jIO,
conflict_force, conflict_force,
conflict_revert, conflict_revert,
conflict_ignore, conflict_ignore,
is_creation, is_modification) { is_creation, is_modification,
from_local,
report) {
var blob, var blob,
status_hash; status_hash;
queue queue
...@@ -11152,14 +11383,20 @@ var jIO = window.jIO, ...@@ -11152,14 +11383,20 @@ var jIO = window.jIO,
var array_buffer = evt.target.result, var array_buffer = evt.target.result,
local_hash = generateHashFromArrayBuffer(array_buffer); local_hash = generateHashFromArrayBuffer(array_buffer);
if (local_hash !== status_hash) { if (local_hash === status_hash) {
return checkAndPropagateAttachment(context, if (!from_local) {
skip_attachment_dict, report.logAttachment(id, name, LOG_NO_CHANGE_ATTACHMENT);
status_hash, local_hash, blob, }
source, destination, id, name, return;
conflict_force, conflict_revert,
conflict_ignore);
} }
return checkAndPropagateAttachment(context,
skip_attachment_dict,
status_hash, local_hash, blob,
source, destination, id, name,
conflict_force, conflict_revert,
conflict_ignore,
from_local,
report);
}); });
} }
...@@ -11167,7 +11404,7 @@ var jIO = window.jIO, ...@@ -11167,7 +11404,7 @@ var jIO = window.jIO,
skip_attachment_dict, skip_attachment_dict,
destination, id, name, source, destination, id, name, source,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore) { conflict_ignore, from_local, report) {
var status_hash; var status_hash;
queue queue
.push(function () { .push(function () {
...@@ -11181,16 +11418,17 @@ var jIO = window.jIO, ...@@ -11181,16 +11418,17 @@ var jIO = window.jIO,
status_hash, null, null, status_hash, null, null,
source, destination, id, name, source, destination, id, name,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore); conflict_ignore, from_local, report);
}); });
} }
function pushDocumentAttachment(context, function pushDocumentAttachment(context,
skip_attachment_dict, id, source, skip_attachment_dict, id, source,
destination, signature_allAttachments, destination, signature_allAttachments,
options) { report, options) {
var local_dict = {}, var local_dict = {},
signature_dict = {}; signature_dict = {},
from_local = options.from_local;
return source.allAttachments(id) return source.allAttachments(id)
.push(undefined, function (error) { .push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) && if ((error instanceof jIO.util.jIOError) &&
...@@ -11235,7 +11473,19 @@ var jIO = window.jIO, ...@@ -11235,7 +11473,19 @@ var jIO = window.jIO,
options.conflict_revert, options.conflict_revert,
options.conflict_ignore, options.conflict_ignore,
is_creation, is_creation,
is_modification]); is_modification,
from_local,
report]);
} else {
if (signature_dict.hasOwnProperty(key)) {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION :
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_CREATION :
LOG_SKIP_REMOTE_ATTACHMENT_CREATION);
}
} }
} }
} }
...@@ -11248,10 +11498,10 @@ var jIO = window.jIO, ...@@ -11248,10 +11498,10 @@ var jIO = window.jIO,
}) })
.push(function () { .push(function () {
var key, argument_list = []; var key, argument_list = [];
if (options.check_deletion === true) { for (key in signature_dict) {
for (key in signature_dict) { if (signature_dict.hasOwnProperty(key)) {
if (signature_dict.hasOwnProperty(key)) { if (!local_dict.hasOwnProperty(key)) {
if (!local_dict.hasOwnProperty(key)) { if (options.check_deletion === true) {
argument_list.push([undefined, argument_list.push([undefined,
context, context,
skip_attachment_dict, skip_attachment_dict,
...@@ -11259,29 +11509,51 @@ var jIO = window.jIO, ...@@ -11259,29 +11509,51 @@ var jIO = window.jIO,
source, source,
options.conflict_force, options.conflict_force,
options.conflict_revert, options.conflict_revert,
options.conflict_ignore]); options.conflict_ignore,
from_local,
report]);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_DELETION :
LOG_SKIP_REMOTE_ATTACHMENT_DELETION);
} }
} }
} }
return dispatchQueue(
context,
checkAttachmentLocalDeletion,
argument_list,
context._parallel_operation_attachment_amount
);
} }
return dispatchQueue(
context,
checkAttachmentLocalDeletion,
argument_list,
context._parallel_operation_attachment_amount
);
}); });
} }
function propagateFastAttachmentDeletion(queue, id, name, storage) { function propagateFastAttachmentDeletion(queue, id, name, storage, signature,
from_local, report) {
report.logAttachment(id, name, from_local ? LOG_DELETE_REMOTE_ATTACHMENT :
LOG_DELETE_LOCAL_ATTACHMENT);
return queue return queue
.push(function () { .push(function () {
return storage.removeAttachment(id, name); return storage.removeAttachment(id, name);
})
.push(function () {
return signature.removeAttachment(id, name);
});
}
function propagateFastSignatureDeletion(queue, id, name, signature,
report) {
report.logAttachment(id, name, LOG_FALSE_CONFLICT_ATTACHMENT);
return queue
.push(function () {
return signature.removeAttachment(id, name);
}); });
} }
function propagateFastAttachmentModification(queue, id, key, source, function propagateFastAttachmentModification(queue, id, key, source,
destination, signature, hash) { destination, signature, hash,
from_local, report) {
return queue return queue
.push(function () { .push(function () {
return signature.getAttachment(id, key, {format: 'json'}) return signature.getAttachment(id, key, {format: 'json'})
...@@ -11294,6 +11566,9 @@ var jIO = window.jIO, ...@@ -11294,6 +11566,9 @@ var jIO = window.jIO,
}) })
.push(function (result) { .push(function (result) {
if (result.hash !== hash) { if (result.hash !== hash) {
report.logAttachment(id, key, from_local ?
LOG_PUT_REMOTE_ATTACHMENT :
LOG_PUT_LOCAL_ATTACHMENT);
return source.getAttachment(id, key) return source.getAttachment(id, key)
.push(function (blob) { .push(function (blob) {
return destination.putAttachment(id, key, blob); return destination.putAttachment(id, key, blob);
...@@ -11312,7 +11587,8 @@ var jIO = window.jIO, ...@@ -11312,7 +11587,8 @@ var jIO = window.jIO,
function repairFastDocumentAttachment(context, id, function repairFastDocumentAttachment(context, id,
signature_hash, signature_hash,
signature_attachment_hash, signature_attachment_hash,
signature_from_local) { signature_from_local,
report) {
if (signature_hash === signature_attachment_hash) { if (signature_hash === signature_attachment_hash) {
// No replication to do // No replication to do
return; return;
...@@ -11333,6 +11609,7 @@ var jIO = window.jIO, ...@@ -11333,6 +11609,7 @@ var jIO = window.jIO,
destination, destination,
push_argument_list = [], push_argument_list = [],
delete_argument_list = [], delete_argument_list = [],
delete_signature_argument_list = [],
signature_attachment_dict = result_list[0], signature_attachment_dict = result_list[0],
local_attachment_dict = result_list[1], local_attachment_dict = result_list[1],
remote_attachment_list = result_list[2], remote_attachment_list = result_list[2],
...@@ -11343,13 +11620,15 @@ var jIO = window.jIO, ...@@ -11343,13 +11620,15 @@ var jIO = window.jIO,
check_remote_modification = check_remote_modification =
context._check_remote_attachment_modification, context._check_remote_attachment_modification,
check_remote_creation = context._check_remote_attachment_creation, check_remote_creation = context._check_remote_attachment_creation,
check_remote_deletion = context._check_remote_attachment_deletion; check_remote_deletion = context._check_remote_attachment_deletion,
from_local;
if (signature_from_local) { if (signature_from_local) {
source_attachment_dict = local_attachment_dict; source_attachment_dict = local_attachment_dict;
destination_attachment_dict = remote_attachment_list; destination_attachment_dict = remote_attachment_list;
source = context._local_sub_storage; source = context._local_sub_storage;
destination = context._remote_sub_storage; destination = context._remote_sub_storage;
from_local = true;
} else { } else {
source_attachment_dict = remote_attachment_list; source_attachment_dict = remote_attachment_list;
destination_attachment_dict = local_attachment_dict; destination_attachment_dict = local_attachment_dict;
...@@ -11360,6 +11639,7 @@ var jIO = window.jIO, ...@@ -11360,6 +11639,7 @@ var jIO = window.jIO,
check_local_deletion = check_remote_deletion; check_local_deletion = check_remote_deletion;
check_remote_creation = check_local_creation; check_remote_creation = check_local_creation;
check_remote_deletion = check_local_deletion; check_remote_deletion = check_local_deletion;
from_local = false;
} }
// Push all source attachments // Push all source attachments
...@@ -11377,8 +11657,20 @@ var jIO = window.jIO, ...@@ -11377,8 +11657,20 @@ var jIO = window.jIO,
source, source,
destination, destination,
context._signature_sub_storage, context._signature_sub_storage,
signature_hash signature_hash,
from_local,
report
]); ]);
} else {
if (signature_attachment_dict.hasOwnProperty(key)) {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION :
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_CREATION :
LOG_SKIP_REMOTE_ATTACHMENT_CREATION);
}
} }
} }
} }
...@@ -11387,16 +11679,19 @@ var jIO = window.jIO, ...@@ -11387,16 +11679,19 @@ var jIO = window.jIO,
for (key in signature_attachment_dict) { for (key in signature_attachment_dict) {
if (signature_attachment_dict.hasOwnProperty(key)) { if (signature_attachment_dict.hasOwnProperty(key)) {
if (check_local_deletion && if (check_local_deletion &&
!source_attachment_dict.hasOwnProperty(key)) { !source_attachment_dict.hasOwnProperty(key) &&
delete_argument_list.push([ !destination_attachment_dict.hasOwnProperty(key)) {
delete_signature_argument_list.push([
undefined, undefined,
id, id,
key, key,
context._signature_sub_storage context._signature_sub_storage,
report
]); ]);
} }
} }
} }
for (key in destination_attachment_dict) { for (key in destination_attachment_dict) {
if (destination_attachment_dict.hasOwnProperty(key)) { if (destination_attachment_dict.hasOwnProperty(key)) {
if (!source_attachment_dict.hasOwnProperty(key)) { if (!source_attachment_dict.hasOwnProperty(key)) {
...@@ -11408,8 +11703,21 @@ var jIO = window.jIO, ...@@ -11408,8 +11703,21 @@ var jIO = window.jIO,
undefined, undefined,
id, id,
key, key,
destination destination,
context._signature_sub_storage,
from_local,
report
]); ]);
} else {
if (signature_attachment_dict.hasOwnProperty(key)) {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_DELETION :
LOG_SKIP_REMOTE_ATTACHMENT_DELETION);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_CREATION :
LOG_SKIP_REMOTE_ATTACHMENT_CREATION);
}
} }
} }
} }
...@@ -11427,6 +11735,12 @@ var jIO = window.jIO, ...@@ -11427,6 +11735,12 @@ var jIO = window.jIO,
propagateFastAttachmentDeletion, propagateFastAttachmentDeletion,
delete_argument_list, delete_argument_list,
context._parallel_operation_attachment_amount context._parallel_operation_attachment_amount
),
dispatchQueue(
context,
propagateFastSignatureDeletion,
delete_signature_argument_list,
context._parallel_operation_attachment_amount
) )
]); ]);
}) })
...@@ -11440,7 +11754,7 @@ var jIO = window.jIO, ...@@ -11440,7 +11754,7 @@ var jIO = window.jIO,
}); });
} }
function repairDocumentAttachment(context, id, signature_hash_key, function repairDocumentAttachment(context, id, report, signature_hash_key,
signature_hash, signature_hash,
signature_attachment_hash, signature_attachment_hash,
signature_from_local) { signature_from_local) {
...@@ -11448,7 +11762,7 @@ var jIO = window.jIO, ...@@ -11448,7 +11762,7 @@ var jIO = window.jIO,
return repairFastDocumentAttachment(context, id, return repairFastDocumentAttachment(context, id,
signature_hash, signature_hash,
signature_attachment_hash, signature_attachment_hash,
signature_from_local); signature_from_local, report);
} }
var skip_attachment_dict = {}; var skip_attachment_dict = {};
...@@ -11482,6 +11796,7 @@ var jIO = window.jIO, ...@@ -11482,6 +11796,7 @@ var jIO = window.jIO,
context._local_sub_storage, context._local_sub_storage,
context._remote_sub_storage, context._remote_sub_storage,
signature_allAttachments, signature_allAttachments,
report,
{ {
conflict_force: (context._conflict_handling === conflict_force: (context._conflict_handling ===
CONFLICT_KEEP_LOCAL), CONFLICT_KEEP_LOCAL),
...@@ -11492,7 +11807,8 @@ var jIO = window.jIO, ...@@ -11492,7 +11807,8 @@ var jIO = window.jIO,
check_modification: check_modification:
context._check_local_attachment_modification, context._check_local_attachment_modification,
check_creation: context._check_local_attachment_creation, check_creation: context._check_local_attachment_creation,
check_deletion: context._check_local_attachment_deletion check_deletion: context._check_local_attachment_deletion,
from_local: true
} }
) )
.push(function () { .push(function () {
...@@ -11512,6 +11828,7 @@ var jIO = window.jIO, ...@@ -11512,6 +11828,7 @@ var jIO = window.jIO,
context._remote_sub_storage, context._remote_sub_storage,
context._local_sub_storage, context._local_sub_storage,
signature_allAttachments, signature_allAttachments,
report,
{ {
use_revert_post: context._use_remote_post, use_revert_post: context._use_remote_post,
conflict_force: (context._conflict_handling === conflict_force: (context._conflict_handling ===
...@@ -11523,7 +11840,8 @@ var jIO = window.jIO, ...@@ -11523,7 +11840,8 @@ var jIO = window.jIO,
check_modification: check_modification:
context._check_remote_attachment_modification, context._check_remote_attachment_modification,
check_creation: context._check_remote_attachment_creation, check_creation: context._check_remote_attachment_creation,
check_deletion: context._check_remote_attachment_deletion check_deletion: context._check_remote_attachment_deletion,
from_local: false
} }
); );
} }
...@@ -11533,15 +11851,17 @@ var jIO = window.jIO, ...@@ -11533,15 +11851,17 @@ var jIO = window.jIO,
function propagateModification(context, source, destination, doc, hash, id, function propagateModification(context, source, destination, doc, hash, id,
skip_document_dict, skip_document_dict,
skip_deleted_document_dict, skip_deleted_document_dict,
report,
options) { options) {
var result = new RSVP.Queue(), var result = new RSVP.Queue(),
post_id, post_id,
to_skip = true, from_local,
from_local; conflict;
if (options === undefined) { if (options === undefined) {
options = {}; options = {};
} }
from_local = options.from_local; from_local = options.from_local;
conflict = options.conflict || false;
if (doc === null) { if (doc === null) {
result result
...@@ -11561,10 +11881,10 @@ var jIO = window.jIO, ...@@ -11561,10 +11881,10 @@ var jIO = window.jIO,
if (options.use_post) { if (options.use_post) {
result result
.push(function () { .push(function () {
report.log(id, from_local ? LOG_POST_REMOTE : LOG_POST_LOCAL);
return destination.post(doc); return destination.post(doc);
}) })
.push(function (new_id) { .push(function (new_id) {
to_skip = false;
post_id = new_id; post_id = new_id;
return source.put(post_id, doc); return source.put(post_id, doc);
}) })
...@@ -11602,7 +11922,6 @@ var jIO = window.jIO, ...@@ -11602,7 +11922,6 @@ var jIO = window.jIO,
return context._signature_sub_storage.remove(id); return context._signature_sub_storage.remove(id);
}) })
.push(function () { .push(function () {
to_skip = true;
return context._signature_sub_storage.put(post_id, { return context._signature_sub_storage.put(post_id, {
hash: hash, hash: hash,
from_local: from_local from_local: from_local
...@@ -11614,6 +11933,12 @@ var jIO = window.jIO, ...@@ -11614,6 +11933,12 @@ var jIO = window.jIO,
} else { } else {
result result
.push(function () { .push(function () {
if (conflict) {
report.log(id, from_local ? LOG_FORCE_PUT_REMOTE :
LOG_FORCE_PUT_LOCAL);
} else {
report.log(id, from_local ? LOG_PUT_REMOTE : LOG_PUT_LOCAL);
}
// Drop signature if the destination document was empty // Drop signature if the destination document was empty
// but a signature exists // but a signature exists
if (options.create_new_document === true) { if (options.create_new_document === true) {
...@@ -11632,11 +11957,6 @@ var jIO = window.jIO, ...@@ -11632,11 +11957,6 @@ var jIO = window.jIO,
}); });
} }
return result return result
.push(function () {
if (to_skip) {
skip_document_dict[id] = null;
}
})
.push(undefined, function (error) { .push(undefined, function (error) {
if (error instanceof SkipError) { if (error instanceof SkipError) {
return; return;
...@@ -11645,30 +11965,46 @@ var jIO = window.jIO, ...@@ -11645,30 +11965,46 @@ var jIO = window.jIO,
}); });
} }
function propagateDeletion(context, destination, id, skip_document_dict, function propagateDeletion(context, destination, id,
skip_deleted_document_dict) { skip_deleted_document_dict, report, options) {
// Do not delete a document if it has an attachment // Do not delete a document if it has an attachment
// ie, replication should prevent losing user data // ie, replication should prevent losing user data
// Synchronize attachments before, to ensure // Synchronize attachments before, to ensure
// all of them will be deleted too // all of them will be deleted too
var result; var result;
if (context._signature_hash_key !== undefined) { if (context._signature_hash_key !== undefined) {
if (options.conflict) {
report.log(id, options.from_local ? LOG_FORCE_DELETE_REMOTE :
LOG_FORCE_DELETE_LOCAL);
} else {
report.log(id, options.from_local ? LOG_DELETE_REMOTE :
LOG_DELETE_LOCAL);
}
result = destination.remove(id) result = destination.remove(id)
.push(function () { .push(function () {
return context._signature_sub_storage.remove(id); return context._signature_sub_storage.remove(id);
}); });
} else { } else {
result = repairDocumentAttachment(context, id) result = repairDocumentAttachment(context, id, report)
.push(function () { .push(function () {
return destination.allAttachments(id); return destination.allAttachments(id);
}) })
.push(function (attachment_dict) { .push(function (attachment_dict) {
if (JSON.stringify(attachment_dict) === "{}") { if (JSON.stringify(attachment_dict) === "{}") {
if (options.conflict) {
report.log(id, options.from_local ? LOG_FORCE_DELETE_REMOTE :
LOG_FORCE_DELETE_LOCAL);
} else {
report.log(id, options.from_local ? LOG_DELETE_REMOTE :
LOG_DELETE_LOCAL);
}
return destination.remove(id) return destination.remove(id)
.push(function () { .push(function () {
return context._signature_sub_storage.remove(id); return context._signature_sub_storage.remove(id);
}); });
} }
report.log(id, options.from_local ? LOG_UNEXPECTED_REMOTE_ATTACHMENT :
LOG_UNEXPECTED_LOCAL_ATTACHMENT);
}, function (error) { }, function (error) {
if ((error instanceof jIO.util.jIOError) && if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) { (error.status_code === 404)) {
...@@ -11679,7 +12015,6 @@ var jIO = window.jIO, ...@@ -11679,7 +12015,6 @@ var jIO = window.jIO,
} }
return result return result
.push(function () { .push(function () {
skip_document_dict[id] = null;
// No need to sync attachment twice on this document // No need to sync attachment twice on this document
skip_deleted_document_dict[id] = null; skip_deleted_document_dict[id] = null;
}); });
...@@ -11692,7 +12027,10 @@ var jIO = window.jIO, ...@@ -11692,7 +12027,10 @@ var jIO = window.jIO,
source, destination, id, source, destination, id,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, conflict_ignore,
report,
options) { options) {
// No need to check twice
skip_document_dict[id] = null;
var from_local = options.from_local; var from_local = options.from_local;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -11723,21 +12061,16 @@ var jIO = window.jIO, ...@@ -11723,21 +12061,16 @@ var jIO = window.jIO,
remote_hash = remote_list[1]; remote_hash = remote_list[1];
if (local_hash === remote_hash) { if (local_hash === remote_hash) {
// Same modifications on both side // Same modifications on both side
report.log(id, LOG_FALSE_CONFLICT);
if (local_hash === null) { if (local_hash === null) {
// Deleted on both side, drop signature // Deleted on both side, drop signature
return context._signature_sub_storage.remove(id) return context._signature_sub_storage.remove(id);
.push(function () {
skip_document_dict[id] = null;
});
} }
return context._signature_sub_storage.put(id, { return context._signature_sub_storage.put(id, {
hash: local_hash, hash: local_hash,
from_local: from_local from_local: from_local
}) });
.push(function () {
skip_document_dict[id] = null;
});
} }
if ((remote_hash === status_hash) || (conflict_force === true)) { if ((remote_hash === status_hash) || (conflict_force === true)) {
...@@ -11745,14 +12078,19 @@ var jIO = window.jIO, ...@@ -11745,14 +12078,19 @@ var jIO = window.jIO,
if (local_hash === null) { if (local_hash === null) {
// Deleted locally // Deleted locally
return propagateDeletion(context, destination, id, return propagateDeletion(context, destination, id,
skip_document_dict, skip_deleted_document_dict,
skip_deleted_document_dict); report,
{from_local: from_local,
conflict: (remote_hash !== status_hash)
});
} }
return propagateModification(context, source, destination, doc, return propagateModification(context, source, destination, doc,
local_hash, id, skip_document_dict, local_hash, id, skip_document_dict,
skip_deleted_document_dict, skip_deleted_document_dict,
report,
{use_post: ((options.use_post) && {use_post: ((options.use_post) &&
(remote_hash === null)), (remote_hash === null)),
conflict: (remote_hash !== status_hash),
from_local: from_local, from_local: from_local,
create_new_document: create_new_document:
((remote_hash === null) && ((remote_hash === null) &&
...@@ -11762,6 +12100,7 @@ var jIO = window.jIO, ...@@ -11762,6 +12100,7 @@ var jIO = window.jIO,
// Conflict cases // Conflict cases
if (conflict_ignore === true) { if (conflict_ignore === true) {
report.log(id, LOG_SKIP_CONFLICT);
return; return;
} }
...@@ -11769,8 +12108,11 @@ var jIO = window.jIO, ...@@ -11769,8 +12108,11 @@ var jIO = window.jIO,
// Automatically resolve conflict or force revert // Automatically resolve conflict or force revert
if (remote_hash === null) { if (remote_hash === null) {
// Deleted remotely // Deleted remotely
return propagateDeletion(context, source, id, skip_document_dict, return propagateDeletion(context, source, id,
skip_deleted_document_dict); skip_deleted_document_dict, report,
{from_local: !from_local,
conflict: (local_hash !== null)
});
} }
return propagateModification( return propagateModification(
context, context,
...@@ -11781,9 +12123,11 @@ var jIO = window.jIO, ...@@ -11781,9 +12123,11 @@ var jIO = window.jIO,
id, id,
skip_document_dict, skip_document_dict,
skip_deleted_document_dict, skip_deleted_document_dict,
report,
{use_post: ((options.use_revert_post) && {use_post: ((options.use_revert_post) &&
(local_hash === null)), (local_hash === null)),
from_local: !from_local, from_local: !from_local,
conflict: true,
create_new_document: ((local_hash === null) && create_new_document: ((local_hash === null) &&
(status_hash !== null))} (status_hash !== null))}
); );
...@@ -11795,17 +12139,17 @@ var jIO = window.jIO, ...@@ -11795,17 +12139,17 @@ var jIO = window.jIO,
return propagateModification(context, source, destination, doc, return propagateModification(context, source, destination, doc,
local_hash, id, skip_document_dict, local_hash, id, skip_document_dict,
skip_deleted_document_dict, skip_deleted_document_dict,
report,
{use_post: options.use_post, {use_post: options.use_post,
conflict: true,
from_local: from_local, from_local: from_local,
create_new_document: create_new_document:
(status_hash !== null)}); (status_hash !== null)});
} }
doc = doc || local_hash; report.log(id, LOG_UNRESOLVED_CONFLICT);
remote_doc = remote_doc || remote_hash; })
throw new jIO.util.jIOError("Conflict on '" + id + "': " + .push(undefined, function (error) {
stringify(doc) + " !== " + report.log(id, LOG_UNEXPECTED_ERROR, error);
stringify(remote_doc),
409);
}); });
} }
...@@ -11814,7 +12158,7 @@ var jIO = window.jIO, ...@@ -11814,7 +12158,7 @@ var jIO = window.jIO,
cache, destination_key, cache, destination_key,
destination, id, source, destination, id, source,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, options) { conflict_ignore, report, options) {
var status_hash; var status_hash;
queue queue
.push(function () { .push(function () {
...@@ -11828,7 +12172,7 @@ var jIO = window.jIO, ...@@ -11828,7 +12172,7 @@ var jIO = window.jIO,
status_hash, null, null, status_hash, null, null,
source, destination, id, source, destination, id,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, conflict_ignore, report,
options); options);
}); });
} }
...@@ -11839,7 +12183,7 @@ var jIO = window.jIO, ...@@ -11839,7 +12183,7 @@ var jIO = window.jIO,
source, destination, id, source, destination, id,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, conflict_ignore,
local_hash, status_hash, local_hash, status_hash, report,
options) { options) {
queue queue
.push(function () { .push(function () {
...@@ -11863,15 +12207,20 @@ var jIO = window.jIO, ...@@ -11863,15 +12207,20 @@ var jIO = window.jIO,
source, destination, id, source, destination, id,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, conflict_ignore,
report,
options); options);
} }
if (!options.from_local) {
report.log(id, LOG_NO_CHANGE);
}
}); });
} }
function pushStorage(context, skip_document_dict, function pushStorage(context, skip_document_dict,
skip_deleted_document_dict, skip_deleted_document_dict,
cache, source_key, destination_key, cache, source_key, destination_key,
source, destination, signature_allDocs, options) { source, destination, signature_allDocs,
report, options) {
var argument_list = [], var argument_list = [],
argument_list_deletion = []; argument_list_deletion = [];
if (!options.hasOwnProperty("use_post")) { if (!options.hasOwnProperty("use_post")) {
...@@ -11952,7 +12301,19 @@ var jIO = window.jIO, ...@@ -11952,7 +12301,19 @@ var jIO = window.jIO,
options.conflict_revert, options.conflict_revert,
options.conflict_ignore, options.conflict_ignore,
local_hash, status_hash, local_hash, status_hash,
report,
options]); options]);
} else if (local_hash === status_hash) {
report.log(key, LOG_NO_CHANGE);
} else {
if (signature_dict.hasOwnProperty(key)) {
report.log(key, options.from_local ?
LOG_SKIP_LOCAL_MODIFICATION :
LOG_SKIP_REMOTE_MODIFICATION);
} else {
report.log(key, options.from_local ? LOG_SKIP_LOCAL_CREATION :
LOG_SKIP_REMOTE_CREATION);
}
} }
} }
} }
...@@ -11979,8 +12340,11 @@ var jIO = window.jIO, ...@@ -11979,8 +12340,11 @@ var jIO = window.jIO,
options.conflict_force, options.conflict_force,
options.conflict_revert, options.conflict_revert,
options.conflict_ignore, options.conflict_ignore,
report,
options]); options]);
} else { } else {
report.log(key, options.from_local ? LOG_SKIP_LOCAL_DELETION :
LOG_SKIP_REMOTE_DELETION);
skip_deleted_document_dict[key] = null; skip_deleted_document_dict[key] = null;
} }
} }
...@@ -12000,11 +12364,11 @@ var jIO = window.jIO, ...@@ -12000,11 +12364,11 @@ var jIO = window.jIO,
}); });
} }
function repairDocument(queue, context, id, signature_hash_key, function repairDocument(queue, context, id, report, signature_hash_key,
signature_hash, signature_attachment_hash, signature_hash, signature_attachment_hash,
signature_from_local) { signature_from_local) {
queue.push(function () { queue.push(function () {
return repairDocumentAttachment(context, id, signature_hash_key, return repairDocumentAttachment(context, id, report, signature_hash_key,
signature_hash, signature_hash,
signature_attachment_hash, signature_attachment_hash,
signature_from_local); signature_from_local);
...@@ -12016,7 +12380,8 @@ var jIO = window.jIO, ...@@ -12016,7 +12380,8 @@ var jIO = window.jIO,
argument_list = arguments, argument_list = arguments,
skip_document_dict = {}, skip_document_dict = {},
skip_deleted_document_dict = {}, skip_deleted_document_dict = {},
cache = {}; cache = {},
report = new ReplicateReport(this._log_level, this._log_console);
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -12083,7 +12448,7 @@ var jIO = window.jIO, ...@@ -12083,7 +12448,7 @@ var jIO = window.jIO,
cache, 'local', 'remote', cache, 'local', 'remote',
context._local_sub_storage, context._local_sub_storage,
context._remote_sub_storage, context._remote_sub_storage,
signature_allDocs, signature_allDocs, report,
{ {
use_post: context._use_remote_post, use_post: context._use_remote_post,
conflict_force: (context._conflict_handling === conflict_force: (context._conflict_handling ===
...@@ -12114,7 +12479,8 @@ var jIO = window.jIO, ...@@ -12114,7 +12479,8 @@ var jIO = window.jIO,
cache, 'remote', 'local', cache, 'remote', 'local',
context._remote_sub_storage, context._remote_sub_storage,
context._local_sub_storage, context._local_sub_storage,
signature_allDocs, { signature_allDocs,
report, {
use_revert_post: context._use_remote_post, use_revert_post: context._use_remote_post,
conflict_force: (context._conflict_handling === conflict_force: (context._conflict_handling ===
CONFLICT_KEEP_REMOTE), CONFLICT_KEEP_REMOTE),
...@@ -12155,9 +12521,10 @@ var jIO = window.jIO, ...@@ -12155,9 +12521,10 @@ var jIO = window.jIO,
// is deleted but not pushed to the other storage // is deleted but not pushed to the other storage
if (!skip_deleted_document_dict.hasOwnProperty(row.id)) { if (!skip_deleted_document_dict.hasOwnProperty(row.id)) {
local_argument_list.push( local_argument_list.push(
[undefined, context, row.id, context._signature_hash_key, [undefined, context, row.id, report,
context._signature_hash_key,
row.value.hash, row.value.attachment_hash, row.value.hash, row.value.attachment_hash,
row.value.from_local] row.value.from_local, report]
); );
} }
} }
...@@ -12169,6 +12536,12 @@ var jIO = window.jIO, ...@@ -12169,6 +12536,12 @@ var jIO = window.jIO,
); );
}); });
} }
})
.push(function () {
if (report.has_error) {
throw report;
}
return report;
}); });
}; };
......
...@@ -8171,10 +8171,9 @@ return new Parser; ...@@ -8171,10 +8171,9 @@ return new Parser;
function readBlobAsText(blob, encoding) { function readBlobAsText(blob, encoding) {
var fr = new FileReader(); var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) { return new RSVP.Promise(function (resolve, reject) {
fr.addEventListener("load", resolve); fr.addEventListener("load", resolve);
fr.addEventListener("error", reject); fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsText(blob, encoding); fr.readAsText(blob, encoding);
}, function () { }, function () {
fr.abort(); fr.abort();
...@@ -8184,10 +8183,9 @@ return new Parser; ...@@ -8184,10 +8183,9 @@ return new Parser;
function readBlobAsArrayBuffer(blob) { function readBlobAsArrayBuffer(blob) {
var fr = new FileReader(); var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) { return new RSVP.Promise(function (resolve, reject) {
fr.addEventListener("load", resolve); fr.addEventListener("load", resolve);
fr.addEventListener("error", reject); fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsArrayBuffer(blob); fr.readAsArrayBuffer(blob);
}, function () { }, function () {
fr.abort(); fr.abort();
...@@ -8197,10 +8195,9 @@ return new Parser; ...@@ -8197,10 +8195,9 @@ return new Parser;
function readBlobAsDataURL(blob) { function readBlobAsDataURL(blob) {
var fr = new FileReader(); var fr = new FileReader();
return new RSVP.Promise(function (resolve, reject, notify) { return new RSVP.Promise(function (resolve, reject) {
fr.addEventListener("load", resolve); fr.addEventListener("load", resolve);
fr.addEventListener("error", reject); fr.addEventListener("error", reject);
fr.addEventListener("progress", notify);
fr.readAsDataURL(blob); fr.readAsDataURL(blob);
}, function () { }, function () {
fr.abort(); fr.abort();
...@@ -9074,7 +9071,218 @@ return new Parser; ...@@ -9074,7 +9071,218 @@ return new Parser;
CONFLICT_THROW = 0, CONFLICT_THROW = 0,
CONFLICT_KEEP_LOCAL = 1, CONFLICT_KEEP_LOCAL = 1,
CONFLICT_KEEP_REMOTE = 2, CONFLICT_KEEP_REMOTE = 2,
CONFLICT_CONTINUE = 3; CONFLICT_CONTINUE = 3,
// 0 - 99 error
LOG_UNEXPECTED_ERROR = 0,
LOG_UNRESOLVED_CONFLICT = 74,
LOG_UNEXPECTED_LOCAL_ATTACHMENT = 49,
LOG_UNEXPECTED_REMOTE_ATTACHMENT = 47,
LOG_UNRESOLVED_ATTACHMENT_CONFLICT = 75,
// 100 - 199 solving conflict
LOG_FORCE_PUT_REMOTE = 116,
LOG_FORCE_DELETE_REMOTE = 136,
LOG_FORCE_PUT_REMOTE_ATTACHMENT = 117,
LOG_FORCE_DELETE_REMOTE_ATTACHMENT = 137,
LOG_FORCE_PUT_LOCAL = 118,
LOG_FORCE_DELETE_LOCAL = 138,
LOG_FORCE_PUT_LOCAL_ATTACHMENT = 119,
LOG_FORCE_DELETE_LOCAL_ATTACHMENT = 139,
// 200 - 299 pushing change
LOG_PUT_REMOTE = 216,
LOG_POST_REMOTE = 226,
LOG_DELETE_REMOTE = 236,
LOG_PUT_REMOTE_ATTACHMENT = 217,
LOG_DELETE_REMOTE_ATTACHMENT = 237,
LOG_PUT_LOCAL = 218,
LOG_POST_LOCAL = 228,
LOG_DELETE_LOCAL = 238,
LOG_PUT_LOCAL_ATTACHMENT = 219,
LOG_DELETE_LOCAL_ATTACHMENT = 239,
LOG_FALSE_CONFLICT = 284,
LOG_FALSE_CONFLICT_ATTACHMENT = 285,
// 300 - 399 nothing to do
LOG_SKIP_LOCAL_CREATION = 348,
LOG_SKIP_LOCAL_MODIFICATION = 358,
LOG_SKIP_LOCAL_DELETION = 368,
LOG_SKIP_REMOTE_CREATION = 346,
LOG_SKIP_REMOTE_MODIFICATION = 356,
LOG_SKIP_REMOTE_DELETION = 366,
LOG_SKIP_LOCAL_ATTACHMENT_CREATION = 349,
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION = 359,
LOG_SKIP_LOCAL_ATTACHMENT_DELETION = 369,
LOG_SKIP_REMOTE_ATTACHMENT_CREATION = 347,
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION = 357,
LOG_SKIP_REMOTE_ATTACHMENT_DELETION = 367,
LOG_SKIP_CONFLICT = 374,
LOG_SKIP_CONFLICT_ATTACHMENT = 375,
LOG_NO_CHANGE = 384,
LOG_NO_CHANGE_ATTACHMENT = 385;
function ReplicateReport(log_level, log_console) {
this._list = [];
this.name = 'ReplicateReport';
this.message = this.name;
this.has_error = false;
this._log_level = log_level;
this._log_console = log_console;
}
ReplicateReport.prototype = {
constructor: ReplicateReport,
LOG_UNEXPECTED_ERROR: LOG_UNEXPECTED_ERROR,
LOG_UNRESOLVED_CONFLICT: LOG_UNRESOLVED_CONFLICT,
LOG_UNEXPECTED_LOCAL_ATTACHMENT: LOG_UNEXPECTED_LOCAL_ATTACHMENT,
LOG_UNEXPECTED_REMOTE_ATTACHMENT: LOG_UNEXPECTED_REMOTE_ATTACHMENT,
LOG_UNRESOLVED_ATTACHMENT_CONFLICT: LOG_UNRESOLVED_ATTACHMENT_CONFLICT,
LOG_FORCE_PUT_REMOTE: LOG_FORCE_PUT_REMOTE,
LOG_FORCE_DELETE_REMOTE: LOG_FORCE_DELETE_REMOTE,
LOG_FORCE_PUT_LOCAL: LOG_FORCE_PUT_LOCAL,
LOG_FORCE_DELETE_LOCAL: LOG_FORCE_DELETE_LOCAL,
LOG_FORCE_PUT_REMOTE_ATTACHMENT: LOG_FORCE_PUT_REMOTE_ATTACHMENT,
LOG_FORCE_DELETE_REMOTE_ATTACHMENT: LOG_FORCE_DELETE_REMOTE_ATTACHMENT,
LOG_FORCE_PUT_LOCAL_ATTACHMENT: LOG_FORCE_PUT_LOCAL_ATTACHMENT,
LOG_FORCE_DELETE_LOCAL_ATTACHMENT: LOG_FORCE_DELETE_LOCAL_ATTACHMENT,
LOG_PUT_REMOTE: LOG_PUT_REMOTE,
LOG_POST_REMOTE: LOG_POST_REMOTE,
LOG_DELETE_REMOTE: LOG_DELETE_REMOTE,
LOG_PUT_REMOTE_ATTACHMENT: LOG_PUT_REMOTE_ATTACHMENT,
LOG_DELETE_REMOTE_ATTACHMENT: LOG_DELETE_REMOTE_ATTACHMENT,
LOG_PUT_LOCAL: LOG_PUT_LOCAL,
LOG_DELETE_LOCAL: LOG_DELETE_LOCAL,
LOG_PUT_LOCAL_ATTACHMENT: LOG_PUT_LOCAL_ATTACHMENT,
LOG_DELETE_LOCAL_ATTACHMENT: LOG_DELETE_LOCAL_ATTACHMENT,
LOG_FALSE_CONFLICT: LOG_FALSE_CONFLICT,
LOG_FALSE_CONFLICT_ATTACHMENT: LOG_FALSE_CONFLICT_ATTACHMENT,
LOG_SKIP_LOCAL_CREATION: LOG_SKIP_LOCAL_CREATION,
LOG_SKIP_LOCAL_MODIFICATION: LOG_SKIP_LOCAL_MODIFICATION,
LOG_SKIP_LOCAL_DELETION: LOG_SKIP_LOCAL_DELETION,
LOG_SKIP_REMOTE_CREATION: LOG_SKIP_REMOTE_CREATION,
LOG_SKIP_REMOTE_MODIFICATION: LOG_SKIP_REMOTE_MODIFICATION,
LOG_SKIP_REMOTE_DELETION: LOG_SKIP_REMOTE_DELETION,
LOG_SKIP_LOCAL_ATTACHMENT_CREATION: LOG_SKIP_LOCAL_ATTACHMENT_CREATION,
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION:
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION,
LOG_SKIP_LOCAL_ATTACHMENT_DELETION: LOG_SKIP_LOCAL_ATTACHMENT_DELETION,
LOG_SKIP_REMOTE_ATTACHMENT_CREATION: LOG_SKIP_REMOTE_ATTACHMENT_CREATION,
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION:
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION,
LOG_SKIP_REMOTE_ATTACHMENT_DELETION: LOG_SKIP_REMOTE_ATTACHMENT_DELETION,
LOG_SKIP_CONFLICT: LOG_SKIP_CONFLICT,
LOG_SKIP_CONFLICT_ATTACHMENT: LOG_SKIP_CONFLICT_ATTACHMENT,
LOG_NO_CHANGE: LOG_NO_CHANGE,
LOG_NO_CHANGE_ATTACHMENT: LOG_NO_CHANGE_ATTACHMENT,
logConsole: function (code, a, b, c) {
if (!this._log_console) {
return;
}
var txt = code,
parsed_code = code,
log;
// Check severity level
if (parsed_code >= 300) {
txt += ' SKIP ';
log = console.info;
} else if (parsed_code >= 200) {
txt += ' SOLVE ';
log = console.log;
} else if (parsed_code >= 100) {
txt += ' FORCE ';
log = console.warn;
} else {
txt += ' ERROR ';
log = console.error;
}
// Check operation
parsed_code = code % 100;
if (parsed_code >= 80) {
txt += 'idem ';
} else if (parsed_code >= 70) {
txt += 'conflict ';
} else if (parsed_code >= 60) {
txt += 'deleted ';
} else if (parsed_code >= 50) {
txt += 'modified ';
} else if (parsed_code >= 40) {
txt += 'created ';
} else if (parsed_code >= 30) {
txt += 'delete ';
} else if (parsed_code >= 20) {
txt += 'post ';
} else if (parsed_code >= 10) {
txt += 'put ';
}
// Check document
parsed_code = code % 10;
if (parsed_code >= 8) {
txt += 'local ';
} else if (parsed_code >= 6) {
txt += 'remote ';
}
if (parsed_code !== 0) {
txt += (parsed_code % 2 === 0) ? 'document' : 'attachment';
}
txt += ' ' + a;
if (b !== undefined) {
txt += ' ' + b;
if (c !== undefined) {
txt += ' ' + c;
}
}
log(txt);
},
log: function (id, type, extra) {
if (type === undefined) {
if (extra === undefined) {
extra = 'Unknown type: ' + type;
}
type = LOG_UNEXPECTED_ERROR;
}
if (type < this._log_level) {
if (extra === undefined) {
this.logConsole(type, id);
this._list.push([type, id]);
} else {
this.logConsole(type, id, extra);
this._list.push([type, id, extra]);
}
if (type < 100) {
this.has_error = true;
}
}
},
logAttachment: function (id, name, type, extra) {
if (type === undefined) {
if (extra === undefined) {
extra = 'Unknown type: ' + type;
}
type = LOG_UNEXPECTED_ERROR;
}
if (type < this._log_level) {
if (extra === undefined) {
this.logConsole(type, id, name);
this._list.push([type, id, name]);
} else {
this.logConsole(type, id, name, extra);
this._list.push([type, id, name, extra]);
}
if (type < 100) {
this.has_error = true;
}
}
},
toString: function () {
return this._list.toString();
}
};
function SkipError(message) { function SkipError(message) {
if ((message !== undefined) && (typeof message !== "string")) { if ((message !== undefined) && (typeof message !== "string")) {
...@@ -9103,6 +9311,8 @@ return new Parser; ...@@ -9103,6 +9311,8 @@ return new Parser;
function ReplicateStorage(spec) { function ReplicateStorage(spec) {
this._query_options = spec.query || {}; this._query_options = spec.query || {};
this._log_level = spec.report_level || 100;
this._log_console = spec.debug || false;
if (spec.signature_hash_key !== undefined) { if (spec.signature_hash_key !== undefined) {
this._query_options.select_list = [spec.signature_hash_key]; this._query_options.select_list = [spec.signature_hash_key];
} }
...@@ -9320,30 +9530,42 @@ return new Parser; ...@@ -9320,30 +9530,42 @@ return new Parser;
}); });
} }
function propagateAttachmentDeletion(context, skip_attachment_dict, function propagateAttachmentDeletion(context,
destination, destination,
id, name) { id, name,
conflict, from_local, report) {
if (conflict) {
report.logAttachment(id, name, from_local ?
LOG_FORCE_DELETE_REMOTE_ATTACHMENT :
LOG_FORCE_DELETE_LOCAL_ATTACHMENT);
} else {
report.logAttachment(id, name, from_local ? LOG_DELETE_REMOTE_ATTACHMENT :
LOG_DELETE_LOCAL_ATTACHMENT);
}
return destination.removeAttachment(id, name) return destination.removeAttachment(id, name)
.push(function () { .push(function () {
return context._signature_sub_storage.removeAttachment(id, name); return context._signature_sub_storage.removeAttachment(id, name);
})
.push(function () {
skip_attachment_dict[name] = null;
}); });
} }
function propagateAttachmentModification(context, skip_attachment_dict, function propagateAttachmentModification(context,
destination, destination,
blob, hash, id, name) { blob, hash, id, name,
from_local, is_conflict, report) {
if (is_conflict) {
report.logAttachment(id, name, from_local ?
LOG_FORCE_PUT_REMOTE_ATTACHMENT :
LOG_FORCE_PUT_LOCAL_ATTACHMENT);
} else {
report.logAttachment(id, name, from_local ? LOG_PUT_REMOTE_ATTACHMENT :
LOG_PUT_LOCAL_ATTACHMENT);
}
return destination.putAttachment(id, name, blob) return destination.putAttachment(id, name, blob)
.push(function () { .push(function () {
return context._signature_sub_storage.putAttachment(id, name, return context._signature_sub_storage.putAttachment(id, name,
JSON.stringify({ JSON.stringify({
hash: hash hash: hash
})); }));
})
.push(function () {
skip_attachment_dict[name] = null;
}); });
} }
...@@ -9352,7 +9574,9 @@ return new Parser; ...@@ -9352,7 +9574,9 @@ return new Parser;
status_hash, local_hash, blob, status_hash, local_hash, blob,
source, destination, id, name, source, destination, id, name,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore) { conflict_ignore, from_local, report) {
// No need to check twice
skip_attachment_dict[name] = null;
var remote_blob; var remote_blob;
return destination.getAttachment(id, name) return destination.getAttachment(id, name)
.push(function (result) { .push(function (result) {
...@@ -9374,39 +9598,39 @@ return new Parser; ...@@ -9374,39 +9598,39 @@ return new Parser;
.push(function (remote_hash) { .push(function (remote_hash) {
if (local_hash === remote_hash) { if (local_hash === remote_hash) {
// Same modifications on both side // Same modifications on both side
report.logAttachment(id, name, LOG_FALSE_CONFLICT_ATTACHMENT);
if (local_hash === null) { if (local_hash === null) {
// Deleted on both side, drop signature // Deleted on both side, drop signature
return context._signature_sub_storage.removeAttachment(id, name) return context._signature_sub_storage.removeAttachment(id, name);
.push(function () {
skip_attachment_dict[name] = null;
});
} }
return context._signature_sub_storage.putAttachment(id, name, return context._signature_sub_storage.putAttachment(id, name,
JSON.stringify({ JSON.stringify({
hash: local_hash hash: local_hash
})) }));
.push(function () {
skip_attachment_dict[name] = null;
});
} }
if ((remote_hash === status_hash) || (conflict_force === true)) { if ((remote_hash === status_hash) || (conflict_force === true)) {
// Modified only locally. No conflict or force // Modified only locally. No conflict or force
if (local_hash === null) { if (local_hash === null) {
// Deleted locally // Deleted locally
return propagateAttachmentDeletion(context, skip_attachment_dict, return propagateAttachmentDeletion(context,
destination, destination,
id, name); id, name,
(remote_hash !== status_hash),
from_local, report);
} }
return propagateAttachmentModification(context, return propagateAttachmentModification(context,
skip_attachment_dict,
destination, blob, destination, blob,
local_hash, id, name); local_hash, id, name,
from_local,
(remote_hash !== status_hash),
report);
} }
// Conflict cases // Conflict cases
if (conflict_ignore === true) { if (conflict_ignore === true) {
report.logAttachment(id, name, LOG_SKIP_CONFLICT_ATTACHMENT);
return; return;
} }
...@@ -9414,17 +9638,21 @@ return new Parser; ...@@ -9414,17 +9638,21 @@ return new Parser;
// Automatically resolve conflict or force revert // Automatically resolve conflict or force revert
if (remote_hash === null) { if (remote_hash === null) {
// Deleted remotely // Deleted remotely
return propagateAttachmentDeletion(context, skip_attachment_dict, return propagateAttachmentDeletion(context,
source, id, name); source, id, name,
(local_hash !== status_hash),
!from_local, report);
} }
return propagateAttachmentModification( return propagateAttachmentModification(
context, context,
skip_attachment_dict,
source, source,
remote_blob, remote_blob,
remote_hash, remote_hash,
id, id,
name name,
!from_local,
(local_hash !== status_hash),
report
); );
} }
...@@ -9432,14 +9660,15 @@ return new Parser; ...@@ -9432,14 +9660,15 @@ return new Parser;
if (remote_hash === null) { if (remote_hash === null) {
// Copy remote modification remotely // Copy remote modification remotely
return propagateAttachmentModification(context, return propagateAttachmentModification(context,
skip_attachment_dict,
destination, blob, destination, blob,
local_hash, id, name); local_hash, id, name, from_local,
false,
report);
} }
throw new jIO.util.jIOError("Conflict on '" + id + report.logAttachment(id, name, LOG_UNRESOLVED_ATTACHMENT_CONFLICT);
"' with attachment '" + })
name + "'", .push(undefined, function (error) {
409); report.logAttachment(id, name, LOG_UNEXPECTED_ERROR, error);
}); });
} }
...@@ -9450,7 +9679,9 @@ return new Parser; ...@@ -9450,7 +9679,9 @@ return new Parser;
conflict_force, conflict_force,
conflict_revert, conflict_revert,
conflict_ignore, conflict_ignore,
is_creation, is_modification) { is_creation, is_modification,
from_local,
report) {
var blob, var blob,
status_hash; status_hash;
queue queue
...@@ -9485,14 +9716,20 @@ return new Parser; ...@@ -9485,14 +9716,20 @@ return new Parser;
var array_buffer = evt.target.result, var array_buffer = evt.target.result,
local_hash = generateHashFromArrayBuffer(array_buffer); local_hash = generateHashFromArrayBuffer(array_buffer);
if (local_hash !== status_hash) { if (local_hash === status_hash) {
return checkAndPropagateAttachment(context, if (!from_local) {
skip_attachment_dict, report.logAttachment(id, name, LOG_NO_CHANGE_ATTACHMENT);
status_hash, local_hash, blob, }
source, destination, id, name, return;
conflict_force, conflict_revert,
conflict_ignore);
} }
return checkAndPropagateAttachment(context,
skip_attachment_dict,
status_hash, local_hash, blob,
source, destination, id, name,
conflict_force, conflict_revert,
conflict_ignore,
from_local,
report);
}); });
} }
...@@ -9500,7 +9737,7 @@ return new Parser; ...@@ -9500,7 +9737,7 @@ return new Parser;
skip_attachment_dict, skip_attachment_dict,
destination, id, name, source, destination, id, name, source,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore) { conflict_ignore, from_local, report) {
var status_hash; var status_hash;
queue queue
.push(function () { .push(function () {
...@@ -9514,16 +9751,17 @@ return new Parser; ...@@ -9514,16 +9751,17 @@ return new Parser;
status_hash, null, null, status_hash, null, null,
source, destination, id, name, source, destination, id, name,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore); conflict_ignore, from_local, report);
}); });
} }
function pushDocumentAttachment(context, function pushDocumentAttachment(context,
skip_attachment_dict, id, source, skip_attachment_dict, id, source,
destination, signature_allAttachments, destination, signature_allAttachments,
options) { report, options) {
var local_dict = {}, var local_dict = {},
signature_dict = {}; signature_dict = {},
from_local = options.from_local;
return source.allAttachments(id) return source.allAttachments(id)
.push(undefined, function (error) { .push(undefined, function (error) {
if ((error instanceof jIO.util.jIOError) && if ((error instanceof jIO.util.jIOError) &&
...@@ -9568,7 +9806,19 @@ return new Parser; ...@@ -9568,7 +9806,19 @@ return new Parser;
options.conflict_revert, options.conflict_revert,
options.conflict_ignore, options.conflict_ignore,
is_creation, is_creation,
is_modification]); is_modification,
from_local,
report]);
} else {
if (signature_dict.hasOwnProperty(key)) {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION :
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_CREATION :
LOG_SKIP_REMOTE_ATTACHMENT_CREATION);
}
} }
} }
} }
...@@ -9581,10 +9831,10 @@ return new Parser; ...@@ -9581,10 +9831,10 @@ return new Parser;
}) })
.push(function () { .push(function () {
var key, argument_list = []; var key, argument_list = [];
if (options.check_deletion === true) { for (key in signature_dict) {
for (key in signature_dict) { if (signature_dict.hasOwnProperty(key)) {
if (signature_dict.hasOwnProperty(key)) { if (!local_dict.hasOwnProperty(key)) {
if (!local_dict.hasOwnProperty(key)) { if (options.check_deletion === true) {
argument_list.push([undefined, argument_list.push([undefined,
context, context,
skip_attachment_dict, skip_attachment_dict,
...@@ -9592,29 +9842,51 @@ return new Parser; ...@@ -9592,29 +9842,51 @@ return new Parser;
source, source,
options.conflict_force, options.conflict_force,
options.conflict_revert, options.conflict_revert,
options.conflict_ignore]); options.conflict_ignore,
from_local,
report]);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_DELETION :
LOG_SKIP_REMOTE_ATTACHMENT_DELETION);
} }
} }
} }
return dispatchQueue(
context,
checkAttachmentLocalDeletion,
argument_list,
context._parallel_operation_attachment_amount
);
} }
return dispatchQueue(
context,
checkAttachmentLocalDeletion,
argument_list,
context._parallel_operation_attachment_amount
);
}); });
} }
function propagateFastAttachmentDeletion(queue, id, name, storage) { function propagateFastAttachmentDeletion(queue, id, name, storage, signature,
from_local, report) {
report.logAttachment(id, name, from_local ? LOG_DELETE_REMOTE_ATTACHMENT :
LOG_DELETE_LOCAL_ATTACHMENT);
return queue return queue
.push(function () { .push(function () {
return storage.removeAttachment(id, name); return storage.removeAttachment(id, name);
})
.push(function () {
return signature.removeAttachment(id, name);
});
}
function propagateFastSignatureDeletion(queue, id, name, signature,
report) {
report.logAttachment(id, name, LOG_FALSE_CONFLICT_ATTACHMENT);
return queue
.push(function () {
return signature.removeAttachment(id, name);
}); });
} }
function propagateFastAttachmentModification(queue, id, key, source, function propagateFastAttachmentModification(queue, id, key, source,
destination, signature, hash) { destination, signature, hash,
from_local, report) {
return queue return queue
.push(function () { .push(function () {
return signature.getAttachment(id, key, {format: 'json'}) return signature.getAttachment(id, key, {format: 'json'})
...@@ -9627,6 +9899,9 @@ return new Parser; ...@@ -9627,6 +9899,9 @@ return new Parser;
}) })
.push(function (result) { .push(function (result) {
if (result.hash !== hash) { if (result.hash !== hash) {
report.logAttachment(id, key, from_local ?
LOG_PUT_REMOTE_ATTACHMENT :
LOG_PUT_LOCAL_ATTACHMENT);
return source.getAttachment(id, key) return source.getAttachment(id, key)
.push(function (blob) { .push(function (blob) {
return destination.putAttachment(id, key, blob); return destination.putAttachment(id, key, blob);
...@@ -9645,7 +9920,8 @@ return new Parser; ...@@ -9645,7 +9920,8 @@ return new Parser;
function repairFastDocumentAttachment(context, id, function repairFastDocumentAttachment(context, id,
signature_hash, signature_hash,
signature_attachment_hash, signature_attachment_hash,
signature_from_local) { signature_from_local,
report) {
if (signature_hash === signature_attachment_hash) { if (signature_hash === signature_attachment_hash) {
// No replication to do // No replication to do
return; return;
...@@ -9666,6 +9942,7 @@ return new Parser; ...@@ -9666,6 +9942,7 @@ return new Parser;
destination, destination,
push_argument_list = [], push_argument_list = [],
delete_argument_list = [], delete_argument_list = [],
delete_signature_argument_list = [],
signature_attachment_dict = result_list[0], signature_attachment_dict = result_list[0],
local_attachment_dict = result_list[1], local_attachment_dict = result_list[1],
remote_attachment_list = result_list[2], remote_attachment_list = result_list[2],
...@@ -9676,13 +9953,15 @@ return new Parser; ...@@ -9676,13 +9953,15 @@ return new Parser;
check_remote_modification = check_remote_modification =
context._check_remote_attachment_modification, context._check_remote_attachment_modification,
check_remote_creation = context._check_remote_attachment_creation, check_remote_creation = context._check_remote_attachment_creation,
check_remote_deletion = context._check_remote_attachment_deletion; check_remote_deletion = context._check_remote_attachment_deletion,
from_local;
if (signature_from_local) { if (signature_from_local) {
source_attachment_dict = local_attachment_dict; source_attachment_dict = local_attachment_dict;
destination_attachment_dict = remote_attachment_list; destination_attachment_dict = remote_attachment_list;
source = context._local_sub_storage; source = context._local_sub_storage;
destination = context._remote_sub_storage; destination = context._remote_sub_storage;
from_local = true;
} else { } else {
source_attachment_dict = remote_attachment_list; source_attachment_dict = remote_attachment_list;
destination_attachment_dict = local_attachment_dict; destination_attachment_dict = local_attachment_dict;
...@@ -9693,6 +9972,7 @@ return new Parser; ...@@ -9693,6 +9972,7 @@ return new Parser;
check_local_deletion = check_remote_deletion; check_local_deletion = check_remote_deletion;
check_remote_creation = check_local_creation; check_remote_creation = check_local_creation;
check_remote_deletion = check_local_deletion; check_remote_deletion = check_local_deletion;
from_local = false;
} }
// Push all source attachments // Push all source attachments
...@@ -9710,8 +9990,20 @@ return new Parser; ...@@ -9710,8 +9990,20 @@ return new Parser;
source, source,
destination, destination,
context._signature_sub_storage, context._signature_sub_storage,
signature_hash signature_hash,
from_local,
report
]); ]);
} else {
if (signature_attachment_dict.hasOwnProperty(key)) {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION :
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_CREATION :
LOG_SKIP_REMOTE_ATTACHMENT_CREATION);
}
} }
} }
} }
...@@ -9720,16 +10012,19 @@ return new Parser; ...@@ -9720,16 +10012,19 @@ return new Parser;
for (key in signature_attachment_dict) { for (key in signature_attachment_dict) {
if (signature_attachment_dict.hasOwnProperty(key)) { if (signature_attachment_dict.hasOwnProperty(key)) {
if (check_local_deletion && if (check_local_deletion &&
!source_attachment_dict.hasOwnProperty(key)) { !source_attachment_dict.hasOwnProperty(key) &&
delete_argument_list.push([ !destination_attachment_dict.hasOwnProperty(key)) {
delete_signature_argument_list.push([
undefined, undefined,
id, id,
key, key,
context._signature_sub_storage context._signature_sub_storage,
report
]); ]);
} }
} }
} }
for (key in destination_attachment_dict) { for (key in destination_attachment_dict) {
if (destination_attachment_dict.hasOwnProperty(key)) { if (destination_attachment_dict.hasOwnProperty(key)) {
if (!source_attachment_dict.hasOwnProperty(key)) { if (!source_attachment_dict.hasOwnProperty(key)) {
...@@ -9741,8 +10036,21 @@ return new Parser; ...@@ -9741,8 +10036,21 @@ return new Parser;
undefined, undefined,
id, id,
key, key,
destination destination,
context._signature_sub_storage,
from_local,
report
]); ]);
} else {
if (signature_attachment_dict.hasOwnProperty(key)) {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_DELETION :
LOG_SKIP_REMOTE_ATTACHMENT_DELETION);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_CREATION :
LOG_SKIP_REMOTE_ATTACHMENT_CREATION);
}
} }
} }
} }
...@@ -9760,6 +10068,12 @@ return new Parser; ...@@ -9760,6 +10068,12 @@ return new Parser;
propagateFastAttachmentDeletion, propagateFastAttachmentDeletion,
delete_argument_list, delete_argument_list,
context._parallel_operation_attachment_amount context._parallel_operation_attachment_amount
),
dispatchQueue(
context,
propagateFastSignatureDeletion,
delete_signature_argument_list,
context._parallel_operation_attachment_amount
) )
]); ]);
}) })
...@@ -9773,7 +10087,7 @@ return new Parser; ...@@ -9773,7 +10087,7 @@ return new Parser;
}); });
} }
function repairDocumentAttachment(context, id, signature_hash_key, function repairDocumentAttachment(context, id, report, signature_hash_key,
signature_hash, signature_hash,
signature_attachment_hash, signature_attachment_hash,
signature_from_local) { signature_from_local) {
...@@ -9781,7 +10095,7 @@ return new Parser; ...@@ -9781,7 +10095,7 @@ return new Parser;
return repairFastDocumentAttachment(context, id, return repairFastDocumentAttachment(context, id,
signature_hash, signature_hash,
signature_attachment_hash, signature_attachment_hash,
signature_from_local); signature_from_local, report);
} }
var skip_attachment_dict = {}; var skip_attachment_dict = {};
...@@ -9815,6 +10129,7 @@ return new Parser; ...@@ -9815,6 +10129,7 @@ return new Parser;
context._local_sub_storage, context._local_sub_storage,
context._remote_sub_storage, context._remote_sub_storage,
signature_allAttachments, signature_allAttachments,
report,
{ {
conflict_force: (context._conflict_handling === conflict_force: (context._conflict_handling ===
CONFLICT_KEEP_LOCAL), CONFLICT_KEEP_LOCAL),
...@@ -9825,7 +10140,8 @@ return new Parser; ...@@ -9825,7 +10140,8 @@ return new Parser;
check_modification: check_modification:
context._check_local_attachment_modification, context._check_local_attachment_modification,
check_creation: context._check_local_attachment_creation, check_creation: context._check_local_attachment_creation,
check_deletion: context._check_local_attachment_deletion check_deletion: context._check_local_attachment_deletion,
from_local: true
} }
) )
.push(function () { .push(function () {
...@@ -9845,6 +10161,7 @@ return new Parser; ...@@ -9845,6 +10161,7 @@ return new Parser;
context._remote_sub_storage, context._remote_sub_storage,
context._local_sub_storage, context._local_sub_storage,
signature_allAttachments, signature_allAttachments,
report,
{ {
use_revert_post: context._use_remote_post, use_revert_post: context._use_remote_post,
conflict_force: (context._conflict_handling === conflict_force: (context._conflict_handling ===
...@@ -9856,7 +10173,8 @@ return new Parser; ...@@ -9856,7 +10173,8 @@ return new Parser;
check_modification: check_modification:
context._check_remote_attachment_modification, context._check_remote_attachment_modification,
check_creation: context._check_remote_attachment_creation, check_creation: context._check_remote_attachment_creation,
check_deletion: context._check_remote_attachment_deletion check_deletion: context._check_remote_attachment_deletion,
from_local: false
} }
); );
} }
...@@ -9866,15 +10184,17 @@ return new Parser; ...@@ -9866,15 +10184,17 @@ return new Parser;
function propagateModification(context, source, destination, doc, hash, id, function propagateModification(context, source, destination, doc, hash, id,
skip_document_dict, skip_document_dict,
skip_deleted_document_dict, skip_deleted_document_dict,
report,
options) { options) {
var result = new RSVP.Queue(), var result = new RSVP.Queue(),
post_id, post_id,
to_skip = true, from_local,
from_local; conflict;
if (options === undefined) { if (options === undefined) {
options = {}; options = {};
} }
from_local = options.from_local; from_local = options.from_local;
conflict = options.conflict || false;
if (doc === null) { if (doc === null) {
result result
...@@ -9894,10 +10214,10 @@ return new Parser; ...@@ -9894,10 +10214,10 @@ return new Parser;
if (options.use_post) { if (options.use_post) {
result result
.push(function () { .push(function () {
report.log(id, from_local ? LOG_POST_REMOTE : LOG_POST_LOCAL);
return destination.post(doc); return destination.post(doc);
}) })
.push(function (new_id) { .push(function (new_id) {
to_skip = false;
post_id = new_id; post_id = new_id;
return source.put(post_id, doc); return source.put(post_id, doc);
}) })
...@@ -9935,7 +10255,6 @@ return new Parser; ...@@ -9935,7 +10255,6 @@ return new Parser;
return context._signature_sub_storage.remove(id); return context._signature_sub_storage.remove(id);
}) })
.push(function () { .push(function () {
to_skip = true;
return context._signature_sub_storage.put(post_id, { return context._signature_sub_storage.put(post_id, {
hash: hash, hash: hash,
from_local: from_local from_local: from_local
...@@ -9947,6 +10266,12 @@ return new Parser; ...@@ -9947,6 +10266,12 @@ return new Parser;
} else { } else {
result result
.push(function () { .push(function () {
if (conflict) {
report.log(id, from_local ? LOG_FORCE_PUT_REMOTE :
LOG_FORCE_PUT_LOCAL);
} else {
report.log(id, from_local ? LOG_PUT_REMOTE : LOG_PUT_LOCAL);
}
// Drop signature if the destination document was empty // Drop signature if the destination document was empty
// but a signature exists // but a signature exists
if (options.create_new_document === true) { if (options.create_new_document === true) {
...@@ -9965,11 +10290,6 @@ return new Parser; ...@@ -9965,11 +10290,6 @@ return new Parser;
}); });
} }
return result return result
.push(function () {
if (to_skip) {
skip_document_dict[id] = null;
}
})
.push(undefined, function (error) { .push(undefined, function (error) {
if (error instanceof SkipError) { if (error instanceof SkipError) {
return; return;
...@@ -9978,30 +10298,46 @@ return new Parser; ...@@ -9978,30 +10298,46 @@ return new Parser;
}); });
} }
function propagateDeletion(context, destination, id, skip_document_dict, function propagateDeletion(context, destination, id,
skip_deleted_document_dict) { skip_deleted_document_dict, report, options) {
// Do not delete a document if it has an attachment // Do not delete a document if it has an attachment
// ie, replication should prevent losing user data // ie, replication should prevent losing user data
// Synchronize attachments before, to ensure // Synchronize attachments before, to ensure
// all of them will be deleted too // all of them will be deleted too
var result; var result;
if (context._signature_hash_key !== undefined) { if (context._signature_hash_key !== undefined) {
if (options.conflict) {
report.log(id, options.from_local ? LOG_FORCE_DELETE_REMOTE :
LOG_FORCE_DELETE_LOCAL);
} else {
report.log(id, options.from_local ? LOG_DELETE_REMOTE :
LOG_DELETE_LOCAL);
}
result = destination.remove(id) result = destination.remove(id)
.push(function () { .push(function () {
return context._signature_sub_storage.remove(id); return context._signature_sub_storage.remove(id);
}); });
} else { } else {
result = repairDocumentAttachment(context, id) result = repairDocumentAttachment(context, id, report)
.push(function () { .push(function () {
return destination.allAttachments(id); return destination.allAttachments(id);
}) })
.push(function (attachment_dict) { .push(function (attachment_dict) {
if (JSON.stringify(attachment_dict) === "{}") { if (JSON.stringify(attachment_dict) === "{}") {
if (options.conflict) {
report.log(id, options.from_local ? LOG_FORCE_DELETE_REMOTE :
LOG_FORCE_DELETE_LOCAL);
} else {
report.log(id, options.from_local ? LOG_DELETE_REMOTE :
LOG_DELETE_LOCAL);
}
return destination.remove(id) return destination.remove(id)
.push(function () { .push(function () {
return context._signature_sub_storage.remove(id); return context._signature_sub_storage.remove(id);
}); });
} }
report.log(id, options.from_local ? LOG_UNEXPECTED_REMOTE_ATTACHMENT :
LOG_UNEXPECTED_LOCAL_ATTACHMENT);
}, function (error) { }, function (error) {
if ((error instanceof jIO.util.jIOError) && if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) { (error.status_code === 404)) {
...@@ -10012,7 +10348,6 @@ return new Parser; ...@@ -10012,7 +10348,6 @@ return new Parser;
} }
return result return result
.push(function () { .push(function () {
skip_document_dict[id] = null;
// No need to sync attachment twice on this document // No need to sync attachment twice on this document
skip_deleted_document_dict[id] = null; skip_deleted_document_dict[id] = null;
}); });
...@@ -10025,7 +10360,10 @@ return new Parser; ...@@ -10025,7 +10360,10 @@ return new Parser;
source, destination, id, source, destination, id,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, conflict_ignore,
report,
options) { options) {
// No need to check twice
skip_document_dict[id] = null;
var from_local = options.from_local; var from_local = options.from_local;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -10056,21 +10394,16 @@ return new Parser; ...@@ -10056,21 +10394,16 @@ return new Parser;
remote_hash = remote_list[1]; remote_hash = remote_list[1];
if (local_hash === remote_hash) { if (local_hash === remote_hash) {
// Same modifications on both side // Same modifications on both side
report.log(id, LOG_FALSE_CONFLICT);
if (local_hash === null) { if (local_hash === null) {
// Deleted on both side, drop signature // Deleted on both side, drop signature
return context._signature_sub_storage.remove(id) return context._signature_sub_storage.remove(id);
.push(function () {
skip_document_dict[id] = null;
});
} }
return context._signature_sub_storage.put(id, { return context._signature_sub_storage.put(id, {
hash: local_hash, hash: local_hash,
from_local: from_local from_local: from_local
}) });
.push(function () {
skip_document_dict[id] = null;
});
} }
if ((remote_hash === status_hash) || (conflict_force === true)) { if ((remote_hash === status_hash) || (conflict_force === true)) {
...@@ -10078,14 +10411,19 @@ return new Parser; ...@@ -10078,14 +10411,19 @@ return new Parser;
if (local_hash === null) { if (local_hash === null) {
// Deleted locally // Deleted locally
return propagateDeletion(context, destination, id, return propagateDeletion(context, destination, id,
skip_document_dict, skip_deleted_document_dict,
skip_deleted_document_dict); report,
{from_local: from_local,
conflict: (remote_hash !== status_hash)
});
} }
return propagateModification(context, source, destination, doc, return propagateModification(context, source, destination, doc,
local_hash, id, skip_document_dict, local_hash, id, skip_document_dict,
skip_deleted_document_dict, skip_deleted_document_dict,
report,
{use_post: ((options.use_post) && {use_post: ((options.use_post) &&
(remote_hash === null)), (remote_hash === null)),
conflict: (remote_hash !== status_hash),
from_local: from_local, from_local: from_local,
create_new_document: create_new_document:
((remote_hash === null) && ((remote_hash === null) &&
...@@ -10095,6 +10433,7 @@ return new Parser; ...@@ -10095,6 +10433,7 @@ return new Parser;
// Conflict cases // Conflict cases
if (conflict_ignore === true) { if (conflict_ignore === true) {
report.log(id, LOG_SKIP_CONFLICT);
return; return;
} }
...@@ -10102,8 +10441,11 @@ return new Parser; ...@@ -10102,8 +10441,11 @@ return new Parser;
// Automatically resolve conflict or force revert // Automatically resolve conflict or force revert
if (remote_hash === null) { if (remote_hash === null) {
// Deleted remotely // Deleted remotely
return propagateDeletion(context, source, id, skip_document_dict, return propagateDeletion(context, source, id,
skip_deleted_document_dict); skip_deleted_document_dict, report,
{from_local: !from_local,
conflict: (local_hash !== null)
});
} }
return propagateModification( return propagateModification(
context, context,
...@@ -10114,9 +10456,11 @@ return new Parser; ...@@ -10114,9 +10456,11 @@ return new Parser;
id, id,
skip_document_dict, skip_document_dict,
skip_deleted_document_dict, skip_deleted_document_dict,
report,
{use_post: ((options.use_revert_post) && {use_post: ((options.use_revert_post) &&
(local_hash === null)), (local_hash === null)),
from_local: !from_local, from_local: !from_local,
conflict: true,
create_new_document: ((local_hash === null) && create_new_document: ((local_hash === null) &&
(status_hash !== null))} (status_hash !== null))}
); );
...@@ -10128,17 +10472,17 @@ return new Parser; ...@@ -10128,17 +10472,17 @@ return new Parser;
return propagateModification(context, source, destination, doc, return propagateModification(context, source, destination, doc,
local_hash, id, skip_document_dict, local_hash, id, skip_document_dict,
skip_deleted_document_dict, skip_deleted_document_dict,
report,
{use_post: options.use_post, {use_post: options.use_post,
conflict: true,
from_local: from_local, from_local: from_local,
create_new_document: create_new_document:
(status_hash !== null)}); (status_hash !== null)});
} }
doc = doc || local_hash; report.log(id, LOG_UNRESOLVED_CONFLICT);
remote_doc = remote_doc || remote_hash; })
throw new jIO.util.jIOError("Conflict on '" + id + "': " + .push(undefined, function (error) {
stringify(doc) + " !== " + report.log(id, LOG_UNEXPECTED_ERROR, error);
stringify(remote_doc),
409);
}); });
} }
...@@ -10147,7 +10491,7 @@ return new Parser; ...@@ -10147,7 +10491,7 @@ return new Parser;
cache, destination_key, cache, destination_key,
destination, id, source, destination, id, source,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, options) { conflict_ignore, report, options) {
var status_hash; var status_hash;
queue queue
.push(function () { .push(function () {
...@@ -10161,7 +10505,7 @@ return new Parser; ...@@ -10161,7 +10505,7 @@ return new Parser;
status_hash, null, null, status_hash, null, null,
source, destination, id, source, destination, id,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, conflict_ignore, report,
options); options);
}); });
} }
...@@ -10172,7 +10516,7 @@ return new Parser; ...@@ -10172,7 +10516,7 @@ return new Parser;
source, destination, id, source, destination, id,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, conflict_ignore,
local_hash, status_hash, local_hash, status_hash, report,
options) { options) {
queue queue
.push(function () { .push(function () {
...@@ -10196,15 +10540,20 @@ return new Parser; ...@@ -10196,15 +10540,20 @@ return new Parser;
source, destination, id, source, destination, id,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, conflict_ignore,
report,
options); options);
} }
if (!options.from_local) {
report.log(id, LOG_NO_CHANGE);
}
}); });
} }
function pushStorage(context, skip_document_dict, function pushStorage(context, skip_document_dict,
skip_deleted_document_dict, skip_deleted_document_dict,
cache, source_key, destination_key, cache, source_key, destination_key,
source, destination, signature_allDocs, options) { source, destination, signature_allDocs,
report, options) {
var argument_list = [], var argument_list = [],
argument_list_deletion = []; argument_list_deletion = [];
if (!options.hasOwnProperty("use_post")) { if (!options.hasOwnProperty("use_post")) {
...@@ -10285,7 +10634,19 @@ return new Parser; ...@@ -10285,7 +10634,19 @@ return new Parser;
options.conflict_revert, options.conflict_revert,
options.conflict_ignore, options.conflict_ignore,
local_hash, status_hash, local_hash, status_hash,
report,
options]); options]);
} else if (local_hash === status_hash) {
report.log(key, LOG_NO_CHANGE);
} else {
if (signature_dict.hasOwnProperty(key)) {
report.log(key, options.from_local ?
LOG_SKIP_LOCAL_MODIFICATION :
LOG_SKIP_REMOTE_MODIFICATION);
} else {
report.log(key, options.from_local ? LOG_SKIP_LOCAL_CREATION :
LOG_SKIP_REMOTE_CREATION);
}
} }
} }
} }
...@@ -10312,8 +10673,11 @@ return new Parser; ...@@ -10312,8 +10673,11 @@ return new Parser;
options.conflict_force, options.conflict_force,
options.conflict_revert, options.conflict_revert,
options.conflict_ignore, options.conflict_ignore,
report,
options]); options]);
} else { } else {
report.log(key, options.from_local ? LOG_SKIP_LOCAL_DELETION :
LOG_SKIP_REMOTE_DELETION);
skip_deleted_document_dict[key] = null; skip_deleted_document_dict[key] = null;
} }
} }
...@@ -10333,11 +10697,11 @@ return new Parser; ...@@ -10333,11 +10697,11 @@ return new Parser;
}); });
} }
function repairDocument(queue, context, id, signature_hash_key, function repairDocument(queue, context, id, report, signature_hash_key,
signature_hash, signature_attachment_hash, signature_hash, signature_attachment_hash,
signature_from_local) { signature_from_local) {
queue.push(function () { queue.push(function () {
return repairDocumentAttachment(context, id, signature_hash_key, return repairDocumentAttachment(context, id, report, signature_hash_key,
signature_hash, signature_hash,
signature_attachment_hash, signature_attachment_hash,
signature_from_local); signature_from_local);
...@@ -10349,7 +10713,8 @@ return new Parser; ...@@ -10349,7 +10713,8 @@ return new Parser;
argument_list = arguments, argument_list = arguments,
skip_document_dict = {}, skip_document_dict = {},
skip_deleted_document_dict = {}, skip_deleted_document_dict = {},
cache = {}; cache = {},
report = new ReplicateReport(this._log_level, this._log_console);
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -10416,7 +10781,7 @@ return new Parser; ...@@ -10416,7 +10781,7 @@ return new Parser;
cache, 'local', 'remote', cache, 'local', 'remote',
context._local_sub_storage, context._local_sub_storage,
context._remote_sub_storage, context._remote_sub_storage,
signature_allDocs, signature_allDocs, report,
{ {
use_post: context._use_remote_post, use_post: context._use_remote_post,
conflict_force: (context._conflict_handling === conflict_force: (context._conflict_handling ===
...@@ -10447,7 +10812,8 @@ return new Parser; ...@@ -10447,7 +10812,8 @@ return new Parser;
cache, 'remote', 'local', cache, 'remote', 'local',
context._remote_sub_storage, context._remote_sub_storage,
context._local_sub_storage, context._local_sub_storage,
signature_allDocs, { signature_allDocs,
report, {
use_revert_post: context._use_remote_post, use_revert_post: context._use_remote_post,
conflict_force: (context._conflict_handling === conflict_force: (context._conflict_handling ===
CONFLICT_KEEP_REMOTE), CONFLICT_KEEP_REMOTE),
...@@ -10488,9 +10854,10 @@ return new Parser; ...@@ -10488,9 +10854,10 @@ return new Parser;
// is deleted but not pushed to the other storage // is deleted but not pushed to the other storage
if (!skip_deleted_document_dict.hasOwnProperty(row.id)) { if (!skip_deleted_document_dict.hasOwnProperty(row.id)) {
local_argument_list.push( local_argument_list.push(
[undefined, context, row.id, context._signature_hash_key, [undefined, context, row.id, report,
context._signature_hash_key,
row.value.hash, row.value.attachment_hash, row.value.hash, row.value.attachment_hash,
row.value.from_local] row.value.from_local, report]
); );
} }
} }
...@@ -10502,6 +10869,12 @@ return new Parser; ...@@ -10502,6 +10869,12 @@ return new Parser;
); );
}); });
} }
})
.push(function () {
if (report.has_error) {
throw report;
}
return report;
}); });
}; };
...@@ -14201,8 +14574,7 @@ return new Parser; ...@@ -14201,8 +14574,7 @@ return new Parser;
store.createIndex("_id", "_id", {unique: false}); store.createIndex("_id", "_id", {unique: false});
} }
function openIndexedDB(jio_storage) { function waitForOpenIndexedDB(db_name, callback) {
var db_name = jio_storage._database_name;
function resolver(resolve, reject) { function resolver(resolve, reject) {
// Open DB // // Open DB //
var request = indexedDB.open(db_name); var request = indexedDB.open(db_name);
...@@ -14244,57 +14616,99 @@ return new Parser; ...@@ -14244,57 +14616,99 @@ return new Parser;
}; };
request.onsuccess = function () { request.onsuccess = function () {
resolve(request.result); return new RSVP.Queue()
.push(function () {
return callback(request.result);
})
.push(function (result) {
request.result.close();
resolve(result);
}, function (error) {
request.result.close();
reject(error);
});
}; };
} }
// XXX Canceller???
return new RSVP.Queue() return new RSVP.Promise(resolver);
.push(function () {
return new RSVP.Promise(resolver);
});
} }
function openTransaction(db, stores, flag, autoclosedb) { function waitForTransaction(db, stores, flag, callback) {
var tx = db.transaction(stores, flag); var tx = db.transaction(stores, flag);
if (autoclosedb !== false) { function canceller() {
try {
tx.abort();
} catch (unused) {
// Transaction already finished
return;
}
}
function resolver(resolve, reject) {
var result;
try {
result = callback(tx);
} catch (error) {
reject(error);
}
tx.oncomplete = function () { tx.oncomplete = function () {
db.close(); return new RSVP.Queue()
.push(function () {
return result;
})
.push(resolve, function (error) {
canceller();
reject(error);
});
};
tx.onerror = function (error) {
canceller();
reject(error);
}; };
tx.onabort = function (evt) {
reject(evt.target);
};
return tx;
} }
tx.onabort = function () { return new RSVP.Promise(resolver, canceller);
db.close();
};
return tx;
} }
function handleCursor(request, callback, resolve, reject) { function waitForIDBRequest(request) {
request.onerror = function (error) { return new RSVP.Promise(function (resolve, reject) {
if (request.transaction) { request.onerror = reject;
request.transaction.abort(); request.onsuccess = resolve;
} });
reject(error); }
};
request.onsuccess = function (evt) { function waitForAllSynchronousCursor(request, callback) {
var cursor = evt.target.result; var force_cancellation = false;
if (cursor) {
// XXX Wait for result
try {
callback(cursor);
} catch (error) {
reject(error);
}
// continue to next iteration function canceller() {
cursor["continue"](); force_cancellation = true;
} else { }
resolve();
} function resolver(resolve, reject) {
}; request.onerror = reject;
request.onsuccess = function (evt) {
var cursor = evt.target.result;
if (cursor && !force_cancellation) {
try {
callback(cursor);
} catch (error) {
reject(error);
}
// continue to next iteration
cursor["continue"]();
} else {
resolve();
}
};
}
return new RSVP.Promise(resolver, canceller);
} }
IndexedDBStorage.prototype.buildQuery = function (options) { IndexedDBStorage.prototype.buildQuery = function (options) {
var result_list = []; var result_list = [],
context = this;
function pushIncludedMetadata(cursor) { function pushIncludedMetadata(cursor) {
result_list.push({ result_list.push({
...@@ -14310,17 +14724,23 @@ return new Parser; ...@@ -14310,17 +14724,23 @@ return new Parser;
"value": {} "value": {}
}); });
} }
return openIndexedDB(this)
.push(function (db) { return new RSVP.Queue()
return new RSVP.Promise(function (resolve, reject) { .push(function () {
var tx = openTransaction(db, ["metadata"], "readonly"); return waitForOpenIndexedDB(context._database_name, function (db) {
if (options.include_docs === true) { return waitForTransaction(db, ["metadata"], "readonly",
handleCursor(tx.objectStore("metadata").index("_id").openCursor(), function (tx) {
pushIncludedMetadata, resolve, reject); if (options.include_docs === true) {
} else { return waitForAllSynchronousCursor(
handleCursor(tx.objectStore("metadata").index("_id") tx.objectStore("metadata").index("_id").openCursor(),
.openKeyCursor(), pushMetadata, resolve, reject); pushIncludedMetadata
} );
}
return waitForAllSynchronousCursor(
tx.objectStore("metadata").index("_id").openKeyCursor(),
pushMetadata
);
});
}); });
}) })
.push(function () { .push(function () {
...@@ -14328,263 +14748,313 @@ return new Parser; ...@@ -14328,263 +14748,313 @@ return new Parser;
}); });
}; };
function handleGet(store, id, resolve, reject) {
var request = store.get(id);
request.onerror = reject;
request.onsuccess = function () {
if (request.result) {
resolve(request.result);
} else {
reject(new jIO.util.jIOError(
"IndexedDB: cannot find object '" + id + "' in the '" +
store.name + "' store",
404
));
}
};
}
IndexedDBStorage.prototype.get = function (id) { IndexedDBStorage.prototype.get = function (id) {
return openIndexedDB(this) var context = this;
.push(function (db) { return new RSVP.Queue()
return new RSVP.Promise(function (resolve, reject) { .push(function () {
var transaction = openTransaction(db, ["metadata"], "readonly"); return waitForOpenIndexedDB(context._database_name, function (db) {
handleGet( return waitForTransaction(db, ["metadata"], "readonly",
transaction.objectStore("metadata"), function (tx) {
id, return waitForIDBRequest(tx.objectStore("metadata").get(id));
resolve, });
reject
);
}); });
}) })
.push(function (result) { .push(function (evt) {
return result.doc; if (evt.target.result) {
return evt.target.result.doc;
}
throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" + id + "' in the 'metadata' store",
404
);
}); });
}; };
IndexedDBStorage.prototype.allAttachments = function (id) { IndexedDBStorage.prototype.allAttachments = function (id) {
var attachment_dict = {}; var attachment_dict = {},
context = this;
function addEntry(cursor) { function addEntry(cursor) {
attachment_dict[cursor.value._attachment] = {}; attachment_dict[cursor.primaryKey.slice(cursor.key.length + 1)] = {};
} }
return openIndexedDB(this) return new RSVP.Queue()
.push(function (db) { .push(function () {
return new RSVP.Promise(function (resolve, reject) { return waitForOpenIndexedDB(context._database_name, function (db) {
var transaction = openTransaction(db, ["metadata", "attachment"], return waitForTransaction(db, ["metadata", "attachment"], "readonly",
"readonly"); function (tx) {
function getAttachments() { return RSVP.all([
handleCursor( waitForIDBRequest(tx.objectStore("metadata").get(id)),
transaction.objectStore("attachment").index("_id") waitForAllSynchronousCursor(
.openCursor(IDBKeyRange.only(id)), tx.objectStore("attachment").index("_id")
addEntry, .openKeyCursor(IDBKeyRange.only(id)),
resolve, addEntry
reject )
); ]);
} });
handleGet(
transaction.objectStore("metadata"),
id,
getAttachments,
reject
);
}); });
}) })
.push(function () { .push(function (result_list) {
var evt = result_list[0];
if (!evt.target.result) {
throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" + id +
"' in the 'metadata' store",
404
);
}
return attachment_dict; return attachment_dict;
}); });
}; };
function handleRequest(request, resolve, reject) {
request.onerror = reject;
request.onsuccess = function () {
resolve(request.result);
};
}
IndexedDBStorage.prototype.put = function (id, metadata) { IndexedDBStorage.prototype.put = function (id, metadata) {
return openIndexedDB(this) return waitForOpenIndexedDB(this._database_name, function (db) {
.push(function (db) { return waitForTransaction(db, ["metadata"], "readwrite",
return new RSVP.Promise(function (resolve, reject) { function (tx) {
var transaction = openTransaction(db, ["metadata"], "readwrite"); return waitForIDBRequest(tx.objectStore("metadata").put({
handleRequest( "_id": id,
transaction.objectStore("metadata").put({ "doc": metadata
"_id": id, }));
"doc": metadata
}),
resolve,
reject
);
}); });
}); });
}; };
function deleteEntry(cursor) {
cursor["delete"]();
}
IndexedDBStorage.prototype.remove = function (id) { IndexedDBStorage.prototype.remove = function (id) {
var resolved_amount = 0; return waitForOpenIndexedDB(this._database_name, function (db) {
return openIndexedDB(this) return waitForTransaction(db, ["metadata", "attachment", "blob"],
.push(function (db) { "readwrite", function (tx) {
return new RSVP.Promise(function (resolve, reject) {
function resolver() { var promise_list = [],
if (resolved_amount < 2) { metadata_store = tx.objectStore("metadata"),
resolved_amount += 1; attachment_store = tx.objectStore("attachment"),
} else { blob_store = tx.objectStore("blob");
resolve();
} function deleteAttachment(cursor) {
} promise_list.push(
var transaction = openTransaction(db, ["metadata", "attachment", waitForIDBRequest(attachment_store.delete(cursor.primaryKey))
"blob"], "readwrite");
handleRequest(
transaction.objectStore("metadata")["delete"](id),
resolver,
reject
);
// XXX Why not possible to delete with KeyCursor?
handleCursor(transaction.objectStore("attachment").index("_id")
.openCursor(IDBKeyRange.only(id)),
deleteEntry,
resolver,
reject
); );
handleCursor(transaction.objectStore("blob").index("_id") }
.openCursor(IDBKeyRange.only(id)), function deleteBlob(cursor) {
deleteEntry, promise_list.push(
resolver, waitForIDBRequest(blob_store.delete(cursor.primaryKey))
reject
); );
}
return RSVP.all([
waitForIDBRequest(metadata_store.delete(id)),
waitForAllSynchronousCursor(
attachment_store.index("_id")
.openKeyCursor(IDBKeyRange.only(id)),
deleteAttachment
),
waitForAllSynchronousCursor(
blob_store.index("_id")
.openKeyCursor(IDBKeyRange.only(id)),
deleteBlob
),
])
.then(function () {
return RSVP.all(promise_list);
});
}); });
}); });
}; };
IndexedDBStorage.prototype.getAttachment = function (id, name, options) { IndexedDBStorage.prototype.getAttachment = function (id, name, options) {
var transaction,
type,
start,
end;
if (options === undefined) { if (options === undefined) {
options = {}; options = {};
} }
return openIndexedDB(this) var db_name = this._database_name,
.push(function (db) { start,
return new RSVP.Promise(function (resolve, reject) { end,
transaction = openTransaction( array_buffer_list = [];
db,
["attachment", "blob"], start = options.start || 0;
"readonly" end = options.end;
);
function getBlob(attachment) { // Stream the blob content
var total_length = attachment.info.length, if ((start !== 0) || (end !== undefined)) {
result_list = [],
store = transaction.objectStore("blob"), if (start < 0 || ((end !== undefined) && (end < 0))) {
start_index, throw new jIO.util.jIOError(
end_index; "_start and _end must be positive",
type = attachment.info.content_type; 400
start = options.start || 0; );
end = options.end || total_length; }
if (end > total_length) { if ((end !== undefined) && (start > end)) {
end = total_length; throw new jIO.util.jIOError("_start is greater than _end",
} 400);
if (start < 0 || end < 0) { }
throw new jIO.util.jIOError(
"_start and _end must be positive", return new RSVP.Queue()
400 .push(function () {
); return waitForOpenIndexedDB(db_name, function (db) {
} return waitForTransaction(db, ["blob"], "readonly",
if (start > end) { function (tx) {
throw new jIO.util.jIOError("_start is greater than _end", var key_path = buildKeyPath([id, name]),
400); blob_store = tx.objectStore("blob"),
} start_index,
start_index = Math.floor(start / UNITE); end_index,
end_index = Math.floor(end / UNITE) - 1; promise_list = [];
if (end % UNITE === 0) {
end_index -= 1;
} start_index = Math.floor(start / UNITE);
function resolver(result) { if (end !== undefined) {
if (result.blob !== undefined) { end_index = Math.floor(end / UNITE);
result_list.push(result); if (end % UNITE === 0) {
} end_index -= 1;
resolve(result_list); }
}
function getPart(i) {
return function (result) {
if (result) {
result_list.push(result);
} }
i += 1;
handleGet(store, function getBlobKey(cursor) {
buildKeyPath([id, name, i]), var index = parseInt(
(i <= end_index) ? getPart(i) : resolver, cursor.primaryKey.slice(key_path.length + 1),
reject 10
),
i;
if ((start !== 0) && (index < start_index)) {
// No need to fetch blobs at the start
return;
}
if ((end !== undefined) && (index > end_index)) {
// No need to fetch blobs at the end
return;
}
i = index - start_index;
// Extend array size
while (i > promise_list.length) {
promise_list.push(null);
i -= 1;
}
// Sort the blob by their index
promise_list.splice(
index - start_index,
0,
waitForIDBRequest(blob_store.get(cursor.primaryKey))
); );
}; }
}
getPart(start_index - 1)(); // Get all blob keys to check if they must be fetched
return waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openKeyCursor(IDBKeyRange.only([id, name])),
getBlobKey
)
.then(function () {
return RSVP.all(promise_list);
});
});
});
})
.push(function (result_list) {
// No need to keep the IDB open
var blob,
index,
i;
for (i = 0; i < result_list.length; i += 1) {
array_buffer_list.push(result_list[i].target.result.blob);
} }
// XXX Should raise if key is not good blob = new Blob(array_buffer_list,
handleGet(transaction.objectStore("attachment"), {type: "application/octet-stream"});
buildKeyPath([id, name]), index = Math.floor(start / UNITE) * UNITE;
getBlob, if (end === undefined) {
reject end = blob.length;
); } else {
end = end - index;
}
return blob.slice(start - index, end,
"application/octet-stream");
}); });
}
// Request the full blob
return new RSVP.Queue()
.push(function () {
return waitForOpenIndexedDB(db_name, function (db) {
return waitForTransaction(db, ["attachment", "blob"], "readonly",
function (tx) {
var key_path = buildKeyPath([id, name]),
attachment_store = tx.objectStore("attachment"),
blob_store = tx.objectStore("blob");
function getBlob(cursor) {
var index = parseInt(
cursor.primaryKey.slice(key_path.length + 1),
10
),
i = index;
// Extend array size
while (i > array_buffer_list.length) {
array_buffer_list.push(null);
i -= 1;
}
// Sort the blob by their index
array_buffer_list.splice(
index,
0,
cursor.value.blob
);
}
return RSVP.all([
// Get the attachment info (mime type)
waitForIDBRequest(attachment_store.get(
key_path
)),
// Get all needed blobs
waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openCursor(IDBKeyRange.only([id, name])),
getBlob
)
]);
});
});
}) })
.push(function (result_list) { .push(function (result_list) {
var array_buffer_list = [], // No need to keep the IDB open
blob, var blob,
i, attachment = result_list[0].target.result;
index,
len = result_list.length; // Should raise if key is not good
for (i = 0; i < len; i += 1) { if (!attachment) {
array_buffer_list.push(result_list[i].blob); throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" +
buildKeyPath([id, name]) +
"' in the 'attachment' store",
404
);
} }
if ((options.start === undefined) && (options.end === undefined)) {
return new Blob(array_buffer_list, {type: type}); blob = new Blob(array_buffer_list,
{type: attachment.info.content_type});
if (blob.length !== attachment.info.total_length) {
throw new jIO.util.jIOError(
"IndexedDB: attachment '" +
buildKeyPath([id, name]) +
"' in the 'attachment' store is broken",
500
);
} }
index = Math.floor(start / UNITE) * UNITE; return blob;
blob = new Blob(array_buffer_list, {type: "application/octet-stream"});
return blob.slice(start - index, end - index,
"application/octet-stream");
}); });
}; };
function removeAttachment(transaction, id, name, resolve, reject) {
// XXX How to get the right attachment
function deleteContent() {
handleCursor(
transaction.objectStore("blob").index("_id_attachment")
.openCursor(IDBKeyRange.only([id, name])),
deleteEntry,
resolve,
reject
);
}
handleRequest(
transaction.objectStore("attachment")["delete"](
buildKeyPath([id, name])
),
deleteContent,
reject
);
}
IndexedDBStorage.prototype.putAttachment = function (id, name, blob) { IndexedDBStorage.prototype.putAttachment = function (id, name, blob) {
var blob_part = [], var db_name = this._database_name;
transaction, return new RSVP.Queue()
db; .push(function () {
return openIndexedDB(this)
.push(function (database) {
db = database;
// Split the blob first // Split the blob first
return jIO.util.readBlobAsArrayBuffer(blob); return jIO.util.readBlobAsArrayBuffer(blob);
}) })
.push(function (event) { .push(function (event) {
var array_buffer = event.target.result, var array_buffer = event.target.result,
blob_part = [],
total_size = blob.size, total_size = blob.size,
handled_size = 0; handled_size = 0;
...@@ -14594,57 +15064,102 @@ return new Parser; ...@@ -14594,57 +15064,102 @@ return new Parser;
handled_size += UNITE; handled_size += UNITE;
} }
// Remove previous attachment return waitForOpenIndexedDB(db_name, function (db) {
transaction = openTransaction(db, ["attachment", "blob"], "readwrite"); return waitForTransaction(db, ["attachment", "blob"], "readwrite",
return new RSVP.Promise(function (resolve, reject) { function (tx) {
function write() { var blob_store,
var len = blob_part.length - 1, promise_list,
attachment_store = transaction.objectStore("attachment"), delete_promise_list = [],
blob_store = transaction.objectStore("blob"); key_path = buildKeyPath([id, name]),
function putBlobPart(i) { i;
return function () { // First write the attachment info on top of previous
i += 1; promise_list = [
handleRequest( waitForIDBRequest(tx.objectStore("attachment").put({
blob_store.put({ "_key_path": key_path,
"_id": id,
"_attachment": name,
"info": {
"content_type": blob.type,
"length": blob.size
}
}))
];
// Then, write all blob parts on top of previous
blob_store = tx.objectStore("blob");
for (i = 0; i < blob_part.length; i += 1) {
promise_list.push(
waitForIDBRequest(blob_store.put({
"_key_path": buildKeyPath([id, name, i]), "_key_path": buildKeyPath([id, name, i]),
"_id" : id, "_id" : id,
"_attachment" : name, "_attachment" : name,
"_part" : i, "_part" : i,
"blob": blob_part[i] "blob": blob_part[i]
}), }))
(i < len) ? putBlobPart(i) : resolve,
reject
); );
}; }
}
handleRequest( function deleteEntry(cursor) {
attachment_store.put({ var index = parseInt(
"_key_path": buildKeyPath([id, name]), cursor.primaryKey.slice(key_path.length + 1),
"_id": id, 10
"_attachment": name, );
"info": { if (index >= blob_part.length) {
"content_type": blob.type, delete_promise_list.push(
"length": blob.size waitForIDBRequest(blob_store.delete(cursor.primaryKey))
);
} }
}), }
putBlobPart(-1),
reject // Finally, remove all remaining blobs
); promise_list.push(
} waitForAllSynchronousCursor(
removeAttachment(transaction, id, name, write, reject); blob_store.index("_id_attachment")
.openKeyCursor(IDBKeyRange.only([id, name])),
deleteEntry
)
);
return RSVP.all(promise_list)
.then(function () {
if (delete_promise_list.length) {
return RSVP.all(delete_promise_list);
}
});
});
}); });
}); });
}; };
IndexedDBStorage.prototype.removeAttachment = function (id, name) { IndexedDBStorage.prototype.removeAttachment = function (id, name) {
return openIndexedDB(this) return waitForOpenIndexedDB(this._database_name, function (db) {
.push(function (db) { return waitForTransaction(db, ["attachment", "blob"], "readwrite",
var transaction = openTransaction(db, ["attachment", "blob"], function (tx) {
"readwrite"); var promise_list = [],
return new RSVP.Promise(function (resolve, reject) { attachment_store = tx.objectStore("attachment"),
removeAttachment(transaction, id, name, resolve, reject); blob_store = tx.objectStore("blob");
function deleteEntry(cursor) {
promise_list.push(
waitForIDBRequest(blob_store.delete(cursor.primaryKey))
);
}
return RSVP.all([
waitForIDBRequest(
attachment_store.delete(buildKeyPath([id, name]))
),
waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openKeyCursor(IDBKeyRange.only([id, name])),
deleteEntry
)
])
.then(function () {
return RSVP.all(promise_list);
});
}); });
}); });
}; };
jIO.addStorage("indexeddb", IndexedDBStorage); jIO.addStorage("indexeddb", IndexedDBStorage);
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
{ {
"name": "jio", "name": "jio",
"version": "v3.34.0", "version": "v3.35.0",
"license": "GPLv3+", "license": "GPLv3+",
"author": "Nexedi SA", "author": "Nexedi SA",
"contributors": [ "contributors": [
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment