diff --git a/app/main.ts b/app/main.ts index 2066c59..d3edf8e 100644 --- a/app/main.ts +++ b/app/main.ts @@ -539,20 +539,35 @@ try { }); ipcMain.handle('quit', async (event: IpcMainInvokeEvent) => { + if (isQuitting) { + console.warn("Already quitting"); + return; + } + isQuitting = true; - if (monerodProcess) { - if (PrivateTestnet.started) { - await PrivateTestnet.stop(); + try { + if (monerodProcess) { + if (PrivateTestnet.started) { + await PrivateTestnet.stop(); + } + else await monerodProcess.stop(); + monerodProcess = null; } - else await monerodProcess.stop(); - monerodProcess = null; + } + catch (error: any) { + console.error("An error occured while stopping monerod on quit handler", error); } - tray.destroy(); - win?.close(); - win?.destroy(); - app.quit(); + try { + tray.destroy(); + win?.close(); + win?.destroy(); + app.quit(); + } + catch(error: any) { + console.error("An error occurred on quit handler: ", error); + } }); ipcMain.handle('start-monerod', (event: IpcMainInvokeEvent, configFilePath: string[]) => { diff --git a/app/package.json b/app/package.json index 7bcb085..9116b67 100644 --- a/app/package.json +++ b/app/package.json @@ -7,6 +7,7 @@ "email": "everoddandeven@protonmail.com" }, "version": "1.1.0", + "main": "main.js", "private": true, "dependencies": { diff --git a/app/process/AppChildProcess.ts b/app/process/AppChildProcess.ts index e2ef17b..8c933af 100644 --- a/app/process/AppChildProcess.ts +++ b/app/process/AppChildProcess.ts @@ -64,6 +64,10 @@ export class AppChildProcess { return this._running; } + public get stopping(): boolean { + return this._stopping; + } + constructor({ command, args, isExe } : { command: string, args?: string[], isExe?: boolean}) { this._command = command; this._args = args; diff --git a/app/process/PrivateTestnet.ts b/app/process/PrivateTestnet.ts index 2bb1326..c77ee1b 100644 --- a/app/process/PrivateTestnet.ts +++ b/app/process/PrivateTestnet.ts @@ -1,15 +1,34 @@ import { MonerodProcess } from "./MonerodProcess"; +import * as os from 'os'; + +function copyArray(src: T[], target?: T[]): T[] { + if (!target) target = []; + if (target.length !== 0) { + target = []; + } + + src.forEach((v) => target.push(v)); + return target; +} export abstract class PrivateTestnet { + private static get isWindows(): boolean { + return os.platform() === 'win32'; + } + + private static get directorySeparator(): '\\' | '/' { + return this.isWindows ? '\\' : '/'; + } + private static readonly _node1Options: string[] = [ - '--testnet', '--no-igd', '--hide-my-port', '--data-dir', '.localnet/xmr_local/node1', + '--testnet', '--no-igd', '--hide-my-port', '--p2p-bind-ip', '127.0.0.1', '--log-level', '0', '--add-exclusive-node', '127.0.0.1:48080', '--add-exclusive-node', '127.0.0.1:58080', '--rpc-access-control-origins', '*', '--fixed-difficulty', '500', '--disable-rpc-ban', '--non-interactive' ]; private static readonly _node2Options: string[] = [ - '--testnet', '--no-igd', '--hide-my-port', '--data-dir', '.localnet/xmr_local/node2', + '--testnet', '--no-igd', '--hide-my-port', '--p2p-bind-ip', '127.0.0.1', '--p2p-bind-port', '48080', '--rpc-bind-port' ,'48081', '--zmq-rpc-bind-port', '48082', '--log-level', '1', '--confirm-external-bind', '--add-exclusive-node', '127.0.0.1:28080', '--add-exclusive-node', '127.0.0.1:58080', '--rpc-access-control-origins', '*', @@ -41,6 +60,51 @@ export abstract class PrivateTestnet { return this._mining; } + private static replaceAll(value: string, oldValue: string, newValue: string): string { + let v = value; + + while(v.includes(oldValue)) v = v.replace(oldValue, newValue); + + return v; + } + + private static parseDataDir(dataDir: string): string { + const separator = this.directorySeparator; + const dataDirSeparator = dataDir.includes('\\') ? '\\' : '/'; + const needsReplace = separator !== dataDirSeparator; + + if (needsReplace) return this.replaceAll(dataDir, dataDirSeparator, separator); + return dataDir; + } + + private static buildDataDir(monerodPath: string, dataDir: string): string { + const separator = this.directorySeparator; + + if (monerodPath === '') return dataDir; + + const components = monerodPath.split(separator); + components.pop(); + const path = components.join(separator); + + return `${path}${separator}${this.parseDataDir(dataDir)}`; + } + + private static buildDataDirFlags(monerodPath: string, dataDir: string): [string, string] { + return ['--data-dir', this.buildDataDir(monerodPath, dataDir)]; + } + + private static getNode1Flags(monerodPath: string): string[] { + const flags = copyArray(this._node1Options); + flags.push(...this.buildDataDirFlags(monerodPath, '.localnet/xmr_local/node1')); + return flags; + } + + private static getNode2Flags(monerodPath: string): string[] { + const flags = copyArray(this._node2Options); + flags.push(...this.buildDataDirFlags(monerodPath, '.localnet/xmr_local/node2')); + return flags; + } + public static init(monerodPath: string): void { if (this.initialized) { throw new Error("Already initiliazed private testnet"); @@ -48,13 +112,13 @@ export abstract class PrivateTestnet { this._node1 = new MonerodProcess({ monerodCmd: monerodPath, - flags: this._node1Options, + flags: this.getNode1Flags(monerodPath), isExe: true }); this._node2 = new MonerodProcess({ monerodCmd: monerodPath, - flags: this._node2Options, + flags: this.getNode2Flags(monerodPath), isExe: true }); diff --git a/src/app/core/services/daemon/daemon-data.service.ts b/src/app/core/services/daemon/daemon-data.service.ts index 7c519b4..c44ef6b 100644 --- a/src/app/core/services/daemon/daemon-data.service.ts +++ b/src/app/core/services/daemon/daemon-data.service.ts @@ -84,7 +84,7 @@ export class DaemonDataService { this.startLoop(); } else { - this.stopLoop(); + if (this.refreshInterval) this.stopLoop(); } }); }); @@ -333,7 +333,7 @@ export class DaemonDataService { private osType?: { platform: string }; private async refresh(): Promise { - if (this.refreshing || this.tooEarlyForRefresh || this.daemonService.stopping) { + if (this.refreshing || this.tooEarlyForRefresh || this.daemonService.stopping || !this._daemonRunning) { return; } @@ -420,7 +420,7 @@ export class DaemonDataService { this._firstRefresh = false; if (!this._daemonRunning) { - this.stopLoop(); + if (this.refreshInterval) this.stopLoop(); this.syncEnd.emit(); return; } @@ -517,7 +517,7 @@ export class DaemonDataService { this.syncError.emit(error); if (!await this.daemonService.isRunning()) { - this.stopLoop(); + if (this.refreshInterval) this.stopLoop(); } } diff --git a/src/app/core/services/daemon/daemon.service.ts b/src/app/core/services/daemon/daemon.service.ts index a6b63cb..f896728 100644 --- a/src/app/core/services/daemon/daemon.service.ts +++ b/src/app/core/services/daemon/daemon.service.ts @@ -1257,12 +1257,9 @@ export class DaemonService { } public async quit(): Promise { + if (this._quitting) throw new Error("Already quitting daemon"); + this._quitting = true; - const running: boolean = await this.isRunning(); - - if (running) { - await this.stopDaemon(); - } window.electronAPI.quit(); } diff --git a/src/app/pages/settings/settings.component.ts b/src/app/pages/settings/settings.component.ts index d74ee39..0964486 100644 --- a/src/app/pages/settings/settings.component.ts +++ b/src/app/pages/settings/settings.component.ts @@ -1,6 +1,6 @@ import { AfterViewInit, Component, NgZone } from '@angular/core'; import { NavbarLink } from '../../shared/components/navbar/navbar.model'; -import { DaemonSettings, DefaultPrivnetNode2Settings } from '../../../common'; +import { DaemonSettings, DefaultPrivnetNode2Settings, PrivnetDaemonSettings } from '../../../common'; import { DaemonService } from '../../core/services/daemon/daemon.service'; import { ElectronService } from '../../core/services'; import { DaemonSettingsError } from '../../../common'; @@ -331,7 +331,7 @@ export class SettingsComponent extends BasePageComponent implements AfterViewIni this.isPortable = await this.electronService.isPortable(); this.networkType = this._currentSettings.mainnet ? 'mainnet' : this._currentSettings.testnet ? 'testnet' : this._currentSettings.stagenet ? 'stagenet' : this._currentSettings.privnet ? 'privnet' : 'mainnet'; - if (this._privnetSettings.monerodPath == '') this._privnetSettings.monerodPath = this._currentSettings.monerodPath; + if (this._privnetSettings.monerodPath == '') this._privnetSettings.setMonerodPath(this._currentSettings.monerodPath); this.refreshLogin(); } @@ -606,7 +606,9 @@ export class SettingsComponent extends BasePageComponent implements AfterViewIni const valid = await this.daemonService.checkValidMonerodPath(file); if (valid) { this.ngZone.run(() => { - this.currentSettings.monerodPath = file; + const currentSettings = this.currentSettings; + if (currentSettings instanceof PrivnetDaemonSettings) currentSettings.setMonerodPath(file); + else currentSettings.monerodPath = file; }); } else { diff --git a/src/common/DefaultPrivnetNode1Settings.ts b/src/common/DefaultPrivnetNode1Settings.ts index a3b6eae..b6f286b 100644 --- a/src/common/DefaultPrivnetNode1Settings.ts +++ b/src/common/DefaultPrivnetNode1Settings.ts @@ -1,24 +1,11 @@ -import { DaemonSettings } from "./DaemonSettings"; +import { PrivnetDaemonSettings } from "./PrivnetDaemonSettings"; -export class DefaultPrivnetNode1Settings extends DaemonSettings { - - public override get isPrivnet(): boolean { - return true; - } +export class DefaultPrivnetNode1Settings extends PrivnetDaemonSettings { constructor(monerodPath: string = '') { - super(); - this.monerodPath = monerodPath; - this.testnet = true; - this.noIgd = true; - this.hideMyPort = true; - this.dataDir = '.localnet/xmr_local/node1'; - this.p2pBindIp = '127.0.0.1'; - this.logLevel = 0; + super(monerodPath, '.localnet/xmr_local/node1'); + this.addExclusiveNode('127.0.0.1:48080'); this.addExclusiveNode('127.0.0.1:58080'); - this.rpcAccessControlOrigins = '*'; - this.fixedDifficulty = 500; - this.disableRpcBan = true; } } \ No newline at end of file diff --git a/src/common/DefaultPrivnetNode2Settings.ts b/src/common/DefaultPrivnetNode2Settings.ts index 47878bf..a5d30be 100644 --- a/src/common/DefaultPrivnetNode2Settings.ts +++ b/src/common/DefaultPrivnetNode2Settings.ts @@ -1,20 +1,10 @@ -import { DaemonSettings } from "./DaemonSettings"; +import { PrivnetDaemonSettings } from "./PrivnetDaemonSettings"; -export class DefaultPrivnetNode2Settings extends DaemonSettings { - - public override get isPrivnet(): boolean { - return true; - } +export class DefaultPrivnetNode2Settings extends PrivnetDaemonSettings { constructor(monerodPath: string = '') { - super(); + super(monerodPath, '.localnet/xmr_local/node2'); - this.monerodPath = monerodPath; - this.testnet = true; - this.noIgd = true; - this.hideMyPort = true; - this.dataDir = '.localnet/xmr_local/node2'; - this.p2pBindIp = '127.0.0.1'; this.p2pBindPort = 48080; this.rpcBindPort = 48081; this.zmqRpcBindPort = 48082; @@ -22,8 +12,5 @@ export class DefaultPrivnetNode2Settings extends DaemonSettings { this.confirmExternalBind = true; this.addExclusiveNode('127.0.0.1:28080'); this.addExclusiveNode('127.0.0.1:58080'); - this.rpcAccessControlOrigins = '*'; - this.fixedDifficulty = 500; - this.disableRpcBan = true; } } \ No newline at end of file diff --git a/src/common/PrivnetDaemonSettings.ts b/src/common/PrivnetDaemonSettings.ts new file mode 100644 index 0000000..9da2085 --- /dev/null +++ b/src/common/PrivnetDaemonSettings.ts @@ -0,0 +1,50 @@ +import { DaemonSettings } from "./DaemonSettings"; + +export class PrivnetDaemonSettings extends DaemonSettings { + + protected _dataDir: string; + + public override get isPrivnet(): boolean { + return true; + } + + constructor(monerodPath: string, dataDir: string) { + super(); + this.testnet = true; + this.noIgd = true; + this.hideMyPort = true; + this.p2pBindIp = '127.0.0.1'; + this.logLevel = 0; + this.rpcAccessControlOrigins = '*'; + this.fixedDifficulty = 500; + this.disableRpcBan = true; + this.syncOnWifi = true; + this._dataDir = dataDir; + this.setMonerodPath(monerodPath); + } + + private refreshDataDir(): void { + if (this.monerodPath == '') { + this.dataDir = ''; + return; + } + + const separator: '\\' | '/' = this.monerodPath.includes('\\') ? '\\' : '/'; + const dataDirSeparator: '\\' | '/' = this._dataDir.includes('\\') ? '\\' : '/'; + const needsReplace = dataDirSeparator !== separator; + + const dataDir = needsReplace ? this._dataDir.replaceAll(dataDirSeparator, separator) : this._dataDir; + + const components = this.monerodPath.split(separator); + components.pop(); + const path = components.join(separator); + + this.dataDir = `${path}${separator}${dataDir}`; + } + + public setMonerodPath(monerodPath: string): void { + this.monerodPath = monerodPath; + + this.refreshDataDir(); + } +} \ No newline at end of file diff --git a/src/common/index.ts b/src/common/index.ts index 79cba3c..6f88702 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -46,5 +46,6 @@ export * from './error'; export * from './request'; export * from './utils'; +export { PrivnetDaemonSettings } from "./PrivnetDaemonSettings"; export { DefaultPrivnetNode1Settings } from "./DefaultPrivnetNode1Settings"; export { DefaultPrivnetNode2Settings } from "./DefaultPrivnetNode2Settings"; diff --git a/tsconfig.json b/tsconfig.json index d993a81..6bfc951 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,7 @@ "es2016", "es2015", "es2018", + "es2021", "dom" ], "useDefineForClassFields": false