File size: 6,634 Bytes
5435413
 
 
 
 
 
 
 
 
 
2aa7dab
 
 
5435413
2aa7dab
 
 
 
 
 
 
 
 
 
 
 
5435413
2aa7dab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5435413
 
 
2aa7dab
 
5435413
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import { db } from './index';
import type { TrainerScanProgress } from './schema';

const TRAINER_SCAN_STORE = 'trainerScanProgress';

// Initialize trainer scan progress records from paths
export async function initializeTrainerScanProgress(imagePaths: string[]): Promise<void> {
  const tx = db.transaction([TRAINER_SCAN_STORE], 'readwrite');
  const store = tx.objectStore(TRAINER_SCAN_STORE);
  
  let processedCount = 0;
  let skippedCount = 0;
  
  for (const imagePath of imagePaths) {
    try {
      // Extract trainer name and image index from path
      // Format: "trainer_images/001_Willow_Snap/image_001.jpg"
      const pathParts = imagePath.split('/');
      if (pathParts.length < 3) {
        console.warn(`⚠️ Skipping invalid path format: ${imagePath}`);
        skippedCount++;
        continue;
      }
      
      const trainerName = pathParts[1]?.trim(); // "001_Willow_Snap"
      const imageFile = pathParts[2]?.trim(); // "image_001.jpg"
      
      if (!trainerName || !imageFile || typeof trainerName !== 'string' || typeof imageFile !== 'string') {
        console.warn(`⚠️ Skipping path with missing or invalid parts: ${imagePath}`, { trainerName, imageFile });
        skippedCount++;
        continue;
      }
      
      const imageMatch = imageFile.match(/image_(\d+)\.jpg/);
      const imageIndex = imageMatch ? parseInt(imageMatch[1]) : 1;
      
      const remoteUrl = `https://huggingface.co/datasets/Fraser/piclets/resolve/main/${imagePath}`;
      
      // Check if this path already exists to avoid duplicates
      const existing = await store.get(imagePath);
      if (!existing) {
        const progressRecord: Omit<TrainerScanProgress, 'id'> = {
          imagePath,
          trainerName,
          imageIndex,
          status: 'pending',
          remoteUrl
        };
        
        await store.add(progressRecord);
        processedCount++;
      } else {
        // Record exists, don't count as processed but note it
        processedCount++;
      }
    } catch (error) {
      console.error(`❌ Failed to process path entry: ${imagePath}`, error);
      skippedCount++;
      // Continue processing other paths despite this failure
    }
  }
  
  console.log(`✅ Trainer scan initialization complete: ${processedCount} records processed, ${skippedCount} skipped`);
  
  await tx.complete;
}

// Get next pending image to process
export async function getNextPendingImage(): Promise<TrainerScanProgress | null> {
  const tx = db.transaction([TRAINER_SCAN_STORE], 'readonly');
  const store = tx.objectStore(TRAINER_SCAN_STORE);
  
  // Get all pending records
  const pendingRecords: TrainerScanProgress[] = [];
  let cursor = await store.openCursor();
  
  while (cursor) {
    if (cursor.value.status === 'pending') {
      pendingRecords.push(cursor.value);
    }
    cursor = await cursor.continue();
  }
  
  // Return the first pending record (if any)
  return pendingRecords.length > 0 ? pendingRecords[0] : null;
}

// Update scan progress status
export async function updateScanProgress(
  imagePath: string, 
  updates: Partial<Omit<TrainerScanProgress, 'id' | 'imagePath'>>
): Promise<void> {
  const tx = db.transaction([TRAINER_SCAN_STORE], 'readwrite');
  const store = tx.objectStore(TRAINER_SCAN_STORE);
  
  const existing = await store.get(imagePath);
  if (existing) {
    const updated = { ...existing, ...updates };
    await store.put(updated);
  }
  
  await tx.complete;
}

// Mark image processing as started
export async function markImageProcessingStarted(imagePath: string): Promise<void> {
  await updateScanProgress(imagePath, {
    status: 'processing',
    startedAt: new Date()
  });
}

// Mark image processing as completed successfully
export async function markImageProcessingCompleted(
  imagePath: string, 
  picletInstanceId: number
): Promise<void> {
  await updateScanProgress(imagePath, {
    status: 'completed',
    picletInstanceId,
    completedAt: new Date()
  });
}

// Mark image processing as failed
export async function markImageProcessingFailed(
  imagePath: string, 
  errorMessage: string
): Promise<void> {
  await updateScanProgress(imagePath, {
    status: 'failed',
    errorMessage,
    completedAt: new Date()
  });
}

// Get scanning statistics
export async function getScanningStats(): Promise<{
  total: number;
  pending: number;
  processing: number;
  completed: number;
  failed: number;
}> {
  const tx = db.transaction([TRAINER_SCAN_STORE], 'readonly');
  const store = tx.objectStore(TRAINER_SCAN_STORE);
  
  const stats = {
    total: 0,
    pending: 0,
    processing: 0,
    completed: 0,
    failed: 0
  };
  
  let cursor = await store.openCursor();
  while (cursor) {
    stats.total++;
    const status = cursor.value.status;
    stats[status]++;
    cursor = await cursor.continue();
  }
  
  return stats;
}

// Get all completed scans for a specific trainer
export async function getCompletedScansForTrainer(trainerName: string): Promise<TrainerScanProgress[]> {
  const tx = db.transaction([TRAINER_SCAN_STORE], 'readonly');
  const store = tx.objectStore(TRAINER_SCAN_STORE);
  
  const results: TrainerScanProgress[] = [];
  let cursor = await store.openCursor();
  
  while (cursor) {
    const record = cursor.value;
    if (record.trainerName === trainerName && record.status === 'completed') {
      results.push(record);
    }
    cursor = await cursor.continue();
  }
  
  return results;
}

// Reset all failed scans back to pending (for retry)
export async function resetFailedScans(): Promise<number> {
  const tx = db.transaction([TRAINER_SCAN_STORE], 'readwrite');
  const store = tx.objectStore(TRAINER_SCAN_STORE);
  
  let resetCount = 0;
  let cursor = await store.openCursor();
  
  while (cursor) {
    if (cursor.value.status === 'failed') {
      const updated = {
        ...cursor.value,
        status: 'pending' as const,
        errorMessage: undefined,
        startedAt: undefined,
        completedAt: undefined
      };
      await cursor.update(updated);
      resetCount++;
    }
    cursor = await cursor.continue();
  }
  
  await tx.complete;
  return resetCount;
}

// Get current processing status (for resuming interrupted sessions)
export async function getCurrentProcessingImage(): Promise<TrainerScanProgress | null> {
  const tx = db.transaction([TRAINER_SCAN_STORE], 'readonly');
  const store = tx.objectStore(TRAINER_SCAN_STORE);
  
  let cursor = await store.openCursor();
  while (cursor) {
    if (cursor.value.status === 'processing') {
      return cursor.value;
    }
    cursor = await cursor.continue();
  }
  
  return null;
}