2026-01-29 15:54:22 +00:00
|
|
|
|
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()) {
|
2026-02-15 15:13:12 +00:00
|
|
|
|
debugPrint('❌ Missing: $imagePath (${item.displayData.displayName})');
|
2026-01-29 15:54:22 +00:00
|
|
|
|
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 {
|
|
|
|
|
|
// マッチしない場合、警告してスキップ
|
2026-02-15 15:13:12 +00:00
|
|
|
|
debugPrint('⚠️ No match for: $oldFileName (${item.displayData.displayName})');
|
2026-01-29 15:54:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (needsRepair && newPaths.isNotEmpty) {
|
|
|
|
|
|
// パスを更新
|
|
|
|
|
|
final updatedItem = item.copyWith(
|
|
|
|
|
|
imagePaths: newPaths,
|
|
|
|
|
|
);
|
|
|
|
|
|
await box.put(item.key, updatedItem); // 🔧 Fixed: updatedItem を保存
|
|
|
|
|
|
repairedItems++;
|
2026-02-15 15:13:12 +00:00
|
|
|
|
debugPrint('✅ Updated: ${item.displayData.displayName} (${newPaths.length} paths)');
|
2026-01-29 15:54:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|