synology_apm_repo.sdk.storage.dircache module¶
DirCache — mandatory directory-listing cache.
resolve_seq_file needs “logical name -> [physical names]” lookups for
every per-generation file (.buk, .inf, .fgp, .ref, …).
Re-listdir()-ing the same directory on every such lookup turns “export
one image that touches a few hundred buckets” into O(n^2) directory scans —
on an object-storage backend, hundreds of paginated list-object API
calls. This is a correctness-adjacent performance requirement, not an
optional optimization: a Session holds exactly one DirCache per
open repository for its whole lifetime; only an explicit invalidate()
(e.g. the TUI’s “refresh” action) forces a re-scan.
generations.py’s S3/Azure db/<name> generation selection is a
separate mechanism with its own transaction-log-based rule — it calls
store.listdir() directly and does not go through this cache.
- synology_apm_repo.sdk.storage.dircache.split_seq_suffix(name)¶
Split a trailing
.<digits>sequence-id suffix offname(FORMAT-SPEC.md: sequence-id-suffix). Returns(name, None)unchanged if there is no such suffix.Note the suffix is only ever the last dot-segment:
"132.buk.1"->("132.buk", 1)— the base name"132.buk"itself legitimately contains a dot."132.buk"(no suffix) ->("132.buk", None).
- class synology_apm_repo.sdk.storage.dircache.DirCache(store)¶
Bases:
objectPer-repository cache of directory listings, keyed by store-relative directory path.
Both
_rawand_groupedareAsyncKeyedCacheinstances — unbounded, session-wide shared. Two Tasks racing on the same cold directory only pay for one listing: the second awaits the first’s still-in-flightstore.listdir()future instead of issuing its own, and both end up with the same result (or exception).- async listdir(dir_path)¶
Cached passthrough to
store.listdir(dir_path).
- async grouped(dir_path)¶
Return
{logical_name: [physical_name, ...]}fordir_path, built from exactly onelistdircall and cached thereafter.logical_nameis each entry with any trailing sequence-id suffix stripped (split_seq_suffix) — a directory entry with no suffix maps to itself.
- async invalidate(dir_path=None)¶
Drop cached entries for
dir_path(or everything, if omitted).Stays
async def(nothing here actually awaits) purely to preserve the existingawait dir_cache.invalidate(...)call shape at every call site.