|
import { db } from './index'; |
|
import type { TrainerScanProgress } from './schema'; |
|
|
|
const TRAINER_SCAN_STORE = 'trainerScanProgress'; |
|
|
|
|
|
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 { |
|
|
|
|
|
const pathParts = imagePath.split('/'); |
|
if (pathParts.length < 3) { |
|
console.warn(`⚠️ Skipping invalid path format: ${imagePath}`); |
|
skippedCount++; |
|
continue; |
|
} |
|
|
|
const trainerName = pathParts[1]?.trim(); |
|
const imageFile = pathParts[2]?.trim(); |
|
|
|
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}`; |
|
|
|
|
|
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 { |
|
|
|
processedCount++; |
|
} |
|
} catch (error) { |
|
console.error(`❌ Failed to process path entry: ${imagePath}`, error); |
|
skippedCount++; |
|
|
|
} |
|
} |
|
|
|
console.log(`✅ Trainer scan initialization complete: ${processedCount} records processed, ${skippedCount} skipped`); |
|
|
|
await tx.complete; |
|
} |
|
|
|
|
|
export async function getNextPendingImage(): Promise<TrainerScanProgress | null> { |
|
const tx = db.transaction([TRAINER_SCAN_STORE], 'readonly'); |
|
const store = tx.objectStore(TRAINER_SCAN_STORE); |
|
|
|
|
|
const pendingRecords: TrainerScanProgress[] = []; |
|
let cursor = await store.openCursor(); |
|
|
|
while (cursor) { |
|
if (cursor.value.status === 'pending') { |
|
pendingRecords.push(cursor.value); |
|
} |
|
cursor = await cursor.continue(); |
|
} |
|
|
|
|
|
return pendingRecords.length > 0 ? pendingRecords[0] : null; |
|
} |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
export async function markImageProcessingStarted(imagePath: string): Promise<void> { |
|
await updateScanProgress(imagePath, { |
|
status: 'processing', |
|
startedAt: new Date() |
|
}); |
|
} |
|
|
|
|
|
export async function markImageProcessingCompleted( |
|
imagePath: string, |
|
picletInstanceId: number |
|
): Promise<void> { |
|
await updateScanProgress(imagePath, { |
|
status: 'completed', |
|
picletInstanceId, |
|
completedAt: new Date() |
|
}); |
|
} |
|
|
|
|
|
export async function markImageProcessingFailed( |
|
imagePath: string, |
|
errorMessage: string |
|
): Promise<void> { |
|
await updateScanProgress(imagePath, { |
|
status: 'failed', |
|
errorMessage, |
|
completedAt: new Date() |
|
}); |
|
} |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
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; |
|
} |