refactor(export): remove task pause/stop and prioritize export by loaded message counts

This commit is contained in:
tisonhuang
2026-03-04 18:20:56 +08:00
parent 54d6cded53
commit cf7190aaec
5 changed files with 106 additions and 378 deletions

View File

@@ -95,11 +95,6 @@ let isDownloadInProgress = false
let downloadProgressHandler: ((progress: any) => void) | null = null
let downloadedHandler: (() => void) | null = null
interface ExportTaskControlState {
pauseRequested: boolean
stopRequested: boolean
}
type AnnualReportYearsLoadStrategy = 'cache' | 'native' | 'hybrid'
type AnnualReportYearsLoadPhase = 'cache' | 'native' | 'scan' | 'done'
@@ -126,31 +121,11 @@ interface AnnualReportYearsTaskState {
updatedAt: number
}
const exportTaskControlMap = new Map<string, ExportTaskControlState>()
const pendingExportTaskControlMap = new Map<string, ExportTaskControlState>()
const annualReportYearsLoadTasks = new Map<string, AnnualReportYearsTaskState>()
const annualReportYearsTaskByCacheKey = new Map<string, string>()
const annualReportYearsSnapshotCache = new Map<string, { snapshot: AnnualReportYearsProgressPayload; updatedAt: number; taskId: string }>()
const annualReportYearsSnapshotTtlMs = 10 * 60 * 1000
const getTaskControlState = (taskId?: string): ExportTaskControlState | null => {
const normalized = typeof taskId === 'string' ? taskId.trim() : ''
if (!normalized) return null
return exportTaskControlMap.get(normalized) || null
}
const createTaskControlState = (taskId?: string): string | null => {
const normalized = typeof taskId === 'string' ? taskId.trim() : ''
if (!normalized) return null
const pending = pendingExportTaskControlMap.get(normalized)
exportTaskControlMap.set(normalized, {
pauseRequested: Boolean(pending?.pauseRequested),
stopRequested: Boolean(pending?.stopRequested)
})
pendingExportTaskControlMap.delete(normalized)
return normalized
}
const normalizeAnnualReportYearsSnapshot = (snapshot: AnnualReportYearsProgressPayload): AnnualReportYearsProgressPayload => {
const years = Array.isArray(snapshot.years) ? [...snapshot.years] : []
return { ...snapshot, years }
@@ -212,22 +187,6 @@ const isYearsLoadCanceled = (taskId: string): boolean => {
return task?.canceled === true
}
const clearTaskControlState = (taskId?: string): void => {
const normalized = typeof taskId === 'string' ? taskId.trim() : ''
if (!normalized) return
exportTaskControlMap.delete(normalized)
pendingExportTaskControlMap.delete(normalized)
}
const queueTaskControlRequest = (taskId: string, action: 'pause' | 'stop'): void => {
const normalized = taskId.trim()
if (!normalized) return
const existing = pendingExportTaskControlMap.get(normalized) || { pauseRequested: false, stopRequested: false }
if (action === 'pause') existing.pauseRequested = true
if (action === 'stop') existing.stopRequested = true
pendingExportTaskControlMap.set(normalized, existing)
}
function createWindow(options: { autoShow?: boolean } = {}) {
// 获取图标路径 - 打包后在 resources 目录
const { autoShow = true } = options
@@ -1269,27 +1228,17 @@ function registerIpcHandlers() {
})
ipcMain.handle('sns:exportTimeline', async (event, options: any) => {
const taskId = typeof options?.taskId === 'string' ? options.taskId : undefined
const controlId = createTaskControlState(taskId)
const exportOptions = { ...(options || {}) }
delete exportOptions.taskId
try {
return snsService.exportTimeline(
exportOptions,
(progress) => {
if (!event.sender.isDestroyed()) {
event.sender.send('sns:exportProgress', progress)
}
},
{
shouldPause: () => Boolean(getTaskControlState(controlId || undefined)?.pauseRequested),
shouldStop: () => Boolean(getTaskControlState(controlId || undefined)?.stopRequested)
return snsService.exportTimeline(
exportOptions,
(progress) => {
if (!event.sender.isDestroyed()) {
event.sender.send('sns:exportProgress', progress)
}
)
} finally {
clearTaskControlState(controlId || undefined)
}
}
)
})
ipcMain.handle('sns:selectExportDir', async () => {
@@ -1412,42 +1361,14 @@ function registerIpcHandlers() {
return exportService.getExportStats(sessionIds, options)
})
ipcMain.handle('export:exportSessions', async (event, sessionIds: string[], outputDir: string, options: ExportOptions, taskId?: string) => {
const controlId = createTaskControlState(taskId)
ipcMain.handle('export:exportSessions', async (event, sessionIds: string[], outputDir: string, options: ExportOptions) => {
const onProgress = (progress: ExportProgress) => {
if (!event.sender.isDestroyed()) {
event.sender.send('export:progress', progress)
}
}
try {
return exportService.exportSessions(sessionIds, outputDir, options, onProgress, {
shouldPause: () => Boolean(getTaskControlState(controlId || undefined)?.pauseRequested),
shouldStop: () => Boolean(getTaskControlState(controlId || undefined)?.stopRequested)
})
} finally {
clearTaskControlState(controlId || undefined)
}
})
ipcMain.handle('export:pauseTask', async (_, taskId: string) => {
const state = getTaskControlState(taskId)
if (!state) {
queueTaskControlRequest(taskId, 'pause')
return { success: true, queued: true }
}
state.pauseRequested = true
return { success: true }
})
ipcMain.handle('export:stopTask', async (_, taskId: string) => {
const state = getTaskControlState(taskId)
if (!state) {
queueTaskControlRequest(taskId, 'stop')
return { success: true, queued: true }
}
state.stopRequested = true
return { success: true }
return exportService.exportSessions(sessionIds, outputDir, options, onProgress)
})
ipcMain.handle('export:exportSession', async (_, sessionId: string, outputPath: string, options: ExportOptions) => {

View File

@@ -306,16 +306,12 @@ contextBridge.exposeInMainWorld('electronAPI', {
export: {
getExportStats: (sessionIds: string[], options: any) =>
ipcRenderer.invoke('export:getExportStats', sessionIds, options),
exportSessions: (sessionIds: string[], outputDir: string, options: any, taskId?: string) =>
ipcRenderer.invoke('export:exportSessions', sessionIds, outputDir, options, taskId),
exportSessions: (sessionIds: string[], outputDir: string, options: any) =>
ipcRenderer.invoke('export:exportSessions', sessionIds, outputDir, options),
exportSession: (sessionId: string, outputPath: string, options: any) =>
ipcRenderer.invoke('export:exportSession', sessionId, outputPath, options),
exportContacts: (outputDir: string, options: any) =>
ipcRenderer.invoke('export:exportContacts', outputDir, options),
pauseTask: (taskId: string) =>
ipcRenderer.invoke('export:pauseTask', taskId),
stopTask: (taskId: string) =>
ipcRenderer.invoke('export:stopTask', taskId),
onProgress: (callback: (payload: { current: number; total: number; currentSession: string; currentSessionId?: string; phase: string }) => void) => {
ipcRenderer.on('export:progress', (_, payload) => callback(payload))
return () => ipcRenderer.removeAllListeners('export:progress')

View File

@@ -6331,15 +6331,15 @@ class ExportService {
total: sessionIds.length,
currentSession: sessionInfo.displayName,
currentSessionId: sessionId,
phase: 'exporting',
phase: 'complete',
phaseLabel: '该会话没有消息,已跳过'
})
return 'done'
}
if (emptySessionIds.has(sessionId)) {
failCount++
failedSessionIds.push(sessionId)
successCount++
successSessionIds.push(sessionId)
activeSessionRatios.delete(sessionId)
completedCount++
onProgress?.({
@@ -6347,7 +6347,7 @@ class ExportService {
total: sessionIds.length,
currentSession: sessionInfo.displayName,
currentSessionId: sessionId,
phase: 'exporting',
phase: 'complete',
phaseLabel: '该会话没有消息,已跳过'
})
return 'done'
@@ -6419,7 +6419,7 @@ class ExportService {
total: sessionIds.length,
currentSession: sessionInfo.displayName,
currentSessionId: sessionId,
phase: 'exporting',
phase: 'complete',
phaseLabel: '无变化,已跳过'
})
return 'done'
@@ -6463,6 +6463,14 @@ class ExportService {
failCount++
failedSessionIds.push(sessionId)
console.error(`导出 ${sessionId} 失败:`, result.error)
onProgress?.({
current: computeAggregateCurrent(),
total: sessionIds.length,
currentSession: sessionInfo.displayName,
currentSessionId: sessionId,
phase: 'complete',
phaseLabel: '导出失败'
})
}
activeSessionRatios.delete(sessionId)