1use serde::{Deserialize, Serialize};
2use std::{
3 path::PathBuf,
4 process::{Command, Stdio},
5};
6use tokio::fs;
7
8use crate::{Error, InstallArgs, Result, desktops_dir, icons_dir};
9
10#[derive(Debug, Serialize, Deserialize)]
11pub struct AppImage {
12 pub file_path: PathBuf,
13 pub executable: String,
14 pub source: Source,
15}
16
17#[derive(Debug, Serialize, Deserialize)]
18pub struct Source {
19 pub identifier: String,
20 pub meta: SourceMetadata,
21}
22
23#[derive(Debug, Serialize, Deserialize)]
24pub struct SourceMetadata {
25 pub url: String,
26}
27
28impl AppImage {
29 pub fn new(options: &InstallArgs) -> Self {
30 Self {
31 file_path: PathBuf::new(),
32 executable: options
33 .executable
34 .as_ref()
35 .unwrap_or(&options.appname)
36 .to_string(),
37 source: Source {
38 identifier: if options.github {
39 "git.github".to_string()
40 } else {
41 "raw_url".to_string()
42 },
43 meta: SourceMetadata {
44 url: options.from.clone(),
45 },
46 },
47 }
48 }
49 async fn extract_assets(&self) -> Result<PathBuf> {
50 let temp_dir = std::env::temp_dir().join("zap-rs");
51
52 fs::create_dir_all(&temp_dir).await?;
53
54 Command::new(&self.file_path)
56 .arg("--appimage-extract")
57 .arg("*.desktop")
58 .current_dir(&temp_dir)
59 .stdout(Stdio::null())
60 .spawn()?
61 .wait()?;
62 Command::new(&self.file_path)
63 .arg("--appimage-extract")
64 .arg("usr/share/applications/*.desktop")
65 .current_dir(&temp_dir)
66 .stdout(Stdio::null())
67 .spawn()?
68 .wait()?;
69
70 Command::new(&self.file_path)
72 .arg("--appimage-extract")
73 .arg("usr/share/icons/hicolor/*/apps/*.png")
74 .current_dir(&temp_dir)
75 .stdout(Stdio::null())
76 .spawn()?
77 .wait()?;
78
79 Ok(temp_dir)
80 }
81 async fn fix_desktop(&self, desktop_file_path: &PathBuf, icon_found: bool) -> Result<()> {
82 let file_content = fs::read_to_string(&desktop_file_path).await?;
83
84 let appimage_path = self.file_path.to_str().ok_or(Error::InvalidPath)?;
85
86 let icon_path = icons_dir()?
87 .join(format!("{}.png", self.executable))
88 .to_str()
89 .ok_or(Error::InvalidPath)?
90 .to_string();
91
92 let fixed_file_content: Vec<String> = file_content
93 .lines()
94 .map(|line| {
95 if line.contains("Exec=") {
96 if let Some(exec_line) = line.split_once(" ") {
97 if let Some(exec_arg) = exec_line.0.split_once("=") {
98 format!("{}={} {}", exec_arg.0, appimage_path, exec_line.1)
99 } else {
100 line.to_string()
101 }
102 } else if let Some(exec_arg) = line.split_once("=") {
103 format!("{}={}", exec_arg.0, appimage_path)
104 } else {
105 line.to_string()
106 }
107 } else if line.contains("Icon=") && icon_found {
108 if let Some(exec_arg) = line.split_once("=") {
109 format!("{}={}", exec_arg.0, icon_path)
110 } else {
111 line.to_string()
112 }
113 } else {
114 line.to_string()
115 }
116 })
117 .collect();
118
119 fs::write(desktop_file_path, fixed_file_content.join("\n")).await?;
120
121 Ok(())
122 }
123 pub async fn integrate_desktop(&self) -> Result<()> {
124 let temp_dir = self.extract_assets().await?;
125 let squashfs = &temp_dir.join("squashfs-root");
126
127 fs::create_dir_all(desktops_dir()?).await?;
128 fs::create_dir_all(icons_dir()?).await?;
129 fs::create_dir_all(
130 PathBuf::from(std::env::var("HOME")?).join(".local/share/applications/"),
131 )
132 .await?;
133
134 let icon_path = icons_dir()?.join(format!("{}.png", self.executable));
135 let desktop_file_paths = (
136 desktops_dir()?.join(format!("{}.desktop", self.executable)),
137 PathBuf::from(std::env::var("HOME")?).join(format!(
138 ".local/share/applications/{}.desktop",
139 self.executable
140 )),
141 );
142
143 let icon_resolutions = [
144 "1024", "720", "512", "256", "192", "128", "96", "72", "64", "48", "36", "32", "24",
145 "22", "16",
146 ];
147
148 let mut icon_found = false;
149
150 for res in icon_resolutions {
151 let icon_dir = squashfs.join(format!("usr/share/icons/hicolor/{}x{}/apps", res, res));
152
153 if fs::try_exists(&icon_dir).await? {
154 let mut squashfs_icon_entries = fs::read_dir(&icon_dir).await?;
155 while let Some(entry) = squashfs_icon_entries.next_entry().await? {
156 if entry.path().extension() == Some("png".as_ref()) {
157 fs::copy(entry.path(), &icon_path).await?;
158 icon_found = true;
159 break;
160 }
161 }
162 if icon_found {
163 break;
164 }
165 }
166 }
167
168 let mut squashfs_entries = fs::read_dir(&squashfs).await?;
169 while let Some(entry) = squashfs_entries.next_entry().await? {
170 if entry.path().extension() == Some("desktop".as_ref()) {
171 fs::copy(fs::canonicalize(entry.path()).await?, &desktop_file_paths.0).await?;
172
173 self.fix_desktop(&desktop_file_paths.0, icon_found).await?;
174
175 fs::copy(&desktop_file_paths.0, &desktop_file_paths.1).await?;
176 }
177 }
178
179 fs::remove_dir_all(temp_dir).await?;
181
182 Ok(())
183 }
184}