Compare commits
2 Commits
523cf78737
...
3640d143cf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3640d143cf | ||
|
|
a934de6bba |
1
himalaya-aarch64-apple-darwin.tar.gz
Normal file
1
himalaya-aarch64-apple-darwin.tar.gz
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Not Found
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user