Compare commits

...

2 Commits

Author SHA1 Message Date
Bendt
3640d143cf add himalay 2025-12-18 10:25:37 -05:00
Bendt
a934de6bba fix 2025-12-18 10:07:01 -05:00
3 changed files with 105 additions and 22 deletions

View File

@@ -0,0 +1 @@
Not Found

View File

@@ -801,6 +801,59 @@ def status():
os.unlink(pid_file) os.unlink(pid_file)
@sync.command(name="interactive")
@click.option(
"--org",
help="Specify the organization name for the subfolder to store emails and calendar events",
default="corteva",
)
@click.option(
"--vdir",
help="Output calendar events in vdir format to the specified directory",
default="~/Calendar",
)
@click.option(
"--notify/--no-notify",
help="Send macOS notifications for new email messages",
default=True,
)
@click.option(
"--dry-run",
is_flag=True,
help="Run in dry-run mode without making changes.",
default=False,
)
@click.option(
"--demo",
is_flag=True,
help="Run with simulated sync (demo mode)",
default=False,
)
def interactive(org, vdir, notify, dry_run, demo):
"""Launch interactive TUI dashboard for sync operations."""
from .sync_dashboard import run_dashboard_sync
sync_config = {
"org": org,
"vdir": vdir,
"notify": notify,
"dry_run": dry_run,
"days_back": 1,
"days_forward": 30,
"download_attachments": False,
"two_way_calendar": False,
"continue_iteration": False,
"icsfile": None,
}
asyncio.run(
run_dashboard_sync(notify=notify, sync_config=sync_config, demo_mode=demo)
)
# Alias 'i' for 'interactive'
sync.add_command(interactive, name="i")
def check_calendar_changes(vdir_path, org): def check_calendar_changes(vdir_path, org):
""" """
Check if there are local calendar changes that need syncing. Check if there are local calendar changes that need syncing.

View File

@@ -529,13 +529,17 @@ class SyncDashboard(App):
def action_daemonize(self) -> None: def action_daemonize(self) -> None:
"""Start sync daemon in background and exit TUI.""" """Start sync daemon in background and exit TUI."""
import subprocess
from src.cli.sync_daemon import SyncDaemon, create_daemon_config from src.cli.sync_daemon import SyncDaemon, create_daemon_config
# Build config from sync_config, adding sync_interval
daemon_config = {
**self._sync_config,
"sync_interval": self.sync_interval,
}
# Check if daemon is already running # Check if daemon is already running
config = create_daemon_config( config = create_daemon_config(**daemon_config)
sync_interval=self.sync_interval,
notify=True, # Enable notifications for daemon
)
daemon = SyncDaemon(config) daemon = SyncDaemon(config)
if daemon.is_running(): if daemon.is_running():
@@ -548,26 +552,51 @@ class SyncDashboard(App):
# Start daemon and exit # Start daemon and exit
self._log_to_task(self.selected_task, "Starting background daemon...") self._log_to_task(self.selected_task, "Starting background daemon...")
# Fork the daemon process # Use subprocess to start the daemon via CLI command
# This properly handles daemonization without conflicting with TUI
try: try:
pid = os.fork() # Build the command with current config
if pid == 0: cmd = [
# Child process - become the daemon sys.executable,
os.setsid() "-m",
# Second fork to prevent zombie processes "src.cli",
pid2 = os.fork() "sync",
if pid2 == 0: "run",
# Grandchild - this becomes the daemon "--daemon",
daemon.start() "--org",
else: self._sync_config.get("org", "corteva"),
os._exit(0) "--vdir",
else: self._sync_config.get("vdir", "~/Calendar"),
# Parent process - wait briefly then exit TUI ]
import time if self._sync_config.get("notify", True):
cmd.append("--notify")
if self._sync_config.get("dry_run", False):
cmd.append("--dry-run")
if self._sync_config.get("two_way_calendar", False):
cmd.append("--two-way-calendar")
if self._sync_config.get("download_attachments", False):
cmd.append("--download-attachments")
time.sleep(0.5) # Start the daemon process detached
self.exit(message="Daemon started. Sync continues in background.") subprocess.Popen(
except OSError as e: cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
stdin=subprocess.DEVNULL,
start_new_session=True,
)
# Give it a moment to start
time.sleep(0.5)
# Verify it started
if daemon.is_running():
self.exit(
message=f"Daemon started (PID {daemon.get_pid()}). Sync continues in background."
)
else:
self._log_to_task(self.selected_task, "Failed to start daemon")
except Exception as e:
self._log_to_task(self.selected_task, f"Failed to daemonize: {e}") self._log_to_task(self.selected_task, f"Failed to daemonize: {e}")
def set_sync_callback(self, callback: Callable) -> None: def set_sync_callback(self, callback: Callable) -> None: