Files
PicList/src/main/lifeCycle/index.ts

386 lines
13 KiB
TypeScript

import '~/lifeCycle/errorHandler'
import path from 'node:path'
import bus from '@core/bus'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import { createTray, setDockMenu } from 'apis/app/system'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import axios from 'axios'
import { app, globalShortcut, nativeTheme, Notification, protocol, screen } from 'electron'
import updater from 'electron-updater'
import fs from 'fs-extra'
import busEventList from '~/events/busEventList'
import { rpcServer } from '~/events/rpc'
import { startFileServer, stopFileServer } from '~/fileServer'
import fixPath from '~/lifeCycle/fixPath'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import getManageApi from '~/manage/Main'
import { clearTempFolder } from '~/manage/utils/common'
import server from '~/server/index'
import webServer from '~/server/webServer'
import { isAutoStartEnabled, setAutoStart } from '~/utils/autoStart'
import beforeOpen from '~/utils/beforeOpen'
import clipboardPoll from '~/utils/clipboardPoll'
import { configPaths } from '~/utils/configPaths'
import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from '~/utils/enum'
import { getUploadFiles } from '~/utils/handleArgv'
import { initI18n } from '~/utils/handleI18n'
import { notificationList } from '~/utils/notification'
import { MemoryMonitor } from '~/utils/performanceOptimizer'
import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static'
import updateChecker from '~/utils/updateChecker'
const isDevelopment = process.env.NODE_ENV !== 'production'
process.noDeprecation = true
const handleStartUpFiles = (argv: string[], cwd: string) => {
const files = getUploadFiles(argv, cwd, logger)
if (files === null) {
logger.info('cli -> uploading file from clipboard')
uploadClipboardFiles()
return true
}
if (files.length > 0) {
logger.info('cli -> uploading files from cli', ...files.map(file => file.path))
const win = windowManager.getAvailableWindow()
uploadChoosedFiles(win.webContents, files)
return true
}
return false
}
updater.autoUpdater.setFeedURL({
provider: 'generic',
url: 'https://release.piclist.cn/latest',
channel: 'latest',
})
updater.autoUpdater.forceDevUpdateConfig = true
updater.autoUpdater.autoDownload = false
updater.autoUpdater.on('update-available', async (info: updater.UpdateInfo) => {
const lang = picgo.getConfig<string>(configPaths.settings.language) || II18nLanguage.ZH_CN
let updateLog = ''
try {
const url =
lang === II18nLanguage.ZH_CN
? 'https://release.piclist.cn/currentVersion.md'
: 'https://release.piclist.cn/currentVersion_en.md'
const res = await axios.get(url)
updateLog = res.data
} catch (e: any) {
logger.error(e)
}
const maxLogLength = 8000
let displayLog = updateLog
let truncatedNote = ''
if (updateLog.length > maxLogLength) {
const truncatePoint = updateLog.lastIndexOf('\n', maxLogLength)
displayLog = updateLog.substring(0, truncatePoint > 0 ? truncatePoint : maxLogLength)
truncatedNote =
lang === II18nLanguage.ZH_CN
? '\n\n... (更多详情请查看完整更新日志)'
: '\n\n... (See full changelog for more details)'
}
windowManager.create(IWindowList.UPDATE_WINDOW)
const updateWindow = windowManager.get(IWindowList.UPDATE_WINDOW)!
updateWindow.webContents.once('did-finish-load', () => {
updateWindow.webContents.send('SHOW_UPDATE_INFO', {
type: 'update-available',
title: lang === II18nLanguage.ZH_CN ? '发现新版本' : 'New Update Available',
version: info.version,
releaseNotes: displayLog + truncatedNote,
})
})
updateWindow.show()
})
updater.autoUpdater.on('download-progress', progressObj => {
const percent = {
progress: progressObj.percent,
}
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)
const updateWindow = windowManager.get(IWindowList.UPDATE_WINDOW)
if (settingWindow) {
settingWindow.webContents.send('updateProgress', percent)
}
if (updateWindow) {
updateWindow.webContents.send('UPDATE_PROGRESS', percent)
}
})
updater.autoUpdater.on('update-downloaded', () => {
const lang = picgo.getConfig<string>(configPaths.settings.language) || II18nLanguage.ZH_CN
if (!windowManager.has(IWindowList.UPDATE_WINDOW)) {
windowManager.create(IWindowList.UPDATE_WINDOW)
}
const updateWindow = windowManager.get(IWindowList.UPDATE_WINDOW)!
const sendUpdateInfo = () => {
updateWindow.webContents.send('SHOW_UPDATE_INFO', {
type: 'update-downloaded',
title: lang === II18nLanguage.ZH_CN ? '更新已下载' : 'Update Downloaded',
message:
lang === II18nLanguage.ZH_CN
? '更新已下载完成,将在下次重启应用时安装。是否立即重启?'
: 'The update has been downloaded and will be installed on the next app restart. Would you like to restart now?',
})
}
if (updateWindow.webContents.isLoading()) {
updateWindow.webContents.once('did-finish-load', sendUpdateInfo)
} else {
sendUpdateInfo()
}
if (!updateWindow.isVisible()) {
updateWindow.show()
}
})
updater.autoUpdater.on('error', err => {
logger.error(err)
})
class LifeCycle {
async #beforeReady() {
protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
// fix the $PATH in macOS & linux
fixPath()
beforeOpen()
getManageApi()
UpDownTaskQueue.getInstance()
initI18n()
rpcServer.start()
busEventList.listen()
if (process.env.NODE_ENV === 'development') {
MemoryMonitor.start(30000)
}
}
#onReady() {
const readyFunction = async () => {
windowManager.create(IWindowList.TRAY_WINDOW)
windowManager.create(IWindowList.SETTING_WINDOW)
const isAutoListenClipboard = picgo.getConfig<boolean>(configPaths.settings.isAutoListenClipboard) || false
const systemTheme = picgo.getConfig<'light' | 'dark' | 'system' | undefined>(configPaths.settings.systemTheme)
if (systemTheme) {
nativeTheme.themeSource = systemTheme
} else {
nativeTheme.themeSource = 'system'
}
const ClipboardWatcher = clipboardPoll
if (isAutoListenClipboard) {
picgo.saveConfig({ [configPaths.settings.isListeningClipboard]: true })
ClipboardWatcher.startListening()
ClipboardWatcher.on('change', () => {
picgo.log.info('clipboard changed')
uploadClipboardFiles()
})
} else {
picgo.saveConfig({ [configPaths.settings.isListeningClipboard]: false })
}
const isHideDock = picgo.getConfig<boolean>(configPaths.settings.isHideDock) || false
let startMode = picgo.getConfig<string>(configPaths.settings.startMode) || ISartMode.QUIET
if (process.platform === 'darwin' && startMode === ISartMode.MINI) {
startMode = ISartMode.QUIET
}
const currentPicBed =
picgo.getConfig<string>(configPaths.picBed.uploader) ||
picgo.getConfig<string>(configPaths.picBed.current) ||
'smms'
const currentPicBedConfig = picgo.getConfig<any>(`picBed.${currentPicBed}`)?._configName || 'Default'
const tooltip = `${currentPicBed} ${currentPicBedConfig}`
if (process.platform === 'darwin') {
isHideDock ? app.dock?.hide() : setDockMenu()
startMode !== ISartMode.NO_TRAY && createTray(tooltip)
} else {
createTray(tooltip)
}
picgo.saveConfig({ [configPaths.needReload]: false })
updateChecker()
// 不需要阻塞
process.nextTick(() => {
shortKeyHandler.init()
})
server.startup()
webServer.start()
startFileServer()
if (process.env.NODE_ENV !== 'development') {
handleStartUpFiles(process.argv, process.cwd())
}
if (notificationList && notificationList.length > 0) {
while (notificationList.length) {
const option = notificationList.pop()
const notice = new Notification(option!)
notice.show()
}
}
await remoteNoticeHandler.init()
remoteNoticeHandler.triggerHook(IRemoteNoticeTriggerHook.APP_START)
if (startMode === ISartMode.MINI && process.platform !== 'darwin') {
windowManager.create(IWindowList.MINI_WINDOW)
const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)!
miniWindow.removeAllListeners()
if (picgo.getConfig<boolean>(configPaths.settings.miniWindowOntop)) {
miniWindow.setAlwaysOnTop(true)
}
const { width, height } = screen.getPrimaryDisplay().workAreaSize
const lastPosition = picgo.getConfig<number[]>(configPaths.settings.miniWindowPosition)
if (lastPosition) {
if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) {
miniWindow.setPosition(width - 100, height - 100)
picgo.saveConfig({ [configPaths.settings.miniWindowPosition]: [width - 100, height - 100] })
} else if (
lastPosition[0] + miniWindow.getSize()[0] > width ||
lastPosition[1] + miniWindow.getSize()[1] > height
) {
miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1])
picgo.saveConfig({
[configPaths.settings.miniWindowPosition]: [
width - miniWindow.getSize()[0],
height - miniWindow.getSize()[1],
],
})
} else {
miniWindow.setPosition(lastPosition[0], lastPosition[1])
}
} else {
miniWindow.setPosition(width - 100, height - 100)
}
const setPositionFunc = () => {
const position = miniWindow.getPosition()
picgo.saveConfig({ [configPaths.settings.miniWindowPosition]: position })
}
miniWindow.on('close', setPositionFunc)
miniWindow.on('move', setPositionFunc)
miniWindow.show()
miniWindow.focus()
} else if (startMode === ISartMode.MAIN) {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
settingWindow.show()
settingWindow.focus()
}
const clipboardDir = path.join(picgo.baseDir, CLIPBOARD_IMAGE_FOLDER)
fs.emptyDir(clipboardDir)
}
app.whenReady().then(readyFunction)
}
#onRunning() {
app.on('second-instance', (_, commandLine, workingDirectory) => {
logger.info('detect second instance')
const result = handleStartUpFiles(commandLine, workingDirectory)
if (!result) {
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)!
if (settingWindow.isMinimized()) {
settingWindow.restore()
}
settingWindow.focus()
}
}
})
app.on('activate', () => {
if (!windowManager.has(IWindowList.TRAY_WINDOW)) {
windowManager.create(IWindowList.TRAY_WINDOW)
}
if (!windowManager.has(IWindowList.SETTING_WINDOW)) {
windowManager.create(IWindowList.SETTING_WINDOW)
}
})
const storedAutoStartEnabled = picgo.getConfig<boolean>(configPaths.settings.autoStart) || false
isAutoStartEnabled()
.then(actualAutoStartEnabled => {
if (actualAutoStartEnabled !== storedAutoStartEnabled) {
logger.warn(
`Auto-start state mismatch detected. Stored: ${storedAutoStartEnabled}, Actual: ${actualAutoStartEnabled}. Syncing...`,
)
setAutoStart(storedAutoStartEnabled).catch(err => {
logger.error('Failed to sync auto-start:', err)
})
}
})
.catch(err => {
logger.error('Failed to check auto-start status:', err)
setAutoStart(storedAutoStartEnabled).catch(fallbackErr => {
logger.error('Failed to set auto-start as fallback:', fallbackErr)
})
})
if (process.platform === 'win32') {
app.setAppUserModelId('com.kuingsmile.piclist')
}
if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) {
process.env.XDG_CURRENT_DESKTOP = 'Unity'
}
}
#onQuit() {
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('will-quit', () => {
UpDownTaskQueue.getInstance().persist()
clearTempFolder()
globalShortcut.unregisterAll()
bus.removeAllListeners()
server.shutdown()
webServer.stop()
stopFileServer()
MemoryMonitor.stop()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
}
async launchApp() {
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
await this.#beforeReady()
this.#onReady()
this.#onRunning()
this.#onQuit()
}
}
}
const lifeCycle = new LifeCycle()
export { lifeCycle }