piclets / src /lib /db /trainerScanning.ts
Fraser's picture
fixes
2aa7dab
raw
history blame
6.63 kB
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;
}