Commit c74c465d authored by Xiaowu Zhang's avatar Xiaowu Zhang

merge indexedDB branch

parents 86eaa055 4ed7981c
......@@ -2427,6 +2427,9 @@ function restCommandRejecter(param, args) {
if (arg.columnNumber !== undefined && arg.columnNumber !== null) {
current_priority.columnNumber = arg.columnNumber;
}
if (arg.fileName !== undefined && arg.fileName !== null) {
current_priority.fileName = arg.fileName;
}
if (arg.filename !== undefined && arg.filename !== null) {
current_priority.filename = arg.filename;
}
......@@ -3659,7 +3662,7 @@ function enableRestParamChecker(jio, shared) {
}
});
["getAttachment", "removeAttachment"].forEach(function (method) {
["removeAttachment"].forEach(function (method) {
shared.on(method, function (param) {
if (!checkId(param)) {
checkAttachmentId(param);
......@@ -3667,6 +3670,26 @@ function enableRestParamChecker(jio, shared) {
});
});
["getAttachment"].forEach(function (method) {
shared.on(method, function (param) {
if (param.storage_spec.type !== "indexeddb" &&
param.storage_spec.type !== "dav" &&
(param.kwargs._start !== undefined
|| param.kwargs._end !== undefined)) {
restCommandRejecter(param, [
'bad_request',
'unsupport',
'_start, _end not support'
]);
return false;
}
if (!checkId(param)) {
checkAttachmentId(param);
}
});
});
["check", "repair"].forEach(function (method) {
shared.on(method, function (param) {
if (param.kwargs._id !== undefined) {
......
......@@ -184,8 +184,15 @@
* An ajax object to do the good request according to the auth type
*/
var ajax = {
"none": function (method, type, url, data) {
"none": function (method, type, url, data, start, end) {
var headers = {};
if (start !== undefined) {
if (end !== undefined) {
headers = {"Range" : "bytes=" + start + "-" + end};
} else {
headers = {"Range" : "bytes=" + start + "-"};
}
}
if (method === "PROPFIND") {
headers.Depth = "1";
}
......@@ -197,8 +204,15 @@
"headers": headers
});
},
"basic": function (method, type, url, data, login) {
"basic": function (method, type, url, data, start, end, login) {
var headers = {"Authorization": "Basic " + login};
if (start !== undefined) {
if (end !== undefined) {
headers.Range = "bytes=" + start + "-" + end;
} else {
headers.Range = "bytes=" + start + "-";
}
}
if (method === "PROPFIND") {
headers.Depth = "1";
}
......@@ -253,6 +267,8 @@
this._url + '/' + idsToFileName(param._id, param._attachment) +
"?_=" + Date.now(),
param._blob,
undefined,
undefined,
this._login
);
};
......@@ -263,6 +279,8 @@
"text",
this._url + '/' + idsToFileName(param._id),
null,
undefined,
undefined,
this._login
).then(function (e) {
try {
......@@ -286,6 +304,8 @@
"blob",
this._url + '/' + idsToFileName(param._id, param._attachment),
null,
param._start,
param._end - 1,
this._login
);
};
......@@ -296,6 +316,8 @@
null,
this._url + '/' + idsToFileName(param._id) + "?_=" + Date.now(),
null,
undefined,
undefined,
this._login
);
};
......@@ -307,6 +329,8 @@
this._url + '/' + idsToFileName(param._id, param._attachment) +
"?_=" + Date.now(),
null,
undefined,
undefined,
this._login
);
};
......@@ -317,6 +341,8 @@
"text",
this._url + '/',
null,
undefined,
undefined,
this._login
).then(function (e) {
var i, rows = [], row, responses = new DOMParser().parseFromString(
......@@ -346,7 +372,9 @@
row.id = row.id[0];
}
if (row !== undefined) {
rows[rows.length] = row;
if (row.id !== "") {
rows[rows.length] = row;
}
}
}
return {"target": {"response": {
......@@ -631,7 +659,18 @@
);
}
};
if (param._start < 0 || param._end < 0) {
command.reject(405,
"invalide _start,_end",
"_start and _end must be positive");
return;
}
if (param._start > param._end) {
command.reject(405,
"invalide _start,_end",
"start is great then end");
return;
}
this._get(param).
then(o.getAttachment).
then(o.success, o.reject, o.notifyProgress);
......@@ -833,10 +872,12 @@
}
e.target.response.rows.forEach(function (row) {
requests[requests.length] = that._get({"_id": row.id}).
then(function (e) {
row.doc = e.target.response;
});
if (row.id !== "") {
requests[requests.length] = that._get({"_id": row.id}).
then(function (e) {
row.doc = e.target.response;
});
}
});
o.count = 0;
......
......@@ -13,7 +13,8 @@
*
* {
* "type": "indexeddb",
* "database": <string>
* "database": <string>,
* "unite": <integer> //byte
* }
*
* The database name will be prefixed by "jio:", so if the database property is
......@@ -28,7 +29,7 @@
*/
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global define, module, require, indexedDB, jIO, RSVP */
/*global define, module, require, indexedDB, jIO, RSVP, Blob, Math*/
(function (dependencies, factory) {
"use strict";
......@@ -47,7 +48,6 @@
var Promise = RSVP.Promise, generateUuid = jIO.util.generateUuid;
// XXX doc string
function metadataObjectToString(value) {
var i, l;
if (Array.isArray(value)) {
......@@ -63,7 +63,7 @@
}
/**
* new IndexedDBStorage(description)
* new IndexedDBStorage(description)
*
* Creates a storage object designed for jIO to store documents into
* indexedDB.
......@@ -77,308 +77,445 @@
throw new TypeError("IndexedDBStorage 'database' description property " +
"must be a non-empty string");
}
if (description.unite !== undefined) {
if (description.unite !== parseInt(description.unite, 10)) {
throw new TypeError("IndexedDBStorage 'unite' description property " +
"must be a integer");
}
} else {
description.unite = 2000000;
}
this._database_name = "jio:" + description.database;
this._unite = description.unite;
}
// XXX doc string
function openRequestOnUpgradeNeeded(shared, event) {
if (shared.aborted) { return; }
shared.db = event.target.result;
try {
shared.store = shared.db.createObjectStore("metadata", {
"keyPath": "_id"
//"autoIncrement": true
});
// `createObjectStore` can throw InvalidStateError - open_req.onerror
// and db.onerror won't be called.
shared.store.createIndex("_id", "_id");
// `store.createIndex` can throw an error
shared.db_created = true;
shared.store.transaction.oncomplete = function () {
delete shared.store;
/**
* creat 3 objectStores
* @param {string} the name of the database
*/
function openIndexedDB(db_name) {
var request;
function resolver(resolve, reject) {
// Open DB //
request = indexedDB.open(db_name);
request.onerror = reject;
// Create DB if necessary //
request.onupgradeneeded = function (evt) {
var db = evt.target.result,
store;
store = db.createObjectStore("metadata", {
"keyPath": "_id"
//"autoIncrement": true
});
store.createIndex("_id", "_id");
store = db.createObjectStore("attachment", {
"keyPath": "_id"
//"autoIncrement": true
});
store.createIndex("_id", "_id");
store = db.createObjectStore("blob", {
"keyPath": ["_id", "_attachment", "_part"]
//"autoIncrement": true
});
store.createIndex("_id_attachment_part",
["_id", "_attachment", "_part"]);
};
request.onsuccess = function () {
resolve(request.result);
};
} catch (e) {
shared.reject(e);
shared.db.close();
shared.aborted = true;
}
return new RSVP.Promise(resolver);
}
IndexedDBStorage.prototype.createDBIfNecessary = function () {
var shared = {}, open_req = indexedDB.open(this._database_name);
// No request.abort() is provided so we cannot cancel database creation
return new Promise(function (resolve, reject) {
shared.reject = reject;
open_req.onupgradeneeded =
openRequestOnUpgradeNeeded.bind(open_req, shared);
open_req.onerror = function () {
if (open_req.result) { open_req.result.close(); }
reject(open_req.error);
return openIndexedDB(this._database_name);
};
/**
*put a data into a store object
*@param {ObjectStore} store The objectstore
*@param {Object} metadata The data to put in
*@return a new promise
*/
function putIndexedDBArrayBuffer(store, metadata) {
var request,
resolver;
request = store.put(metadata);
resolver = function (resolve, reject) {
request.onerror = function (e) {
reject(e);
};
open_req.onsuccess = function () {
// *Called at t + 3*
open_req.result.close();
resolve(shared.db_created ? "created" : "no_content");
request.onsuccess = function () {
resolve(metadata);
};
});
};
};
return new RSVP.Promise(resolver);
}
// XXX doc string
IndexedDBStorage.prototype.get = function (command, param) {
var shared = {"connector": this};
new Promise(function (resolve, reject) {
shared.reject = reject;
function putIndexedDB(store, metadata, readData) {
var request,
resolver;
try {
request = store.put(metadata);
resolver = function (resolve, reject) {
request.onerror = function (e) {
reject(e);
};
request.onsuccess = function () {
resolve(metadata);
};
};
return new RSVP.Promise(resolver);
} catch (e) {
return putIndexedDBArrayBuffer(store,
{"_id" : metadata._id,
"_attachment" : metadata._attachment,
"_part" : metadata._part,
"blob": readData});
}
}
// Open DB //
var open_req = indexedDB.open(shared.connector._database_name);
open_req.onerror = function (event) {
reject(event.target.errorCode);
function transactionEnd(transaction) {
var resolver;
resolver = function (resolve, reject) {
transaction.onabort = reject;
transaction.oncomplete = function () {
resolve("end");
};
};
return new RSVP.Promise(resolver);
}
/**
* get a data from a store object
* @param {ObjectStore} store The objectstore
* @param {String} id The data id
* return a new promise
*/
function getIndexedDB(store, id) {
function resolver(resolve, reject) {
var request = store.get(id);
request.onerror = reject;
request.onsuccess = function () {
resolve(request.result);
};
}
return new RSVP.Promise(resolver);
}
// Create DB if necessary //
open_req.onupgradeneeded =
openRequestOnUpgradeNeeded.bind(open_req, shared);
/**
* delete a data of a store object
* @param {ObjectStore} store The objectstore
* @param {String} id The data id
* @return a new promise
*
*/
function removeIndexedDB(store, id) {
function resolver(resolve, reject) {
var request = store["delete"](id);
request.onerror = function (e) {
reject(e);
};
request.onsuccess = function () {
resolve(request.result);
};
}
return new RSVP.Promise(resolver);
}
open_req.onsuccess = function (event) {
if (shared.aborted) { return; }
try {
shared.db = event.target.result;
// Open transaction //
shared.tx = shared.db.transaction("metadata", "readonly");
shared.tx.onerror = function () {
reject(shared.tx.error);
shared.db.close();
};
shared.onCancel = function () {
shared.tx.abort();
shared.db.close();
};
/**
* research an id in a store
* @param {ObjectStore} store The objectstore
* @param {String} id The index id
* @param {var} researchID The data id
* return a new promise
*/
function researchIndexedDB(store, id, researchID) {
function resolver(resolve) {
var index = store.index(researchID);
index.get(id).onsuccess = function (evt) {
resolve({"result" : evt.target.result, "store": store});
};
}
return new RSVP.Promise(resolver);
}
// Get index //
shared.store = shared.tx.objectStore("metadata");
shared.index_request = shared.store.index("_id");
// Get metadata //
shared.index_request.get(param._id).onsuccess = function (event) {
if (shared.aborted) { return; }
if (event.target.result === undefined) {
reject({"status": 404});
return;
}
shared.final_result = {"data": event.target.result};
};
function promiseResearch(transaction, id, table, researchID) {
var store = transaction.objectStore(table);
return researchIndexedDB(store, id, researchID);
}
// Respond to jIO //
shared.tx.oncomplete = function () {
resolve(shared.final_result);
shared.db.close();
};
} catch (e1) {
reject(e1);
shared.db.close();
}
};
}).then(command.success, command.error, command.notify);
};
/**
* put or post a metadata into objectstore:metadata,attachment
* @param {function} open The function to open a basedata
* @param {function} research The function to reserach
* @param {function} ongoing The function to process
* @param {function} end The completed function
* @param {Object} command The JIO command
* @param {Object} metadata The data to put
*/
IndexedDBStorage.prototype._putOrPost =
function (open, research, ongoing, end, command, metadata) {
var jio_storage = this,
transaction,
global_db,
result;
return new RSVP.Queue()
.push(function () {
//open a database
return open(jio_storage._database_name);
})
.push(function (db) {
global_db = db;
transaction = db.transaction(["metadata",
"attachment"], "readwrite");
//research in metadata
return research(transaction, metadata._id, "metadata", "_id");
})
.push(function (researchResult) {
return ongoing(researchResult);
})
.push(function (ongoingResult) {
//research in attachment
result = ongoingResult;
return research(transaction, metadata._id, "attachment", "_id");
})
.push(function (researchResult) {
//create an id in attachment si necessary
if (researchResult.result === undefined) {
return putIndexedDB(researchResult.store, {"_id": metadata._id});
}
})
.push(function () {
return transactionEnd(transaction);
})
.push(function () {
return end(result);
})
.push(undefined, function (error) {
if (global_db !== undefined) {
global_db.close();
}
throw error;
})
.push(command.success, command.error, command.notify);
};
// XXX doc string
IndexedDBStorage.prototype.post = function (command, metadata) {
var shared = {"connector": this};
if (!metadata._id) {
metadata._id = generateUuid();
}
new Promise(function (resolve, reject) {
shared.reject = reject;
// Open DB //
var open_req = indexedDB.open(shared.connector._database_name);
open_req.onerror = function (event) {
reject(event.target.errorCode);
};
// Create DB if necessary //
open_req.onupgradeneeded =
openRequestOnUpgradeNeeded.bind(open_req, shared);
open_req.onsuccess = function (event) {
if (shared.aborted) { return; }
try {
shared.db = event.target.result;
// Open transaction //
shared.tx = shared.db.transaction("metadata", "readwrite");
shared.tx.onerror = function () {
reject(shared.tx.error);
shared.db.close();
};
shared.onCancel = function () {
shared.tx.abort();
shared.db.close();
};
/**
* Retrieve data
*
*@param {Object} command The JIO command
*@param {Object} param The command parameters
*/
IndexedDBStorage.prototype.get = function (command, param) {
var jio_storage = this,
transaction,
global_db,
meta;
return new RSVP.Queue()
.push(function () {
return openIndexedDB(jio_storage._database_name);
})
.push(function (db) {
global_db = db;
transaction = db.transaction(["metadata", "attachment"], "readwrite");
var store = transaction.objectStore("metadata");
return getIndexedDB(store, param._id);
})
.push(function (result) {
if (result) {
//get a part data from metadata
meta = result;
var store = transaction.objectStore("attachment");
return getIndexedDB(store, param._id);
}
throw ({"status": 404, "reason": "Not Found",
"message": "IndexeddbStorage, unable to get document."});
})
.push(function (result) {
//get the reste data from attachment
if (result._attachment) {
meta._attachment = result._attachment;
}
return transactionEnd(transaction);
})
.push(function () {
return ({"data": meta});
})
.push(undefined, function (error) {
if (global_db !== undefined) {
global_db.close();
}
throw error;
})
.push(command.success, command.error, command.notify);
};
// Get index //
shared.store = shared.tx.objectStore("metadata");
shared.index_request = shared.store.index("_id");
// Get metadata //
shared.index_request.get(metadata._id).onsuccess = function (event) {
if (shared.aborted) { return; }
if (event.target.result !== undefined) {
shared.db.close();
reject({"status": 409, "reason": "document already exist"});
return;
}
delete metadata._attachments;
// Push metadata //
shared.store.put(metadata);
};
shared.tx.oncomplete = function () {
// Respond to jIO //
shared.db.close();
resolve({"id": metadata._id});
};
} catch (e1) {
reject(e1);
shared.db.close();
/**
* Remove a document
*
* @param {Object} command The JIO command
* @param {Object} param The command parameters
*/
IndexedDBStorage.prototype.remove = function (command, param) {
var jio_storage = this,
transaction,
global_db,
queue = new RSVP.Queue();
function removeAllPart(store, attachment, part, totalLength) {
if (part * jio_storage._unite >= totalLength) {
return;
}
return removeIndexedDB(store, [param._id, attachment, part])
.then(function () {
return removeAllPart(store, attachment, part + 1, totalLength);
});
}
function removeAll(store, array, index, allAttachment) {
var totalLength = allAttachment[array[index]].length;
return removeAllPart(store, array[index], 0, totalLength)
.then(function () {
if (index < array.length - 1) {
return removeAll(store, array, index + 1, allAttachment);
}
});
}
return queue.push(function () {
return openIndexedDB(jio_storage._database_name);
})
.push(function (db) {
global_db = db;
transaction = db.transaction(["metadata",
"attachment", "blob"], "readwrite");
return promiseResearch(transaction, param._id, "metadata", "_id");
})
.push(function (resultResearch) {
if (resultResearch.result === undefined) {
throw ({"status": 404, "reason": "Not Found",
"message": "IndexeddbStorage, unable to get metadata."});
}
};
}).then(command.success, command.error, command.notify);
//delete metadata
return removeIndexedDB(resultResearch.store, param._id);
})
.push(function () {
var store = transaction.objectStore("attachment");
return getIndexedDB(store, param._id);
})
.push(function (result) {
if (result._attachment) {
var array, store;
array = Object.keys(result._attachment);
store = transaction.objectStore("blob");
return removeAll(store, array, 0, result._attachment);
}
})
.push(function () {
var store = transaction.objectStore("attachment");
//delete attachment
return removeIndexedDB(store, param._id);
})
.push(function () {
return transactionEnd(transaction);
})
.push(function () {
return ({"status": 204});
})
.push(undefined, function (error) {
if (global_db !== undefined) {
global_db.close();
}
throw error;
})
.push(command.success, command.error, command.notify);
};
// XXX doc string
IndexedDBStorage.prototype.put = function (command, metadata) {
var shared = {"connector": this};
new Promise(function (resolve, reject) {
shared.reject = reject;
// Open DB //
var open_req = indexedDB.open(shared.connector._database_name);
open_req.onerror = function (event) {
reject(event.target.errorCode);
};
// Create DB if necessary //
open_req.onupgradeneeded =
openRequestOnUpgradeNeeded.bind(open_req, shared);
/**
* Creates a new document if not already existes
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to put
*/
IndexedDBStorage.prototype.post = function (command, metadata) {
var that = this;
if (!metadata._id) {
metadata._id = generateUuid();
}
function promiseOngoingPost(researchResult) {
if (researchResult.result === undefined) {
delete metadata._attachment;
return putIndexedDB(researchResult.store, metadata);
}
throw ({"status": 409, "reason": "Document exists"});
}
open_req.onsuccess = function (event) {
if (shared.aborted) { return; }
try {
shared.db = event.target.result;
// Open transaction //
shared.tx = shared.db.transaction("metadata", "readwrite");
shared.tx.onerror = function () {
reject(shared.tx.error);
shared.db.close();
};
shared.onCancel = function () {
shared.tx.abort();
shared.db.close();
};
function promiseEndPost(metadata) {
return ({"id": metadata._id});
}
// Get index //
shared.store = shared.tx.objectStore("metadata");
shared.index = shared.store.index("_id");
// Get metadata //
shared.index.get(metadata._id).onsuccess = function (event) {
if (shared.aborted) { return; }
var i, l, key, array, data;
if (event.target.result !== undefined) {
shared.found = true;
data = event.target.result;
// Update metadata //
array = Object.keys(metadata);
for (i = 0, l = array.length; i < l; i += 1) {
key = array[i];
metadata[key] = metadataObjectToString(metadata[key]);
}
if (data._attachments) {
metadata._attachments = data._attachments;
}
} else {
delete metadata._attachments;
}
// Push metadata //
shared.store.put(metadata);
};
return that._putOrPost(openIndexedDB, promiseResearch,
promiseOngoingPost, promiseEndPost,
command, metadata);
shared.tx.oncomplete = function () {
// Respond to jIO //
shared.db.close();
resolve({"status": shared.found ? 204 : 201});
};
} catch (e1) {
reject(e1);
shared.db.close();
}
};
}).then(command.success, command.error, command.notify);
};
/**
* Creates or updates a document
* @param {Object} command The JIO command
* @param {Object} metadata The metadata to post
*/
IndexedDBStorage.prototype.put = function (command, metadata) {
var that = this,
found;
function promiseOngoingPut(researchResult) {
var key;
for (key in metadata) {
if (metadata.hasOwnProperty(key)) {
metadata[key] = metadataObjectToString(metadata[key]);
}
}
delete metadata._attachment;
if (researchResult.result !== undefined) {
found = true;
}
return putIndexedDB(researchResult.store, metadata);
}
// XXX doc string
IndexedDBStorage.prototype.remove = function (command, param) {
var shared = {"connector": this};
new Promise(function (resolve, reject) {
shared.reject = reject;
// Open DB //
var open_req = indexedDB.open(shared.connector._database_name);
open_req.onerror = function (event) {
reject(event.target.errorCode);
};
function promiseEndPut() {
return {"status": (found ? 204 : 201) };
}
return that._putOrPost(openIndexedDB, promiseResearch,
promiseOngoingPut, promiseEndPut,
command, metadata);
// Create DB if necessary //
open_req.onupgradeneeded =
openRequestOnUpgradeNeeded.bind(open_req, shared);
};
open_req.onsuccess = function (event) {
if (shared.aborted) { return; }
try {
shared.db = event.target.result;
// Open transaction //
shared.tx = shared.db.transaction("metadata", "readwrite");
shared.tx.onerror = function () {
reject(shared.tx.error);
shared.db.close();
};
shared.onCancel = function () {
shared.tx.abort();
shared.db.close();
};
// Get index //
shared.store = shared.tx.objectStore("metadata");
shared.index = shared.store.index("_id");
// Get metadata //
shared.index.get(param._id).onsuccess = function (event) {
if (shared.aborted) { return; }
if (event.target.result === undefined) {
shared.db.close();
reject({"status": 404});
return;
}
// Delete metadata //
shared.store["delete"](param._id);
// XXX Delete attachments //
};
shared.tx.oncomplete = function () {
// Respond to jIO //
shared.db.close();
resolve();
};
} catch (e1) {
reject(e1);
shared.db.close();
}
};
}).then(command.success, command.error, command.notify);
};
// XXX doc string
IndexedDBStorage.prototype.getList = function (option) {
/**
* Retrieve a list of present document
*
* @method allDocs
* @param {Object} command The JIO command
* @param {Object} param The command parameters
* @param {Object} options The command options
* @param {Boolean} [options.include_docs=false]
* Also retrieve the actual document content.
*/
IndexedDBStorage.prototype.getListMetadata = function (option) {
var rows = [], onCancel, open_req = indexedDB.open(this._database_name);
return new Promise(function (resolve, reject, notify) {
open_req.onerror = function () {
......@@ -386,30 +523,25 @@
reject(open_req.error);
};
open_req.onsuccess = function () {
var tx, store, index, date, index_req, db = open_req.result;
var tx, date, j = 0, index_req, db = open_req.result;
try {
tx = db.transaction("metadata", "readonly");
tx = db.transaction(["metadata", "attachment"], "readonly");
onCancel = function () {
tx.abort();
db.close();
};
store = tx.objectStore("metadata");
index = store.index("_id");
index_req = index.openCursor();
index_req = tx.objectStore("metadata").index("_id").openCursor();
date = Date.now();
index_req.onsuccess = function (event) {
var cursor = event.target.result, now, value, i, key;
if (cursor) {
// Called for each matching record.
// Called for each matching record
// notification management
now = Date.now();
if (date <= now - 1000) {
notify({"loaded": rows.length});
date = now;
}
// option.limit management
if (Array.isArray(option.limit)) {
if (option.limit.length > 1) {
......@@ -433,7 +565,6 @@
option.limit[0] -= 1;
}
}
value = {};
// option.select_list management
if (option.select_list) {
......@@ -442,7 +573,6 @@
value[key] = cursor.value[key];
}
}
// option.include_docs management
if (option.include_docs) {
rows.push({
......@@ -456,14 +586,37 @@
"value": value
});
}
// continue to next iteration
cursor["continue"]();
} else {
notify({"loaded": rows.length});
// No more matching records.
resolve({"data": {"rows": rows, "total_rows": rows.length}});
db.close();
index_req = tx.objectStore("attachment").
index("_id").openCursor();
index_req.onsuccess = function (event) {
//second table
cursor = event.target.result;
if (cursor) {
value = {};
if (cursor.value._attachment) {
if (option.select_list) {
for (i = 0; i < option.select_list.length; i += 1) {
key = option.select_list[i];
value[key] = cursor.value._attachment[key];
}
}
//add info of attachment into metadata
rows[j].value._attachment = value;
if (option.include_docs) {
rows[j].doc._attachment = cursor.value._attachment;
}
}
j += 1;
cursor["continue"]();
} else {
notify({"loaded": rows.length});
resolve({"data": {"rows": rows, "total_rows": rows.length}});
db.close();
}
};
}
};
} catch (e) {
......@@ -478,10 +631,254 @@
});
};
/**
* Add an attachment to a document
*
* @param {Object} command The JIO command
* @param {Object} metadata The data
*
*/
IndexedDBStorage.prototype.putAttachment = function (command, metadata) {
var jio_storage = this,
transaction,
global_db,
BlobInfo,
readResult;
function putAllPart(store, metadata, readResult, count, part) {
var blob,
readPart,
end;
if (count >= metadata._blob.size) {
return;
}
end = count + jio_storage._unite;
blob = metadata._blob.slice(count, end);
readPart = readResult.slice(count, end);
return putIndexedDB(store, {"_id": metadata._id,
"_attachment" : metadata._attachment,
"_part" : part,
"blob": blob}, readPart)
.then(function () {
return putAllPart(store, metadata, readResult, end, part + 1);
});
}
return jIO.util.readBlobAsArrayBuffer(metadata._blob)
.then(function (event) {
readResult = event.target.result;
BlobInfo = {
"content_type": metadata._blob.type,
"length": metadata._blob.size
};
return new RSVP.Queue()
.push(function () {
return openIndexedDB(jio_storage._database_name);
})
.push(function (db) {
global_db = db;
transaction = db.transaction(["attachment",
"blob"], "readwrite");
return promiseResearch(transaction,
metadata._id, "attachment", "_id");
})
.push(function (researchResult) {
if (researchResult.result === undefined) {
throw ({"status": 404, "reason": "Not Found",
"message": "indexeddbStorage unable to put attachment"});
}
//update attachment
researchResult.result._attachment = researchResult.
result._attachment || {};
researchResult.result._attachment[metadata._attachment] =
(BlobInfo === undefined) ? "BlobInfo" : BlobInfo;
return putIndexedDB(researchResult.store, researchResult.result);
})
.push(function () {
//put in blob
var store = transaction.objectStore("blob");
return putAllPart(store, metadata, readResult, 0, 0);
})
.push(function () {
return transactionEnd(transaction);
})
.push(function () {
return {"status": 204};
})
.push(undefined, function (error) {
if (global_db !== undefined) {
global_db.close();
}
throw error;
})
.push(command.success, command.error, command.notify);
});
};
/**
* Retriev a document attachment
*
* @param {Object} command The JIO command
* @param {Object} param The command parameter
*/
IndexedDBStorage.prototype.getAttachment = function (command, param) {
var jio_storage = this,
transaction,
global_db,
blob,
totalLength;
function getDesirePart(store, start, end) {
if (start > end) {
return;
}
return getIndexedDB(store, [param._id, param._attachment, start])
.then(function (result) {
var blobPart = result.blob;
if (result.blob.byteLength !== undefined) {
blobPart = new Blob([result.blob]);
}
if (blob) {
blob = new Blob([blob, blobPart]);
} else {
blob = blobPart;
}
return getDesirePart(store, start + 1, end);
});
}
return new RSVP.Queue()
.push(function () {
return openIndexedDB(jio_storage._database_name);
})
.push(function (db) {
global_db = db;
transaction = db.transaction(["attachment", "blob"], "readwrite");
//check if the attachment exists
return promiseResearch(transaction,
param._id, "attachment", "_id");
})
.push(function (researchResult) {
var result = researchResult.result,
start,
end;
if (result === undefined ||
result._attachment[param._attachment] === undefined) {
throw ({"status": 404, "reason": "missing attachment",
"message": "IndexeddbStorage, unable to get attachment."});
}
totalLength = result._attachment[param._attachment].length;
param._start = param._start === undefined ? 0 : param._start;
param._end = param._end === undefined ? totalLength
: param._end;
if (param._end > totalLength) {
param._end = totalLength;
}
if (param._start < 0 || param._end < 0) {
throw ({"status": 404, "reason": "invalide _start, _end",
"message": "_start and _end must be positive"});
}
if (param._start > param._end) {
throw ({"status": 404, "reason": "invalide offset",
"message": "start is great then end"});
}
start = Math.floor(param._start / jio_storage._unite);
end = Math.floor(param._end / jio_storage._unite);
if (param._end % jio_storage._unite === 0) {
end -= 1;
}
return getDesirePart(transaction.objectStore("blob"),
start,
end);
})
.push(function () {
var start = param._start % jio_storage._unite,
end = start + param._end - param._start;
blob = blob.slice(start, end);
return ({ "data": new Blob([blob], {type: "text/plain"})});
})
.push(undefined, function (error) {
// Check if transaction is ongoing, if so, abort it
if (transaction !== undefined) {
transaction.abort();
}
if (global_db !== undefined) {
global_db.close();
}
throw error;
})
.push(command.success, command.error, command.notify);
};
/**
* Remove an attachment
*
* @method removeAttachment
* @param {Object} command The JIO command
* @param {Object} param The command parameters
*/
IndexedDBStorage.prototype.removeAttachment = function (command, param) {
var jio_storage = this,
transaction,
global_db,
totalLength;
function removePart(store, part) {
if (part * jio_storage._unite >= totalLength) {
return;
}
return removeIndexedDB(store, [param._id, param._attachment, part])
.then(function () {
return removePart(store, part + 1);
});
}
return new RSVP.Queue()
.push(function () {
return openIndexedDB(jio_storage._database_name);
})
.push(function (db) {
global_db = db;
transaction = db.transaction(["attachment", "blob"], "readwrite");
//check if the attachment exists
return promiseResearch(transaction, param._id,
"attachment", "_id");
})
.push(function (researchResult) {
var result = researchResult.result;
if (result === undefined ||
result._attachment[param._attachment] === undefined) {
throw ({"status": 404, "reason": "missing attachment",
"message":
"IndexeddbStorage, document attachment not found."});
}
totalLength = result._attachment[param._attachment].length;
//updata attachment
delete result._attachment[param._attachment];
return putIndexedDB(researchResult.store, result);
})
.push(function () {
var store = transaction.objectStore("blob");
return removePart(store, 0);
})
.push(function () {
return transactionEnd(transaction);
})
.push(function () {
return ({ "status": 204 });
})
.push(undefined, function (error) {
if (global_db !== undefined) {
global_db.close();
}
throw error;
})
.push(command.success, command.error, command.notify);
};
IndexedDBStorage.prototype.allDocs = function (command, param, option) {
/*jslint unparam: true */
this.createDBIfNecessary().
then(this.getList.bind(this, option)).
then(this.getListMetadata.bind(this, option)).
then(command.success, command.error, command.notify);
};
......@@ -494,5 +891,4 @@
};
jIO.addStorage("indexeddb", IndexedDBStorage);
}));
......@@ -96,7 +96,7 @@ function enableRestParamChecker(jio, shared) {
}
});
["getAttachment", "removeAttachment"].forEach(function (method) {
["removeAttachment"].forEach(function (method) {
shared.on(method, function (param) {
if (!checkId(param)) {
checkAttachmentId(param);
......@@ -104,6 +104,26 @@ function enableRestParamChecker(jio, shared) {
});
});
["getAttachment"].forEach(function (method) {
shared.on(method, function (param) {
if (param.storage_spec.type !== "indexeddb" &&
param.storage_spec.type !== "dav" &&
(param.kwargs._start !== undefined
|| param.kwargs._end !== undefined)) {
restCommandRejecter(param, [
'bad_request',
'unsupport',
'_start, _end not support'
]);
return false;
}
if (!checkId(param)) {
checkAttachmentId(param);
}
});
});
["check", "repair"].forEach(function (method) {
shared.on(method, function (param) {
if (param.kwargs._id !== undefined) {
......
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global module, test, stop, start, expect, ok, deepEqual, location, sinon,
davstorage_spec, RSVP, jIO, test_util, dav_storage, btoa, define,
setTimeout, clearTimeout */
setTimeout, clearTimeout, parseInt */
// define([module_name], [dependencies], module);
(function (dependencies, module) {
......@@ -62,7 +62,7 @@
* X-Requested-With, X-HTTP-Method-Override, Accept, Authorization,
* Depth"
*/
test("Scenario", 34, function () {
test("Scenario", 48, function () {
var server, responses = [], shared = {}, jio = jIO.createJIO(spec, {
"workspace": {},
......@@ -94,7 +94,7 @@
jIO.util.ajax = function (param) {
var timeout, xhr = {}, response = responses.shift(), statusTexts = {
"404": "Not Found"
};
}, start, end, str;
if (!Array.isArray(response)) {
setTimeout(function () {
throw new ReferenceError("Fake server, no response set for " +
......@@ -116,6 +116,17 @@
timeout = setTimeout(function () {
/*global Blob*/
if (xhr.responseType === 'blob') {
if (param.headers.Range !== undefined) {
str = param.headers.Range;
start = parseInt(str.substring(str.indexOf("=") + 1,
str.indexOf("-")), 10);
if (str.indexOf("NaN") !== -1) {
response[2] = response[2].substring(start);
} else {
end = parseInt(str.substring(str.indexOf("-") + 1), 10);
response[2] = response[2].substring(start, end + 1);
}
}
xhr.response = new Blob([response[2]], {
"type": response[1]["Content-Type"] ||
response[1]["Content-type"] ||
......@@ -969,6 +980,179 @@
});
}
function getFirstAttachmentRange1() {
responses.push([200, {
"Content-Type": "application/octet-stream"
}, JSON.stringify({
"_id": "a",
"title": "Hoo",
"_attachments": {
"aa": {
"content_type": "text/plain",
"digest": "sha256-38760eabb666e8e61ee628a17c4090cc5" +
"0728e095ff24218119d51bd22475363",
"length": 3
},
"ab": {
"content_type": "text/plain",
"digest": "sha256-e124adcce1fb2f88e1ea799c3d0820845" +
"ed343e6c739e54131fcb3a56e4bc1bd",
"length": 3
}
}
})]); // GET
responses.push([200, {
"Content-Type": "application/octet-stream"
}, "aab"]); // GET
return jio.getAttachment({"_id": "a",
"_attachment": "aa",
"_start": 0});
}
function getFirstAttachmentRangeTest1(answer) {
var blob = answer.data;
answer.data = "<blob>";
deepEqual(answer, {
"attachment": "aa",
"data": "<blob>",
"id": "a",
"digest": "sha256-38760eabb666e8e61ee628a17c4090cc5" +
"0728e095ff24218119d51bd22475363",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get first attachment with range:_start: 0, _end: undefined");
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "aab", "Check blob text content");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getFirstAttachmentRange2() {
responses.push([200, {
"Content-Type": "application/octet-stream"
}, JSON.stringify({
"_id": "a",
"title": "Hoo",
"_attachments": {
"aa": {
"content_type": "text/plain",
"digest": "sha256-38760eabb666e8e61ee628a17c4090cc5" +
"0728e095ff24218119d51bd22475363",
"length": 3
},
"ab": {
"content_type": "text/plain",
"digest": "sha256-e124adcce1fb2f88e1ea799c3d0820845" +
"ed343e6c739e54131fcb3a56e4bc1bd",
"length": 3
}
}
})]); // GET
responses.push([200, {
"Content-Type": "application/octet-stream"
}, "aab"]); // GET
return jio.getAttachment({"_id": "a",
"_attachment": "aa",
"_start": 0,
"_end": 1});
}
function getFirstAttachmentRangeTest2(answer) {
var blob = answer.data;
answer.data = "<blob>";
deepEqual(answer, {
"attachment": "aa",
"data": "<blob>",
"id": "a",
"digest": "sha256-38760eabb666e8e61ee628a17c4090cc5" +
"0728e095ff24218119d51bd22475363",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get first attachment with range: _start:0, _end:1");
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "a", "Check blob text content");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getFirstAttachmentRange3() {
responses.push([200, {
"Content-Type": "application/octet-stream"
}, JSON.stringify({
"_id": "a",
"title": "Hoo",
"_attachments": {
"aa": {
"content_type": "text/plain",
"digest": "sha256-38760eabb666e8e61ee628a17c4090cc5" +
"0728e095ff24218119d51bd22475363",
"length": 3
},
"ab": {
"content_type": "text/plain",
"digest": "sha256-e124adcce1fb2f88e1ea799c3d0820845" +
"ed343e6c739e54131fcb3a56e4bc1bd",
"length": 3
}
}
})]); // GET
responses.push([200, {
"Content-Type": "application/octet-stream"
}, "aab"]); // GET
return jio.getAttachment({"_id": "a",
"_attachment": "aa",
"_start": 1,
"_end": 3});
}
function getFirstAttachmentRangeTest3(answer) {
var blob = answer.data;
answer.data = "<blob>";
deepEqual(answer, {
"attachment": "aa",
"data": "<blob>",
"id": "a",
"digest": "sha256-38760eabb666e8e61ee628a17c4090cc5" +
"0728e095ff24218119d51bd22475363",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get first attachment with range:_start:1, _end:3 ");
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "ab", "Check blob text content");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getSecondAttachment() {
responses.push([200, {
"Content-Type": "application/octet-stream"
......@@ -1009,7 +1193,7 @@
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get first attachment");
}, "Get second attachment");
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "aba", "Check blob text content");
......@@ -1018,6 +1202,100 @@
});
}
function getSecondAttachmentRange1() {
return success(jio.getAttachment({"_id": "a",
"_attachment": "ab",
"_start": -1}));
}
function getSecondAttachmentRangeTest1(answer) {
deepEqual(answer, {
"attachment": "ab",
"error": "method_not_allowed",
"id": "a",
"message": "_start and _end must be positive",
"method": "getAttachment",
"reason": "invalide _start,_end",
"result": "error",
"status": 405,
"statusText": "Method Not Allowed"
}, "Get second attachment with range: _start: -1");
}
function getSecondAttachmentRange2() {
return success(jio.getAttachment({"_id": "a",
"_attachment": "ab",
"_start": 1,
"_end": 0}));
}
function getSecondAttachmentRangeTest2(answer) {
deepEqual(answer, {
"attachment": "ab",
"error": "method_not_allowed",
"id": "a",
"message": "start is great then end",
"method": "getAttachment",
"reason": "invalide _start,_end",
"result": "error",
"status": 405,
"statusText": "Method Not Allowed"
}, "Get second attachment with range: _start: -1");
}
function getSecondAttachmentRange3() {
responses.push([200, {
"Content-Type": "application/octet-stream"
}, JSON.stringify({
"_id": "a",
"title": "Hoo",
"_attachments": {
"aa": {
"content_type": "text/plain",
"digest": "sha256-38760eabb666e8e61ee628a17c4090cc5" +
"0728e095ff24218119d51bd22475363",
"length": 3
},
"ab": {
"content_type": "text/plain",
"digest": "sha256-e124adcce1fb2f88e1ea799c3d0820845" +
"ed343e6c739e54131fcb3a56e4bc1bd",
"length": 3
}
}
})]); // GET
responses.push([200, {
"Content-Type": "application/octet-stream"
}, "aab"]); // GET
return jio.getAttachment({"_id": "a",
"_attachment": "ab",
"_start": 1,
"_end": 2});
}
function getSecondAttachmentRangeTest3(answer) {
var blob = answer.data;
answer.data = "<blob>";
deepEqual(answer, {
"attachment": "ab",
"data": "<blob>",
"id": "a",
"digest": "sha256-e124adcce1fb2f88e1ea799c3d0820845" +
"ed343e6c739e54131fcb3a56e4bc1bd",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get second attachment with range:_start:1, _end:2 ");
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "a", "Check blob text content");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getLastDocument() {
responses.push([200, {
"Content-Type": "application/octet-stream"
......@@ -1348,8 +1626,14 @@
then(updateLastDocument).then(updateLastDocumentTest).
// getA a 200
then(getFirstAttachment).then(getFirstAttachmentTest).
then(getFirstAttachmentRange1).then(getFirstAttachmentRangeTest1).
then(getFirstAttachmentRange2).then(getFirstAttachmentRangeTest2).
then(getFirstAttachmentRange3).then(getFirstAttachmentRangeTest3).
// getA b 200
then(getSecondAttachment).then(getSecondAttachmentTest).
then(getSecondAttachmentRange1).then(getSecondAttachmentRangeTest1).
then(getSecondAttachmentRange2).then(getSecondAttachmentRangeTest2).
then(getSecondAttachmentRange3).then(getSecondAttachmentRangeTest3).
// get 200
then(getLastDocument).then(getLastDocumentTest).
// removeA b 204
......
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global module, test, stop, start, expect, ok, deepEqual, location, sinon,
davstorage_spec, RSVP, jIO, test_util, dav_storage, btoa, define,
setTimeout, clearTimeout, indexedDB */
// define([module_name], [dependencies], module);
(function (dependencies, module) {
"use strict";
if (typeof define === 'function' && define.amd) {
return define(dependencies, module);
}
module(test_util, RSVP, jIO);
}([
'test_util',
'rsvp',
'jio',
'indexeddbstorage',
'qunit'
], function (util, RSVP, jIO) {
"use strict";
module("indexeddbStorage");
function success(promise) {
return new RSVP.Promise(function (resolve, notify) {
promise.then(resolve, resolve, notify);
}, function () {
promise.cancel();
});
}
test("Scenario", 46, function () {
indexedDB.deleteDatabase("jio:test");
var server, shared = {}, jio = jIO.createJIO(
{"type" : "indexeddb",
"database" : "test"
},
{"workspace": {}}
);
stop();
server = {restore: function () {
return;
}};
function postNewDocument() {
return jio.post({"title": "Unique ID"});
}
function postNewDocumentTest(answer) {
var uuid = answer.id;
answer.id = "<uuid>";
deepEqual(answer, {
"id": "<uuid>",
"method": "post",
"result": "success",
"status": 201,
"statusText": "Created"
}, "Post a new document");
ok(util.isUuid(uuid), "New document id should look like " +
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx : " + uuid);
shared.created_document_id = uuid;
}
function getCreatedDocument() {
return jio.get({"_id": shared.created_document_id});
}
function getCreatedDocumentTest(answer) {
deepEqual(answer, {
"data": {
"_id": shared.created_document_id,
"title": "Unique ID"
},
"id": shared.created_document_id,
"method": "get",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get new document");
}
function postSpecificDocument() {
return jio.post({"_id": "b", "title": "Bee"});
}
function postSpecificDocumentTest(answer) {
deepEqual(answer, {
"id": "b",
"method": "post",
"result": "success",
"status": 201,
"statusText": "Created"
}, "Post specific document");
}
function listDocument() {
return jio.allDocs();
}
function list2DocumentsTest(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a) {
return a.id === "b" ? 1 : 0;
});
}
deepEqual(answer, {
"data": {
"total_rows": 2,
"rows": [{
"id": shared.created_document_id,
"value": {}
}, {
"id": "b",
"value": {}
}]
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 2 documents");
}
function listDocumentsWithMetadata() {
return jio.allDocs({"include_docs": true});
}
function list2DocumentsWithMetadataTest(answer) {
if (answer && answer.data && Array.isArray(answer.data.rows)) {
answer.data.rows.sort(function (a) {
return a.id === "b" ? 1 : 0;
});
}
deepEqual(answer, {
"data": {
"total_rows": 2,
"rows": [{
"id": shared.created_document_id,
"value": {},
"doc": {
"_id": shared.created_document_id,
"title": "Unique ID"
}
}, {
"id": "b",
"value": {},
"doc": {
"_id": "b",
"title": "Bee"
}
}]
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List 2 documents with their metadata");
}
function removeCreatedDocument() {
return jio.remove({"_id": shared.created_document_id});
}
function removeCreatedDocumentTest(answer) {
deepEqual(answer, {
"id": shared.created_document_id,
"method": "remove",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove first document.");
}
function removeSpecificDocument() {
return jio.remove({"_id": "b"});
}
function removeSpecificDocumentTest(answer) {
deepEqual(answer, {
"id": "b",
"method": "remove",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove second document.");
}
function listEmptyStorage() {
return jio.allDocs();
}
function listEmptyStorageTest(answer) {
deepEqual(answer, {
"data": {
"total_rows": 0,
"rows": []
},
"method": "allDocs",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "List empty storage");
}
function putNewDocument() {
return jio.put({"_id": "a", "title": "Hey"});
}
function putNewDocumentTest(answer) {
deepEqual(answer, {
"id": "a",
"method": "put",
"result": "success",
"status": 201,
"statusText": "Created"
}, "Put new document");
}
function getCreatedDocument2() {
return jio.get({"_id": "a"});
}
function getCreatedDocument2Test(answer) {
deepEqual(answer, {
"data": {
"_id": "a",
"title": "Hey"
},
"id": "a",
"method": "get",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get new document");
}
function postSameDocument() {
return success(jio.post({"_id": "a", "title": "Hoo"}));
}
function postSameDocumentTest(answer) {
deepEqual(answer, {
"error": "conflict",
"id": "a",
"message": "Command failed",
"method": "post",
"reason": "Document exists",
"result": "error",
"status": 409,
"statusText": "Conflict"
}, "Unable to post the same document (conflict)");
}
function putAttachmentToNonExistentDocument() {
return success(jio.putAttachment({
"_id": "ahaha",
"_attachment": "aa",
"_data": "aaa",
"_content_type": "text/plain"
}));
}
function putAttachmentToNonExistentDocumentTest(answer) {
deepEqual(answer, {
"attachment": "aa",
"error": "not_found",
"id": "ahaha",
"message": "indexeddbStorage unable to put attachment",
"method": "putAttachment",
"reason": "Not Found",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Put attachment to a non existent document -> 404 Not Found");
}
function createAttachment() {
return jio.putAttachment({
"_id": "a",
"_attachment": "aa",
"_data": "aaa",
"_content_type": "text/plain"
});
}
function createAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "aa",
"id": "a",
"method": "putAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Create new attachment");
}
function updateAttachment() {
return jio.putAttachment({
"_id": "a",
"_attachment": "aa",
"_data": "aab",
"_content_type": "text/plain"
});
}
function updateAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "aa",
"id": "a",
"method": "putAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Update last attachment");
}
function createAnotherAttachment() {
return jio.putAttachment({
"_id": "a",
"_attachment": "ab",
"_data": "aba",
"_content_type": "text/plain"
});
}
function createAnotherAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "ab",
"id": "a",
"method": "putAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Create another attachment");
}
function updateLastDocument() {
return jio.put({"_id": "a", "title": "Hoo"});
}
function updateLastDocumentTest(answer) {
deepEqual(answer, {
"id": "a",
"method": "put",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Update document metadata");
}
function getFirstAttachment() {
return jio.getAttachment({"_id": "a", "_attachment": "aa"});
}
function getFirstAttachmentTest(answer) {
var blob = answer.data;
answer.data = "<blob>";
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "aab", "Check blob text content");
deepEqual(answer, {
"attachment": "aa",
"data": "<blob>",
"id": "a",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get first attachment");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getFirstAttachmentRange1() {
return jio.getAttachment({"_id": "a",
"_attachment": "aa",
"_start": 0});
}
function getFirstAttachmentRangeTest1(answer) {
var blob = answer.data;
answer.data = "<blob>";
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "aab", "Check blob text content");
deepEqual(answer, {
"attachment": "aa",
"data": "<blob>",
"id": "a",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get first attachment with range :_start:0, _end:undefined");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getFirstAttachmentRange2() {
return jio.getAttachment({"_id": "a",
"_attachment": "aa",
"_start": 0,
"_end": 1});
}
function getFirstAttachmentRangeTest2(answer) {
var blob = answer.data;
answer.data = "<blob>";
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "a", "Check blob text content");
deepEqual(answer, {
"attachment": "aa",
"data": "<blob>",
"id": "a",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get first attachment with range :_start:0, _end:1");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getFirstAttachmentRange3() {
return jio.getAttachment({"_id": "a",
"_attachment": "aa",
"_start": 1,
"_end": 3});
}
function getFirstAttachmentRangeTest3(answer) {
var blob = answer.data;
answer.data = "<blob>";
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "ab", "Check blob text content");
deepEqual(answer, {
"attachment": "aa",
"data": "<blob>",
"id": "a",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get first attachment with range :_start:1, _end:3");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getSecondAttachment() {
return jio.getAttachment({"_id": "a", "_attachment": "ab"});
}
function getSecondAttachmentTest(answer) {
var blob = answer.data;
answer.data = "<blob>";
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "aba", "Check blob text content");
deepEqual(answer, {
"attachment": "ab",
"data": "<blob>",
"id": "a",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get second attachment");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getSecondAttachmentRange1() {
return success(jio.getAttachment({"_id": "a",
"_attachment": "ab",
"_start": -1}));
}
function getSecondAttachmentRangeTest1(answer) {
deepEqual(answer, {
"attachment": "ab",
"error": "not_found",
"id": "a",
"message": "_start and _end must be positive",
"method": "getAttachment",
"reason": "invalide _start, _end",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "get attachment with _start or _end negative -> 404 Not Found");
}
function getSecondAttachmentRange2() {
return success(jio.getAttachment({"_id": "a",
"_attachment": "ab",
"_start": 1,
"_end": 0}));
}
function getSecondAttachmentRangeTest2(answer) {
deepEqual(answer, {
"attachment": "ab",
"error": "not_found",
"id": "a",
"message": "start is great then end",
"method": "getAttachment",
"reason": "invalide offset",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "get attachment with _start > _end -> 404 Not Found");
}
function getSecondAttachmentRange3() {
return jio.getAttachment({"_id": "a",
"_attachment": "ab",
"_start": 1,
"_end": 2});
}
function getSecondAttachmentRangeTest3(answer) {
var blob = answer.data;
answer.data = "<blob>";
return jIO.util.readBlobAsText(blob).then(function (e) {
deepEqual(blob.type, "text/plain", "Check blob type");
deepEqual(e.target.result, "b", "Check blob text content");
deepEqual(answer, {
"attachment": "ab",
"data": "<blob>",
"id": "a",
"method": "getAttachment",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get second attachment with range :_start:1, _end:3");
}, function (err) {
deepEqual(err, "no error", "Check blob text content");
});
}
function getLastDocument() {
return jio.get({"_id": "a"});
}
function getLastDocumentTest(answer) {
deepEqual(answer, {
"data": {
"_id": "a",
"title": "Hoo",
"_attachment": {
"aa": {
"content_type": "text/plain",
"length": 3
},
"ab": {
"content_type": "text/plain",
"length": 3
}
}
},
"id": "a",
"method": "get",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get last document metadata");
}
function removeSecondAttachment() {
return jio.removeAttachment({"_id": "a", "_attachment": "ab"});
}
function removeSecondAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "ab",
"id": "a",
"method": "removeAttachment",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove second document");
}
function getInexistentSecondAttachment() {
return success(jio.getAttachment({"_id": "a", "_attachment": "ab"}));
}
function getInexistentSecondAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "ab",
"error": "not_found",
"id": "a",
"message": "IndexeddbStorage, unable to get attachment.",
"method": "getAttachment",
"reason": "missing attachment",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Get inexistent second attachment");
}
function getOneAttachmentDocument() {
return jio.get({"_id": "a"});
}
function getOneAttachmentDocumentTest(answer) {
deepEqual(answer, {
"data": {
"_attachment": {
"aa": {
"content_type": "text/plain",
"length": 3
}
},
"_id": "a",
"title": "Hoo"
},
"id": "a",
"method": "get",
"result": "success",
"status": 200,
"statusText": "Ok"
}, "Get document metadata");
}
function removeSecondAttachmentAgain() {
return success(jio.removeAttachment({"_id": "a", "_attachment": "ab"}));
}
function removeSecondAttachmentAgainTest(answer) {
deepEqual(answer, {
"attachment": "ab",
"error": "not_found",
"id": "a",
"message": "IndexeddbStorage, document attachment not found.",
"method": "removeAttachment",
"reason": "missing attachment",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Remove inexistent attachment");
}
function removeDocument() {
return jio.remove({"_id": "a"});
}
function removeDocumentTest(answer) {
deepEqual(answer, {
"id": "a",
"method": "remove",
"result": "success",
"status": 204,
"statusText": "No Content"
}, "Remove document and its attachments");
}
function getInexistentFirstAttachment() {
return success(jio.getAttachment({"_id": "a", "_attachment": "aa"}));
}
function getInexistentFirstAttachmentTest(answer) {
deepEqual(answer, {
"attachment": "aa",
"error": "not_found",
"id": "a",
"message": "IndexeddbStorage, unable to get attachment.",
"method": "getAttachment",
"reason": "missing attachment",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Get inexistent first attachment");
}
function getInexistentDocument() {
return success(jio.get({"_id": "a"}));
}
function getInexistentDocumentTest(answer) {
deepEqual(answer, {
"error": "not_found",
"id": "a",
"message": "IndexeddbStorage, unable to get document.",
"method": "get",
"reason": "Not Found",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Get inexistent document");
}
function removeInexistentDocument() {
return success(jio.remove({"_id": "a"}));
}
function removeInexistentDocumentTest(answer) {
deepEqual(answer, {
"error": "not_found",
"id": "a",
"message": "IndexeddbStorage, unable to get metadata.",
"method": "remove",
"reason": "Not Found",
"result": "error",
"status": 404,
"statusText": "Not Found"
}, "Remove already removed document");
}
function unexpectedError(error) {
if (error instanceof Error) {
deepEqual([
error.name + ": " + error.message,
error
], "UNEXPECTED ERROR", "Unexpected error");
} else {
deepEqual(error, "UNEXPECTED ERROR", "Unexpected error");
}
}
// # Post new documents, list them and remove them
// post a 201
postNewDocument().then(postNewDocumentTest).
// get 200
then(getCreatedDocument).then(getCreatedDocumentTest).
// post b 201
then(postSpecificDocument).then(postSpecificDocumentTest).
// allD 200 2 documents
then(listDocument).then(list2DocumentsTest).
// allD+include_docs 200 2 documents
then(listDocumentsWithMetadata).then(list2DocumentsWithMetadataTest).
// remove a 204
then(removeCreatedDocument).then(removeCreatedDocumentTest).
// remove b 204
then(removeSpecificDocument).then(removeSpecificDocumentTest).
// allD 200 empty storage
then(listEmptyStorage).then(listEmptyStorageTest).
// # Create and update documents, and some attachment and remove them
// put 201
then(putNewDocument).then(putNewDocumentTest).
// get 200
then(getCreatedDocument2).then(getCreatedDocument2Test).
// post 409
then(postSameDocument).then(postSameDocumentTest).
// putA 404
then(putAttachmentToNonExistentDocument).
then(putAttachmentToNonExistentDocumentTest).
// putA a 204
then(createAttachment).then(createAttachmentTest).
// putA a 204
then(updateAttachment).then(updateAttachmentTest).
// putA b 204
then(createAnotherAttachment).then(createAnotherAttachmentTest).
// put 204
then(updateLastDocument).then(updateLastDocumentTest).
// getA a 200
then(getFirstAttachment).then(getFirstAttachmentTest).
then(getFirstAttachmentRange1).then(getFirstAttachmentRangeTest1).
then(getFirstAttachmentRange2).then(getFirstAttachmentRangeTest2).
then(getFirstAttachmentRange3).then(getFirstAttachmentRangeTest3).
// getA b 200
then(getSecondAttachment).then(getSecondAttachmentTest).
then(getSecondAttachmentRange1).then(getSecondAttachmentRangeTest1).
then(getSecondAttachmentRange2).then(getSecondAttachmentRangeTest2).
then(getSecondAttachmentRange3).then(getSecondAttachmentRangeTest3).
// get 200
then(getLastDocument).then(getLastDocumentTest).
// removeA b 204
then(removeSecondAttachment).then(removeSecondAttachmentTest).
// getA b 404
then(getInexistentSecondAttachment).
then(getInexistentSecondAttachmentTest).
// get 200
then(getOneAttachmentDocument).then(getOneAttachmentDocumentTest).
// removeA b 404
then(removeSecondAttachmentAgain).then(removeSecondAttachmentAgainTest).
// remove 204
then(removeDocument).then(removeDocumentTest).
// getA a 404
then(getInexistentFirstAttachment).then(getInexistentFirstAttachmentTest).
// get 404
then(getInexistentDocument).then(getInexistentDocumentTest).
// remove 404
then(removeInexistentDocument).then(removeInexistentDocumentTest).
// end
fail(unexpectedError).
always(start).
always(function () {
server.restore();
});
});
}));
......@@ -57,7 +57,15 @@
<script src="../src/jio.storage/querystorage.js"></script>
<script src="../test/jio.storage/querystorage.tests.js"></script>
<script src="../src/jio.storage/replicatestorage.js"></script>
<script src="../test/jio.storage/replicatestorage.tests.js"></script>
<!--
<script src="../src/jio.storage/indexeddbstorage.js"></script>
<script src="../test/jio.storage/indexeddbstorage.tests.js"></script>
-->
</body>
</html>
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