From 9207af0aaa4c3655128a755c1c25e27a013476d1 Mon Sep 17 00:00:00 2001 From: Thomas Nordquist Date: Sun, 17 Feb 2019 21:02:17 +0100 Subject: [PATCH] Improve settings storage - add error reporting - refactor --- app/src/App.tsx | 27 ++++++++++- app/src/ErrorBoundary.tsx | 11 ++--- app/src/PersistantStorage.ts | 18 ++++++-- app/src/UpdateNotifier.tsx | 5 +- app/src/actions/Connection.ts | 6 +-- app/src/actions/ConnectionManager.ts | 20 ++++++-- app/src/actions/Global.ts | 6 +++ app/src/actions/index.ts | 3 +- .../ConnectionSetup/ConnectionSettings.tsx | 14 ------ .../{ConnectionSetup => }/Notification.tsx | 0 app/src/components/Sidebar/Sidebar.tsx | 1 - app/src/reducers/index.ts | 19 ++++++-- backend/src/ConfigStorage.ts | 46 +++++++++++++++---- backend/src/index.ts | 3 -- events/EventBus.ts | 1 - events/StorageEvents.ts | 5 +- src/electron.ts | 14 ++++-- 17 files changed, 133 insertions(+), 66 deletions(-) create mode 100644 app/src/actions/Global.ts rename app/src/components/{ConnectionSetup => }/Notification.tsx (100%) diff --git a/app/src/App.tsx b/app/src/App.tsx index 0d8c325..4b978ba 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -2,13 +2,16 @@ import * as React from 'react' import ConnectionSetup from './components/ConnectionSetup/ConnectionSetup' import CssBaseline from '@material-ui/core/CssBaseline' import ErrorBoundary from './ErrorBoundary' +import Notification from './components/Notification' import Sidebar from './components/Sidebar/Sidebar' import TitleBar from './components/TitleBar' import Tree from './components/Tree/Tree' import UpdateNotifier from './UpdateNotifier' import { AppState } from './reducers' +import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import { default as SplitPane } from 'react-split-pane' +import { globalActions } from './actions' import { Theme, withStyles } from '@material-ui/core/styles' const Settings = React.lazy(() => import('./components/Settings')) @@ -18,6 +21,8 @@ interface Props { connectionId: string classes: any settingsVisible: boolean + error?: string + actions: any } class App extends React.PureComponent { @@ -26,6 +31,18 @@ class App extends React.PureComponent { this.state = { } } + private renderError() { + if (this.props.error) { + const error = typeof this.props.error === 'string' ? this.props.error : JSON.stringify(this.props.error) + return ( + { this.props.actions.showError(undefined) }} + /> + ) + } + } + public render() { const { settingsVisible } = this.props const { content, contentShift, centerContent, paneDefaults, heightProperty } = this.props.classes @@ -34,6 +51,7 @@ class App extends React.PureComponent {
+ {this.renderError()} Loading...
}> @@ -75,6 +93,7 @@ const mapStateToProps = (state: AppState) => { return { settingsVisible: state.settings.visible, connectionId: state.connection.connectionId, + error: state.globalState.error, } } @@ -120,4 +139,10 @@ const styles = (theme: Theme) => { } } -export default withStyles(styles)(connect(mapStateToProps)(App)) +const mapDispatchToProps = (dispatch: any) => { + return { + actions: bindActionCreators(globalActions, dispatch), + } +} + +export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(App)) diff --git a/app/src/ErrorBoundary.tsx b/app/src/ErrorBoundary.tsx index 4b69c70..a47940a 100644 --- a/app/src/ErrorBoundary.tsx +++ b/app/src/ErrorBoundary.tsx @@ -1,5 +1,9 @@ import * as React from 'react' +import PersistantStorage from './PersistantStorage' +import SentimentDissatisfied from '@material-ui/icons/SentimentDissatisfied' +import Warning from '@material-ui/icons/Warning' import { electronRendererTelementry } from 'electron-telemetry' +import { Theme, withStyles } from '@material-ui/core/styles' import { Button, Modal, @@ -8,11 +12,6 @@ import { Typography, } from '@material-ui/core' -import Warning from '@material-ui/icons/Warning' -import SentimentDissatisfied from '@material-ui/icons/SentimentDissatisfied' - -import { Theme, withStyles } from '@material-ui/core/styles' - interface State { error?: Error } @@ -41,7 +40,7 @@ class ErrorBoundary extends React.Component { } private clearStorage = () => { - localStorage.clear() + PersistantStorage.clear() window.location = window.location } diff --git a/app/src/PersistantStorage.ts b/app/src/PersistantStorage.ts index 580a128..2d9be57 100644 --- a/app/src/PersistantStorage.ts +++ b/app/src/PersistantStorage.ts @@ -30,8 +30,13 @@ class RemoteStorage implements PersistantStorage { private expectAck(transactionId: string): Promise { const ack = makeStorageAcknoledgementEvent(transactionId) return new Promise((resolve, reject) => { - const callback = () => { - resolve() + const callback = (msg: any) => { + console.log(msg) + if (msg && msg.error) { + reject(msg.error) + } else { + resolve() + } rendererEvents.unsubscribe(ack, callback) } rendererEvents.subscribe(ack, callback) @@ -52,8 +57,13 @@ class RemoteStorage implements PersistantStorage { const promise = new Promise((resolve, reject) => { const callback = (msg: any) => { - const data = msg.data && JSON.parse(msg.data) - resolve(data) + console.log(msg) + + if (msg.error) { + reject(msg.error) + } else { + resolve(msg.data) + } rendererEvents.unsubscribe(responseEvent, callback) } rendererEvents.subscribe(responseEvent, callback) diff --git a/app/src/UpdateNotifier.tsx b/app/src/UpdateNotifier.tsx index f2ffce0..50e88c2 100644 --- a/app/src/UpdateNotifier.tsx +++ b/app/src/UpdateNotifier.tsx @@ -115,7 +115,6 @@ class UpdateNotifier extends React.Component { vertical: 'top', horizontal: 'right', } - console.log(this.state.newerVersions) return ( ({ const mapStateToProps = (state: AppState) => { return { - showUpdateNotification: state.tooBigReducer.showUpdateNotification, - showUpdateDetails: state.tooBigReducer.showUpdateDetails, + showUpdateNotification: state.globalState.showUpdateNotification, + showUpdateDetails: state.globalState.showUpdateDetails, } } diff --git a/app/src/actions/Connection.ts b/app/src/actions/Connection.ts index 9f34c32..66efa44 100644 --- a/app/src/actions/Connection.ts +++ b/app/src/actions/Connection.ts @@ -12,6 +12,7 @@ import { Dispatch } from 'redux' import { MqttOptions } from '../../../backend/src/DataSource' import { showTree } from './Tree' import { TopicViewModel } from '../TopicViewModel' +import { showError } from './Global' export const connect = (options: MqttOptions, connectionId: string) => (dispatch: Dispatch, getState: () => AppState) => { dispatch(connecting(connectionId)) @@ -44,11 +45,6 @@ export const connecting: (connectionId: string) => Action = (connectionId: strin type: ActionTypes.CONNECTION_SET_CONNECTING, }) -export const showError = (error?: string) => ({ - error, - type: ActionTypes.CONNECTION_SET_SHOW_ERROR, -}) - export const disconnect = () => (dispatch: Dispatch, getState: () => AppState) => { const { connectionId, tree } = getState().connection rendererEvents.emit(removeConnection, connectionId) diff --git a/app/src/actions/ConnectionManager.ts b/app/src/actions/ConnectionManager.ts index bb35228..af1f1f3 100644 --- a/app/src/actions/ConnectionManager.ts +++ b/app/src/actions/ConnectionManager.ts @@ -1,8 +1,9 @@ import { AppState } from '../reducers' +import { clearLegacyConnectionOptions, loadLegacyConnectionOptions } from '../model/LegacyConnectionSettings' import { ConnectionOptions, createEmptyConnection, makeDefaultConnections } from '../model/ConnectionOptions' import { default as persistantStorage, StorageIdentifier } from '../PersistantStorage' import { Dispatch } from 'redux' -import { loadLegacyConnectionOptions, clearLegacyConnectionOptions } from '../model/LegacyConnectionSettings' +import { showError } from './Global' import { ActionTypes, Action, @@ -13,8 +14,13 @@ const storedConnectionsIdentifier: StorageIdentifier<{[s: string]: ConnectionOpt } export const loadConnectionSettings = () => async (dispatch: Dispatch, getState: () => AppState) => { - await ensureConnectionsHaveBeenInitialized() - const connections = await persistantStorage.load(storedConnectionsIdentifier) + let connections + try { + await ensureConnectionsHaveBeenInitialized() + connections = await persistantStorage.load(storedConnectionsIdentifier) + } catch (error) { + dispatch(showError(error)) + } if (!connections) { return @@ -27,8 +33,12 @@ export const loadConnectionSettings = () => async (dispatch: Dispatch, getS } } -export const saveConnectionSettings = () => (_dispatch: Dispatch, getState: () => AppState) => { - persistantStorage.store(storedConnectionsIdentifier, getState().connectionManager.connections) +export const saveConnectionSettings = () => async (dispatch: Dispatch, getState: () => AppState) => { + try { + await persistantStorage.store(storedConnectionsIdentifier, getState().connectionManager.connections) + } catch (error) { + dispatch(showError(error)) + } } export const updateConnection = (connectionId: string, changeSet: any): Action => ({ diff --git a/app/src/actions/Global.ts b/app/src/actions/Global.ts new file mode 100644 index 0000000..88fe2ef --- /dev/null +++ b/app/src/actions/Global.ts @@ -0,0 +1,6 @@ +import { ActionTypes } from '../reducers' + +export const showError = (error?: string) => ({ + error, + type: ActionTypes.showError, +}) diff --git a/app/src/actions/index.ts b/app/src/actions/index.ts index 8e078d4..3c205b2 100644 --- a/app/src/actions/index.ts +++ b/app/src/actions/index.ts @@ -5,5 +5,6 @@ import * as updateNotifierActions from './UpdateNotifier' import * as connectionActions from './Connection' import * as sidebarActons from './Sidebar' import * as connectionManagerActions from './ConnectionManager' +import * as globalActions from './Global' -export { settingsActions, treeActions, publishActions, updateNotifierActions, connectionActions, sidebarActons, connectionManagerActions } +export { settingsActions, treeActions, publishActions, updateNotifierActions, connectionActions, sidebarActons, connectionManagerActions, globalActions } diff --git a/app/src/components/ConnectionSetup/ConnectionSettings.tsx b/app/src/components/ConnectionSetup/ConnectionSettings.tsx index db05134..e9bd715 100644 --- a/app/src/components/ConnectionSetup/ConnectionSettings.tsx +++ b/app/src/components/ConnectionSetup/ConnectionSettings.tsx @@ -1,7 +1,6 @@ import * as React from 'react' import Delete from '@material-ui/icons/Delete' import Settings from '@material-ui/icons/Settings' -import Notification from './Notification' import PowerSettingsNew from '@material-ui/icons/PowerSettingsNew' import Save from '@material-ui/icons/Save' import Visibility from '@material-ui/icons/Visibility' @@ -35,7 +34,6 @@ interface Props { managerActions: typeof connectionManagerActions connected: boolean connecting: boolean - error?: string } const protocols = [ @@ -106,19 +104,8 @@ class ConnectionSettings extends React.Component { ) - let renderError = null - if (this.props.error) { - renderError = ( - { this.props.actions.showError(undefined) }} - /> - ) - } - return (
- {renderError}
@@ -324,7 +311,6 @@ const mapStateToProps = (state: AppState) => { return { connected: state.connection.connected, connecting: state.connection.connecting, - error: state.connection.error, } } diff --git a/app/src/components/ConnectionSetup/Notification.tsx b/app/src/components/Notification.tsx similarity index 100% rename from app/src/components/ConnectionSetup/Notification.tsx rename to app/src/components/Notification.tsx diff --git a/app/src/components/Sidebar/Sidebar.tsx b/app/src/components/Sidebar/Sidebar.tsx index 3d8392f..68bffa8 100644 --- a/app/src/components/Sidebar/Sidebar.tsx +++ b/app/src/components/Sidebar/Sidebar.tsx @@ -147,7 +147,6 @@ class Sidebar extends React.Component { } private valueRenderWidthChange = (width: number) => { - console.log(width) this.setState({ valueRenderWidth: width }) } diff --git a/app/src/reducers/index.ts b/app/src/reducers/index.ts index d120348..93b2ace 100644 --- a/app/src/reducers/index.ts +++ b/app/src/reducers/index.ts @@ -10,16 +10,18 @@ import { ConnectionManagerState, connectionManagerReducer } from './ConnectionMa export enum ActionTypes { showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION', showUpdateDetails = 'SHOW_UPDATE_DETAILS', + showError = 'SHOW_ERROR', } export interface CustomAction extends Action { type: ActionTypes, showUpdateNotification?: boolean showUpdateDetails?: boolean + error?: string } export interface AppState { - tooBigReducer: TooBigOfState + globalState: GlobalState tree: TreeState settings: SettingsState, publish: PublishState @@ -27,16 +29,17 @@ export interface AppState { connectionManager: ConnectionManagerState } -export interface TooBigOfState { +export interface GlobalState { showUpdateNotification?: boolean showUpdateDetails: boolean + error?: string } -const initialBigState: TooBigOfState = { +const initialBigState: GlobalState = { showUpdateDetails: false, } -const tooBigReducer: Reducer = (state = initialBigState, action) => { +const globalState: Reducer = (state = initialBigState, action) => { if (!state) { throw Error('No initial state') } @@ -49,6 +52,12 @@ const tooBigReducer: Reducer = (state = showUpdateNotification: action.showUpdateNotification, } + case ActionTypes.showError: + return { + ...state, + error: action.error, + } + case ActionTypes.showUpdateDetails: if (action.showUpdateDetails === undefined) { return state @@ -64,7 +73,7 @@ const tooBigReducer: Reducer = (state = } const reducer = combineReducers({ - tooBigReducer, + globalState, publish: publishReducer, connection: connectionReducer, settings: settingsReducer, diff --git a/backend/src/ConfigStorage.ts b/backend/src/ConfigStorage.ts index 0fadf30..5e247cd 100644 --- a/backend/src/ConfigStorage.ts +++ b/backend/src/ConfigStorage.ts @@ -10,32 +10,58 @@ import { } from '../../events/StorageEvents' export default class ConfigStorage { - private adapter: any + private file: string + private database: any constructor(file: string) { - this.adapter = new FileAsync(file) + this.file = file + } + + private async getDb() { + const adapter = new FileAsync(this.file) + if (!this.database) { + this.database = await lowdb(adapter) + } + + return this.database } public async init() { - const database: lowdb.LoDashExplicitAsyncWrapper = await lowdb(this.adapter) backendEvents.subscribe(storageStoreEvent, async (event) => { - await database.set(event.store, event.data).write() - backendEvents.emit(makeStorageAcknoledgementEvent(event.transactionId), undefined) + const ack = makeStorageAcknoledgementEvent(event.transactionId) + try { + const db = await this.getDb() + await db.set(event.store, event.data).write() + backendEvents.emit(ack, undefined) + } catch (error) { + console.error(error) + backendEvents.emit(ack, { error, transactionId: event.transactionId, store: event.store }) + } }) backendEvents.subscribe(storageLoadEvent, async (event) => { const responseEvent = makeStorageResponseEvent(event.transactionId) try { - const data = await database.get(event.store).value() - backendEvents.emit(responseEvent, { data, transactionId: event.transactionId }) + const db = await this.getDb() + const data = await db.get(event.store).value() + backendEvents.emit(responseEvent, { data, transactionId: event.transactionId, store: event.store }) } catch (error) { console.error(error) - backendEvents.emit(responseEvent, { transactionId: event.transactionId }) + backendEvents.emit(responseEvent, { error, transactionId: event.transactionId, store: event.store }) } }) backendEvents.subscribe(storageClearEvent, async (event) => { - await database.drop() - backendEvents.emit(makeStorageAcknoledgementEvent(event.transactionId), undefined) + try { + const db = await this.getDb() + const keys = await db.keys().value() + for (const key of keys) { + await db.unset(key).write() + } + backendEvents.emit(makeStorageAcknoledgementEvent(event.transactionId), undefined) + } catch (error) { + backendEvents.emit(makeStorageAcknoledgementEvent(event.transactionId), { error, transactionId: event.transactionId }) + } + }) } } diff --git a/backend/src/index.ts b/backend/src/index.ts index 4c4e217..7c7ecd6 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -80,6 +80,3 @@ class UpdateNotifier { } export const updateNotifier = new UpdateNotifier() - -const configStorage = new ConfigStorage('blah.json') -configStorage.init() diff --git a/events/EventBus.ts b/events/EventBus.ts index 6c97cf8..b808cac 100644 --- a/events/EventBus.ts +++ b/events/EventBus.ts @@ -25,7 +25,6 @@ class IpcMainEventBus implements EventBusInterface { console.log('subscribing', subscribeEvent.topic) this.ipc.on(subscribeEvent.topic, (event: any, arg: any) => { this.client = event.sender - console.log(subscribeEvent.topic, arg) callback(arg) }) } diff --git a/events/StorageEvents.ts b/events/StorageEvents.ts index 054c48a..83fefc8 100644 --- a/events/StorageEvents.ts +++ b/events/StorageEvents.ts @@ -5,8 +5,9 @@ interface StorageEvent { } export interface StoreCommand extends StorageEvent { - store: string, - data: any + store?: string, + data?: any + error?: any } export interface LoadCommand extends StorageEvent { diff --git a/src/electron.ts b/src/electron.ts index e7d96a8..df8af8a 100644 --- a/src/electron.ts +++ b/src/electron.ts @@ -1,11 +1,12 @@ -import { UpdateInfo } from '../events' -import { BrowserWindow, app, Menu } from 'electron' -import * as path from 'path' -import { menuTemplate } from './MenuTemplate' -import { autoUpdater } from 'electron-updater' import * as log from 'electron-log' +import * as path from 'path' +import ConfigStorage from '../backend/src/ConfigStorage' +import { app, BrowserWindow, Menu } from 'electron' +import { autoUpdater } from 'electron-updater' import { ConnectionManager, updateNotifier } from '../backend/src/index' import { electronTelemetryFactory } from 'electron-telemetry' +import { menuTemplate } from './MenuTemplate' +import { UpdateInfo } from '../events' const isDev = require('electron-is-dev') let electronTelemetry: any @@ -24,6 +25,9 @@ log.info('App starting...') const connectionManager = new ConnectionManager() connectionManager.manageConnections() +const configStorage = new ConfigStorage(path.join(app.getPath('appData'), app.getName(), 'settings.json')) +configStorage.init() + // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let mainWindow: BrowserWindow | undefined