Commit 1b863d06 authored by Vincent Bechu's avatar Vincent Bechu

Update jio erp5 core

parent 81e17101
...@@ -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,144 @@ return new Parser; ...@@ -9074,7 +9071,144 @@ 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 = 1,
LOG_UNEXPECTED_LOCAL_ATTACHMENT = 2,
LOG_UNEXPECTED_REMOTE_ATTACHMENT = 3,
LOG_UNRESOLVED_ATTACHMENT_CONFLICT = 11,
// 100 - 199 solving conflict
LOG_FORCE_PUT_REMOTE = 100,
LOG_FORCE_DELETE_REMOTE = 103,
LOG_FORCE_PUT_REMOTE_ATTACHMENT = 110,
LOG_FORCE_DELETE_REMOTE_ATTACHMENT = 113,
LOG_FORCE_PUT_LOCAL = 150,
LOG_FORCE_DELETE_LOCAL = 153,
LOG_FORCE_PUT_LOCAL_ATTACHMENT = 160,
LOG_FORCE_DELETE_LOCAL_ATTACHMENT = 163,
// 200 - 299 pushing change
LOG_PUT_REMOTE = 200,
LOG_POST_REMOTE = 201,
LOG_DELETE_REMOTE = 203,
LOG_PUT_REMOTE_ATTACHMENT = 210,
LOG_DELETE_REMOTE_ATTACHMENT = 213,
LOG_PUT_LOCAL = 250,
LOG_POST_LOCAL = 251,
LOG_DELETE_LOCAL = 253,
LOG_PUT_LOCAL_ATTACHMENT = 260,
LOG_DELETE_LOCAL_ATTACHMENT = 263,
LOG_FALSE_CONFLICT = 298,
LOG_FALSE_CONFLICT_ATTACHMENT = 299,
// 300 - 399 nothing to do
LOG_SKIP_LOCAL_CREATION = 300,
LOG_SKIP_LOCAL_MODIFICATION = 301,
LOG_SKIP_LOCAL_DELETION = 303,
LOG_SKIP_REMOTE_CREATION = 350,
LOG_SKIP_REMOTE_MODIFICATION = 351,
LOG_SKIP_REMOTE_DELETION = 353,
LOG_SKIP_LOCAL_ATTACHMENT_CREATION = 310,
LOG_SKIP_LOCAL_ATTACHMENT_MODIFICATION = 311,
LOG_SKIP_LOCAL_ATTACHMENT_DELETION = 313,
LOG_SKIP_REMOTE_ATTACHMENT_CREATION = 360,
LOG_SKIP_REMOTE_ATTACHMENT_MODIFICATION = 361,
LOG_SKIP_REMOTE_ATTACHMENT_DELETION = 363,
LOG_SKIP_CONFLICT = 396,
LOG_SKIP_CONFLICT_ATTACHMENT = 397,
LOG_NO_CHANGE = 398,
LOG_NO_CHANGE_ATTACHMENT = 399;
function ReplicateReport() {
this._list = [];
this.name = 'ReplicateReport';
this.message = this.name;
this.has_error = false;
}
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,
log: function (id, type, extra) {
if (type === undefined) {
if (extra === undefined) {
extra = 'Unknown type: ' + type;
}
type = LOG_UNEXPECTED_ERROR;
}
if (extra === undefined) {
this._list.push([type, id]);
} else {
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 (extra === undefined) {
this._list.push([type, id, name]);
} else {
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")) {
...@@ -9320,30 +9454,42 @@ return new Parser; ...@@ -9320,30 +9454,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 +9498,9 @@ return new Parser; ...@@ -9352,7 +9498,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 +9522,39 @@ return new Parser; ...@@ -9374,39 +9522,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 +9562,21 @@ return new Parser; ...@@ -9414,17 +9562,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 +9584,15 @@ return new Parser; ...@@ -9432,14 +9584,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 +9603,9 @@ return new Parser; ...@@ -9450,7 +9603,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 +9640,20 @@ return new Parser; ...@@ -9485,14 +9640,20 @@ return new Parser;
var array_buffer = evt.target.result, var array_buffer = evt.target.result,
local_hash = generateHashFromArrayBuffer(array_buffer); local_hash = generateHashFromArrayBuffer(array_buffer);
if (local_hash !== status_hash) { if (local_hash === status_hash) {
return checkAndPropagateAttachment(context, if (!from_local) {
skip_attachment_dict, report.logAttachment(id, name, LOG_NO_CHANGE_ATTACHMENT);
status_hash, local_hash, blob, }
source, destination, id, name, return;
conflict_force, conflict_revert,
conflict_ignore);
} }
return checkAndPropagateAttachment(context,
skip_attachment_dict,
status_hash, local_hash, blob,
source, destination, id, name,
conflict_force, conflict_revert,
conflict_ignore,
from_local,
report);
}); });
} }
...@@ -9500,7 +9661,7 @@ return new Parser; ...@@ -9500,7 +9661,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 +9675,17 @@ return new Parser; ...@@ -9514,16 +9675,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 +9730,19 @@ return new Parser; ...@@ -9568,7 +9730,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 +9755,10 @@ return new Parser; ...@@ -9581,10 +9755,10 @@ return new Parser;
}) })
.push(function () { .push(function () {
var key, argument_list = []; var key, argument_list = [];
if (options.check_deletion === true) { for (key in signature_dict) {
for (key in signature_dict) { if (signature_dict.hasOwnProperty(key)) {
if (signature_dict.hasOwnProperty(key)) { if (!local_dict.hasOwnProperty(key)) {
if (!local_dict.hasOwnProperty(key)) { if (options.check_deletion === true) {
argument_list.push([undefined, argument_list.push([undefined,
context, context,
skip_attachment_dict, skip_attachment_dict,
...@@ -9592,29 +9766,51 @@ return new Parser; ...@@ -9592,29 +9766,51 @@ return new Parser;
source, source,
options.conflict_force, options.conflict_force,
options.conflict_revert, options.conflict_revert,
options.conflict_ignore]); options.conflict_ignore,
from_local,
report]);
} else {
report.logAttachment(id, key, from_local ?
LOG_SKIP_LOCAL_ATTACHMENT_DELETION :
LOG_SKIP_REMOTE_ATTACHMENT_DELETION);
} }
} }
} }
return dispatchQueue(
context,
checkAttachmentLocalDeletion,
argument_list,
context._parallel_operation_attachment_amount
);
} }
return dispatchQueue(
context,
checkAttachmentLocalDeletion,
argument_list,
context._parallel_operation_attachment_amount
);
}); });
} }
function propagateFastAttachmentDeletion(queue, id, name, storage) { function propagateFastAttachmentDeletion(queue, id, name, storage, signature,
from_local, report) {
report.logAttachment(id, name, from_local ? LOG_DELETE_REMOTE_ATTACHMENT :
LOG_DELETE_LOCAL_ATTACHMENT);
return queue return queue
.push(function () { .push(function () {
return storage.removeAttachment(id, name); return storage.removeAttachment(id, name);
})
.push(function () {
return signature.removeAttachment(id, name);
});
}
function propagateFastSignatureDeletion(queue, id, name, signature,
report) {
report.logAttachment(id, name, LOG_FALSE_CONFLICT_ATTACHMENT);
return queue
.push(function () {
return signature.removeAttachment(id, name);
}); });
} }
function propagateFastAttachmentModification(queue, id, key, source, function propagateFastAttachmentModification(queue, id, key, source,
destination, signature, hash) { destination, signature, hash,
from_local, report) {
return queue return queue
.push(function () { .push(function () {
return signature.getAttachment(id, key, {format: 'json'}) return signature.getAttachment(id, key, {format: 'json'})
...@@ -9627,6 +9823,9 @@ return new Parser; ...@@ -9627,6 +9823,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 +9844,8 @@ return new Parser; ...@@ -9645,7 +9844,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 +9866,7 @@ return new Parser; ...@@ -9666,6 +9866,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 +9877,15 @@ return new Parser; ...@@ -9676,13 +9877,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 +9896,7 @@ return new Parser; ...@@ -9693,6 +9896,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 +9914,20 @@ return new Parser; ...@@ -9710,8 +9914,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 +9936,19 @@ return new Parser; ...@@ -9720,16 +9936,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 +9960,21 @@ return new Parser; ...@@ -9741,8 +9960,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 +9992,12 @@ return new Parser; ...@@ -9760,6 +9992,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 +10011,7 @@ return new Parser; ...@@ -9773,7 +10011,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 +10019,7 @@ return new Parser; ...@@ -9781,7 +10019,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 +10053,7 @@ return new Parser; ...@@ -9815,6 +10053,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 +10064,8 @@ return new Parser; ...@@ -9825,7 +10064,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 +10085,7 @@ return new Parser; ...@@ -9845,6 +10085,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 +10097,8 @@ return new Parser; ...@@ -9856,7 +10097,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 +10108,17 @@ return new Parser; ...@@ -9866,15 +10108,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 +10138,10 @@ return new Parser; ...@@ -9894,10 +10138,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 +10179,6 @@ return new Parser; ...@@ -9935,7 +10179,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 +10190,12 @@ return new Parser; ...@@ -9947,6 +10190,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 +10214,6 @@ return new Parser; ...@@ -9965,11 +10214,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 +10222,46 @@ return new Parser; ...@@ -9978,30 +10222,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 +10272,6 @@ return new Parser; ...@@ -10012,7 +10272,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 +10284,10 @@ return new Parser; ...@@ -10025,7 +10284,10 @@ return new Parser;
source, destination, id, source, destination, id,
conflict_force, conflict_revert, conflict_force, conflict_revert,
conflict_ignore, conflict_ignore,
report,
options) { options) {
// No need to check twice
skip_document_dict[id] = null;
var from_local = options.from_local; var from_local = options.from_local;
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -10056,21 +10318,16 @@ return new Parser; ...@@ -10056,21 +10318,16 @@ return new Parser;
remote_hash = remote_list[1]; remote_hash = remote_list[1];
if (local_hash === remote_hash) { if (local_hash === remote_hash) {
// Same modifications on both side // Same modifications on both side
report.log(id, LOG_FALSE_CONFLICT);
if (local_hash === null) { if (local_hash === null) {
// Deleted on both side, drop signature // Deleted on both side, drop signature
return context._signature_sub_storage.remove(id) return context._signature_sub_storage.remove(id);
.push(function () {
skip_document_dict[id] = null;
});
} }
return context._signature_sub_storage.put(id, { return context._signature_sub_storage.put(id, {
hash: local_hash, hash: local_hash,
from_local: from_local from_local: from_local
}) });
.push(function () {
skip_document_dict[id] = null;
});
} }
if ((remote_hash === status_hash) || (conflict_force === true)) { if ((remote_hash === status_hash) || (conflict_force === true)) {
...@@ -10078,14 +10335,19 @@ return new Parser; ...@@ -10078,14 +10335,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 +10357,7 @@ return new Parser; ...@@ -10095,6 +10357,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 +10365,11 @@ return new Parser; ...@@ -10102,8 +10365,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 +10380,11 @@ return new Parser; ...@@ -10114,9 +10380,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 +10396,17 @@ return new Parser; ...@@ -10128,17 +10396,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 +10415,7 @@ return new Parser; ...@@ -10147,7 +10415,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 +10429,7 @@ return new Parser; ...@@ -10161,7 +10429,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 +10440,7 @@ return new Parser; ...@@ -10172,7 +10440,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 +10464,20 @@ return new Parser; ...@@ -10196,15 +10464,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 +10558,19 @@ return new Parser; ...@@ -10285,7 +10558,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 +10597,11 @@ return new Parser; ...@@ -10312,8 +10597,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 +10621,11 @@ return new Parser; ...@@ -10333,11 +10621,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 +10637,8 @@ return new Parser; ...@@ -10349,7 +10637,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();
return new RSVP.Queue() return new RSVP.Queue()
.push(function () { .push(function () {
...@@ -10416,7 +10705,7 @@ return new Parser; ...@@ -10416,7 +10705,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 +10736,8 @@ return new Parser; ...@@ -10447,7 +10736,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 +10778,10 @@ return new Parser; ...@@ -10488,9 +10778,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 +10793,12 @@ return new Parser; ...@@ -10502,6 +10793,12 @@ return new Parser;
); );
}); });
} }
})
.push(function () {
if (report.has_error) {
throw report;
}
return report;
}); });
}; };
...@@ -14201,8 +14498,7 @@ return new Parser; ...@@ -14201,8 +14498,7 @@ return new Parser;
store.createIndex("_id", "_id", {unique: false}); store.createIndex("_id", "_id", {unique: false});
} }
function openIndexedDB(jio_storage) { function waitForOpenIndexedDB(db_name, callback) {
var db_name = jio_storage._database_name;
function resolver(resolve, reject) { function resolver(resolve, reject) {
// Open DB // // Open DB //
var request = indexedDB.open(db_name); var request = indexedDB.open(db_name);
...@@ -14244,57 +14540,99 @@ return new Parser; ...@@ -14244,57 +14540,99 @@ return new Parser;
}; };
request.onsuccess = function () { request.onsuccess = function () {
resolve(request.result); return new RSVP.Queue()
.push(function () {
return callback(request.result);
})
.push(function (result) {
request.result.close();
resolve(result);
}, function (error) {
request.result.close();
reject(error);
});
}; };
} }
// XXX Canceller???
return new RSVP.Queue() return new RSVP.Promise(resolver);
.push(function () {
return new RSVP.Promise(resolver);
});
} }
function openTransaction(db, stores, flag, autoclosedb) { function waitForTransaction(db, stores, flag, callback) {
var tx = db.transaction(stores, flag); var tx = db.transaction(stores, flag);
if (autoclosedb !== false) { function canceller() {
try {
tx.abort();
} catch (unused) {
// Transaction already finished
return;
}
}
function resolver(resolve, reject) {
var result;
try {
result = callback(tx);
} catch (error) {
reject(error);
}
tx.oncomplete = function () { tx.oncomplete = function () {
db.close(); return new RSVP.Queue()
.push(function () {
return result;
})
.push(resolve, function (error) {
canceller();
reject(error);
});
};
tx.onerror = function (error) {
canceller();
reject(error);
};
tx.onabort = function (evt) {
reject(evt.target);
}; };
return tx;
} }
tx.onabort = function () { return new RSVP.Promise(resolver, canceller);
db.close();
};
return tx;
} }
function handleCursor(request, callback, resolve, reject) { function waitForIDBRequest(request) {
request.onerror = function (error) { return new RSVP.Promise(function (resolve, reject) {
if (request.transaction) { request.onerror = reject;
request.transaction.abort(); request.onsuccess = resolve;
} });
reject(error); }
};
request.onsuccess = function (evt) { function waitForAllSynchronousCursor(request, callback) {
var cursor = evt.target.result; var force_cancellation = false;
if (cursor) {
// XXX Wait for result
try {
callback(cursor);
} catch (error) {
reject(error);
}
// continue to next iteration function canceller() {
cursor["continue"](); force_cancellation = true;
} else { }
resolve();
} function resolver(resolve, reject) {
}; request.onerror = reject;
request.onsuccess = function (evt) {
var cursor = evt.target.result;
if (cursor && !force_cancellation) {
try {
callback(cursor);
} catch (error) {
reject(error);
}
// continue to next iteration
cursor["continue"]();
} else {
resolve();
}
};
}
return new RSVP.Promise(resolver, canceller);
} }
IndexedDBStorage.prototype.buildQuery = function (options) { IndexedDBStorage.prototype.buildQuery = function (options) {
var result_list = []; var result_list = [],
context = this;
function pushIncludedMetadata(cursor) { function pushIncludedMetadata(cursor) {
result_list.push({ result_list.push({
...@@ -14310,17 +14648,23 @@ return new Parser; ...@@ -14310,17 +14648,23 @@ return new Parser;
"value": {} "value": {}
}); });
} }
return openIndexedDB(this)
.push(function (db) { return new RSVP.Queue()
return new RSVP.Promise(function (resolve, reject) { .push(function () {
var tx = openTransaction(db, ["metadata"], "readonly"); return waitForOpenIndexedDB(context._database_name, function (db) {
if (options.include_docs === true) { return waitForTransaction(db, ["metadata"], "readonly",
handleCursor(tx.objectStore("metadata").index("_id").openCursor(), function (tx) {
pushIncludedMetadata, resolve, reject); if (options.include_docs === true) {
} else { return waitForAllSynchronousCursor(
handleCursor(tx.objectStore("metadata").index("_id") tx.objectStore("metadata").index("_id").openCursor(),
.openKeyCursor(), pushMetadata, resolve, reject); pushIncludedMetadata
} );
}
return waitForAllSynchronousCursor(
tx.objectStore("metadata").index("_id").openKeyCursor(),
pushMetadata
);
});
}); });
}) })
.push(function () { .push(function () {
...@@ -14328,263 +14672,313 @@ return new Parser; ...@@ -14328,263 +14672,313 @@ return new Parser;
}); });
}; };
function handleGet(store, id, resolve, reject) {
var request = store.get(id);
request.onerror = reject;
request.onsuccess = function () {
if (request.result) {
resolve(request.result);
} else {
reject(new jIO.util.jIOError(
"IndexedDB: cannot find object '" + id + "' in the '" +
store.name + "' store",
404
));
}
};
}
IndexedDBStorage.prototype.get = function (id) { IndexedDBStorage.prototype.get = function (id) {
return openIndexedDB(this) var context = this;
.push(function (db) { return new RSVP.Queue()
return new RSVP.Promise(function (resolve, reject) { .push(function () {
var transaction = openTransaction(db, ["metadata"], "readonly"); return waitForOpenIndexedDB(context._database_name, function (db) {
handleGet( return waitForTransaction(db, ["metadata"], "readonly",
transaction.objectStore("metadata"), function (tx) {
id, return waitForIDBRequest(tx.objectStore("metadata").get(id));
resolve, });
reject
);
}); });
}) })
.push(function (result) { .push(function (evt) {
return result.doc; if (evt.target.result) {
return evt.target.result.doc;
}
throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" + id + "' in the 'metadata' store",
404
);
}); });
}; };
IndexedDBStorage.prototype.allAttachments = function (id) { IndexedDBStorage.prototype.allAttachments = function (id) {
var attachment_dict = {}; var attachment_dict = {},
context = this;
function addEntry(cursor) { function addEntry(cursor) {
attachment_dict[cursor.value._attachment] = {}; attachment_dict[cursor.primaryKey.slice(cursor.key.length + 1)] = {};
} }
return openIndexedDB(this) return new RSVP.Queue()
.push(function (db) { .push(function () {
return new RSVP.Promise(function (resolve, reject) { return waitForOpenIndexedDB(context._database_name, function (db) {
var transaction = openTransaction(db, ["metadata", "attachment"], return waitForTransaction(db, ["metadata", "attachment"], "readonly",
"readonly"); function (tx) {
function getAttachments() { return RSVP.all([
handleCursor( waitForIDBRequest(tx.objectStore("metadata").get(id)),
transaction.objectStore("attachment").index("_id") waitForAllSynchronousCursor(
.openCursor(IDBKeyRange.only(id)), tx.objectStore("attachment").index("_id")
addEntry, .openKeyCursor(IDBKeyRange.only(id)),
resolve, addEntry
reject )
); ]);
} });
handleGet(
transaction.objectStore("metadata"),
id,
getAttachments,
reject
);
}); });
}) })
.push(function () { .push(function (result_list) {
var evt = result_list[0];
if (!evt.target.result) {
throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" + id +
"' in the 'metadata' store",
404
);
}
return attachment_dict; return attachment_dict;
}); });
}; };
function handleRequest(request, resolve, reject) {
request.onerror = reject;
request.onsuccess = function () {
resolve(request.result);
};
}
IndexedDBStorage.prototype.put = function (id, metadata) { IndexedDBStorage.prototype.put = function (id, metadata) {
return openIndexedDB(this) return waitForOpenIndexedDB(this._database_name, function (db) {
.push(function (db) { return waitForTransaction(db, ["metadata"], "readwrite",
return new RSVP.Promise(function (resolve, reject) { function (tx) {
var transaction = openTransaction(db, ["metadata"], "readwrite"); return waitForIDBRequest(tx.objectStore("metadata").put({
handleRequest( "_id": id,
transaction.objectStore("metadata").put({ "doc": metadata
"_id": id, }));
"doc": metadata
}),
resolve,
reject
);
}); });
}); });
}; };
function deleteEntry(cursor) {
cursor["delete"]();
}
IndexedDBStorage.prototype.remove = function (id) { IndexedDBStorage.prototype.remove = function (id) {
var resolved_amount = 0; return waitForOpenIndexedDB(this._database_name, function (db) {
return openIndexedDB(this) return waitForTransaction(db, ["metadata", "attachment", "blob"],
.push(function (db) { "readwrite", function (tx) {
return new RSVP.Promise(function (resolve, reject) {
function resolver() { var promise_list = [],
if (resolved_amount < 2) { metadata_store = tx.objectStore("metadata"),
resolved_amount += 1; attachment_store = tx.objectStore("attachment"),
} else { blob_store = tx.objectStore("blob");
resolve();
} function deleteAttachment(cursor) {
} promise_list.push(
var transaction = openTransaction(db, ["metadata", "attachment", waitForIDBRequest(attachment_store.delete(cursor.primaryKey))
"blob"], "readwrite");
handleRequest(
transaction.objectStore("metadata")["delete"](id),
resolver,
reject
);
// XXX Why not possible to delete with KeyCursor?
handleCursor(transaction.objectStore("attachment").index("_id")
.openCursor(IDBKeyRange.only(id)),
deleteEntry,
resolver,
reject
); );
handleCursor(transaction.objectStore("blob").index("_id") }
.openCursor(IDBKeyRange.only(id)), function deleteBlob(cursor) {
deleteEntry, promise_list.push(
resolver, waitForIDBRequest(blob_store.delete(cursor.primaryKey))
reject
); );
}
return RSVP.all([
waitForIDBRequest(metadata_store.delete(id)),
waitForAllSynchronousCursor(
attachment_store.index("_id")
.openKeyCursor(IDBKeyRange.only(id)),
deleteAttachment
),
waitForAllSynchronousCursor(
blob_store.index("_id")
.openKeyCursor(IDBKeyRange.only(id)),
deleteBlob
),
])
.then(function () {
return RSVP.all(promise_list);
});
}); });
}); });
}; };
IndexedDBStorage.prototype.getAttachment = function (id, name, options) { IndexedDBStorage.prototype.getAttachment = function (id, name, options) {
var transaction,
type,
start,
end;
if (options === undefined) { if (options === undefined) {
options = {}; options = {};
} }
return openIndexedDB(this) var db_name = this._database_name,
.push(function (db) { start,
return new RSVP.Promise(function (resolve, reject) { end,
transaction = openTransaction( array_buffer_list = [];
db,
["attachment", "blob"], start = options.start || 0;
"readonly" end = options.end;
);
function getBlob(attachment) { // Stream the blob content
var total_length = attachment.info.length, if ((start !== 0) || (end !== undefined)) {
result_list = [],
store = transaction.objectStore("blob"), if (start < 0 || ((end !== undefined) && (end < 0))) {
start_index, throw new jIO.util.jIOError(
end_index; "_start and _end must be positive",
type = attachment.info.content_type; 400
start = options.start || 0; );
end = options.end || total_length; }
if (end > total_length) { if ((end !== undefined) && (start > end)) {
end = total_length; throw new jIO.util.jIOError("_start is greater than _end",
} 400);
if (start < 0 || end < 0) { }
throw new jIO.util.jIOError(
"_start and _end must be positive", return new RSVP.Queue()
400 .push(function () {
); return waitForOpenIndexedDB(db_name, function (db) {
} return waitForTransaction(db, ["blob"], "readonly",
if (start > end) { function (tx) {
throw new jIO.util.jIOError("_start is greater than _end", var key_path = buildKeyPath([id, name]),
400); blob_store = tx.objectStore("blob"),
} start_index,
start_index = Math.floor(start / UNITE); end_index,
end_index = Math.floor(end / UNITE) - 1; promise_list = [];
if (end % UNITE === 0) {
end_index -= 1;
} start_index = Math.floor(start / UNITE);
function resolver(result) { if (end !== undefined) {
if (result.blob !== undefined) { end_index = Math.floor(end / UNITE);
result_list.push(result); if (end % UNITE === 0) {
} end_index -= 1;
resolve(result_list); }
}
function getPart(i) {
return function (result) {
if (result) {
result_list.push(result);
} }
i += 1;
handleGet(store, function getBlobKey(cursor) {
buildKeyPath([id, name, i]), var index = parseInt(
(i <= end_index) ? getPart(i) : resolver, cursor.primaryKey.slice(key_path.length + 1),
reject 10
),
i;
if ((start !== 0) && (index < start_index)) {
// No need to fetch blobs at the start
return;
}
if ((end !== undefined) && (index > end_index)) {
// No need to fetch blobs at the end
return;
}
i = index - start_index;
// Extend array size
while (i > promise_list.length) {
promise_list.push(null);
i -= 1;
}
// Sort the blob by their index
promise_list.splice(
index - start_index,
0,
waitForIDBRequest(blob_store.get(cursor.primaryKey))
); );
}; }
}
getPart(start_index - 1)(); // Get all blob keys to check if they must be fetched
return waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openKeyCursor(IDBKeyRange.only([id, name])),
getBlobKey
)
.then(function () {
return RSVP.all(promise_list);
});
});
});
})
.push(function (result_list) {
// No need to keep the IDB open
var blob,
index,
i;
for (i = 0; i < result_list.length; i += 1) {
array_buffer_list.push(result_list[i].target.result.blob);
} }
// XXX Should raise if key is not good blob = new Blob(array_buffer_list,
handleGet(transaction.objectStore("attachment"), {type: "application/octet-stream"});
buildKeyPath([id, name]), index = Math.floor(start / UNITE) * UNITE;
getBlob, if (end === undefined) {
reject end = blob.length;
); } else {
end = end - index;
}
return blob.slice(start - index, end,
"application/octet-stream");
});
}
// Request the full blob
return new RSVP.Queue()
.push(function () {
return waitForOpenIndexedDB(db_name, function (db) {
return waitForTransaction(db, ["attachment", "blob"], "readonly",
function (tx) {
var key_path = buildKeyPath([id, name]),
attachment_store = tx.objectStore("attachment"),
blob_store = tx.objectStore("blob");
function getBlob(cursor) {
var index = parseInt(
cursor.primaryKey.slice(key_path.length + 1),
10
),
i = index;
// Extend array size
while (i > array_buffer_list.length) {
array_buffer_list.push(null);
i -= 1;
}
// Sort the blob by their index
array_buffer_list.splice(
index,
0,
cursor.value.blob
);
}
return RSVP.all([
// Get the attachment info (mime type)
waitForIDBRequest(attachment_store.get(
key_path
)),
// Get all needed blobs
waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openCursor(IDBKeyRange.only([id, name])),
getBlob
)
]);
});
}); });
}) })
.push(function (result_list) { .push(function (result_list) {
var array_buffer_list = [], // No need to keep the IDB open
blob, var blob,
i, attachment = result_list[0].target.result;
index,
len = result_list.length; // Should raise if key is not good
for (i = 0; i < len; i += 1) { if (!attachment) {
array_buffer_list.push(result_list[i].blob); throw new jIO.util.jIOError(
"IndexedDB: cannot find object '" +
buildKeyPath([id, name]) +
"' in the 'attachment' store",
404
);
} }
if ((options.start === undefined) && (options.end === undefined)) {
return new Blob(array_buffer_list, {type: type}); blob = new Blob(array_buffer_list,
{type: attachment.info.content_type});
if (blob.length !== attachment.info.total_length) {
throw new jIO.util.jIOError(
"IndexedDB: attachment '" +
buildKeyPath([id, name]) +
"' in the 'attachment' store is broken",
500
);
} }
index = Math.floor(start / UNITE) * UNITE; return blob;
blob = new Blob(array_buffer_list, {type: "application/octet-stream"});
return blob.slice(start - index, end - index,
"application/octet-stream");
}); });
}; };
function removeAttachment(transaction, id, name, resolve, reject) {
// XXX How to get the right attachment
function deleteContent() {
handleCursor(
transaction.objectStore("blob").index("_id_attachment")
.openCursor(IDBKeyRange.only([id, name])),
deleteEntry,
resolve,
reject
);
}
handleRequest(
transaction.objectStore("attachment")["delete"](
buildKeyPath([id, name])
),
deleteContent,
reject
);
}
IndexedDBStorage.prototype.putAttachment = function (id, name, blob) { IndexedDBStorage.prototype.putAttachment = function (id, name, blob) {
var blob_part = [], var db_name = this._database_name;
transaction, return new RSVP.Queue()
db; .push(function () {
return openIndexedDB(this)
.push(function (database) {
db = database;
// Split the blob first // Split the blob first
return jIO.util.readBlobAsArrayBuffer(blob); return jIO.util.readBlobAsArrayBuffer(blob);
}) })
.push(function (event) { .push(function (event) {
var array_buffer = event.target.result, var array_buffer = event.target.result,
blob_part = [],
total_size = blob.size, total_size = blob.size,
handled_size = 0; handled_size = 0;
...@@ -14594,57 +14988,102 @@ return new Parser; ...@@ -14594,57 +14988,102 @@ return new Parser;
handled_size += UNITE; handled_size += UNITE;
} }
// Remove previous attachment return waitForOpenIndexedDB(db_name, function (db) {
transaction = openTransaction(db, ["attachment", "blob"], "readwrite"); return waitForTransaction(db, ["attachment", "blob"], "readwrite",
return new RSVP.Promise(function (resolve, reject) { function (tx) {
function write() { var blob_store,
var len = blob_part.length - 1, promise_list,
attachment_store = transaction.objectStore("attachment"), delete_promise_list = [],
blob_store = transaction.objectStore("blob"); key_path = buildKeyPath([id, name]),
function putBlobPart(i) { i;
return function () { // First write the attachment info on top of previous
i += 1; promise_list = [
handleRequest( waitForIDBRequest(tx.objectStore("attachment").put({
blob_store.put({ "_key_path": key_path,
"_id": id,
"_attachment": name,
"info": {
"content_type": blob.type,
"length": blob.size
}
}))
];
// Then, write all blob parts on top of previous
blob_store = tx.objectStore("blob");
for (i = 0; i < blob_part.length; i += 1) {
promise_list.push(
waitForIDBRequest(blob_store.put({
"_key_path": buildKeyPath([id, name, i]), "_key_path": buildKeyPath([id, name, i]),
"_id" : id, "_id" : id,
"_attachment" : name, "_attachment" : name,
"_part" : i, "_part" : i,
"blob": blob_part[i] "blob": blob_part[i]
}), }))
(i < len) ? putBlobPart(i) : resolve,
reject
); );
}; }
}
handleRequest( function deleteEntry(cursor) {
attachment_store.put({ var index = parseInt(
"_key_path": buildKeyPath([id, name]), cursor.primaryKey.slice(key_path.length + 1),
"_id": id, 10
"_attachment": name, );
"info": { if (index >= blob_part.length) {
"content_type": blob.type, delete_promise_list.push(
"length": blob.size waitForIDBRequest(blob_store.delete(cursor.primaryKey))
);
} }
}), }
putBlobPart(-1),
reject // Finally, remove all remaining blobs
); promise_list.push(
} waitForAllSynchronousCursor(
removeAttachment(transaction, id, name, write, reject); blob_store.index("_id_attachment")
.openKeyCursor(IDBKeyRange.only([id, name])),
deleteEntry
)
);
return RSVP.all(promise_list)
.then(function () {
if (delete_promise_list.length) {
return RSVP.all(delete_promise_list);
}
});
});
}); });
}); });
}; };
IndexedDBStorage.prototype.removeAttachment = function (id, name) { IndexedDBStorage.prototype.removeAttachment = function (id, name) {
return openIndexedDB(this) return waitForOpenIndexedDB(this._database_name, function (db) {
.push(function (db) { return waitForTransaction(db, ["attachment", "blob"], "readwrite",
var transaction = openTransaction(db, ["attachment", "blob"], function (tx) {
"readwrite"); var promise_list = [],
return new RSVP.Promise(function (resolve, reject) { attachment_store = tx.objectStore("attachment"),
removeAttachment(transaction, id, name, resolve, reject); blob_store = tx.objectStore("blob");
function deleteEntry(cursor) {
promise_list.push(
waitForIDBRequest(blob_store.delete(cursor.primaryKey))
);
}
return RSVP.all([
waitForIDBRequest(
attachment_store.delete(buildKeyPath([id, name]))
),
waitForAllSynchronousCursor(
blob_store.index("_id_attachment")
.openKeyCursor(IDBKeyRange.only([id, name])),
deleteEntry
)
])
.then(function () {
return RSVP.all(promise_list);
});
}); });
}); });
}; };
jIO.addStorage("indexeddb", IndexedDBStorage); jIO.addStorage("indexeddb", IndexedDBStorage);
......
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