优化清理缓存逻辑

This commit is contained in:
DelLevin-Home
2026-06-28 07:43:57 +08:00
parent 8bdb921329
commit 370f1c6787
4 changed files with 96 additions and 16 deletions

View File

@@ -12,7 +12,7 @@ http://docmost.iletter.top/share/ropwljpyvn/p/mook-note-lHmPTswdDC
| :--------------------------------------------: | :--------------------------------------------: | :--------------------------------------------: | | :--------------------------------------------: | :--------------------------------------------: | :--------------------------------------------: |
| ![1782484732881](image/README/1782484732881.png) | ![1782484753882](image/README/1782484753882.png) | ![1782484769014](image/README/1782484769014.png) | | ![1782484732881](image/README/1782484732881.png) | ![1782484753882](image/README/1782484753882.png) | ![1782484769014](image/README/1782484769014.png) |
更多预览图片请到**应用功能****详情预览**里面查看 更多预览图片请到**应用功能**详情预览里面查看
## 功能特性 ## 功能特性

View File

@@ -17,6 +17,8 @@ import 'utils/usage_stats_service.dart';
import 'providers/app_provider.dart'; import 'providers/app_provider.dart';
import 'providers/note_plus_provider.dart'; import 'providers/note_plus_provider.dart';
final RouteObserver<ModalRoute<void>> routeObserver = RouteObserver<ModalRoute<void>>();
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await UserPrefs.init(); await UserPrefs.init();
@@ -217,6 +219,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
], ],
home: const HomePage(), home: const HomePage(),
navigatorKey: _navigatorKey, navigatorKey: _navigatorKey,
navigatorObservers: [routeObserver],
onGenerateRoute: AppRouter.generateRoute, onGenerateRoute: AppRouter.generateRoute,
builder: (context, child) { builder: (context, child) {
return _AppIconWrapper(iconName: iconName, child: child!); return _AppIconWrapper(iconName: iconName, child: child!);

View File

@@ -6,6 +6,7 @@ import 'package:path/path.dart' as path;
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../main.dart' show routeObserver;
import '../models/data_models.dart'; import '../models/data_models.dart';
import '../providers/app_provider.dart'; import '../providers/app_provider.dart';
import '../utils/user_prefs.dart'; import '../utils/user_prefs.dart';
@@ -32,7 +33,7 @@ class ProfilePage extends StatefulWidget {
State<ProfilePage> createState() => _ProfilePageState(); State<ProfilePage> createState() => _ProfilePageState();
} }
class _ProfilePageState extends State<ProfilePage> { class _ProfilePageState extends State<ProfilePage> with RouteAware {
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
final UserPrefs _userPrefs = UserPrefs(); final UserPrefs _userPrefs = UserPrefs();
@@ -46,6 +47,24 @@ class _ProfilePageState extends State<ProfilePage> {
_loadUserData(); _loadUserData();
} }
@override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context)!);
}
@override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
@override
void didPopNext() {
// 从其他页面返回时刷新用户数据(头像、昵称等)
_loadUserData();
}
Future<void> _loadUserData() async { Future<void> _loadUserData() async {
try { try {
await UserPrefs.init(); await UserPrefs.init();
@@ -1821,7 +1840,7 @@ class _SettingsPageState extends State<SettingsPage> {
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: colors.onSurface)), color: colors.onSurface)),
content: Text('这将删除所有未在数据库中引用的图片文件。确定要继续吗?', content: Text('这将删除所有未在数据库中引用的文件。确定要继续吗?',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: colors.onSurface.withValues(alpha: 0.6), color: colors.onSurface.withValues(alpha: 0.6),
@@ -1877,12 +1896,13 @@ class _SettingsPageState extends State<SettingsPage> {
Navigator.pop(context); Navigator.pop(context);
if (context.mounted) { if (context.mounted) {
final total = deletedImages + deletedEpubs + deletedTemp + deletedEmptyDirs; final total =
deletedImages + deletedEpubs + deletedTemp + deletedEmptyDirs;
if (total == 0) { if (total == 0) {
ToastUtil.show(context, '没有需要清理的缓存'); ToastUtil.show(context, '没有需要清理的缓存');
} else { } else {
ToastUtil.show( ToastUtil.show(context,
context, '已清理 $deletedImages 个孤立图片,$deletedEpubs 个孤立电子书,$deletedTemp 个临时文件,$deletedEmptyDirs 个空文件夹'); '已清理 $deletedImages 个孤立图片,$deletedEpubs 个孤立电子书,$deletedTemp 个临时文件,$deletedEmptyDirs 个空文件夹');
} }
} }
} catch (e) { } catch (e) {
@@ -1939,10 +1959,16 @@ class _SettingsPageState extends State<SettingsPage> {
Future<int> _cleanOrphanedEpubBooks(AppProvider provider) async { Future<int> _cleanOrphanedEpubBooks(AppProvider provider) async {
int deletedCount = 0; int deletedCount = 0;
try { try {
// 收集数据库中所有 reader_books 的 bookId
final db = await DatabaseHelper.instance.database; final db = await DatabaseHelper.instance.database;
final rows = await db.query('reader_books', columns: ['id']); // 收集数据库中所有引用的 epub_books 子目录名
final dbIds = rows.map((r) => r['id'] as String).toSet(); final rows = await db.query('reader_books', columns: ['id', 'file_path', 'cover_path']);
final usedDirs = <String>{};
for (final r in rows) {
final id = r['id'] as String?;
if (id != null && id.isNotEmpty) usedDirs.add(id);
_collectEpubDirName(r['file_path'] as String?, usedDirs);
_collectEpubDirName(r['cover_path'] as String?, usedDirs);
}
final appDir = await getApplicationDocumentsDirectory(); final appDir = await getApplicationDocumentsDirectory();
final epubDir = Directory('${appDir.path}/epub_books'); final epubDir = Directory('${appDir.path}/epub_books');
@@ -1951,7 +1977,7 @@ class _SettingsPageState extends State<SettingsPage> {
await for (final entity in epubDir.list(followLinks: false)) { await for (final entity in epubDir.list(followLinks: false)) {
if (entity is Directory) { if (entity is Directory) {
final dirName = path.basename(entity.path); final dirName = path.basename(entity.path);
if (!dbIds.contains(dirName)) { if (!usedDirs.contains(dirName)) {
try { try {
await entity.delete(recursive: true); await entity.delete(recursive: true);
deletedCount++; deletedCount++;
@@ -1965,6 +1991,17 @@ class _SettingsPageState extends State<SettingsPage> {
return deletedCount; return deletedCount;
} }
/// 从绝对路径中提取 epub_books/ 下的目录名
void _collectEpubDirName(String? pathStr, Set<String> dirs) {
if (pathStr == null || pathStr.isEmpty) return;
final marker = '/epub_books/';
final idx = pathStr.indexOf(marker);
if (idx < 0) return;
final rest = pathStr.substring(idx + marker.length);
final slashIdx = rest.indexOf('/');
dirs.add(slashIdx >= 0 ? rest.substring(0, slashIdx) : rest);
}
Future<int> _cleanTempDirectory() async { Future<int> _cleanTempDirectory() async {
int deletedCount = 0; int deletedCount = 0;
final now = DateTime.now(); final now = DateTime.now();

View File

@@ -61,10 +61,8 @@ class BackupService {
} }
} }
} }
for (final rb in readerBooks) { // reader_books 的封面在 epub_books/ 目录下,由 epub_books 归档处理
final p = rb['cover_path'] as String?; // 不加入 imagePaths避免 basename 碰撞导致所有封面变成同一个路径
if (p != null && p.isNotEmpty) imagePaths.add(p);
}
final userPrefs = UserPrefs(); final userPrefs = UserPrefs();
final userInfo = { final userInfo = {
@@ -258,6 +256,8 @@ class BackupService {
int imageCount = 0; int imageCount = 0;
// 完整相对路径 → 新绝对路径 的映射(避免同名文件碰撞) // 完整相对路径 → 新绝对路径 的映射(避免同名文件碰撞)
final imagePathMap = <String, String>{}; final imagePathMap = <String, String>{};
// epub_books/ 内相对路径 → 新绝对路径 的映射
final epubFileMap = <String, String>{};
if (extension == '.zip') { if (extension == '.zip') {
final bytes = await file.readAsBytes(); final bytes = await file.readAsBytes();
@@ -288,6 +288,7 @@ class BackupService {
final outputFile = File(path.join(epubDir.path, relativePath)); final outputFile = File(path.join(epubDir.path, relativePath));
if (!await outputFile.parent.exists()) await outputFile.parent.create(recursive: true); if (!await outputFile.parent.exists()) await outputFile.parent.create(recursive: true);
await outputFile.writeAsBytes(archiveFile.content as List<int>); await outputFile.writeAsBytes(archiveFile.content as List<int>);
epubFileMap[relativePath] = outputFile.path;
} }
} }
} else { } else {
@@ -368,7 +369,9 @@ class BackupService {
} }
if (data.containsKey('reader_books')) { if (data.containsKey('reader_books')) {
for (final rb in data['reader_books'] as List) { for (final rb in data['reader_books'] as List) {
await txn.insert('reader_books', _updateImagePath(_convertToDbMapSafe(rb, readerBooksCols), 'cover_path', imagePathMap)); var row = _updateImagePath(_convertToDbMapSafe(rb, readerBooksCols), 'cover_path', imagePathMap);
row = _updateEpubPaths(row, epubFileMap);
await txn.insert('reader_books', row);
} }
} }
if (data.containsKey('book_annotations')) { if (data.containsKey('book_annotations')) {
@@ -410,6 +413,7 @@ class BackupService {
final backupData = jsonDecode(utf8.decode(dataFile.content as List<int>)) as Map<String, dynamic>; final backupData = jsonDecode(utf8.decode(dataFile.content as List<int>)) as Map<String, dynamic>;
final imagePathMap = <String, String>{}; final imagePathMap = <String, String>{};
final epubFileMap = <String, String>{};
int imageCount = 0; int imageCount = 0;
final appDir = await getApplicationDocumentsDirectory(); final appDir = await getApplicationDocumentsDirectory();
@@ -431,6 +435,7 @@ class BackupService {
final outputFile = File(path.join(epubDir.path, relativePath)); final outputFile = File(path.join(epubDir.path, relativePath));
if (!await outputFile.parent.exists()) await outputFile.parent.create(recursive: true); if (!await outputFile.parent.exists()) await outputFile.parent.create(recursive: true);
await outputFile.writeAsBytes(archiveFile.content as List<int>); await outputFile.writeAsBytes(archiveFile.content as List<int>);
epubFileMap[relativePath] = outputFile.path;
} }
} }
@@ -506,7 +511,9 @@ class BackupService {
} }
if (data.containsKey('reader_books')) { if (data.containsKey('reader_books')) {
for (final rb in data['reader_books'] as List) { for (final rb in data['reader_books'] as List) {
await txn.insert('reader_books', _updateImagePath(_convertToDbMapSafe(rb, readerBooksCols), 'cover_path', imagePathMap)); var row = _updateImagePath(_convertToDbMapSafe(rb, readerBooksCols), 'cover_path', imagePathMap);
row = _updateEpubPaths(row, epubFileMap);
await txn.insert('reader_books', row);
} }
} }
if (data.containsKey('book_annotations')) { if (data.containsKey('book_annotations')) {
@@ -651,6 +658,39 @@ class BackupService {
return {}; return {};
} }
/// 更新 epub 阅读器的 file_path 和 cover_path
Map<String, dynamic> _updateEpubPaths(Map<String, dynamic> item, Map<String, String> epubFileMap) {
if (epubFileMap.isEmpty) return item;
final newItem = Map<String, dynamic>.from(item);
final oldFilePath = item['file_path'] as String?;
if (oldFilePath != null && oldFilePath.isNotEmpty) {
final oldRel = _toEpubRelativePath(oldFilePath);
if (oldRel != null && epubFileMap.containsKey(oldRel)) {
newItem['file_path'] = epubFileMap[oldRel];
}
}
final oldCoverPath = item['cover_path'] as String?;
if (oldCoverPath != null && oldCoverPath.isNotEmpty) {
final oldRel = _toEpubRelativePath(oldCoverPath);
if (oldRel != null && epubFileMap.containsKey(oldRel)) {
newItem['cover_path'] = epubFileMap[oldRel];
}
}
return newItem;
}
/// 从绝对路径中提取 epub_books/ 下的相对路径
String? _toEpubRelativePath(String absolutePath) {
final idx = absolutePath.indexOf('/epub_books/');
if (idx >= 0) return absolutePath.substring(idx + 13); // skip '/epub_books/'
final winIdx = absolutePath.indexOf('\\epub_books\\');
if (winIdx >= 0) return absolutePath.substring(winIdx + 13);
return null;
}
/// 更新单值图片路径poster_path / cover_path /// 更新单值图片路径poster_path / cover_path
Map<String, dynamic> _updateImagePath(Map<String, dynamic> item, String pathField, Map<String, String> imagePathMap) { Map<String, dynamic> _updateImagePath(Map<String, dynamic> item, String pathField, Map<String, String> imagePathMap) {
final newItem = Map<String, dynamic>.from(item); final newItem = Map<String, dynamic>.from(item);