not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

498 lines
12 KiB

  1. define(["sugar-web/bus", "sugar-web/env"], function(bus, env) {
  2. 'use strict';
  3. var datastore = {};
  4. var html5storage = {};
  5. var html5indexedDB = {};
  6. var datastorePrefix = 'sugar_datastore_';
  7. var filestoreName = 'sugar_filestore';
  8. var initialized = false;
  9. var pending = 0;
  10. var waiter = null;
  11. //- Utility function
  12. // Get parameter from query string
  13. datastore.getUrlParameter = function(name) {
  14. var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
  15. return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
  16. };
  17. // Create a uuid
  18. datastore.createUUID = function() {
  19. var s = [];
  20. var hexDigits = "0123456789abcdef";
  21. for (var i = 0; i < 36; i++) {
  22. s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
  23. }
  24. s[14] = "4";
  25. s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
  26. s[8] = s[13] = s[18] = s[23] = "-";
  27. var uuid = s.join("");
  28. return uuid;
  29. };
  30. // Callback checker
  31. datastore.callbackChecker = function(callback) {
  32. if (callback === undefined || callback === null) {
  33. callback = function() {};
  34. }
  35. return callback;
  36. };
  37. // Handle pending requests
  38. function addPending() {
  39. pending++;
  40. }
  41. function removePending() {
  42. if (--pending == 0 && waiter) {
  43. waiter();
  44. waiter = null;
  45. }
  46. }
  47. function waitPending(callback) {
  48. setTimeout(function(){
  49. if (pending == 0) {
  50. if (callback) {
  51. callback()
  52. }
  53. } else {
  54. waiter = callback;
  55. }
  56. }, 50);
  57. }
  58. //- Static Datastore methods
  59. // Create a new datastore entry
  60. datastore.create = function(metadata, callback, text) {
  61. var callback_c = datastore.callbackChecker(callback);
  62. var objectId = datastore.createUUID();
  63. if (text !== undefined) {
  64. metadata["textsize"] = text.length;
  65. }
  66. var sugar_settings = html5storage.getValue("sugar_settings");
  67. if (sugar_settings) {
  68. metadata["buddy_name"] = sugar_settings.name;
  69. metadata["buddy_color"] = sugar_settings.colorvalue;
  70. }
  71. if (html5storage.setValue(datastorePrefix + objectId, {
  72. metadata: metadata,
  73. text: (text === undefined) ? null : { link: objectId }
  74. })) {
  75. if (text !== undefined) {
  76. html5indexedDB.setValue(objectId, text, function(err) {
  77. if (err) {
  78. callback_c(-1, null);
  79. } else {
  80. callback_c(null, objectId);
  81. }
  82. });
  83. } else {
  84. callback_c(null, objectId);
  85. }
  86. } else {
  87. callback_c(-1, null);
  88. }
  89. }
  90. // Find entries matching an activity type
  91. datastore.find = function(id) {
  92. var results = [];
  93. if (!html5storage.test())
  94. return results;
  95. for (var key in html5storage.getAll()) {
  96. if (key.substr(0, datastorePrefix.length) == datastorePrefix) {
  97. var entry = html5storage.getValue(key);
  98. entry.objectId = key.substr(datastorePrefix.length);
  99. if (id === undefined || entry.metadata.activity == id) {
  100. results.push(entry);
  101. }
  102. }
  103. }
  104. return results;
  105. }
  106. // Remove an entry in the datastore
  107. datastore.remove = function(objectId, then) {
  108. html5storage.removeValue(datastorePrefix + objectId);
  109. html5indexedDB.removeValue(objectId, then);
  110. }
  111. // Wait for pending save actions
  112. datastore.waitPendingSave = function(callback) {
  113. waitPending(callback);
  114. };
  115. //- Instance datastore methods
  116. function DatastoreObject(objectId) {
  117. this.objectId = objectId;
  118. this.newMetadata = {};
  119. this.newDataAsText = null;
  120. this.toload = false;
  121. // Init environment from query string values
  122. if (!initialized) {
  123. env.getEnvironment(function() {
  124. initialized = true;
  125. });
  126. }
  127. // Init or create objectId if need
  128. var that = this;
  129. if (this.objectId === undefined) {
  130. var env_objectId = window.top.sugar.environment.objectId;
  131. if (env_objectId != null) {
  132. this.objectId = env_objectId;
  133. this.toload = true;
  134. }
  135. }
  136. }
  137. // Load metadata
  138. DatastoreObject.prototype.getMetadata = function(callback) {
  139. var callback_c = datastore.callbackChecker(callback);
  140. var result = html5storage.getValue(datastorePrefix + this.objectId);
  141. if (result != null) {
  142. this.setMetadata(result.metadata);
  143. this.toload = false;
  144. callback_c(null, result.metadata);
  145. }
  146. };
  147. // Load text
  148. DatastoreObject.prototype.loadAsText = function(callback) {
  149. var callback_c = datastore.callbackChecker(callback);
  150. var that = this;
  151. var result = html5storage.getValue(datastorePrefix + that.objectId);
  152. if (result != null) {
  153. that.setMetadata(result.metadata);
  154. var text = null;
  155. if (result.text) {
  156. html5indexedDB.getValue(that.objectId, function(err, text) {
  157. if (err) {
  158. callback_c(-1, null, null);
  159. } else {
  160. that.setDataAsText(text);
  161. that.toload = false;
  162. callback_c(null, result.metadata, text);
  163. }
  164. });
  165. } else {
  166. that.toload = false;
  167. callback_c(null, result.metadata, text);
  168. }
  169. }
  170. };
  171. // Set metadata
  172. DatastoreObject.prototype.setMetadata = function(metadata) {
  173. for (var key in metadata) {
  174. this.newMetadata[key] = metadata[key];
  175. }
  176. };
  177. // Set text
  178. DatastoreObject.prototype.setDataAsText = function(text) {
  179. this.newDataAsText = text;
  180. };
  181. // Save data
  182. DatastoreObject.prototype.save = function(callback, dontupdatemetadata) {
  183. addPending();
  184. if (this.objectId === undefined) {
  185. var that = this;
  186. this.newMetadata["timestamp"] = this.newMetadata["creation_time"] = new Date().getTime();
  187. this.newMetadata["file_size"] = 0;
  188. datastore.create(this.newMetadata, function(error, oid) {
  189. if (error == null) {
  190. that.objectId = oid;
  191. }
  192. });
  193. } else {
  194. if (this.toload) {
  195. this.getMetadata(null);
  196. this.toload = false;
  197. }
  198. }
  199. var callback_c = datastore.callbackChecker(callback);
  200. if (!dontupdatemetadata) {
  201. this.newMetadata["timestamp"] = new Date().getTime();
  202. }
  203. var sugar_settings = html5storage.getValue("sugar_settings");
  204. if (sugar_settings && !dontupdatemetadata) {
  205. this.newMetadata["buddy_name"] = sugar_settings.name;
  206. this.newMetadata["buddy_color"] = sugar_settings.colorvalue;
  207. }
  208. if (this.newDataAsText != null) {
  209. if (!dontupdatemetadata) {
  210. this.newMetadata["textsize"] = this.newDataAsText.length;
  211. }
  212. }
  213. var that = this;
  214. if (html5storage.setValue(datastorePrefix + this.objectId, {
  215. metadata: this.newMetadata,
  216. text: (this.newDataAsText === undefined) ? null : { link: this.objectId }
  217. })) {
  218. if (this.newDataAsText != null) {
  219. html5indexedDB.setValue(that.objectId, this.newDataAsText, function(err) {
  220. if (err) {
  221. callback_c(-1, null, null);
  222. } else {
  223. callback_c(null, that.newMetadata, that.newDataAsText);
  224. }
  225. removePending();
  226. });
  227. } else {
  228. callback_c(null, this.newMetadata, this.newDataAsText);
  229. removePending();
  230. }
  231. } else {
  232. callback_c(-1, null);
  233. removePending();
  234. }
  235. };
  236. datastore.DatastoreObject = DatastoreObject;
  237. datastore.localStorage = html5storage;
  238. datastore.indexedDB = html5indexedDB;
  239. // -- HTML5 local storage handling
  240. // Load storage - Need for Chrome App
  241. html5storage.load = function(then) {
  242. if (then) {
  243. then();
  244. }
  245. };
  246. html5storage.load();
  247. // Test if HTML5 storage is available
  248. html5storage.test = function() {
  249. return (typeof(Storage) !== "undefined" && typeof(window.localStorage) !== "undefined");
  250. };
  251. // Set a value in the storage
  252. html5storage.setValue = function(key, value) {
  253. if (this.test()) {
  254. try {
  255. window.localStorage.setItem(key, JSON.stringify(value));
  256. return true;
  257. } catch (err) {
  258. console.log("ERROR: Unable to update local storage");
  259. return false;
  260. }
  261. }
  262. };
  263. // Get a value in the storage
  264. html5storage.getValue = function(key) {
  265. if (this.test()) {
  266. try {
  267. return JSON.parse(window.localStorage.getItem(key));
  268. } catch (err) {
  269. return null;
  270. }
  271. }
  272. return null;
  273. };
  274. // Remove a value in the storage
  275. html5storage.removeValue = function(key) {
  276. if (this.test()) {
  277. try {
  278. window.localStorage.removeItem(key);
  279. } catch (err) {}
  280. }
  281. };
  282. // Get all values
  283. html5storage.getAll = function() {
  284. if (this.test()) {
  285. try {
  286. return window.localStorage;
  287. } catch (err) {
  288. return null;
  289. }
  290. }
  291. return null;
  292. };
  293. // -- HTML5 IndexedDB handling
  294. html5indexedDB.db = null;
  295. // Test indexedDB support
  296. html5indexedDB.test = function() {
  297. return window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
  298. }
  299. // Load database or create database on first launch
  300. html5indexedDB.load = function(then) {
  301. if (html5indexedDB.db != null) {
  302. then(null);
  303. return;
  304. }
  305. if (!html5indexedDB.test()) {
  306. if (then) {
  307. then(-1);
  308. }
  309. return;
  310. }
  311. var request = window.indexedDB.open(filestoreName, 1);
  312. request.onerror = function(event) {
  313. if (then) {
  314. then(-2);
  315. }
  316. };
  317. request.onsuccess = function(event) {
  318. html5indexedDB.db = request.result;
  319. if (then) {
  320. then(null);
  321. }
  322. };
  323. request.onupgradeneeded = function(event) {
  324. var db = event.target.result;
  325. var objectStore = db.createObjectStore(filestoreName, {keyPath: "objectId"});
  326. objectStore.createIndex("objectId", "objectId", { unique: true });
  327. };
  328. }
  329. // Set a value in the database
  330. function indexedDB_setValue(key, value, then) {
  331. var transaction = html5indexedDB.db.transaction([filestoreName], "readwrite");
  332. var objectStore = transaction.objectStore(filestoreName);
  333. var request = objectStore.put({objectId: key, text: value});
  334. request.onerror = function(event) {
  335. if (then) {
  336. then(request.errorCode);
  337. }
  338. };
  339. request.onsuccess = function(event) {
  340. if (then) {
  341. then(null);
  342. }
  343. };
  344. }
  345. html5indexedDB.setValue = function(key, value, then) {
  346. if (html5indexedDB.db == null) {
  347. html5indexedDB.load(function(err) {
  348. if (err) {
  349. console.log("FATAL ERROR: indexedDB not supported, could be related to use of private mode");
  350. } else {
  351. indexedDB_setValue(key, value, then);
  352. }
  353. });
  354. } else {
  355. indexedDB_setValue(key, value, then);
  356. }
  357. };
  358. // Get a value from the database
  359. function indexedDB_getValue(key, then) {
  360. var transaction = html5indexedDB.db.transaction([filestoreName], "readonly");
  361. var objectStore = transaction.objectStore(filestoreName);
  362. var request = objectStore.get(key);
  363. request.onerror = function(event) {
  364. if (then) {
  365. then(request.errorCode, null);
  366. }
  367. };
  368. request.onsuccess = function(event) {
  369. if (then) {
  370. then(null, request.result?request.result.text:null);
  371. }
  372. };
  373. }
  374. html5indexedDB.getValue = function(key, then) {
  375. if (html5indexedDB.db == null) {
  376. html5indexedDB.load(function(err) {
  377. if (err) {
  378. console.log("FATAL ERROR: indexedDB not supported, could be related to use of private mode");
  379. } else {
  380. indexedDB_getValue(key, then);
  381. }
  382. });
  383. } else {
  384. indexedDB_getValue(key, then);
  385. }
  386. };
  387. // Get all existing keys in the database
  388. function indexedDB_getAll(then) {
  389. var transaction = html5indexedDB.db.transaction([filestoreName], "readonly");
  390. var objectStore = transaction.objectStore(filestoreName);
  391. var request = objectStore.openCursor();
  392. var keys = [];
  393. request.onerror = function(event) {
  394. if (then) {
  395. then(request.errorCode, null);
  396. }
  397. };
  398. request.onsuccess = function(event) {
  399. var cursor = event.target.result;
  400. if (cursor) {
  401. keys.push(cursor.key);
  402. cursor.continue();
  403. } else {
  404. if (then) {
  405. then(null, keys);
  406. }
  407. }
  408. };
  409. }
  410. html5indexedDB.getAll = function(then) {
  411. if (html5indexedDB.db == null) {
  412. html5indexedDB.load(function(err) {
  413. if (err) {
  414. console.log("FATAL ERROR: indexedDB not supported, could be related to use of private mode");
  415. } else {
  416. indexedDB_getAll(then);
  417. }
  418. });
  419. } else {
  420. indexedDB_getAll(then);
  421. }
  422. };
  423. // Remove a value from the database
  424. function indexedDB_removeValue(key, then) {
  425. var transaction = html5indexedDB.db.transaction([filestoreName], "readwrite");
  426. var objectStore = transaction.objectStore(filestoreName);
  427. var request = objectStore.delete(key);
  428. request.onerror = function(event) {
  429. if (then) {
  430. then(request.errorCode);
  431. }
  432. };
  433. request.onsuccess = function(event) {
  434. if (then) {
  435. then(null);
  436. }
  437. };
  438. }
  439. html5indexedDB.removeValue = function(key, then) {
  440. if (html5indexedDB.db == null) {
  441. html5indexedDB.load(function(err) {
  442. if (err) {
  443. console.log("FATAL ERROR: indexedDB not supported, could be related to use of private mode");
  444. } else {
  445. indexedDB_removeValue(key, then);
  446. }
  447. });
  448. } else {
  449. indexedDB_removeValue(key, then);
  450. }
  451. };
  452. return datastore;
  453. });