Allow background processing (can close web page and reopen)
This commit is contained in:
parent
d52e334ba7
commit
eadd1b5d7b
6 changed files with 184 additions and 78 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import ConnectionStatus from './lib/components/ConnectionStatus.svelte';
|
import ConnectionStatus from './lib/components/ConnectionStatus.svelte';
|
||||||
import OutputManager from './lib/components/OutputManager.svelte';
|
import OutputManager from './lib/components/OutputManager.svelte';
|
||||||
import PeopleSelector from './lib/components/PeopleSelector.svelte';
|
import PeopleSelector from './lib/components/PeopleSelector.svelte';
|
||||||
|
|
@ -11,6 +12,7 @@
|
||||||
let selectedPerson = $state(null);
|
let selectedPerson = $state(null);
|
||||||
let jobStatus = $state('idle');
|
let jobStatus = $state('idle');
|
||||||
let progress = $state({ completed: 0, total: 0, message: '' });
|
let progress = $state({ completed: 0, total: 0, message: '' });
|
||||||
|
let pollInterval = $state(null);
|
||||||
|
|
||||||
let isJobRunning = $derived(
|
let isJobRunning = $derived(
|
||||||
jobStatus === 'running' || jobStatus === 'compiling_video' || jobStatus === 'cancelling'
|
jobStatus === 'running' || jobStatus === 'compiling_video' || jobStatus === 'cancelling'
|
||||||
|
|
@ -18,6 +20,10 @@
|
||||||
|
|
||||||
function handleConnectionChange(data) {
|
function handleConnectionChange(data) {
|
||||||
connectionOk = data.connected;
|
connectionOk = data.connected;
|
||||||
|
// Check for running job when connection is established
|
||||||
|
if (data.connected) {
|
||||||
|
checkAndPollProgress();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePersonSelect(person) {
|
function handlePersonSelect(person) {
|
||||||
|
|
@ -27,7 +33,66 @@
|
||||||
function handleJobUpdate(data) {
|
function handleJobUpdate(data) {
|
||||||
jobStatus = data.status;
|
jobStatus = data.status;
|
||||||
progress = data;
|
progress = data;
|
||||||
|
|
||||||
|
// Start polling if job just started
|
||||||
|
if (data.status === 'running' || data.status === 'compiling_video') {
|
||||||
|
startPolling();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkAndPollProgress() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/progress');
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
jobStatus = data.status;
|
||||||
|
progress = data;
|
||||||
|
|
||||||
|
// If a job is running, start polling
|
||||||
|
if (data.status === 'running' || data.status === 'compiling_video' || data.status === 'cancelling') {
|
||||||
|
startPolling();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to check progress:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollProgress() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/progress');
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
jobStatus = data.status;
|
||||||
|
progress = data;
|
||||||
|
|
||||||
|
// Stop polling when job completes
|
||||||
|
if (data.status === 'completed' || data.status === 'cancelled' || data.status === 'error' || data.status === 'idle') {
|
||||||
|
stopPolling();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Poll failed:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startPolling() {
|
||||||
|
if (pollInterval) return; // Already polling
|
||||||
|
pollInterval = setInterval(pollProgress, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopPolling() {
|
||||||
|
if (pollInterval) {
|
||||||
|
clearInterval(pollInterval);
|
||||||
|
pollInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// Initial check will happen when connection is established
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
stopPolling();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|
@ -47,7 +112,7 @@
|
||||||
disabled={isJobRunning}
|
disabled={isJobRunning}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if selectedPerson}
|
{#if selectedPerson && !isJobRunning}
|
||||||
<ProcessingControls
|
<ProcessingControls
|
||||||
personId={selectedPerson.id}
|
personId={selectedPerson.id}
|
||||||
personName={selectedPerson.name}
|
personName={selectedPerson.name}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from 'svelte';
|
|
||||||
|
|
||||||
let { personId, personName, jobStatus, onupdate } = $props();
|
let { personId, personName, jobStatus, onupdate } = $props();
|
||||||
|
|
||||||
let dateFrom = $state('');
|
let dateFrom = $state('');
|
||||||
let dateTo = $state('');
|
let dateTo = $state('');
|
||||||
let pollInterval = $state(null);
|
|
||||||
let assetCount = $state(null);
|
let assetCount = $state(null);
|
||||||
let loadingCount = $state(false);
|
let loadingCount = $state(false);
|
||||||
|
|
||||||
|
|
@ -51,7 +48,13 @@
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok && data.success) {
|
if (res.ok && data.success) {
|
||||||
startPolling();
|
// Notify parent to start polling
|
||||||
|
onupdate?.({
|
||||||
|
status: 'running',
|
||||||
|
completed: 0,
|
||||||
|
total: 0,
|
||||||
|
message: 'Starting...',
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
onupdate?.({
|
onupdate?.({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
|
|
@ -77,43 +80,6 @@
|
||||||
console.error('Cancel failed:', e);
|
console.error('Cancel failed:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pollProgress() {
|
|
||||||
try {
|
|
||||||
const res = await fetch('/api/progress');
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
onupdate?.(data);
|
|
||||||
|
|
||||||
if (data.status === 'completed' || data.status === 'cancelled' || data.status === 'error') {
|
|
||||||
stopPolling();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Poll failed:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startPolling() {
|
|
||||||
stopPolling();
|
|
||||||
pollProgress();
|
|
||||||
pollInterval = setInterval(pollProgress, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopPolling() {
|
|
||||||
if (pollInterval) {
|
|
||||||
clearInterval(pollInterval);
|
|
||||||
pollInterval = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
// Check if there's already a running job
|
|
||||||
pollProgress();
|
|
||||||
});
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
stopPolling();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="processing-controls">
|
<div class="processing-controls">
|
||||||
|
|
@ -147,15 +113,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
{#if isRunning}
|
<button class="start-btn" onclick={startProcessing}>
|
||||||
<button class="cancel-btn" onclick={cancelProcessing}>
|
Start Processing
|
||||||
Cancel
|
</button>
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<button class="start-btn" onclick={startProcessing}>
|
|
||||||
Start Processing
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -271,13 +231,4 @@
|
||||||
.start-btn:hover {
|
.start-btn:hover {
|
||||||
background: #4338ca;
|
background: #4338ca;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-btn {
|
|
||||||
background: #dc2626;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn:hover {
|
|
||||||
background: #b91c1c;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@
|
||||||
error: 'error',
|
error: 'error',
|
||||||
}[jobStatus] || '');
|
}[jobStatus] || '');
|
||||||
|
|
||||||
|
let canCancel = $derived(jobStatus === 'running' || jobStatus === 'compiling_video');
|
||||||
|
|
||||||
|
// Person being processed
|
||||||
|
let personDisplay = $derived(progress.person_name || progress.person_id || null);
|
||||||
|
|
||||||
// Skip statistics from the backend
|
// Skip statistics from the backend
|
||||||
let skipStats = $derived(progress.skip_stats || {
|
let skipStats = $derived(progress.skip_stats || {
|
||||||
face_too_small: 0,
|
face_too_small: 0,
|
||||||
|
|
@ -39,8 +44,6 @@
|
||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let hasSkips = $derived(skipStats.total > 0);
|
|
||||||
|
|
||||||
// Calculate kept (successful) count: completed - skipped
|
// Calculate kept (successful) count: completed - skipped
|
||||||
let keptCount = $derived(Math.max(0, progress.completed - skipStats.total));
|
let keptCount = $derived(Math.max(0, progress.completed - skipStats.total));
|
||||||
|
|
||||||
|
|
@ -58,14 +61,32 @@
|
||||||
{ label: 'Crop failed', count: skipStats.crop_failed },
|
{ label: 'Crop failed', count: skipStats.crop_failed },
|
||||||
].filter(r => r.count > 0)
|
].filter(r => r.count > 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
async function cancelProcessing() {
|
||||||
|
try {
|
||||||
|
await fetch('/api/cancel', { method: 'POST' });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Cancel failed:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="progress-display">
|
<div class="progress-display">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<span class="status {statusClass}">{statusLabel}</span>
|
<div class="header-left">
|
||||||
{#if progress.total > 0}
|
<span class="status {statusClass}">{statusLabel}</span>
|
||||||
<span class="count">{progress.completed} / {progress.total}</span>
|
{#if personDisplay}
|
||||||
{/if}
|
<span class="person-name">{personDisplay}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
{#if progress.total > 0}
|
||||||
|
<span class="count">{progress.completed} / {progress.total}</span>
|
||||||
|
{/if}
|
||||||
|
{#if canCancel}
|
||||||
|
<button class="cancel-btn" onclick={cancelProcessing}>Cancel</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if jobStatus === 'running' || jobStatus === 'compiling_video' || jobStatus === 'cancelling'}
|
{#if jobStatus === 'running' || jobStatus === 'compiling_video' || jobStatus === 'cancelling'}
|
||||||
|
|
@ -114,6 +135,18 @@
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.person-name {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|
@ -142,11 +175,33 @@
|
||||||
color: #f87171;
|
color: #f87171;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.count {
|
.count {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
background: #dc2626;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background: #333;
|
background: #333;
|
||||||
|
|
|
||||||
|
|
@ -97,18 +97,20 @@ pub async fn run_job(state: AppState, params: JobParams, cancel_token: Cancellat
|
||||||
// Clear the cancellation token when done
|
// Clear the cancellation token when done
|
||||||
state.clear_cancel_token().await;
|
state.clear_cancel_token().await;
|
||||||
|
|
||||||
// Get final skip stats from progress
|
// Get final state from progress (skip stats and person info)
|
||||||
let final_skip_stats = state.progress.read().await.skip_stats.clone();
|
let final_progress = state.progress.read().await.clone();
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(output_path) => {
|
Ok(output_path) => {
|
||||||
state
|
state
|
||||||
.update_progress(Progress {
|
.update_progress(Progress {
|
||||||
status: JobStatus::Completed,
|
status: JobStatus::Completed,
|
||||||
completed: 0,
|
completed: final_progress.completed,
|
||||||
total: 0,
|
total: final_progress.total,
|
||||||
message: Some(format!("Video saved to: {}", output_path.display())),
|
message: Some(format!("Video saved to: {}", output_path.display())),
|
||||||
skip_stats: final_skip_stats,
|
skip_stats: final_progress.skip_stats,
|
||||||
|
person_id: final_progress.person_id,
|
||||||
|
person_name: final_progress.person_name,
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
@ -116,10 +118,12 @@ pub async fn run_job(state: AppState, params: JobParams, cancel_token: Cancellat
|
||||||
state
|
state
|
||||||
.update_progress(Progress {
|
.update_progress(Progress {
|
||||||
status: JobStatus::Cancelled,
|
status: JobStatus::Cancelled,
|
||||||
completed: 0,
|
completed: final_progress.completed,
|
||||||
total: 0,
|
total: final_progress.total,
|
||||||
message: Some("Job cancelled by user".to_string()),
|
message: Some("Job cancelled by user".to_string()),
|
||||||
skip_stats: final_skip_stats,
|
skip_stats: final_progress.skip_stats,
|
||||||
|
person_id: final_progress.person_id,
|
||||||
|
person_name: final_progress.person_name,
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
@ -128,10 +132,12 @@ pub async fn run_job(state: AppState, params: JobParams, cancel_token: Cancellat
|
||||||
state
|
state
|
||||||
.update_progress(Progress {
|
.update_progress(Progress {
|
||||||
status: JobStatus::Error(e.to_string()),
|
status: JobStatus::Error(e.to_string()),
|
||||||
completed: 0,
|
completed: final_progress.completed,
|
||||||
total: 0,
|
total: final_progress.total,
|
||||||
message: Some(e.to_string()),
|
message: Some(e.to_string()),
|
||||||
skip_stats: final_skip_stats,
|
skip_stats: final_progress.skip_stats,
|
||||||
|
person_id: final_progress.person_id,
|
||||||
|
person_name: final_progress.person_name,
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
@ -193,6 +199,8 @@ async fn run_job_inner(
|
||||||
total: 0,
|
total: 0,
|
||||||
message: Some("Fetching assets from Immich...".to_string()),
|
message: Some("Fetching assets from Immich...".to_string()),
|
||||||
skip_stats: SkipStats::default(),
|
skip_stats: SkipStats::default(),
|
||||||
|
person_id: Some(params.person_id.clone()),
|
||||||
|
person_name: params.person_name.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -244,6 +252,8 @@ async fn run_job_inner(
|
||||||
total,
|
total,
|
||||||
message: Some(format!("Processing {} images...", total)),
|
message: Some(format!("Processing {} images...", total)),
|
||||||
skip_stats: SkipStats::default(),
|
skip_stats: SkipStats::default(),
|
||||||
|
person_id: Some(params.person_id.clone()),
|
||||||
|
person_name: params.person_name.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -267,6 +277,10 @@ async fn run_job_inner(
|
||||||
|
|
||||||
let mut handles = Vec::with_capacity(assets_with_faces.len());
|
let mut handles = Vec::with_capacity(assets_with_faces.len());
|
||||||
|
|
||||||
|
// Clone person info for use in spawned tasks
|
||||||
|
let task_person_id = params.person_id.clone();
|
||||||
|
let task_person_name = params.person_name.clone();
|
||||||
|
|
||||||
for (asset, face_data) in assets_with_faces {
|
for (asset, face_data) in assets_with_faces {
|
||||||
// Check for cancellation before spawning more tasks
|
// Check for cancellation before spawning more tasks
|
||||||
if cancel_token.is_cancelled() {
|
if cancel_token.is_cancelled() {
|
||||||
|
|
@ -295,6 +309,10 @@ async fn run_job_inner(
|
||||||
let skip_decode_failed = skip_decode_failed.clone();
|
let skip_decode_failed = skip_decode_failed.clone();
|
||||||
let skip_crop_failed = skip_crop_failed.clone();
|
let skip_crop_failed = skip_crop_failed.clone();
|
||||||
|
|
||||||
|
// Clone person info for this task
|
||||||
|
let person_id = task_person_id.clone();
|
||||||
|
let person_name = task_person_name.clone();
|
||||||
|
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
// Check cancellation at start of task
|
// Check cancellation at start of task
|
||||||
if task_cancel_token.is_cancelled() {
|
if task_cancel_token.is_cancelled() {
|
||||||
|
|
@ -353,6 +371,8 @@ async fn run_job_inner(
|
||||||
total,
|
total,
|
||||||
message: Some(format!("Processing images... ({}/{})", done, total)),
|
message: Some(format!("Processing images... ({}/{})", done, total)),
|
||||||
skip_stats: current_skip_stats,
|
skip_stats: current_skip_stats,
|
||||||
|
person_id: Some(person_id.clone()),
|
||||||
|
person_name: person_name.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
@ -419,6 +439,8 @@ async fn run_job_inner(
|
||||||
total,
|
total,
|
||||||
message: Some("Processing complete, preparing video...".to_string()),
|
message: Some("Processing complete, preparing video...".to_string()),
|
||||||
skip_stats: skip_stats.clone(),
|
skip_stats: skip_stats.clone(),
|
||||||
|
person_id: Some(params.person_id.clone()),
|
||||||
|
person_name: params.person_name.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -442,6 +464,8 @@ async fn run_job_inner(
|
||||||
total: successful as u32,
|
total: successful as u32,
|
||||||
message: Some("Compiling video...".to_string()),
|
message: Some("Compiling video...".to_string()),
|
||||||
skip_stats: skip_stats.clone(),
|
skip_stats: skip_stats.clone(),
|
||||||
|
person_id: Some(params.person_id.clone()),
|
||||||
|
person_name: params.person_name.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,8 @@ struct ProgressResponse {
|
||||||
total: u32,
|
total: u32,
|
||||||
message: Option<String>,
|
message: Option<String>,
|
||||||
skip_stats: SkipStatsResponse,
|
skip_stats: SkipStatsResponse,
|
||||||
|
person_id: Option<String>,
|
||||||
|
person_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_progress(State(state): State<AppState>) -> Json<ProgressResponse> {
|
async fn get_progress(State(state): State<AppState>) -> Json<ProgressResponse> {
|
||||||
|
|
@ -292,6 +294,8 @@ async fn get_progress(State(state): State<AppState>) -> Json<ProgressResponse> {
|
||||||
crop_failed: skip_stats.crop_failed,
|
crop_failed: skip_stats.crop_failed,
|
||||||
total: skip_stats.total(),
|
total: skip_stats.total(),
|
||||||
},
|
},
|
||||||
|
person_id: progress.person_id.clone(),
|
||||||
|
person_name: progress.person_name.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -322,7 +326,7 @@ async fn start_processing(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset progress
|
// Reset progress with person info
|
||||||
state
|
state
|
||||||
.update_progress(Progress {
|
.update_progress(Progress {
|
||||||
status: JobStatus::Running,
|
status: JobStatus::Running,
|
||||||
|
|
@ -330,6 +334,8 @@ async fn start_processing(
|
||||||
total: 0,
|
total: 0,
|
||||||
message: Some("Starting...".to_string()),
|
message: Some("Starting...".to_string()),
|
||||||
skip_stats: SkipStats::default(),
|
skip_stats: SkipStats::default(),
|
||||||
|
person_id: Some(request.person_id.clone()),
|
||||||
|
person_name: request.person_name.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,9 @@ pub struct Progress {
|
||||||
pub total: u32,
|
pub total: u32,
|
||||||
pub message: Option<String>,
|
pub message: Option<String>,
|
||||||
pub skip_stats: SkipStats,
|
pub skip_stats: SkipStats,
|
||||||
|
/// The person being processed (for display when resuming)
|
||||||
|
pub person_id: Option<String>,
|
||||||
|
pub person_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Progress {
|
impl Default for Progress {
|
||||||
|
|
@ -64,6 +67,8 @@ impl Default for Progress {
|
||||||
total: 0,
|
total: 0,
|
||||||
message: None,
|
message: None,
|
||||||
skip_stats: SkipStats::default(),
|
skip_stats: SkipStats::default(),
|
||||||
|
person_id: None,
|
||||||
|
person_name: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue