mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-07-01 19:26:53 +02:00
feat: pace startup housekeeper runs
This commit is contained in:
@@ -29,6 +29,7 @@
|
|||||||
"Splash-Screen auf das eingebettete CCMA-Hintergrundmotiv umgestellt und redundante Titeltexte entfernt.",
|
"Splash-Screen auf das eingebettete CCMA-Hintergrundmotiv umgestellt und redundante Titeltexte entfernt.",
|
||||||
"Konfigurierbare Mindestanzeigezeit des Splash-Screens mit fünf Sekunden Standardwert ergänzt.",
|
"Konfigurierbare Mindestanzeigezeit des Splash-Screens mit fünf Sekunden Standardwert ergänzt.",
|
||||||
"Theme-unabhängigen Splash-Fortschrittsbalken in Bild-Blau mit Silberrahmen und zeitbasiertem Fortschritt eingeführt.",
|
"Theme-unabhängigen Splash-Fortschrittsbalken in Bild-Blau mit Silberrahmen und zeitbasiertem Fortschritt eingeführt.",
|
||||||
|
"Splash-Start verteilt seine Mindestanzeigezeit als optionales Mitglieder-Delay auf den Hausmeisterlauf, um insbesondere Netzwerk-Dateisysteme zu entlasten.",
|
||||||
"Hausmeister um konfigurierbare Geburtstags- und Mitgliedsjubiläumsmeldungen erweitert.",
|
"Hausmeister um konfigurierbare Geburtstags- und Mitgliedsjubiläumsmeldungen erweitert.",
|
||||||
"Statusänderungen werden mit altem und neuem Klartextwert in der Mitgliederchronik protokolliert.",
|
"Statusänderungen werden mit altem und neuem Klartextwert in der Mitgliederchronik protokolliert.",
|
||||||
"Fensterposition, normaler Fensterzustand und Maximierung werden gespeichert; der Splash startet auf dem zuletzt verwendeten Monitor.",
|
"Fensterposition, normaler Fensterzustand und Maximierung werden gespeichert; der Splash startet auf dem zuletzt verwendeten Monitor.",
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
@@ -54,8 +56,14 @@ class Housekeeper:
|
|||||||
self.state_path = repository.root / "housekeeper.json"
|
self.state_path = repository.root / "housekeeper.json"
|
||||||
self.lock_path = repository.root / ".housekeeper.lock"
|
self.lock_path = repository.root / ".housekeeper.lock"
|
||||||
|
|
||||||
def run(self, today: date | None = None) -> list[HousekeeperFinding]:
|
def run(
|
||||||
|
self,
|
||||||
|
today: date | None = None,
|
||||||
|
*,
|
||||||
|
member_delay: float = 0.0,
|
||||||
|
) -> list[HousekeeperFinding]:
|
||||||
current_date = today or date.today()
|
current_date = today or date.today()
|
||||||
|
delay = _non_negative_delay(member_delay)
|
||||||
with _exclusive_lock(self.lock_path):
|
with _exclusive_lock(self.lock_path):
|
||||||
original = self._load_state()
|
original = self._load_state()
|
||||||
working = copy.deepcopy(original)
|
working = copy.deepcopy(original)
|
||||||
@@ -69,7 +77,9 @@ class Housekeeper:
|
|||||||
|
|
||||||
rules = load_rules(self.repository.root)
|
rules = load_rules(self.repository.root)
|
||||||
repository_config = self.repository.get_configuration()
|
repository_config = self.repository.get_configuration()
|
||||||
for member_id in sorted(member_ids):
|
for index, member_id in enumerate(sorted(member_ids)):
|
||||||
|
if index and delay:
|
||||||
|
time.sleep(delay)
|
||||||
try:
|
try:
|
||||||
member, contributions = self.repository.preflight_member_record(member_id)
|
member, contributions = self.repository.preflight_member_record(member_id)
|
||||||
except RepositoryError as exc:
|
except RepositoryError as exc:
|
||||||
@@ -405,6 +415,14 @@ def _remove_orphaned_member_items(items: dict[str, dict[str, Any]], member_ids:
|
|||||||
del items[key]
|
del items[key]
|
||||||
|
|
||||||
|
|
||||||
|
def _non_negative_delay(value: float) -> float:
|
||||||
|
try:
|
||||||
|
delay = float(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return 0.0
|
||||||
|
return max(0.0, delay) if math.isfinite(delay) else 0.0
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _exclusive_lock(path: Path):
|
def _exclusive_lock(path: Path):
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
+13
-1
@@ -119,10 +119,16 @@ class SplashScreen(tk.Toplevel):
|
|||||||
errors = self.repository.validate()
|
errors = self.repository.validate()
|
||||||
self._messages.put(("status", "Baue Suchindex …"))
|
self._messages.put(("status", "Baue Suchindex …"))
|
||||||
self.repository.list_members()
|
self.repository.list_members()
|
||||||
|
member_count = len(self.repository.list_member_ids())
|
||||||
findings = []
|
findings = []
|
||||||
if self.run_housekeeper:
|
if self.run_housekeeper:
|
||||||
self._messages.put(("status", "Starte Hausmeister …"))
|
self._messages.put(("status", "Starte Hausmeister …"))
|
||||||
findings = Housekeeper(self.repository, self.housekeeper_settings).run()
|
findings = Housekeeper(self.repository, self.housekeeper_settings).run(
|
||||||
|
member_delay=_member_delay_for_splash(
|
||||||
|
self.minimum_display_seconds,
|
||||||
|
member_count,
|
||||||
|
)
|
||||||
|
)
|
||||||
result = StartupResult(self.repository, errors, findings)
|
result = StartupResult(self.repository, errors, findings)
|
||||||
self._messages.put(("result", result))
|
self._messages.put(("result", result))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -265,3 +271,9 @@ def _progress_value(elapsed: float, minimum_seconds: float, startup_finished: bo
|
|||||||
if startup_finished:
|
if startup_finished:
|
||||||
return elapsed_ratio * 100.0
|
return elapsed_ratio * 100.0
|
||||||
return elapsed_ratio * 95.0
|
return elapsed_ratio * 95.0
|
||||||
|
|
||||||
|
|
||||||
|
def _member_delay_for_splash(minimum_seconds: float, member_count: int) -> float:
|
||||||
|
if minimum_seconds <= 0 or member_count <= 0:
|
||||||
|
return 0.0
|
||||||
|
return minimum_seconds / member_count
|
||||||
|
|||||||
@@ -4,11 +4,25 @@ from datetime import date
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
import ccma.services.housekeeper as housekeeper_module
|
||||||
from ccma.rules.loader import RuleLoadError
|
from ccma.rules.loader import RuleLoadError
|
||||||
from ccma.services.housekeeper import Housekeeper
|
from ccma.services.housekeeper import Housekeeper
|
||||||
from ccma.storage.repository import MemberRepository
|
from ccma.storage.repository import MemberRepository
|
||||||
|
|
||||||
|
|
||||||
|
def test_housekeeper_optionally_waits_between_members(tmp_path, monkeypatch) -> None:
|
||||||
|
repository = MemberRepository(tmp_path)
|
||||||
|
repository.initialize()
|
||||||
|
repository.create_member(first_name="First", last_name="Member")
|
||||||
|
repository.create_member(first_name="Second", last_name="Member")
|
||||||
|
delays: list[float] = []
|
||||||
|
monkeypatch.setattr(housekeeper_module.time, "sleep", delays.append)
|
||||||
|
|
||||||
|
Housekeeper(repository).run(today=date(2026, 6, 21), member_delay=0.25)
|
||||||
|
|
||||||
|
assert delays == [0.25]
|
||||||
|
|
||||||
|
|
||||||
def test_store_rule_overrides_builtin_rule_with_same_filename(tmp_path) -> None:
|
def test_store_rule_overrides_builtin_rule_with_same_filename(tmp_path) -> None:
|
||||||
repository = MemberRepository(tmp_path)
|
repository = MemberRepository(tmp_path)
|
||||||
repository.initialize()
|
repository.initialize()
|
||||||
|
|||||||
@@ -32,7 +32,11 @@ def test_splash_position_centers_on_pointer_and_stays_on_screen() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_splash_minimum_time_only_waits_for_remaining_duration() -> None:
|
def test_splash_minimum_time_only_waits_for_remaining_duration() -> None:
|
||||||
from ccma.ui.splash import _progress_value, _remaining_minimum_ms
|
from ccma.ui.splash import (
|
||||||
|
_member_delay_for_splash,
|
||||||
|
_progress_value,
|
||||||
|
_remaining_minimum_ms,
|
||||||
|
)
|
||||||
|
|
||||||
assert _remaining_minimum_ms(100.0, 5.0, 102.25) == 2750
|
assert _remaining_minimum_ms(100.0, 5.0, 102.25) == 2750
|
||||||
assert _remaining_minimum_ms(100.0, 5.0, 106.0) == 0
|
assert _remaining_minimum_ms(100.0, 5.0, 106.0) == 0
|
||||||
@@ -41,6 +45,9 @@ def test_splash_minimum_time_only_waits_for_remaining_duration() -> None:
|
|||||||
assert _progress_value(5.0, 5.0, startup_finished=False) == 95.0
|
assert _progress_value(5.0, 5.0, startup_finished=False) == 95.0
|
||||||
assert _progress_value(2.5, 5.0, startup_finished=True) == 50.0
|
assert _progress_value(2.5, 5.0, startup_finished=True) == 50.0
|
||||||
assert _progress_value(5.0, 5.0, startup_finished=True) == 100.0
|
assert _progress_value(5.0, 5.0, startup_finished=True) == 100.0
|
||||||
|
assert _member_delay_for_splash(5.0, 10) == 0.5
|
||||||
|
assert _member_delay_for_splash(5.0, 0) == 0.0
|
||||||
|
assert _member_delay_for_splash(0.0, 10) == 0.0
|
||||||
|
|
||||||
|
|
||||||
def test_event_labels_hide_board_actor_but_keep_automatic_marker() -> None:
|
def test_event_labels_hide_board_actor_but_keep_automatic_marker() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user