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

Release 3.35.0

Fix IndexedDB transaction handling.

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