Skip to content

Commit a46a885

Browse files
committed
perf: persist drive listing worker
1 parent 4eeb7c7 commit a46a885

3 files changed

Lines changed: 162 additions & 108 deletions

File tree

src/rovr/app.py

Lines changed: 34 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from __future__ import annotations
22

33
import asyncio
4-
import multiprocessing
54
import os
65
import sys
76
import threading
87
from contextlib import suppress
98
from io import TextIOWrapper
109
from os import path
1110
from subprocess import Popen, TimeoutExpired
11+
from time import monotonic
1212
from typing import Callable, Iterable
1313

1414
from rich.console import RenderableType
@@ -63,7 +63,7 @@
6363
PreviewContainer,
6464
)
6565
from rovr.footer import Clipboard, MetadataContainer, ProcessContainer
66-
from rovr.functions import drive_workers, multiprocessing_utils
66+
from rovr.functions import drive_workers
6767
from rovr.functions.cwd import chdir, getcwd
6868
from rovr.functions.path import (
6969
dump_exc,
@@ -641,19 +641,19 @@ def cd(
641641
@work(thread=True)
642642
def watch_for_changes_and_update(self) -> None:
643643
cwd = getcwd()
644-
file_list: FileList = self.query_one(FileList)
645644
pins_path = path.join(RovrVars.ROVRCONFIG, "pins.json")
646645
with suppress(OSError):
647646
self._pins_mtime = path.getmtime(pins_path)
648647
state_path = path.join(RovrVars.ROVRSTATE, "state.toml")
649-
state_mtime = None
650648
with suppress(OSError):
651649
state_mtime = path.getmtime(state_path)
652-
drive_update_every = int(config["interface"]["drive_watcher_frequency"])
653-
count: int = -2
650+
drive_update_every = max(1.0, config["interface"]["drive_watcher_frequency"])
651+
next_drive_check = 0.0
652+
drive_watcher = drive_workers.DriveWatcher(
653+
sys.platform, config["settings"]["drive_exclude"], drive_update_every
654+
)
654655
style_available: bool = self.CUSTOM_STYLE_AVAILABLE
655656
custom_style_path = path.join(RovrVars.ROVRCONFIG, "style.tcss")
656-
new_drives: list[str] | None = None
657657
cwd_mtime: float | None = None
658658
pin_sidebar = self.query_one(PinnedSidebar)
659659

@@ -663,14 +663,11 @@ def watch_for_changes_and_update(self) -> None:
663663

664664
while True:
665665
if self._shutdown_event.wait(timeout=1):
666-
return
666+
break
667667
if i_should_shut_down():
668-
return
668+
break
669669
if (file_list := self.file_list).parent is None or not file_list.is_running:
670670
continue
671-
count += 1
672-
if count >= drive_update_every:
673-
count = 0
674671
try:
675672
new_cwd = getcwd()
676673
if not self.file_list.file_list_pause_check:
@@ -683,12 +680,10 @@ def watch_for_changes_and_update(self) -> None:
683680
else:
684681
# only rescan when the directory mtime changed;
685682
# renames/creates/deletes always bump it
686-
new_cwd_mtime = None
687683
with suppress(OSError):
688684
new_cwd_mtime = path.getmtime(cwd)
689685
if new_cwd_mtime != cwd_mtime:
690686
cwd_mtime = new_cwd_mtime
691-
items = None
692687
with suppress(OSError):
693688
items = get_filtered_dir_names(
694689
cwd,
@@ -710,11 +705,9 @@ def watch_for_changes_and_update(self) -> None:
710705
)
711706

712707
if i_should_shut_down():
713-
return
708+
break
714709

715710
# check pins.json
716-
new_mtime = None
717-
reload_called: bool = False
718711
with suppress(OSError):
719712
new_mtime = path.getmtime(pins_path)
720713
if new_mtime != self._pins_mtime:
@@ -726,12 +719,10 @@ def watch_for_changes_and_update(self) -> None:
726719
# really is no issue here, thanks to any AI
727720
# models raising false issues on thread safety
728721
pin_sidebar.reload_pins()
729-
reload_called = True
730722
if i_should_shut_down():
731-
return
723+
break
732724

733725
# check state.toml
734-
new_state_mtime = None
735726
with suppress(OSError):
736727
new_state_mtime = path.getmtime(state_path)
737728
if new_state_mtime != state_mtime:
@@ -741,53 +732,30 @@ def watch_for_changes_and_update(self) -> None:
741732
self.app.call_from_thread(state_manager._load_state)
742733
self.app.call_from_thread(state_manager.restore_state)
743734
if i_should_shut_down():
744-
return
735+
break
745736

746737
# check drives
747-
if count == 0 and not reload_called:
748-
try:
749-
if self.MULTIPROCESSING_PROCESS_ALLOWED:
750-
# Run drive check in a separate process using multiprocessing.Process
751-
# Using Queue to get the result back from the process
752-
result_queue: multiprocessing.Queue[list[str]] = (
753-
multiprocessing.Queue()
754-
)
755-
756-
process = multiprocessing.Process(
757-
target=drive_workers.get_mounted_drives_worker,
758-
args=(result_queue, sys.platform, config),
759-
)
760-
multiprocessing_utils.start_process(process)
761-
process.join(timeout=2.0)
762-
763-
if process.is_alive():
764-
# Timeout - terminate the process
765-
process.terminate()
766-
process.join(timeout=0.5)
767-
if process.is_alive():
768-
process.kill()
769-
elif not result_queue.empty():
770-
# Process completed successfully
771-
new_drives = result_queue.get_nowait()
772-
else:
773-
new_drives = drive_workers.get_mounted_drives(
774-
sys.platform, config
775-
)
776-
if new_drives is not None and new_drives != pin_sidebar.DRIVES:
777-
pin_sidebar.reload_pins()
778-
except Exception as exc:
779-
if multiprocessing_process_error_checker(self, exc):
780-
count = -1 # try again immediately on next loop
781-
else:
782-
self.notify(
783-
f"{type(exc).__name__}: {exc}",
784-
title="Drives Watcher",
785-
severity="warning",
786-
markup=False,
787-
)
788-
dump_exc(self, exc)
738+
try:
739+
new_drives = None
740+
if self.MULTIPROCESSING_PROCESS_ALLOWED:
741+
new_drives = drive_watcher.poll()
742+
elif monotonic() >= next_drive_check:
743+
drive_watcher.close()
744+
next_drive_check = monotonic() + drive_update_every
745+
new_drives = drive_workers.get_mounted_drives(sys.platform, config)
746+
if new_drives is not None and new_drives != pin_sidebar.DRIVES:
747+
pin_sidebar.reload_pins(drives=new_drives)
748+
except Exception as exc:
749+
if not multiprocessing_process_error_checker(self, exc):
750+
self.notify(
751+
f"{type(exc).__name__}: {exc}",
752+
title="Drives Watcher",
753+
severity="warning",
754+
markup=False,
755+
)
756+
dump_exc(self, exc)
789757
if i_should_shut_down():
790-
return
758+
break
791759

792760
# check highlighted file mtime
793761
if not self.file_list.file_list_pause_check:
@@ -823,7 +791,7 @@ def watch_for_changes_and_update(self) -> None:
823791
dir_entry
824792
)
825793
if i_should_shut_down():
826-
return
794+
break
827795

828796
if not self.CUSTOM_STYLE_AVAILABLE:
829797
if not style_available and path.exists(custom_style_path):
@@ -835,6 +803,7 @@ def watch_for_changes_and_update(self) -> None:
835803
)
836804
elif not path.exists(custom_style_path):
837805
style_available = False
806+
drive_watcher.close()
838807

