mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
fix: add PowerMonitorService for system shutdown handling (#11115)
* feat: add PowerMonitorService for system shutdown handling - Add PowerMonitorService to monitor system shutdown events - Use @paymoapp/electron-shutdown-handler for Windows platform - Use Electron's powerMonitor for macOS and Linux platforms - Support registering multiple shutdown handlers via dependency injection - Register shutdown handlers in ipc.ts to disable auto-update and save data 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * format code --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7dce1d776b
commit
5fea202a7d
@ -82,6 +82,7 @@
|
|||||||
"@libsql/client": "0.14.0",
|
"@libsql/client": "0.14.0",
|
||||||
"@libsql/win32-x64-msvc": "^0.4.7",
|
"@libsql/win32-x64-msvc": "^0.4.7",
|
||||||
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
||||||
|
"@paymoapp/electron-shutdown-handler": "^1.1.2",
|
||||||
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"font-list": "^2.0.0",
|
"font-list": "^2.0.0",
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import { appMenuService } from './services/AppMenuService'
|
|||||||
import { configManager } from './services/ConfigManager'
|
import { configManager } from './services/ConfigManager'
|
||||||
import mcpService from './services/MCPService'
|
import mcpService from './services/MCPService'
|
||||||
import { nodeTraceService } from './services/NodeTraceService'
|
import { nodeTraceService } from './services/NodeTraceService'
|
||||||
|
import powerMonitorService from './services/PowerMonitorService'
|
||||||
import {
|
import {
|
||||||
CHERRY_STUDIO_PROTOCOL,
|
CHERRY_STUDIO_PROTOCOL,
|
||||||
handleProtocolUrl,
|
handleProtocolUrl,
|
||||||
@ -132,6 +133,7 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
appMenuService?.setupApplicationMenu()
|
appMenuService?.setupApplicationMenu()
|
||||||
|
|
||||||
nodeTraceService.init()
|
nodeTraceService.init()
|
||||||
|
powerMonitorService.init()
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
const mainWindow = windowService.getMainWindow()
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
|||||||
@ -50,6 +50,7 @@ import * as NutstoreService from './services/NutstoreService'
|
|||||||
import ObsidianVaultService from './services/ObsidianVaultService'
|
import ObsidianVaultService from './services/ObsidianVaultService'
|
||||||
import { ocrService } from './services/ocr/OcrService'
|
import { ocrService } from './services/ocr/OcrService'
|
||||||
import OvmsManager from './services/OvmsManager'
|
import OvmsManager from './services/OvmsManager'
|
||||||
|
import powerMonitorService from './services/PowerMonitorService'
|
||||||
import { proxyManager } from './services/ProxyManager'
|
import { proxyManager } from './services/ProxyManager'
|
||||||
import { pythonService } from './services/PythonService'
|
import { pythonService } from './services/PythonService'
|
||||||
import { FileServiceManager } from './services/remotefile/FileServiceManager'
|
import { FileServiceManager } from './services/remotefile/FileServiceManager'
|
||||||
@ -115,6 +116,18 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
const appUpdater = new AppUpdater()
|
const appUpdater = new AppUpdater()
|
||||||
const notificationService = new NotificationService()
|
const notificationService = new NotificationService()
|
||||||
|
|
||||||
|
// Register shutdown handlers
|
||||||
|
powerMonitorService.registerShutdownHandler(() => {
|
||||||
|
appUpdater.setAutoUpdate(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
powerMonitorService.registerShutdownHandler(() => {
|
||||||
|
const mw = windowService.getMainWindow()
|
||||||
|
if (mw && !mw.isDestroyed()) {
|
||||||
|
mw.webContents.send(IpcChannel.App_SaveData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const checkMainWindow = () => {
|
const checkMainWindow = () => {
|
||||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||||
throw new Error('Main window does not exist or has been destroyed')
|
throw new Error('Main window does not exist or has been destroyed')
|
||||||
|
|||||||
112
src/main/services/PowerMonitorService.ts
Normal file
112
src/main/services/PowerMonitorService.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { isLinux, isMac, isWin } from '@main/constant'
|
||||||
|
import ElectronShutdownHandler from '@paymoapp/electron-shutdown-handler'
|
||||||
|
import { BrowserWindow } from 'electron'
|
||||||
|
import { powerMonitor } from 'electron'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('PowerMonitorService')
|
||||||
|
|
||||||
|
type ShutdownHandler = () => void | Promise<void>
|
||||||
|
|
||||||
|
export class PowerMonitorService {
|
||||||
|
private static instance: PowerMonitorService
|
||||||
|
private initialized = false
|
||||||
|
private shutdownHandlers: ShutdownHandler[] = []
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
// Private constructor to prevent direct instantiation
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): PowerMonitorService {
|
||||||
|
if (!PowerMonitorService.instance) {
|
||||||
|
PowerMonitorService.instance = new PowerMonitorService()
|
||||||
|
}
|
||||||
|
return PowerMonitorService.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a shutdown handler to be called when system shutdown is detected
|
||||||
|
* @param handler - The handler function to be called on shutdown
|
||||||
|
*/
|
||||||
|
public registerShutdownHandler(handler: ShutdownHandler): void {
|
||||||
|
this.shutdownHandlers.push(handler)
|
||||||
|
logger.info('Shutdown handler registered', { totalHandlers: this.shutdownHandlers.length })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize power monitor to listen for shutdown events
|
||||||
|
*/
|
||||||
|
public init(): void {
|
||||||
|
if (this.initialized) {
|
||||||
|
logger.warn('PowerMonitorService already initialized')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWin) {
|
||||||
|
this.initWindowsShutdownHandler()
|
||||||
|
} else if (isMac || isLinux) {
|
||||||
|
this.initElectronPowerMonitor()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialized = true
|
||||||
|
logger.info('PowerMonitorService initialized', { platform: process.platform })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute all registered shutdown handlers
|
||||||
|
*/
|
||||||
|
private async executeShutdownHandlers(): Promise<void> {
|
||||||
|
logger.info('Executing shutdown handlers', { count: this.shutdownHandlers.length })
|
||||||
|
for (const handler of this.shutdownHandlers) {
|
||||||
|
try {
|
||||||
|
await handler()
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error executing shutdown handler', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize shutdown handler for Windows using @paymoapp/electron-shutdown-handler
|
||||||
|
*/
|
||||||
|
private initWindowsShutdownHandler(): void {
|
||||||
|
try {
|
||||||
|
const zeroMemoryWindow = new BrowserWindow({ show: false })
|
||||||
|
// Set the window handle for the shutdown handler
|
||||||
|
ElectronShutdownHandler.setWindowHandle(zeroMemoryWindow.getNativeWindowHandle())
|
||||||
|
|
||||||
|
// Listen for shutdown event
|
||||||
|
ElectronShutdownHandler.on('shutdown', async () => {
|
||||||
|
logger.info('System shutdown event detected (Windows)')
|
||||||
|
// Execute all registered shutdown handlers
|
||||||
|
await this.executeShutdownHandlers()
|
||||||
|
// Release the shutdown block to allow the system to shut down
|
||||||
|
ElectronShutdownHandler.releaseShutdown()
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info('Windows shutdown handler registered')
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to initialize Windows shutdown handler', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize power monitor for macOS and Linux using Electron's powerMonitor
|
||||||
|
*/
|
||||||
|
private initElectronPowerMonitor(): void {
|
||||||
|
try {
|
||||||
|
powerMonitor.on('shutdown', async () => {
|
||||||
|
logger.info('System shutdown event detected', { platform: process.platform })
|
||||||
|
// Execute all registered shutdown handlers
|
||||||
|
await this.executeShutdownHandlers()
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info('Electron powerMonitor shutdown listener registered')
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to initialize Electron powerMonitor', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default export as singleton instance
|
||||||
|
export default PowerMonitorService.getInstance()
|
||||||
68
yarn.lock
68
yarn.lock
@ -6787,6 +6787,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@paymoapp/electron-shutdown-handler@npm:^1.1.2":
|
||||||
|
version: 1.1.2
|
||||||
|
resolution: "@paymoapp/electron-shutdown-handler@npm:1.1.2"
|
||||||
|
dependencies:
|
||||||
|
node-addon-api: "npm:^5.0.0"
|
||||||
|
node-gyp: "npm:latest"
|
||||||
|
prebuild-install: "npm:^7.1.2"
|
||||||
|
checksum: 10c0/c774ded900870cd0eae79f2281e971561328b9d2f555b8763a75773f12d953edfa3923067f257bbda5001f9c934343d55344f7a7aac5eff8739762c91e9f37a7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@pdf-lib/standard-fonts@npm:^1.0.0":
|
"@pdf-lib/standard-fonts@npm:^1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "@pdf-lib/standard-fonts@npm:1.0.0"
|
resolution: "@pdf-lib/standard-fonts@npm:1.0.0"
|
||||||
@ -12816,6 +12827,7 @@ __metadata:
|
|||||||
"@opentelemetry/sdk-trace-node": "npm:^2.0.0"
|
"@opentelemetry/sdk-trace-node": "npm:^2.0.0"
|
||||||
"@opentelemetry/sdk-trace-web": "npm:^2.0.0"
|
"@opentelemetry/sdk-trace-web": "npm:^2.0.0"
|
||||||
"@opeoginni/github-copilot-openai-compatible": "npm:0.1.19"
|
"@opeoginni/github-copilot-openai-compatible": "npm:0.1.19"
|
||||||
|
"@paymoapp/electron-shutdown-handler": "npm:^1.1.2"
|
||||||
"@playwright/test": "npm:^1.52.0"
|
"@playwright/test": "npm:^1.52.0"
|
||||||
"@radix-ui/react-context-menu": "npm:^2.2.16"
|
"@radix-ui/react-context-menu": "npm:^2.2.16"
|
||||||
"@reduxjs/toolkit": "npm:^2.2.5"
|
"@reduxjs/toolkit": "npm:^2.2.5"
|
||||||
@ -16036,6 +16048,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"detect-libc@npm:^2.0.0":
|
||||||
|
version: 2.1.2
|
||||||
|
resolution: "detect-libc@npm:2.1.2"
|
||||||
|
checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"detect-libc@npm:^2.0.1":
|
"detect-libc@npm:^2.0.1":
|
||||||
version: 2.0.3
|
version: 2.0.3
|
||||||
resolution: "detect-libc@npm:2.0.3"
|
resolution: "detect-libc@npm:2.0.3"
|
||||||
@ -22425,6 +22444,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"napi-build-utils@npm:^2.0.0":
|
||||||
|
version: 2.0.0
|
||||||
|
resolution: "napi-build-utils@npm:2.0.0"
|
||||||
|
checksum: 10c0/5833aaeb5cc5c173da47a102efa4680a95842c13e0d9cc70428bd3ee8d96bb2172f8860d2811799b5daa5cbeda779933601492a2028a6a5351c6d0fcf6de83db
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"native-promise-only@npm:0.8.1":
|
"native-promise-only@npm:0.8.1":
|
||||||
version: 0.8.1
|
version: 0.8.1
|
||||||
resolution: "native-promise-only@npm:0.8.1"
|
resolution: "native-promise-only@npm:0.8.1"
|
||||||
@ -22508,6 +22534,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"node-addon-api@npm:^5.0.0":
|
||||||
|
version: 5.1.0
|
||||||
|
resolution: "node-addon-api@npm:5.1.0"
|
||||||
|
dependencies:
|
||||||
|
node-gyp: "npm:latest"
|
||||||
|
checksum: 10c0/0eb269786124ba6fad9df8007a149e03c199b3e5a3038125dfb3e747c2d5113d406a4e33f4de1ea600aa2339be1f137d55eba1a73ee34e5fff06c52a5c296d1d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"node-addon-api@npm:^8.4.0":
|
"node-addon-api@npm:^8.4.0":
|
||||||
version: 8.4.0
|
version: 8.4.0
|
||||||
resolution: "node-addon-api@npm:8.4.0"
|
resolution: "node-addon-api@npm:8.4.0"
|
||||||
@ -23722,6 +23757,28 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"prebuild-install@npm:^7.1.2":
|
||||||
|
version: 7.1.3
|
||||||
|
resolution: "prebuild-install@npm:7.1.3"
|
||||||
|
dependencies:
|
||||||
|
detect-libc: "npm:^2.0.0"
|
||||||
|
expand-template: "npm:^2.0.3"
|
||||||
|
github-from-package: "npm:0.0.0"
|
||||||
|
minimist: "npm:^1.2.3"
|
||||||
|
mkdirp-classic: "npm:^0.5.3"
|
||||||
|
napi-build-utils: "npm:^2.0.0"
|
||||||
|
node-abi: "npm:^3.3.0"
|
||||||
|
pump: "npm:^3.0.0"
|
||||||
|
rc: "npm:^1.2.7"
|
||||||
|
simple-get: "npm:^4.0.0"
|
||||||
|
tar-fs: "npm:^2.0.0"
|
||||||
|
tunnel-agent: "npm:^0.6.0"
|
||||||
|
bin:
|
||||||
|
prebuild-install: bin.js
|
||||||
|
checksum: 10c0/25919a42b52734606a4036ab492d37cfe8b601273d8dfb1fa3c84e141a0a475e7bad3ab848c741d2f810cef892fcf6059b8c7fe5b29f98d30e0c29ad009bedff
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"prelude-ls@npm:^1.2.1":
|
"prelude-ls@npm:^1.2.1":
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
resolution: "prelude-ls@npm:1.2.1"
|
resolution: "prelude-ls@npm:1.2.1"
|
||||||
@ -26301,6 +26358,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"simple-get@npm:^4.0.0":
|
||||||
|
version: 4.0.1
|
||||||
|
resolution: "simple-get@npm:4.0.1"
|
||||||
|
dependencies:
|
||||||
|
decompress-response: "npm:^6.0.0"
|
||||||
|
once: "npm:^1.3.1"
|
||||||
|
simple-concat: "npm:^1.0.0"
|
||||||
|
checksum: 10c0/b0649a581dbca741babb960423248899203165769747142033479a7dc5e77d7b0fced0253c731cd57cf21e31e4d77c9157c3069f4448d558ebc96cf9e1eebcf0
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"simple-swizzle@npm:^0.2.2":
|
"simple-swizzle@npm:^0.2.2":
|
||||||
version: 0.2.2
|
version: 0.2.2
|
||||||
resolution: "simple-swizzle@npm:0.2.2"
|
resolution: "simple-swizzle@npm:0.2.2"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user