Database class cleanup
This commit is contained in:
@@ -91,7 +91,10 @@ class ApplicationViewCtrl extends PureCtrl {
|
||||
super.onAppEvent(eventName);
|
||||
if (eventName === ApplicationEvents.LocalDataIncrementalLoad) {
|
||||
this.updateLocalDataStatus();
|
||||
} else if (eventName === ApplicationEvents.SyncStatusChanged) {
|
||||
} else if (
|
||||
eventName === ApplicationEvents.SyncStatusChanged ||
|
||||
eventName === ApplicationEvents.FailedSync
|
||||
) {
|
||||
this.updateSyncStatus();
|
||||
} else if (eventName === ApplicationEvents.LocalDataLoaded) {
|
||||
this.updateLocalDataStatus();
|
||||
@@ -153,7 +156,12 @@ class ApplicationViewCtrl extends PureCtrl {
|
||||
updateSyncStatus() {
|
||||
const syncStatus = this.application.getSyncStatus();
|
||||
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.`;
|
||||
this.syncStatus = this.application.getStatusService().replaceStatusWithString(
|
||||
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 {
|
||||
constructor() {
|
||||
this.locked = true;
|
||||
}
|
||||
|
||||
/** @access public */
|
||||
deinit() {
|
||||
this.alertService = null;
|
||||
this.db = null;
|
||||
}
|
||||
|
||||
/** @access public */
|
||||
setApplication(application) {
|
||||
this.alertService = application.alertService;
|
||||
}
|
||||
|
||||
displayOfflineAlert() {
|
||||
var message = "There was an issue loading your offline database. This could happen for two reasons:";
|
||||
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.";
|
||||
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 });
|
||||
/**
|
||||
* Relinquishes the lock and allows db operations to proceed
|
||||
* @access public
|
||||
*/
|
||||
unlock() {
|
||||
this.locked = false;
|
||||
}
|
||||
|
||||
setLocked(locked) {
|
||||
this.locked = locked;
|
||||
}
|
||||
|
||||
async openDatabase({ onUpgradeNeeded } = {}) {
|
||||
/**
|
||||
* 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
|
||||
* 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) {
|
||||
throw 'Attempting to open locked database';
|
||||
throw Error('Attempting to open locked database');
|
||||
}
|
||||
|
||||
const request = window.indexedDB.open('standardnotes', 1);
|
||||
|
||||
if (this.db) {
|
||||
return this.db;
|
||||
}
|
||||
const request = window.indexedDB.open(DB_NAME, 1);
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onerror = (event) => {
|
||||
if (event.target.errorCode) {
|
||||
this.alertService.alert({ text: 'Offline database issue: ' + event.target.errorCode });
|
||||
this.showAlert('Offline database issue: ' + event.target.errorCode);
|
||||
} else {
|
||||
this.displayOfflineAlert();
|
||||
}
|
||||
console.error('Offline database issue:', event);
|
||||
resolve(null);
|
||||
reject(new Error('Unable to open db'));
|
||||
};
|
||||
request.onblocked = (event) => {
|
||||
reject(Error('IndexedDB open request blocked'));
|
||||
};
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
const db = event.target.result;
|
||||
db.onversionchange = function (event) {
|
||||
db.onversionchange = () => {
|
||||
db.close();
|
||||
};
|
||||
db.onerror = function (errorEvent) {
|
||||
console.error('Database error: ' + errorEvent.target.errorCode);
|
||||
db.onerror = (errorEvent) => {
|
||||
throw Error('Database error: ' + errorEvent.target.errorCode);
|
||||
};
|
||||
this.db = db;
|
||||
resolve(db);
|
||||
};
|
||||
|
||||
request.onblocked = (event) => {
|
||||
console.error('Request blocked error:', event.target.errorCode);
|
||||
};
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = event.target.result;
|
||||
db.onversionchange = function (event) {
|
||||
db.onversionchange = () => {
|
||||
db.close();
|
||||
};
|
||||
|
||||
// Create an objectStore for this database
|
||||
const objectStore = db.createObjectStore('items', { keyPath: 'uuid' });
|
||||
objectStore.createIndex('uuid', 'uuid', { unique: true });
|
||||
objectStore.transaction.oncomplete = function (event) {
|
||||
// Ready to store values in the newly created objectStore.
|
||||
if (db.version === 1 && onUpgradeNeeded) {
|
||||
onUpgradeNeeded();
|
||||
/* Create an objectStore for this database */
|
||||
const objectStore = db.createObjectStore(
|
||||
STORE_NAME,
|
||||
{ keyPath: 'uuid' }
|
||||
);
|
||||
objectStore.createIndex(
|
||||
'uuid',
|
||||
'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() {
|
||||
const db = await this.openDatabase();
|
||||
const objectStore = db.transaction('items').objectStore('items');
|
||||
const payloads = [];
|
||||
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;
|
||||
if (cursor) {
|
||||
payloads.push(cursor.value);
|
||||
@@ -87,99 +122,106 @@ export class Database {
|
||||
});
|
||||
}
|
||||
|
||||
/** @access public */
|
||||
async savePayload(payload) {
|
||||
this.savePayloads([payload]);
|
||||
return this.savePayloads([payload]);
|
||||
}
|
||||
|
||||
/** @access public */
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
const db = await this.openDatabase();
|
||||
const transaction = db.transaction('items', 'readwrite');
|
||||
return new Promise(async (resolve, reject) => {
|
||||
transaction.oncomplete = (event) => { };
|
||||
transaction.onerror = function (event) {
|
||||
console.error('Transaction error:', event.target.errorCode);
|
||||
showGenericError(event.target.error);
|
||||
const transaction = db.transaction(STORE_NAME, READ_WRITE);
|
||||
return new Promise((resolve, reject) => {
|
||||
transaction.oncomplete = () => { };
|
||||
transaction.onerror = (event) => {
|
||||
this.showGenericError(event.target.error);
|
||||
};
|
||||
transaction.onblocked = function (event) {
|
||||
console.error('Transaction blocked error:', event.target.errorCode);
|
||||
showGenericError(event.target.error);
|
||||
transaction.onblocked = (event) => {
|
||||
this.showGenericError(event.target.error);
|
||||
};
|
||||
transaction.onabort = function (event) {
|
||||
console.error('Offline saving aborted:', event);
|
||||
transaction.onabort = (event) => {
|
||||
const error = event.target.error;
|
||||
if (error.name === 'QuotaExceededError') {
|
||||
this.alertService.alert({ text:
|
||||
'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.'
|
||||
});
|
||||
if (error.name === QUOTE_EXCEEDED_ERROR) {
|
||||
this.showAlert(OUT_OF_SPACE);
|
||||
} else {
|
||||
showGenericError(error);
|
||||
this.showGenericError(error);
|
||||
}
|
||||
reject(error);
|
||||
};
|
||||
|
||||
const payloadObjectStore = transaction.objectStore('items');
|
||||
|
||||
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();
|
||||
const objectStore = transaction.objectStore(STORE_NAME);
|
||||
this.putItems(objectStore, payloads).then(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) {
|
||||
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) => {
|
||||
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();
|
||||
};
|
||||
request.onerror = (event) => {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
reject();
|
||||
deleteRequest.onblocked = (event) => {
|
||||
this.showAlert(DB_DELETION_BLOCKED);
|
||||
reject(Error('Delete request blocked'));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async clearAllPayloads() {
|
||||
const deleteRequest = window.indexedDB.deleteDatabase('standardnotes');
|
||||
/** @access private */
|
||||
showAlert(message) {
|
||||
this.alertService.alert({ text: message });
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
deleteRequest.onerror = function (event) {
|
||||
console.error('Error deleting database.');
|
||||
resolve();
|
||||
};
|
||||
/**
|
||||
* @access private
|
||||
* @param {object} error - {code, name}
|
||||
*/
|
||||
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) {
|
||||
resolve();
|
||||
};
|
||||
|
||||
deleteRequest.onblocked = function (event) {
|
||||
console.error('Delete request blocked');
|
||||
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.' });
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
/** @access private */
|
||||
displayOfflineAlert() {
|
||||
const message =
|
||||
"There was an issue loading your offline database. This could happen for two reasons:" +
|
||||
"\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." +
|
||||
"\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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@ export class WebDeviceInterface extends DeviceInterface {
|
||||
this.database.setApplication(application);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
deinit() {
|
||||
super.deinit();
|
||||
this.database.deinit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @value storage
|
||||
*/
|
||||
@@ -56,22 +62,14 @@ export class WebDeviceInterface extends DeviceInterface {
|
||||
localStorage.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @database
|
||||
*/
|
||||
|
||||
async openDatabase() {
|
||||
this.database.setLocked(false);
|
||||
this.database.openDatabase({
|
||||
onUpgradeNeeded: () => {
|
||||
/**
|
||||
* New database/database wiped, delete syncToken so that items
|
||||
* can be refetched entirely from server
|
||||
*/
|
||||
/** @todo notify parent */
|
||||
// this.syncManager.clearSyncPositionTokens();
|
||||
// this.sync();
|
||||
}
|
||||
this.database.unlock();
|
||||
return new Promise((resolve) => {
|
||||
this.database.openDatabase(() => {
|
||||
resolve({ isNewDatabase: true });
|
||||
}).then(() => {
|
||||
resolve({ isNewDatabase: false });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -112,7 +110,7 @@ export class WebDeviceInterface extends DeviceInterface {
|
||||
/** @keychian */
|
||||
async getKeychainValue() {
|
||||
const value = localStorage.getItem(KEYCHAIN_STORAGE_KEY);
|
||||
if(value) {
|
||||
if (value) {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user