dashboard sync app
This commit is contained in:
158
src/cli/sync.py
158
src/cli/sync.py
@@ -1,6 +1,8 @@
|
||||
import click
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
@@ -31,7 +33,7 @@ from src.services.godspeed.sync import GodspeedSync
|
||||
# Timing state management
|
||||
def get_sync_state_file():
|
||||
"""Get the path to the sync state file."""
|
||||
return os.path.expanduser("~/.local/share/gtd-terminal-tools/sync_state.json")
|
||||
return os.path.expanduser("~/.local/share/luk/sync_state.json")
|
||||
|
||||
|
||||
def load_sync_state():
|
||||
@@ -97,7 +99,7 @@ def get_godspeed_sync_directory():
|
||||
return docs_dir
|
||||
|
||||
# Fall back to data directory
|
||||
data_dir = home / ".local" / "share" / "gtd-terminal-tools" / "godspeed"
|
||||
data_dir = home / ".local" / "share" / "luk" / "godspeed"
|
||||
return data_dir
|
||||
|
||||
|
||||
@@ -265,11 +267,12 @@ async def fetch_calendar_async(
|
||||
# Update progress bar with total events
|
||||
progress.update(task_id, total=total_events)
|
||||
|
||||
# Define org_vdir_path up front if vdir_path is specified
|
||||
org_vdir_path = os.path.join(vdir_path, org_name) if vdir_path else None
|
||||
|
||||
# Save events to appropriate format
|
||||
if not dry_run:
|
||||
if vdir_path:
|
||||
# Create org-specific directory within vdir path
|
||||
org_vdir_path = os.path.join(vdir_path, org_name)
|
||||
if vdir_path and org_vdir_path:
|
||||
progress.console.print(
|
||||
f"[cyan]Saving events to vdir: {org_vdir_path}[/cyan]"
|
||||
)
|
||||
@@ -342,7 +345,7 @@ async def fetch_calendar_async(
|
||||
progress.update(task_id, total=next_total_events)
|
||||
|
||||
if not dry_run:
|
||||
if vdir_path:
|
||||
if vdir_path and org_vdir_path:
|
||||
save_events_to_vdir(
|
||||
next_events, org_vdir_path, progress, task_id, dry_run
|
||||
)
|
||||
@@ -494,9 +497,9 @@ async def _sync_outlook_data(
|
||||
os.getenv("MAILDIR_PATH", os.path.expanduser("~/Mail")) + f"/{org}"
|
||||
)
|
||||
messages_before = 0
|
||||
new_dir = os.path.join(maildir_path, "new")
|
||||
cur_dir = os.path.join(maildir_path, "cur")
|
||||
if notify:
|
||||
new_dir = os.path.join(maildir_path, "new")
|
||||
cur_dir = os.path.join(maildir_path, "cur")
|
||||
if os.path.exists(new_dir):
|
||||
messages_before += len([f for f in os.listdir(new_dir) if ".eml" in f])
|
||||
if os.path.exists(cur_dir):
|
||||
@@ -572,7 +575,51 @@ async def _sync_outlook_data(
|
||||
click.echo("Sync complete.")
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.group()
|
||||
def sync():
|
||||
"""Email and calendar synchronization."""
|
||||
pass
|
||||
|
||||
|
||||
def daemonize():
|
||||
"""Properly daemonize the process for Unix systems."""
|
||||
# First fork
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# Parent exits
|
||||
sys.exit(0)
|
||||
except OSError as e:
|
||||
sys.stderr.write(f"Fork #1 failed: {e}\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Decouple from parent environment
|
||||
os.chdir("/")
|
||||
os.setsid()
|
||||
os.umask(0)
|
||||
|
||||
# Second fork
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# Parent exits
|
||||
sys.exit(0)
|
||||
except OSError as e:
|
||||
sys.stderr.write(f"Fork #2 failed: {e}\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Redirect standard file descriptors
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
si = open(os.devnull, "r")
|
||||
so = open(os.devnull, "a+")
|
||||
se = open(os.devnull, "a+")
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
|
||||
|
||||
@sync.command()
|
||||
@click.option(
|
||||
"--dry-run",
|
||||
is_flag=True,
|
||||
@@ -628,13 +675,19 @@ async def _sync_outlook_data(
|
||||
help="Run in daemon mode.",
|
||||
default=False,
|
||||
)
|
||||
@click.option(
|
||||
"--dashboard",
|
||||
is_flag=True,
|
||||
help="Run with TUI dashboard.",
|
||||
default=False,
|
||||
)
|
||||
@click.option(
|
||||
"--notify",
|
||||
is_flag=True,
|
||||
help="Send macOS notifications for new email messages",
|
||||
default=False,
|
||||
)
|
||||
def sync(
|
||||
def run(
|
||||
dry_run,
|
||||
vdir,
|
||||
icsfile,
|
||||
@@ -645,23 +698,31 @@ def sync(
|
||||
download_attachments,
|
||||
two_way_calendar,
|
||||
daemon,
|
||||
dashboard,
|
||||
notify,
|
||||
):
|
||||
if daemon:
|
||||
asyncio.run(
|
||||
daemon_mode(
|
||||
dry_run,
|
||||
vdir,
|
||||
icsfile,
|
||||
org,
|
||||
days_back,
|
||||
days_forward,
|
||||
continue_iteration,
|
||||
download_attachments,
|
||||
two_way_calendar,
|
||||
notify,
|
||||
)
|
||||
if dashboard:
|
||||
from .sync_dashboard import run_dashboard_sync
|
||||
|
||||
asyncio.run(run_dashboard_sync())
|
||||
elif daemon:
|
||||
from .sync_daemon import create_daemon_config, SyncDaemon
|
||||
|
||||
config = create_daemon_config(
|
||||
dry_run=dry_run,
|
||||
vdir=vdir,
|
||||
icsfile=icsfile,
|
||||
org=org,
|
||||
days_back=days_back,
|
||||
days_forward=days_forward,
|
||||
continue_iteration=continue_iteration,
|
||||
download_attachments=download_attachments,
|
||||
two_way_calendar=two_way_calendar,
|
||||
notify=notify,
|
||||
)
|
||||
|
||||
daemon_instance = SyncDaemon(config)
|
||||
daemon_instance.start()
|
||||
else:
|
||||
asyncio.run(
|
||||
_sync_outlook_data(
|
||||
@@ -679,6 +740,55 @@ def sync(
|
||||
)
|
||||
|
||||
|
||||
@sync.command()
|
||||
def stop():
|
||||
"""Stop the sync daemon."""
|
||||
pid_file = os.path.expanduser("~/.config/luk/luk.pid")
|
||||
|
||||
if not os.path.exists(pid_file):
|
||||
click.echo("Daemon is not running (no PID file found)")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(pid_file, "r") as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
# Send SIGTERM to process
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
|
||||
# Remove PID file
|
||||
os.unlink(pid_file)
|
||||
|
||||
click.echo(f"Daemon stopped (PID {pid})")
|
||||
except (ValueError, ProcessLookupError, OSError) as e:
|
||||
click.echo(f"Error stopping daemon: {e}")
|
||||
# Clean up stale PID file
|
||||
if os.path.exists(pid_file):
|
||||
os.unlink(pid_file)
|
||||
|
||||
|
||||
@sync.command()
|
||||
def status():
|
||||
"""Check the status of the sync daemon."""
|
||||
pid_file = os.path.expanduser("~/.config/luk/luk.pid")
|
||||
|
||||
if not os.path.exists(pid_file):
|
||||
click.echo("Daemon is not running")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(pid_file, "r") as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
# Check if process exists
|
||||
os.kill(pid, 0) # Send signal 0 to check if process exists
|
||||
click.echo(f"Daemon is running (PID {pid})")
|
||||
except (ValueError, ProcessLookupError, OSError):
|
||||
click.echo("Daemon is not running (stale PID file)")
|
||||
# Clean up stale PID file
|
||||
os.unlink(pid_file)
|
||||
|
||||
|
||||
def check_calendar_changes(vdir_path, org):
|
||||
"""
|
||||
Check if there are local calendar changes that need syncing.
|
||||
|
||||
Reference in New Issue
Block a user