226 lines
7.8 KiB
Dart
226 lines
7.8 KiB
Dart
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);
|
||
}
|
||
}
|
||
}
|