Swap to a dual-working approach

Plan C; or is it plan D? Something like that. Anyway... in this approach we
keep a single "forever" async task worker per directory tree, which in turn
looks at the async Queue, and when a new node appears on it it starts a
short-lived thread to load the directory data.

This seems to be working fine on macOS. Next up is testing on Windows.
This commit is contained in:
Dave Pearson
2023-05-17 13:28:07 +01:00
parent ecde90b1c3
commit 26e6dbbfa3

View File

@@ -11,7 +11,7 @@ from rich.text import Text, TextType
from .. import work from .. import work
from ..message import Message from ..message import Message
from ..reactive import var from ..reactive import var
from ..worker import Worker, get_current_worker from ..worker import Worker, WorkerCancelled, WorkerFailed, get_current_worker
from ._tree import TOGGLE_STYLE, Tree, TreeNode from ._tree import TOGGLE_STYLE, Tree, TreeNode
@@ -269,22 +269,22 @@ class DirectoryTree(Tree[DirEntry]):
yield entry yield entry
@work @work
def _load_directory(self, node: TreeNode[DirEntry]) -> None: def _load_directory(self, node: TreeNode[DirEntry]) -> list[Path]:
"""Load the directory contents for a given node. """Load the directory contents for a given node.
Args: Args:
node: The node to load the directory contents for. node: The node to load the directory contents for.
Returns:
The list of entries within the directory associated with the node.
""" """
assert node.data is not None assert node.data is not None
node.data.loaded = True node.data.loaded = True
worker = get_current_worker() return sorted(
self.app.call_from_thread( self.filter_paths(
self._populate_node, self._directory_content(node.data.path, get_current_worker())
node,
sorted(
self.filter_paths(self._directory_content(node.data.path, worker)),
key=lambda path: (not path.is_dir(), path.name.lower()),
), ),
key=lambda path: (not path.is_dir(), path.name.lower()),
) )
@work(exclusive=True) @work(exclusive=True)
@@ -292,7 +292,26 @@ class DirectoryTree(Tree[DirEntry]):
"""Background loading queue processor.""" """Background loading queue processor."""
worker = get_current_worker() worker = get_current_worker()
while not worker.is_cancelled: while not worker.is_cancelled:
self._load_directory(await self._to_load.get()) # Get the next node that needs loading off the queue. Note that
# this blocks if the queue is empty.
node = await self._to_load.get()
content: list[Path] = []
try:
# Spin up a short-lived thread that will load the content of
# the directory associated with that node.
content = await self._load_directory(node).wait()
except WorkerCancelled:
# The worker was cancelled, that would suggest we're all
# done here and we should get out of the loader in general.
break
except WorkerFailed:
# This particular worker failed to start. We don't know the
# reason so let's no-op that (for now anyway).
pass
# We're still here and we have directory content, get it into
# the tree.
if content:
self._populate_node(node, content)
def _on_tree_node_expanded(self, event: Tree.NodeExpanded) -> None: def _on_tree_node_expanded(self, event: Tree.NodeExpanded) -> None:
event.stop() event.stop()