Database class cleanup
This commit is contained in:
@@ -91,7 +91,10 @@ class ApplicationViewCtrl extends PureCtrl {
|
|||||||
super.onAppEvent(eventName);
|
super.onAppEvent(eventName);
|
||||||
if (eventName === ApplicationEvents.LocalDataIncrementalLoad) {
|
if (eventName === ApplicationEvents.LocalDataIncrementalLoad) {
|
||||||
this.updateLocalDataStatus();
|
this.updateLocalDataStatus();
|
||||||
} else if (eventName === ApplicationEvents.SyncStatusChanged) {
|
} else if (
|
||||||
|
eventName === ApplicationEvents.SyncStatusChanged ||
|
||||||
|
eventName === ApplicationEvents.FailedSync
|
||||||
|
) {
|
||||||
this.updateSyncStatus();
|
this.updateSyncStatus();
|
||||||
} else if (eventName === ApplicationEvents.LocalDataLoaded) {
|
} else if (eventName === ApplicationEvents.LocalDataLoaded) {
|
||||||
this.updateLocalDataStatus();
|
this.updateLocalDataStatus();
|
||||||
@@ -153,7 +156,12 @@ class ApplicationViewCtrl extends PureCtrl {
|
|||||||
updateSyncStatus() {
|
updateSyncStatus() {
|
||||||
const syncStatus = this.application.getSyncStatus();
|
const syncStatus = this.application.getSyncStatus();
|
||||||
const stats = syncStatus.getStats();
|
const stats = syncStatus.getStats();
|
||||||
if (stats.downloadCount > 20) {
|
if (syncStatus.hasError()) {
|
||||||
|
this.syncStatus = this.application.getStatusService().replaceStatusWithString(
|
||||||
|
this.syncStatus,
|
||||||
|
'Unable to Sync'
|
||||||
|
);
|
||||||
|
} else if (stats.downloadCount > 20) {
|
||||||
const text = `Downloading ${stats.downloadCount} items. Keep app open.`;
|
const text = `Downloading ${stats.downloadCount} items. Keep app open.`;
|
||||||
this.syncStatus = this.application.getStatusService().replaceStatusWithString(
|
this.syncStatus = this.application.getStatusService().replaceStatusWithString(
|
||||||
this.syncStatus,
|
this.syncStatus,
|
||||||
|
|||||||
@@ -1,81 +1,116 @@
|
|||||||
|
const DB_NAME = 'standardnotes';
|
||||||
|
const STORE_NAME = 'items';
|
||||||
|
const READ_WRITE = 'readwrite';
|
||||||
|
|
||||||
|
const OUT_OF_SPACE =
|
||||||
|
'Unable to save changes locally because your device is out of space. ' +
|
||||||
|
'Please free up some disk space and try again, otherwise, your data may end ' +
|
||||||
|
'up in an inconsistent state.';
|
||||||
|
|
||||||
|
const DB_DELETION_BLOCKED =
|
||||||
|
'Your browser is blocking Standard Notes from deleting the local database. ' +
|
||||||
|
'Make sure there are no other open windows of this app and try again. ' +
|
||||||
|
'If the issue persists, please manually delete app data to sign out.';
|
||||||
|
|
||||||
|
const QUOTE_EXCEEDED_ERROR = 'QuotaExceededError';
|
||||||
|
|
||||||
export class Database {
|
export class Database {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.locked = true;
|
this.locked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @access public */
|
||||||
|
deinit() {
|
||||||
|
this.alertService = null;
|
||||||
|
this.db = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @access public */
|
||||||
setApplication(application) {
|
setApplication(application) {
|
||||||
this.alertService = application.alertService;
|
this.alertService = application.alertService;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayOfflineAlert() {
|
/**
|
||||||
var message = "There was an issue loading your offline database. This could happen for two reasons:";
|
* Relinquishes the lock and allows db operations to proceed
|
||||||
message += "\n\n1. You're in a private window in your browser. We can't save your data without access to the local database. Please use a non-private window.";
|
* @access public
|
||||||
message += "\n\n2. You have two windows of the app open at the same time. Please close any other app instances and reload the page.";
|
*/
|
||||||
this.alertService.alert({ text: message });
|
unlock() {
|
||||||
|
this.locked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocked(locked) {
|
/**
|
||||||
this.locked = locked;
|
* Opens the database natively, or returns the existing database object if already opened.
|
||||||
}
|
* @access public
|
||||||
|
* @param {function} onNewDatabase - Callback to invoke when a database has been created
|
||||||
async openDatabase({ onUpgradeNeeded } = {}) {
|
* as part of the open process. This can happen on new application sessions, or if the
|
||||||
|
* browser deleted the database without the user being aware.
|
||||||
|
*/
|
||||||
|
async openDatabase(onNewDatabase) {
|
||||||
if (this.locked) {
|
if (this.locked) {
|
||||||
throw 'Attempting to open locked database';
|
throw Error('Attempting to open locked database');
|
||||||
}
|
}
|
||||||
|
if (this.db) {
|
||||||
const request = window.indexedDB.open('standardnotes', 1);
|
return this.db;
|
||||||
|
}
|
||||||
|
const request = window.indexedDB.open(DB_NAME, 1);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request.onerror = (event) => {
|
request.onerror = (event) => {
|
||||||
if (event.target.errorCode) {
|
if (event.target.errorCode) {
|
||||||
this.alertService.alert({ text: 'Offline database issue: ' + event.target.errorCode });
|
this.showAlert('Offline database issue: ' + event.target.errorCode);
|
||||||
} else {
|
} else {
|
||||||
this.displayOfflineAlert();
|
this.displayOfflineAlert();
|
||||||
}
|
}
|
||||||
console.error('Offline database issue:', event);
|
reject(new Error('Unable to open db'));
|
||||||
resolve(null);
|
};
|
||||||
|
request.onblocked = (event) => {
|
||||||
|
reject(Error('IndexedDB open request blocked'));
|
||||||
};
|
};
|
||||||
|
|
||||||
request.onsuccess = (event) => {
|
request.onsuccess = (event) => {
|
||||||
const db = event.target.result;
|
const db = event.target.result;
|
||||||
db.onversionchange = function (event) {
|
db.onversionchange = () => {
|
||||||
db.close();
|
db.close();
|
||||||
};
|
};
|
||||||
db.onerror = function (errorEvent) {
|
db.onerror = (errorEvent) => {
|
||||||
console.error('Database error: ' + errorEvent.target.errorCode);
|
throw Error('Database error: ' + errorEvent.target.errorCode);
|
||||||
};
|
};
|
||||||
|
this.db = db;
|
||||||
resolve(db);
|
resolve(db);
|
||||||
};
|
};
|
||||||
|
|
||||||
request.onblocked = (event) => {
|
|
||||||
console.error('Request blocked error:', event.target.errorCode);
|
|
||||||
};
|
|
||||||
|
|
||||||
request.onupgradeneeded = (event) => {
|
request.onupgradeneeded = (event) => {
|
||||||
const db = event.target.result;
|
const db = event.target.result;
|
||||||
db.onversionchange = function (event) {
|
db.onversionchange = () => {
|
||||||
db.close();
|
db.close();
|
||||||
};
|
};
|
||||||
|
/* Create an objectStore for this database */
|
||||||
// Create an objectStore for this database
|
const objectStore = db.createObjectStore(
|
||||||
const objectStore = db.createObjectStore('items', { keyPath: 'uuid' });
|
STORE_NAME,
|
||||||
objectStore.createIndex('uuid', 'uuid', { unique: true });
|
{ keyPath: 'uuid' }
|
||||||
objectStore.transaction.oncomplete = function (event) {
|
);
|
||||||
// Ready to store values in the newly created objectStore.
|
objectStore.createIndex(
|
||||||
if (db.version === 1 && onUpgradeNeeded) {
|
'uuid',
|
||||||
onUpgradeNeeded();
|
'uuid',
|
||||||
|
{ unique: true }
|
||||||
|
);
|
||||||
|
objectStore.transaction.oncomplete = () => {
|
||||||
|
/* Ready to store values in the newly created objectStore. */
|
||||||
|
if (db.version === 1 && onNewDatabase) {
|
||||||
|
onNewDatabase();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @access public */
|
||||||
async getAllPayloads() {
|
async getAllPayloads() {
|
||||||
const db = await this.openDatabase();
|
const db = await this.openDatabase();
|
||||||
const objectStore = db.transaction('items').objectStore('items');
|
|
||||||
const payloads = [];
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
objectStore.openCursor().onsuccess = (event) => {
|
const objectStore =
|
||||||
|
db.transaction(STORE_NAME).
|
||||||
|
objectStore(STORE_NAME);
|
||||||
|
const payloads = [];
|
||||||
|
const cursorRequest = objectStore.openCursor();
|
||||||
|
cursorRequest.onsuccess = (event) => {
|
||||||
const cursor = event.target.result;
|
const cursor = event.target.result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
payloads.push(cursor.value);
|
payloads.push(cursor.value);
|
||||||
@@ -87,99 +122,106 @@ export class Database {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @access public */
|
||||||
async savePayload(payload) {
|
async savePayload(payload) {
|
||||||
this.savePayloads([payload]);
|
return this.savePayloads([payload]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @access public */
|
||||||
async savePayloads(payloads) {
|
async savePayloads(payloads) {
|
||||||
const showGenericError = (error) => {
|
|
||||||
this.alertService.alert({
|
|
||||||
text: `Unable to save changes locally due to an unknown system issue. Issue Code: ${error.code} Issue Name: ${error.name}.`
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (payloads.length === 0) {
|
if (payloads.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = await this.openDatabase();
|
const db = await this.openDatabase();
|
||||||
const transaction = db.transaction('items', 'readwrite');
|
const transaction = db.transaction(STORE_NAME, READ_WRITE);
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
transaction.oncomplete = (event) => { };
|
transaction.oncomplete = () => { };
|
||||||
transaction.onerror = function (event) {
|
transaction.onerror = (event) => {
|
||||||
console.error('Transaction error:', event.target.errorCode);
|
this.showGenericError(event.target.error);
|
||||||
showGenericError(event.target.error);
|
|
||||||
};
|
};
|
||||||
transaction.onblocked = function (event) {
|
transaction.onblocked = (event) => {
|
||||||
console.error('Transaction blocked error:', event.target.errorCode);
|
this.showGenericError(event.target.error);
|
||||||
showGenericError(event.target.error);
|
|
||||||
};
|
};
|
||||||
transaction.onabort = function (event) {
|
transaction.onabort = (event) => {
|
||||||
console.error('Offline saving aborted:', event);
|
|
||||||
const error = event.target.error;
|
const error = event.target.error;
|
||||||
if (error.name === 'QuotaExceededError') {
|
if (error.name === QUOTE_EXCEEDED_ERROR) {
|
||||||
this.alertService.alert({ text:
|
this.showAlert(OUT_OF_SPACE);
|
||||||
'Unable to save changes locally because your device is out of space. Please free up some disk space and try again, otherwise, your data may end up in an inconsistent state.'
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
showGenericError(error);
|
this.showGenericError(error);
|
||||||
}
|
}
|
||||||
reject(error);
|
reject(error);
|
||||||
};
|
};
|
||||||
|
const objectStore = transaction.objectStore(STORE_NAME);
|
||||||
const payloadObjectStore = transaction.objectStore('items');
|
this.putItems(objectStore, payloads).then(resolve);
|
||||||
|
|
||||||
const putPayload = async (payload) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = payloadObjectStore.put(payload);
|
|
||||||
request.onerror = (event) => {
|
|
||||||
console.error('DB put error:', event.target.error);
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
request.onsuccess = resolve;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const payload of payloads) {
|
|
||||||
await putPayload(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @access private */
|
||||||
|
putItems(objectStore, items) {
|
||||||
|
return Promise.all(items.map((item) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = objectStore.put(item);
|
||||||
|
request.onerror = resolve;
|
||||||
|
request.onsuccess = resolve;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @access public */
|
||||||
async deletePayload(uuid) {
|
async deletePayload(uuid) {
|
||||||
const db = await this.openDatabase();
|
const db = await this.openDatabase();
|
||||||
const request = db.transaction('items', 'readwrite').objectStore('items').delete(uuid);
|
const request =
|
||||||
|
db.transaction(STORE_NAME, READ_WRITE)
|
||||||
|
.objectStore(STORE_NAME)
|
||||||
|
.delete(uuid);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request.onsuccess = (event) => {
|
request.onsuccess = resolve;
|
||||||
|
request.onerror = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @access public */
|
||||||
|
async clearAllPayloads() {
|
||||||
|
const deleteRequest = window.indexedDB.deleteDatabase(DB_NAME);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
deleteRequest.onerror = () => {
|
||||||
|
reject(Error('Error deleting database.'));
|
||||||
|
};
|
||||||
|
deleteRequest.onsuccess = () => {
|
||||||
|
this.db = null;
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
request.onerror = (event) => {
|
deleteRequest.onblocked = (event) => {
|
||||||
// eslint-disable-next-line prefer-promise-reject-errors
|
this.showAlert(DB_DELETION_BLOCKED);
|
||||||
reject();
|
reject(Error('Delete request blocked'));
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearAllPayloads() {
|
/** @access private */
|
||||||
const deleteRequest = window.indexedDB.deleteDatabase('standardnotes');
|
showAlert(message) {
|
||||||
|
this.alertService.alert({ text: message });
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
/**
|
||||||
deleteRequest.onerror = function (event) {
|
* @access private
|
||||||
console.error('Error deleting database.');
|
* @param {object} error - {code, name}
|
||||||
resolve();
|
*/
|
||||||
};
|
showGenericError(error) {
|
||||||
|
const message =
|
||||||
|
`Unable to save changes locally due to an unknown system issue. ` +
|
||||||
|
`Issue Code: ${error.code} Issue Name: ${error.name}.`;
|
||||||
|
this.showAlert(message);
|
||||||
|
}
|
||||||
|
|
||||||
deleteRequest.onsuccess = function (event) {
|
/** @access private */
|
||||||
resolve();
|
displayOfflineAlert() {
|
||||||
};
|
const message =
|
||||||
|
"There was an issue loading your offline database. This could happen for two reasons:" +
|
||||||
deleteRequest.onblocked = function (event) {
|
"\n\n1. You're in a private window in your browser. We can't save your data without " +
|
||||||
console.error('Delete request blocked');
|
"access to the local database. Please use a non-private window." +
|
||||||
this.alertService.alert({ text: 'Your browser is blocking Standard Notes from deleting the local database. Make sure there are no other open windows of this app and try again. If the issue persists, please manually delete app data to sign out.' });
|
"\n\n2. You have two windows of the app open at the same time. " +
|
||||||
resolve();
|
"Please close any other app instances and reload the page.";
|
||||||
};
|
this.alertService.alert({ text: message });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ export class WebDeviceInterface extends DeviceInterface {
|
|||||||
this.database.setApplication(application);
|
this.database.setApplication(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
deinit() {
|
||||||
|
super.deinit();
|
||||||
|
this.database.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @value storage
|
* @value storage
|
||||||
*/
|
*/
|
||||||
@@ -56,22 +62,14 @@ export class WebDeviceInterface extends DeviceInterface {
|
|||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @database
|
|
||||||
*/
|
|
||||||
|
|
||||||
async openDatabase() {
|
async openDatabase() {
|
||||||
this.database.setLocked(false);
|
this.database.unlock();
|
||||||
this.database.openDatabase({
|
return new Promise((resolve) => {
|
||||||
onUpgradeNeeded: () => {
|
this.database.openDatabase(() => {
|
||||||
/**
|
resolve({ isNewDatabase: true });
|
||||||
* New database/database wiped, delete syncToken so that items
|
}).then(() => {
|
||||||
* can be refetched entirely from server
|
resolve({ isNewDatabase: false });
|
||||||
*/
|
});
|
||||||
/** @todo notify parent */
|
|
||||||
// this.syncManager.clearSyncPositionTokens();
|
|
||||||
// this.sync();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +110,7 @@ export class WebDeviceInterface extends DeviceInterface {
|
|||||||
/** @keychian */
|
/** @keychian */
|
||||||
async getKeychainValue() {
|
async getKeychainValue() {
|
||||||
const value = localStorage.getItem(KEYCHAIN_STORAGE_KEY);
|
const value = localStorage.getItem(KEYCHAIN_STORAGE_KEY);
|
||||||
if(value) {
|
if (value) {
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
767
dist/javascripts/app.js
vendored
767
dist/javascripts/app.js
vendored
File diff suppressed because one or more lines are too long
2
dist/javascripts/app.js.map
vendored
2
dist/javascripts/app.js.map
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user