From ba175b48d1d1268a2c04e47ede84fdd9e33b098c Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 30 Jun 2025 00:09:41 -0400 Subject: [PATCH] Add auto-sync mechanism for Nostr --- src/main.py | 11 +++++++++- src/password_manager/manager.py | 18 +++++++++++++++++ src/tests/test_auto_sync.py | 36 +++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/tests/test_auto_sync.py diff --git a/src/main.py b/src/main.py index a72488f..31fd3a7 100644 --- a/src/main.py +++ b/src/main.py @@ -5,6 +5,7 @@ import sys import logging import signal import getpass +import time from colorama import init as colorama_init from termcolor import colored import traceback @@ -453,7 +454,7 @@ def handle_settings(password_manager: PasswordManager) -> None: print(colored("Invalid choice.", "red")) -def display_menu(password_manager: PasswordManager): +def display_menu(password_manager: PasswordManager, sync_interval: float = 60.0): """ Displays the interactive menu and handles user input to perform various actions. """ @@ -466,6 +467,14 @@ def display_menu(password_manager: PasswordManager): 5. Exit """ while True: + # Periodically push updates to Nostr + if ( + password_manager.is_dirty + and time.time() - password_manager.last_update >= sync_interval + ): + handle_post_to_nostr(password_manager) + password_manager.is_dirty = False + # Flush logging handlers for handler in logging.getLogger().handlers: handler.flush() diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 2cc7ff1..241fe3e 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -16,6 +16,7 @@ import getpass import os from typing import Optional import shutil +import time from termcolor import colored from password_manager.encryption import EncryptionManager @@ -47,6 +48,7 @@ from pathlib import Path from local_bip85.bip85 import BIP85 from bip_utils import Bip39SeedGenerator, Bip39MnemonicGenerator, Bip39Languages +from datetime import datetime from utils.fingerprint_manager import FingerprintManager @@ -83,6 +85,10 @@ class PasswordManager: self.nostr_client: Optional[NostrClient] = None self.config_manager: Optional[ConfigManager] = None + # Track changes to trigger periodic Nostr sync + self.is_dirty: bool = False + self.last_update: float = time.time() + # Initialize the fingerprint manager first self.initialize_fingerprint_manager() @@ -735,6 +741,10 @@ class PasswordManager: website_name, length, username, url, blacklisted=False ) + # Mark database as dirty for background sync + self.is_dirty = True + self.last_update = time.time() + # Generate the password using the assigned index password = self.password_generator.generate_password(length, index) @@ -911,6 +921,10 @@ class PasswordManager: index, new_username, new_url, new_blacklisted ) + # Mark database as dirty for background sync + self.is_dirty = True + self.last_update = time.time() + print(colored(f"Entry updated successfully for index {index}.", "green")) # Push the updated index to Nostr so changes are backed up. @@ -949,6 +963,10 @@ class PasswordManager: self.entry_manager.delete_entry(index_to_delete) + # Mark database as dirty for background sync + self.is_dirty = True + self.last_update = time.time() + # Push updated index to Nostr after deletion try: encrypted_data = self.get_encrypted_data() diff --git a/src/tests/test_auto_sync.py b/src/tests/test_auto_sync.py new file mode 100644 index 0000000..af0fef0 --- /dev/null +++ b/src/tests/test_auto_sync.py @@ -0,0 +1,36 @@ +import time +from types import SimpleNamespace +from pathlib import Path +import pytest + +import sys + +sys.path.append(str(Path(__file__).resolve().parents[1])) + +import main + + +def test_auto_sync_triggers_post(monkeypatch): + pm = SimpleNamespace( + is_dirty=True, + last_update=time.time() - 0.2, + nostr_client=SimpleNamespace(close_client_pool=lambda: None), + handle_add_password=lambda: None, + handle_retrieve_entry=lambda: None, + handle_modify_entry=lambda: None, + ) + + called = False + + def fake_post(manager): + nonlocal called + called = True + + monkeypatch.setattr(main, "handle_post_to_nostr", fake_post) + monkeypatch.setattr("builtins.input", lambda _: "5") + + with pytest.raises(SystemExit): + main.display_menu(pm, sync_interval=0.1) + + assert called + assert pm.is_dirty is False