diff --git a/app/src/PersistantStorage.ts b/app/src/PersistantStorage.ts index 34bafb8..580a128 100644 --- a/app/src/PersistantStorage.ts +++ b/app/src/PersistantStorage.ts @@ -1,21 +1,80 @@ +import { rendererEvents } from '../../events' +import { v4 } from 'uuid' + +import { + storageStoreEvent, + makeStorageResponseEvent, + storageLoadEvent, + storageClearEvent, + makeStorageAcknoledgementEvent, +} from '../../events/StorageEvents' + export interface StorageIdentifier { id: string } export interface PersistantStorage { - store(identifier: StorageIdentifier, data: Model): void - load(identifier: StorageIdentifier): Model | undefined + store(identifier: StorageIdentifier, data: Model): Promise + load(identifier: StorageIdentifier): Promise + clear(): Promise } -class LocalStorage implements PersistantStorage { - public store(identifier: StorageIdentifier, data: Model) { - localStorage.setItem(identifier.id, JSON.stringify(data)) +class RemoteStorage implements PersistantStorage { + private timeoutCallback(event: any, callback: any, reject: any) { + setTimeout(() => { + reject('remote storage timeout') + rendererEvents.unsubscribe(event, callback) + }, 10000) } - public load(identifier: StorageIdentifier): Model | undefined { - const data = localStorage.getItem(identifier.id) - return data && JSON.parse(data) + private expectAck(transactionId: string): Promise { + const ack = makeStorageAcknoledgementEvent(transactionId) + return new Promise((resolve, reject) => { + const callback = () => { + resolve() + rendererEvents.unsubscribe(ack, callback) + } + rendererEvents.subscribe(ack, callback) + this.timeoutCallback(ack, callback, reject) + }) + } + + public store(identifier: StorageIdentifier, data: Model): Promise { + const transactionId = v4() + const expectation = this.expectAck(transactionId) + rendererEvents.emit(storageStoreEvent, { data, transactionId, store: identifier.id }) + return expectation + } + + public load(identifier: StorageIdentifier): Promise { + const transactionId = v4() + const responseEvent = makeStorageResponseEvent(transactionId) + + const promise = new Promise((resolve, reject) => { + const callback = (msg: any) => { + const data = msg.data && JSON.parse(msg.data) + resolve(data) + rendererEvents.unsubscribe(responseEvent, callback) + } + rendererEvents.subscribe(responseEvent, callback) + this.timeoutCallback(responseEvent, callback, reject) + }) + + rendererEvents.emit(storageLoadEvent, { + transactionId, + store: identifier.id, + }) + + return promise + } + + public clear(): Promise { + const transactionId = v4() + const expectation = this.expectAck(transactionId) + + rendererEvents.emit(storageClearEvent, { transactionId }) + return expectation } } -export default new LocalStorage() +export default new RemoteStorage() diff --git a/app/src/actions/ConnectionManager.ts b/app/src/actions/ConnectionManager.ts index 7b8c084..bb35228 100644 --- a/app/src/actions/ConnectionManager.ts +++ b/app/src/actions/ConnectionManager.ts @@ -12,9 +12,9 @@ const storedConnectionsIdentifier: StorageIdentifier<{[s: string]: ConnectionOpt id: 'ConnectionManager_connections', } -export const loadConnectionSettings = () => (dispatch: Dispatch, getState: () => AppState) => { - ensureConnectionsHaveBeenInitialized() - const connections = persistantStorage.load(storedConnectionsIdentifier) +export const loadConnectionSettings = () => async (dispatch: Dispatch, getState: () => AppState) => { + await ensureConnectionsHaveBeenInitialized() + const connections = await persistantStorage.load(storedConnectionsIdentifier) if (!connections) { return @@ -92,9 +92,8 @@ export const deleteConnection = (connectionId: string) => (dispatch: Dispatch { const mqttOptions = toMqttConnection(this.props.connection) if (mqttOptions) { - console.log(mqttOptions) this.props.actions.connect(mqttOptions, this.props.connection.id) } } diff --git a/backend/src/ConfigStorage.ts b/backend/src/ConfigStorage.ts new file mode 100644 index 0000000..0fadf30 --- /dev/null +++ b/backend/src/ConfigStorage.ts @@ -0,0 +1,41 @@ +import * as FileAsync from 'lowdb/adapters/FileAsync' +import * as lowdb from 'lowdb' +import { backendEvents } from '../../events' +import { + makeStorageResponseEvent, + storageClearEvent, + storageLoadEvent, + storageStoreEvent, + makeStorageAcknoledgementEvent, +} from '../../events/StorageEvents' + +export default class ConfigStorage { + private adapter: any + constructor(file: string) { + this.adapter = new FileAsync(file) + } + + 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) + }) + + 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 }) + } catch (error) { + console.error(error) + backendEvents.emit(responseEvent, { transactionId: event.transactionId }) + } + }) + + backendEvents.subscribe(storageClearEvent, async (event) => { + await database.drop() + backendEvents.emit(makeStorageAcknoledgementEvent(event.transactionId), undefined) + }) + } +} diff --git a/backend/src/index.ts b/backend/src/index.ts index e6ca758..4c4e217 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -12,7 +12,7 @@ import { updateAvailable, } from '../../events' import { DataSource, MqttSource } from './DataSource' - +import ConfigStorage from './ConfigStorage' import { UpdateInfo } from 'builder-util-runtime' export class ConnectionManager { @@ -80,3 +80,6 @@ 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 421303f..6c97cf8 100644 --- a/events/EventBus.ts +++ b/events/EventBus.ts @@ -21,10 +21,11 @@ class IpcMainEventBus implements EventBusInterface { this.ipc = ipc } - public subscribe(event: Event, callback:(msg: MessageType) => void) { - console.log('subscribing', event.topic) - this.ipc.on(event.topic, (event: any, arg: any) => { + public subscribe(subscribeEvent: Event, callback:(msg: MessageType) => void) { + 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 new file mode 100644 index 0000000..054c48a --- /dev/null +++ b/events/StorageEvents.ts @@ -0,0 +1,38 @@ +import { Event } from './' + +interface StorageEvent { + transactionId: string +} + +export interface StoreCommand extends StorageEvent { + store: string, + data: any +} + +export interface LoadCommand extends StorageEvent { + store: string, +} + +export const storageStoreEvent: Event = { + topic: 'storage/store', +} + +export const storageLoadEvent: Event = { + topic: 'storage/load', +} + +export function makeStorageAcknoledgementEvent(transactionId: string): Event { + return { + topic: `storage/ack/${transactionId}`, + } +} + +export function makeStorageResponseEvent(transactionId: string): Event { + return { + topic: `storage/response/${transactionId}`, + } +} + +export const storageClearEvent: Event = { + topic: 'storage/clear', +} diff --git a/package.json b/package.json index de0e2aa..f69ac78 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "license": "ISC", "devDependencies": { "@types/chai": "^4.1.7", + "@types/lowdb": "^1.0.6", "@types/mime": "^2.0.0", "@types/mocha": "^5.2.5", "@types/mustache": "^0.8.32", @@ -75,6 +76,7 @@ "electron-log": "^2.2.17", "electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git#dist", "electron-updater": "^4.0.6", + "lowdb": "^1.0.0", "mqtt": "^2.18.8", "sha1": "^1.1.1" } diff --git a/yarn.lock b/yarn.lock index c88e88a..f625924 100644 --- a/yarn.lock +++ b/yarn.lock @@ -119,6 +119,18 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.7.tgz#1b8e33b61a8c09cbe1f85133071baa0dbf9fa71a" integrity sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA== +"@types/lodash@*": + version "4.14.121" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.121.tgz#9327e20d49b95fc2bf983fc2f045b2c6effc80b9" + integrity sha512-ORj7IBWj13iYufXt/VXrCNMbUuCTJfhzme5kx9U/UtcIPdJYuvPDUAlHlbNhz/8lKCLy9XGIZnGrqXOtQbPGoQ== + +"@types/lowdb@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/lowdb/-/lowdb-1.0.6.tgz#0e7adecb87cd79c1e97d50043d9835b65d59023f" + integrity sha512-C/p2p3ud6buHPUaj5QTN3gGera9Pi39aCQoQ1ngRZ2hsWeoqok4aCF/Jjj8FDsnSOTaQHrKI92/KHGt6S+Oy+Q== + dependencies: + "@types/lodash" "*" + "@types/mime@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" @@ -1589,7 +1601,7 @@ got@^6.7.1: unzip-response "^2.0.1" url-parse-lax "^1.0.0" -graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: +graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== @@ -2171,7 +2183,7 @@ lodash.zip@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020" integrity sha1-7GZi5IlkCO1KtsVCo5kLcswIACA= -lodash@^4.17.10, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.10: +lodash@4, lodash@^4.17.10, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.10: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -2194,6 +2206,17 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" +lowdb@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-1.0.0.tgz#5243be6b22786ccce30e50c9a33eac36b20c8064" + integrity sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ== + dependencies: + graceful-fs "^4.1.3" + is-promise "^2.1.0" + lodash "4" + pify "^3.0.0" + steno "^0.4.1" + lowercase-keys@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -3296,6 +3319,13 @@ stat-mode@^0.2.2: resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" integrity sha1-5sgLYjEj19gM8TLOU480YokHJQI= +steno@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/steno/-/steno-0.4.4.tgz#071105bdfc286e6615c0403c27e9d7b5dcb855cb" + integrity sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs= + dependencies: + graceful-fs "^4.1.3" + stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"