- 修复 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
164 lines
4.7 KiB
Dart
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');
|
|
}
|
|
}
|