mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Change to a single loader thread with a queue
This commit is contained in:
@@ -2,14 +2,18 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import ClassVar, Iterable
|
from queue import Empty, Queue
|
||||||
|
from typing import ClassVar, Iterable, Iterator
|
||||||
|
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
from rich.text import Text, TextType
|
from rich.text import Text, TextType
|
||||||
|
from typing_extensions import Final
|
||||||
|
|
||||||
|
from .. import work
|
||||||
from ..events import Mount
|
from ..events import Mount
|
||||||
from ..message import Message
|
from ..message import Message
|
||||||
from ..reactive import var
|
from ..reactive import var
|
||||||
|
from ..worker import Worker, get_current_worker
|
||||||
from ._tree import TOGGLE_STYLE, Tree, TreeNode
|
from ._tree import TOGGLE_STYLE, Tree, TreeNode
|
||||||
|
|
||||||
|
|
||||||
@@ -116,6 +120,7 @@ class DirectoryTree(Tree[DirEntry]):
|
|||||||
classes: A space-separated list of classes, or None for no classes.
|
classes: A space-separated list of classes, or None for no classes.
|
||||||
disabled: Whether the directory tree is disabled or not.
|
disabled: Whether the directory tree is disabled or not.
|
||||||
"""
|
"""
|
||||||
|
self._to_load: Queue[TreeNode[DirEntry]] = Queue()
|
||||||
super().__init__(
|
super().__init__(
|
||||||
str(path),
|
str(path),
|
||||||
data=DirEntry(Path(path)),
|
data=DirEntry(Path(path)),
|
||||||
@@ -129,7 +134,13 @@ class DirectoryTree(Tree[DirEntry]):
|
|||||||
def reload(self) -> None:
|
def reload(self) -> None:
|
||||||
"""Reload the `DirectoryTree` contents."""
|
"""Reload the `DirectoryTree` contents."""
|
||||||
self.reset(str(self.path), DirEntry(Path(self.path)))
|
self.reset(str(self.path), DirEntry(Path(self.path)))
|
||||||
self._load_directory(self.root)
|
# Orphan the old queue...
|
||||||
|
self._to_load = Queue()
|
||||||
|
# ...and replace the old load with a new one.
|
||||||
|
self._loader()
|
||||||
|
# We have a fresh queue, we have a fresh loader, get the fresh root
|
||||||
|
# loading up.
|
||||||
|
self._to_load.put_nowait(self.root)
|
||||||
|
|
||||||
def validate_path(self, path: str | Path) -> Path:
|
def validate_path(self, path: str | Path) -> Path:
|
||||||
"""Ensure that the path is of the `Path` type.
|
"""Ensure that the path is of the `Path` type.
|
||||||
@@ -229,19 +240,14 @@ class DirectoryTree(Tree[DirEntry]):
|
|||||||
"""
|
"""
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
def _load_directory(self, node: TreeNode[DirEntry]) -> None:
|
def _populate_node(self, node: TreeNode[DirEntry], content: Iterable[Path]) -> None:
|
||||||
"""Load the directory contents for a given node.
|
"""Populate the given tree node with the given directory content.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
node: The node to load the directory contents for.
|
node: The Tree node to populate.
|
||||||
|
content: The collection of `Path` objects to populate the node with.
|
||||||
"""
|
"""
|
||||||
assert node.data is not None
|
for path in content:
|
||||||
node.data.loaded = True
|
|
||||||
directory = sorted(
|
|
||||||
self.filter_paths(node.data.path.iterdir()),
|
|
||||||
key=lambda path: (not path.is_dir(), path.name.lower()),
|
|
||||||
)
|
|
||||||
for path in directory:
|
|
||||||
node.add(
|
node.add(
|
||||||
path.name,
|
path.name,
|
||||||
data=DirEntry(path),
|
data=DirEntry(path),
|
||||||
@@ -249,8 +255,52 @@ class DirectoryTree(Tree[DirEntry]):
|
|||||||
)
|
)
|
||||||
node.expand()
|
node.expand()
|
||||||
|
|
||||||
def _on_mount(self, _: Mount) -> None:
|
def _directory_content(self, location: Path, worker: Worker) -> Iterator[Path]:
|
||||||
self._load_directory(self.root)
|
"""Load the content of a given directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
location: The location to load from.
|
||||||
|
worker: The worker that the loading is taking place in.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Path: A entry within the location.
|
||||||
|
"""
|
||||||
|
for entry in location.iterdir():
|
||||||
|
if worker.is_cancelled:
|
||||||
|
break
|
||||||
|
yield entry
|
||||||
|
|
||||||
|
def _load_directory(self, node: TreeNode[DirEntry], worker: Worker) -> None:
|
||||||
|
"""Load the directory contents for a given node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node: The node to load the directory contents for.
|
||||||
|
"""
|
||||||
|
assert node.data is not None
|
||||||
|
node.data.loaded = True
|
||||||
|
self.app.call_from_thread(
|
||||||
|
self._populate_node,
|
||||||
|
node,
|
||||||
|
sorted(
|
||||||
|
self.filter_paths(self._directory_content(node.data.path, worker)),
|
||||||
|
key=lambda path: (not path.is_dir(), path.name.lower()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOADER_INTERVAL: Final[float] = 0.2
|
||||||
|
"""How long the loader should block while waiting for queue content."""
|
||||||
|
|
||||||
|
@work(exclusive=True)
|
||||||
|
def _loader(self) -> None:
|
||||||
|
"""Background loading queue processor."""
|
||||||
|
worker = get_current_worker()
|
||||||
|
while not worker.is_cancelled:
|
||||||
|
try:
|
||||||
|
self._load_directory(
|
||||||
|
self._to_load.get(timeout=self._LOADER_INTERVAL), worker
|
||||||
|
)
|
||||||
|
except Empty:
|
||||||
|
pass
|
||||||
|
|
||||||
def _on_tree_node_expanded(self, event: Tree.NodeExpanded) -> None:
|
def _on_tree_node_expanded(self, event: Tree.NodeExpanded) -> None:
|
||||||
event.stop()
|
event.stop()
|
||||||
@@ -259,7 +309,7 @@ class DirectoryTree(Tree[DirEntry]):
|
|||||||
return
|
return
|
||||||
if dir_entry.path.is_dir():
|
if dir_entry.path.is_dir():
|
||||||
if not dir_entry.loaded:
|
if not dir_entry.loaded:
|
||||||
self._load_directory(event.node)
|
self._to_load.put_nowait(event.node)
|
||||||
else:
|
else:
|
||||||
self.post_message(self.FileSelected(self, event.node, dir_entry.path))
|
self.post_message(self.FileSelected(self, event.node, dir_entry.path))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user