aven 0.1.6

Local-first task manager CLI and sync server
Documentation
use anyhow::Result;
use sqlx::SqliteConnection;

use crate::config as app_config;
use crate::db::get_meta;
use crate::operations::{
    init_config as init_config_operation, show_config as show_config_operation,
    show_config_paths as show_config_paths_operation,
};
use crate::sync::sync_server_url_is_valid;

use super::TuiStore;
use super::types::{SyncStatusCheck, TuiSyncStatus};

impl TuiStore {
    pub(crate) fn config_info_lines(&self) -> Result<Vec<String>> {
        let outcome = show_config_operation()?;
        let mut lines = vec![
            format!("config path: {}", outcome.path.display()),
            String::new(),
        ];
        lines.extend(outcome.text.lines().map(str::to_string));
        Ok(lines)
    }

    pub(crate) fn config_path_lines(&self) -> Result<Vec<String>> {
        Ok(show_config_paths_operation()?.lines)
    }

    pub(crate) fn init_config(&self) -> Result<String> {
        let outcome = init_config_operation()?;
        Ok(format!("created config {}", outcome.path.display()))
    }

    pub(super) async fn load_sync_status(
        &self,
        conn: &mut SqliteConnection,
    ) -> Result<TuiSyncStatus> {
        let config = match app_config::AppConfig::load() {
            Ok(config) => config,
            Err(error) => {
                return Ok(TuiSyncStatus {
                    config_error: Some(format!("{error:#}")),
                    ..TuiSyncStatus::default()
                });
            }
        };
        let pinned_server = get_meta(conn, "sync_server_url").await?;
        let configured_server = configured_server_check(&config);
        let server_match = configured_server
            .as_ref()
            .filter(|server| server.ok)
            .and_then(|server| {
                pinned_server.as_deref().map(|pinned| {
                    let configured = server.value.trim_end_matches('/');
                    SyncStatusCheck::new(
                        pinned == configured,
                        if pinned == configured {
                            "yes".to_string()
                        } else {
                            format!("pinned={pinned} configured={configured}")
                        },
                    )
                })
            });
        let daemon_server = daemon_server_check(&config);
        let daemon_wake = match config.wake_addr() {
            Ok(addr) => SyncStatusCheck::new(true, addr.to_string()),
            Err(error) => SyncStatusCheck::new(false, format!("{error:#}")),
        };
        let pending_changes: i64 =
            sqlx::query_scalar("SELECT count(*) FROM changes WHERE server_seq IS NULL")
                .fetch_one(&mut *conn)
                .await?;
        let conflicts: i64 =
            sqlx::query_scalar("SELECT count(*) FROM conflicts WHERE resolved = 0")
                .fetch_one(&mut *conn)
                .await?;

        Ok(TuiSyncStatus {
            enabled: config.sync.enabled,
            config_error: None,
            configured_server,
            pinned_server,
            server_match,
            daemon_server,
            auth_token_configured: config.sync_auth_token().is_some(),
            interval_seconds: config.sync_interval_seconds(),
            daemon_wake,
            pending_changes,
            conflicts,
            sync_cursor: get_meta(conn, "sync_cursor").await?,
            local_sequence: get_meta(conn, "local_seq").await?,
            last_attempt: get_meta(conn, "sync_last_attempt_at").await?,
            last_success: get_meta(conn, "sync_last_success_at").await?,
            last_error: get_meta(conn, "sync_last_error").await?,
            last_pushed: get_meta(conn, "sync_last_pushed").await?,
            last_pulled: get_meta(conn, "sync_last_pulled").await?,
            last_cursor: get_meta(conn, "sync_last_cursor").await?,
        })
    }
}

fn configured_server_check(config: &app_config::AppConfig) -> Option<SyncStatusCheck> {
    match app_config::resolve_sync_server(None, config) {
        Ok(server) => Some(SyncStatusCheck::new(
            sync_server_url_is_valid(&server),
            server,
        )),
        Err(error) if config.sync.enabled => {
            Some(SyncStatusCheck::new(false, format!("{error:#}")))
        }
        Err(_) => None,
    }
}

fn daemon_server_check(config: &app_config::AppConfig) -> Option<SyncStatusCheck> {
    match config.sync.server_url.as_deref() {
        Some(server) => Some(SyncStatusCheck::new(
            sync_server_url_is_valid(server),
            server,
        )),
        None if config.sync.enabled => Some(SyncStatusCheck::new(false, "not configured")),
        None => None,
    }
}