snap_wish/lib/data/datasources/local/database_migration.dart

496 lines
15 KiB
Dart

import 'package:hive/hive.dart';
import '../../models/hive_inspiration_image.dart';
import '../../models/hive_image_folder.dart';
import '../../models/hive_image_tag.dart';
import '../../../core/utils/logger.dart';
/// 数据库迁移管理类 - 负责处理数据库版本升级和数据迁移
/// 确保应用在升级时能够正确迁移旧版本数据
class DatabaseMigration {
/// 当前数据库版本号 - 每次数据库结构变更时递增
static const int currentVersion = 1;
/// 数据库版本键名 - 在设置盒中存储版本信息的键
static const String versionKey = 'database_version';
/// 执行数据库迁移 - 根据当前版本和目标版本执行相应的迁移逻辑
/// [fromVersion] 当前数据库版本
/// [imagesBox] 图片数据盒
/// [foldersBox] 文件夹数据盒
/// [tagsBox] 标签数据盒
/// [settingsBox] 设置数据盒
static Future<void> migrateFromVersion(
int fromVersion,
Box<HiveInspirationImage> imagesBox,
Box<HiveImageFolder> foldersBox,
Box<HiveImageTag> tagsBox,
Box<dynamic> settingsBox,
) async {
// TODO: 使用日志系统替代print
// print('开始数据库迁移: 从版本 $fromVersion 到版本 $currentVersion');
// 按版本顺序执行迁移
for (int version = fromVersion + 1; version <= currentVersion; version++) {
// TODO: 使用日志系统替代print
// print('执行迁移到版本 $version');
switch (version) {
case 1:
await _migrateToVersion1(foldersBox, tagsBox);
break;
// case 2:
// await _migrateToVersion2(imagesBox, foldersBox, tagsBox);
// break;
// 在这里添加更多版本的迁移逻辑
default:
// TODO: 使用日志系统替代print
// print('未知的数据库版本: $version');
break;
}
// 更新版本号
await settingsBox.put(versionKey, version);
// TODO: 使用日志系统替代print
// print('成功迁移到版本 $version');
}
// TODO: 使用日志系统替代print
// print('数据库迁移完成');
}
/// 迁移到版本1 - 初始版本,创建默认数据
/// [foldersBox] 文件夹数据盒
/// [tagsBox] 标签数据盒
static Future<void> _migrateToVersion1(
Box<HiveImageFolder> foldersBox,
Box<HiveImageTag> tagsBox,
) async {
// TODO: 使用日志系统替代print
// print('创建版本1的默认数据...');
// 创建默认文件夹
await createDefaultFolders(foldersBox);
// 创建默认标签
await createDefaultTags(tagsBox);
// TODO: 使用日志系统替代print
// print('版本1默认数据创建完成');
}
/// 创建默认文件夹 - 初始化系统必需的文件夹
/// [foldersBox] 文件夹数据盒
static Future<void> createDefaultFolders(Box<HiveImageFolder> foldersBox) async {
// TODO: 使用日志系统替代print
// print('创建默认文件夹...');
// 默认文件夹配置
final defaultFolders = [
{
'id': 'default',
'name': '默认',
'icon': 'folder',
'description': '默认文件夹,用于存放未分类的图片',
},
{
'id': 'favorites',
'name': '收藏',
'icon': 'favorite',
'description': '收藏的图片',
},
{
'id': 'inspiration',
'name': '灵感',
'icon': 'lightbulb',
'description': '灵感素材',
},
];
for (final folderConfig in defaultFolders) {
// 检查文件夹是否已存在
if (!foldersBox.containsKey(folderConfig['id'])) {
final now = DateTime.now();
final folder = HiveImageFolder(
id: folderConfig['id'] as String,
name: folderConfig['name'] as String,
icon: folderConfig['icon'] as String,
createdAt: now,
updatedAt: now,
lastUsedAt: now,
);
await foldersBox.put(folder.id, folder);
} else {
Logger.debug('默认文件夹已存在: ${folderConfig['name']}');
}
}
}
/// 创建默认标签 - 初始化系统常用的标签
/// [tagsBox] 标签数据盒
static Future<void> createDefaultTags(Box<HiveImageTag> tagsBox) async {
Logger.info('创建默认标签...');
// 默认标签配置
final defaultTags = [
{
'id': 'tag_favorite',
'name': '收藏',
'icon': 'favorite',
'color': '#FF4444',
'description': '收藏的标签',
},
{
'id': 'tag_inspiration',
'name': '灵感',
'icon': 'lightbulb',
'color': '#FFD700',
'description': '灵感素材',
},
{
'id': 'tag_design',
'name': '设计',
'icon': 'palette',
'color': '#9C27B0',
'description': '设计相关',
},
{
'id': 'tag_photo',
'name': '摄影',
'icon': 'camera_alt',
'color': '#2196F3',
'description': '摄影作品',
},
{
'id': 'tag_nature',
'name': '自然',
'icon': 'nature',
'color': '#4CAF50',
'description': '自然风光',
},
{
'id': 'tag_architecture',
'name': '建筑',
'icon': 'location_city',
'color': '#795548',
'description': '建筑摄影',
},
{
'id': 'tag_food',
'name': '美食',
'icon': 'restaurant',
'color': '#FF9800',
'description': '美食摄影',
},
{
'id': 'tag_travel',
'name': '旅行',
'icon': 'flight',
'color': '#00BCD4',
'description': '旅行记录',
},
];
for (final tagConfig in defaultTags) {
// 检查标签是否已存在
if (!tagsBox.containsKey(tagConfig['id'])) {
final now = DateTime.now();
final tag = HiveImageTag(
id: tagConfig['id'] as String,
name: tagConfig['name'] as String,
icon: tagConfig['icon'] as String,
color: tagConfig['color'] as String,
usageCount: 0,
lastUsedAt: now,
);
await tagsBox.put(tag.id, tag);
}
}
}
/// 验证数据库完整性 - 检查数据库是否损坏或缺少必要数据
/// [imagesBox] 图片数据盒
/// [foldersBox] 文件夹数据盒
/// [tagsBox] 标签数据盒
/// 返回验证结果,如果发现问题会尝试修复
static Future<bool> validateDatabaseIntegrity(
Box<HiveInspirationImage> imagesBox,
Box<HiveImageFolder> foldersBox,
Box<HiveImageTag> tagsBox,
) async {
Logger.info('开始数据库完整性验证...');
bool isValid = true;
try {
// 验证默认文件夹是否存在
if (!foldersBox.containsKey('default')) {
// TODO: 使用日志系统替代print
// print('警告:缺少默认文件夹,正在创建...');
await createDefaultFolders(foldersBox);
isValid = false;
}
// 验证图片数据完整性
for (final image in imagesBox.values) {
// 验证文件夹关联
if (image.folderId != null && !foldersBox.containsKey(image.folderId)) {
// TODO: 使用日志系统替代print
// print('警告:图片 ${image.id} 关联了不存在的文件夹 ${image.folderId},正在修复...');
// 将图片移动到默认文件夹
final updatedImage = HiveInspirationImage(
id: image.id,
filePath: image.filePath,
thumbnailPath: image.thumbnailPath,
folderId: 'default',
tags: image.tags,
note: image.note,
createdAt: image.createdAt,
updatedAt: image.updatedAt,
originalName: image.originalName,
fileSize: image.fileSize,
mimeType: image.mimeType,
width: image.width,
height: image.height,
isFavorite: image.isFavorite,
);
await imagesBox.put(image.id, updatedImage);
isValid = false;
}
// 验证标签关联
for (final tagId in image.tags) {
if (!tagsBox.containsKey(tagId)) {
// TODO: 使用日志系统替代print
// print('警告:图片 ${image.id} 关联了不存在的标签 $tagId');
// 移除无效的标签关联
final updatedTags = List<String>.from(image.tags)..remove(tagId);
final updatedImage = HiveInspirationImage(
id: image.id,
filePath: image.filePath,
thumbnailPath: image.thumbnailPath,
folderId: image.folderId,
tags: updatedTags,
note: image.note,
createdAt: image.createdAt,
updatedAt: image.updatedAt,
originalName: image.originalName,
fileSize: image.fileSize,
mimeType: image.mimeType,
width: image.width,
height: image.height,
isFavorite: image.isFavorite,
);
await imagesBox.put(image.id, updatedImage);
isValid = false;
}
}
}
// 验证标签使用次数统计
for (final tag in tagsBox.values) {
final actualUsageCount = imagesBox.values
.where((image) => image.tags.contains(tag.id))
.length;
if (tag.usageCount != actualUsageCount) {
final updatedTag = HiveImageTag(
id: tag.id,
name: tag.name,
icon: tag.icon,
color: tag.color,
usageCount: actualUsageCount,
lastUsedAt: tag.lastUsedAt,
);
await tagsBox.put(tag.id, updatedTag);
isValid = false;
}
}
} catch (e) {
// TODO: 使用日志系统替代print
// print('数据库完整性验证失败: $e');
isValid = false;
}
if (isValid) {
// TODO: 使用日志系统替代print
// print('数据库完整性验证通过');
} else {
// TODO: 使用日志系统替代print
// print('数据库完整性验证发现问题并已修复');
}
return isValid;
}
/// 备份数据库 - 创建数据库的备份副本
/// [backupPath] 备份文件路径
/// 返回是否备份成功
static Future<bool> backupDatabase(String backupPath) async {
try {
// TODO: 使用日志系统替代print
// print('开始备份数据库到: $backupPath');
// TODO: 实现数据库备份逻辑
// 这需要根据实际的Hive存储路径来实现
// TODO: 使用日志系统替代print
// print('数据库备份完成');
return true;
} catch (e) {
// TODO: 使用日志系统替代print
// print('数据库备份失败: $e');
return false;
}
}
/// 恢复数据库 - 从备份文件恢复数据库
/// [backupPath] 备份文件路径
/// 返回是否恢复成功
static Future<bool> restoreDatabase(String backupPath) async {
try {
// TODO: 使用日志系统替代print
// print('开始从备份恢复数据库: $backupPath');
// TODO: 实现数据库恢复逻辑
// 这需要根据实际的Hive存储路径来实现
// TODO: 使用日志系统替代print
// print('数据库恢复完成');
return true;
} catch (e) {
// TODO: 使用日志系统替代print
// print('数据库恢复失败: $e');
return false;
}
}
/// 获取数据库统计信息 - 获取数据库的使用统计
/// [imagesBox] 图片数据盒
/// [foldersBox] 文件夹数据盒
/// [tagsBox] 标签数据盒
/// 返回包含统计信息的Map
static Map<String, dynamic> getDatabaseStats(
Box<HiveInspirationImage> imagesBox,
Box<HiveImageFolder> foldersBox,
Box<HiveImageTag> tagsBox,
) {
return {
'version': currentVersion,
'images_count': imagesBox.length,
'folders_count': foldersBox.length,
'tags_count': tagsBox.length,
'total_size': _calculateTotalSize(imagesBox),
'last_updated': DateTime.now().toIso8601String(),
};
}
/// 计算总数据大小 - 估算数据库的总大小
/// [imagesBox] 图片数据盒
/// 返回估算的总大小(字节)
static int _calculateTotalSize(Box<HiveInspirationImage> imagesBox) {
int totalSize = 0;
for (final image in imagesBox.values) {
totalSize += image.fileSize;
}
return totalSize;
}
/// 清理数据库 - 清理无效数据和临时文件
/// [imagesBox] 图片数据盒
/// [foldersBox] 文件夹数据盒
/// [tagsBox] 标签数据盒
/// 返回清理结果的统计信息
static Future<Map<String, int>> cleanupDatabase(
Box<HiveInspirationImage> imagesBox,
Box<HiveImageFolder> foldersBox,
Box<HiveImageTag> tagsBox,
) async {
// TODO: 使用日志系统替代print
// print('开始清理数据库...');
final cleanupStats = <String, int>{};
try {
// 清理未使用的标签
final unusedTags = tagsBox.values.where((tag) => tag.usageCount == 0).toList();
cleanupStats['unused_tags_removed'] = unusedTags.length;
for (final tag in unusedTags) {
await tagsBox.delete(tag.id);
}
// 清理无效的图片文件引用
final invalidImages = imagesBox.values.where((image) {
// 这里可以添加文件存在性检查
// 暂时只检查文件夹关联
return image.folderId != null && !foldersBox.containsKey(image.folderId);
}).toList();
cleanupStats['invalid_images_fixed'] = invalidImages.length;
for (final image in invalidImages) {
final updatedImage = HiveInspirationImage(
id: image.id,
filePath: image.filePath,
thumbnailPath: image.thumbnailPath,
folderId: 'default',
tags: image.tags,
note: image.note,
createdAt: image.createdAt,
updatedAt: image.updatedAt,
originalName: image.originalName,
fileSize: image.fileSize,
mimeType: image.mimeType,
width: image.width,
height: image.height,
isFavorite: image.isFavorite,
);
await imagesBox.put(image.id, updatedImage);
}
// TODO: 使用日志系统替代print
// print('数据库清理完成: $cleanupStats');
} catch (e) {
// TODO: 使用日志系统替代print
// print('数据库清理失败: $e');
}
return cleanupStats;
}
}
/// 数据库异常类 - 数据库操作相关的异常
class DatabaseException implements Exception {
final String message;
final String? code;
final dynamic originalError;
DatabaseException(this.message, {this.code, this.originalError});
@override
String toString() => 'DatabaseException: $message${code != null ? ' (代码: $code)' : ''}';
}
/// 数据库迁移异常类 - 数据迁移过程中的异常
class DatabaseMigrationException extends DatabaseException {
final int fromVersion;
final int toVersion;
DatabaseMigrationException({
required this.fromVersion,
required this.toVersion,
required String message,
String? code,
dynamic originalError,
}) : super(
'数据库迁移失败 (版本 $fromVersion -> $toVersion): $message',
code: code,
originalError: originalError,
);
}