Refactor analyze recovery and worker busy status

The analyze recovery scan has been refactored to iterate through user
directories and enqueue pending analysis jobs more efficiently. The
`WorkerBusy` status has been introduced as an `Arc<AtomicBool>` to track
whether the analyze worker is currently processing a job.

This `WorkerBusy` flag is used in the `analyze::worker::run` function
and managed by a `BusyGuard` RAII struct, ensuring the flag is correctly
set and unset even in case of panics.

The `handle_my_cases` and `handle_case_detail` handlers in `user_web.rs`
now utilize the `WorkerBusy` flag to accurately display the "analyzing"
status and to trigger a self-heal of orphaned analysis inputs when the
worker is idle.

Additionally, the `WorkerBusy` type is now exported from
`server/src/lib.rs` to be accessible by other modules. The test cases
have been updated to include the `WorkerBusy` parameter when spawning
the analyze worker.
This commit is contained in:
2026-04-16 00:19:08 +02:00
parent 4e145dd167
commit 9072d7a833
7 changed files with 191 additions and 84 deletions
+11 -8
View File
@@ -18,10 +18,17 @@ section h2 { font-size: 1.1em; color: #555; border-bottom: 1px solid #ddd; paddi
.case-row .check { align-self: start; padding-top: 0.25em; }
.case-row .body { min-width: 0; }
.case-row .body a { color: inherit; text-decoration: none; }
.case-row .body a:hover h3 { text-decoration: underline; }
.case-row .body a:hover .line1 { text-decoration: underline; }
.case-row .body h3 { margin: 0 0 0.2em 0; font-size: 1em; font-weight: 600; }
.case-row .body small { color: #666; font-family: monospace; font-size: 0.85em; }
.case-row .oneliner { color: #333; margin: 0.2em 0 0; }
.case-row .line1 { font-size: 1em; margin: 0; }
.case-row .line1 .time { font-family: monospace; font-weight: bold; color: #444; }
.case-row .line2 { margin: 0.2em 0 0; color: #555; font-size: 0.95em; display: flex; align-items: center; gap: 0.5em; }
.case-row .uuid { color: #999; font-family: monospace; font-size: 0.7em; margin-top: 0.2em; }
.status-badge { display: inline-block; padding: 0.05em 0.5em; border-radius: 999px; font-size: 0.75em; font-weight: bold; color: white; }
.status-badge.open { background: #e24a4a; }
.status-badge.done { background: #7ed321; }
.case-row .actions { display: flex; gap: 0.4em; }
.case-row .actions button { font-size: 0.85em; padding: 0.35em 0.8em; }
@@ -60,13 +67,9 @@ section h2 { font-size: 1.1em; color: #555; border-bottom: 1px solid #ddd; paddi
<div class="check"><input type="checkbox" name="case_id" value="{{ case.case_id }}"></div>
<div class="body">
{% if case.has_document %}<a href="/web/cases/{{ case.case_id }}/document">{% else %}<a href="/web/cases/{{ case.case_id }}">{% endif %}
<h3>{{ case.case_id_short }} — {{ case.recordings_count }} Aufnahme(n){% if case.has_document %}<span class="label done-doc">ausgewertet</span>{% else if case.analyzing %}<span class="label analyzing">wird analysiert</span>{% endif %}</h3>
{% match case.oneliner %}
{% when Some with (t) %}
<div class="oneliner">{{ t }}</div>
{% when None %}
{% endmatch %}
<small>{{ case.most_recent }}</small>
<div class="line1"><span class="time">{{ case.time_hms }}</span>{% match case.oneliner %}{% when Some with (t) %} — {{ t }}{% when None %}{% endmatch %}</div>
<div class="line2">{{ case.recordings_count }} Aufnahmen{% if case.has_document %} — <span class="status-badge done">ausgewertet</span>{% else %} — <span class="status-badge open">offen</span>{% endif %}{% if case.analyzing %} <span class="label analyzing">wird analysiert</span>{% endif %}</div>
{% if is_admin %}<div class="uuid">{{ case.case_id }}</div>{% endif %}
</a>
</div>
<div class="actions">