mirror of
https://git.srhtnin.garden/sarah/SaraFem.git
synced 2026-10-15 02:23:17 +02:00
Fixed dead network mounts freezing folder listing in Browser.py
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4c05e62949
commit
1f3913bb56
1 file changed
+31
-11
+31
-11
@@ -277,6 +277,8 @@ class Browser(ft.Row):
|
||||
# entries make the cache meaningless.
|
||||
self.ColumnCells: dict[str, list[ft.Container]] = {}
|
||||
self.MetadataCache: dict[Path, tuple[float, dict[str, str]]] = {}
|
||||
# Filled by _BuildEntries from the directory listing itself, see _IsDir.
|
||||
self.EntryIsDir: dict[Path, bool] = {}
|
||||
self.FolderIconOverrides: dict[Path, FolderIconOverride] = LoadFolderIconOverrides()
|
||||
# Bumped by _RefreshEntries every time it rebuilds the list, so a
|
||||
# background _PopulateEntriesAsync pass from a superseded build can
|
||||
@@ -1138,12 +1140,14 @@ class Browser(ft.Row):
|
||||
blocking on it. _PopulateEntriesAsync fills the real values in
|
||||
afterwards, off the UI thread. A cache hit (e.g. a same-directory
|
||||
refresh after a rename) still shows immediately, no flicker."""
|
||||
Cached = self.MetadataCache.get(Entry)
|
||||
if Cached is None:
|
||||
return {} # don't stat on a miss: a dead network mount blocks for ~20s
|
||||
try:
|
||||
Mtime = Entry.stat().st_mtime
|
||||
except OSError:
|
||||
return {}
|
||||
Cached = self.MetadataCache.get(Entry)
|
||||
if Cached is not None and Cached[0] == Mtime:
|
||||
if Cached[0] == Mtime:
|
||||
return Cached[1]
|
||||
return {}
|
||||
|
||||
@@ -1197,7 +1201,7 @@ class Browser(ft.Row):
|
||||
pass resolves the real icon. Identical to _LeadingFor's non-
|
||||
thumbnail branches, so only images visibly change once their
|
||||
thumbnail loads — everything else never flickers."""
|
||||
if Entry.is_dir():
|
||||
if self._IsDir(Entry):
|
||||
return self._ResolveFolderIconFast(Entry, Size)
|
||||
return ft.Icon(ft.Icons.INSERT_DRIVE_FILE_OUTLINED, size=Size, color=Palette.Overlay0)
|
||||
|
||||
@@ -1273,7 +1277,7 @@ class Browser(ft.Row):
|
||||
NameKey = Entry.name.lower()
|
||||
if Field == "Size":
|
||||
try:
|
||||
Primary = 0 if Entry.is_dir() else Entry.stat().st_size
|
||||
Primary = 0 if self._IsDir(Entry) else Entry.stat().st_size
|
||||
except OSError:
|
||||
Primary = 0
|
||||
elif Field == "Modified":
|
||||
@@ -1282,16 +1286,24 @@ class Browser(ft.Row):
|
||||
except OSError:
|
||||
Primary = 0.0
|
||||
elif Field == "Type":
|
||||
Primary = "" if Entry.is_dir() else Entry.suffix.lower()
|
||||
Primary = "" if self._IsDir(Entry) else Entry.suffix.lower()
|
||||
else: # "Name"
|
||||
Primary = NameKey
|
||||
return (Primary, NameKey)
|
||||
|
||||
Reverse = not self.Config.SortAscending
|
||||
Dirs = sorted((E for E in Entries if E.is_dir()), key=KeyFn, reverse=Reverse)
|
||||
Files = sorted((E for E in Entries if not E.is_dir()), key=KeyFn, reverse=Reverse)
|
||||
Dirs = sorted((E for E in Entries if self._IsDir(E)), key=KeyFn, reverse=Reverse)
|
||||
Files = sorted((E for E in Entries if not self._IsDir(E)), key=KeyFn, reverse=Reverse)
|
||||
return Dirs + Files
|
||||
|
||||
def _IsDir(self, Entry: Path) -> bool:
|
||||
"""Entry.is_dir() answered from the type _BuildEntries' scandir already
|
||||
read, so the synchronous build never stats an entry. A dead network
|
||||
mount (e.g. a CIFS share whose host is down) blocks every stat for
|
||||
~20s, which froze the whole listing for minutes."""
|
||||
IsDir = self.EntryIsDir.get(Entry)
|
||||
return Entry.is_dir() if IsDir is None else IsDir
|
||||
|
||||
def _BuildEntries(self, DirPath: Path) -> list[ft.Control]:
|
||||
self.EntryTiles = {}
|
||||
self.ColumnCells = {}
|
||||
@@ -1300,7 +1312,15 @@ class Browser(ft.Row):
|
||||
# next refresh, same as any other entry list change.
|
||||
self.FolderIconOverrides = LoadFolderIconOverrides()
|
||||
try:
|
||||
Entries = list(DirPath.iterdir())
|
||||
with os.scandir(DirPath) as Scan:
|
||||
self.EntryIsDir = {}
|
||||
for DirEntry in Scan:
|
||||
try:
|
||||
# Uses the listing's d_type, no stat (except for symlinks).
|
||||
self.EntryIsDir[DirPath / DirEntry.name] = DirEntry.is_dir()
|
||||
except OSError:
|
||||
self.EntryIsDir[DirPath / DirEntry.name] = False
|
||||
Entries = list(self.EntryIsDir)
|
||||
except (PermissionError, FileNotFoundError):
|
||||
self.CurrentEntries = []
|
||||
return [ft.Text("Can't read this folder.", italic=True)]
|
||||
@@ -1445,7 +1465,7 @@ class Browser(ft.Row):
|
||||
),
|
||||
expand=True,
|
||||
)
|
||||
if Entry.is_dir():
|
||||
if self._IsDir(Entry):
|
||||
# A directory row is both a drag source (like any other entry, so
|
||||
# it can itself be moved elsewhere) and a drop target (so other
|
||||
# entries can be dropped into it) — same MoveIntoDirectory Sidebar
|
||||
@@ -1544,7 +1564,7 @@ class Browser(ft.Row):
|
||||
on_secondary_tap_down=lambda e, p=Entry: self._RequestMenu(p, e),
|
||||
),
|
||||
)
|
||||
if Entry.is_dir():
|
||||
if self._IsDir(Entry):
|
||||
# Same drop-target treatment as the list view's directory rows —
|
||||
# see _BuildListRow.
|
||||
return ft.DragTarget(
|
||||
@@ -1555,7 +1575,7 @@ class Browser(ft.Row):
|
||||
return GridDraggable
|
||||
|
||||
def _LeadingFor(self, Entry: Path, Size: int = 32):
|
||||
if Entry.is_dir():
|
||||
if self._IsDir(Entry):
|
||||
return self._ResolveFolderIconFull(Entry, Size)
|
||||
if IsThumbnailable(Entry):
|
||||
ThumbnailPath = GetThumbnail(Entry, Size=(Size * 2, Size * 2))
|
||||
|
||||
Reference in new issue
Block a user