wip: refactor
This commit is contained in:
20
src/cli/__init__.py
Normal file
20
src/cli/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# CLI module for the application
|
||||
|
||||
import click
|
||||
|
||||
from .sync import sync
|
||||
from .drive import drive
|
||||
from .email import email
|
||||
from .calendar import calendar
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
"""Root command for the CLI."""
|
||||
pass
|
||||
|
||||
|
||||
cli.add_command(sync)
|
||||
cli.add_command(drive)
|
||||
cli.add_command(email)
|
||||
cli.add_command(calendar)
|
||||
4
src/cli/__main__.py
Normal file
4
src/cli/__main__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import cli
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
8
src/cli/calendar.py
Normal file
8
src/cli/calendar.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import click
|
||||
import subprocess
|
||||
|
||||
@click.command()
|
||||
def calendar():
|
||||
"""Open the calendar (khal interactive)."""
|
||||
click.echo("Opening calendar...")
|
||||
subprocess.run(["khal", "interactive"])
|
||||
9
src/cli/drive.py
Normal file
9
src/cli/drive.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import click
|
||||
import subprocess
|
||||
|
||||
|
||||
@click.command()
|
||||
def drive():
|
||||
"""View OneDrive files."""
|
||||
click.echo("Launching OneDrive viewer...")
|
||||
subprocess.run(["python3", "src/drive_view_tui.py"])
|
||||
9
src/cli/email.py
Normal file
9
src/cli/email.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import click
|
||||
from src.maildir_gtd.app import launch_email_viewer
|
||||
|
||||
|
||||
@click.command()
|
||||
def email():
|
||||
"""Read emails from Maildir."""
|
||||
click.echo("Opening email viewer...")
|
||||
launch_email_viewer()
|
||||
283
src/cli/sync.py
Normal file
283
src/cli/sync.py
Normal file
@@ -0,0 +1,283 @@
|
||||
import click
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from rich.progress import Progress, SpinnerColumn, MofNCompleteColumn
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from src.utils.mail_utils.helpers import ensure_directory_exists
|
||||
from src.utils.calendar_utils import save_events_to_vdir, save_events_to_file
|
||||
from src.services.microsoft_graph.calendar import fetch_calendar_events
|
||||
from src.services.microsoft_graph.mail import (
|
||||
fetch_mail_async,
|
||||
archive_mail_async,
|
||||
delete_mail_async,
|
||||
synchronize_maildir_async,
|
||||
)
|
||||
from src.services.microsoft_graph.auth import get_access_token
|
||||
|
||||
# Function to create Maildir structure
|
||||
def create_maildir_structure(base_path):
|
||||
"""
|
||||
Create the standard Maildir directory structure.
|
||||
|
||||
Args:
|
||||
base_path (str): Base path for the Maildir.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
ensure_directory_exists(os.path.join(base_path, "cur"))
|
||||
ensure_directory_exists(os.path.join(base_path, "new"))
|
||||
ensure_directory_exists(os.path.join(base_path, "tmp"))
|
||||
ensure_directory_exists(os.path.join(base_path, ".Archives"))
|
||||
ensure_directory_exists(os.path.join(base_path, ".Trash", "cur"))
|
||||
|
||||
|
||||
async def fetch_calendar_async(headers, progress, task_id, dry_run, vdir_path, ics_path, org_name, days_back, days_forward, continue_iteration):
|
||||
"""
|
||||
Fetch calendar events and save them in the appropriate format.
|
||||
|
||||
Args:
|
||||
headers: Authentication headers for Microsoft Graph API
|
||||
progress: Progress instance for updating progress bars
|
||||
task_id: ID of the task in the progress bar
|
||||
|
||||
Returns:
|
||||
List of event dictionaries
|
||||
|
||||
Raises:
|
||||
Exception: If there's an error fetching or saving events
|
||||
"""
|
||||
try:
|
||||
# Use the utility function to fetch calendar events
|
||||
progress.console.print(
|
||||
"[cyan]Fetching events from Microsoft Graph API...[/cyan]"
|
||||
)
|
||||
events, total_events = await fetch_calendar_events(
|
||||
headers=headers, days_back=days_back, days_forward=days_forward
|
||||
)
|
||||
|
||||
progress.console.print(
|
||||
f"[cyan]Got {len(events)} events from API (reported total: {total_events})[/cyan]"
|
||||
)
|
||||
|
||||
# Update progress bar with total events
|
||||
progress.update(task_id, total=total_events)
|
||||
|
||||
# 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)
|
||||
progress.console.print(
|
||||
f"[cyan]Saving events to vdir: {org_vdir_path}[/cyan]"
|
||||
)
|
||||
save_events_to_vdir(events, org_vdir_path,
|
||||
progress, task_id, dry_run)
|
||||
progress.console.print(
|
||||
f"[green]Finished saving events to vdir: {org_vdir_path}[/green]"
|
||||
)
|
||||
elif ics_path:
|
||||
# Save to a single ICS file in the output_ics directory
|
||||
progress.console.print(
|
||||
f"[cyan]Saving events to ICS file: {ics_path}/events_latest.ics[/cyan]"
|
||||
)
|
||||
save_events_to_file(
|
||||
events, f"{ics_path}/events_latest.ics", progress, task_id, dry_run
|
||||
)
|
||||
progress.console.print(
|
||||
f"[green]Finished saving events to ICS file[/green]"
|
||||
)
|
||||
else:
|
||||
# No destination specified
|
||||
progress.console.print(
|
||||
"[yellow]Warning: No destination path (--vdir or --icsfile) specified for calendar events.[/yellow]"
|
||||
)
|
||||
else:
|
||||
progress.console.print(
|
||||
f"[DRY-RUN] Would save {len(events)} events to {
|
||||
'vdir format' if vdir_path else 'single ICS file'}"
|
||||
)
|
||||
progress.update(task_id, advance=len(events))
|
||||
|
||||
# Interactive mode: Ask if the user wants to continue with the next date range
|
||||
if continue_iteration:
|
||||
# Move to the next date range
|
||||
next_start_date = datetime.now() - timedelta(days=days_back)
|
||||
next_end_date = next_start_date + timedelta(days=days_forward)
|
||||
|
||||
progress.console.print(
|
||||
f"\nCurrent date range: {next_start_date.strftime(
|
||||
'%Y-%m-%d')} to {next_end_date.strftime('%Y-%m-%d')}"
|
||||
)
|
||||
|
||||
user_response = click.prompt(
|
||||
"\nContinue to iterate? [y/N]", default="N").strip().lower()
|
||||
|
||||
while user_response == "y":
|
||||
progress.console.print(
|
||||
f"\nFetching events for {next_start_date.strftime(
|
||||
'%Y-%m-%d')} to {next_end_date.strftime('%Y-%m-%d')}..."
|
||||
)
|
||||
|
||||
# Reset the progress bar for the new fetch
|
||||
progress.update(task_id, completed=0, total=0)
|
||||
|
||||
# Fetch events for the next date range
|
||||
next_events, next_total_events = await fetch_calendar_events(
|
||||
headers=headers,
|
||||
days_back=0,
|
||||
days_forward=days_forward,
|
||||
start_date=next_start_date,
|
||||
end_date=next_end_date,
|
||||
)
|
||||
|
||||
# Update progress bar with total events
|
||||
progress.update(task_id, total=next_total_events)
|
||||
|
||||
if not dry_run:
|
||||
if vdir_path:
|
||||
save_events_to_vdir(
|
||||
next_events, org_vdir_path, progress, task_id, dry_run
|
||||
)
|
||||
else:
|
||||
save_events_to_file(
|
||||
next_events,
|
||||
f"output_ics/outlook_events_{next_start_date.strftime('%Y%m%d')}.ics",
|
||||
progress,
|
||||
task_id,
|
||||
dry_run,
|
||||
)
|
||||
else:
|
||||
progress.console.print(
|
||||
f"[DRY-RUN] Would save {len(next_events)} events to {
|
||||
'vdir format' if vdir_path else 'output_ics/outlook_events_' + next_start_date.strftime('%Y%m%d') + '.ics'}"
|
||||
)
|
||||
progress.update(task_id, advance=len(next_events))
|
||||
|
||||
# Calculate the next date range
|
||||
next_start_date = next_end_date
|
||||
next_end_date = next_start_date + timedelta(days=days_forward)
|
||||
|
||||
progress.console.print(
|
||||
f"\nNext date range would be: {next_start_date.strftime(
|
||||
'%Y-%m-%d')} to {next_end_date.strftime('%Y-%m-%d')}"
|
||||
)
|
||||
user_response = click.prompt(
|
||||
"\nContinue to iterate? [y/N]", default="N").strip().lower()
|
||||
|
||||
return events
|
||||
except Exception as e:
|
||||
progress.console.print(
|
||||
f"[red]Error fetching or saving calendar events: {str(e)}[/red]"
|
||||
)
|
||||
import traceback
|
||||
|
||||
progress.console.print(f"[red]{traceback.format_exc()}[/red]")
|
||||
progress.update(task_id, completed=True)
|
||||
return []
|
||||
|
||||
|
||||
async def _sync_outlook_data(dry_run, vdir, icsfile, org, days_back, days_forward, continue_iteration, download_attachments):
|
||||
"""Synchronize data from external sources."""
|
||||
|
||||
# Save emails to Maildir
|
||||
maildir_path = (
|
||||
os.getenv("MAILDIR_PATH", os.path.expanduser(
|
||||
"~/Mail")) + f"/{org}"
|
||||
)
|
||||
attachments_dir = os.path.join(maildir_path, "attachments")
|
||||
ensure_directory_exists(attachments_dir)
|
||||
create_maildir_structure(maildir_path)
|
||||
|
||||
# Define scopes for Microsoft Graph API
|
||||
scopes = [
|
||||
"https://graph.microsoft.com/Calendars.Read",
|
||||
"https://graph.microsoft.com/Mail.ReadWrite",
|
||||
]
|
||||
|
||||
# Authenticate and get access token
|
||||
access_token, headers = get_access_token(scopes)
|
||||
|
||||
# Set up the progress bars
|
||||
progress = Progress(
|
||||
SpinnerColumn(), MofNCompleteColumn(), *Progress.get_default_columns()
|
||||
)
|
||||
|
||||
with progress:
|
||||
task_fetch = progress.add_task("[green]Syncing Inbox...", total=0)
|
||||
task_calendar = progress.add_task(
|
||||
"[cyan]Fetching calendar...", total=0)
|
||||
task_read = progress.add_task("[blue]Marking as read...", total=0)
|
||||
task_archive = progress.add_task("[yellow]Archiving mail...", total=0)
|
||||
task_delete = progress.add_task("[red]Deleting mail...", total=0)
|
||||
|
||||
await asyncio.gather(
|
||||
synchronize_maildir_async(
|
||||
maildir_path, headers, progress, task_read, dry_run
|
||||
),
|
||||
archive_mail_async(maildir_path, headers,
|
||||
progress, task_archive, dry_run),
|
||||
delete_mail_async(maildir_path, headers,
|
||||
progress, task_delete, dry_run),
|
||||
fetch_mail_async(
|
||||
maildir_path,
|
||||
attachments_dir,
|
||||
headers,
|
||||
progress,
|
||||
task_fetch,
|
||||
dry_run,
|
||||
download_attachments,
|
||||
),
|
||||
fetch_calendar_async(headers, progress, task_calendar, dry_run, vdir, icsfile, org, days_back, days_forward, continue_iteration),
|
||||
)
|
||||
click.echo("Sync complete.")
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--dry-run",
|
||||
is_flag=True,
|
||||
help="Run in dry-run mode without making changes.",
|
||||
default=False,
|
||||
)
|
||||
@click.option(
|
||||
"--vdir",
|
||||
help="Output calendar events in vdir format to the specified directory (each event in its own file)",
|
||||
default="~/Calendar",
|
||||
)
|
||||
@click.option(
|
||||
"--icsfile", help="Output calendar events into this ics file path.", default=None
|
||||
)
|
||||
@click.option(
|
||||
"--org",
|
||||
help="Specify the organization name for the subfolder to store emails and calendar events",
|
||||
default="corteva",
|
||||
)
|
||||
@click.option(
|
||||
"--days-back",
|
||||
type=int,
|
||||
help="Number of days to look back for calendar events",
|
||||
default=1,
|
||||
)
|
||||
@click.option(
|
||||
"--days-forward",
|
||||
type=int,
|
||||
help="Number of days to look forward for calendar events",
|
||||
default=6,
|
||||
)
|
||||
@click.option(
|
||||
"--continue-iteration",
|
||||
is_flag=True,
|
||||
help="Enable interactive mode to continue fetching more date ranges",
|
||||
default=False,
|
||||
)
|
||||
@click.option(
|
||||
"--download-attachments",
|
||||
is_flag=True,
|
||||
help="Download email attachments",
|
||||
default=False,
|
||||
)
|
||||
def sync(dry_run, vdir, icsfile, org, days_back, days_forward, continue_iteration, download_attachments):
|
||||
asyncio.run(_sync_outlook_data(dry_run, vdir, icsfile, org, days_back, days_forward, continue_iteration, download_attachments))
|
||||
Reference in New Issue
Block a user