refactor(server): bundle worker pipeline into PipelineState
Group the four worker handles (analyze/transcribe x busy/tx) into a new PipelineState struct with a FromRef<AppState> impl. Convert heal_orphans_if_idle from a free function with 6 parameters into a method on PipelineState. The two web handlers (handle_my_cases, handle_case_detail) now take one State<PipelineState> instead of four separate State extractors. The original FromRef impls for AnalyzeBusy/Sender and TranscribeBusy/ Sender remain intact so handlers that need only one of them keep their narrower signature.
This commit is contained in:
@@ -51,6 +51,20 @@ impl Drop for BusyGuard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bundle of the four worker-pipeline handles (analyze + transcribe,
|
||||||
|
/// each with a busy-flag and a job sender). Extracted so handlers that
|
||||||
|
/// need the whole pipeline (self-heal, status probes) can take a single
|
||||||
|
/// `State<PipelineState>` instead of four separate `State(...)` params.
|
||||||
|
/// The individual `FromRef<AppState>` impls below remain valid, so
|
||||||
|
/// handlers that only need one sender keep their narrower signature.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PipelineState {
|
||||||
|
pub analyze_busy: AnalyzeBusy,
|
||||||
|
pub analyze_tx: AnalyzeSender,
|
||||||
|
pub transcribe_busy: TranscribeBusy,
|
||||||
|
pub transcribe_tx: TranscribeSender,
|
||||||
|
}
|
||||||
|
|
||||||
/// Shared application state. Clonable — all fields are cheap to clone
|
/// Shared application state. Clonable — all fields are cheap to clone
|
||||||
/// (`Arc` and `mpsc::Sender`).
|
/// (`Arc` and `mpsc::Sender`).
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -99,6 +113,17 @@ impl FromRef<AppState> for TranscribeBusy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromRef<AppState> for PipelineState {
|
||||||
|
fn from_ref(state: &AppState) -> Self {
|
||||||
|
PipelineState {
|
||||||
|
analyze_busy: state.analyze_busy.clone(),
|
||||||
|
analyze_tx: state.analyze_tx.clone(),
|
||||||
|
transcribe_busy: state.transcribe_busy.clone(),
|
||||||
|
transcribe_tx: state.transcribe_tx.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Test/simple entrypoint: jobs pushed into either channel are dropped
|
/// Test/simple entrypoint: jobs pushed into either channel are dropped
|
||||||
/// because the receivers are not retained. Use [`create_router_with_state`]
|
/// because the receivers are not retained. Use [`create_router_with_state`]
|
||||||
/// from `main.rs` where real workers own the receivers.
|
/// from `main.rs` where real workers own the receivers.
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ use tracing::{info, warn};
|
|||||||
|
|
||||||
use time::{format_description::well_known::Rfc3339, Date, OffsetDateTime};
|
use time::{format_description::well_known::Rfc3339, Date, OffsetDateTime};
|
||||||
|
|
||||||
use crate::analyze::{recovery as analyze_recovery, AnalyzeSender, ANALYSIS_INPUT_FILE, DOCUMENT_FILE};
|
use crate::analyze::{recovery as analyze_recovery, ANALYSIS_INPUT_FILE, DOCUMENT_FILE};
|
||||||
use crate::auth::AuthenticatedWebUser;
|
use crate::auth::AuthenticatedWebUser;
|
||||||
use crate::case_id::{CaseId, CaseIdPath};
|
use crate::case_id::{CaseId, CaseIdPath};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
use crate::routes::web::{scan_recordings, RecordingView};
|
use crate::routes::web::{scan_recordings, RecordingView};
|
||||||
use crate::transcribe::{recovery as transcribe_recovery, TranscribeSender};
|
use crate::transcribe::recovery as transcribe_recovery;
|
||||||
use crate::{AnalyzeBusy, TranscribeBusy};
|
use crate::PipelineState;
|
||||||
|
|
||||||
struct UserCaseView {
|
struct UserCaseView {
|
||||||
case_id: String,
|
case_id: String,
|
||||||
@@ -201,42 +201,27 @@ pub(crate) async fn any_document_exists(case_dir: &Path) -> bool {
|
|||||||
/// Self-heal: if a worker is idle but orphans exist on disk for this user,
|
/// Self-heal: if a worker is idle but orphans exist on disk for this user,
|
||||||
/// re-enqueue them. Page-load is the trigger; no cron, no periodic task.
|
/// re-enqueue them. Page-load is the trigger; no cron, no periodic task.
|
||||||
/// Runs for both pipelines so a page-load on either view cleans both.
|
/// Runs for both pipelines so a page-load on either view cleans both.
|
||||||
async fn heal_orphans_if_idle(
|
impl PipelineState {
|
||||||
user_root: &Path,
|
pub async fn heal_orphans_if_idle(&self, user_root: &Path, slug: &str) {
|
||||||
slug: &str,
|
if !self.analyze_busy.0.load(Ordering::Acquire) {
|
||||||
analyze_busy: &AnalyzeBusy,
|
analyze_recovery::enqueue_pending_for_user(user_root, &self.analyze_tx).await;
|
||||||
analyze_tx: &AnalyzeSender,
|
}
|
||||||
transcribe_busy: &TranscribeBusy,
|
if !self.transcribe_busy.0.load(Ordering::Acquire) {
|
||||||
transcribe_tx: &TranscribeSender,
|
transcribe_recovery::enqueue_pending_for_user(user_root, slug, &self.transcribe_tx)
|
||||||
) {
|
.await;
|
||||||
if !analyze_busy.0.load(Ordering::Acquire) {
|
}
|
||||||
analyze_recovery::enqueue_pending_for_user(user_root, analyze_tx).await;
|
|
||||||
}
|
|
||||||
if !transcribe_busy.0.load(Ordering::Acquire) {
|
|
||||||
transcribe_recovery::enqueue_pending_for_user(user_root, slug, transcribe_tx).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_my_cases(
|
pub async fn handle_my_cases(
|
||||||
user: AuthenticatedWebUser,
|
user: AuthenticatedWebUser,
|
||||||
State(config): State<Arc<Config>>,
|
State(config): State<Arc<Config>>,
|
||||||
State(analyze_busy): State<AnalyzeBusy>,
|
State(pipeline): State<PipelineState>,
|
||||||
State(analyze_tx): State<AnalyzeSender>,
|
|
||||||
State(transcribe_busy): State<TranscribeBusy>,
|
|
||||||
State(transcribe_tx): State<TranscribeSender>,
|
|
||||||
) -> Result<Html<String>, AppError> {
|
) -> Result<Html<String>, AppError> {
|
||||||
let user_root = config.data_path.join(&user.slug);
|
let user_root = config.data_path.join(&user.slug);
|
||||||
heal_orphans_if_idle(
|
pipeline.heal_orphans_if_idle(&user_root, &user.slug).await;
|
||||||
&user_root,
|
|
||||||
&user.slug,
|
|
||||||
&analyze_busy,
|
|
||||||
&analyze_tx,
|
|
||||||
&transcribe_busy,
|
|
||||||
&transcribe_tx,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let busy = analyze_busy.0.load(Ordering::Acquire);
|
let busy = pipeline.analyze_busy.0.load(Ordering::Acquire);
|
||||||
let cases = scan_user_cases(&config, &user.slug, busy).await;
|
let cases = scan_user_cases(&config, &user.slug, busy).await;
|
||||||
let total = cases.len();
|
let total = cases.len();
|
||||||
let groups = group_by_utc_date(cases);
|
let groups = group_by_utc_date(cases);
|
||||||
@@ -260,23 +245,12 @@ pub async fn handle_my_cases(
|
|||||||
pub async fn handle_case_detail(
|
pub async fn handle_case_detail(
|
||||||
user: AuthenticatedWebUser,
|
user: AuthenticatedWebUser,
|
||||||
State(config): State<Arc<Config>>,
|
State(config): State<Arc<Config>>,
|
||||||
State(analyze_busy): State<AnalyzeBusy>,
|
State(pipeline): State<PipelineState>,
|
||||||
State(analyze_tx): State<AnalyzeSender>,
|
|
||||||
State(transcribe_busy): State<TranscribeBusy>,
|
|
||||||
State(transcribe_tx): State<TranscribeSender>,
|
|
||||||
CaseIdPath(case_id): CaseIdPath,
|
CaseIdPath(case_id): CaseIdPath,
|
||||||
) -> Result<Html<String>, AppError> {
|
) -> Result<Html<String>, AppError> {
|
||||||
// IDOR guard: case_dir must live under the session's user_slug.
|
// IDOR guard: case_dir must live under the session's user_slug.
|
||||||
let user_root = config.data_path.join(&user.slug);
|
let user_root = config.data_path.join(&user.slug);
|
||||||
heal_orphans_if_idle(
|
pipeline.heal_orphans_if_idle(&user_root, &user.slug).await;
|
||||||
&user_root,
|
|
||||||
&user.slug,
|
|
||||||
&analyze_busy,
|
|
||||||
&analyze_tx,
|
|
||||||
&transcribe_busy,
|
|
||||||
&transcribe_tx,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let case_dir = locate_case_or_404(
|
let case_dir = locate_case_or_404(
|
||||||
&user_root,
|
&user_root,
|
||||||
@@ -295,8 +269,8 @@ pub async fn handle_case_detail(
|
|||||||
.map(|s| s.trim().to_string())
|
.map(|s| s.trim().to_string())
|
||||||
.filter(|s| !s.is_empty());
|
.filter(|s| !s.is_empty());
|
||||||
|
|
||||||
let a_busy = analyze_busy.0.load(Ordering::Acquire);
|
let a_busy = pipeline.analyze_busy.0.load(Ordering::Acquire);
|
||||||
let t_busy = transcribe_busy.0.load(Ordering::Acquire);
|
let t_busy = pipeline.transcribe_busy.0.load(Ordering::Acquire);
|
||||||
let flags = compute_flags(&case_dir, &recordings, config.llm_configured(), a_busy).await;
|
let flags = compute_flags(&case_dir, &recordings, config.llm_configured(), a_busy).await;
|
||||||
let case_id_short = case_id_str.chars().take(8).collect();
|
let case_id_short = case_id_str.chars().take(8).collect();
|
||||||
let is_admin = user.is_admin();
|
let is_admin = user.is_admin();
|
||||||
|
|||||||
Reference in New Issue
Block a user