# 🚨 Critical Bug Report: 画像誤削除問題

**発見日**: 2026-01-22  
**影響**: ⚠️ ユーザーデータ消失  
**ステータス**: ✅ 修正完了

---

## 📊 問題の概要

### 症状
- カード一覧・詳細画面で日本酒の写真が表示されない
- ストレージ: 558MB → 409MB（Androidキャッシュクリア後）
- 一部の画像ファイルが消失

### 影響範囲
- **Critical**: ユーザーが撮影した日本酒の写真が削除された
- **被害画像**: `_gallery.jpg`, `_compressed.jpg` を含むすべてのファイル
- **被害者**: 一時ファイルクリーンアップを実行したユーザー

---

## 🔍 根本原因

### バグのあるコード

```dart
// lib/services/image_batch_compression_service.dart:157-192 (修正前)
static Future<(int, int)> cleanupTempFiles() async {
  // ❌ 問題: getApplicationDocumentsDirectory() をスキャン（永続ファイルがある場所）
  final directory = await getApplicationDocumentsDirectory();
  final dir = Directory(directory.path);
  
  await for (final entity in dir.list()) {
    if (entity is File) {
      final fileName = entity.path.split('/').last;
      
      // ❌ 問題: _compressed, _gallery を含むすべてのファイルを削除
      if (fileName.contains('_compressed') || fileName.contains('_gallery')) {
        await entity.delete(); // ← 本物の画像を削除！
      }
    }
  }
}
```

### なぜ本物の画像が削除されたのか？

#### 1. ギャラリー保存用一時ファイル
```dart
// lib/screens/camera_screen.dart:225 (修正前)
final String galleryPath = join(directory.path, '${const Uuid().v4()}_gallery.jpg');
//                              ↑ directory = getApplicationDocumentsDirectory()
```
- `_gallery.jpg` という名前で永続ディレクトリに保存
- 削除処理が失敗した場合、ファイルが残る
- `cleanupTempFiles()` で削除される

#### 2. 圧縮用一時ファイル
```dart
// lib/services/image_compression_service.dart:96 (修正前)
static Future<String> _generateCompressedPath(String sourcePath) async {
  final directory = await getApplicationDocumentsDirectory();
  return path.join(directory.path, '${fileName}_compressed$extension');
}
```
- `_compressed.jpg` という名前で永続ディレクトリに保存
- 一括圧縮で使用される
- `cleanupTempFiles()` で削除される

### 設計ミス

| ディレクトリ | 用途 | 実際の使い方（修正前） |
|------------|------|---------------------|
| `getApplicationDocumentsDirectory()` | **永続ファイル** | ✅ 本物の画像<br>❌ 一時ファイルも保存 |
| `getTemporaryDirectory()` | **一時ファイル** | ❌ 使われていない |

**問題点**: 永続ファイルと一時ファイルが同じディレクトリに混在

---

## ✅ 修正内容

### 修正1: `cleanupTempFiles()` のスキャン対象を変更

```dart
// lib/services/image_batch_compression_service.dart:157-192 (修正後)
static Future<(int, int)> cleanupTempFiles() async {
  // ✅ 修正: getTemporaryDirectory() をスキャン（一時ファイルのみ）
  final directory = await getTemporaryDirectory();
  final dir = Directory(directory.path);
  
  await for (final entity in dir.list()) {
    if (entity is File) {
      final fileName = entity.path.split('/').last;
      
      // ✅ 修正: 画像ファイルすべてを削除（一時ディレクトリ内のみ）
      if (fileName.endsWith('.jpg') || fileName.endsWith('.jpeg') || fileName.endsWith('.png')) {
        await entity.delete(); // ← 安全！
      }
    }
  }
}
```

**変更点**:
- `getApplicationDocumentsDirectory()` → `getTemporaryDirectory()`
- `contains('_compressed')` → `endsWith('.jpg')`（全画像を削除でOK）

### 修正2: ギャラリー用一時ファイルを `getTemporaryDirectory()` へ

```dart
// lib/screens/camera_screen.dart:222-231 (修正後)
// ✅ 修正: 一時ファイルは getTemporaryDirectory() に保存
final tempDir = await getTemporaryDirectory();
final String galleryPath = join(tempDir.path, '${const Uuid().v4()}_gallery.jpg');
```

### 修正3: 圧縮用一時ファイルを `getTemporaryDirectory()` へ

