mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 05:11:24 +08:00
Implement file reference count cleanup with deleteIfZero support
- Update DexieMessageDataSource to delete files when count reaches zero and deleteIfZero is true - Add deleteIfZero parameter to MessageDataSource interface and all implementations - Modify updateFileCountV2 thunk to pass deleteIfZero parameter through DbService
This commit is contained in:
parent
c872707791
commit
7fdae0173c
@ -1,101 +0,0 @@
|
||||
# Agent Session 消息持久化问题修复
|
||||
|
||||
## 问题描述
|
||||
在Agent会话中发送消息后,如果切换到其他会话再切回来,消息会丢失。错误信息:
|
||||
```
|
||||
[MessageThunk] persistAgentExchange: missing user or assistant message entity
|
||||
```
|
||||
|
||||
## 问题原因
|
||||
1. **原始实现问题**:
|
||||
- `saveMessageAndBlocksToDB` 对Agent会话直接返回,不保存消息
|
||||
- 消息只存在于Redux state中
|
||||
|
||||
2. **V2实现问题**:
|
||||
- `AgentMessageDataSource.appendMessage` 是空操作
|
||||
- 期望通过 `persistExchange` 在响应完成后保存
|
||||
|
||||
3. **时序问题**:
|
||||
- `persistAgentExchange` 在Agent响应完成后才被调用
|
||||
- 如果用户在响应过程中切换会话,Redux state被清空
|
||||
- `persistAgentExchange` 找不到消息实体,保存失败
|
||||
|
||||
## 解决方案
|
||||
修改 `AgentMessageDataSource.appendMessage` 方法,让它立即保存消息到后端,而不是等待响应完成。
|
||||
|
||||
### 修改内容
|
||||
```typescript
|
||||
// src/renderer/src/services/db/AgentMessageDataSource.ts
|
||||
|
||||
async appendMessage(topicId: string, message: Message, blocks: MessageBlock[]): Promise<void> {
|
||||
// 立即保存消息,不等待persistExchange
|
||||
const sessionId = extractSessionId(topicId)
|
||||
|
||||
const payload: AgentPersistedMessage = {
|
||||
message,
|
||||
blocks
|
||||
}
|
||||
|
||||
// 通过IPC立即保存单个消息
|
||||
await window.electron.ipcRenderer.invoke(IpcChannel.AgentMessage_PersistExchange, {
|
||||
sessionId,
|
||||
agentSessionId: '',
|
||||
...(message.role === 'user'
|
||||
? { user: { payload } }
|
||||
: { assistant: { payload } }
|
||||
)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 影响分析
|
||||
|
||||
### 优点
|
||||
1. 消息立即持久化,不会因切换会话而丢失
|
||||
2. 即使Agent响应失败,用户消息也已保存
|
||||
3. 提高了数据安全性
|
||||
|
||||
### 潜在问题
|
||||
1. **可能的重复保存**:
|
||||
- `appendMessage` 保存一次
|
||||
- `persistAgentExchange` 可能再次保存
|
||||
- 需要后端处理重复消息(通过messageId去重)
|
||||
|
||||
2. **性能考虑**:
|
||||
- 每条消息都触发IPC调用
|
||||
- 可能增加延迟
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 测试步骤
|
||||
1. 启用V2功能
|
||||
2. 创建Agent会话
|
||||
3. 发送消息
|
||||
4. 在Agent响应过程中立即切换到其他会话
|
||||
5. 切回Agent会话
|
||||
6. **期望结果**:消息应该正确显示,不会丢失
|
||||
|
||||
### 测试场景
|
||||
- ✅ 正常发送和接收
|
||||
- ✅ 响应中切换会话
|
||||
- ✅ 快速连续发送多条消息
|
||||
- ✅ 网络中断恢复
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **批量保存**:
|
||||
- 考虑缓存多条消息后批量保存
|
||||
- 减少IPC调用次数
|
||||
|
||||
2. **去重机制**:
|
||||
- 后端通过messageId去重
|
||||
- 避免重复存储
|
||||
|
||||
3. **错误处理**:
|
||||
- 添加重试机制
|
||||
- 失败时的降级策略
|
||||
|
||||
## 回滚方案
|
||||
如果修复引起新问题:
|
||||
1. 恢复 `AgentMessageDataSource.appendMessage` 为原始空操作
|
||||
2. 考虑其他解决方案(如在切换会话前强制调用persistExchange)
|
||||
@ -1,247 +0,0 @@
|
||||
# Agent Session 消息状态持久化方案
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 当前流程
|
||||
1. **发送消息时**:
|
||||
- 创建助手消息,状态为 `PENDING`
|
||||
- 通过 `appendMessage` 立即保存到后端(包含pending状态)
|
||||
|
||||
2. **切换会话后重新加载**:
|
||||
- 从后端加载消息
|
||||
- 但状态可能丢失或被覆盖
|
||||
|
||||
### 根本问题
|
||||
后端可能没有正确保存或返回消息的 `status` 字段。
|
||||
|
||||
## 解决方案:确保状态正确持久化
|
||||
|
||||
### 方案A:修改 AgentMessageDataSource(前端方案)
|
||||
|
||||
```typescript
|
||||
// src/renderer/src/services/db/AgentMessageDataSource.ts
|
||||
|
||||
// 1. 保存消息时确保状态被保存
|
||||
async appendMessage(topicId: string, message: Message, blocks: MessageBlock[]): Promise<void> {
|
||||
const sessionId = extractSessionId(topicId)
|
||||
|
||||
const payload: AgentPersistedMessage = {
|
||||
message: {
|
||||
...message,
|
||||
// 明确保存状态
|
||||
status: message.status || AssistantMessageStatus.PENDING
|
||||
},
|
||||
blocks
|
||||
}
|
||||
|
||||
await window.electron.ipcRenderer.invoke(IpcChannel.AgentMessage_PersistExchange, {
|
||||
sessionId,
|
||||
agentSessionId: '',
|
||||
...(message.role === 'user'
|
||||
? { user: { payload } }
|
||||
: { assistant: { payload } }
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// 2. 加载消息时恢复流式状态
|
||||
async fetchMessages(topicId: string): Promise<{ messages: Message[], blocks: MessageBlock[] }> {
|
||||
const sessionId = extractSessionId(topicId)
|
||||
const historicalMessages = await window.electron.ipcRenderer.invoke(
|
||||
IpcChannel.AgentMessage_GetHistory,
|
||||
{ sessionId }
|
||||
)
|
||||
|
||||
const messages: Message[] = []
|
||||
const blocks: MessageBlock[] = []
|
||||
let hasStreamingMessage = false
|
||||
|
||||
for (const persistedMsg of historicalMessages) {
|
||||
if (persistedMsg?.message) {
|
||||
const message = persistedMsg.message
|
||||
|
||||
// 检查是否有未完成的消息
|
||||
if (message.status === 'pending' || message.status === 'processing') {
|
||||
hasStreamingMessage = true
|
||||
|
||||
// 如果消息创建时间超过5分钟,标记为错误
|
||||
const messageAge = Date.now() - new Date(message.createdAt).getTime()
|
||||
if (messageAge > 5 * 60 * 1000) {
|
||||
message.status = 'error'
|
||||
}
|
||||
}
|
||||
|
||||
messages.push(message)
|
||||
if (persistedMsg.blocks) {
|
||||
blocks.push(...persistedMsg.blocks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有流式消息,恢复loading状态
|
||||
if (hasStreamingMessage) {
|
||||
// 这里需要dispatch action,可能需要通过回调或其他方式
|
||||
store.dispatch(newMessagesActions.setTopicLoading({ topicId, loading: true }))
|
||||
}
|
||||
|
||||
return { messages, blocks }
|
||||
}
|
||||
```
|
||||
|
||||
### 方案B:后端修改(更彻底的方案)
|
||||
|
||||
需要确保后端:
|
||||
|
||||
1. **sessionMessageRepository.ts** 正确保存消息状态
|
||||
```typescript
|
||||
// src/main/services/agents/database/sessionMessageRepository.ts
|
||||
|
||||
async persistExchange(params: PersistExchangeParams): Promise<void> {
|
||||
// 保存时确保状态字段被正确存储
|
||||
if (params.user) {
|
||||
await this.saveMessage({
|
||||
...params.user.payload.message,
|
||||
status: params.user.payload.message.status // 确保状态被保存
|
||||
})
|
||||
}
|
||||
|
||||
if (params.assistant) {
|
||||
await this.saveMessage({
|
||||
...params.assistant.payload.message,
|
||||
status: params.assistant.payload.message.status // 确保状态被保存
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async getHistory(sessionId: string): Promise<AgentPersistedMessage[]> {
|
||||
// 返回时确保状态字段被包含
|
||||
const messages = await this.db.getMessages(sessionId)
|
||||
return messages.map(msg => ({
|
||||
message: {
|
||||
...msg,
|
||||
status: msg.status // 确保状态被返回
|
||||
},
|
||||
blocks: msg.blocks
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
2. **添加会话级别的流式状态**
|
||||
```typescript
|
||||
interface AgentSession {
|
||||
id: string
|
||||
// ... 其他字段
|
||||
streamingMessageId?: string // 当前正在流式的消息ID
|
||||
streamingStartTime?: number // 流式开始时间
|
||||
}
|
||||
|
||||
// 开始流式时更新
|
||||
async startStreaming(sessionId: string, messageId: string) {
|
||||
await this.updateSession(sessionId, {
|
||||
streamingMessageId: messageId,
|
||||
streamingStartTime: Date.now()
|
||||
})
|
||||
}
|
||||
|
||||
// 结束流式时清除
|
||||
async stopStreaming(sessionId: string) {
|
||||
await this.updateSession(sessionId, {
|
||||
streamingMessageId: null,
|
||||
streamingStartTime: null
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 方案C:混合方案(推荐)
|
||||
|
||||
1. **前端立即保存状态**(已实现)
|
||||
2. **后端确保状态持久化**
|
||||
3. **加载时智能恢复状态**
|
||||
|
||||
```typescript
|
||||
// AgentMessageDataSource.ts
|
||||
async fetchMessages(topicId: string): Promise<{ messages: Message[], blocks: MessageBlock[] }> {
|
||||
const sessionId = extractSessionId(topicId)
|
||||
const historicalMessages = await window.electron.ipcRenderer.invoke(
|
||||
IpcChannel.AgentMessage_GetHistory,
|
||||
{ sessionId }
|
||||
)
|
||||
|
||||
const messages: Message[] = []
|
||||
const blocks: MessageBlock[] = []
|
||||
|
||||
for (const persistedMsg of historicalMessages) {
|
||||
if (persistedMsg?.message) {
|
||||
const message = { ...persistedMsg.message }
|
||||
|
||||
// 智能恢复状态
|
||||
if (message.status === 'pending' || message.status === 'processing') {
|
||||
// 检查消息年龄
|
||||
const age = Date.now() - new Date(message.createdAt).getTime()
|
||||
|
||||
if (age > 5 * 60 * 1000) {
|
||||
// 超过5分钟,标记为错误
|
||||
message.status = 'error'
|
||||
} else if (age > 30 * 1000 && message.blocks?.length > 0) {
|
||||
// 超过30秒且有内容,可能已完成
|
||||
message.status = 'success'
|
||||
}
|
||||
// 否则保持原状态,让UI显示暂停按钮
|
||||
}
|
||||
|
||||
messages.push(message)
|
||||
if (persistedMsg.blocks) {
|
||||
blocks.push(...persistedMsg.blocks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { messages, blocks }
|
||||
}
|
||||
```
|
||||
|
||||
## 实施步骤
|
||||
|
||||
### 步骤1:验证后端是否保存状态
|
||||
1. 在 `appendMessage` 中添加日志,确认状态被发送
|
||||
2. 检查后端数据库,确认状态被保存
|
||||
3. 在 `fetchMessages` 中添加日志,确认状态被返回
|
||||
|
||||
### 步骤2:修复状态持久化
|
||||
1. 如果后端没有保存状态,修改后端代码
|
||||
2. 如果后端保存了但没返回,修改返回逻辑
|
||||
|
||||
### 步骤3:添加状态恢复逻辑
|
||||
1. 在 `fetchMessages` 中智能恢复状态
|
||||
2. 对于未完成的消息,根据时间判断是否需要标记为错误
|
||||
|
||||
### 步骤4:恢复loading状态
|
||||
1. 如果有pending/processing消息,设置loading为true
|
||||
2. 让UI正确显示暂停按钮
|
||||
|
||||
## 测试验证
|
||||
|
||||
1. **正常流程**
|
||||
- 发送消息
|
||||
- 观察pending状态
|
||||
- 响应完成后状态变为success
|
||||
|
||||
2. **切换会话**
|
||||
- 发送消息开始响应
|
||||
- 立即切换会话
|
||||
- 切回来,pending状态应该保持
|
||||
- 暂停按钮应该显示
|
||||
|
||||
3. **页面刷新**
|
||||
- 响应过程中刷新
|
||||
- 重新加载后状态应该合理(pending或error)
|
||||
|
||||
4. **超时处理**
|
||||
- 模拟长时间pending
|
||||
- 验证超时后自动标记为error
|
||||
|
||||
## 优势
|
||||
- 符合现有架构,数据统一持久化
|
||||
- 状态与消息一起保存,数据一致性好
|
||||
- 页面刷新也能恢复
|
||||
- 不需要额外的状态管理器
|
||||
@ -1,212 +0,0 @@
|
||||
# V2 Database Service 手动测试用例
|
||||
|
||||
## 准备工作
|
||||
```javascript
|
||||
// 1. 打开浏览器控制台,启用V2功能
|
||||
localStorage.setItem('featureFlags', JSON.stringify({ USE_UNIFIED_DB_SERVICE: true }))
|
||||
location.reload()
|
||||
|
||||
// 2. 确认功能已启用
|
||||
JSON.parse(localStorage.getItem('featureFlags') || '{}')
|
||||
// 应该看到: { USE_UNIFIED_DB_SERVICE: true }
|
||||
```
|
||||
|
||||
## 测试场景一:基础聊天功能 ✅
|
||||
|
||||
### 1.1 消息发送与保存
|
||||
**测试功能**: `saveMessageAndBlocksToDBV2`, `updateBlocksV2`
|
||||
1. 创建新的聊天会话
|
||||
2. 发送消息:"你好,请介绍一下React Hooks的使用"
|
||||
3. 等待助手回复完成
|
||||
4. 刷新页面
|
||||
5. **验证**: 消息应该被正确保存并重新加载
|
||||
|
||||
### 1.2 消息加载(已测试稳定)
|
||||
**测试功能**: `loadTopicMessagesThunkV2`
|
||||
1. 切换到其他会话
|
||||
2. 再切换回刚才的会话
|
||||
3. **验证**: 消息应该立即加载,无需等待
|
||||
|
||||
### 1.3 实时流式更新
|
||||
**测试功能**: `updateSingleBlockV2` (throttled updates)
|
||||
1. 发送一个需要较长回复的问题:"请详细解释JavaScript的事件循环机制"
|
||||
2. 观察助手回复时的流式更新
|
||||
3. **验证**: 文字应该平滑流式显示,没有卡顿或丢失
|
||||
|
||||
## 测试场景二:消息编辑与删除 🗑️
|
||||
|
||||
### 2.1 删除单条消息
|
||||
**测试功能**: `deleteMessageFromDBV2`
|
||||
1. 在现有会话中,右键点击任意一条消息
|
||||
2. 选择"删除"
|
||||
3. 刷新页面
|
||||
4. **验证**: 被删除的消息不应再出现
|
||||
|
||||
### 2.2 删除消息组(用户问题+助手回答)
|
||||
**测试功能**: `deleteMessagesFromDBV2`
|
||||
1. 找到一组问答(用户提问+助手回答)
|
||||
2. 删除整组对话
|
||||
3. **验证**: 用户消息和对应的助手回答都被删除
|
||||
|
||||
### 2.3 清空会话
|
||||
**测试功能**: `clearMessagesFromDBV2`
|
||||
1. 在一个有多条消息的会话中
|
||||
2. 使用"清空会话"功能
|
||||
3. 刷新页面
|
||||
4. **验证**: 会话应该为空,但会话本身还存在
|
||||
|
||||
## 测试场景三:文件和图片处理 📎
|
||||
|
||||
### 3.1 上传图片
|
||||
**测试功能**: `saveMessageAndBlocksToDBV2`, `updateFileCountV2`
|
||||
1. 在输入框中上传一张图片
|
||||
2. 添加文字:"这张图片是什么内容?"
|
||||
3. 发送消息
|
||||
4. 刷新页面
|
||||
5. **验证**: 图片应该正确显示,文件引用计数正确
|
||||
|
||||
### 3.2 上传文件
|
||||
**测试功能**: `bulkAddBlocksV2`
|
||||
1. 上传一个文本文件或PDF
|
||||
2. 发送消息询问文件内容
|
||||
3. **验证**: 文件应该被正确处理和显示
|
||||
|
||||
### 3.3 复制带图片的消息到新会话
|
||||
**测试功能**: `bulkAddBlocksV2`, `updateFileCountV2`
|
||||
1. 选择包含图片的消息
|
||||
2. 复制到新的会话
|
||||
3. **验证**: 图片在新会话中正确显示,文件引用计数增加
|
||||
|
||||
## 测试场景四:Agent Session 功能 🤖
|
||||
|
||||
### 4.1 Agent会话消息加载
|
||||
**测试功能**: `loadTopicMessagesThunkV2` (agent-session分支)
|
||||
1. 创建或打开一个Agent会话
|
||||
2. 发送消息给Agent
|
||||
3. 切换到其他会话再切回
|
||||
4. **验证**: Agent会话消息正确加载
|
||||
|
||||
### 4.2 Agent会话消息持久化 🔥 (已修复)
|
||||
**测试功能**: `saveMessageAndBlocksToDBV2` → `AgentMessageDataSource.appendMessage`
|
||||
1. 在Agent会话中发送消息
|
||||
2. **立即切换到其他会话**(不等待响应完成)
|
||||
3. 切回Agent会话
|
||||
4. **验证**: 用户消息应该已保存并显示
|
||||
5. 等待Agent响应完成
|
||||
6. 刷新页面
|
||||
7. **验证**: 完整对话正确保存
|
||||
|
||||
### 4.3 Agent会话清空(应该无操作)
|
||||
**测试功能**: `clearMessagesFromDBV2` (agent no-op)
|
||||
1. 尝试清空Agent会话
|
||||
2. **验证**: 操作应该被正确处理(可能显示不支持或静默处理)
|
||||
|
||||
## 测试场景五:高级功能 🚀
|
||||
|
||||
### 5.1 消息重新生成
|
||||
**测试功能**: `updateMessageV2`, `updateBlocksV2`
|
||||
1. 选择一条助手回复
|
||||
2. 点击"重新生成"
|
||||
3. **验证**: 原消息被重置,新回复正常生成
|
||||
|
||||
### 5.2 消息分支
|
||||
**测试功能**: `saveMessageAndBlocksToDBV2`
|
||||
1. 选择一条用户消息
|
||||
2. 创建分支并输入不同的问题
|
||||
3. **验证**: 分支正确创建,两个分支独立存在
|
||||
|
||||
### 5.3 翻译功能
|
||||
**测试功能**: `updateSingleBlockV2`
|
||||
1. 选择一条消息
|
||||
2. 点击翻译按钮
|
||||
3. **验证**: 翻译块正确创建和更新
|
||||
|
||||
### 5.4 多模型响应
|
||||
**测试功能**: `saveMessageAndBlocksToDBV2`, `updateBlocksV2`
|
||||
1. 启用多模型功能
|
||||
2. 发送一个问题
|
||||
3. **验证**: 多个模型的响应都正确保存
|
||||
|
||||
## 测试场景六:并发和性能 ⚡
|
||||
|
||||
### 6.1 快速切换会话
|
||||
**测试功能**: `loadTopicMessagesThunkV2`
|
||||
1. 快速在多个会话间切换
|
||||
2. **验证**: 消息加载无错误,无内存泄漏
|
||||
|
||||
### 6.2 大量消息处理
|
||||
**测试功能**: 所有V2函数
|
||||
1. 在一个会话中累积50+条消息
|
||||
2. 执行各种操作(删除、编辑、刷新)
|
||||
3. **验证**: 性能无明显下降
|
||||
|
||||
### 6.3 同时操作
|
||||
1. 在流式回复过程中切换会话
|
||||
2. 在文件上传过程中发送新消息
|
||||
3. **验证**: 操作不冲突,数据一致
|
||||
|
||||
## 测试场景七:错误处理 ⚠️
|
||||
|
||||
### 7.1 网络中断恢复
|
||||
1. 发送消息
|
||||
2. 在回复过程中断网
|
||||
3. 恢复网络
|
||||
4. **验证**: 消息状态正确,可以重试
|
||||
|
||||
### 7.2 异常数据处理
|
||||
1. 尝试删除不存在的消息(通过控制台)
|
||||
2. **验证**: 错误被优雅处理,不崩溃
|
||||
|
||||
## 测试检查清单
|
||||
|
||||
### 功能验证
|
||||
- [x] 普通聊天消息发送/接收
|
||||
- [ ] Agent会话消息发送/接收
|
||||
- [x] 消息删除(单个/批量/清空)
|
||||
- [x] 文件/图片上传和显示
|
||||
- [x] 消息编辑和更新
|
||||
- [x] 流式响应更新
|
||||
- [x] 消息重新生成
|
||||
- [x] 分支创建
|
||||
- [x] 翻译功能
|
||||
|
||||
### 数据一致性
|
||||
- [x] 刷新后数据保持一致
|
||||
- [x] 切换会话数据正确
|
||||
- [x] 文件引用计数正确
|
||||
- [ ] Agent会话数据隔离
|
||||
|
||||
### 性能表现
|
||||
- [x] 消息加载速度正常
|
||||
- [x] 流式更新流畅
|
||||
- [x] 大量数据处理正常
|
||||
- [x] 内存使用合理
|
||||
|
||||
### 错误处理
|
||||
- [x] 网络错误处理正确
|
||||
- [x] 异常操作不崩溃
|
||||
- [x] 错误信息清晰
|
||||
|
||||
## 回滚测试
|
||||
|
||||
完成所有测试后,验证回滚功能:
|
||||
```javascript
|
||||
// 禁用V2功能
|
||||
localStorage.setItem('featureFlags', JSON.stringify({ USE_UNIFIED_DB_SERVICE: false }))
|
||||
location.reload()
|
||||
|
||||
// 验证切换回原实现后一切正常
|
||||
```
|
||||
|
||||
## 问题记录
|
||||
|
||||
如果发现问题,请记录:
|
||||
1. 测试场景编号
|
||||
2. 具体操作步骤
|
||||
3. 预期结果
|
||||
4. 实际结果
|
||||
5. 浏览器控制台错误信息(如有)
|
||||
|
||||
---
|
||||
|
||||
**提示**: 建议按顺序执行测试,每个大场景可以单独测试。重点关注数据一致性和错误处理。
|
||||
@ -466,12 +466,12 @@ export class AgentMessageDataSource implements MessageDataSource {
|
||||
logger.warn(`bulkAddBlocks called for agent session, operation not supported individually`)
|
||||
}
|
||||
|
||||
async updateFileCount(fileId: string, _delta: number): Promise<void> {
|
||||
async updateFileCount(fileId: string, _delta: number, _deleteIfZero?: boolean): Promise<void> {
|
||||
// Agent sessions don't manage file reference counts locally
|
||||
logger.warn(`updateFileCount called for agent session file ${fileId}, operation not supported`)
|
||||
}
|
||||
|
||||
async updateFileCounts(_files: Array<{ id: string; delta: number }>): Promise<void> {
|
||||
async updateFileCounts(_files: Array<{ id: string; delta: number; deleteIfZero?: boolean }>): Promise<void> {
|
||||
// Agent sessions don't manage file reference counts locally
|
||||
logger.warn(`updateFileCounts called for agent session, operation not supported`)
|
||||
}
|
||||
|
||||
@ -150,16 +150,16 @@ class DbService implements MessageDataSource {
|
||||
return this.dexieSource.updateBlocks(blocks)
|
||||
}
|
||||
|
||||
async updateFileCount(fileId: string, delta: number): Promise<void> {
|
||||
async updateFileCount(fileId: string, delta: number, deleteIfZero: boolean = false): Promise<void> {
|
||||
// File operations only apply to Dexie source
|
||||
if (this.dexieSource.updateFileCount) {
|
||||
return this.dexieSource.updateFileCount(fileId, delta)
|
||||
return this.dexieSource.updateFileCount(fileId, delta, deleteIfZero)
|
||||
}
|
||||
// No-op if not supported
|
||||
logger.warn(`updateFileCount not supported for file ${fileId}`)
|
||||
}
|
||||
|
||||
async updateFileCounts(files: Array<{ id: string; delta: number }>): Promise<void> {
|
||||
async updateFileCounts(files: Array<{ id: string; delta: number; deleteIfZero?: boolean }>): Promise<void> {
|
||||
// File operations only apply to Dexie source
|
||||
if (this.dexieSource.updateFileCounts) {
|
||||
return this.dexieSource.updateFileCounts(files)
|
||||
|
||||
@ -364,29 +364,40 @@ export class DexieMessageDataSource implements MessageDataSource {
|
||||
|
||||
// ============ File Operations ============
|
||||
|
||||
async updateFileCount(fileId: string, delta: number): Promise<void> {
|
||||
async updateFileCount(fileId: string, delta: number, deleteIfZero: boolean = false): Promise<void> {
|
||||
try {
|
||||
await db.files
|
||||
.where('id')
|
||||
.equals(fileId)
|
||||
.modify((f) => {
|
||||
if (f) {
|
||||
f.count = (f.count || 0) + delta
|
||||
}
|
||||
})
|
||||
await db.transaction('rw', db.files, async () => {
|
||||
const file = await db.files.get(fileId)
|
||||
|
||||
if (!file) {
|
||||
logger.warn(`File ${fileId} not found for count update`)
|
||||
return
|
||||
}
|
||||
|
||||
const newCount = (file.count || 0) + delta
|
||||
|
||||
if (newCount <= 0 && deleteIfZero) {
|
||||
// Delete the file when count reaches 0 or below
|
||||
await FileManager.deleteFile(fileId, false)
|
||||
await db.files.delete(fileId)
|
||||
logger.info(`Deleted file ${fileId} as reference count reached ${newCount}`)
|
||||
} else {
|
||||
// Update the count
|
||||
await db.files.update(fileId, { count: Math.max(0, newCount) })
|
||||
logger.debug(`Updated file ${fileId} count to ${Math.max(0, newCount)}`)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`Failed to update file count for ${fileId}:`, error as Error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async updateFileCounts(files: Array<{ id: string; delta: number }>): Promise<void> {
|
||||
async updateFileCounts(files: Array<{ id: string; delta: number; deleteIfZero?: boolean }>): Promise<void> {
|
||||
try {
|
||||
await db.transaction('rw', db.files, async () => {
|
||||
for (const file of files) {
|
||||
await this.updateFileCount(file.id, file.delta)
|
||||
}
|
||||
})
|
||||
for (const file of files) {
|
||||
await this.updateFileCount(file.id, file.delta, file.deleteIfZero || false)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to update file counts:', error as Error)
|
||||
throw error
|
||||
|
||||
@ -109,13 +109,16 @@ export interface MessageDataSource {
|
||||
|
||||
/**
|
||||
* Update file reference count
|
||||
* @param fileId - The file ID to update
|
||||
* @param delta - The change in reference count (positive or negative)
|
||||
* @param deleteIfZero - Whether to delete the file when count reaches 0
|
||||
*/
|
||||
updateFileCount?(fileId: string, delta: number): Promise<void>
|
||||
updateFileCount?(fileId: string, delta: number, deleteIfZero?: boolean): Promise<void>
|
||||
|
||||
/**
|
||||
* Update multiple file reference counts
|
||||
*/
|
||||
updateFileCounts?(files: Array<{ id: string; delta: number }>): Promise<void>
|
||||
updateFileCounts?(files: Array<{ id: string; delta: number; deleteIfZero?: boolean }>): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -87,9 +87,8 @@ export const updateFileCountV2 = async (
|
||||
deleteIfZero: boolean = false
|
||||
): Promise<void> => {
|
||||
try {
|
||||
// DbService.updateFileCount only accepts fileId and delta
|
||||
// deleteIfZero parameter is not currently supported in DbService
|
||||
await dbService.updateFileCount(fileId, delta)
|
||||
// Pass all parameters to dbService, including deleteIfZero
|
||||
await dbService.updateFileCount(fileId, delta, deleteIfZero)
|
||||
logger.info('Updated file count', { fileId, delta, deleteIfZero })
|
||||
} catch (error) {
|
||||
logger.error('Failed to update file count:', { fileId, delta, error })
|
||||
|
||||
Loading…
Reference in New Issue
Block a user