zap_rs/
appimage.rs

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        // Extract desktop file
55        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        // Extract icon
71        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        // Clean up
180        fs::remove_dir_all(temp_dir).await?;
181
182        Ok(())
183    }
184}