```dart
// lib/services/image_compression_service.dart:94-100 (修正後)
static Future<String> _generateCompressedPath(String sourcePath) async {
  // ✅ 修正: 一時ファイルは getTemporaryDirectory() に保存
  final directory = await getTemporaryDirectory();
  return path.join(directory.path, '${fileName}_compressed$extension');
}
```

---

## 📊 修正後のディレクトリ構成

| ディレクトリ | 用途 | 保存されるファイル |
|------------|------|------------------|
| `getApplicationDocumentsDirectory()` | **永続ファイル** | ✅ 本物の画像（UUID.jpg）<br>✅ Hive DB |
| `getTemporaryDirectory()` | **一時ファイル** | ✅ _gallery.jpg<br>✅ _compressed.jpg<br>✅ その他一時ファイル |

**メリット**:
- 一時ファイルクリーンアップで**絶対に本物の画像が削除されない**
- ディレクトリ構成が明確

---

## 🔒 再発防止策

### 1. コーディング規約
```dart
// ✅ 永続ファイル（本物の画像）
final directory = await getApplicationDocumentsDirectory();
final permanentPath = join(directory.path, '${const Uuid().v4()}.jpg');

// ✅ 一時ファイル（処理後に削除）
final tempDirectory = await getTemporaryDirectory();
final tempPath = join(tempDirectory.path, '${const Uuid().v4()}_temp.jpg');
```

### 2. クリーンアップ関数のルール
- **必ず `getTemporaryDirectory()` のみをスキャン**
- `getApplicationDocumentsDirectory()` をスキャンしない

### 3. 一時ファイルの命名規則
- 接尾辞不要（ディレクトリで分離）
- `getTemporaryDirectory()` 内のすべてのファイルは削除OK

---

## 🚨 ユーザーへの影響と対応

### 影響を受けたユーザー
- 一時ファイルクリーンアップを実行したユーザー
- **症状**: 一部の日本酒写真が表示されない

### 復旧方法
❌ **復旧不可能**
- 削除された画像ファイルは復元できません
- バックアップがない場合、データ損失

### 対応策
1. ユーザーに謝罪
2. 削除された日本酒を再撮影してもらう
3. 今後はバックアップ機能を強化

---

## 📋 修正ファイル一覧

1. `lib/services/image_batch_compression_service.dart`
   - `cleanupTempFiles()` を修正
   - スキャン対象を `getTemporaryDirectory()` に変更

2. `lib/screens/camera_screen.dart`
   - ギャラリー用一時ファイルを `getTemporaryDirectory()` に保存

3. `lib/services/image_compression_service.dart`
   - `_generateCompressedPath()` を修正
   - 一時ファイルを `getTemporaryDirectory()` に保存

---

## ✅ テスト計画

### 1. 一時ファイルクリーンアップのテスト
1. 日本酒を3枚撮影
2. 開発者メニュー → 一時ファイルクリーンアップ
3. **確認**: 日本酒の写真がすべて表示されることを確認
4. **確認**: ストレージ使用量が増加していないことを確認

### 2. ストレージ構成の確認
```bash
# Android Debug Bridge (adb) で確認
adb shell run-as com.example.ponshu_room_lite ls /data/user/0/com.example.ponshu_room_lite/app_flutter/
# → 永続ファイルのみ（UUID.jpg）

adb shell run-as com.example.ponshu_room_lite ls /data/user/0/com.example.ponshu_room_lite/cache/
# → 一時ファイルのみ（_gallery.jpg, _compressed.jpg）
```

### 3. 一括圧縮のテスト
1. 既存画像を一括圧縮
2. **確認**: すべての日本酒の写真が表示されることを確認
3. **確認**: ストレージ使用量が削減されたことを確認

---

## 📝 教訓

### ❌ やってはいけないこと
1. 永続ファイルと一時ファイルを同じディレクトリに保存
2. ファイル名の接尾辞でファイルタイプを判定
3. `contains()` で部分一致検索して削除

### ✅ やるべきこと
1. 永続ファイルと一時ファイルを別のディレクトリに保存
2. ディレクトリ構成で責任を分離
3. 削除前に慎重に確認

### 🎯 今後の改善
1. バックアップ機能の強化（自動バックアップ）
2. 削除前の確認ダイアログ（ファイル一覧表示）
3. ユニットテスト追加（ファイル操作）

---

**作成日**: 2026-01-22  
**作成者**: Cursor AI  
**レビュアー**: 必要  
**優先度**: 🔴 Critical
