mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-08 07:25:51 +00:00
规范化资源文件;修复消息气泡宽度异常的问题;优化资源管理页面性能
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -56,6 +56,8 @@ Thumbs.db
|
|||||||
*.aps
|
*.aps
|
||||||
|
|
||||||
wcdb/
|
wcdb/
|
||||||
|
!resources/wcdb/
|
||||||
|
!resources/wcdb/**
|
||||||
xkey/
|
xkey/
|
||||||
server/
|
server/
|
||||||
*info
|
*info
|
||||||
|
|||||||
@@ -2558,7 +2558,13 @@ function registerIpcHandlers() {
|
|||||||
ipcMain.handle('image:decrypt', async (_, payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) => {
|
ipcMain.handle('image:decrypt', async (_, payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) => {
|
||||||
return imageDecryptService.decryptImage(payload)
|
return imageDecryptService.decryptImage(payload)
|
||||||
})
|
})
|
||||||
ipcMain.handle('image:resolveCache', async (_, payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; disableUpdateCheck?: boolean }) => {
|
ipcMain.handle('image:resolveCache', async (_, payload: {
|
||||||
|
sessionId?: string
|
||||||
|
imageMd5?: string
|
||||||
|
imageDatName?: string
|
||||||
|
disableUpdateCheck?: boolean
|
||||||
|
allowCacheIndex?: boolean
|
||||||
|
}) => {
|
||||||
return imageDecryptService.resolveCachedImage(payload)
|
return imageDecryptService.resolveCachedImage(payload)
|
||||||
})
|
})
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
@@ -2566,13 +2572,14 @@ function registerIpcHandlers() {
|
|||||||
async (
|
async (
|
||||||
_,
|
_,
|
||||||
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
||||||
options?: { disableUpdateCheck?: boolean }
|
options?: { disableUpdateCheck?: boolean; allowCacheIndex?: boolean }
|
||||||
) => {
|
) => {
|
||||||
const list = Array.isArray(payloads) ? payloads : []
|
const list = Array.isArray(payloads) ? payloads : []
|
||||||
const rows = await Promise.all(list.map(async (payload) => {
|
const rows = await Promise.all(list.map(async (payload) => {
|
||||||
return imageDecryptService.resolveCachedImage({
|
return imageDecryptService.resolveCachedImage({
|
||||||
...payload,
|
...payload,
|
||||||
disableUpdateCheck: options?.disableUpdateCheck === true
|
disableUpdateCheck: options?.disableUpdateCheck === true,
|
||||||
|
allowCacheIndex: options?.allowCacheIndex !== false
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
return { success: true, rows }
|
return { success: true, rows }
|
||||||
@@ -2583,7 +2590,7 @@ function registerIpcHandlers() {
|
|||||||
async (
|
async (
|
||||||
_,
|
_,
|
||||||
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
||||||
options?: { allowDecrypt?: boolean }
|
options?: { allowDecrypt?: boolean; allowCacheIndex?: boolean }
|
||||||
) => {
|
) => {
|
||||||
imagePreloadService.enqueue(payloads || [], options)
|
imagePreloadService.enqueue(payloads || [], options)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -266,15 +266,21 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
image: {
|
image: {
|
||||||
decrypt: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) =>
|
decrypt: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) =>
|
||||||
ipcRenderer.invoke('image:decrypt', payload),
|
ipcRenderer.invoke('image:decrypt', payload),
|
||||||
resolveCache: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; disableUpdateCheck?: boolean }) =>
|
resolveCache: (payload: {
|
||||||
|
sessionId?: string
|
||||||
|
imageMd5?: string
|
||||||
|
imageDatName?: string
|
||||||
|
disableUpdateCheck?: boolean
|
||||||
|
allowCacheIndex?: boolean
|
||||||
|
}) =>
|
||||||
ipcRenderer.invoke('image:resolveCache', payload),
|
ipcRenderer.invoke('image:resolveCache', payload),
|
||||||
resolveCacheBatch: (
|
resolveCacheBatch: (
|
||||||
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
||||||
options?: { disableUpdateCheck?: boolean }
|
options?: { disableUpdateCheck?: boolean; allowCacheIndex?: boolean }
|
||||||
) => ipcRenderer.invoke('image:resolveCacheBatch', payloads, options),
|
) => ipcRenderer.invoke('image:resolveCacheBatch', payloads, options),
|
||||||
preload: (
|
preload: (
|
||||||
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
||||||
options?: { allowDecrypt?: boolean }
|
options?: { allowDecrypt?: boolean; allowCacheIndex?: boolean }
|
||||||
) => ipcRenderer.invoke('image:preload', payloads, options),
|
) => ipcRenderer.invoke('image:preload', payloads, options),
|
||||||
onUpdateAvailable: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string }) => void) => {
|
onUpdateAvailable: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string }) => void) => {
|
||||||
const listener = (_: unknown, payload: { cacheKey: string; imageMd5?: string; imageDatName?: string }) => callback(payload)
|
const listener = (_: unknown, payload: { cacheKey: string; imageMd5?: string; imageDatName?: string }) => callback(payload)
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ type CachedImagePayload = {
|
|||||||
imageDatName?: string
|
imageDatName?: string
|
||||||
preferFilePath?: boolean
|
preferFilePath?: boolean
|
||||||
disableUpdateCheck?: boolean
|
disableUpdateCheck?: boolean
|
||||||
|
allowCacheIndex?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type DecryptImagePayload = CachedImagePayload & {
|
type DecryptImagePayload = CachedImagePayload & {
|
||||||
@@ -116,7 +117,9 @@ export class ImageDecryptService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async resolveCachedImage(payload: CachedImagePayload): Promise<DecryptResult & { hasUpdate?: boolean }> {
|
async resolveCachedImage(payload: CachedImagePayload): Promise<DecryptResult & { hasUpdate?: boolean }> {
|
||||||
|
if (payload.allowCacheIndex !== false) {
|
||||||
await this.ensureCacheIndexed()
|
await this.ensureCacheIndexed()
|
||||||
|
}
|
||||||
const cacheKeys = this.getCacheKeys(payload)
|
const cacheKeys = this.getCacheKeys(payload)
|
||||||
const cacheKey = cacheKeys[0]
|
const cacheKey = cacheKeys[0]
|
||||||
if (!cacheKey) {
|
if (!cacheKey) {
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ type PreloadImagePayload = {
|
|||||||
|
|
||||||
type PreloadOptions = {
|
type PreloadOptions = {
|
||||||
allowDecrypt?: boolean
|
allowDecrypt?: boolean
|
||||||
|
allowCacheIndex?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type PreloadTask = PreloadImagePayload & {
|
type PreloadTask = PreloadImagePayload & {
|
||||||
key: string
|
key: string
|
||||||
allowDecrypt: boolean
|
allowDecrypt: boolean
|
||||||
|
allowCacheIndex: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ImagePreloadService {
|
export class ImagePreloadService {
|
||||||
@@ -27,6 +29,7 @@ export class ImagePreloadService {
|
|||||||
enqueue(payloads: PreloadImagePayload[], options?: PreloadOptions): void {
|
enqueue(payloads: PreloadImagePayload[], options?: PreloadOptions): void {
|
||||||
if (!Array.isArray(payloads) || payloads.length === 0) return
|
if (!Array.isArray(payloads) || payloads.length === 0) return
|
||||||
const allowDecrypt = options?.allowDecrypt !== false
|
const allowDecrypt = options?.allowDecrypt !== false
|
||||||
|
const allowCacheIndex = options?.allowCacheIndex !== false
|
||||||
for (const payload of payloads) {
|
for (const payload of payloads) {
|
||||||
if (!allowDecrypt && this.queue.length >= this.maxQueueSize) break
|
if (!allowDecrypt && this.queue.length >= this.maxQueueSize) break
|
||||||
const cacheKey = payload.imageMd5 || payload.imageDatName
|
const cacheKey = payload.imageMd5 || payload.imageDatName
|
||||||
@@ -34,7 +37,7 @@ export class ImagePreloadService {
|
|||||||
const key = `${payload.sessionId || 'unknown'}|${cacheKey}`
|
const key = `${payload.sessionId || 'unknown'}|${cacheKey}`
|
||||||
if (this.pending.has(key)) continue
|
if (this.pending.has(key)) continue
|
||||||
this.pending.add(key)
|
this.pending.add(key)
|
||||||
this.queue.push({ ...payload, key, allowDecrypt })
|
this.queue.push({ ...payload, key, allowDecrypt, allowCacheIndex })
|
||||||
}
|
}
|
||||||
this.processQueue()
|
this.processQueue()
|
||||||
}
|
}
|
||||||
@@ -71,7 +74,8 @@ export class ImagePreloadService {
|
|||||||
sessionId: task.sessionId,
|
sessionId: task.sessionId,
|
||||||
imageMd5: task.imageMd5,
|
imageMd5: task.imageMd5,
|
||||||
imageDatName: task.imageDatName,
|
imageDatName: task.imageDatName,
|
||||||
disableUpdateCheck: !task.allowDecrypt
|
disableUpdateCheck: !task.allowDecrypt,
|
||||||
|
allowCacheIndex: task.allowCacheIndex
|
||||||
})
|
})
|
||||||
if (cached.success) return
|
if (cached.success) return
|
||||||
if (!task.allowDecrypt) return
|
if (!task.allowDecrypt) return
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export class KeyService {
|
|||||||
|
|
||||||
private getDllPath(): string {
|
private getDllPath(): string {
|
||||||
const isPackaged = typeof app !== 'undefined' && app ? app.isPackaged : process.env.NODE_ENV === 'production'
|
const isPackaged = typeof app !== 'undefined' && app ? app.isPackaged : process.env.NODE_ENV === 'production'
|
||||||
|
const archDir = process.arch === 'arm64' ? 'arm64' : 'x64'
|
||||||
const candidates: string[] = []
|
const candidates: string[] = []
|
||||||
|
|
||||||
if (process.env.WX_KEY_DLL_PATH) {
|
if (process.env.WX_KEY_DLL_PATH) {
|
||||||
@@ -68,11 +69,20 @@ export class KeyService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isPackaged) {
|
if (isPackaged) {
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'win32', archDir, 'wx_key.dll'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'win32', 'x64', 'wx_key.dll'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'win32', 'wx_key.dll'))
|
||||||
candidates.push(join(process.resourcesPath, 'resources', 'wx_key.dll'))
|
candidates.push(join(process.resourcesPath, 'resources', 'wx_key.dll'))
|
||||||
candidates.push(join(process.resourcesPath, 'wx_key.dll'))
|
candidates.push(join(process.resourcesPath, 'wx_key.dll'))
|
||||||
} else {
|
} else {
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'win32', archDir, 'wx_key.dll'))
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'win32', 'x64', 'wx_key.dll'))
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'win32', 'wx_key.dll'))
|
||||||
candidates.push(join(cwd, 'resources', 'wx_key.dll'))
|
candidates.push(join(cwd, 'resources', 'wx_key.dll'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'win32', archDir, 'wx_key.dll'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'win32', 'x64', 'wx_key.dll'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'win32', 'wx_key.dll'))
|
||||||
candidates.push(join(app.getAppPath(), 'resources', 'wx_key.dll'))
|
candidates.push(join(app.getAppPath(), 'resources', 'wx_key.dll'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,23 @@ export class KeyServiceLinux {
|
|||||||
|
|
||||||
private getHelperPath(): string {
|
private getHelperPath(): string {
|
||||||
const isPackaged = app.isPackaged
|
const isPackaged = app.isPackaged
|
||||||
|
const archDir = process.arch === 'arm64' ? 'arm64' : 'x64'
|
||||||
const candidates: string[] = []
|
const candidates: string[] = []
|
||||||
if (process.env.WX_KEY_HELPER_PATH) candidates.push(process.env.WX_KEY_HELPER_PATH)
|
if (process.env.WX_KEY_HELPER_PATH) candidates.push(process.env.WX_KEY_HELPER_PATH)
|
||||||
if (isPackaged) {
|
if (isPackaged) {
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'linux', archDir, 'xkey_helper_linux'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'linux', 'x64', 'xkey_helper_linux'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'linux', 'xkey_helper_linux'))
|
||||||
candidates.push(join(process.resourcesPath, 'resources', 'xkey_helper_linux'))
|
candidates.push(join(process.resourcesPath, 'resources', 'xkey_helper_linux'))
|
||||||
candidates.push(join(process.resourcesPath, 'xkey_helper_linux'))
|
candidates.push(join(process.resourcesPath, 'xkey_helper_linux'))
|
||||||
} else {
|
} else {
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'linux', archDir, 'xkey_helper_linux'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'linux', 'x64', 'xkey_helper_linux'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'linux', 'xkey_helper_linux'))
|
||||||
candidates.push(join(app.getAppPath(), 'resources', 'xkey_helper_linux'))
|
candidates.push(join(app.getAppPath(), 'resources', 'xkey_helper_linux'))
|
||||||
|
candidates.push(join(process.cwd(), 'resources', 'key', 'linux', archDir, 'xkey_helper_linux'))
|
||||||
|
candidates.push(join(process.cwd(), 'resources', 'key', 'linux', 'x64', 'xkey_helper_linux'))
|
||||||
|
candidates.push(join(process.cwd(), 'resources', 'key', 'linux', 'xkey_helper_linux'))
|
||||||
candidates.push(join(app.getAppPath(), '..', 'Xkey', 'build', 'xkey_helper_linux'))
|
candidates.push(join(app.getAppPath(), '..', 'Xkey', 'build', 'xkey_helper_linux'))
|
||||||
}
|
}
|
||||||
for (const p of candidates) {
|
for (const p of candidates) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export class KeyServiceMac {
|
|||||||
|
|
||||||
private getHelperPath(): string {
|
private getHelperPath(): string {
|
||||||
const isPackaged = app.isPackaged
|
const isPackaged = app.isPackaged
|
||||||
|
const archDir = process.arch === 'arm64' ? 'arm64' : 'x64'
|
||||||
const candidates: string[] = []
|
const candidates: string[] = []
|
||||||
|
|
||||||
if (process.env.WX_KEY_HELPER_PATH) {
|
if (process.env.WX_KEY_HELPER_PATH) {
|
||||||
@@ -34,12 +35,21 @@ export class KeyServiceMac {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isPackaged) {
|
if (isPackaged) {
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', archDir, 'xkey_helper'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'universal', 'xkey_helper'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'xkey_helper'))
|
||||||
candidates.push(join(process.resourcesPath, 'resources', 'xkey_helper'))
|
candidates.push(join(process.resourcesPath, 'resources', 'xkey_helper'))
|
||||||
candidates.push(join(process.resourcesPath, 'xkey_helper'))
|
candidates.push(join(process.resourcesPath, 'xkey_helper'))
|
||||||
} else {
|
} else {
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'macos', archDir, 'xkey_helper'))
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'macos', 'universal', 'xkey_helper'))
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'macos', 'xkey_helper'))
|
||||||
candidates.push(join(cwd, 'resources', 'xkey_helper'))
|
candidates.push(join(cwd, 'resources', 'xkey_helper'))
|
||||||
candidates.push(join(cwd, 'Xkey', 'build', 'xkey_helper'))
|
candidates.push(join(cwd, 'Xkey', 'build', 'xkey_helper'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', archDir, 'xkey_helper'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'universal', 'xkey_helper'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'xkey_helper'))
|
||||||
candidates.push(join(app.getAppPath(), 'resources', 'xkey_helper'))
|
candidates.push(join(app.getAppPath(), 'resources', 'xkey_helper'))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,14 +62,24 @@ export class KeyServiceMac {
|
|||||||
|
|
||||||
private getImageScanHelperPath(): string {
|
private getImageScanHelperPath(): string {
|
||||||
const isPackaged = app.isPackaged
|
const isPackaged = app.isPackaged
|
||||||
|
const archDir = process.arch === 'arm64' ? 'arm64' : 'x64'
|
||||||
const candidates: string[] = []
|
const candidates: string[] = []
|
||||||
|
|
||||||
if (isPackaged) {
|
if (isPackaged) {
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', archDir, 'image_scan_helper'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'universal', 'image_scan_helper'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'image_scan_helper'))
|
||||||
candidates.push(join(process.resourcesPath, 'resources', 'image_scan_helper'))
|
candidates.push(join(process.resourcesPath, 'resources', 'image_scan_helper'))
|
||||||
candidates.push(join(process.resourcesPath, 'image_scan_helper'))
|
candidates.push(join(process.resourcesPath, 'image_scan_helper'))
|
||||||
} else {
|
} else {
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'macos', archDir, 'image_scan_helper'))
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'macos', 'universal', 'image_scan_helper'))
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'macos', 'image_scan_helper'))
|
||||||
candidates.push(join(cwd, 'resources', 'image_scan_helper'))
|
candidates.push(join(cwd, 'resources', 'image_scan_helper'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', archDir, 'image_scan_helper'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'universal', 'image_scan_helper'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'image_scan_helper'))
|
||||||
candidates.push(join(app.getAppPath(), 'resources', 'image_scan_helper'))
|
candidates.push(join(app.getAppPath(), 'resources', 'image_scan_helper'))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +92,7 @@ export class KeyServiceMac {
|
|||||||
|
|
||||||
private getDylibPath(): string {
|
private getDylibPath(): string {
|
||||||
const isPackaged = app.isPackaged
|
const isPackaged = app.isPackaged
|
||||||
|
const archDir = process.arch === 'arm64' ? 'arm64' : 'x64'
|
||||||
const candidates: string[] = []
|
const candidates: string[] = []
|
||||||
|
|
||||||
if (process.env.WX_KEY_DYLIB_PATH) {
|
if (process.env.WX_KEY_DYLIB_PATH) {
|
||||||
@@ -79,11 +100,20 @@ export class KeyServiceMac {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isPackaged) {
|
if (isPackaged) {
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', archDir, 'libwx_key.dylib'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'universal', 'libwx_key.dylib'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'libwx_key.dylib'))
|
||||||
candidates.push(join(process.resourcesPath, 'resources', 'libwx_key.dylib'))
|
candidates.push(join(process.resourcesPath, 'resources', 'libwx_key.dylib'))
|
||||||
candidates.push(join(process.resourcesPath, 'libwx_key.dylib'))
|
candidates.push(join(process.resourcesPath, 'libwx_key.dylib'))
|
||||||
} else {
|
} else {
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'macos', archDir, 'libwx_key.dylib'))
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'macos', 'universal', 'libwx_key.dylib'))
|
||||||
|
candidates.push(join(cwd, 'resources', 'key', 'macos', 'libwx_key.dylib'))
|
||||||
candidates.push(join(cwd, 'resources', 'libwx_key.dylib'))
|
candidates.push(join(cwd, 'resources', 'libwx_key.dylib'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', archDir, 'libwx_key.dylib'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'universal', 'libwx_key.dylib'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'libwx_key.dylib'))
|
||||||
candidates.push(join(app.getAppPath(), 'resources', 'libwx_key.dylib'))
|
candidates.push(join(app.getAppPath(), 'resources', 'libwx_key.dylib'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ export class WcdbCore {
|
|||||||
private videoHardlinkCache: Map<string, { result: { success: boolean; data?: any; error?: string }; updatedAt: number }> = new Map()
|
private videoHardlinkCache: Map<string, { result: { success: boolean; data?: any; error?: string }; updatedAt: number }> = new Map()
|
||||||
private readonly hardlinkCacheTtlMs = 10 * 60 * 1000
|
private readonly hardlinkCacheTtlMs = 10 * 60 * 1000
|
||||||
private readonly hardlinkCacheMaxEntries = 20000
|
private readonly hardlinkCacheMaxEntries = 20000
|
||||||
|
private mediaStreamSessionCache: Array<{ sessionId: string; displayName: string; sortTimestamp: number }> | null = null
|
||||||
|
private mediaStreamSessionCacheAt = 0
|
||||||
|
private readonly mediaStreamSessionCacheTtlMs = 12 * 1000
|
||||||
private logTimer: NodeJS.Timeout | null = null
|
private logTimer: NodeJS.Timeout | null = null
|
||||||
private lastLogTail: string | null = null
|
private lastLogTail: string | null = null
|
||||||
private lastResolvedLogPath: string | null = null
|
private lastResolvedLogPath: string | null = null
|
||||||
@@ -277,7 +280,9 @@ export class WcdbCore {
|
|||||||
const isLinux = process.platform === 'linux'
|
const isLinux = process.platform === 'linux'
|
||||||
const isArm64 = process.arch === 'arm64'
|
const isArm64 = process.arch === 'arm64'
|
||||||
const libName = isMac ? 'libwcdb_api.dylib' : isLinux ? 'libwcdb_api.so' : 'wcdb_api.dll'
|
const libName = isMac ? 'libwcdb_api.dylib' : isLinux ? 'libwcdb_api.so' : 'wcdb_api.dll'
|
||||||
const subDir = isMac ? 'macos' : isLinux ? 'linux' : (isArm64 ? 'arm64' : '')
|
const legacySubDir = isMac ? 'macos' : isLinux ? 'linux' : (isArm64 ? 'arm64' : '')
|
||||||
|
const platformDir = isMac ? 'macos' : (isLinux ? 'linux' : 'win32')
|
||||||
|
const archDir = isMac ? 'universal' : (isArm64 ? 'arm64' : 'x64')
|
||||||
|
|
||||||
const envDllPath = process.env.WCDB_DLL_PATH
|
const envDllPath = process.env.WCDB_DLL_PATH
|
||||||
if (envDllPath && envDllPath.length > 0) {
|
if (envDllPath && envDllPath.length > 0) {
|
||||||
@@ -287,20 +292,33 @@ export class WcdbCore {
|
|||||||
// 基础路径探测
|
// 基础路径探测
|
||||||
const isPackaged = typeof process['resourcesPath'] !== 'undefined'
|
const isPackaged = typeof process['resourcesPath'] !== 'undefined'
|
||||||
const resourcesPath = isPackaged ? process.resourcesPath : join(process.cwd(), 'resources')
|
const resourcesPath = isPackaged ? process.resourcesPath : join(process.cwd(), 'resources')
|
||||||
|
const roots = [
|
||||||
const candidates = [
|
process.env.WCDB_RESOURCES_PATH || null,
|
||||||
// 环境变量指定 resource 目录
|
this.resourcesPath || null,
|
||||||
process.env.WCDB_RESOURCES_PATH ? join(process.env.WCDB_RESOURCES_PATH, subDir, libName) : null,
|
join(resourcesPath, 'resources'),
|
||||||
// 显式 setPaths 设置的路径
|
resourcesPath,
|
||||||
this.resourcesPath ? join(this.resourcesPath, subDir, libName) : null,
|
join(process.cwd(), 'resources')
|
||||||
// resources/macos/libwcdb_api.dylib 或 resources/wcdb_api.dll
|
|
||||||
join(resourcesPath, 'resources', subDir, libName),
|
|
||||||
// resources/libwcdb_api.dylib 或 resources/wcdb_api.dll (扁平结构)
|
|
||||||
join(resourcesPath, subDir, libName),
|
|
||||||
// CWD fallback
|
|
||||||
join(process.cwd(), 'resources', subDir, libName)
|
|
||||||
].filter(Boolean) as string[]
|
].filter(Boolean) as string[]
|
||||||
|
|
||||||
|
const normalizedArch = process.arch === 'arm64' ? 'arm64' : 'x64'
|
||||||
|
const relativeCandidates = [
|
||||||
|
join('wcdb', platformDir, archDir, libName),
|
||||||
|
join('wcdb', platformDir, normalizedArch, libName),
|
||||||
|
join('wcdb', platformDir, 'x64', libName),
|
||||||
|
join('wcdb', platformDir, 'universal', libName),
|
||||||
|
join('wcdb', platformDir, libName)
|
||||||
|
]
|
||||||
|
|
||||||
|
const candidates: string[] = []
|
||||||
|
for (const root of roots) {
|
||||||
|
for (const relativePath of relativeCandidates) {
|
||||||
|
candidates.push(join(root, relativePath))
|
||||||
|
}
|
||||||
|
// 兼容旧目录:resources/macos/libwcdb_api.dylib 或 resources/wcdb_api.dll
|
||||||
|
candidates.push(join(root, legacySubDir, libName))
|
||||||
|
candidates.push(join(root, libName))
|
||||||
|
}
|
||||||
|
|
||||||
for (const path of candidates) {
|
for (const path of candidates) {
|
||||||
if (existsSync(path)) return path
|
if (existsSync(path)) return path
|
||||||
}
|
}
|
||||||
@@ -1465,6 +1483,11 @@ export class WcdbCore {
|
|||||||
this.videoHardlinkCache.clear()
|
this.videoHardlinkCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private clearMediaStreamSessionCache(): void {
|
||||||
|
this.mediaStreamSessionCache = null
|
||||||
|
this.mediaStreamSessionCacheAt = 0
|
||||||
|
}
|
||||||
|
|
||||||
isReady(): boolean {
|
isReady(): boolean {
|
||||||
return this.ensureReady()
|
return this.ensureReady()
|
||||||
}
|
}
|
||||||
@@ -1580,6 +1603,7 @@ export class WcdbCore {
|
|||||||
this.currentDbStoragePath = null
|
this.currentDbStoragePath = null
|
||||||
this.initialized = false
|
this.initialized = false
|
||||||
this.clearHardlinkCaches()
|
this.clearHardlinkCaches()
|
||||||
|
this.clearMediaStreamSessionCache()
|
||||||
this.stopLogPolling()
|
this.stopLogPolling()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1957,7 +1981,7 @@ export class WcdbCore {
|
|||||||
error?: string
|
error?: string
|
||||||
}> {
|
}> {
|
||||||
if (!this.ensureReady()) return { success: false, error: 'WCDB 未连接' }
|
if (!this.ensureReady()) return { success: false, error: 'WCDB 未连接' }
|
||||||
if (!this.wcdbScanMediaStream) return { success: false, error: '当前数据服务版本不支持媒体流扫描,请先更新 wcdb 数据服务' }
|
if (!this.wcdbScanMediaStream) return { success: false, error: '当前数据服务版本不支持资源扫描,请先更新 wcdb 数据服务' }
|
||||||
try {
|
try {
|
||||||
const toInt = (value: unknown): number => {
|
const toInt = (value: unknown): number => {
|
||||||
const n = Number(value || 0)
|
const n = Number(value || 0)
|
||||||
@@ -2168,12 +2192,26 @@ export class WcdbCore {
|
|||||||
const offset = Math.max(0, toInt(options?.offset))
|
const offset = Math.max(0, toInt(options?.offset))
|
||||||
const limit = Math.min(1200, Math.max(40, toInt(options?.limit) || 240))
|
const limit = Math.min(1200, Math.max(40, toInt(options?.limit) || 240))
|
||||||
|
|
||||||
|
const getSessionRows = async (): Promise<{
|
||||||
|
success: boolean
|
||||||
|
rows?: Array<{ sessionId: string; displayName: string; sortTimestamp: number }>
|
||||||
|
error?: string
|
||||||
|
}> => {
|
||||||
|
const now = Date.now()
|
||||||
|
const cachedRows = this.mediaStreamSessionCache
|
||||||
|
if (
|
||||||
|
cachedRows &&
|
||||||
|
now - this.mediaStreamSessionCacheAt <= this.mediaStreamSessionCacheTtlMs
|
||||||
|
) {
|
||||||
|
return { success: true, rows: cachedRows }
|
||||||
|
}
|
||||||
|
|
||||||
const sessionsRes = await this.getSessions()
|
const sessionsRes = await this.getSessions()
|
||||||
if (!sessionsRes.success || !Array.isArray(sessionsRes.sessions)) {
|
if (!sessionsRes.success || !Array.isArray(sessionsRes.sessions)) {
|
||||||
return { success: false, error: sessionsRes.error || '读取会话失败' }
|
return { success: false, error: sessionsRes.error || '读取会话失败' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessions = (sessionsRes.sessions || [])
|
const rows = (sessionsRes.sessions || [])
|
||||||
.map((row: any) => ({
|
.map((row: any) => ({
|
||||||
sessionId: String(
|
sessionId: String(
|
||||||
row.username ||
|
row.username ||
|
||||||
@@ -2196,9 +2234,22 @@ export class WcdbCore {
|
|||||||
.filter((row) => Boolean(row.sessionId))
|
.filter((row) => Boolean(row.sessionId))
|
||||||
.sort((a, b) => b.sortTimestamp - a.sortTimestamp)
|
.sort((a, b) => b.sortTimestamp - a.sortTimestamp)
|
||||||
|
|
||||||
const sessionRows = requestedSessionId
|
this.mediaStreamSessionCache = rows
|
||||||
? sessions.filter((row) => row.sessionId === requestedSessionId)
|
this.mediaStreamSessionCacheAt = now
|
||||||
: sessions
|
return { success: true, rows }
|
||||||
|
}
|
||||||
|
|
||||||
|
let sessionRows: Array<{ sessionId: string; displayName: string; sortTimestamp: number }> = []
|
||||||
|
if (requestedSessionId) {
|
||||||
|
sessionRows = [{ sessionId: requestedSessionId, displayName: requestedSessionId, sortTimestamp: 0 }]
|
||||||
|
} else {
|
||||||
|
const sessionsRowsRes = await getSessionRows()
|
||||||
|
if (!sessionsRowsRes.success || !Array.isArray(sessionsRowsRes.rows)) {
|
||||||
|
return { success: false, error: sessionsRowsRes.error || '读取会话失败' }
|
||||||
|
}
|
||||||
|
sessionRows = sessionsRowsRes.rows
|
||||||
|
}
|
||||||
|
|
||||||
if (sessionRows.length === 0) {
|
if (sessionRows.length === 0) {
|
||||||
return { success: true, items: [], hasMore: false, nextOffset: offset }
|
return { success: true, items: [], hasMore: false, nextOffset: offset }
|
||||||
}
|
}
|
||||||
@@ -2219,10 +2270,10 @@ export class WcdbCore {
|
|||||||
outHasMore
|
outHasMore
|
||||||
)
|
)
|
||||||
if (result !== 0 || !outPtr[0]) {
|
if (result !== 0 || !outPtr[0]) {
|
||||||
return { success: false, error: `扫描媒体流失败: ${result}` }
|
return { success: false, error: `扫描资源失败: ${result}` }
|
||||||
}
|
}
|
||||||
const jsonStr = this.decodeJsonPtr(outPtr[0])
|
const jsonStr = this.decodeJsonPtr(outPtr[0])
|
||||||
if (!jsonStr) return { success: false, error: '解析媒体流失败' }
|
if (!jsonStr) return { success: false, error: '解析资源失败' }
|
||||||
const rows = JSON.parse(jsonStr)
|
const rows = JSON.parse(jsonStr)
|
||||||
const list = Array.isArray(rows) ? rows as Array<Record<string, any>> : []
|
const list = Array.isArray(rows) ? rows as Array<Record<string, any>> : []
|
||||||
|
|
||||||
@@ -2254,19 +2305,39 @@ export class WcdbCore {
|
|||||||
rawMessageContent &&
|
rawMessageContent &&
|
||||||
(rawMessageContent.includes('<') || rawMessageContent.includes('md5') || rawMessageContent.includes('videomsg'))
|
(rawMessageContent.includes('<') || rawMessageContent.includes('md5') || rawMessageContent.includes('videomsg'))
|
||||||
)
|
)
|
||||||
const content = useRawMessageContent
|
const decodeContentIfNeeded = (): string => {
|
||||||
? rawMessageContent
|
if (useRawMessageContent) return rawMessageContent
|
||||||
: decodeMessageContent(rawMessageContent, rawCompressContent)
|
if (!rawMessageContent && !rawCompressContent) return ''
|
||||||
|
return decodeMessageContent(rawMessageContent, rawCompressContent)
|
||||||
|
}
|
||||||
const packedPayload = extractPackedPayload(row)
|
const packedPayload = extractPackedPayload(row)
|
||||||
const imageMd5ByColumn = pickString(row, ['image_md5', 'imageMd5'])
|
const imageMd5ByColumn = pickString(row, ['image_md5', 'imageMd5'])
|
||||||
const imageMd5 = localType === 3
|
|
||||||
? (imageMd5ByColumn || extractImageMd5(content) || extractHexMd5(packedPayload) || undefined)
|
|
||||||
: undefined
|
|
||||||
const imageDatName = localType === 3 ? (extractImageDatName(row, content) || undefined) : undefined
|
|
||||||
const videoMd5ByColumn = pickString(row, ['video_md5', 'videoMd5', 'raw_md5', 'rawMd5'])
|
const videoMd5ByColumn = pickString(row, ['video_md5', 'videoMd5', 'raw_md5', 'rawMd5'])
|
||||||
const videoMd5 = localType === 43
|
|
||||||
? (videoMd5ByColumn || extractVideoMd5(content) || extractHexMd5(packedPayload) || undefined)
|
let content = ''
|
||||||
: undefined
|
let imageMd5: string | undefined
|
||||||
|
let imageDatName: string | undefined
|
||||||
|
let videoMd5: string | undefined
|
||||||
|
|
||||||
|
if (localType === 3) {
|
||||||
|
imageMd5 = imageMd5ByColumn || extractHexMd5(packedPayload) || undefined
|
||||||
|
imageDatName = extractImageDatName(row, '') || undefined
|
||||||
|
if (!imageMd5 || !imageDatName) {
|
||||||
|
content = decodeContentIfNeeded()
|
||||||
|
if (!imageMd5) imageMd5 = extractImageMd5(content) || extractHexMd5(packedPayload) || undefined
|
||||||
|
if (!imageDatName) imageDatName = extractImageDatName(row, content) || undefined
|
||||||
|
}
|
||||||
|
} else if (localType === 43) {
|
||||||
|
videoMd5 = videoMd5ByColumn || extractHexMd5(packedPayload) || undefined
|
||||||
|
if (!videoMd5) {
|
||||||
|
content = decodeContentIfNeeded()
|
||||||
|
videoMd5 = extractVideoMd5(content) || extractHexMd5(packedPayload) || undefined
|
||||||
|
} else if (useRawMessageContent) {
|
||||||
|
// 占位态标题只依赖简单 XML,已带 md5 时不做额外解压
|
||||||
|
content = rawMessageContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sessionId,
|
sessionId,
|
||||||
sessionDisplayName: sessionNameMap.get(sessionId) || sessionId,
|
sessionDisplayName: sessionNameMap.get(sessionId) || sessionId,
|
||||||
@@ -2280,7 +2351,7 @@ export class WcdbCore {
|
|||||||
imageMd5,
|
imageMd5,
|
||||||
imageDatName,
|
imageDatName,
|
||||||
videoMd5,
|
videoMd5,
|
||||||
content: content || undefined
|
content: localType === 43 ? (content || undefined) : undefined
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
14
package.json
14
package.json
@@ -98,7 +98,7 @@
|
|||||||
"gatekeeperAssess": false,
|
"gatekeeperAssess": false,
|
||||||
"entitlements": "electron/entitlements.mac.plist",
|
"entitlements": "electron/entitlements.mac.plist",
|
||||||
"entitlementsInherit": "electron/entitlements.mac.plist",
|
"entitlementsInherit": "electron/entitlements.mac.plist",
|
||||||
"icon": "resources/icon.icns"
|
"icon": "resources/icons/macos/icon.icns"
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": [
|
"target": [
|
||||||
@@ -107,19 +107,19 @@
|
|||||||
"icon": "public/icon.ico",
|
"icon": "public/icon.ico",
|
||||||
"extraFiles": [
|
"extraFiles": [
|
||||||
{
|
{
|
||||||
"from": "resources/msvcp140.dll",
|
"from": "resources/runtime/win32/msvcp140.dll",
|
||||||
"to": "."
|
"to": "."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "resources/msvcp140_1.dll",
|
"from": "resources/runtime/win32/msvcp140_1.dll",
|
||||||
"to": "."
|
"to": "."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "resources/vcruntime140.dll",
|
"from": "resources/runtime/win32/vcruntime140.dll",
|
||||||
"to": "."
|
"to": "."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "resources/vcruntime140_1.dll",
|
"from": "resources/runtime/win32/vcruntime140_1.dll",
|
||||||
"to": "."
|
"to": "."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
"synopsis": "WeFlow for Linux",
|
"synopsis": "WeFlow for Linux",
|
||||||
"extraFiles": [
|
"extraFiles": [
|
||||||
{
|
{
|
||||||
"from": "resources/linux/install.sh",
|
"from": "resources/installer/linux/install.sh",
|
||||||
"to": "install.sh"
|
"to": "install.sh"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
"node_modules/sherpa-onnx-*/**/*",
|
"node_modules/sherpa-onnx-*/**/*",
|
||||||
"node_modules/ffmpeg-static/**/*"
|
"node_modules/ffmpeg-static/**/*"
|
||||||
],
|
],
|
||||||
"icon": "resources/icon.icns"
|
"icon": "resources/icons/macos/icon.icns"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"picomatch": "^4.0.4",
|
"picomatch": "^4.0.4",
|
||||||
|
|||||||
Binary file not shown.
0
resources/xkey_helper_linux → resources/key/linux/x64/xkey_helper_linux
Executable file → Normal file
0
resources/xkey_helper_linux → resources/key/linux/x64/xkey_helper_linux
Executable file → Normal file
0
resources/image_scan_helper → resources/key/macos/universal/image_scan_helper
Executable file → Normal file
0
resources/image_scan_helper → resources/key/macos/universal/image_scan_helper
Executable file → Normal file
0
resources/libwx_key.dylib → resources/key/macos/universal/libwx_key.dylib
Executable file → Normal file
0
resources/libwx_key.dylib → resources/key/macos/universal/libwx_key.dylib
Executable file → Normal file
0
resources/xkey_helper → resources/key/macos/universal/xkey_helper
Executable file → Normal file
0
resources/xkey_helper → resources/key/macos/universal/xkey_helper
Executable file → Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
resources/linux/libwcdb_api.so → resources/wcdb/linux/x64/libwcdb_api.so
Executable file → Normal file
BIN
resources/linux/libwcdb_api.so → resources/wcdb/linux/x64/libwcdb_api.so
Executable file → Normal file
Binary file not shown.
0
resources/macos/libWCDB.dylib → resources/wcdb/macos/universal/libWCDB.dylib
Executable file → Normal file
0
resources/macos/libWCDB.dylib → resources/wcdb/macos/universal/libWCDB.dylib
Executable file → Normal file
BIN
resources/wcdb/macos/universal/libwcdb_api.dylib
Normal file
BIN
resources/wcdb/macos/universal/libwcdb_api.dylib
Normal file
Binary file not shown.
BIN
resources/wcdb/win32/arm64/wcdb_api.dll
Normal file
BIN
resources/wcdb/win32/arm64/wcdb_api.dll
Normal file
Binary file not shown.
@@ -1965,6 +1965,10 @@
|
|||||||
color: var(--on-primary);
|
color: var(--on-primary);
|
||||||
border-radius: 18px 18px 4px 18px;
|
border-radius: 18px 18px 4px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bubble-body {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对方发送的消息 - 左侧白色
|
// 对方发送的消息 - 左侧白色
|
||||||
@@ -1974,6 +1978,10 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
border-radius: 18px 18px 18px 4px;
|
border-radius: 18px 18px 18px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bubble-body {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.system {
|
&.system {
|
||||||
@@ -2038,6 +2046,12 @@
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 让文字气泡按内容收缩,不被群昵称行宽度牵连
|
||||||
|
.message-bubble:not(.system) .bubble-content {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
// 表情包消息
|
// 表情包消息
|
||||||
.message-bubble.emoji {
|
.message-bubble.emoji {
|
||||||
.bubble-content {
|
.bubble-content {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState, type HTMLAttributes } from 'react'
|
import { forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState, type HTMLAttributes } from 'react'
|
||||||
import { Calendar, Image as ImageIcon, Loader2, PlayCircle, RefreshCw, Trash2, UserRound } from 'lucide-react'
|
import { Calendar, Image as ImageIcon, Loader2, PlayCircle, RefreshCw, Trash2, UserRound } from 'lucide-react'
|
||||||
import { VirtuosoGrid } from 'react-virtuoso'
|
import { VirtuosoGrid } from 'react-virtuoso'
|
||||||
|
import { finishBackgroundTask, registerBackgroundTask, updateBackgroundTask } from '../services/backgroundTaskMonitor'
|
||||||
import './ResourcesPage.scss'
|
import './ResourcesPage.scss'
|
||||||
|
|
||||||
type MediaTab = 'image' | 'video'
|
type MediaTab = 'image' | 'video'
|
||||||
@@ -35,10 +36,14 @@ type DialogState = {
|
|||||||
onConfirm?: (() => void) | null
|
onConfirm?: (() => void) | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_SIZE = 120
|
const PAGE_SIZE = 96
|
||||||
const MAX_IMAGE_CACHE_RESOLVE_PER_TICK = 18
|
const MAX_IMAGE_CACHE_RESOLVE_PER_TICK = 12
|
||||||
const MAX_IMAGE_CACHE_PRELOAD_PER_TICK = 36
|
const MAX_IMAGE_CACHE_PRELOAD_PER_TICK = 24
|
||||||
const MAX_VIDEO_POSTER_RESOLVE_PER_TICK = 4
|
const MAX_VIDEO_POSTER_RESOLVE_PER_TICK = 3
|
||||||
|
const INITIAL_IMAGE_PRELOAD_END = 48
|
||||||
|
const INITIAL_IMAGE_RESOLVE_END = 12
|
||||||
|
const TASK_PROGRESS_UPDATE_MIN_INTERVAL_MS = 250
|
||||||
|
const TASK_PROGRESS_UPDATE_MAX_STEPS = 100
|
||||||
|
|
||||||
const GridList = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function GridList(props, ref) {
|
const GridList = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function GridList(props, ref) {
|
||||||
const { className = '', ...rest } = props
|
const { className = '', ...rest } = props
|
||||||
@@ -409,7 +414,13 @@ function ResourcesPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.electronAPI.chat.connect()
|
if (reset) {
|
||||||
|
const connectResult = await window.electronAPI.chat.connect()
|
||||||
|
if (!connectResult.success) {
|
||||||
|
setError(connectResult.error || '连接数据库失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
const requestOffset = reset ? 0 : nextOffset
|
const requestOffset = reset ? 0 : nextOffset
|
||||||
const streamResult = await window.electronAPI.chat.getMediaStream({
|
const streamResult = await window.electronAPI.chat.getMediaStream({
|
||||||
sessionId: selectedContact === 'all' ? undefined : selectedContact,
|
sessionId: selectedContact === 'all' ? undefined : selectedContact,
|
||||||
@@ -524,7 +535,6 @@ function ResourcesPage() {
|
|||||||
let cancelled = false
|
let cancelled = false
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
try {
|
try {
|
||||||
await window.electronAPI.chat.connect()
|
|
||||||
const sessionResult = await window.electronAPI.chat.getSessions()
|
const sessionResult = await window.electronAPI.chat.getSessions()
|
||||||
if (!cancelled && sessionResult.success && Array.isArray(sessionResult.sessions)) {
|
if (!cancelled && sessionResult.success && Array.isArray(sessionResult.sessions)) {
|
||||||
const initialNameMap: Record<string, string> = {}
|
const initialNameMap: Record<string, string> = {}
|
||||||
@@ -674,7 +684,10 @@ function ResourcesPage() {
|
|||||||
resolvingImageCacheBatchRef.current = true
|
resolvingImageCacheBatchRef.current = true
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const result = await window.electronAPI.image.resolveCacheBatch(payloads, { disableUpdateCheck: true })
|
const result = await window.electronAPI.image.resolveCacheBatch(payloads, {
|
||||||
|
disableUpdateCheck: true,
|
||||||
|
allowCacheIndex: false
|
||||||
|
})
|
||||||
const rows = Array.isArray(result?.rows) ? result.rows : []
|
const rows = Array.isArray(result?.rows) ? result.rows : []
|
||||||
const pathPatch: Record<string, string> = {}
|
const pathPatch: Record<string, string> = {}
|
||||||
const updatePatch: Record<string, boolean> = {}
|
const updatePatch: Record<string, boolean> = {}
|
||||||
@@ -741,7 +754,10 @@ function ResourcesPage() {
|
|||||||
if (payloads.length >= MAX_IMAGE_CACHE_PRELOAD_PER_TICK) break
|
if (payloads.length >= MAX_IMAGE_CACHE_PRELOAD_PER_TICK) break
|
||||||
}
|
}
|
||||||
if (payloads.length === 0) return
|
if (payloads.length === 0) return
|
||||||
void window.electronAPI.image.preload(payloads, { allowDecrypt: false })
|
void window.electronAPI.image.preload(payloads, {
|
||||||
|
allowDecrypt: false,
|
||||||
|
allowCacheIndex: false
|
||||||
|
})
|
||||||
}, [displayItems])
|
}, [displayItems])
|
||||||
|
|
||||||
const resolveItemVideoMd5 = useCallback(async (item: MediaStreamItem): Promise<string> => {
|
const resolveItemVideoMd5 = useCallback(async (item: MediaStreamItem): Promise<string> => {
|
||||||
@@ -813,14 +829,18 @@ function ResourcesPage() {
|
|||||||
if (!pending) return
|
if (!pending) return
|
||||||
pendingRangeRef.current = null
|
pendingRangeRef.current = null
|
||||||
if (tab === 'image') {
|
if (tab === 'image') {
|
||||||
preloadImageCacheRange(pending.start - 8, pending.end + 32)
|
preloadImageCacheRange(pending.start - 4, pending.end + 20)
|
||||||
resolveImageCacheRange(pending.start - 2, pending.end + 8)
|
resolveImageCacheRange(pending.start - 1, pending.end + 6)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resolvePosterRange(pending.start, pending.end)
|
resolvePosterRange(pending.start, pending.end)
|
||||||
}, [preloadImageCacheRange, resolveImageCacheRange, resolvePosterRange, tab])
|
}, [preloadImageCacheRange, resolveImageCacheRange, resolvePosterRange, tab])
|
||||||
|
|
||||||
const scheduleRangeResolve = useCallback((start: number, end: number) => {
|
const scheduleRangeResolve = useCallback((start: number, end: number) => {
|
||||||
|
const previous = pendingRangeRef.current
|
||||||
|
if (previous && start >= previous.start && end <= previous.end) {
|
||||||
|
return
|
||||||
|
}
|
||||||
pendingRangeRef.current = { start, end }
|
pendingRangeRef.current = { start, end }
|
||||||
if (rangeTimerRef.current !== null) {
|
if (rangeTimerRef.current !== null) {
|
||||||
window.clearTimeout(rangeTimerRef.current)
|
window.clearTimeout(rangeTimerRef.current)
|
||||||
@@ -832,8 +852,8 @@ function ResourcesPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (displayItems.length === 0) return
|
if (displayItems.length === 0) return
|
||||||
if (tab === 'image') {
|
if (tab === 'image') {
|
||||||
preloadImageCacheRange(0, Math.min(displayItems.length - 1, 80))
|
preloadImageCacheRange(0, Math.min(displayItems.length - 1, INITIAL_IMAGE_PRELOAD_END))
|
||||||
resolveImageCacheRange(0, Math.min(displayItems.length - 1, 20))
|
resolveImageCacheRange(0, Math.min(displayItems.length - 1, INITIAL_IMAGE_RESOLVE_END))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resolvePosterRange(0, Math.min(displayItems.length - 1, 12))
|
resolvePosterRange(0, Math.min(displayItems.length - 1, 12))
|
||||||
@@ -1057,18 +1077,50 @@ function ResourcesPage() {
|
|||||||
|
|
||||||
setBatchBusy(true)
|
setBatchBusy(true)
|
||||||
let success = 0
|
let success = 0
|
||||||
|
let failed = 0
|
||||||
const previewPatch: Record<string, string> = {}
|
const previewPatch: Record<string, string> = {}
|
||||||
const updatePatch: Record<string, boolean> = {}
|
const updatePatch: Record<string, boolean> = {}
|
||||||
|
const taskId = registerBackgroundTask({
|
||||||
|
sourcePage: 'other',
|
||||||
|
title: '资源页图片批量解密',
|
||||||
|
detail: `正在解密图片(0/${imageItems.length})`,
|
||||||
|
progressText: `0 / ${imageItems.length}`,
|
||||||
|
cancelable: false
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
|
let completed = 0
|
||||||
|
const progressStep = Math.max(1, Math.floor(imageItems.length / TASK_PROGRESS_UPDATE_MAX_STEPS))
|
||||||
|
let lastProgressBucket = 0
|
||||||
|
let lastProgressUpdateAt = Date.now()
|
||||||
|
const updateTaskProgress = (force: boolean = false) => {
|
||||||
|
const now = Date.now()
|
||||||
|
const bucket = Math.floor(completed / progressStep)
|
||||||
|
const crossedBucket = bucket !== lastProgressBucket
|
||||||
|
const intervalReached = now - lastProgressUpdateAt >= TASK_PROGRESS_UPDATE_MIN_INTERVAL_MS
|
||||||
|
if (!force && !crossedBucket && !intervalReached) return
|
||||||
|
updateBackgroundTask(taskId, {
|
||||||
|
detail: `正在解密图片(${completed}/${imageItems.length})`,
|
||||||
|
progressText: `${completed} / ${imageItems.length}`
|
||||||
|
})
|
||||||
|
lastProgressBucket = bucket
|
||||||
|
lastProgressUpdateAt = now
|
||||||
|
}
|
||||||
for (const item of imageItems) {
|
for (const item of imageItems) {
|
||||||
if (!item.imageMd5 && !item.imageDatName) continue
|
if (!item.imageMd5 && !item.imageDatName) {
|
||||||
|
failed += 1
|
||||||
|
completed += 1
|
||||||
|
updateTaskProgress()
|
||||||
|
continue
|
||||||
|
}
|
||||||
const result = await window.electronAPI.image.decrypt({
|
const result = await window.electronAPI.image.decrypt({
|
||||||
sessionId: item.sessionId,
|
sessionId: item.sessionId,
|
||||||
imageMd5: item.imageMd5 || undefined,
|
imageMd5: item.imageMd5 || undefined,
|
||||||
imageDatName: item.imageDatName || undefined,
|
imageDatName: item.imageDatName || undefined,
|
||||||
force: true
|
force: true
|
||||||
})
|
})
|
||||||
if (!result?.success) continue
|
if (!result?.success) {
|
||||||
|
failed += 1
|
||||||
|
} else {
|
||||||
success += 1
|
success += 1
|
||||||
if (result.localPath) {
|
if (result.localPath) {
|
||||||
const key = getItemKey(item)
|
const key = getItemKey(item)
|
||||||
@@ -1076,6 +1128,10 @@ function ResourcesPage() {
|
|||||||
updatePatch[key] = isLikelyThumbnailPreview(result.localPath)
|
updatePatch[key] = isLikelyThumbnailPreview(result.localPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
completed += 1
|
||||||
|
updateTaskProgress()
|
||||||
|
}
|
||||||
|
updateTaskProgress(true)
|
||||||
|
|
||||||
if (Object.keys(previewPatch).length > 0) {
|
if (Object.keys(previewPatch).length > 0) {
|
||||||
setPreviewPathMap((prev) => ({ ...prev, ...previewPatch }))
|
setPreviewPathMap((prev) => ({ ...prev, ...previewPatch }))
|
||||||
@@ -1083,8 +1139,17 @@ function ResourcesPage() {
|
|||||||
if (Object.keys(updatePatch).length > 0) {
|
if (Object.keys(updatePatch).length > 0) {
|
||||||
setPreviewUpdateMap((prev) => ({ ...prev, ...updatePatch }))
|
setPreviewUpdateMap((prev) => ({ ...prev, ...updatePatch }))
|
||||||
}
|
}
|
||||||
setActionMessage(`批量解密完成:成功 ${success},失败 ${imageItems.length - success}`)
|
setActionMessage(`批量解密完成:成功 ${success},失败 ${failed}`)
|
||||||
showAlert(`批量解密完成:成功 ${success},失败 ${imageItems.length - success}`, '批量解密完成')
|
showAlert(`批量解密完成:成功 ${success},失败 ${failed}`, '批量解密完成')
|
||||||
|
finishBackgroundTask(taskId, success > 0 || failed === 0 ? 'completed' : 'failed', {
|
||||||
|
detail: `资源页图片批量解密完成:成功 ${success},失败 ${failed}`,
|
||||||
|
progressText: `成功 ${success} / 失败 ${failed}`
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
finishBackgroundTask(taskId, 'failed', {
|
||||||
|
detail: `资源页图片批量解密失败:${String(e)}`
|
||||||
|
})
|
||||||
|
showAlert(`批量解密失败:${String(e)}`, '批量解密失败')
|
||||||
} finally {
|
} finally {
|
||||||
setBatchBusy(false)
|
setBatchBusy(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
|
import {
|
||||||
|
finishBackgroundTask,
|
||||||
|
registerBackgroundTask,
|
||||||
|
updateBackgroundTask
|
||||||
|
} from '../services/backgroundTaskMonitor'
|
||||||
|
import type { BackgroundTaskSourcePage } from '../types/backgroundTask'
|
||||||
|
|
||||||
export interface BatchImageDecryptState {
|
export interface BatchImageDecryptState {
|
||||||
isBatchDecrypting: boolean
|
isBatchDecrypting: boolean
|
||||||
@@ -8,8 +14,9 @@ export interface BatchImageDecryptState {
|
|||||||
result: { success: number; fail: number }
|
result: { success: number; fail: number }
|
||||||
startTime: number
|
startTime: number
|
||||||
sessionName: string
|
sessionName: string
|
||||||
|
taskId: string | null
|
||||||
|
|
||||||
startDecrypt: (total: number, sessionName: string) => void
|
startDecrypt: (total: number, sessionName: string, sourcePage?: BackgroundTaskSourcePage) => void
|
||||||
updateProgress: (current: number, total: number) => void
|
updateProgress: (current: number, total: number) => void
|
||||||
finishDecrypt: (success: number, fail: number) => void
|
finishDecrypt: (success: number, fail: number) => void
|
||||||
setShowToast: (show: boolean) => void
|
setShowToast: (show: boolean) => void
|
||||||
@@ -17,7 +24,26 @@ export interface BatchImageDecryptState {
|
|||||||
reset: () => void
|
reset: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useBatchImageDecryptStore = create<BatchImageDecryptState>((set) => ({
|
const clampProgress = (current: number, total: number): { current: number; total: number } => {
|
||||||
|
const normalizedTotal = Number.isFinite(total) ? Math.max(0, Math.floor(total)) : 0
|
||||||
|
const normalizedCurrentRaw = Number.isFinite(current) ? Math.max(0, Math.floor(current)) : 0
|
||||||
|
const normalizedCurrent = normalizedTotal > 0
|
||||||
|
? Math.min(normalizedCurrentRaw, normalizedTotal)
|
||||||
|
: normalizedCurrentRaw
|
||||||
|
return { current: normalizedCurrent, total: normalizedTotal }
|
||||||
|
}
|
||||||
|
|
||||||
|
const TASK_PROGRESS_UPDATE_MIN_INTERVAL_MS = 250
|
||||||
|
const TASK_PROGRESS_UPDATE_MAX_STEPS = 100
|
||||||
|
|
||||||
|
const taskProgressUpdateMeta = new Map<string, { lastAt: number; lastBucket: number; step: number }>()
|
||||||
|
|
||||||
|
const calcProgressStep = (total: number): number => {
|
||||||
|
if (total <= 0) return 1
|
||||||
|
return Math.max(1, Math.floor(total / TASK_PROGRESS_UPDATE_MAX_STEPS))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useBatchImageDecryptStore = create<BatchImageDecryptState>((set, get) => ({
|
||||||
isBatchDecrypting: false,
|
isBatchDecrypting: false,
|
||||||
progress: { current: 0, total: 0 },
|
progress: { current: 0, total: 0 },
|
||||||
showToast: false,
|
showToast: false,
|
||||||
@@ -25,40 +51,127 @@ export const useBatchImageDecryptStore = create<BatchImageDecryptState>((set) =>
|
|||||||
result: { success: 0, fail: 0 },
|
result: { success: 0, fail: 0 },
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
sessionName: '',
|
sessionName: '',
|
||||||
|
taskId: null,
|
||||||
|
|
||||||
startDecrypt: (total, sessionName) => set({
|
startDecrypt: (total, sessionName, sourcePage = 'chat') => {
|
||||||
|
const previousTaskId = get().taskId
|
||||||
|
if (previousTaskId) {
|
||||||
|
taskProgressUpdateMeta.delete(previousTaskId)
|
||||||
|
finishBackgroundTask(previousTaskId, 'canceled', {
|
||||||
|
detail: '已被新的批量解密任务替换',
|
||||||
|
progressText: '已替换'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedProgress = clampProgress(0, total)
|
||||||
|
const normalizedSessionName = String(sessionName || '').trim()
|
||||||
|
const title = normalizedSessionName
|
||||||
|
? `图片批量解密(${normalizedSessionName})`
|
||||||
|
: '图片批量解密'
|
||||||
|
const taskId = registerBackgroundTask({
|
||||||
|
sourcePage,
|
||||||
|
title,
|
||||||
|
detail: `正在解密图片(${normalizedProgress.current}/${normalizedProgress.total})`,
|
||||||
|
progressText: `${normalizedProgress.current} / ${normalizedProgress.total}`,
|
||||||
|
cancelable: false
|
||||||
|
})
|
||||||
|
taskProgressUpdateMeta.set(taskId, {
|
||||||
|
lastAt: Date.now(),
|
||||||
|
lastBucket: 0,
|
||||||
|
step: calcProgressStep(normalizedProgress.total)
|
||||||
|
})
|
||||||
|
|
||||||
|
set({
|
||||||
isBatchDecrypting: true,
|
isBatchDecrypting: true,
|
||||||
progress: { current: 0, total },
|
progress: normalizedProgress,
|
||||||
showToast: true,
|
showToast: true,
|
||||||
showResultToast: false,
|
showResultToast: false,
|
||||||
result: { success: 0, fail: 0 },
|
result: { success: 0, fail: 0 },
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
sessionName
|
sessionName: normalizedSessionName,
|
||||||
}),
|
taskId
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
updateProgress: (current, total) => set({
|
updateProgress: (current, total) => {
|
||||||
progress: { current, total }
|
const previousProgress = get().progress
|
||||||
}),
|
const normalizedProgress = clampProgress(current, total)
|
||||||
|
const taskId = get().taskId
|
||||||
|
if (taskId) {
|
||||||
|
const now = Date.now()
|
||||||
|
const meta = taskProgressUpdateMeta.get(taskId)
|
||||||
|
const step = meta?.step || calcProgressStep(normalizedProgress.total)
|
||||||
|
const bucket = Math.floor(normalizedProgress.current / step)
|
||||||
|
const intervalReached = !meta || (now - meta.lastAt >= TASK_PROGRESS_UPDATE_MIN_INTERVAL_MS)
|
||||||
|
const crossedBucket = !meta || bucket !== meta.lastBucket
|
||||||
|
const isFinal = normalizedProgress.total > 0 && normalizedProgress.current >= normalizedProgress.total
|
||||||
|
if (crossedBucket || intervalReached || isFinal) {
|
||||||
|
updateBackgroundTask(taskId, {
|
||||||
|
detail: `正在解密图片(${normalizedProgress.current}/${normalizedProgress.total})`,
|
||||||
|
progressText: `${normalizedProgress.current} / ${normalizedProgress.total}`
|
||||||
|
})
|
||||||
|
taskProgressUpdateMeta.set(taskId, {
|
||||||
|
lastAt: now,
|
||||||
|
lastBucket: bucket,
|
||||||
|
step
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
previousProgress.current !== normalizedProgress.current ||
|
||||||
|
previousProgress.total !== normalizedProgress.total
|
||||||
|
) {
|
||||||
|
set({
|
||||||
|
progress: normalizedProgress
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
finishDecrypt: (success, fail) => set({
|
finishDecrypt: (success, fail) => {
|
||||||
|
const taskId = get().taskId
|
||||||
|
const normalizedSuccess = Number.isFinite(success) ? Math.max(0, Math.floor(success)) : 0
|
||||||
|
const normalizedFail = Number.isFinite(fail) ? Math.max(0, Math.floor(fail)) : 0
|
||||||
|
if (taskId) {
|
||||||
|
taskProgressUpdateMeta.delete(taskId)
|
||||||
|
const status = normalizedSuccess > 0 || normalizedFail === 0 ? 'completed' : 'failed'
|
||||||
|
finishBackgroundTask(taskId, status, {
|
||||||
|
detail: `图片批量解密完成:成功 ${normalizedSuccess},失败 ${normalizedFail}`,
|
||||||
|
progressText: `成功 ${normalizedSuccess} / 失败 ${normalizedFail}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
isBatchDecrypting: false,
|
isBatchDecrypting: false,
|
||||||
showToast: false,
|
showToast: false,
|
||||||
showResultToast: true,
|
showResultToast: true,
|
||||||
result: { success, fail },
|
result: { success: normalizedSuccess, fail: normalizedFail },
|
||||||
startTime: 0
|
startTime: 0,
|
||||||
}),
|
taskId: null
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
setShowToast: (show) => set({ showToast: show }),
|
setShowToast: (show) => set({ showToast: show }),
|
||||||
setShowResultToast: (show) => set({ showResultToast: show }),
|
setShowResultToast: (show) => set({ showResultToast: show }),
|
||||||
|
|
||||||
reset: () => set({
|
reset: () => {
|
||||||
|
const taskId = get().taskId
|
||||||
|
if (taskId) {
|
||||||
|
taskProgressUpdateMeta.delete(taskId)
|
||||||
|
finishBackgroundTask(taskId, 'canceled', {
|
||||||
|
detail: '批量解密任务已重置',
|
||||||
|
progressText: '已停止'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
isBatchDecrypting: false,
|
isBatchDecrypting: false,
|
||||||
progress: { current: 0, total: 0 },
|
progress: { current: 0, total: 0 },
|
||||||
showToast: false,
|
showToast: false,
|
||||||
showResultToast: false,
|
showResultToast: false,
|
||||||
result: { success: 0, fail: 0 },
|
result: { success: 0, fail: 0 },
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
sessionName: ''
|
sessionName: '',
|
||||||
|
taskId: null
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
12
src/types/electron.d.ts
vendored
12
src/types/electron.d.ts
vendored
@@ -403,10 +403,16 @@ export interface ElectronAPI {
|
|||||||
|
|
||||||
image: {
|
image: {
|
||||||
decrypt: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) => Promise<{ success: boolean; localPath?: string; liveVideoPath?: string; error?: string }>
|
decrypt: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) => Promise<{ success: boolean; localPath?: string; liveVideoPath?: string; error?: string }>
|
||||||
resolveCache: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; disableUpdateCheck?: boolean }) => Promise<{ success: boolean; localPath?: string; hasUpdate?: boolean; liveVideoPath?: string; error?: string }>
|
resolveCache: (payload: {
|
||||||
|
sessionId?: string
|
||||||
|
imageMd5?: string
|
||||||
|
imageDatName?: string
|
||||||
|
disableUpdateCheck?: boolean
|
||||||
|
allowCacheIndex?: boolean
|
||||||
|
}) => Promise<{ success: boolean; localPath?: string; hasUpdate?: boolean; liveVideoPath?: string; error?: string }>
|
||||||
resolveCacheBatch: (
|
resolveCacheBatch: (
|
||||||
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
||||||
options?: { disableUpdateCheck?: boolean }
|
options?: { disableUpdateCheck?: boolean; allowCacheIndex?: boolean }
|
||||||
) => Promise<{
|
) => Promise<{
|
||||||
success: boolean
|
success: boolean
|
||||||
rows?: Array<{ success: boolean; localPath?: string; hasUpdate?: boolean; error?: string }>
|
rows?: Array<{ success: boolean; localPath?: string; hasUpdate?: boolean; error?: string }>
|
||||||
@@ -414,7 +420,7 @@ export interface ElectronAPI {
|
|||||||
}>
|
}>
|
||||||
preload: (
|
preload: (
|
||||||
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>,
|
||||||
options?: { allowDecrypt?: boolean }
|
options?: { allowDecrypt?: boolean; allowCacheIndex?: boolean }
|
||||||
) => Promise<boolean>
|
) => Promise<boolean>
|
||||||
onUpdateAvailable: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string }) => void) => () => void
|
onUpdateAvailable: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string }) => void) => () => void
|
||||||
onCacheResolved: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string; localPath: string }) => void) => () => void
|
onCacheResolved: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string; localPath: string }) => void) => () => void
|
||||||
|
|||||||
Reference in New Issue
Block a user