indexeddb implementation, #15

This commit is contained in:
Mo Bitar
2017-01-11 17:50:47 -06:00
parent 7459df9b5a
commit 3ffc9ee667
9 changed files with 600 additions and 281 deletions

View File

@@ -1,6 +1,11 @@
class BaseCtrl {
constructor($rootScope, modelManager, apiController) {
constructor($rootScope, modelManager, apiController, dbManager) {
apiController.getCurrentUser(function(){});
dbManager.openDatabase(null, function(){
// new database, delete syncToken so that items can be refetched entirely from server
apiController.clearSyncToken();
apiController.sync();
})
}
}

View File

@@ -2,18 +2,21 @@ angular.module('app.frontend')
.controller('HomeCtrl', function ($scope, $rootScope, $timeout, apiController, modelManager) {
$rootScope.bodyClass = "app-body-class";
apiController.loadLocalItems();
apiController.loadLocalItems(function(items){
$scope.$apply();
apiController.sync(null);
// refresh every 30s
setInterval(function () {
apiController.sync(null);
}, 30000);
});
$scope.allTag = new Tag({all: true});
$scope.allTag.title = "All";
$scope.tags = modelManager.tags;
$scope.allTag.notes = modelManager.notes;
apiController.sync(null);
// refresh every 30s
setInterval(function () {
apiController.sync(null);
}, 30000);
/*
Tags Ctrl Callbacks
*/

View File

@@ -20,11 +20,11 @@ angular.module('app.frontend')
}
this.$get = function($rootScope, Restangular, modelManager, ngDialog) {
return new ApiController($rootScope, Restangular, modelManager, ngDialog);
this.$get = function($rootScope, Restangular, modelManager, ngDialog, dbManager) {
return new ApiController($rootScope, Restangular, modelManager, ngDialog, dbManager);
}
function ApiController($rootScope, Restangular, modelManager, ngDialog) {
function ApiController($rootScope, Restangular, modelManager, ngDialog, dbManager) {
this.user = {};
this.syncToken = localStorage.getItem("syncToken");
@@ -213,44 +213,62 @@ angular.module('app.frontend')
}
this.syncWithOptions = function(callback, options = {}) {
var dirtyItems = modelManager.getDirtyItems();
this.writeItemsToLocalStorage(dirtyItems, function(responseItems){
if(!this.isUserSignedIn()) {
// delete anything needing to be deleted
dirtyItems.forEach(function(item){
if(item.deleted) {
modelManager.removeItemLocally(item);
}
}.bind(this))
modelManager.clearDirtyItems();
if(callback) {
callback();
}
}
}.bind(this))
if(!this.isUserSignedIn()) {
if(this.syncOpInProgress) {
// will perform anoter sync after current completes
this.repeatSync = true;
return;
}
this.syncOpInProgress = true;
var allDirtyItems = modelManager.getDirtyItems();
let submitLimit = 100;
var dirtyItems = allDirtyItems.slice(0, submitLimit);
if(dirtyItems.length < allDirtyItems.length) {
// more items left to be synced, repeat
this.repeatSync = true;
} else {
this.repeatSync = false;
}
if(!this.isUserSignedIn()) {
this.writeItemsToLocalStorage(dirtyItems, function(responseItems){
// delete anything needing to be deleted
dirtyItems.forEach(function(item){
if(item.deleted) {
modelManager.removeItemLocally(item);
}
}.bind(this))
modelManager.clearDirtyItems(dirtyItems);
if(callback) {
callback();
}
}.bind(this))
return;
}
var request = Restangular.one("items/sync");
request.limit = 150;
request.sync_token = this.syncToken;
request.cursor_token = this.cursorToken;
request.items = _.map(dirtyItems, function(item){
return this.createRequestParamsForItem(item, options.additionalFields);
}.bind(this));
// console.log("syncing items", request.items);
if(this.syncToken) {
request.sync_token = this.syncToken;
}
request.post().then(function(response) {
modelManager.clearDirtyItems();
modelManager.clearDirtyItems(dirtyItems);
// handle sync token
this.setSyncToken(response.sync_token);
$rootScope.$broadcast("sync:updated_token", this.syncToken);
// handle cursor token (more results waiting, perform another sync)
this.cursorToken = response.cursor_token;
var retrieved = this.handleItemsResponse(response.retrieved_items, null);
// merge only metadata for saved items
var omitFields = ["content", "enc_item_key", "auth_hash"];
@@ -261,9 +279,16 @@ angular.module('app.frontend')
this.writeItemsToLocalStorage(saved, null);
this.writeItemsToLocalStorage(retrieved, null);
if(callback) {
callback(response);
this.syncOpInProgress = false;
if(this.cursorToken || this.repeatSync == true) {
this.syncWithOptions(callback, options);
} else {
if(callback) {
callback(response);
}
}
}.bind(this))
.catch(function(response){
console.log("Sync error: ", response);
@@ -388,6 +413,11 @@ angular.module('app.frontend')
Import
*/
this.clearSyncToken = function() {
this.syncToken = null;
localStorage.removeItem("syncToken");
}
this.importJSONData = function(data, password, callback) {
console.log("Importing data", data);
@@ -488,27 +518,20 @@ angular.module('app.frontend')
}
this.writeItemsToLocalStorage = function(items, callback) {
for(var item of items) {
var params = this.paramsForItem(item, this.isUserSignedIn(), ["created_at", "updated_at", "presentation_url"], false)
localStorage.setItem("item-" + item.uuid, JSON.stringify(params));
}
if(callback) {
callback(items);
}
var params = items.map(function(item) {
return this.paramsForItem(item, this.isUserSignedIn(), ["created_at", "updated_at", "presentation_url"], false)
}.bind(this));
dbManager.saveItems(params, callback);
}
this.loadLocalItems = function() {
var itemsParams = [];
for (var i = 0; i < localStorage.length; i++){
var key = localStorage.key(i);
if(key.startsWith("item-")) {
var item = localStorage.getItem(key);
itemsParams.push(JSON.parse(item));
}
}
this.loadLocalItems = function(callback) {
var params = dbManager.getAllItems(function(items){
var items = this.handleItemsResponse(items, null);
Item.sortItemsByDate(items);
callback(items);
}.bind(this))
var items = this.handleItemsResponse(itemsParams, null);
Item.sortItemsByDate(items);
}
/*

View File

@@ -0,0 +1,120 @@
class DBManager {
openDatabase(callback, onUgradeNeeded) {
var request = window.indexedDB.open("standardnotes", 1);
request.onerror = function(event) {
alert("Please enable permissions for Standard Notes to use IndexedDB for offline mode.");
if(callback) {
callback(null);
}
};
request.onsuccess = (event) => {
// console.log("Successfully opened database", event.target.result);
var db = event.target.result;
db.onerror = function(errorEvent) {
console.log("Database error: " + errorEvent.target.errorCode);
}
if(callback) {
callback(db);
}
};
request.onupgradeneeded = (event) => {
var db = event.target.result;
if(db.version === 1) {
if(onUgradeNeeded) {
onUgradeNeeded();
}
}
// Create an objectStore for this database
var objectStore = db.createObjectStore("items", { keyPath: "uuid" });
objectStore.createIndex("title", "title", { unique: false });
objectStore.createIndex("uuid", "uuid", { unique: true });
objectStore.transaction.oncomplete = function(event) {
// Ready to store values in the newly created objectStore.
};
};
}
getAllItems(callback) {
this.openDatabase((db) => {
var objectStore = db.transaction("items").objectStore("items");
var items = [];
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
items.push(cursor.value);
cursor.continue();
}
else {
callback(items);
}
};
}, null)
}
saveItem(item) {
saveItems([item]);
}
saveItems(items, callback) {
if(items.length == 0) {
if(callback) {
callback();
}
return;
}
this.openDatabase((db) => {
var transaction = db.transaction("items", "readwrite");
transaction.oncomplete = function(event) {
};
transaction.onerror = function(event) {
console.log("Transaction error:", event.target.errorCode);
};
var itemObjectStore = transaction.objectStore("items");
var i = 0;
putNext();
function putNext() {
if (i < items.length) {
var item = items[i];
itemObjectStore.put(item).onsuccess = putNext;
++i;
} else {
if(callback){
callback();
}
}
}
}, null)
}
deleteItem(item) {
this.openDatabase((db) => {
var request = db.transaction("items", "readwrite").objectStore("items").delete(item.uuid);
request.onsuccess = function(event) {
console.log("Successfully deleted item", item.uuid);
};
}, null)
}
getItemByUUID(uuid, callback) {
this.openDatabase((db) => {
var request = db.transaction("items", "readonly").objectStore("items").get(uuid);
request.onsuccess = function(event) {
callback(event.result);
};
}, null);
}
}
angular.module('app.frontend').service('dbManager', DBManager);

View File

@@ -189,10 +189,14 @@ class ModelManager {
return this.items.filter(function(item){return item.dirty == true && !item.dummy})
}
clearDirtyItems() {
this.getDirtyItems().forEach(function(item){
clearDirtyItems(items) {
for(var item of items) {
item.setDirty(false);
})
}
}
clearAllDirtyItems() {
this.clearDirtyItems(this.getDirtyItems());
}
setItemToBeDeleted(item) {