spacr.qt.path_probe

Check cached path answers, with optional bounded waiting for unknown paths.

spacr/qt/widgets/file_list.py asked os.path.exists for every remembered path, on the GUI thread, to colour the missing ones red. That is free for a local disk and it is not free for a network one: measured on one workstation, os.path.exists on a path under /nas_mnt – an autofs mount with timeout=600 – had not returned after twenty seconds, because the stat is what triggers the automount and the share was asleep.

A blocked GUI thread does not look like a slow path check. It looks like this, and every one of these was reported as a separate defect on 2026-09-04:

  • “opening map barcodes crashes spacr” – it is not a crash, it is a freeze with no traceback, which is why nothing reached the logs. The journal shows automount request ... triggered by spacr immediately before each force-quit.

  • “it kind of flickers … several events are happening upon hover and they sometimes lag for a couple of seconds” – hover events queue while the thread is stalled and then replay in a burst.

  • “i see millisecond glimmers of parts of the module screens on the home screen” – repaints are deferred, so the stack shows what it has not been given time to paint over.

  • “it seems to happen … after i have opened one or two moduals” – each module adds its own remembered paths, so there is more to stat.

Code on the GUI thread must therefore not access the filesystem for a path the user supplied; this module provides the check it uses instead.

Non-waiting checks return a cached answer or the caller’s default immediately and queue an unknown path for a background check. The default is True for exists() and False for isdir(). A background result updates the cache; probes emits its answered signal only when that answer differs from the cached one. The optimistic existence default avoids drawing an unknown path as missing while its check is pending. Passing wait=True instead waits synchronously for an uncached answer, as described by exists().

Functions

exists(→ bool)

Return a cached path answer, or probe an unknown path.

forget(→ None)

Remove cached answers without cancelling queued or running probes.

isdir(→ bool)

Whether path is a directory, answered from cache.

known(→ Optional[bool])

The cached answer for path, or None when it is not known yet.

prime(→ None)

Cache a known answer without starting a probe.

Module Contents

spacr.qt.path_probe.exists(path, *, default: bool = True, want_dir: bool = False, wait: bool = False) → bool[source]

Return a cached path answer, or probe an unknown path.

Parameters:
  • path – the path to ask about. Anything falsy is False.

  • default – what to return for an unknown path when wait=False. True by default; this value does not control a waiting probe’s timeout answer.

  • want_dir – ask isdir rather than exists. Cached separately, because a path can exist and not be a directory.

  • wait – return a cached answer immediately, or synchronously wait up to PROBE_TIMEOUT_S for an uncached stat and cache its answer. A timeout returns and caches True, even if default is False. It does not cancel the stat thread, and that thread’s later result does not update this cached timeout answer.

Returns:

False for a falsy path; otherwise a cached answer, the non-waiting default with a check queued or pending, or the waiting probe’s answer (True on timeout).

spacr.qt.path_probe.forget(path=None) → None[source]

Remove cached answers without cancelling queued or running probes. A subsequent non-waiting exists() returns its default while an existing probe is pending; it queues a new check only when none is pending. A probe already in progress can repopulate the cache after this call.

Parameters:

path – one path, or None for all of them. Called when the user has just created or deleted something and the cache would otherwise keep answering with what was true before.

spacr.qt.path_probe.isdir(path, *, default: bool = False, wait: bool = False) → bool[source]

Whether path is a directory, answered from cache.

default is False here and True in exists(), and the asymmetry is deliberate: the callers of this one are choosing a folder to OPEN a dialog in, and opening it somewhere that turns out not to exist is worse than opening it at the default location.

Parameters:
  • path – the path to ask about; passed to exists() with want_dir=True, so anything falsy is False.

  • wait – use the synchronous waiting mode of exists() for an uncached path. Its timeout answer is True, even though this function’s non-waiting default is False.

wait is off by default. The freeze this module was written for came from statting remembered paths – a passive pass over everything a settings file mentions, on the GUI thread, to colour the missing ones red. No user action waits on that pass, so answering from cache and checking later costs nothing.

Expanding a folder the user has just dropped or chosen is the opposite situation. They performed an action and are waiting for its result, and the unknown-path default turns “add this folder’s files” into “add this folder as a file” – silently, and only the first time a path is seen. The calling thread waits up to PROBE_TIMEOUT_S (5 s) for the stat. A timeout caches True without cancelling the stat thread; that thread’s later answer does not replace the cached timeout result.

spacr.qt.path_probe.known(path, *, want_dir: bool = False) → bool | None[source]

The cached answer for path, or None when it is not known yet.

Parameters:

path – the path whose cached answer is wanted, looked up by its string form (a falsy path as the empty string).

spacr.qt.path_probe.prime(path, answer: bool, *, want_dir: bool = False) → None[source]

Cache a known answer without starting a probe.

For example, a file dialog has just reported that a path exists; asking the filesystem again would be a second stat for a fact already in hand. This clears the matching pending marker but does not cancel a queued or running probe. Such a probe can later overwrite the primed answer and emit the answered signal on probes if its result changes the cached value. This call itself emits no signal.

Parameters:
  • path – the path the answer is about.

  • answer – what is already known to be true of it.

  • want_dir – record it against isdir() rather than exists(). The two are cached separately – a path can exist and not be a directory – so a caller who knows both must say so twice. Without it, isdir() answers with its False default until a background probe completes.

Nested helpers

_stat_with_timeout.run() → None

Ask the filesystem the question that is allowed to block.

It runs on a worker, so an autofs mount that takes twenty seconds to wake does not freeze the window. OSError is an answer of “no”, not a failure – a path that cannot be stat-ed cannot be used either.

spacr/qt/path_probe.py:106