dadk_user/executor/
source.rs

1use log::info;
2use regex::Regex;
3use reqwest::Url;
4use serde::{Deserialize, Serialize};
5use std::os::unix::fs::PermissionsExt;
6use std::{
7    fs::File,
8    path::PathBuf,
9    process::{Command, Stdio},
10};
11use zip::ZipArchive;
12
13use crate::utils::{file::FileUtils, stdio::StdioUtils};
14
15use super::cache::CacheDir;
16
17use anyhow::{Error, Result};
18
19/// # Git源
20///
21/// 从Git仓库获取源码
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
23pub struct GitSource {
24    /// Git仓库地址
25    url: String,
26    /// 分支(可选,如果为空,则拉取master)branch和revision只能同时指定一个
27    branch: Option<String>,
28    /// 特定的提交的hash值(可选,如果为空,则拉取branch的最新提交)
29    revision: Option<String>,
30}
31
32impl GitSource {
33    pub fn new(url: String, branch: Option<String>, revision: Option<String>) -> Self {
34        Self {
35            url,
36            branch,
37            revision,
38        }
39    }
40    /// # 验证参数合法性
41    ///
42    /// 仅进行形式校验,不会检查Git仓库是否存在,以及分支是否存在、是否有权限访问等
43    pub fn validate(&mut self) -> Result<()> {
44        if self.url.is_empty() {
45            return Err(Error::msg("url is empty"));
46        }
47        // branch和revision不能同时为空
48        if self.branch.is_none() && self.revision.is_none() {
49            self.branch = Some("master".to_string());
50        }
51        // branch和revision只能同时指定一个
52        if self.branch.is_some() && self.revision.is_some() {
53            return Err(Error::msg("branch and revision are both specified"));
54        }
55
56        if self.branch.is_some() {
57            if self.branch.as_ref().unwrap().is_empty() {
58                return Err(Error::msg("branch is empty"));
59            }
60        }
61        if self.revision.is_some() {
62            if self.revision.as_ref().unwrap().is_empty() {
63                return Err(Error::msg("revision is empty"));
64            }
65        }
66        return Ok(());
67    }
68
69    pub fn trim(&mut self) {
70        self.url = self.url.trim().to_string();
71        if let Some(branch) = &mut self.branch {
72            *branch = branch.trim().to_string();
73        }
74
75        if let Some(revision) = &mut self.revision {
76            *revision = revision.trim().to_string();
77        }
78    }
79
80    /// # 确保Git仓库已经克隆到指定目录,并且切换到指定分支/Revision
81    ///
82    /// 如果目录不存在,则会自动创建
83    ///
84    /// ## 参数
85    ///
86    /// - `target_dir` - 目标目录
87    ///
88    /// ## 返回
89    ///
90    /// - `Ok(())` - 成功
91    /// - `Err(String)` - 失败,错误信息
92    pub fn prepare(&self, target_dir: &CacheDir) -> Result<(), String> {
93        info!(
94            "Preparing git repo: {}, branch: {:?}, revision: {:?}",
95            self.url, self.branch, self.revision
96        );
97
98        target_dir.create().map_err(|e| {
99            format!(
100                "Failed to create target dir: {}, message: {e:?}",
101                target_dir.path.display()
102            )
103        })?;
104
105        if target_dir.is_empty().map_err(|e| {
106            format!(
107                "Failed to check if target dir is empty: {}, message: {e:?}",
108                target_dir.path.display()
109            )
110        })? {
111            info!("Target dir is empty, cloning repo");
112            self.clone_repo(target_dir)?;
113        }
114
115        self.checkout(target_dir)?;
116
117        self.pull(target_dir)?;
118
119        return Ok(());
120    }
121
122    fn check_repo(&self, target_dir: &CacheDir) -> Result<bool, String> {
123        let path: &PathBuf = &target_dir.path;
124        let mut cmd = Command::new("git");
125        cmd.arg("remote").arg("get-url").arg("origin");
126
127        // 设置工作目录
128        cmd.current_dir(path);
129
130        // 创建子进程,执行命令
131        let proc: std::process::Child = cmd
132            .stderr(Stdio::piped())
133            .stdout(Stdio::piped())
134            .spawn()
135            .map_err(|e| e.to_string())?;
136        let output = proc.wait_with_output().map_err(|e| e.to_string())?;
137
138        if output.status.success() {
139            let mut r = String::from_utf8(output.stdout).unwrap();
140            r.pop();
141            Ok(r == self.url)
142        } else {
143            return Err(format!(
144                "git remote get-url origin failed, status: {:?},  stderr: {:?}",
145                output.status,
146                StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
147            ));
148        }
149    }
150
151    fn set_url(&self, target_dir: &CacheDir) -> Result<(), String> {
152        let path: &PathBuf = &target_dir.path;
153        let mut cmd = Command::new("git");
154        cmd.arg("remote")
155            .arg("set-url")
156            .arg("origin")
157            .arg(self.url.as_str());
158
159        // 设置工作目录
160        cmd.current_dir(path);
161
162        // 创建子进程,执行命令
163        let proc: std::process::Child = cmd
164            .stderr(Stdio::piped())
165            .spawn()
166            .map_err(|e| e.to_string())?;
167        let output = proc.wait_with_output().map_err(|e| e.to_string())?;
168
169        if !output.status.success() {
170            return Err(format!(
171                "git remote set-url origin failed, status: {:?},  stderr: {:?}",
172                output.status,
173                StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
174            ));
175        }
176        Ok(())
177    }
178
179    fn checkout(&self, target_dir: &CacheDir) -> Result<(), String> {
180        // 确保目标目录中的仓库为所指定仓库
181        if !self.check_repo(target_dir).map_err(|e| {
182            format!(
183                "Failed to check repo: {}, message: {e:?}",
184                target_dir.path.display()
185            )
186        })? {
187            info!("Target dir isn't specified repo, change remote url");
188            self.set_url(target_dir)?;
189        }
190
191        let do_checkout = || -> Result<(), String> {
192            let mut cmd = Command::new("git");
193            cmd.current_dir(&target_dir.path);
194            cmd.arg("checkout");
195
196            if let Some(branch) = &self.branch {
197                cmd.arg(branch);
198            }
199            if let Some(revision) = &self.revision {
200                cmd.arg(revision);
201            }
202
203            // 强制切换分支,且安静模式
204            cmd.arg("-f").arg("-q");
205
206            // 创建子进程,执行命令
207            let proc: std::process::Child = cmd
208                .stderr(Stdio::piped())
209                .spawn()
210                .map_err(|e| e.to_string())?;
211            let output = proc.wait_with_output().map_err(|e| e.to_string())?;
212
213            if !output.status.success() {
214                return Err(format!(
215                    "Failed to checkout {}, message: {}",
216                    target_dir.path.display(),
217                    String::from_utf8_lossy(&output.stdout)
218                ));
219            }
220
221            let mut subcmd = Command::new("git");
222            subcmd.current_dir(&target_dir.path);
223            subcmd.arg("submodule").arg("update").arg("--remote");
224
225            //当checkout仓库的子进程结束后,启动checkout子模块的子进程
226            let subproc: std::process::Child = subcmd
227                .stderr(Stdio::piped())
228                .spawn()
229                .map_err(|e| e.to_string())?;
230            let suboutput = subproc.wait_with_output().map_err(|e| e.to_string())?;
231
232            if !suboutput.status.success() {
233                return Err(format!(
234                    "Failed to checkout submodule {}, message: {}",
235                    target_dir.path.display(),
236                    String::from_utf8_lossy(&suboutput.stdout)
237                ));
238            }
239            return Ok(());
240        };
241
242        if let Err(_) = do_checkout() {
243            // 如果切换分支失败,则尝试重新fetch
244            if self.revision.is_some() {
245                self.set_fetch_config(target_dir)?;
246                self.unshallow(target_dir)?
247            };
248
249            self.fetch_all(target_dir).ok();
250            do_checkout()?;
251        }
252
253        return Ok(());
254    }
255
256    pub fn clone_repo(&self, cache_dir: &CacheDir) -> Result<(), String> {
257        let path: &PathBuf = &cache_dir.path;
258        let mut cmd = Command::new("git");
259        cmd.arg("clone").arg(&self.url).arg(".").arg("--recursive");
260
261        if let Some(branch) = &self.branch {
262            cmd.arg("--branch").arg(branch).arg("--depth").arg("1");
263        }
264
265        // 对于克隆,如果指定了revision,则直接克隆整个仓库,稍后再切换到指定的revision
266
267        // 设置工作目录
268        cmd.current_dir(path);
269
270        // 创建子进程,执行命令
271        let proc: std::process::Child = cmd
272            .stderr(Stdio::piped())
273            .stdout(Stdio::inherit())
274            .spawn()
275            .map_err(|e| e.to_string())?;
276        let output = proc.wait_with_output().map_err(|e| e.to_string())?;
277
278        if !output.status.success() {
279            return Err(format!(
280                "clone git repo failed, status: {:?},  stderr: {:?}",
281                output.status,
282                StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
283            ));
284        }
285
286        let mut subcmd = Command::new("git");
287        subcmd
288            .arg("submodule")
289            .arg("update")
290            .arg("--init")
291            .arg("--recursive")
292            .arg("--force");
293
294        subcmd.current_dir(path);
295
296        //当克隆仓库的子进程结束后,启动保证克隆子模块的子进程
297        let subproc: std::process::Child = subcmd
298            .stderr(Stdio::piped())
299            .stdout(Stdio::inherit())
300            .spawn()
301            .map_err(|e| e.to_string())?;
302        let suboutput = subproc.wait_with_output().map_err(|e| e.to_string())?;
303
304        if !suboutput.status.success() {
305            return Err(format!(
306                "clone submodule failed, status: {:?},  stderr: {:?}",
307                suboutput.status,
308                StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&suboutput.stderr), 5)
309            ));
310        }
311        return Ok(());
312    }
313
314    /// 设置fetch所有分支
315    fn set_fetch_config(&self, target_dir: &CacheDir) -> Result<(), String> {
316        let mut cmd = Command::new("git");
317        cmd.current_dir(&target_dir.path);
318        cmd.arg("config")
319            .arg("remote.origin.fetch")
320            .arg("+refs/heads/*:refs/remotes/origin/*");
321
322        // 创建子进程,执行命令
323        let proc: std::process::Child = cmd
324            .stderr(Stdio::piped())
325            .spawn()
326            .map_err(|e| e.to_string())?;
327        let output = proc.wait_with_output().map_err(|e| e.to_string())?;
328
329        if !output.status.success() {
330            return Err(format!(
331                "Failed to set fetch config {}, message: {}",
332                target_dir.path.display(),
333                StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
334            ));
335        }
336        return Ok(());
337    }
338    /// # 把浅克隆的仓库变成深克隆
339    fn unshallow(&self, target_dir: &CacheDir) -> Result<(), String> {
340        if self.is_shallow(target_dir)? == false {
341            return Ok(());
342        }
343
344        let mut cmd = Command::new("git");
345        cmd.current_dir(&target_dir.path);
346        cmd.arg("fetch").arg("--unshallow");
347
348        cmd.arg("-f");
349
350        // 创建子进程,执行命令
351        let proc: std::process::Child = cmd
352            .stderr(Stdio::piped())
353            .spawn()
354            .map_err(|e| e.to_string())?;
355        let output = proc.wait_with_output().map_err(|e| e.to_string())?;
356
357        if !output.status.success() {
358            return Err(format!(
359                "Failed to unshallow {}, message: {}",
360                target_dir.path.display(),
361                StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
362            ));
363        }
364        return Ok(());
365    }
366
367    /// 判断当前仓库是否是浅克隆
368    fn is_shallow(&self, target_dir: &CacheDir) -> Result<bool, String> {
369        let mut cmd = Command::new("git");
370        cmd.current_dir(&target_dir.path);
371        cmd.arg("rev-parse").arg("--is-shallow-repository");
372
373        let proc: std::process::Child = cmd
374            .stderr(Stdio::piped())
375            .spawn()
376            .map_err(|e| e.to_string())?;
377        let output = proc.wait_with_output().map_err(|e| e.to_string())?;
378
379        if !output.status.success() {
380            return Err(format!(
381                "Failed to check if shallow {}, message: {}",
382                target_dir.path.display(),
383                StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
384            ));
385        }
386
387        let is_shallow = String::from_utf8_lossy(&output.stdout).trim() == "true";
388        return Ok(is_shallow);
389    }
390
391    fn fetch_all(&self, target_dir: &CacheDir) -> Result<(), String> {
392        self.set_fetch_config(target_dir)?;
393        let mut cmd = Command::new("git");
394        cmd.current_dir(&target_dir.path);
395        cmd.arg("fetch").arg("--all");
396
397        // 安静模式
398        cmd.arg("-f").arg("-q");
399
400        // 创建子进程,执行命令
401        let proc: std::process::Child = cmd
402            .stderr(Stdio::piped())
403            .spawn()
404            .map_err(|e| e.to_string())?;
405        let output = proc.wait_with_output().map_err(|e| e.to_string())?;
406
407        if !output.status.success() {
408            return Err(format!(
409                "Failed to fetch all {}, message: {}",
410                target_dir.path.display(),
411                StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
412            ));
413        }
414
415        return Ok(());
416    }
417
418    fn pull(&self, target_dir: &CacheDir) -> Result<(), String> {
419        // 如果没有指定branch,则不执行pull
420        if !self.branch.is_some() {
421            return Ok(());
422        }
423        info!("git pulling: {}", target_dir.path.display());
424
425        let mut cmd = Command::new("git");
426        cmd.current_dir(&target_dir.path);
427        cmd.arg("pull");
428
429        // 安静模式
430        cmd.arg("-f").arg("-q");
431
432        // 创建子进程,执行命令
433        let proc: std::process::Child = cmd
434            .stderr(Stdio::piped())
435            .spawn()
436            .map_err(|e| e.to_string())?;
437        let output = proc.wait_with_output().map_err(|e| e.to_string())?;
438
439        // 如果pull失败,且指定了branch,则报错
440        if !output.status.success() {
441            return Err(format!(
442                "Failed to pull {}, message: {}",
443                target_dir.path.display(),
444                StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
445            ));
446        }
447
448        return Ok(());
449    }
450}
451
452/// # 本地源
453#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
454pub struct LocalSource {
455    /// 本地目录/文件的路径
456    path: PathBuf,
457}
458
459impl LocalSource {
460    #[allow(dead_code)]
461    pub fn new(path: PathBuf) -> Self {
462        Self { path }
463    }
464
465    pub fn validate(&self, expect_file: Option<bool>) -> Result<()> {
466        if !self.path.exists() {
467            return Err(Error::msg(format!("path {:?} not exists", self.path)));
468        }
469
470        if let Some(expect_file) = expect_file {
471            if expect_file && !self.path.is_file() {
472                return Err(Error::msg(format!("path {:?} is not a file", self.path)));
473            }
474
475            if !expect_file && !self.path.is_dir() {
476                return Err(Error::msg(format!(
477                    "path {:?} is not a directory",
478                    self.path
479                )));
480            }
481        }
482
483        return Ok(());
484    }
485
486    pub fn trim(&mut self) {}
487
488    pub fn path(&self) -> &PathBuf {
489        &self.path
490    }
491}
492
493/// # 在线压缩包源
494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
495pub struct ArchiveSource {
496    /// 压缩包的URL
497    url: String,
498    /// 把压缩包中的哪个目录作为根目录(可选)
499    /// 默认是压缩包内的根目录
500    #[serde(default)]
501    rootdir: Option<String>,
502}
503
504impl ArchiveSource {
505    #[allow(dead_code)]
506    pub fn new(url: String, rootdir: Option<String>) -> Self {
507        Self { url, rootdir }
508    }
509
510    pub fn validate(&self) -> Result<()> {
511        if self.url.is_empty() {
512            return Err(Error::msg("url is empty"));
513        }
514
515        // 判断是一个网址
516        if let Ok(url) = Url::parse(&self.url) {
517            if url.scheme() != "http" && url.scheme() != "https" {
518                return Err(Error::msg(format!(
519                    "url {:?} is not a http/https url",
520                    self.url
521                )));
522            }
523        } else {
524            return Err(Error::msg(format!("url {:?} is not a valid url", self.url)));
525        }
526
527        if self.rootdir.is_some() && self.rootdir.as_ref().unwrap().starts_with('/') {
528            return Err(Error::msg(format!(
529                "archive rootdir {:?} starts with '/'",
530                self.rootdir
531            )));
532        }
533        return Ok(());
534    }
535
536    pub fn trim(&mut self) {
537        self.url = self.url.trim().to_string();
538    }
539
540    /// @brief 下载压缩包并把其中的文件提取至target_dir目录下
541    ///
542    ///从URL中下载压缩包到临时文件夹 target_dir/DRAGONOS_ARCHIVE_TEMP 后
543    ///原地解压,提取文件后删除下载的压缩包。如果 target_dir 非空,就直接使用
544    ///其中内容,不进行重复下载和覆盖
545    ///
546    /// @param target_dir 文件缓存目录
547    ///
548    /// @return 根据结果返回OK或Err
549    pub fn download_unzip(&self, target_dir: &CacheDir) -> Result<(), String> {
550        let url = Url::parse(&self.url).unwrap();
551        let archive_name = url.path_segments().unwrap().last().unwrap();
552        let path = &(target_dir.path.join("DRAGONOS_ARCHIVE_TEMP"));
553        //如果source目录没有临时文件夹,且不为空,说明之前成功执行过一次,那么就直接使用之前的缓存
554        if !path.exists()
555            && !target_dir.is_empty().map_err(|e| {
556                format!(
557                    "Failed to check if target dir is empty: {}, message: {e:?}",
558                    target_dir.path.display()
559                )
560            })?
561        {
562            //如果source文件夹非空,就直接使用,不再重复下载压缩文件,这里可以考虑加入交互
563            info!("Source files already exist. Using previous source file cache. You should clean {:?} before re-download the archive ", target_dir.path);
564            return Ok(());
565        }
566
567        if path.exists() {
568            std::fs::remove_dir_all(path).map_err(|e| e.to_string())?;
569        }
570        //创建临时目录
571        std::fs::create_dir(path).map_err(|e| e.to_string())?;
572        info!("downloading {:?}, url: {:?}", archive_name, self.url);
573        FileUtils::download_file(&self.url, path).map_err(|e| e.to_string())?;
574        //下载成功,开始尝试解压
575        info!("download {:?} finished, start unzip", archive_name);
576        let archive_file = ArchiveFile::new(&path.join(archive_name));
577        archive_file.unzip(self.rootdir.as_ref())?;
578        //删除创建的临时文件夹
579        // std::fs::remove_dir_all(path).map_err(|e| e.to_string())?;
580        return Ok(());
581    }
582}
583
584pub struct ArchiveFile {
585    /// archive file所在目录
586    archive_path: PathBuf,
587    /// 压缩文件名
588    archive_name: String,
589    archive_type: ArchiveType,
590}
591
592impl ArchiveFile {
593    pub fn new(archive_path: &PathBuf) -> Self {
594        info!("archive_path: {:?}", archive_path);
595        //匹配压缩文件类型
596        let archive_name = archive_path.file_name().unwrap().to_str().unwrap();
597        for (regex, archivetype) in [
598            (Regex::new(r"^(.+)\.tar\.gz$").unwrap(), ArchiveType::TarGz),
599            (Regex::new(r"^(.+)\.tar\.xz$").unwrap(), ArchiveType::TarXz),
600            (Regex::new(r"^(.+)\.zip$").unwrap(), ArchiveType::Zip),
601        ] {
602            if regex.is_match(archive_name) {
603                return Self {
604                    archive_path: archive_path.parent().unwrap().to_path_buf(),
605                    archive_name: archive_name.to_string(),
606                    archive_type: archivetype,
607                };
608            }
609        }
610        Self {
611            archive_path: archive_path.parent().unwrap().to_path_buf(),
612            archive_name: archive_name.to_string(),
613            archive_type: ArchiveType::Undefined,
614        }
615    }
616
617    fn do_unzip_tar_file(&self, in_archive_rootdir: Option<&String>) -> Result<(), String> {
618        let mut cmd = Command::new("tar");
619        cmd.arg("-xf").arg(&self.archive_name);
620
621        // 处理in_archive_rootdir参数,只解压压缩文件内的指定目录
622        if let Some(in_archive_rootdir) = in_archive_rootdir {
623            let mut components = 0;
624            in_archive_rootdir.split('/').for_each(|x| {
625                if x != "" {
626                    components += 1;
627                }
628            });
629
630            cmd.arg(format!("--strip-components={}", components));
631            cmd.arg(&in_archive_rootdir);
632        }
633
634        cmd.current_dir(&self.archive_path);
635
636        log::debug!("unzip tar file: {:?}", cmd);
637
638        let proc: std::process::Child = cmd
639            .stderr(Stdio::piped())
640            .stdout(Stdio::inherit())
641            .spawn()
642            .map_err(|e| e.to_string())?;
643        let output = proc.wait_with_output().map_err(|e| e.to_string())?;
644        if !output.status.success() {
645            return Err(format!(
646                "unzip tar file failed, status: {:?},  stderr: {:?}",
647                output.status,
648                StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
649            ));
650        }
651        Ok(())
652    }
653
654    fn do_unzip_zip_file(&self, in_archive_rootdir: Option<&String>) -> Result<(), String> {
655        let file =
656            File::open(&self.archive_path.join(&self.archive_name)).map_err(|e| e.to_string())?;
657        let mut archive = ZipArchive::new(file).map_err(|e| e.to_string())?;
658        for i in 0..archive.len() {
659            let mut file = archive.by_index(i).map_err(|e| e.to_string())?;
660            let file_name = file.name();
661
662            // 处理in_archive_rootdir参数,只解压指定目录下的内容
663            let outpath = if let Some(rootdir) = in_archive_rootdir {
664                if !file_name.starts_with(rootdir) {
665                    continue;
666                }
667                // 去除rootdir前缀,保留剩余路径
668                let relative_path = file_name.strip_prefix(rootdir).unwrap();
669                let relative_path = relative_path.trim_start_matches("/");
670                self.archive_path.join(relative_path)
671            } else {
672                match file.enclosed_name() {
673                    Some(path) => self.archive_path.join(path),
674                    None => continue,
675                }
676            };
677            if (*file.name()).ends_with('/') {
678                std::fs::create_dir_all(&outpath).map_err(|e| e.to_string())?;
679            } else {
680                if let Some(p) = outpath.parent() {
681                    if !p.exists() {
682                        std::fs::create_dir_all(&p).map_err(|e| e.to_string())?;
683                    }
684                }
685                let mut outfile = File::create(&outpath).map_err(|e| e.to_string())?;
686                std::io::copy(&mut file, &mut outfile).map_err(|e| e.to_string())?;
687            }
688            //设置解压后权限,在Linux中Unzip会丢失权限
689            #[cfg(unix)]
690            {
691                if let Some(mode) = file.unix_mode() {
692                    std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))
693                        .map_err(|e| e.to_string())?;
694                }
695            }
696        }
697        Ok(())
698    }
699
700    /// @brief 对self.archive_path路径下名为self.archive_name的压缩文件(tar.gz或zip)进行解压缩
701    ///
702    /// 在此函数中进行路径和文件名有效性的判断,如果有效的话就开始解压缩,根据ArchiveType枚举类型来
703    /// 生成不同的命令来对压缩文件进行解压缩,暂时只支持tar.gz和zip格式,并且都是通过调用bash来解压缩
704    /// 没有引入第三方rust库
705    ///
706    ///
707    /// @return 根据结果返回OK或Err
708    pub fn unzip(&self, in_archive_rootdir: Option<&String>) -> Result<(), String> {
709        let path = &self.archive_path;
710        if !path.is_dir() {
711            return Err(format!("Archive directory {:?} is wrong", path));
712        }
713        if !path.join(&self.archive_name).is_file() {
714            return Err(format!(
715                " {:?} is not a file",
716                path.join(&self.archive_name)
717            ));
718        }
719        //根据压缩文件的类型生成cmd指令
720        match &self.archive_type {
721            ArchiveType::TarGz | ArchiveType::TarXz => {
722                self.do_unzip_tar_file(in_archive_rootdir)?;
723            }
724
725            ArchiveType::Zip => {
726                self.do_unzip_zip_file(in_archive_rootdir)?;
727            }
728            _ => {
729                return Err("unsupported archive type".to_string());
730            }
731        }
732        //删除下载的压缩包
733        info!("unzip successfully, removing archive ");
734        std::fs::remove_file(path.join(&self.archive_name)).map_err(|e| e.to_string())?;
735        //从解压的文件夹中提取出文件并删除下载的压缩包等价于指令"cd *;mv ./* ../../"
736        // for entry in path.read_dir().map_err(|e| e.to_string())? {
737        //     let entry = entry.map_err(|e| e.to_string())?;
738        //     let path = entry.path();
739        //     FileUtils::move_files(&path, &self.archive_path.parent().unwrap())
740        //         .map_err(|e| e.to_string())?;
741        //     //删除空的单独文件夹
742        //     std::fs::remove_dir_all(&path).map_err(|e| e.to_string())?;
743        // }
744        std::process::Command::new("sh")
745            .arg("-c")
746            .arg(format!(
747                "mv {p}/* {parent} && rm -rf {p}",
748                p = &self.archive_path.to_string_lossy(),
749                parent = self.archive_path.parent().unwrap().to_string_lossy()
750            ))
751            .output()
752            .map_err(|e| e.to_string())?;
753        return Ok(());
754    }
755}
756
757pub enum ArchiveType {
758    TarGz,
759    TarXz,
760    Zip,
761    Undefined,
762}