define(["sugar-web/bus", "sugar-web/env"], function(bus, env) {
|
|
|
|
'use strict';
|
|
|
|
var datastore = {};
|
|
|
|
var html5storage = {};
|
|
var html5indexedDB = {};
|
|
var datastorePrefix = 'sugar_datastore_';
|
|
var filestoreName = 'sugar_filestore';
|
|
var initialized = false;
|
|
|
|
var pending = 0;
|
|
var waiter = null;
|
|
|
|
//- Utility function
|
|
|
|
// Get parameter from query string
|
|
datastore.getUrlParameter = function(name) {
|
|
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
|
|
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
|
|
};
|
|
|
|
// Create a uuid
|
|
datastore.createUUID = function() {
|
|
var s = [];
|
|
var hexDigits = "0123456789abcdef";
|
|
for (var i = 0; i < 36; i++) {
|
|
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
|
|
}
|
|
s[14] = "4";
|
|
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
|
|
s[8] = s[13] = s[18] = s[23] = "-";
|
|
|
|
var uuid = s.join("");
|
|
return uuid;
|
|
};
|
|
|
|
// Callback checker
|
|
datastore.callbackChecker = function(callback) {
|
|
if (callback === undefined || callback === null) {
|
|
callback = function() {};
|
|
}
|
|
return callback;
|
|
};
|
|
|
|
// Handle pending requests
|
|
function addPending() {
|
|
pending++;
|
|
}
|
|
function removePending() {
|
|
if (--pending == 0 && waiter) {
|
|
waiter();
|
|
waiter = null;
|
|
}
|
|
}
|
|
function waitPending(callback) {
|
|
setTimeout(function(){
|
|
if (pending == 0) {
|
|
if (callback) {
|
|
callback()
|
|
}
|
|
} else {
|
|
waiter = callback;
|
|
}
|
|
}, 50);
|
|
}
|
|
|
|
|
|
//- Static Datastore methods
|
|
|
|
// Create a new datastore entry
|
|
datastore.create = function(metadata, callback, text) {
|
|
var callback_c = datastore.callbackChecker(callback);
|
|
var objectId = datastore.createUUID();
|
|
if (text !== undefined) {
|
|
metadata["textsize"] = text.length;
|
|
}
|
|
var sugar_settings = html5storage.getValue("sugar_settings");
|
|
if (sugar_settings) {
|
|
metadata["buddy_name"] = sugar_settings.name;
|
|
metadata["buddy_color"] = sugar_settings.colorvalue;
|
|
}
|
|
if (html5storage.setValue(datastorePrefix + objectId, {
|
|
metadata: metadata,
|
|
text: (text === undefined) ? null : { link: objectId }
|
|
})) {
|
|
if (text !== undefined) {
|
|
html5indexedDB.setValue(objectId, text, function(err) {
|
|
if (err) {
|
|
callback_c(-1, null);
|
|
} else {
|
|
callback_c(null, objectId);
|
|
}
|
|
});
|
|
} else {
|
|
callback_c(null, objectId);
|
|
}
|
|
} else {
|
|
callback_c(-1, null);
|
|
}
|
|
}
|
|
|
|
// Find entries matching an activity type
|
|
datastore.find = function(id) {
|
|
var results = [];
|
|
if (!html5storage.test())
|
|
return results;
|
|
for (var key in html5storage.getAll()) {
|
|
if (key.substr(0, datastorePrefix.length) == datastorePrefix) {
|
|
var entry = html5storage.getValue(key);
|
|
entry.objectId = key.substr(datastorePrefix.length);
|
|
if (id === undefined || entry.metadata.activity == id) {
|
|
results.push(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
// Remove an entry in the datastore
|
|
datastore.remove = function(objectId, then) {
|
|
html5storage.removeValue(datastorePrefix + objectId);
|
|
html5indexedDB.removeValue(objectId, then);
|
|
}
|
|
|
|
// Wait for pending save actions
|
|
datastore.waitPendingSave = function(callback) {
|
|
waitPending(callback);
|
|
};
|
|
|
|
//- Instance datastore methods
|
|
function DatastoreObject(objectId) {
|
|
this.objectId = objectId;
|
|
this.newMetadata = {};
|
|
this.newDataAsText = null;
|
|
this.toload = false;
|
|
|
|
// Init environment from query string values
|
|
if (!initialized) {
|
|
env.getEnvironment(function() {
|
|
initialized = true;
|
|
});
|
|
}
|
|
|
|
// Init or create objectId if need
|
|
var that = this;
|
|
if (this.objectId === undefined) {
|
|
var env_objectId = window.top.sugar.environment.objectId;
|
|
if (env_objectId != null) {
|
|
this.objectId = env_objectId;
|
|
this.toload = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load metadata
|
|
DatastoreObject.prototype.getMetadata = function(callback) {
|
|
var callback_c = datastore.callbackChecker(callback);
|
|
var result = html5storage.getValue(datastorePrefix + this.objectId);
|
|
if (result != null) {
|
|
this.setMetadata(result.metadata);
|
|
this.toload = false;
|
|
callback_c(null, result.metadata);
|
|
}
|
|
};
|
|
|
|
// Load text
|
|
DatastoreObject.prototype.loadAsText = function(callback) {
|
|
var callback_c = datastore.callbackChecker(callback);
|
|
var that = this;
|
|
var result = html5storage.getValue(datastorePrefix + that.objectId);
|
|
if (result != null) {
|
|
that.setMetadata(result.metadata);
|
|
var text = null;
|
|
if (result.text) {
|
|
html5indexedDB.getValue(that.objectId, function(err, text) {
|
|
if (err) {
|
|
callback_c(-1, null, null);
|
|
} else {
|
|
that.setDataAsText(text);
|
|
that.toload = false;
|
|
callback_c(null, result.metadata, text);
|
|
}
|
|
});
|
|
} else {
|
|
that.toload = false;
|
|
callback_c(null, result.metadata, text);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Set metadata
|
|
DatastoreObject.prototype.setMetadata = function(metadata) {
|
|
for (var key in metadata) {
|
|
this.newMetadata[key] = metadata[key];
|
|
}
|
|
};
|
|
|
|
// Set text
|
|
DatastoreObject.prototype.setDataAsText = function(text) {
|
|
this.newDataAsText = text;
|
|
};
|
|
|
|
// Save data
|
|
DatastoreObject.prototype.save = function(callback, dontupdatemetadata) {
|
|
addPending();
|
|
if (this.objectId === undefined) {
|
|
var that = this;
|
|
this.newMetadata["timestamp"] = this.newMetadata["creation_time"] = new Date().getTime();
|
|
this.newMetadata["file_size"] = 0;
|
|
datastore.create(this.newMetadata, function(error, oid) {
|
|
if (error == null) {
|
|
that.objectId = oid;
|
|
}
|
|
});
|
|
} else {
|
|
if (this.toload) {
|
|
this.getMetadata(null);
|
|
this.toload = false;
|
|
}
|
|
}
|
|
var callback_c = datastore.callbackChecker(callback);
|
|
if (!dontupdatemetadata) {
|
|
this.newMetadata["timestamp"] = new Date().getTime();
|
|
}
|
|
var sugar_settings = html5storage.getValue("sugar_settings");
|
|
if (sugar_settings && !dontupdatemetadata) {
|
|
this.newMetadata["buddy_name"] = sugar_settings.name;
|
|
this.newMetadata["buddy_color"] = sugar_settings.colorvalue;
|
|
}
|
|
if (this.newDataAsText != null) {
|
|
if (!dontupdatemetadata) {
|
|
this.newMetadata["textsize"] = this.newDataAsText.length;
|
|
}
|
|
}
|
|
var that = this;
|
|
if (html5storage.setValue(datastorePrefix + this.objectId, {
|
|
metadata: this.newMetadata,
|
|
text: (this.newDataAsText === undefined) ? null : { link: this.objectId }
|
|
})) {
|
|
if (this.newDataAsText != null) {
|
|
html5indexedDB.setValue(that.objectId, this.newDataAsText, function(err) {
|
|
if (err) {
|
|
callback_c(-1, null, null);
|
|
} else {
|
|
callback_c(null, that.newMetadata, that.newDataAsText);
|
|
}
|
|
removePending();
|
|
});
|
|
} else {
|
|
callback_c(null, this.newMetadata, this.newDataAsText);
|
|
removePending();
|
|
}
|
|
} else {
|
|
callback_c(-1, null);
|
|
removePending();
|
|
}
|
|
};
|
|
|
|
datastore.DatastoreObject = DatastoreObject;
|
|
|
|
datastore.localStorage = html5storage;
|
|
datastore.indexedDB = html5indexedDB;
|
|
|
|
|
|
// -- HTML5 local storage handling
|
|
|
|
// Load storage - Need for Chrome App
|
|
html5storage.load = function(then) {
|
|
if (then) {
|
|
then();
|
|
}
|
|
};
|
|
html5storage.load();
|
|
|
|
// Test if HTML5 storage is available
|
|
html5storage.test = function() {
|
|
return (typeof(Storage) !== "undefined" && typeof(window.localStorage) !== "undefined");
|
|
};
|
|
|
|
// Set a value in the storage
|
|
html5storage.setValue = function(key, value) {
|
|
if (this.test()) {
|
|
try {
|
|
window.localStorage.setItem(key, JSON.stringify(value));
|
|
return true;
|
|
} catch (err) {
|
|
console.log("ERROR: Unable to update local storage");
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Get a value in the storage
|
|
html5storage.getValue = function(key) {
|
|
if (this.test()) {
|
|
try {
|
|
return JSON.parse(window.localStorage.getItem(key));
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// Remove a value in the storage
|
|
html5storage.removeValue = function(key) {
|
|
if (this.test()) {
|
|
try {
|
|
window.localStorage.removeItem(key);
|
|
} catch (err) {}
|
|
}
|
|
};
|
|
|
|
// Get all values
|
|
html5storage.getAll = function() {
|
|
if (this.test()) {
|
|
try {
|
|
return window.localStorage;
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
|
|
// -- HTML5 IndexedDB handling
|
|
|
|
html5indexedDB.db = null;
|
|
|
|
// Test indexedDB support
|
|
html5indexedDB.test = function() {
|
|
return window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
|
}
|
|
|
|
// Load database or create database on first launch
|
|
html5indexedDB.load = function(then) {
|
|
if (html5indexedDB.db != null) {
|
|
then(null);
|
|
return;
|
|
}
|
|
if (!html5indexedDB.test()) {
|
|
if (then) {
|
|
then(-1);
|
|
}
|
|
return;
|
|
}
|
|
var request = window.indexedDB.open(filestoreName, 1);
|
|
request.onerror = function(event) {
|
|
if (then) {
|
|
then(-2);
|
|
}
|
|
};
|
|
request.onsuccess = function(event) {
|
|
html5indexedDB.db = request.result;
|
|
if (then) {
|
|
then(null);
|
|
}
|
|
};
|
|
request.onupgradeneeded = function(event) {
|
|
var db = event.target.result;
|
|
var objectStore = db.createObjectStore(filestoreName, {keyPath: "objectId"});
|
|
objectStore.createIndex("objectId", "objectId", { unique: true });
|
|
};
|
|
}
|
|
|
|
// Set a value in the database
|
|
function indexedDB_setValue(key, value, then) {
|
|
var transaction = html5indexedDB.db.transaction([filestoreName], "readwrite");
|
|
var objectStore = transaction.objectStore(filestoreName);
|
|
var request = objectStore.put({objectId: key, text: value});
|
|
request.onerror = function(event) {
|
|
if (then) {
|
|
then(request.errorCode);
|
|
}
|
|
};
|
|
request.onsuccess = function(event) {
|
|
if (then) {
|
|
then(null);
|
|
}
|
|
};
|
|
}
|
|
html5indexedDB.setValue = function(key, value, then) {
|
|
if (html5indexedDB.db == null) {
|
|
html5indexedDB.load(function(err) {
|
|
if (err) {
|
|
console.log("FATAL ERROR: indexedDB not supported, could be related to use of private mode");
|
|
} else {
|
|
indexedDB_setValue(key, value, then);
|
|
}
|
|
});
|
|
} else {
|
|
indexedDB_setValue(key, value, then);
|
|
}
|
|
};
|
|
|
|
// Get a value from the database
|
|
function indexedDB_getValue(key, then) {
|
|
var transaction = html5indexedDB.db.transaction([filestoreName], "readonly");
|
|
var objectStore = transaction.objectStore(filestoreName);
|
|
var request = objectStore.get(key);
|
|
request.onerror = function(event) {
|
|
if (then) {
|
|
then(request.errorCode, null);
|
|
}
|
|
};
|
|
request.onsuccess = function(event) {
|
|
if (then) {
|
|
then(null, request.result?request.result.text:null);
|
|
}
|
|
};
|
|
}
|
|
html5indexedDB.getValue = function(key, then) {
|
|
if (html5indexedDB.db == null) {
|
|
html5indexedDB.load(function(err) {
|
|
if (err) {
|
|
console.log("FATAL ERROR: indexedDB not supported, could be related to use of private mode");
|
|
} else {
|
|
indexedDB_getValue(key, then);
|
|
}
|
|
});
|
|
} else {
|
|
indexedDB_getValue(key, then);
|
|
}
|
|
};
|
|
|
|
// Get all existing keys in the database
|
|
function indexedDB_getAll(then) {
|
|
var transaction = html5indexedDB.db.transaction([filestoreName], "readonly");
|
|
var objectStore = transaction.objectStore(filestoreName);
|
|
var request = objectStore.openCursor();
|
|
var keys = [];
|
|
request.onerror = function(event) {
|
|
if (then) {
|
|
then(request.errorCode, null);
|
|
}
|
|
};
|
|
request.onsuccess = function(event) {
|
|
var cursor = event.target.result;
|
|
if (cursor) {
|
|
keys.push(cursor.key);
|
|
cursor.continue();
|
|
} else {
|
|
if (then) {
|
|
then(null, keys);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
html5indexedDB.getAll = function(then) {
|
|
if (html5indexedDB.db == null) {
|
|
html5indexedDB.load(function(err) {
|
|
if (err) {
|
|
console.log("FATAL ERROR: indexedDB not supported, could be related to use of private mode");
|
|
} else {
|
|
indexedDB_getAll(then);
|
|
}
|
|
});
|
|
} else {
|
|
indexedDB_getAll(then);
|
|
}
|
|
};
|
|
|
|
// Remove a value from the database
|
|
function indexedDB_removeValue(key, then) {
|
|
var transaction = html5indexedDB.db.transaction([filestoreName], "readwrite");
|
|
var objectStore = transaction.objectStore(filestoreName);
|
|
var request = objectStore.delete(key);
|
|
request.onerror = function(event) {
|
|
if (then) {
|
|
then(request.errorCode);
|
|
}
|
|
};
|
|
request.onsuccess = function(event) {
|
|
if (then) {
|
|
then(null);
|
|
}
|
|
};
|
|
}
|
|
html5indexedDB.removeValue = function(key, then) {
|
|
if (html5indexedDB.db == null) {
|
|
html5indexedDB.load(function(err) {
|
|
if (err) {
|
|
console.log("FATAL ERROR: indexedDB not supported, could be related to use of private mode");
|
|
} else {
|
|
indexedDB_removeValue(key, then);
|
|
}
|
|
});
|
|
} else {
|
|
indexedDB_removeValue(key, then);
|
|
}
|
|
};
|
|
|
|
return datastore;
|
|
});
|