266 lines
9.1 KiB
Dart
266 lines
9.1 KiB
Dart
import 'package:hive/hive.dart';
|
||
import 'package:path_provider/path_provider.dart' as path_provider;
|
||
import '../../../data/models/hive_inspiration_image.dart';
|
||
import '../../../data/models/hive_image_folder.dart';
|
||
import '../../../data/models/hive_image_tag.dart';
|
||
import '../../../core/utils/logger.dart';
|
||
import 'database_migration.dart';
|
||
|
||
/// Hive数据库管理类 - 负责本地数据存储和初始化
|
||
/// 提供应用程序所有数据的本地持久化存储功能
|
||
class HiveDatabase {
|
||
/// 图片数据存储盒名称 - 存储所有灵感图片数据
|
||
static const String _imagesBoxName = 'inspiration_images';
|
||
|
||
/// 文件夹数据存储盒名称 - 存储所有文件夹信息
|
||
static const String _foldersBoxName = 'image_folders';
|
||
|
||
/// 标签数据存储盒名称 - 存储所有标签信息
|
||
static const String _tagsBoxName = 'image_tags';
|
||
|
||
/// 应用设置存储盒名称 - 存储应用配置和数据库版本
|
||
static const String _settingsBoxName = 'app_settings';
|
||
|
||
/// 当前数据库版本号 - 用于数据迁移管理
|
||
/// 每次数据库结构变更时,需要递增此版本号
|
||
static const int _currentVersion = DatabaseMigration.currentVersion;
|
||
|
||
/// 数据库版本键名 - 在设置盒中存储版本信息的键
|
||
static const String _versionKey = DatabaseMigration.versionKey;
|
||
|
||
/// 初始化Hive数据库 - 应用程序启动时调用
|
||
/// 设置存储路径、注册适配器、打开数据盒并执行数据迁移
|
||
static Future<void> init() async {
|
||
final appDocumentDir = await path_provider.getApplicationDocumentsDirectory();
|
||
Hive.init(appDocumentDir.path);
|
||
|
||
_registerAdapters();
|
||
|
||
await Hive.openBox<HiveInspirationImage>(_imagesBoxName);
|
||
await Hive.openBox<HiveImageFolder>(_foldersBoxName);
|
||
await Hive.openBox<HiveImageTag>(_tagsBoxName);
|
||
await Hive.openBox<dynamic>(_settingsBoxName);
|
||
|
||
await _performMigration();
|
||
}
|
||
|
||
/// 注册Hive类型适配器 - 将自定义类型注册到Hive
|
||
/// 确保类型ID不冲突,每个类型有唯一的typeId
|
||
static void _registerAdapters() {
|
||
if (!Hive.isAdapterRegistered(0)) {
|
||
Hive.registerAdapter(HiveInspirationImageAdapter());
|
||
}
|
||
if (!Hive.isAdapterRegistered(1)) {
|
||
Hive.registerAdapter(HiveImageFolderAdapter());
|
||
}
|
||
if (!Hive.isAdapterRegistered(2)) {
|
||
Hive.registerAdapter(HiveImageTagAdapter());
|
||
}
|
||
}
|
||
|
||
/// 执行数据库迁移 - 检查版本并执行必要的迁移操作
|
||
/// 使用专门的迁移管理类来处理复杂的迁移逻辑
|
||
static Future<void> _performMigration() async {
|
||
final settingsBox = Hive.box<dynamic>(_settingsBoxName);
|
||
final currentDbVersion = settingsBox.get(_versionKey, defaultValue: 0) as int;
|
||
|
||
if (currentDbVersion < _currentVersion) {
|
||
try {
|
||
Logger.info('检测到数据库版本更新: $currentDbVersion -> $_currentVersion');
|
||
|
||
// 使用专门的迁移管理类执行迁移
|
||
await DatabaseMigration.migrateFromVersion(
|
||
currentDbVersion,
|
||
imagesBox,
|
||
foldersBox,
|
||
tagsBox,
|
||
settingsBox,
|
||
);
|
||
|
||
Logger.info('数据库迁移完成');
|
||
} catch (e) {
|
||
Logger.error('数据库迁移失败', error: e);
|
||
// 如果迁移失败,尝试恢复到已知状态
|
||
await _handleMigrationFailure(currentDbVersion, e);
|
||
}
|
||
} else if (currentDbVersion > _currentVersion) {
|
||
// 处理降级情况(通常不应该发生)
|
||
Logger.warning('数据库版本高于应用版本 ($currentDbVersion > $_currentVersion)');
|
||
await settingsBox.put(_versionKey, _currentVersion);
|
||
}
|
||
}
|
||
|
||
/// 处理迁移失败 - 当迁移过程中出现错误时的恢复逻辑
|
||
/// [fromVersion] 迁移起始版本
|
||
/// [error] 迁移过程中出现的错误
|
||
static Future<void> _handleMigrationFailure(int fromVersion, dynamic error) async {
|
||
Logger.error('处理迁移失败,尝试恢复到安全状态...', error: error);
|
||
|
||
try {
|
||
// 如果是最初版本(0)的迁移失败,可以尝试重新创建基础结构
|
||
if (fromVersion == 0) {
|
||
await _createEmergencyData();
|
||
}
|
||
|
||
// 记录错误信息到设置中,便于后续分析
|
||
final settingsBox = Hive.box<dynamic>(_settingsBoxName);
|
||
await settingsBox.put('last_migration_error', error.toString());
|
||
await settingsBox.put('last_migration_time', DateTime.now().toIso8601String());
|
||
|
||
} catch (recoveryError) {
|
||
Logger.fatal('迁移恢复失败', error: recoveryError);
|
||
// 如果恢复也失败,只能抛出异常让上层处理
|
||
throw DatabaseMigrationException(
|
||
fromVersion: fromVersion,
|
||
toVersion: _currentVersion,
|
||
message: '数据库迁移失败且无法恢复',
|
||
originalError: error,
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
/// 创建紧急恢复数据 - 当迁移失败时的紧急恢复方案
|
||
/// 确保应用至少能正常运行,包含最基本的数据结构
|
||
static Future<void> _createEmergencyData() async {
|
||
Logger.warning('创建紧急恢复数据...');
|
||
|
||
try {
|
||
final foldersBox = Hive.box<HiveImageFolder>(_foldersBoxName);
|
||
final settingsBox = Hive.box<dynamic>(_settingsBoxName);
|
||
|
||
// 确保至少有一个默认文件夹存在
|
||
if (!foldersBox.containsKey('default')) {
|
||
final now = DateTime.now();
|
||
final emergencyFolder = HiveImageFolder(
|
||
id: 'default',
|
||
name: '默认',
|
||
icon: 'folder',
|
||
createdAt: now,
|
||
updatedAt: now,
|
||
lastUsedAt: now,
|
||
);
|
||
await foldersBox.put('default', emergencyFolder);
|
||
Logger.info('创建紧急默认文件夹');
|
||
}
|
||
|
||
// 设置当前版本号,避免重复迁移
|
||
await settingsBox.put(_versionKey, _currentVersion);
|
||
|
||
Logger.info('紧急恢复数据创建完成');
|
||
|
||
} catch (e) {
|
||
Logger.error('紧急恢复数据创建失败', error: e);
|
||
// 如果连紧急恢复都失败,只能抛出致命异常
|
||
throw Exception('数据库初始化失败,应用无法正常运行: $e');
|
||
}
|
||
}
|
||
|
||
// Getters for boxes
|
||
static Box<HiveInspirationImage> get imagesBox => Hive.box<HiveInspirationImage>(_imagesBoxName);
|
||
static Box<HiveImageFolder> get foldersBox => Hive.box<HiveImageFolder>(_foldersBoxName);
|
||
static Box<HiveImageTag> get tagsBox => Hive.box<HiveImageTag>(_tagsBoxName);
|
||
static Box<dynamic> get settingsBox => Hive.box<dynamic>(_settingsBoxName);
|
||
|
||
/// 清理所有数据 - 清空数据库中的所有数据
|
||
/// 主要用于测试和调试,生产环境慎用
|
||
/// 返回清理操作是否成功
|
||
static Future<bool> clearAll() async {
|
||
try {
|
||
Logger.info('开始清理所有数据库数据...');
|
||
|
||
await imagesBox.clear();
|
||
await foldersBox.clear();
|
||
await tagsBox.clear();
|
||
await settingsBox.clear();
|
||
|
||
Logger.info('数据库清理完成');
|
||
return true;
|
||
} catch (e) {
|
||
Logger.error('数据库清理失败', error: e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 关闭数据库 - 释放所有Hive资源
|
||
/// 应用退出时调用,确保数据完整性
|
||
/// 返回关闭操作是否成功
|
||
static Future<bool> close() async {
|
||
try {
|
||
Logger.info('正在关闭Hive数据库...');
|
||
|
||
await Hive.close();
|
||
|
||
Logger.info('Hive数据库已关闭');
|
||
return true;
|
||
} catch (e) {
|
||
Logger.error('关闭Hive数据库失败', error: e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 验证数据库完整性 - 检查数据库是否损坏或缺少必要数据
|
||
/// 返回数据库是否通过完整性检查
|
||
static Future<bool> validateIntegrity() async {
|
||
try {
|
||
Logger.info('开始验证数据库完整性...');
|
||
|
||
final isValid = await DatabaseMigration.validateDatabaseIntegrity(
|
||
imagesBox,
|
||
foldersBox,
|
||
tagsBox,
|
||
);
|
||
|
||
if (isValid) {
|
||
Logger.info('数据库完整性验证通过');
|
||
} else {
|
||
Logger.info('数据库完整性验证发现问题并已修复');
|
||
}
|
||
|
||
return isValid;
|
||
} catch (e) {
|
||
Logger.error('数据库完整性验证失败', error: e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 获取数据库统计信息 - 获取数据库的使用统计
|
||
/// 返回包含统计信息的Map,包括版本、数量、大小等
|
||
static Map<String, dynamic> getStatistics() {
|
||
try {
|
||
return DatabaseMigration.getDatabaseStats(
|
||
imagesBox,
|
||
foldersBox,
|
||
tagsBox,
|
||
);
|
||
} catch (e) {
|
||
Logger.error('获取数据库统计信息失败', error: e);
|
||
return {
|
||
'error': '获取统计信息失败',
|
||
'message': e.toString(),
|
||
};
|
||
}
|
||
}
|
||
|
||
/// 清理数据库 - 清理无效数据和临时文件
|
||
/// 返回清理结果的统计信息,包括清理的项目数量
|
||
static Future<Map<String, int>> cleanup() async {
|
||
try {
|
||
Logger.info('开始清理数据库...');
|
||
|
||
final cleanupStats = await DatabaseMigration.cleanupDatabase(
|
||
imagesBox,
|
||
foldersBox,
|
||
tagsBox,
|
||
);
|
||
|
||
Logger.info('数据库清理完成: $cleanupStats');
|
||
return cleanupStats;
|
||
} catch (e) {
|
||
Logger.error('数据库清理失败', error: e);
|
||
return {
|
||
'error': 1,
|
||
};
|
||
}
|
||
}
|
||
} |