spacr.qt.path_probe

Answer “is this path there?” without ever blocking the interface.

WHY THIS EXISTS. 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.

So the rule is absolute: NOTHING on the GUI thread may touch the filesystem for a path the user supplied. This module is how that is kept.

HOW IT ANSWERS. From a cache, immediately. A path it has not seen is reported as PRESENT and queued for a background check; when the answer arrives the cache is updated and probes emits, so a widget can redraw. Optimistic on purpose: a path drawn as missing for a moment and then corrected is a widget that cried wolf, while a path drawn as present and then corrected is one that simply learned something.

Functions

exists(→ bool)

Whether path is there, answered from cache and never blocking.

forget(→ None)

Drop what is cached, so the next exists() asks again.

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)

Record an answer somebody already has, without a probe.

Module Contents

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

Whether path is there, answered from cache and never blocking.

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

  • default – what to say while the answer is unknown. True by default – see the module docstring on why optimism is the right way round here.

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

  • wait – bound the wait by PROBE_TIMEOUT_S instead of answering default immediately, and cache what comes back. For callers whose whole question is “is this one missing” – see isdir() for why the optimistic default is wrong for them.

Returns:

the cached answer, or default with a check queued.

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

Drop what is cached, so the next exists() asks again.

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 – bound the wait by PROBE_TIMEOUT_S instead of answering default immediately, and cache what comes back.

WHY wait EXISTS, and why it is not the 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. Nobody asked for it and nobody was waiting on it, so answering from cache and checking later is strictly better.

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, which is why it survived review. The wait is still bounded, so a sleeping autofs mount costs a fraction of a second rather than twenty.

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]

Record an answer somebody already has, without a probe.

The file dialog has just told us a path exists; asking the filesystem again would be a second stat for a fact already in hand.

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 this there was no way to state the directory half at all, and isdir kept answering with its False default until a background probe caught up.

Nested helpers

_stat_with_timeout.run() → None

Ask the filesystem the question that is allowed to block.

This body is the reason the module exists: it runs on a worker, where an autofs mount taking twenty seconds to wake costs nobody a frozen window. OSError is an answer of “no”, not a failure – a path that cannot be stat-ed is a path the user cannot use either.

spacr/qt/path_probe.py:104