839808
@work(exclusive=True)
840809
async def on_resize(self, event: events.Resize) -> None:

src/rovr/core/pinned_sidebar.py

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import multiprocessing
2-
import sys
31
from os import R_OK, access, path
42
from threading import Lock
53
from typing import ClassVar, cast
@@ -13,12 +11,9 @@
1311

1412
from rovr.classes.mixins import Action, Actionable, CursorNavigationMixin
1513
from rovr.classes.textual_options import PinnedSidebarOption
16-
from rovr.functions import drive_workers as drive_utils
1714
from rovr.functions import icons as icon_utils
18-
from rovr.functions import multiprocessing_utils
1915
from rovr.functions import path as path_utils
2016
from rovr.functions import pins as pin_utils
21-
from rovr.functions.utils import multiprocessing_process_error_checker
2217
from rovr.variables.constants import bindings, config
2318

2419

@@ -36,11 +31,11 @@ class PinnedSidebar(
3631
ACTIONS: list[Action] = [Action("focus_search", config["keybinds"]["focus_search"])]
3732

3833
@work(exclusive=True, thread=True)
39-
def reload_pins(self) -> None:
34+
def reload_pins(self, drives: list[str] | None = None) -> None:
4035
with self.tlock:
41-
self._reload_pins()
36+
self._reload_pins(drives)
4237

43-
def _reload_pins(self) -> None:
38+
def _reload_pins(self, drives: list[str] | None = None) -> None:
4439
"""Reload pins shown
4540
4641
Raises:
@@ -161,44 +156,16 @@ def _reload_pins(self) -> None:
161156
)
162157
if self.app.return_code is not None:
163158
return
164-
self.app.call_from_thread(self.set_options, self.list_of_options)
165-
if prev_highlighted < len(self.list_of_options):
166-
self.app.call_from_thread(setattr, self, "highlighted", prev_highlighted)
167-
self.refresh_drives(id_list, None)
168-
else:
169-
self.refresh_drives(id_list, prev_highlighted)
159+
self.refresh_drives(id_list, prev_highlighted, drives)
170160

171161
def refresh_drives(
172-
self, id_list: list[str], prev_highlighted: int | None = None
162+
self,
163+
id_list: list[str],
164+
prev_highlighted: int | None = None,
165+
drives: list[str] | None = None,
173166
) -> None:
174-
# force refresh
175-
try:
176-
if self.app.MULTIPROCESSING_PROCESS_ALLOWED:
177-
result_queue: multiprocessing.Queue[list[str]] = multiprocessing.Queue()
178-
process = multiprocessing.Process(
179-
target=drive_utils.get_mounted_drives_worker,
180-
args=(result_queue, sys.platform, config),
181-
)
182-
multiprocessing_utils.start_process(process)
183-
process.join(timeout=2.0)
184-
185-
if process.is_alive():
186-
process.terminate()
187-
process.join(timeout=0.5)
188-
if process.is_alive():
189-
process.kill()
190-
return
191-
192-
if result_queue.empty():
193-
return
194-
195-
drives = result_queue.get_nowait()
196-
else:
197-
drives = drive_utils.get_mounted_drives(sys.platform, config)
198-
except Exception as exc:
199-
if not multiprocessing_process_error_checker(self.app, exc):
200-
return
201-
drives = drive_utils.get_mounted_drives(sys.platform, config)
167+
if drives is None:
168+
drives = self.DRIVES
202169
self.DRIVES = drives
203170
new_options: list[PinnedSidebarOption] = []
204171
for drive in drives:

0 commit comments

Comments
 (0)