Skip to content

Commit 4d4be62

Browse files
authored
Update app.rs
1 parent b31c32d commit 4d4be62

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed

src/app.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,191 @@
1+
use anyhow::{Context, Result};
2+
use tokio::process::Command as AsyncCommand;
13

4+
#[derive(Clone)]
5+
pub struct Package {
6+
pub name: String,
7+
pub source: Source,
8+
pub description: String,
9+
}
10+
11+
#[derive(Clone, PartialEq)]
12+
pub enum Source {
13+
Apt,
14+
Snap,
15+
Flatpak,
16+
All,
17+
}
18+
19+
impl Source {
20+
pub fn as_str(&self) -> &'static str {
21+
match self {
22+
Source::Apt => "APT",
23+
Source::Snap => "SNAP",
24+
Source::Flatpak => "FLATPAK",
25+
Source::All => "ALL",
26+
}
27+
}
28+
}
29+
30+
pub enum InputMode {
31+
Normal,
32+
Editing,
33+
}
34+
35+
pub struct App {
36+
pub input: String,
37+
pub input_mode: InputMode,
38+
pub packages: Vec<Package>,
39+
pub package_list_state: ratatui::widgets::ListState,
40+
pub selected_source: Source,
41+
pub message: String,
42+
pub dot_count: usize,
43+
}
44+
45+
impl App {
46+
pub fn new() -> App {
47+
App {
48+
input: String::new(),
49+
input_mode: InputMode::Normal,
50+
packages: Vec::new(),
51+
package_list_state: ratatui::widgets::ListState::default(),
52+
selected_source: Source::All,
53+
message: String::new(),
54+
dot_count: 0,
55+
}
56+
}
57+
58+
pub fn get_filtered_packages(&self) -> Vec<Package> {
59+
if self.selected_source == Source::All {
60+
self.packages.clone()
61+
} else {
62+
self.packages
63+
.iter()
64+
.filter(|p| p.source == self.selected_source)
65+
.cloned()
66+
.collect()
67+
}
68+
}
69+
}
70+
71+
pub async fn search_packages(input: String) -> Result<Vec<Package>> {
72+
let mut packages = Vec::new();
73+
// Search APT
74+
let apt_output = AsyncCommand::new("apt-cache")
75+
.arg("search")
76+
.arg("--names-only")
77+
.arg(&input)
78+
.output()
79+
.await
80+
.context("Failed to execute apt-cache search")?;
81+
if apt_output.status.success() {
82+
let apt_str = String::from_utf8_lossy(&apt_output.stdout);
83+
for line in apt_str.lines() {
84+
let trimmed = line.trim();
85+
if !trimmed.is_empty() {
86+
if let Some((name, desc)) = trimmed.split_once(" - ") {
87+
packages.push(Package {
88+
name: name.trim().to_string(),
89+
source: Source::Apt,
90+
description: desc.trim().to_string(),
91+
});
92+
}
93+
}
94+
}
95+
}
96+
// Search Snap
97+
let snap_output = AsyncCommand::new("snap")
98+
.arg("find")
99+
.arg(&input)
100+
.output()
101+
.await
102+
.context("Failed to execute snap find")?;
103+
if snap_output.status.success() {
104+
let snap_str = String::from_utf8_lossy(&snap_output.stdout);
105+
let lines: Vec<&str> = snap_str.lines().collect();
106+
let start = if !lines.is_empty() && lines[0].contains("Name") { 1 } else { 0 };
107+
for line in &lines[start..] {
108+
let trimmed = line.trim();
109+
if !trimmed.is_empty() {
110+
let parts: Vec<&str> = trimmed.split_whitespace().collect();
111+
if parts.len() >= 5 {
112+
let name = parts[0].to_string();
113+
let description = parts[4..].join(" ");
114+
packages.push(Package {
115+
name,
116+
source: Source::Snap,
117+
description,
118+
});
119+
}
120+
}
121+
}
122+
}
123+
// Search Flatpak
124+
let flatpak_output = AsyncCommand::new("flatpak")
125+
.arg("search")
126+
.arg(&input)
127+
.output()
128+
.await
129+
.context("Failed to execute flatpak search")?;
130+
if flatpak_output.status.success() {
131+
let flatpak_str = String::from_utf8_lossy(&flatpak_output.stdout);
132+
let lines: Vec<&str> = flatpak_str.lines().collect();
133+
let start = if !lines.is_empty() && lines[0].contains("Name") { 1 } else { 0 };
134+
for line in &lines[start..] {
135+
let trimmed = line.trim();
136+
if !trimmed.is_empty() {
137+
let parts: Vec<&str> = trimmed.split('\t').collect();
138+
if parts.len() >= 3 {
139+
let name = parts[2].to_string(); // Application ID
140+
let description = format!("{} - {}", parts.get(0).unwrap_or(&""), parts.get(1).unwrap_or(&""));
141+
packages.push(Package {
142+
name,
143+
source: Source::Flatpak,
144+
description,
145+
});
146+
}
147+
}
148+
}
149+
}
150+
Ok(packages)
151+
}
152+
153+
pub async fn install_package(pkg: Package) -> Result<String> {
154+
let (cmd, args) = match pkg.source {
155+
Source::Apt => ("apt", vec!["install", "-y", &pkg.name]),
156+
Source::Snap => ("snap", vec!["install", &pkg.name]),
157+
Source::Flatpak => ("flatpak", vec!["install", "--assumeyes", &pkg.name]),
158+
_ => return Ok("Invalid source".to_string()),
159+
};
160+
let output = AsyncCommand::new("sudo")
161+
.arg(cmd)
162+
.args(&args)
163+
.output()
164+
.await
165+
.context("Failed to install package")?;
166+
if output.status.success() {
167+
Ok(format!("Installed {} from {}", pkg.name, pkg.source.as_str()))
168+
} else {
169+
Err(anyhow::anyhow!("Failed to install {} from {}: {}", pkg.name, pkg.source.as_str(), String::from_utf8_lossy(&output.stderr)))
170+
}
171+
}
172+
173+
pub async fn remove_package(pkg: Package) -> Result<String> {
174+
let (cmd, args) = match pkg.source {
175+
Source::Apt => ("apt", vec!["remove", "-y", &pkg.name]),
176+
Source::Snap => ("snap", vec!["remove", &pkg.name]),
177+
Source::Flatpak => ("flatpak", vec!["uninstall", "--assumeyes", &pkg.name]),
178+
_ => return Ok("Invalid source".to_string()),
179+
};
180+
let output = AsyncCommand::new("sudo")
181+
.arg(cmd)
182+
.args(&args)
183+
.output()
184+
.await
185+
.context("Failed to remove package")?;
186+
if output.status.success() {
187+
Ok(format!("Removed {} from {}", pkg.name, pkg.source.as_str()))
188+
} else {
189+
Err(anyhow::anyhow!("Failed to remove {} from {}: {}", pkg.name, pkg.source.as_str(), String::from_utf8_lossy(&output.stderr)))
190+
}
191+
}

0 commit comments

Comments
 (0)