Files
xianyan/lib/features/file_transfer/services/cache_manager_service.dart
Developer 81ea0f60dc fix: 修复 FactoryReset 误删用户文件并将应用数据迁移至 Application Support
- 修复 macOS/Windows/Linux 非沙盒下 FactoryReset 递归删除 ~/Documents 导致用户项目源代码丢失的严重 bug,改为仅删除已知应用专属文件/子目录并增加路径安全校验

- 数据库文件从 getApplicationDocumentsDirectory() 迁移到 getApplicationSupportDirectory()(应用专属),含自动迁移逻辑

- 启用 macOS Debug 模式沙盒,使开发环境与生产环境路径行为一致

- 统一迁移 13 处应用数据存储位置(Hive、聊天附件、字体、稍后读同步等)到 Application Support,应用启动时执行一次性迁移

- backup_service.dart 备份文件迁移至 Application Support,getBackupList() 兼容扫描新旧两个路径并去重

- clearCache() 同样修复危险递归清空逻辑

详见 CHANGELOG.md v6.136.0 ~ v6.138.0
2026-06-26 08:46:23 +08:00

164 lines
4.7 KiB
Dart

// ============================================================
// 闲言APP — 缓存管理服务
// 创建时间: 2026-05-09
// 更新时间: 2026-05-09
// 作用: 传输缓存管理 — 缩略图/临时文件/传输记录清理
// 上次更新: 初始版本
// ============================================================
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:xianyan/core/utils/logger.dart';
import '../database/transfer_database.dart';
class CacheCategory {
const CacheCategory({
required this.name,
required this.emoji,
required this.directory,
});
final String name;
final String emoji;
final String directory;
}
class CacheInfo {
const CacheInfo({
required this.category,
required this.size,
required this.fileCount,
});
final CacheCategory category;
final int size;
final int fileCount;
String get sizeText => _formatSize(size);
static String _formatSize(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
if (bytes < 1024 * 1024 * 1024)
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
}
}
class CacheManagerService {
CacheManagerService();
static const List<CacheCategory> categories = [
CacheCategory(name: '缩略图', emoji: '🖼️', directory: 'thumbnails'),
CacheCategory(name: '临时文件', emoji: '📁', directory: 'temp'),
CacheCategory(name: '传输记录', emoji: '📋', directory: 'records'),
CacheCategory(name: '接收文件', emoji: '📥', directory: 'received'),
];
Future<String> _getBasePath() async {
final dir = await getApplicationSupportDirectory();
return '${dir.path}${Platform.pathSeparator}file_transfer';
}
Future<Directory> _getCategoryDir(CacheCategory category) async {
final basePath = await _getBasePath();
final dir = Directory(
'$basePath${Platform.pathSeparator}${category.directory}',
);
if (!await dir.exists()) {
await dir.create(recursive: true);
}
return dir;
}
Future<List<CacheInfo>> getAllCacheInfo() async {
final infos = <CacheInfo>[];
for (final category in categories) {
final info = await getCacheInfo(category);
infos.add(info);
}
return infos;
}
Future<CacheInfo> getCacheInfo(CacheCategory category) async {
try {
final dir = await _getCategoryDir(category);
int totalSize = 0;
int fileCount = 0;
await for (final entity in dir.list(recursive: true)) {
if (entity is File) {
try {
totalSize += await entity.length();
fileCount++;
} catch (_) {}
}
}
return CacheInfo(
category: category,
size: totalSize,
fileCount: fileCount,
);
} catch (e) {
Log.w('CacheManager: Failed to get info for ${category.name}: $e');
return CacheInfo(category: category, size: 0, fileCount: 0);
}
}
Future<int> getTotalCacheSize() async {
final infos = await getAllCacheInfo();
return infos.fold<int>(0, (sum, info) => sum + info.size);
}
Future<void> clearCategory(CacheCategory category) async {
try {
final dir = await _getCategoryDir(category);
await for (final entity in dir.list()) {
if (entity is File) {
await entity.delete();
} else if (entity is Directory) {
await entity.delete(recursive: true);
}
}
Log.i('CacheManager: Cleared ${category.name}');
} catch (e) {
Log.e('CacheManager: Failed to clear ${category.name}: $e');
}
}
Future<void> clearAll() async {
for (final category in categories) {
await clearCategory(category);
}
Log.i('CacheManager: All caches cleared');
}
Future<void> cleanOldRecords({int keepDays = 30}) async {
await TransferDatabase.instance.cleanOldRecords(keepDays);
Log.i('CacheManager: Old records cleaned (keep $keepDays days)');
}
Future<String> getSavePath(String fileName, {String? subDir}) async {
final basePath = await _getBasePath();
final dir = subDir != null
? '$basePath${Platform.pathSeparator}$subDir'
: '$basePath${Platform.pathSeparator}received';
final directory = Directory(dir);
if (!await directory.exists()) {
await directory.create(recursive: true);
}
return '$dir${Platform.pathSeparator}$fileName';
}
Future<String> getThumbnailPath(String fileName) async {
return await getSavePath(fileName, subDir: 'thumbnails');
}
Future<String> getTempPath(String fileName) async {
return await getSavePath(fileName, subDir: 'temp');
}
}