diff --git a/.gitignore b/.gitignore index 66440f0..ae42d85 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ Thumbs.db *.aps wcdb/ +*info diff --git a/electron/main.ts b/electron/main.ts index 84332c9..1332dfa 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -482,6 +482,40 @@ function registerIpcHandlers() { return analyticsService.getTimeDistribution() }) + // 缓存管理 + ipcMain.handle('cache:clearAnalytics', async () => { + return analyticsService.clearCache() + }) + + ipcMain.handle('cache:clearImages', async () => { + const imageResult = await imageDecryptService.clearCache() + const emojiResult = chatService.clearCaches({ includeMessages: false, includeContacts: false, includeEmojis: true }) + const errors = [imageResult, emojiResult] + .filter((result) => !result.success) + .map((result) => result.error) + .filter(Boolean) as string[] + if (errors.length > 0) { + return { success: false, error: errors.join('; ') } + } + return { success: true } + }) + + ipcMain.handle('cache:clearAll', async () => { + const [analyticsResult, imageResult] = await Promise.all([ + analyticsService.clearCache(), + imageDecryptService.clearCache() + ]) + const chatResult = chatService.clearCaches() + const errors = [analyticsResult, imageResult, chatResult] + .filter((result) => !result.success) + .map((result) => result.error) + .filter(Boolean) as string[] + if (errors.length > 0) { + return { success: false, error: errors.join('; ') } + } + return { success: true } + }) + // 群聊分析相关 ipcMain.handle('groupAnalytics:getGroupChats', async () => { return groupAnalyticsService.getGroupChats() diff --git a/electron/preload.ts b/electron/preload.ts index 897d9b7..f8883e2 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -140,6 +140,13 @@ contextBridge.exposeInMainWorld('electronAPI', { } }, + // 缓存管理 + cache: { + clearAnalytics: () => ipcRenderer.invoke('cache:clearAnalytics'), + clearImages: () => ipcRenderer.invoke('cache:clearImages'), + clearAll: () => ipcRenderer.invoke('cache:clearAll') + }, + // 群聊分析 groupAnalytics: { getGroupChats: () => ipcRenderer.invoke('groupAnalytics:getGroupChats'), diff --git a/electron/services/analyticsService.ts b/electron/services/analyticsService.ts index 9e83f64..43c1f79 100644 --- a/electron/services/analyticsService.ts +++ b/electron/services/analyticsService.ts @@ -1,7 +1,7 @@ import { ConfigService } from './config' import { wcdbService } from './wcdbService' import { join } from 'path' -import { readFile, writeFile } from 'fs/promises' +import { readFile, writeFile, rm } from 'fs/promises' import { app } from 'electron' export interface ChatStatistics { @@ -528,6 +528,18 @@ class AnalyticsService { return { success: false, error: String(e) } } } + + async clearCache(): Promise<{ success: boolean; error?: string }> { + this.aggregateCache = null + this.fallbackAggregateCache = null + this.aggregatePromise = null + try { + await rm(this.getCacheFilePath(), { force: true }) + return { success: true } + } catch (e) { + return { success: false, error: String(e) } + } + } } export const analyticsService = new AnalyticsService() diff --git a/electron/services/contactCacheService.ts b/electron/services/contactCacheService.ts index e29e4a1..60f4474 100644 --- a/electron/services/contactCacheService.ts +++ b/electron/services/contactCacheService.ts @@ -1,5 +1,5 @@ import { join, dirname } from 'path' -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' +import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs' import { app } from 'electron' export interface ContactCacheEntry { @@ -72,4 +72,13 @@ export class ContactCacheService { console.error('ContactCacheService: 保存缓存失败', error) } } + + clear(): void { + this.cache = {} + try { + rmSync(this.cacheFilePath, { force: true }) + } catch (error) { + console.error('ContactCacheService: 清理缓存失败', error) + } + } } diff --git a/electron/services/imageDecryptService.ts b/electron/services/imageDecryptService.ts index 370431d..7c472a8 100644 --- a/electron/services/imageDecryptService.ts +++ b/electron/services/imageDecryptService.ts @@ -2,7 +2,7 @@ import { app, BrowserWindow } from 'electron' import { basename, dirname, extname, join } from 'path' import { pathToFileURL } from 'url' import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, appendFileSync } from 'fs' -import { writeFile } from 'fs/promises' +import { writeFile, rm, readdir } from 'fs/promises' import crypto from 'crypto' import { Worker } from 'worker_threads' import { ConfigService } from './config' @@ -1646,6 +1646,71 @@ export class ImageDecryptService { await writeFile(outputPath, decrypted) } + + async clearCache(): Promise<{ success: boolean; error?: string }> { + this.resolvedCache.clear() + this.hardlinkCache.clear() + this.pending.clear() + this.updateFlags.clear() + this.cacheIndexed = false + this.cacheIndexing = null + + const configured = this.configService.get('cachePath') + const root = configured + ? join(configured, 'Images') + : join(app.getPath('documents'), 'WeFlow', 'Images') + + try { + if (!existsSync(root)) { + return { success: true } + } + const monthPattern = /^\d{4}-\d{2}$/ + const clearFilesInDir = async (dirPath: string): Promise => { + let entries: Array<{ name: string; isDirectory: () => boolean }> + try { + entries = await readdir(dirPath, { withFileTypes: true }) + } catch { + return + } + for (const entry of entries) { + const fullPath = join(dirPath, entry.name) + if (entry.isDirectory()) { + await clearFilesInDir(fullPath) + continue + } + try { + await rm(fullPath, { force: true }) + } catch { } + } + } + const traverse = async (dirPath: string): Promise => { + let entries: Array<{ name: string; isDirectory: () => boolean }> + try { + entries = await readdir(dirPath, { withFileTypes: true }) + } catch { + return + } + for (const entry of entries) { + const fullPath = join(dirPath, entry.name) + if (entry.isDirectory()) { + if (monthPattern.test(entry.name)) { + await clearFilesInDir(fullPath) + } else { + await traverse(fullPath) + } + continue + } + try { + await rm(fullPath, { force: true }) + } catch { } + } + } + await traverse(root) + return { success: true } + } catch (e) { + return { success: false, error: String(e) } + } + } } export const imageDecryptService = new ImageDecryptService() diff --git a/electron/services/messageCacheService.ts b/electron/services/messageCacheService.ts index 7fffa74..f26caf5 100644 --- a/electron/services/messageCacheService.ts +++ b/electron/services/messageCacheService.ts @@ -1,5 +1,5 @@ import { join, dirname } from 'path' -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' +import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs' import { app } from 'electron' export interface SessionMessageCacheEntry { @@ -65,4 +65,13 @@ export class MessageCacheService { console.error('MessageCacheService: 保存缓存失败', error) } } + + clear(): void { + this.cache = {} + try { + rmSync(this.cacheFilePath, { force: true }) + } catch (error) { + console.error('MessageCacheService: 清理缓存失败', error) + } + } } diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 8a717ce..c84a6f4 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useRef } from 'react' import { useAppStore } from '../stores/appStore' import { useThemeStore, themes } from '../stores/themeStore' +import { useAnalyticsStore } from '../stores/analyticsStore' import { dialog } from '../services/ipc' import * as configService from '../services/config' import { @@ -27,6 +28,7 @@ interface WxidOption { function SettingsPage() { const { setDbConnected, setLoading, reset } = useAppStore() const { currentTheme, themeMode, setTheme, setThemeMode } = useThemeStore() + const clearAnalyticsStoreCache = useAnalyticsStore((state) => state.clearCache) const [activeTab, setActiveTab] = useState('appearance') const [decryptKey, setDecryptKey] = useState('') @@ -55,6 +57,11 @@ function SettingsPage() { const [dbKeyStatus, setDbKeyStatus] = useState('') const [imageKeyStatus, setImageKeyStatus] = useState('') const [isManualStartPrompt, setIsManualStartPrompt] = useState(false) + const [isClearingAnalyticsCache, setIsClearingAnalyticsCache] = useState(false) + const [isClearingImageCache, setIsClearingImageCache] = useState(false) + const [isClearingAllCache, setIsClearingAllCache] = useState(false) + + const isClearingCache = isClearingAnalyticsCache || isClearingImageCache || isClearingAllCache useEffect(() => { loadConfig() @@ -428,6 +435,59 @@ function SettingsPage() { } } + const handleClearAnalyticsCache = async () => { + if (isClearingCache) return + setIsClearingAnalyticsCache(true) + try { + const result = await window.electronAPI.cache.clearAnalytics() + if (result.success) { + clearAnalyticsStoreCache() + showMessage('已清除分析缓存', true) + } else { + showMessage(`清除分析缓存失败: ${result.error || '未知错误'}`, false) + } + } catch (e) { + showMessage(`清除分析缓存失败: ${e}`, false) + } finally { + setIsClearingAnalyticsCache(false) + } + } + + const handleClearImageCache = async () => { + if (isClearingCache) return + setIsClearingImageCache(true) + try { + const result = await window.electronAPI.cache.clearImages() + if (result.success) { + showMessage('已清除图片缓存', true) + } else { + showMessage(`清除图片缓存失败: ${result.error || '未知错误'}`, false) + } + } catch (e) { + showMessage(`清除图片缓存失败: ${e}`, false) + } finally { + setIsClearingImageCache(false) + } + } + + const handleClearAllCache = async () => { + if (isClearingCache) return + setIsClearingAllCache(true) + try { + const result = await window.electronAPI.cache.clearAll() + if (result.success) { + clearAnalyticsStoreCache() + showMessage('已清除所有缓存', true) + } else { + showMessage(`清除所有缓存失败: ${result.error || '未知错误'}`, false) + } + } catch (e) { + showMessage(`清除所有缓存失败: ${e}`, false) + } finally { + setIsClearingAllCache(false) + } + } + const renderAppearanceTab = () => (
@@ -597,9 +657,15 @@ function SettingsPage() {

管理应用缓存数据

- - - + + +

清除当前配置并重新开始首次引导

diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index d50e4b5..ddb14ff 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -148,6 +148,11 @@ export interface ElectronAPI { }> onProgress: (callback: (payload: { status: string; progress: number }) => void) => () => void } + cache: { + clearAnalytics: () => Promise<{ success: boolean; error?: string }> + clearImages: () => Promise<{ success: boolean; error?: string }> + clearAll: () => Promise<{ success: boolean; error?: string }> + } groupAnalytics: { getGroupChats: () => Promise<{ success: boolean