mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
Add async background sync task management
This commit is contained in:
@@ -1056,6 +1056,7 @@ def display_menu(
|
|||||||
continue
|
continue
|
||||||
logging.info("Exiting the program.")
|
logging.info("Exiting the program.")
|
||||||
print(colored("Exiting the program.", "green"))
|
print(colored("Exiting the program.", "green"))
|
||||||
|
getattr(password_manager, "cleanup", lambda: None)()
|
||||||
password_manager.nostr_client.close_client_pool()
|
password_manager.nostr_client.close_client_pool()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
if choice == "1":
|
if choice == "1":
|
||||||
@@ -1259,6 +1260,7 @@ def main(argv: list[str] | None = None, *, fingerprint: str | None = None) -> in
|
|||||||
print(colored("\nReceived shutdown signal. Exiting gracefully...", "yellow"))
|
print(colored("\nReceived shutdown signal. Exiting gracefully...", "yellow"))
|
||||||
logging.info(f"Received shutdown signal: {sig}. Initiating graceful shutdown.")
|
logging.info(f"Received shutdown signal: {sig}. Initiating graceful shutdown.")
|
||||||
try:
|
try:
|
||||||
|
getattr(password_manager, "cleanup", lambda: None)()
|
||||||
password_manager.nostr_client.close_client_pool()
|
password_manager.nostr_client.close_client_pool()
|
||||||
logging.info("NostrClient closed successfully.")
|
logging.info("NostrClient closed successfully.")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -1277,6 +1279,7 @@ def main(argv: list[str] | None = None, *, fingerprint: str | None = None) -> in
|
|||||||
logger.info("Program terminated by user via KeyboardInterrupt.")
|
logger.info("Program terminated by user via KeyboardInterrupt.")
|
||||||
print(colored("\nProgram terminated by user.", "yellow"))
|
print(colored("\nProgram terminated by user.", "yellow"))
|
||||||
try:
|
try:
|
||||||
|
getattr(password_manager, "cleanup", lambda: None)()
|
||||||
password_manager.nostr_client.close_client_pool()
|
password_manager.nostr_client.close_client_pool()
|
||||||
logging.info("NostrClient closed successfully.")
|
logging.info("NostrClient closed successfully.")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -1287,6 +1290,7 @@ def main(argv: list[str] | None = None, *, fingerprint: str | None = None) -> in
|
|||||||
logger.error(f"A user-related error occurred: {e}", exc_info=True)
|
logger.error(f"A user-related error occurred: {e}", exc_info=True)
|
||||||
print(colored(f"Error: {e}", "red"))
|
print(colored(f"Error: {e}", "red"))
|
||||||
try:
|
try:
|
||||||
|
getattr(password_manager, "cleanup", lambda: None)()
|
||||||
password_manager.nostr_client.close_client_pool()
|
password_manager.nostr_client.close_client_pool()
|
||||||
logging.info("NostrClient closed successfully.")
|
logging.info("NostrClient closed successfully.")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -1297,6 +1301,7 @@ def main(argv: list[str] | None = None, *, fingerprint: str | None = None) -> in
|
|||||||
logger.error(f"An unexpected error occurred: {e}", exc_info=True)
|
logger.error(f"An unexpected error occurred: {e}", exc_info=True)
|
||||||
print(colored(f"Error: An unexpected error occurred: {e}", "red"))
|
print(colored(f"Error: An unexpected error occurred: {e}", "red"))
|
||||||
try:
|
try:
|
||||||
|
getattr(password_manager, "cleanup", lambda: None)()
|
||||||
password_manager.nostr_client.close_client_pool()
|
password_manager.nostr_client.close_client_pool()
|
||||||
logging.info("NostrClient closed successfully.")
|
logging.info("NostrClient closed successfully.")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
@@ -1281,20 +1281,30 @@ class PasswordManager:
|
|||||||
|
|
||||||
async def _worker() -> None:
|
async def _worker() -> None:
|
||||||
try:
|
try:
|
||||||
if hasattr(self, "nostr_client") and hasattr(self, "vault"):
|
await self.attempt_initial_sync_async()
|
||||||
self.attempt_initial_sync()
|
await self.sync_index_from_nostr_async()
|
||||||
if hasattr(self, "sync_index_from_nostr"):
|
|
||||||
self.sync_index_from_nostr()
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning(f"Background sync failed: {exc}")
|
logger.warning(f"Background sync failed: {exc}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
threading.Thread(target=lambda: asyncio.run(_worker()), daemon=True).start()
|
thread = threading.Thread(
|
||||||
|
target=lambda: asyncio.run(_worker()), daemon=True
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
self._sync_task = thread
|
||||||
else:
|
else:
|
||||||
self._sync_task = asyncio.create_task(_worker())
|
self._sync_task = asyncio.create_task(_worker())
|
||||||
|
|
||||||
|
def cleanup(self) -> None:
|
||||||
|
"""Cancel any pending background sync task."""
|
||||||
|
task = getattr(self, "_sync_task", None)
|
||||||
|
if isinstance(task, asyncio.Task) and not task.done():
|
||||||
|
task.cancel()
|
||||||
|
elif isinstance(task, threading.Thread) and task.is_alive():
|
||||||
|
task.join(timeout=0.1)
|
||||||
|
|
||||||
def start_background_relay_check(self) -> None:
|
def start_background_relay_check(self) -> None:
|
||||||
"""Check relay health in a background thread."""
|
"""Check relay health in a background thread."""
|
||||||
if (
|
if (
|
||||||
|
@@ -195,6 +195,9 @@ class MainWindow(toga.Window):
|
|||||||
bus.unsubscribe("sync_started", self.sync_started)
|
bus.unsubscribe("sync_started", self.sync_started)
|
||||||
bus.unsubscribe("sync_finished", self.sync_finished)
|
bus.unsubscribe("sync_finished", self.sync_finished)
|
||||||
bus.unsubscribe("vault_locked", self.vault_locked)
|
bus.unsubscribe("vault_locked", self.vault_locked)
|
||||||
|
manager = getattr(self.nostr, "_manager", None)
|
||||||
|
if manager is not None:
|
||||||
|
manager.cleanup()
|
||||||
|
|
||||||
|
|
||||||
class EntryDialog(toga.Window):
|
class EntryDialog(toga.Window):
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
import time
|
import time
|
||||||
|
import asyncio
|
||||||
|
import warnings
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
@@ -17,14 +19,15 @@ def test_unlock_triggers_sync(monkeypatch, tmp_path):
|
|||||||
pm.initialize_managers = lambda: None
|
pm.initialize_managers = lambda: None
|
||||||
called = {"sync": False}
|
called = {"sync": False}
|
||||||
|
|
||||||
def fake_sync(self):
|
async def fake_sync(self):
|
||||||
called["sync"] = True
|
called["sync"] = True
|
||||||
|
|
||||||
monkeypatch.setattr(PasswordManager, "sync_index_from_nostr", fake_sync)
|
monkeypatch.setattr(PasswordManager, "sync_index_from_nostr_async", fake_sync)
|
||||||
|
|
||||||
pm.unlock_vault("pw")
|
pm.unlock_vault("pw")
|
||||||
pm.start_background_sync()
|
pm.start_background_sync()
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
pm.cleanup()
|
||||||
|
|
||||||
assert called["sync"]
|
assert called["sync"]
|
||||||
|
|
||||||
@@ -54,3 +57,29 @@ def test_quick_unlock_background_sync(monkeypatch, tmp_path):
|
|||||||
pm.exit_managed_account()
|
pm.exit_managed_account()
|
||||||
|
|
||||||
assert called["bg"]
|
assert called["bg"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_background_sync_running_loop(monkeypatch):
|
||||||
|
pm = PasswordManager.__new__(PasswordManager)
|
||||||
|
pm.offline_mode = False
|
||||||
|
called = {"init": False, "sync": False}
|
||||||
|
|
||||||
|
async def fake_attempt(self):
|
||||||
|
called["init"] = True
|
||||||
|
|
||||||
|
async def fake_sync(self):
|
||||||
|
called["sync"] = True
|
||||||
|
|
||||||
|
monkeypatch.setattr(PasswordManager, "attempt_initial_sync_async", fake_attempt)
|
||||||
|
monkeypatch.setattr(PasswordManager, "sync_index_from_nostr_async", fake_sync)
|
||||||
|
|
||||||
|
async def runner():
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
pm.start_background_sync()
|
||||||
|
await asyncio.sleep(0.01)
|
||||||
|
assert not any(issubclass(wi.category, RuntimeWarning) for wi in w)
|
||||||
|
|
||||||
|
asyncio.run(runner())
|
||||||
|
pm.cleanup()
|
||||||
|
assert called["init"] and called["sync"]
|
||||||
|
Reference in New Issue
Block a user