generated from dellevin/template
优化清理缓存逻辑
This commit is contained in:
@@ -17,6 +17,8 @@ import 'utils/usage_stats_service.dart';
|
||||
import 'providers/app_provider.dart';
|
||||
import 'providers/note_plus_provider.dart';
|
||||
|
||||
final RouteObserver<ModalRoute<void>> routeObserver = RouteObserver<ModalRoute<void>>();
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await UserPrefs.init();
|
||||
@@ -217,6 +219,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||
],
|
||||
home: const HomePage(),
|
||||
navigatorKey: _navigatorKey,
|
||||
navigatorObservers: [routeObserver],
|
||||
onGenerateRoute: AppRouter.generateRoute,
|
||||
builder: (context, child) {
|
||||
return _AppIconWrapper(iconName: iconName, child: child!);
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../main.dart' show routeObserver;
|
||||
import '../models/data_models.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
@@ -32,7 +33,7 @@ class ProfilePage extends StatefulWidget {
|
||||
State<ProfilePage> createState() => _ProfilePageState();
|
||||
}
|
||||
|
||||
class _ProfilePageState extends State<ProfilePage> {
|
||||
class _ProfilePageState extends State<ProfilePage> with RouteAware {
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
final UserPrefs _userPrefs = UserPrefs();
|
||||
|
||||
@@ -46,6 +47,24 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
_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 {
|
||||
try {
|
||||
await UserPrefs.init();
|
||||
@@ -1821,7 +1840,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.onSurface)),
|
||||
content: Text('这将删除所有未在数据库中引用的图片文件。确定要继续吗?',
|
||||
content: Text('这将删除所有未在数据库中引用的文件。确定要继续吗?',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: colors.onSurface.withValues(alpha: 0.6),
|
||||
@@ -1877,12 +1896,13 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
|
||||
Navigator.pop(context);
|
||||
if (context.mounted) {
|
||||
final total = deletedImages + deletedEpubs + deletedTemp + deletedEmptyDirs;
|
||||
final total =
|
||||
deletedImages + deletedEpubs + deletedTemp + deletedEmptyDirs;
|
||||
if (total == 0) {
|
||||
ToastUtil.show(context, '没有需要清理的缓存');
|
||||
} else {
|
||||
ToastUtil.show(
|
||||
context, '已清理 $deletedImages 个孤立图片,$deletedEpubs 个孤立电子书,$deletedTemp 个临时文件,$deletedEmptyDirs 个空文件夹');
|
||||
ToastUtil.show(context,
|
||||
'已清理 $deletedImages 个孤立图片,$deletedEpubs 个孤立电子书,$deletedTemp 个临时文件,$deletedEmptyDirs 个空文件夹');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -1939,10 +1959,16 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
Future<int> _cleanOrphanedEpubBooks(AppProvider provider) async {
|
||||
int deletedCount = 0;
|
||||
try {
|
||||
// 收集数据库中所有 reader_books 的 bookId
|
||||
final db = await DatabaseHelper.instance.database;
|
||||
final rows = await db.query('reader_books', columns: ['id']);
|
||||
final dbIds = rows.map((r) => r['id'] as String).toSet();
|
||||
// 收集数据库中所有引用的 epub_books 子目录名
|
||||
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 epubDir = Directory('${appDir.path}/epub_books');
|
||||
@@ -1951,7 +1977,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
await for (final entity in epubDir.list(followLinks: false)) {
|
||||
if (entity is Directory) {
|
||||
final dirName = path.basename(entity.path);
|
||||
if (!dbIds.contains(dirName)) {
|
||||
if (!usedDirs.contains(dirName)) {
|
||||
try {
|
||||
await entity.delete(recursive: true);
|
||||
deletedCount++;
|
||||
@@ -1965,6 +1991,17 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
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 {
|
||||
int deletedCount = 0;
|
||||
final now = DateTime.now();
|
||||
|
||||
@@ -61,10 +61,8 @@ class BackupService {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final rb in readerBooks) {
|
||||
final p = rb['cover_path'] as String?;
|
||||
if (p != null && p.isNotEmpty) imagePaths.add(p);
|
||||
}
|
||||
// reader_books 的封面在 epub_books/ 目录下,由 epub_books 归档处理
|
||||
// 不加入 imagePaths,避免 basename 碰撞导致所有封面变成同一个路径
|
||||
|
||||
final userPrefs = UserPrefs();
|
||||
final userInfo = {
|
||||
@@ -258,6 +256,8 @@ class BackupService {
|
||||
int imageCount = 0;
|
||||
// 完整相对路径 → 新绝对路径 的映射(避免同名文件碰撞)
|
||||
final imagePathMap = <String, String>{};
|
||||
// epub_books/ 内相对路径 → 新绝对路径 的映射
|
||||
final epubFileMap = <String, String>{};
|
||||
|
||||
if (extension == '.zip') {
|
||||
final bytes = await file.readAsBytes();
|
||||
@@ -288,6 +288,7 @@ class BackupService {
|
||||
final outputFile = File(path.join(epubDir.path, relativePath));
|
||||
if (!await outputFile.parent.exists()) await outputFile.parent.create(recursive: true);
|
||||
await outputFile.writeAsBytes(archiveFile.content as List<int>);
|
||||
epubFileMap[relativePath] = outputFile.path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -368,7 +369,9 @@ class BackupService {
|
||||
}
|
||||
if (data.containsKey('reader_books')) {
|
||||
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')) {
|
||||
@@ -410,6 +413,7 @@ class BackupService {
|
||||
|
||||
final backupData = jsonDecode(utf8.decode(dataFile.content as List<int>)) as Map<String, dynamic>;
|
||||
final imagePathMap = <String, String>{};
|
||||
final epubFileMap = <String, String>{};
|
||||
int imageCount = 0;
|
||||
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
@@ -431,6 +435,7 @@ class BackupService {
|
||||
final outputFile = File(path.join(epubDir.path, relativePath));
|
||||
if (!await outputFile.parent.exists()) await outputFile.parent.create(recursive: true);
|
||||
await outputFile.writeAsBytes(archiveFile.content as List<int>);
|
||||
epubFileMap[relativePath] = outputFile.path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,7 +511,9 @@ class BackupService {
|
||||
}
|
||||
if (data.containsKey('reader_books')) {
|
||||
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')) {
|
||||
@@ -651,6 +658,39 @@ class BackupService {
|
||||
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)
|
||||
Map<String, dynamic> _updateImagePath(Map<String, dynamic> item, String pathField, Map<String, String> imagePathMap) {
|
||||
final newItem = Map<String, dynamic>.from(item);
|
||||
|
||||
Reference in New Issue
Block a user