mrdocument 0.5.3

Automatic PDF transcription and classification via OpenAI
Documentation
use clap::Parser;
use mrdocument::profile::Profile;
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use which::which;

async fn setup_mrdocument_service(
    install_path: PathBuf,
    api_key: PathBuf,
) -> Result<(), Box<dyn std::error::Error>> {
    fs::create_dir_all(install_path.clone())?;
    let config_dir = dirs::config_local_dir()
        .ok_or("Cannot determine config dir")?
        .join("MrDocument");
    let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?;
    let api_key_string = std::fs::read_to_string(api_key)?;
    let pdftoppm_path = which("pdftoppm")
        .map_err(|_| "Missing depedency: pdftoppm")?
        .parent()
        .ok_or(())
        .map_err(|_| "Missing depedency: pdftoppm")?
        .to_path_buf();
    let pdftk_path = which("pdftk")
        .map_err(|_| "Missing depedency: pdftk")?
        .parent()
        .ok_or(())
        .map_err(|_| "Missing depedency: pdftk")?
        .to_path_buf();

    let profile = Profile::default().with_path(install_path.clone());
    profile.write_to_file().await?;

    let api_key_path = config_dir.join("openai-api-key");
    let mut api_key_file = File::create(&api_key_path)?;
    write!(api_key_file, "{}", api_key_string)?;

    let plist_filename = "com.olekli.mrdocument.plist";
    let plist_content = format!(
        r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
   "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.olekli.mrdocument</string>

    <key>ProgramArguments</key>
    <array>
        <string>{mrdocument_path}</string>
    </array>

    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>{pdftoppm_path}:{pdftk_path}:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>

    <key>StandardErrorPath</key>
    <string>{log_path}</string>

    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>
"#,
        mrdocument_path = home_dir.join(".cargo/bin/mrdocument").display(),
        log_path = profile.paths.path.join("log").display(),
        pdftoppm_path = pdftoppm_path.display(),
        pdftk_path = pdftk_path.display(),
    );

    let plist_path = install_path.join(plist_filename);
    let mut plist_file = File::create(&plist_path)?;
    plist_file.write_all(plist_content.as_bytes())?;

    let launch_agents_dir = home_dir.join("Library/LaunchAgents");
    if !launch_agents_dir.exists() {
        fs::create_dir_all(&launch_agents_dir)?;
    }
    let destination_plist_path = launch_agents_dir.join(plist_filename);
    fs::rename(&plist_path, &destination_plist_path)?;

    #[cfg(target_family = "unix")]
    {
        use std::os::unix::fs::PermissionsExt;
        fs::set_permissions(&destination_plist_path, fs::Permissions::from_mode(0o644))?;
    }

    let output = Command::new("launchctl")
        .arg("load")
        .arg(&destination_plist_path)
        .output()?;

    if !output.status.success() {
        return Err(format!(
            "Failed to load launchd service: {}",
            String::from_utf8_lossy(&output.stderr)
        )
        .into());
    }

    println!("Service com.olekli.mrdocument has been set up and launched.");

    Ok(())
}

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    #[arg(long)]
    install_path: String,
    #[arg(long)]
    api_key_path: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Cli::parse();
    setup_mrdocument_service(args.install_path.into(), args.api_key_path.into()).await
}