1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
//! Linux tool management (winetricks, cabextract)
//!
//! Handles downloading and managing Linux CLI tools.
//! Tools are stored in ~/.local/share/fluorine/bin/ for Fluorine Manager.
use std::error::Error;
use std::fs;
use std::io::Read;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::process::Command;
use crate::logging::{log_error, log_info, log_warning};
// ============================================================================
// NaK Bin Directory (~/.local/share/fluorine/bin/)
// ============================================================================
/// Get the tool bin directory path (~/.local/share/fluorine/bin/)
/// This is accessible from both native and Flatpak environments.
pub fn get_nak_bin_path() -> PathBuf {
crate::paths::data_dir().join("bin")
}
/// Check if a command exists (either in system PATH or tool bin)
pub fn check_command_available(cmd: &str) -> bool {
// Check system PATH first
if Command::new("which")
.arg(cmd)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
{
return true;
}
// Check tool bin directory
let nak_bin = get_nak_bin_path().join(cmd);
nak_bin.exists()
}
// ============================================================================
// Winetricks
// ============================================================================
const WINETRICKS_URL: &str =
"https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks";
/// Get the path to winetricks (without downloading)
pub fn get_winetricks_path() -> PathBuf {
get_nak_bin_path().join("winetricks")
}
/// Ensures winetricks is downloaded and up-to-date.
pub fn ensure_winetricks() -> Result<PathBuf, Box<dyn Error>> {
let bin_dir = get_nak_bin_path();
let winetricks_path = bin_dir.join("winetricks");
fs::create_dir_all(&bin_dir)?;
log_info("Checking for winetricks updates...");
match ureq::get(WINETRICKS_URL).call() {
Ok(response) => {
let mut new_content = Vec::new();
response.into_reader().read_to_end(&mut new_content)?;
let should_update = if winetricks_path.exists() {
let existing = fs::read(&winetricks_path).unwrap_or_default();
existing != new_content
} else {
true
};
if should_update {
fs::write(&winetricks_path, &new_content)?;
let mut perms = fs::metadata(&winetricks_path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&winetricks_path, perms)?;
if winetricks_path.exists() {
log_info("Winetricks updated to latest version");
} else {
log_info(&format!("Winetricks downloaded to {:?}", winetricks_path));
}
}
}
Err(e) => {
if winetricks_path.exists() {
log_warning(&format!("Failed to check winetricks updates: {}", e));
} else {
return Err(format!("Failed to download winetricks: {}", e).into());
}
}
}
Ok(winetricks_path)
}
// ============================================================================
// Cabextract (required by winetricks for DirectX cabs)
// ============================================================================
const CABEXTRACT_URL: &str =
"https://github.com/SulfurNitride/NaK/releases/download/Cabextract/cabextract-linux-x86_64.zip";
/// Ensures cabextract is available in our bin directory.
///
/// Always downloads to ~/.local/share/fluorine/bin/ because winetricks runs
/// inside pressure-vessel where system binaries under /usr are not visible.
pub fn ensure_cabextract() -> Result<PathBuf, Box<dyn Error>> {
// Check if we already downloaded it to our bin dir
let bin_dir = get_nak_bin_path();
let cabextract_path = bin_dir.join("cabextract");
if cabextract_path.exists() {
return Ok(cabextract_path);
}
// Download cabextract zip — system copy is unusable inside pressure-vessel
log_info("Downloading cabextract for use inside container...");
fs::create_dir_all(&bin_dir)?;
let response = ureq::get(CABEXTRACT_URL).call().map_err(|e| {
format!(
"Failed to download cabextract: {}. Please install cabextract manually.",
e
)
})?;
let zip_path = bin_dir.join("cabextract.zip");
let mut zip_file = fs::File::create(&zip_path)?;
std::io::copy(&mut response.into_reader(), &mut zip_file)?;
let status = Command::new("unzip")
.arg("-o")
.arg(&zip_path)
.arg("-d")
.arg(&bin_dir)
.status()?;
if !status.success() {
let _ = Command::new("python3")
.arg("-c")
.arg(format!(
"import zipfile; zipfile.ZipFile('{}').extractall('{}')",
zip_path.display(),
bin_dir.display()
))
.status();
}
let _ = fs::remove_file(&zip_path);
if cabextract_path.exists() {
let mut perms = fs::metadata(&cabextract_path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&cabextract_path, perms)?;
log_info(&format!("cabextract downloaded to {:?}", cabextract_path));
Ok(cabextract_path)
} else {
log_error("Failed to extract cabextract from zip");
Err("Failed to extract cabextract from zip".into())
}
}
|