Commit 4687f745 authored by Romain Courteaud's avatar Romain Courteaud

[erp5_core/erp5_web_renderjs_ui] Update jIO 3.35.0

parent 2831afd4
...@@ -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();
...@@ -8852,7 +8849,7 @@ return new Parser; ...@@ -8852,7 +8849,7 @@ return new Parser;
var ceilHeapSize = function (v) { var ceilHeapSize = function (v) {
// The asm.js spec says: // The asm.js spec says:
// The heap object's byteLength must be either // The heap object's byteLength must be either
// 2^n for n in [12, 24) or 2^24 * n for n 1. // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1.
// Also, byteLengths smaller than 2^16 are deprecated. // Also, byteLengths smaller than 2^16 are deprecated.
var p; var p;
// If v is smaller than 2^16, the smallest possible solution // If v is smaller than 2^16, the smallest possible solution
...@@ -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) {
if (!from_local) {
report.logAttachment(id, name, LOG_NO_CHANGE_ATTACHMENT);
}
return;
}
return checkAndPropagateAttachment(context, return checkAndPropagateAttachment(context,
skip_attachment_dict, skip_attachment_dict,
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);
}); });
} }
...@@ -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,7 +9842,14 @@ return new Parser; ...@@ -9592,7 +9842,14 @@ 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);
}
} }
} }
} }
...@@ -9602,19 +9859,34 @@ return new Parser; ...@@ -9602,19 +9859,34 @@ return new Parser;
argument_list, argument_list,
context._parallel_operation_attachment_amount 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,20 +10394,15 @@ return new Parser; ...@@ -10056,20 +10394,15 @@ 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;
}); });
} }
...@@ -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,47 +14616,86 @@ return new Parser; ...@@ -14244,47 +14616,86 @@ return new Parser;
}; };
request.onsuccess = function () { request.onsuccess = function () {
resolve(request.result);
};
}
// XXX Canceller???
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return new RSVP.Promise(resolver); return callback(request.result);
})
.push(function (result) {
request.result.close();
resolve(result);
}, function (error) {
request.result.close();
reject(error);
}); });
};
} }
function openTransaction(db, stores, flag, autoclosedb) { return new RSVP.Promise(resolver);
}
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) {
tx.onabort = function () { canceller();
db.close(); reject(error);
};
tx.onabort = function (evt) {
reject(evt.target);
}; };
return tx; return tx;
} }
return new RSVP.Promise(resolver, canceller);
}
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);
};
function waitForAllSynchronousCursor(request, callback) {
var force_cancellation = false;
function canceller() {
force_cancellation = true;
}
function resolver(resolve, reject) {
request.onerror = reject;
request.onsuccess = function (evt) { request.onsuccess = function (evt) {
var cursor = evt.target.result; var cursor = evt.target.result;
if (cursor) { if (cursor && !force_cancellation) {
// XXX Wait for result
try { try {
callback(cursor); callback(cursor);
} catch (error) { } catch (error) {
reject(error); reject(error);
} }
// continue to next iteration // continue to next iteration
cursor["continue"](); cursor["continue"]();
} else { } else {
...@@ -14292,9 +14703,12 @@ return new Parser; ...@@ -14292,9 +14703,12 @@ return new Parser;
} }
}; };
} }
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) {
return waitForTransaction(db, ["metadata"], "readonly",
function (tx) {
if (options.include_docs === true) { if (options.include_docs === true) {
handleCursor(tx.objectStore("metadata").index("_id").openCursor(), return waitForAllSynchronousCursor(
pushIncludedMetadata, resolve, reject); tx.objectStore("metadata").index("_id").openCursor(),
} else { pushIncludedMetadata
handleCursor(tx.objectStore("metadata").index("_id") );
.openKeyCursor(), pushMetadata, resolve, reject);
} }
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(
transaction.objectStore("metadata").put({
"_id": id, "_id": id,
"doc": metadata "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"],
"readonly"
);
function getBlob(attachment) {
var total_length = attachment.info.length,
result_list = [],
store = transaction.objectStore("blob"),
start_index,
end_index;
type = attachment.info.content_type;
start = options.start || 0; start = options.start || 0;
end = options.end || total_length; end = options.end;
if (end > total_length) {
end = total_length; // Stream the blob content
} if ((start !== 0) || (end !== undefined)) {
if (start < 0 || end < 0) {
if (start < 0 || ((end !== undefined) && (end < 0))) {
throw new jIO.util.jIOError( throw new jIO.util.jIOError(
"_start and _end must be positive", "_start and _end must be positive",
400 400
); );
} }
if (start > end) { if ((end !== undefined) && (start > end)) {
throw new jIO.util.jIOError("_start is greater than _end", throw new jIO.util.jIOError("_start is greater than _end",
400); 400);
} }
return new RSVP.Queue()
.push(function () {
return waitForOpenIndexedDB(db_name, function (db) {
return waitForTransaction(db, ["blob"], "readonly",
function (tx) {
var key_path = buildKeyPath([id, name]),
blob_store = tx.objectStore("blob"),
start_index,
end_index,
promise_list = [];
start_index = Math.floor(start / UNITE); start_index = Math.floor(start / UNITE);
end_index = Math.floor(end / UNITE) - 1; if (end !== undefined) {
end_index = Math.floor(end / UNITE);
if (end % UNITE === 0) { if (end % UNITE === 0) {
end_index -= 1; end_index -= 1;
} }
function resolver(result) {
if (result.blob !== undefined) {
result_list.push(result);
}
resolve(result_list);
} }
function getPart(i) {
return function (result) { function getBlobKey(cursor) {
if (result) { var index = parseInt(
result_list.push(result); cursor.primaryKey.slice(key_path.length + 1),
10
),
i;
if ((start !== 0) && (index < start_index)) {
// No need to fetch blobs at the start
return;
} }
i += 1; if ((end !== undefined) && (index > end_index)) {
handleGet(store, // No need to fetch blobs at the end
buildKeyPath([id, name, i]), return;
(i <= end_index) ? getPart(i) : resolver,
reject
);
};
} }
getPart(start_index - 1)();
i = index - start_index;
// Extend array size
while (i > promise_list.length) {
promise_list.push(null);
i -= 1;
} }
// XXX Should raise if key is not good // Sort the blob by their index
handleGet(transaction.objectStore("attachment"), promise_list.splice(
buildKeyPath([id, name]), index - start_index,
getBlob, 0,
reject waitForIDBRequest(blob_store.get(cursor.primaryKey))
); );
}
// 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) { .push(function (result_list) {
var array_buffer_list = [], // No need to keep the IDB open
blob, var blob,
i,
index, index,
len = result_list.length; i;
for (i = 0; i < len; i += 1) {
array_buffer_list.push(result_list[i].blob); for (i = 0; i < result_list.length; i += 1) {
} array_buffer_list.push(result_list[i].target.result.blob);
if ((options.start === undefined) && (options.end === undefined)) {
return new Blob(array_buffer_list, {type: type});
} }
blob = new Blob(array_buffer_list,
{type: "application/octet-stream"});
index = Math.floor(start / UNITE) * UNITE; index = Math.floor(start / UNITE) * UNITE;
blob = new Blob(array_buffer_list, {type: "application/octet-stream"}); if (end === undefined) {
return blob.slice(start - index, end - index, end = blob.length;
} else {
end = end - index;
}
return blob.slice(start - index, end,
"application/octet-stream"); "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
);
}
function removeAttachment(transaction, id, name, resolve, reject) { return RSVP.all([
// XXX How to get the right attachment // Get the attachment info (mime type)
function deleteContent() { waitForIDBRequest(attachment_store.get(
handleCursor( key_path
transaction.objectStore("blob").index("_id_attachment") )),
// Get all needed blobs
waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openCursor(IDBKeyRange.only([id, name])), .openCursor(IDBKeyRange.only([id, name])),
deleteEntry, getBlob
resolve, )
reject ]);
});
});
})
.push(function (result_list) {
// No need to keep the IDB open
var blob,
attachment = result_list[0].target.result;
// Should raise if key is not good
if (!attachment) {
throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" +
buildKeyPath([id, name]) +
"' in the 'attachment' store",
404
); );
} }
handleRequest(
transaction.objectStore("attachment")["delete"]( blob = new Blob(array_buffer_list,
buildKeyPath([id, name]) {type: attachment.info.content_type});
), if (blob.length !== attachment.info.total_length) {
deleteContent, throw new jIO.util.jIOError(
reject "IndexedDB: attachment '" +
buildKeyPath([id, name]) +
"' in the 'attachment' store is broken",
500
); );
} }
return blob;
});
};
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,55 +15064,100 @@ return new Parser; ...@@ -14594,55 +15064,100 @@ 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(
attachment_store.put({ function deleteEntry(cursor) {
"_key_path": buildKeyPath([id, name]), var index = parseInt(
"_id": id, cursor.primaryKey.slice(key_path.length + 1),
"_attachment": name, 10
"info": { );
"content_type": blob.type, if (index >= blob_part.length) {
"length": blob.size delete_promise_list.push(
waitForIDBRequest(blob_store.delete(cursor.primaryKey))
);
} }
}), }
putBlobPart(-1),
reject // Finally, remove all remaining blobs
promise_list.push(
waitForAllSynchronousCursor(
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);
} }
removeAttachment(transaction, id, name, write, reject); });
});
}); });
}); });
}; };
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);
});
}); });
}); });
}; };
......
...@@ -236,7 +236,7 @@ ...@@ -236,7 +236,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>969.40891.1984.28398</string> </value> <value> <string>970.58387.45482.44544</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -254,7 +254,7 @@ ...@@ -254,7 +254,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1538062922.79</float> <float>1539695044.71</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -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();
...@@ -8852,7 +8849,7 @@ return new Parser; ...@@ -8852,7 +8849,7 @@ return new Parser;
var ceilHeapSize = function (v) { var ceilHeapSize = function (v) {
// The asm.js spec says: // The asm.js spec says:
// The heap object's byteLength must be either // The heap object's byteLength must be either
// 2^n for n in [12, 24) or 2^24 * n for n 1. // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1.
// Also, byteLengths smaller than 2^16 are deprecated. // Also, byteLengths smaller than 2^16 are deprecated.
var p; var p;
// If v is smaller than 2^16, the smallest possible solution // If v is smaller than 2^16, the smallest possible solution
...@@ -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) {
if (!from_local) {
report.logAttachment(id, name, LOG_NO_CHANGE_ATTACHMENT);
}
return;
}
return checkAndPropagateAttachment(context, return checkAndPropagateAttachment(context,
skip_attachment_dict, skip_attachment_dict,
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);
}); });
} }
...@@ -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,7 +9842,14 @@ return new Parser; ...@@ -9592,7 +9842,14 @@ 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);
}
} }
} }
} }
...@@ -9602,19 +9859,34 @@ return new Parser; ...@@ -9602,19 +9859,34 @@ return new Parser;
argument_list, argument_list,
context._parallel_operation_attachment_amount 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,20 +10394,15 @@ return new Parser; ...@@ -10056,20 +10394,15 @@ 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;
}); });
} }
...@@ -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,47 +14616,86 @@ return new Parser; ...@@ -14244,47 +14616,86 @@ return new Parser;
}; };
request.onsuccess = function () { request.onsuccess = function () {
resolve(request.result);
};
}
// XXX Canceller???
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
return new RSVP.Promise(resolver); return callback(request.result);
})
.push(function (result) {
request.result.close();
resolve(result);
}, function (error) {
request.result.close();
reject(error);
}); });
};
} }
function openTransaction(db, stores, flag, autoclosedb) { return new RSVP.Promise(resolver);
}
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) {
tx.onabort = function () { canceller();
db.close(); reject(error);
};
tx.onabort = function (evt) {
reject(evt.target);
}; };
return tx; return tx;
} }
return new RSVP.Promise(resolver, canceller);
}
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);
};
function waitForAllSynchronousCursor(request, callback) {
var force_cancellation = false;
function canceller() {
force_cancellation = true;
}
function resolver(resolve, reject) {
request.onerror = reject;
request.onsuccess = function (evt) { request.onsuccess = function (evt) {
var cursor = evt.target.result; var cursor = evt.target.result;
if (cursor) { if (cursor && !force_cancellation) {
// XXX Wait for result
try { try {
callback(cursor); callback(cursor);
} catch (error) { } catch (error) {
reject(error); reject(error);
} }
// continue to next iteration // continue to next iteration
cursor["continue"](); cursor["continue"]();
} else { } else {
...@@ -14292,9 +14703,12 @@ return new Parser; ...@@ -14292,9 +14703,12 @@ return new Parser;
} }
}; };
} }
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) {
return waitForTransaction(db, ["metadata"], "readonly",
function (tx) {
if (options.include_docs === true) { if (options.include_docs === true) {
handleCursor(tx.objectStore("metadata").index("_id").openCursor(), return waitForAllSynchronousCursor(
pushIncludedMetadata, resolve, reject); tx.objectStore("metadata").index("_id").openCursor(),
} else { pushIncludedMetadata
handleCursor(tx.objectStore("metadata").index("_id") );
.openKeyCursor(), pushMetadata, resolve, reject);
} }
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(
transaction.objectStore("metadata").put({
"_id": id, "_id": id,
"doc": metadata "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"],
"readonly"
);
function getBlob(attachment) {
var total_length = attachment.info.length,
result_list = [],
store = transaction.objectStore("blob"),
start_index,
end_index;
type = attachment.info.content_type;
start = options.start || 0; start = options.start || 0;
end = options.end || total_length; end = options.end;
if (end > total_length) {
end = total_length; // Stream the blob content
} if ((start !== 0) || (end !== undefined)) {
if (start < 0 || end < 0) {
if (start < 0 || ((end !== undefined) && (end < 0))) {
throw new jIO.util.jIOError( throw new jIO.util.jIOError(
"_start and _end must be positive", "_start and _end must be positive",
400 400
); );
} }
if (start > end) { if ((end !== undefined) && (start > end)) {
throw new jIO.util.jIOError("_start is greater than _end", throw new jIO.util.jIOError("_start is greater than _end",
400); 400);
} }
return new RSVP.Queue()
.push(function () {
return waitForOpenIndexedDB(db_name, function (db) {
return waitForTransaction(db, ["blob"], "readonly",
function (tx) {
var key_path = buildKeyPath([id, name]),
blob_store = tx.objectStore("blob"),
start_index,
end_index,
promise_list = [];
start_index = Math.floor(start / UNITE); start_index = Math.floor(start / UNITE);
end_index = Math.floor(end / UNITE) - 1; if (end !== undefined) {
end_index = Math.floor(end / UNITE);
if (end % UNITE === 0) { if (end % UNITE === 0) {
end_index -= 1; end_index -= 1;
} }
function resolver(result) {
if (result.blob !== undefined) {
result_list.push(result);
}
resolve(result_list);
} }
function getPart(i) {
return function (result) { function getBlobKey(cursor) {
if (result) { var index = parseInt(
result_list.push(result); cursor.primaryKey.slice(key_path.length + 1),
10
),
i;
if ((start !== 0) && (index < start_index)) {
// No need to fetch blobs at the start
return;
} }
i += 1; if ((end !== undefined) && (index > end_index)) {
handleGet(store, // No need to fetch blobs at the end
buildKeyPath([id, name, i]), return;
(i <= end_index) ? getPart(i) : resolver,
reject
);
};
} }
getPart(start_index - 1)();
i = index - start_index;
// Extend array size
while (i > promise_list.length) {
promise_list.push(null);
i -= 1;
} }
// XXX Should raise if key is not good // Sort the blob by their index
handleGet(transaction.objectStore("attachment"), promise_list.splice(
buildKeyPath([id, name]), index - start_index,
getBlob, 0,
reject waitForIDBRequest(blob_store.get(cursor.primaryKey))
); );
}
// 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) { .push(function (result_list) {
var array_buffer_list = [], // No need to keep the IDB open
blob, var blob,
i,
index, index,
len = result_list.length; i;
for (i = 0; i < len; i += 1) {
array_buffer_list.push(result_list[i].blob); for (i = 0; i < result_list.length; i += 1) {
} array_buffer_list.push(result_list[i].target.result.blob);
if ((options.start === undefined) && (options.end === undefined)) {
return new Blob(array_buffer_list, {type: type});
} }
blob = new Blob(array_buffer_list,
{type: "application/octet-stream"});
index = Math.floor(start / UNITE) * UNITE; index = Math.floor(start / UNITE) * UNITE;
blob = new Blob(array_buffer_list, {type: "application/octet-stream"}); if (end === undefined) {
return blob.slice(start - index, end - index, end = blob.length;
} else {
end = end - index;
}
return blob.slice(start - index, end,
"application/octet-stream"); "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
);
}
function removeAttachment(transaction, id, name, resolve, reject) { return RSVP.all([
// XXX How to get the right attachment // Get the attachment info (mime type)
function deleteContent() { waitForIDBRequest(attachment_store.get(
handleCursor( key_path
transaction.objectStore("blob").index("_id_attachment") )),
// Get all needed blobs
waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openCursor(IDBKeyRange.only([id, name])), .openCursor(IDBKeyRange.only([id, name])),
deleteEntry, getBlob
resolve, )
reject ]);
});
});
})
.push(function (result_list) {
// No need to keep the IDB open
var blob,
attachment = result_list[0].target.result;
// Should raise if key is not good
if (!attachment) {
throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" +
buildKeyPath([id, name]) +
"' in the 'attachment' store",
404
); );
} }
handleRequest(
transaction.objectStore("attachment")["delete"]( blob = new Blob(array_buffer_list,
buildKeyPath([id, name]) {type: attachment.info.content_type});
), if (blob.length !== attachment.info.total_length) {
deleteContent, throw new jIO.util.jIOError(
reject "IndexedDB: attachment '" +
buildKeyPath([id, name]) +
"' in the 'attachment' store is broken",
500
); );
} }
return blob;
});
};
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,55 +15064,100 @@ return new Parser; ...@@ -14594,55 +15064,100 @@ 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(
attachment_store.put({ function deleteEntry(cursor) {
"_key_path": buildKeyPath([id, name]), var index = parseInt(
"_id": id, cursor.primaryKey.slice(key_path.length + 1),
"_attachment": name, 10
"info": { );
"content_type": blob.type, if (index >= blob_part.length) {
"length": blob.size delete_promise_list.push(
waitForIDBRequest(blob_store.delete(cursor.primaryKey))
);
} }
}), }
putBlobPart(-1),
reject // Finally, remove all remaining blobs
promise_list.push(
waitForAllSynchronousCursor(
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);
} }
removeAttachment(transaction, id, name, write, reject); });
});
}); });
}); });
}; };
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);
});
}); });
}); });
}; };
......
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