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; });