i2pd-launch 0.0.5

Deletes current router/interface state and launches i2pd
/*
Delete state and launch i2pd installed on Windows using scoop.
Authored by Radim Kolar 2024

This is free and unencumbered software released into the public domain.
SPDX-License-Identifier: Unlicense OR CC0-1.0
*/

#![forbid(unsafe_code)]
#![allow(non_snake_case)]

use windows_sys::Win32::UI::Shell::FOLDERID_ProgramData;
use windows_sys::Win32::UI::Shell::FOLDERID_Profile;
use windows_sys::core::GUID;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

/** start i2pd */

#[cfg(not(target_os = "windows"))] 
fn main() -> ExitCode { 
   println!("This program is supported only on Windows.");
   ExitCode::From(3)
}

/**
  Clean state and starts i2pd

  ## Return codes
  - 0 Success
  - 1 No scoop i2pd installation detected
  - 2 i2pd Executable not found
  - 3 This program supported only on Windows
*/
fn main() -> ExitCode {
    let global_i2p = dirs_sys::known_folder(FOLDERID_ProgramData).map_or(false, |f| detect_scoop_i2p(&f));
    let user_i2p = dirs_sys::known_folder(FOLDERID_Profile).map_or(false, |f| detect_scoop_i2p(&f));
    if !( global_i2p || user_i2p ) {
       println!("no scoop i2pd install detected");
       ExitCode::from(1)
    } else {
       println!("scoop i2pd installation detected: global {}, user {}.", global_i2p, user_i2p);
       let workdir = { if user_i2p { get_scoop_i2p_dir( FOLDERID_Profile ) } 
                       else { get_scoop_i2p_dir( FOLDERID_ProgramData ) }
       };
       // Check if I2PD_EXE exists becaue it might be deleted by antivirus
       let exefile = { let mut p = PathBuf::from(&workdir);p.push(I2PD_EXE); p };
       if !exefile.exists() {
          // Handle the case when the main executable is missing
          println!("Error: {} not exists / might have been deleted by antivirus.", I2PD_EXE);
          return ExitCode::from(2);
       }

       // Change to the working directory
       std::env::set_current_dir(&workdir).expect("Failed to change directory to scoop i2pd installation");
       // Delete files listed in TO_DELETE, retry errors
       let mut TO_RETRY = Vec::new();
       for file in TO_DELETE.iter() { 
          let file_path = workdir.join(file);
          if file_path.exists() {
             let rc = std::fs::remove_file(&file_path);
             if rc.is_err() {
                eprintln!("Can not delete {}, will retry.", file_path.display());
                TO_RETRY.push(file_path);
             }
          }
       }
       // Retry to delete files
       if ! TO_RETRY.is_empty() {
          // Pause execution for 5 seconds
          std::thread::sleep(std::time::Duration::from_secs(5));
          for file_path in TO_RETRY {
             if file_path.exists() {
                let rc = std::fs::remove_file(&file_path);
                if rc.is_err() {
                   eprintln!("Still can not delete {}", file_path.display());
                }
             }
          }
       }
       // Launch I2PD_EXE without arguments
       println!("starting {}", I2PD_EXE);
       std::process::Command::new(I2PD_EXE).spawn().expect("Failed to launch i2pd");
       ExitCode::SUCCESS
    }
}

fn get_scoop_i2p_dir(folder: GUID) -> PathBuf {
   let mut f = dirs_sys::known_folder(folder).unwrap();
   f.push(SCOOP_I2P_INSTALL);
   f
}

fn detect_scoop_i2p(search: impl AsRef<Path>) -> bool {
   #[cfg(debug_assertions)]
   println!("Testing {}", search.as_ref().display());
   let mut p = PathBuf::from(search.as_ref());
   if p.is_dir() {
      p.push(SCOOP_I2P_INSTALL);
      #[cfg(debug_assertions)]
      println!("Testing2 {}", p.display());
      p.is_dir()
   } else {
      false
   }
}

const TO_DELETE: [&str; 5] = [ "i2pd.log", "ntcp2.keys", "ssu2.keys", "router.info", "router.keys" ];
const SCOOP_I2P_INSTALL: &str = "scoop\\apps\\i2pd\\current";
const I2PD_EXE: &str = "i2pd.exe";