webapp-rs 0.1.0

A simple CLI tool to create webapps (only support firefox and linux for now.
use std::fs;
use std::path::PathBuf;

use anyhow::{Context, Result};

use crate::{
    browsers::WebBrowser,
    cli::{CreateArgs, EditArgs},
    icons,
    os::OperatingSystem,
    utils::{generate_codename, normalize_url},
};

mod desktop_file;
use desktop_file::DesktopFile;

pub fn get_local_path() -> Result<PathBuf> {
    dirs::data_local_dir().ok_or_else(|| anyhow::anyhow!("No data local directory found"))
}

pub fn create_shortcut(
    _os: &OperatingSystem,
    browser: &WebBrowser,
    args: CreateArgs,
) -> Result<String> {
    let codename = generate_codename(&args.name)?;
    let profiles_root = browser.get_profiles_root()?;
    let profile_dir = profiles_root.join(&codename);
    let apps_dir = get_local_path()?.join("applications");
    fs::create_dir_all(&apps_dir)?;
    let desktop_file_path = apps_dir.join(format!("WebApp-{}.desktop", codename));
    let url = normalize_url(&args.url)?;

    // Résoudre l'icône (copier/télécharger si nécessaire, ou fetch automatique)
    let resolved_icon = match &args.icon {
        Some(icon) => icons::resolve_icon(&codename, icon).context("Failed to resolve icon")?,
        None => icons::fetch_favicon(&url, &codename).context("Failed to fetch favicon")?,
    };

    // Créer le profil Firefox
    browser
        .create_profile(&profile_dir, args.no_ublock)
        .context("Failed to create browser profile")?;

    // Générer la commande d'exécution
    let exec = browser.format_exec_command(
        &profile_dir,
        &url,
        &resolved_icon,
        args.private_window,
        args.custom_params.as_deref(),
    );

    // Générer le fichier desktop
    DesktopFile::create(
        &desktop_file_path,
        &args.name,
        &url,
        &resolved_icon,
        &args.category,
        &codename,
        &exec,
        args.private_window,
    )
    .context("Failed to create desktop file")?;

    println!("✓ Created webapp: {} (codename: {})", args.name, codename);
    println!("  Desktop file: {:?}", desktop_file_path);
    println!("  Profile: {:?}", profile_dir);

    Ok(codename)
}

pub fn list_webapps(_os: &OperatingSystem) -> Result<()> {
    let apps_dir = get_local_path()?.join("applications");
    let entries = fs::read_dir(&apps_dir).context("Failed to read applications directory")?;

    println!("Webapps:");
    let mut count = 0;

    for entry in entries {
        let entry = entry?;
        let path = entry.path();

        if let Some(name) = path.file_name() {
            let name = name.to_string_lossy();
            if name.starts_with("WebApp-") && name.ends_with(".desktop") {
                count += 1;

                // Extraire le codename
                let codename = name
                    .strip_prefix("WebApp-")
                    .unwrap()
                    .strip_suffix(".desktop")
                    .unwrap();

                // Lire les infos du fichier desktop
                if let Ok(content) = fs::read_to_string(&path) {
                    let app_name = content
                        .lines()
                        .find(|l| l.starts_with("Name="))
                        .and_then(|l| l.strip_prefix("Name="))
                        .unwrap_or("Unknown");

                    let app_url = content
                        .lines()
                        .find(|l| l.starts_with("X-WebApp-URL="))
                        .and_then(|l| l.strip_prefix("X-WebApp-URL="))
                        .unwrap_or("Unknown");

                    println!("  - {} ({})", app_name, codename);
                    println!("    Codename: {}", codename);
                    println!("    URL: {}", app_url);
                    println!("    File: {:?}", path);
                    println!();
                } else {
                    println!("  - {}", name);
                }
            }
        }
    }

    if count == 0 {
        println!("  No webapps found.");
    } else {
        println!("Total: {} webapp(s)", count);
    }

    Ok(())
}

pub fn run_webapp(_os: &OperatingSystem, codename: &str) -> Result<()> {
    use std::process::Command;

    let apps_dir = get_local_path()?.join("applications");
    let desktop_file = apps_dir.join(format!("WebApp-{}.desktop", codename));

    if !desktop_file.exists() {
        anyhow::bail!("Webapp not found: {}", codename);
    }

    // Lire et exécuter la commande Exec
    let exec_cmd = DesktopFile::get_exec_command(&desktop_file)?;

    Command::new("sh")
        .arg("-c")
        .arg(&exec_cmd)
        .spawn()
        .context("Failed to launch webapp")?;

    println!("✓ Launched webapp: {}", codename);
    Ok(())
}

pub fn edit_webapp(
    _os: &OperatingSystem,
    _browser: &WebBrowser,
    codename: &str,
    args: EditArgs,
) -> Result<()> {
    let apps_dir = get_local_path()?.join("applications");
    let desktop_file = apps_dir.join(format!("WebApp-{}.desktop", codename));

    if !desktop_file.exists() {
        anyhow::bail!("Webapp not found: {}", codename);
    }

    // Résoudre l'icône si fournie
    let edit_args = if let Some(icon) = &args.icon {
        let resolved_icon =
            icons::resolve_icon(codename, icon).context("Failed to resolve icon")?;
        EditArgs {
            icon: Some(resolved_icon),
            private_window: args.private_window,
            name: args.name.clone(),
            url: args.url.clone(),
        }
    } else {
        args
    };

    DesktopFile::edit(&desktop_file, edit_args).context("Failed to edit desktop file")?;

    println!("✓ Edited webapp: {}", codename);
    Ok(())
}

pub fn update_webapps(browser: &WebBrowser) -> Result<()> {
    let profiles_root = browser.get_profiles_root()?;
    let entries = fs::read_dir(&profiles_root).context("Failed to read profiles directory")?;

    let mut count = 0;

    for entry in entries {
        let entry = entry?;
        let profile_dir = entry.path();

        if profile_dir.is_dir() {
            browser
                .update_profile(&profile_dir)
                .context("Failed to update profile")?;
            count += 1;
            println!(
                "✓ Updated: {}",
                profile_dir.file_name().unwrap().to_string_lossy()
            );
        }
    }

    if count == 0 {
        println!("No webapp profiles found.");
    } else {
        println!("✓ Updated {} profile(s)", count);
    }

    Ok(())
}

pub fn delete_webapp(_os: &OperatingSystem, browser: &WebBrowser, codename: &str) -> Result<()> {
    let profiles_root = browser.get_profiles_root()?;
    let apps_dir = get_local_path()?.join("applications");
    let profile_dir = profiles_root.join(codename);
    let desktop_file = apps_dir.join(format!("WebApp-{}.desktop", codename));

    let mut deleted = false;

    if profile_dir.exists() {
        browser.delete_profile(&profile_dir)?;
        println!("✓ Deleted profile: {:?}", profile_dir);
        deleted = true;
    }

    if desktop_file.exists() {
        DesktopFile::delete(&desktop_file)?;
        println!("✓ Deleted desktop file: {:?}", desktop_file);
        deleted = true;
    }

    // Supprimer l'icône personnalisée si elle existe
    icons::delete_icon(codename)?;

    if !deleted {
        anyhow::bail!("Webapp not found: {}", codename);
    }

    println!("✓ Deleted webapp: {}", codename);
    Ok(())
}