diff --git a/app/main.ts b/app/main.ts index d32a80e..829adec 100644 --- a/app/main.ts +++ b/app/main.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, ipcMain, screen, dialog, Tray, Menu, MenuItemConstructorOptions, IpcMainInvokeEvent } from 'electron'; +import { app, BrowserWindow, ipcMain, screen, dialog, Tray, Menu, MenuItemConstructorOptions, IpcMainInvokeEvent, Notification, NotificationConstructorOptions } from 'electron'; import { ChildProcessWithoutNullStreams, exec, ExecException, spawn } from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; @@ -248,22 +248,46 @@ function getMonerodVersion(monerodFilePath: string): void { }) } +function checkValidMonerodPath(monerodPath: string): void { + let foundUsage: boolean = false; + const monerodProcess = spawn(monerodPath, ['--help']); + + monerodProcess.stderr.on('data', (data) => { + win?.webContents.send('on-check-valid-monerod-path', false); + }); + + monerodProcess.stdout.on('data', (data) => { + if (`${data}`.includes('monerod [options|settings] [daemon_command...]')) { + foundUsage = true; + } + }); + + monerodProcess.on('close', (code: number) => { + win?.webContents.send('on-check-valid-monerod-path', foundUsage); + }) + +} + let moneroFirstStdout: boolean = true; function startMoneroDaemon(commandOptions: string[]): ChildProcessWithoutNullStreams { const monerodPath = commandOptions.shift(); if (!monerodPath) { - win?.webContents.send('monero-stderr', `Invalid monerod path provided: ${monerodPath}`); + const error = `Invalid monerod path provided: ${monerodPath}`; + win?.webContents.send('monero-stderr', error); throw new Error("Invalid monerod path provided"); } if (monerodProcess != null) { - win?.webContents.send('monero-stderr', 'Monerod already started'); + const error: string = 'Monero daemon already started'; + win?.webContents.send('monero-stderr', error); throw new Error("Monerod already started"); } - console.log("Starting monerod daemon with options: " + commandOptions.join(" ")); + const message: string = "Starting monerod daemon with options: " + commandOptions.join(" "); + + console.log(message); moneroFirstStdout = true; @@ -471,6 +495,10 @@ const extractTarBz2 = (filePath: string, destination: string): Promise = }); }; +function showNotification(options?: NotificationConstructorOptions): void { + new Notification(options).show(); +} + try { // This method will be called when Electron has finished // initialization and is ready to create browser windows. @@ -587,6 +615,14 @@ try { monitorMonerod(); }); + ipcMain.handle('check-valid-monerod-path', (event: IpcMainInvokeEvent, path: string) => { + checkValidMonerodPath(path); + }) + + ipcMain.handle('show-notification', (event: IpcMainInvokeEvent, options?: NotificationConstructorOptions) => { + showNotification(options); + }); + } catch (e) { // Catch Error console.error(e); diff --git a/app/preload.js b/app/preload.js index 5590a25..9dac983 100644 --- a/app/preload.js +++ b/app/preload.js @@ -46,6 +46,12 @@ contextBridge.exposeInMainWorld('electronAPI', { onDownloadProgress: (callback) => { ipcRenderer.on('download-progress', callback); }, + checkValidMonerodPath: (path) => { + ipcRenderer.invoke('check-valid-monerod-path', path); + }, + onCheckValidMonerodPath: (callback) => { + ipcRenderer.on('on-check-valid-monerod-path', callback); + }, selectFolder: () => { ipcRenderer.invoke('select-folder') }, @@ -70,6 +76,9 @@ contextBridge.exposeInMainWorld('electronAPI', { gotOsType: (callback) => { ipcRenderer.on('got-os-type', callback); }, + showNotification: (options) => { + ipcRenderer.invoke('show-notification', options); + }, quit: () => { ipcRenderer.invoke('quit'); } diff --git a/src/app/core/services/daemon/daemon-data.service.ts b/src/app/core/services/daemon/daemon-data.service.ts index 6d7866e..4c62ee7 100644 --- a/src/app/core/services/daemon/daemon-data.service.ts +++ b/src/app/core/services/daemon/daemon-data.service.ts @@ -1,6 +1,6 @@ import { EventEmitter, Injectable, NgZone } from '@angular/core'; import { DaemonService } from './daemon.service'; -import { BlockCount, BlockHeader, Chain, Connection, CoreIsBusyError, DaemonInfo, MinerData, MiningStatus, NetStats, NetStatsHistory, PeerInfo, ProcessStats, PublicNode, SyncInfo, TxBacklogEntry, TxPool } from '../../../../common'; +import { BlockCount, BlockHeader, Chain, Connection, CoreIsBusyError, DaemonInfo, MinerData, MiningStatus, NetStats, NetStatsHistory, PeerInfo, ProcessStats, PublicNode, SyncInfo, TimeUtils, TxBacklogEntry, TxPool } from '../../../../common'; @Injectable({ providedIn: 'root' @@ -332,36 +332,6 @@ export class DaemonDataService { public syncDisabledByWifiPolicy: boolean = false; public syncDisabledByPeriodPolicy: boolean = false; - private isInTimeRange(fromHours: string, toHours: string): boolean { - const now = new Date(); - - // Estraiamo l'ora e i minuti dalla stringa in formato hh:mm - const [fromHour, fromMinute] = fromHours.split(":").map(Number); - const [toHour, toMinute] = toHours.split(":").map(Number); - - // Otteniamo l'ora corrente in ore e minuti - const currentHour = now.getHours(); - const currentMinute = now.getMinutes(); - - // Creiamo oggetti Date per le ore 'from', 'to', e l'ora attuale - const currentTime = new Date(); - currentTime.setHours(currentHour, currentMinute, 0, 0); - - const fromTime = new Date(); - fromTime.setHours(fromHour, fromMinute, 0, 0); - - const toTime = new Date(); - toTime.setHours(toHour, toMinute, 0, 0); - - // Gestione del caso in cui la fascia oraria attraversi la mezzanotte - if (fromTime > toTime) { - // Se l'ora attuale è dopo 'fromTime' o prima di 'toTime' - return currentTime >= fromTime || currentTime <= toTime; - } else { - // Caso normale: la fascia oraria è nello stesso giorno - return currentTime >= fromTime && currentTime <= toTime; - } - } private async refresh(): Promise { if (this.refreshing || this.tooEarlyForRefresh) { @@ -407,14 +377,20 @@ export class DaemonDataService { this.syncDisabledByWifiPolicy = false; } - if (!syncAlreadyDisabled && !this.syncDisabledByPeriodPolicy && settings.syncPeriodEnabled && !this.isInTimeRange(settings.syncPeriodFrom, settings.syncPeriodTo)) { + if (!syncAlreadyDisabled && !this.syncDisabledByWifiPolicy && !this.syncDisabledByPeriodPolicy && settings.syncPeriodEnabled && !TimeUtils.isInTimeRange(settings.syncPeriodFrom, settings.syncPeriodTo)) { await this.daemonService.disableSync(); this.syncDisabledByPeriodPolicy = true; } - else if (syncAlreadyDisabled && this.syncDisabledByPeriodPolicy && settings.syncPeriodEnabled && this.isInTimeRange(settings.syncPeriodFrom, settings.syncPeriodTo)) { + else if (syncAlreadyDisabled && !this.syncDisabledByWifiPolicy && this.syncDisabledByPeriodPolicy && settings.syncPeriodEnabled && TimeUtils.isInTimeRange(settings.syncPeriodFrom, settings.syncPeriodTo)) { await this.daemonService.enableSync(); this.syncDisabledByPeriodPolicy = false; } + else if (syncAlreadyDisabled && !this.syncDisabledByWifiPolicy && settings.syncPeriodEnabled && !TimeUtils.isInTimeRange(settings.syncPeriodFrom, settings.syncPeriodTo)) { + this.syncDisabledByPeriodPolicy = true; + } + else { + this.syncDisabledByPeriodPolicy = false; + } this.syncStart.emit({ first: this._firstRefresh }); diff --git a/src/app/core/services/daemon/daemon.service.ts b/src/app/core/services/daemon/daemon.service.ts index e1a260d..25c1fbf 100644 --- a/src/app/core/services/daemon/daemon.service.ts +++ b/src/app/core/services/daemon/daemon.service.ts @@ -78,7 +78,7 @@ import { TxInfo } from '../../../../common/TxInfo'; import { DaemonSettings } from '../../../../common/DaemonSettings'; import { MethodNotFoundError } from '../../../../common/error/MethodNotFoundError'; import { openDB, IDBPDatabase } from "idb" -import { PeerInfo, ProcessStats, TxPool } from '../../../../common'; +import { PeerInfo, ProcessStats, TimeUtils, TxPool } from '../../../../common'; import { MoneroInstallerService } from '../monero-installer/monero-installer.service'; @Injectable({ @@ -124,6 +124,7 @@ export class DaemonService { public readonly onDaemonStatusChanged: EventEmitter = new EventEmitter(); public readonly onDaemonStopStart: EventEmitter = new EventEmitter(); public readonly onDaemonStopEnd: EventEmitter = new EventEmitter(); + public readonly onSavedSettings: EventEmitter = new EventEmitter(); private isRunningPromise?: Promise; @@ -169,6 +170,12 @@ export class DaemonService { public async disableSync(): Promise { this.disablingSync = true; + window.electronAPI.showNotification({ + title: 'Disabling sync', + body: 'Node sync is about to be disabled', + closeButtonText: 'Dismiss' + }); + try { const running: boolean = await this.isRunning(); @@ -185,9 +192,21 @@ export class DaemonService { this.settings.noSync = true; await this.startDaemon(this.settings); + + window.electronAPI.showNotification({ + title: 'Sync disabled', + body: 'Node sync disabled successfully', + closeButtonText: 'Dismiss' + }); + } catch(error: any) { console.error(error); + window.electronAPI.showNotification({ + title: 'Error', + body: 'An error occurred while disabling sync', + closeButtonText: 'Dimiss' + }) } this.disablingSync = false; @@ -246,6 +265,7 @@ export class DaemonService { public async saveSettings(settings: DaemonSettings, restartDaemon: boolean = true): Promise { const db = await this.openDbPromise; await db.put(this.storeName, { id: 1, ...settings }); + this.onSavedSettings.emit(settings); if (restartDaemon) { const running = await this.isRunning(); @@ -263,6 +283,22 @@ export class DaemonService { } } + public async checkValidMonerodPath(path: string): Promise { + if (path == null || path == undefined || path.replace(' ', '') == '') { + return false; + } + + const checkPromise = new Promise((resolve) => { + window.electronAPI.onCheckValidMonerodPath((event: any, valid: boolean) => { + resolve(valid); + }); + }); + + window.electronAPI.checkValidMonerodPath(path); + + return await checkPromise; + } + public async getSettings(): Promise { const db = await this.openDbPromise; const result = await db.get(this.storeName, 1); @@ -359,10 +395,10 @@ export class DaemonService { this.settings.noSync = true; } - else if (!this.settings.noSync && !this.settings.syncOnWifi && !await this.isWifiConnected()) { - console.log("Enabling sync ..."); + else if (!this.settings.noSync && this.settings.syncPeriodEnabled && !TimeUtils.isInTimeRange(this.settings.syncPeriodFrom, this.settings.syncPeriodTo)) { + console.log("Disabling sync ..."); - this.settings.noSync = false; + this.settings.noSync = true; } const startPromise = new Promise((resolve, reject) => { @@ -370,15 +406,25 @@ export class DaemonService { console.debug(event); if (started) { - console.log("Daemon started"); + console.log("monerod started"); this.delay(3000).then(() => { this.isRunning(true).then((running: boolean) => { + window.electronAPI.showNotification({ + title: 'Daemon started', + body: 'Successfully started daemon', + closeButtonText: 'Dismiss' + }); this.onDaemonStatusChanged.emit(running); this.startedAt = new Date(); this.starting = false; resolve(); }).catch((error: any) => { console.error(error); + window.electronAPI.showNotification({ + title: 'Daemon error', + body: 'An error occurred while checking daemon status', + closeButtonText: 'Dismiss' + }); this.onDaemonStatusChanged.emit(false); this.startedAt = undefined; this.starting = false; @@ -391,12 +437,15 @@ export class DaemonService { } else { console.log("Daemon not started"); + window.electronAPI.showNotification({ + title: 'Daemon Error', + body: 'Could not start monerod' + }); this.onDaemonStatusChanged.emit(false); this.startedAt = undefined; this.starting = false; reject('Could not start daemon'); } - }) }); @@ -706,33 +755,19 @@ export class DaemonService { } else if (dontUseRpc) { const monerodPath: string = (await this.getSettings()).monerodPath; - const wdw = (window as any); if (monerodPath == '') { throw new Error("Daemon not configured"); } return new Promise((resolve, reject) => { - if (this.electronService.isElectron) { - this.electronService.ipcRenderer.on('on-monerod-version', (event, version: string) => { - resolve(DaemonVersion.parse(version)); - }); - - this.electronService.ipcRenderer.on('on-monerod-version-error', (event, version: string) => { - reject(version); - }); - - this.electronService.ipcRenderer.send('get-monerod-version', monerodPath); - } - else if (wdw.electronAPI && wdw.electronAPI.getMoneroVersion) { - wdw.electronAPI.onMoneroVersion((event: any, version: string) => { - resolve(DaemonVersion.parse(version)); - }) - wdw.electronAPI.onMoneroVersionError((event: any, error: string) => { - reject(error); - }); - wdw.electronAPI.getMoneroVersion(monerodPath); - } + window.electronAPI.onMoneroVersion((event: any, version: string) => { + resolve(DaemonVersion.parse(version)); + }) + window.electronAPI.onMoneroVersionError((event: any, error: string) => { + reject(error); + }); + window.electronAPI.getMoneroVersion(monerodPath); }); } @@ -946,16 +981,6 @@ export class DaemonService { } throw new Error('Could not stop daemon'); - - /* - if (this.electronService.isElectron) { - return; - } - - this.daemonRunning = false; - this.onDaemonStatusChanged.emit(false); - this.onDaemonStopEnd.emit(); - */ } public async setLimit(limitDown: number, limitUp: number): Promise<{ limitDown: number, limitUp: number }> { @@ -1073,10 +1098,8 @@ export class DaemonService { throw new Error("Download path not configured"); } - //const downloadUrl = 'https://downloads.getmonero.org/cli/linux64'; // Cambia in base al sistema - const destination = settings.downloadUpgradePath; // Aggiorna con il percorso desiderato - - const moneroFolder = await this.installer.downloadMonero(destination); + const destination = settings.downloadUpgradePath; // Aggiorna con il percorso desiderato + const moneroFolder = await this.installer.downloadMonero(destination, settings.monerodPath != ''); settings.monerodPath = `${moneroFolder}/monerod`; diff --git a/src/app/core/services/monero-installer/monero-installer.service.ts b/src/app/core/services/monero-installer/monero-installer.service.ts index 58be834..16befea 100644 --- a/src/app/core/services/monero-installer/monero-installer.service.ts +++ b/src/app/core/services/monero-installer/monero-installer.service.ts @@ -16,58 +16,62 @@ export class MoneroInstallerService { linuxriscv64: 'https://downloads.getmonero.org/cli/linuxriscv64' }; - private _upgrading: boolean = false; private _progress: { progress: number, status: string } = { progress: 0, status: 'Starting upgrade' } + private alreadyConfigured: boolean = false; + public get upgrading(): boolean { - return this._upgrading; + return this._downloading && this.alreadyConfigured; } + public get installing(): boolean { + return this._downloading && !this.alreadyConfigured; + } + + private _downloading: boolean = false; + public get progress(): { progress: number, status: string } { return this._progress; } constructor(private ngZone: NgZone) {} - public async downloadMonero(destination: string): Promise { - this._upgrading = true; + public async downloadMonero(destination: string, alreadyConfigured: boolean): Promise { + this.alreadyConfigured = alreadyConfigured; + this._downloading = true; const downloadUrl = await this.getMoneroDownloadLink(); try { const result = await new Promise((resolve, reject) => { - const wdw = (window as any); - - if (wdw.electronAPI && wdw.electronAPI.onDownloadProgress && wdw.electronAPI.downloadMonerod) { - wdw.electronAPI.onDownloadProgress((event: any, progress: { progress: number, status: string }) => { - //console.log(`${progress.progress.toFixed(2)} % ${progress.status}`); - this.ngZone.run(() => { - this._progress = progress; - }); - if (progress.status.includes('Error')) { - reject(progress.status); - } - - if (progress.progress == 200) { - resolve(progress.status); - } - + window.electronAPI.onDownloadProgress((event: any, progress: { progress: number, status: string }) => { + + this.ngZone.run(() => { + this._progress = progress; }); - - wdw.electronAPI.downloadMonerod(downloadUrl, destination); - } + + if (progress.status.includes('Error')) { + reject(progress.status); + } + + if (progress.progress == 200) { + resolve(progress.status); + } + + }); + + window.electronAPI.downloadMonerod(downloadUrl, destination); }); - this._upgrading = false; + this._downloading = false; return result; } catch (error) { console.error(error); - this._upgrading = false; + this._downloading = false; throw error; } - } private async getMoneroDownloadLink(): Promise { diff --git a/src/app/pages/blockchain/blockchain.component.html b/src/app/pages/blockchain/blockchain.component.html index 7d65a19..d2a1a58 100644 --- a/src/app/pages/blockchain/blockchain.component.html +++ b/src/app/pages/blockchain/blockchain.component.html @@ -11,7 +11,7 @@ -
+
-