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

226 lines
7.8 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}