mirror of
https://github.com/Misterio77/Foundry.git
synced 2026-08-24 02:14:13 -05:00
feat(projects/khora): refresh on vdir changes
Monitor local calendar directories and debounce automatic refreshes when vdirsyncer or another application changes event files. Assisted-by: pi (gpt-5.6-sol)
This commit is contained in:
@@ -9,8 +9,8 @@ The order reflects engineering dependencies rather than product priority.
|
||||
refresh, and zoom shortcuts.
|
||||
3. **Session persistence — done.** Remember the selected view, zoom, sidebar
|
||||
width, collapsed accounts, and visible calendars across restarts.
|
||||
4. **Filesystem monitoring** — refresh when local vdir files change, without
|
||||
requiring the toolbar refresh button.
|
||||
4. **Filesystem monitoring — done.** Refresh when local vdir files change,
|
||||
without requiring the toolbar refresh button.
|
||||
5. **Forward agenda** — replace the selected-day list with a lazily loaded,
|
||||
continuously scrolling agenda starting at the selected date.
|
||||
6. **Overlapping event layout** — place simultaneous timed events in adjacent
|
||||
|
||||
@@ -29,6 +29,15 @@ class KhalRepository:
|
||||
if settings.get("type", "calendar") == "calendar"
|
||||
)
|
||||
|
||||
@property
|
||||
def watch_paths(self) -> tuple[Path, ...]:
|
||||
paths = {
|
||||
Path(settings["path"]).expanduser()
|
||||
for settings in self._config["calendars"].values()
|
||||
if settings.get("type", "calendar") == "calendar"
|
||||
}
|
||||
return tuple(sorted(paths))
|
||||
|
||||
def events_on(self, day: dt.date, visible: set[str] | None = None) -> tuple[Event, ...]:
|
||||
return self.events_for_days((day,), visible)[day]
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ class KhoraWindow(Adw.ApplicationWindow):
|
||||
self._state_store = state_store or StateStore()
|
||||
self._state = self._state_store.load()
|
||||
self._save_source = 0
|
||||
self._refresh_source = 0
|
||||
self._file_monitors: list[Gio.FileMonitor] = []
|
||||
self._visible_calendars: set[str] = set()
|
||||
self._collapsed_accounts = set(self._state.collapsed_accounts)
|
||||
self._view_mode = (
|
||||
@@ -87,6 +89,7 @@ class KhoraWindow(Adw.ApplicationWindow):
|
||||
self._split.connect("notify::position", lambda *_: self._schedule_state_save())
|
||||
self._toolbar.set_content(self._split)
|
||||
self._refresh()
|
||||
self._install_file_monitors()
|
||||
self._clock_source = GLib.timeout_add_seconds(30, self._on_clock_tick)
|
||||
self.connect("close-request", self._on_close_request)
|
||||
|
||||
@@ -224,6 +227,29 @@ class KhoraWindow(Adw.ApplicationWindow):
|
||||
overlay.add_overlay(self._empty)
|
||||
return overlay
|
||||
|
||||
def _install_file_monitors(self) -> None:
|
||||
assert self._repository is not None
|
||||
for path in getattr(self._repository, "watch_paths", ()):
|
||||
try:
|
||||
monitor = Gio.File.new_for_path(str(path)).monitor_directory(
|
||||
Gio.FileMonitorFlags.WATCH_MOVES,
|
||||
None,
|
||||
)
|
||||
except GLib.Error:
|
||||
continue
|
||||
monitor.connect("changed", self._on_vdir_changed)
|
||||
self._file_monitors.append(monitor)
|
||||
|
||||
def _on_vdir_changed(self, *_args) -> None:
|
||||
if self._refresh_source:
|
||||
GLib.source_remove(self._refresh_source)
|
||||
self._refresh_source = GLib.timeout_add(500, self._refresh_after_vdir_change)
|
||||
|
||||
def _refresh_after_vdir_change(self) -> bool:
|
||||
self._refresh_source = 0
|
||||
self._refresh()
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
def _error_page(self, error: str) -> Adw.StatusPage:
|
||||
return Adw.StatusPage(
|
||||
icon_name="dialog-error-symbolic",
|
||||
@@ -626,6 +652,12 @@ class KhoraWindow(Adw.ApplicationWindow):
|
||||
if self._save_source:
|
||||
GLib.source_remove(self._save_source)
|
||||
self._save_source = 0
|
||||
if self._refresh_source:
|
||||
GLib.source_remove(self._refresh_source)
|
||||
self._refresh_source = 0
|
||||
for monitor in self._file_monitors:
|
||||
monitor.cancel()
|
||||
self._file_monitors.clear()
|
||||
self._save_state()
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import datetime as dt
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
from khora.khal_adapter import KhalRepository
|
||||
@@ -25,6 +26,10 @@ def test_calendar_accounts_come_from_their_parent_directory() -> None:
|
||||
calendars = repository.calendars
|
||||
|
||||
assert tuple(calendar.account for calendar in calendars) == ("personal", "university")
|
||||
assert repository.watch_paths == (
|
||||
Path("/home/example/Calendars/personal/default"),
|
||||
Path("/home/example/Calendars/university/classes"),
|
||||
)
|
||||
|
||||
|
||||
def test_event_details_cross_the_khal_boundary() -> None:
|
||||
|
||||
Reference in New Issue
Block a user