use crate::error::{AppError, Result};
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
const MARKER_START: &str = "# TR-300 Machine Report";
const MARKER_END: &str = "# End TR-300";
const POWERSHELL_ADDITIONS: &str = r#"# TR-300 Machine Report
Set-Alias -Name report -Value tr300
# Auto-run on interactive shell
if ($Host.Name -eq 'ConsoleHost') {
tr300 --fast
}
# End TR-300"#;
pub fn install_path() -> PathBuf {
if let Some(local_app_data) = dirs::data_local_dir() {
return local_app_data
.join("Programs")
.join("tr300")
.join("tr300.exe");
}
if let Ok(userprofile) = std::env::var("USERPROFILE") {
return PathBuf::from(userprofile)
.join("AppData")
.join("Local")
.join("Programs")
.join("tr300")
.join("tr300.exe");
}
PathBuf::from(r"C:\Program Files\tr300\tr300.exe")
}
fn get_powershell_profile() -> Option<PathBuf> {
if let Ok(output) = Command::new("powershell")
.args(["-NoProfile", "-Command", "$PROFILE"])
.output()
{
let profile_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !profile_path.is_empty() {
return Some(PathBuf::from(profile_path));
}
}
if let Some(docs) = dirs::document_dir() {
return Some(
docs.join("WindowsPowerShell")
.join("Microsoft.PowerShell_profile.ps1"),
);
}
None
}
pub fn install() -> Result<()> {
let profile_path = get_powershell_profile()
.ok_or_else(|| AppError::platform("Could not determine PowerShell profile path"))?;
if let Some(parent) = profile_path.parent() {
if !parent.exists() {
fs::create_dir_all(parent).map_err(|e| {
AppError::platform(format!("Failed to create profile directory: {}", e))
})?;
}
}
let existing_content = if profile_path.exists() {
fs::read_to_string(&profile_path)
.map_err(|e| AppError::platform(format!("Failed to read profile: {}", e)))?
} else {
String::new()
};
let cleaned_content = remove_tr300_block(&existing_content);
let new_content = if cleaned_content.trim().is_empty() {
POWERSHELL_ADDITIONS.to_string()
} else {
format!(
"{}\r\n\r\n{}",
cleaned_content.trim_end(),
POWERSHELL_ADDITIONS
)
};
fs::write(&profile_path, new_content)
.map_err(|e| AppError::platform(format!("Failed to write profile: {}", e)))?;
println!("Modified PowerShell profile:");
println!(" - {}", profile_path.display());
Ok(())
}
pub fn uninstall() -> Result<()> {
let profile_path = get_powershell_profile()
.ok_or_else(|| AppError::platform("Could not determine PowerShell profile path"))?;
if !profile_path.exists() {
println!("No PowerShell profile found.");
return Ok(());
}
let content = fs::read_to_string(&profile_path)
.map_err(|e| AppError::platform(format!("Failed to read profile: {}", e)))?;
if !content.contains(MARKER_START) {
println!("No TR-300 configuration found in PowerShell profile.");
return Ok(());
}
let mut new_lines = Vec::new();
let mut in_tr300_block = false;
for line in content.lines() {
if line.contains(MARKER_START) {
in_tr300_block = true;
continue;
}
if line.contains(MARKER_END) {
in_tr300_block = false;
continue;
}
if !in_tr300_block {
new_lines.push(line);
}
}
while new_lines.last().map(|s| s.is_empty()).unwrap_or(false) {
new_lines.pop();
}
let new_content = if new_lines.is_empty() {
String::new()
} else {
new_lines.join("\r\n") + "\r\n"
};
fs::write(&profile_path, new_content)
.map_err(|e| AppError::platform(format!("Failed to write profile: {}", e)))?;
println!("Cleaned PowerShell profile:");
println!(" - {}", profile_path.display());
Ok(())
}
fn remove_tr300_block(content: &str) -> String {
let mut lines: Vec<&str> = content.lines().collect();
lines = remove_delimited_block(&lines, MARKER_START, MARKER_END);
let mut result = Vec::new();
let mut prev_blank = false;
for line in lines {
let is_blank = line.trim().is_empty();
if is_blank && prev_blank {
continue;
}
result.push(line);
prev_blank = is_blank;
}
while result.last().map(|s| s.trim().is_empty()).unwrap_or(false) {
result.pop();
}
if result.is_empty() {
String::new()
} else {
result.join("\r\n") + "\r\n"
}
}
fn remove_delimited_block<'a>(lines: &[&'a str], start: &str, end: &str) -> Vec<&'a str> {
let mut result = Vec::new();
let mut in_block = false;
for line in lines {
if line.contains(start) {
in_block = true;
continue;
}
if line.contains(end) {
in_block = false;
continue;
}
if !in_block {
result.push(*line);
}
}
result
}
pub fn find_binary_location() -> Option<PathBuf> {
if let Ok(exe_path) = env::current_exe() {
if exe_path.exists() {
return Some(exe_path);
}
}
let path = install_path();
if path.exists() {
return Some(path);
}
None
}
pub fn get_binary_parent_dir(binary_path: &std::path::Path) -> Option<PathBuf> {
binary_path.parent().map(|p| p.to_path_buf())
}
pub fn remove_binary(binary_path: &PathBuf) -> Result<()> {
if !binary_path.exists() {
return Ok(());
}
fs::remove_file(binary_path).map_err(|e| {
AppError::platform(format!(
"Failed to remove binary {}: {}",
binary_path.display(),
e
))
})?;
println!("Removed binary: {}", binary_path.display());
Ok(())
}
pub fn remove_empty_parent_dir(dir: &PathBuf) -> Result<()> {
if !dir.exists() {
return Ok(());
}
let is_empty = match fs::read_dir(dir) {
Ok(mut entries) => entries.next().is_none(),
Err(_) => false,
};
if is_empty {
fs::remove_dir(dir).map_err(|e| {
AppError::platform(format!(
"Failed to remove directory {}: {}",
dir.display(),
e
))
})?;
println!("Removed empty directory: {}", dir.display());
}
Ok(())
}
pub fn uninstall_complete() -> Result<()> {
uninstall()?;
if let Some(binary_path) = find_binary_location() {
let parent_dir = get_binary_parent_dir(&binary_path);
remove_binary(&binary_path)?;
if let Some(dir) = parent_dir {
if dir.to_string_lossy().to_lowercase().contains("tr300") {
let _ = remove_empty_parent_dir(&dir);
}
}
}
Ok(())
}