ponshu-room-lite/lib/services/image_path_repair_service.dart

226 lines
7.8 KiB
Dart
Raw Normal View History

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import '../models/sake_item.dart';
/// 画像パス修復サービス
///
/// バックアップ復元後に画像パスが不整合になった場合の修復
class ImagePathRepairService {
/// 画像パスの整合性をチェック
///
/// 戻り値: (総アイテム数, 問題のあるアイテム数, 欠損ファイル数)
static Future<(int, int, int)> diagnose() async {
try {
final box = Hive.box<SakeItem>('sake_items');
final items = box.values.toList();
int totalItems = items.length;
int problematicItems = 0;
int missingFiles = 0;
debugPrint('🔍 画像パス診断開始: $totalItems アイテム');
for (final item in items) {
bool hasIssue = false;
for (final imagePath in item.displayData.imagePaths) {
final file = File(imagePath);
if (!await file.exists()) {
debugPrint('❌ Missing: $imagePath (${item.displayData.displayName})');
missingFiles++;
hasIssue = true;
}
}
if (hasIssue) {
problematicItems++;
}
}
debugPrint('📊 診断結果: $totalItems アイテム中 $problematicItems に問題あり ($missingFiles ファイル欠損)');
return (totalItems, problematicItems, missingFiles);
} catch (e) {
debugPrint('❌ 診断エラー: $e');
return (0, 0, 0);
}
}
/// 画像パスを修復
///
/// 戦略:
/// 1. 存在しないパスを検出
/// 2. getApplicationDocumentsDirectory() 内の実際のファイルを探す
/// 3. ファイル名UUIDで照合
/// 4. パスを更新
///
/// 戻り値: (修復したアイテム数, 修復した画像パス数)
static Future<(int, int)> repair() async {
try {
final box = Hive.box<SakeItem>('sake_items');
final items = box.values.toList();
final appDir = await getApplicationDocumentsDirectory();
// アプリディレクトリ内のすべての画像ファイルを取得
final availableFiles = <String>[];
final dir = Directory(appDir.path);
await for (final entity in dir.list()) {
if (entity is File) {
final fileName = path.basename(entity.path);
if (fileName.endsWith('.jpg') || fileName.endsWith('.jpeg') || fileName.endsWith('.png')) {
availableFiles.add(entity.path);
}
}
}
debugPrint('📁 利用可能な画像ファイル: ${availableFiles.length}');
int repairedItems = 0;
int repairedPaths = 0;
for (final item in items) {
bool needsRepair = false;
List<String> newPaths = [];
for (final oldPath in item.displayData.imagePaths) {
final file = File(oldPath);
if (await file.exists()) {
// パスが有効な場合はそのまま
newPaths.add(oldPath);
} else {
// パスが無効な場合、ファイル名で照合
final oldFileName = path.basename(oldPath);
// 完全一致を探す
String? matchedPath;
for (final availablePath in availableFiles) {
if (path.basename(availablePath) == oldFileName) {
matchedPath = availablePath;
break;
}
}
if (matchedPath != null) {
newPaths.add(matchedPath);
repairedPaths++;
needsRepair = true;
debugPrint('🔧 Repaired: $oldFileName -> $matchedPath');
} else {
// マッチしない場合、警告してスキップ
debugPrint('⚠️ No match for: $oldFileName (${item.displayData.displayName})');
}
}
}
if (needsRepair && newPaths.isNotEmpty) {
// パスを更新
final updatedItem = item.copyWith(
imagePaths: newPaths,
);
await box.put(item.key, updatedItem); // 🔧 Fixed: updatedItem を保存
repairedItems++;
debugPrint('✅ Updated: ${item.displayData.displayName} (${newPaths.length} paths)');
}
}
debugPrint('✅ 修復完了: $repairedItems アイテム、$repairedPaths パス');
return (repairedItems, repairedPaths);
} catch (e) {
debugPrint('❌ 修復エラー: $e');
return (0, 0);
}
}
/// 孤立したファイルを検出Hiveに参照されていない画像ファイル
///
/// 戻り値: (孤立ファイル数, 合計サイズ(bytes))
static Future<(int, int)> findOrphanedFiles() async {
try {
final box = Hive.box<SakeItem>('sake_items');
final items = box.values.toList();
final appDir = await getApplicationDocumentsDirectory();
// Hiveに登録されているすべての画像パスを収集
final registeredPaths = <String>{};
for (final item in items) {
registeredPaths.addAll(item.displayData.imagePaths);
}
// アプリディレクトリ内のすべての画像ファイルを取得
int orphanedCount = 0;
int totalSize = 0;
final dir = Directory(appDir.path);
await for (final entity in dir.list()) {
if (entity is File) {
final fileName = path.basename(entity.path);
if (fileName.endsWith('.jpg') || fileName.endsWith('.jpeg') || fileName.endsWith('.png')) {
if (!registeredPaths.contains(entity.path)) {
final size = await entity.length();
orphanedCount++;
totalSize += size;
debugPrint('🗑️ Orphaned: $fileName (${(size / 1024).toStringAsFixed(1)}KB)');
}
}
}
}
debugPrint('📊 孤立ファイル: $orphanedCount 個 (${(totalSize / 1024 / 1024).toStringAsFixed(1)}MB)');
return (orphanedCount, totalSize);
} catch (e) {
debugPrint('❌ 孤立ファイル検出エラー: $e');
return (0, 0);
}
}
/// 孤立ファイルを削除
///
/// 戻り値: (削除したファイル数, 削減したサイズ(bytes))
static Future<(int, int)> cleanOrphanedFiles() async {
try {
final box = Hive.box<SakeItem>('sake_items');
final items = box.values.toList();
final appDir = await getApplicationDocumentsDirectory();
// Hiveに登録されているすべての画像パスを収集
final registeredPaths = <String>{};
for (final item in items) {
registeredPaths.addAll(item.displayData.imagePaths);
}
// アプリディレクトリ内のすべての画像ファイルを取得
int deletedCount = 0;
int deletedSize = 0;
final dir = Directory(appDir.path);
await for (final entity in dir.list()) {
if (entity is File) {
final fileName = path.basename(entity.path);
if (fileName.endsWith('.jpg') || fileName.endsWith('.jpeg') || fileName.endsWith('.png')) {
if (!registeredPaths.contains(entity.path)) {
final size = await entity.length();
await entity.delete();
deletedCount++;
deletedSize += size;
debugPrint('🗑️ Deleted: $fileName (${(size / 1024).toStringAsFixed(1)}KB)');
}
}
}
}
debugPrint('✅ 孤立ファイル削除完了: $deletedCount 個 (${(deletedSize / 1024 / 1024).toStringAsFixed(1)}MB)');
return (deletedCount, deletedSize);
} catch (e) {
debugPrint('❌ 孤立ファイル削除エラー: $e');
return (0, 0);
}
}
}