4 Commits
Author SHA1 Message Date
ba721779c3 fix(downloads): abort blocked model fetches on cancel (#888)
* fix(downloads): abort blocked model fetches on cancel

requests.get() has no way to abort while it is still resolving,
connecting, or waiting for response headers — which is what an
unreachable download server looks like. The cancel flag was only
checked between response chunks, so Cancel sat until the request
timed out. The blocking open now runs on a helper thread the caller
polls every 200 ms; on cancel it raises at once while the abandoned
attempt stays bounded by the existing timeouts.

The settings dialog's download workers also reported "Model ready to
use" whenever the apply returned True, but reconfigure() can no-op
when the selection matches live state — success without a download.
They now pass force_reinit so a reported success means the engine
actually initialized the downloaded model.

Fixes #679

* fix(downloads): bound cancel openers, close late responses

- Track blocked request opener threads and give each attempt its own stop
  event so a cancel-and-retry cannot stack unaccounted requests and a
  response landing after cancel is closed instead of leaked
- Catch requests.exceptions.RequestException in the opener instead of a
  bare Exception
- Close the streaming response on the mid-stream cancel path
- Make the blocked-open test helper cancellable and reap tracked openers;
  check each download worker call site for force_reinit separately; add
  missing return annotations on new test helpers and methods

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
2026-09-29 22:52:12 -07:00
Jatin K MalikandGitHub 40bc31bebf fix(settings): stop freezing when applying an already-downloaded model (#790)
* fix(settings): apply already-downloaded models off the GTK main loop

Changing Model Size, Specialization, or Language froze Settings when the
model was already on disk, because reconfigure ran in the changed handler.
Move that apply onto a worker thread (same shape as the download path) and
keep the apply-guard held until GLib.idle_add clears it so a second pick
cannot start a parallel load.

Fixes #789.

* fix(settings): guard Test/apply while downloaded-model worker runs

Block apply_settings and Test Dictation while _applying_settings is held
so a concurrent Test click cannot race the shared engine. Annotate the
new worker/helpers per AGENTS.md and cover the no-op/block behavior.

* fix(settings): resync pickers when a second pick lands mid-apply

While an already-downloaded model worker was running, Model Size /
Specialization / Language could still move. The apply guard blocked
saving the new pick, then finish only cleared the flag, so the UI
could show B with dictation still on A. On finish, if the selection
no longer matches the saved config, put the pickers back. Idle
resyncs from workers now skip a destroyed dialog the same way update
check does.

* fix(settings): always resync pickers after model apply finishes

A second Model Size pick on whisper.cpp could move the size combo
while the apply guard skipped rebuilding specialization, so selected
settings still looked like the saved variant and finish skipped the
resync. Always put the pickers back on the saved config when the
worker ends, and restore language from that config too.

* test(settings): prove mid-apply picker handlers round-trip on finish

Call the real size, specialization, and language handlers while the
apply guard is held, and run finish through the real resync path so
whisper.cpp size and language cannot silently stick on the second pick.
2026-09-07 14:11:54 -07:00
Jatin K MalikandGitHub d8ff7052cd fix(settings): stop Test Dictation calling a missing model "no speech" (#702)
* fix(settings): stop Test Dictation calling a missing model "no speech"

start_recognition now returns whether listening actually began. Test
Dictation checks that before starting its timer, so a missing or
auto-paused model gets a real error instead of "No speech detected".

* fix(settings): reconfigure Test Dictation against the live engine

UI matching config.json is not enough when speech_engine is still on
another engine or model size. Compare those too before the listen timer.

* fix(tests): keep settings_dialog lookups on one module

_load_settings_dialog() restored sys.modules but left
vocalinux.ui.settings_dialog pointing at the private reload.
Python 3.9/3.10 mock follows that attribute, so
test_whisper_list_and_delete patched a different copy than it
imported and the expanduser stub hid the real cache.

Point the package attribute back at the restored module, and
patch the imported module object in the deletion test.
2026-08-18 17:59:57 -07:00
Kacper PaczosandGitHub 5a978d9875 fix(settings): report failed model downloads instead of claiming success (#690)
Both modal download paths ran _apply_settings_internal() on a worker
thread and then unconditionally marked the progress dialog complete.
Since _apply_settings_internal() swallows every exception internally,
a download that failed or was cancelled still ended with "Complete!",
the dedicated except blocks below (the "Download cancelled" and
Whisper-not-installed branches) were unreachable, and the swallowed
path built and ran a Gtk.MessageDialog from the download thread, off
the main loop.

Give _apply_settings_internal() a raise_errors flag and pass it from
the two thread call sites. Their handlers become reachable and already
report through the progress dialog via GLib.idle_add, so failures now
surface with the real reason and no Gtk work happens off the main
loop. Main-loop callers keep the old contract: swallow, show the error
dialog, return False.
2026-08-17 23:32:42 -07:00