indexeddb implementation, #15
This commit is contained in:
@@ -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();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
120
app/assets/javascripts/app/services/dbManager.js
Normal file
120
app/assets/javascripts/app/services/dbManager.js
Normal 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);
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user