173779f3dSLoGin use log::info;
273779f3dSLoGin use regex::Regex;
373779f3dSLoGin use reqwest::Url;
473779f3dSLoGin use serde::{Deserialize, Serialize};
573779f3dSLoGin use std::os::unix::fs::PermissionsExt;
673779f3dSLoGin use std::{
773779f3dSLoGin fs::File,
873779f3dSLoGin path::PathBuf,
973779f3dSLoGin process::{Command, Stdio},
1073779f3dSLoGin };
1173779f3dSLoGin use zip::ZipArchive;
1273779f3dSLoGin
1373779f3dSLoGin use crate::utils::{file::FileUtils, stdio::StdioUtils};
1473779f3dSLoGin
1573779f3dSLoGin use super::cache::CacheDir;
1673779f3dSLoGin
17*d2ade6efSJomo use anyhow::{Error, Result};
18*d2ade6efSJomo
1973779f3dSLoGin /// # Git源
2073779f3dSLoGin ///
2173779f3dSLoGin /// 从Git仓库获取源码
2273779f3dSLoGin #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2373779f3dSLoGin pub struct GitSource {
2473779f3dSLoGin /// Git仓库地址
2573779f3dSLoGin url: String,
2673779f3dSLoGin /// 分支(可选,如果为空,则拉取master)branch和revision只能同时指定一个
2773779f3dSLoGin branch: Option<String>,
2873779f3dSLoGin /// 特定的提交的hash值(可选,如果为空,则拉取branch的最新提交)
2973779f3dSLoGin revision: Option<String>,
3073779f3dSLoGin }
new(url: String, branch: Option<String>, revision: Option<String>) -> Self3173779f3dSLoGin
3273779f3dSLoGin impl GitSource {
3373779f3dSLoGin pub fn new(url: String, branch: Option<String>, revision: Option<String>) -> Self {
3473779f3dSLoGin Self {
3573779f3dSLoGin url,
3673779f3dSLoGin branch,
3773779f3dSLoGin revision,
3873779f3dSLoGin }
3973779f3dSLoGin }
4073779f3dSLoGin /// # 验证参数合法性
4173779f3dSLoGin ///
4273779f3dSLoGin /// 仅进行形式校验,不会检查Git仓库是否存在,以及分支是否存在、是否有权限访问等
43*d2ade6efSJomo pub fn validate(&mut self) -> Result<()> {
4473779f3dSLoGin if self.url.is_empty() {
45*d2ade6efSJomo return Err(Error::msg("url is empty"));
4673779f3dSLoGin }
4773779f3dSLoGin // branch和revision不能同时为空
4873779f3dSLoGin if self.branch.is_none() && self.revision.is_none() {
4973779f3dSLoGin self.branch = Some("master".to_string());
5073779f3dSLoGin }
5173779f3dSLoGin // branch和revision只能同时指定一个
5273779f3dSLoGin if self.branch.is_some() && self.revision.is_some() {
53*d2ade6efSJomo return Err(Error::msg("branch and revision are both specified"));
5473779f3dSLoGin }
5573779f3dSLoGin
5673779f3dSLoGin if self.branch.is_some() {
5773779f3dSLoGin if self.branch.as_ref().unwrap().is_empty() {
58*d2ade6efSJomo return Err(Error::msg("branch is empty"));
5973779f3dSLoGin }
6073779f3dSLoGin }
6173779f3dSLoGin if self.revision.is_some() {
6273779f3dSLoGin if self.revision.as_ref().unwrap().is_empty() {
63*d2ade6efSJomo return Err(Error::msg("revision is empty"));
6473779f3dSLoGin }
6573779f3dSLoGin }
6673779f3dSLoGin return Ok(());
6773779f3dSLoGin }
6873779f3dSLoGin
6973779f3dSLoGin pub fn trim(&mut self) {
7073779f3dSLoGin self.url = self.url.trim().to_string();
7173779f3dSLoGin if let Some(branch) = &mut self.branch {
7273779f3dSLoGin *branch = branch.trim().to_string();
7373779f3dSLoGin }
7473779f3dSLoGin
7573779f3dSLoGin if let Some(revision) = &mut self.revision {
7673779f3dSLoGin *revision = revision.trim().to_string();
7773779f3dSLoGin }
7873779f3dSLoGin }
7973779f3dSLoGin
8073779f3dSLoGin /// # 确保Git仓库已经克隆到指定目录,并且切换到指定分支/Revision
8173779f3dSLoGin ///
8273779f3dSLoGin /// 如果目录不存在,则会自动创建
8373779f3dSLoGin ///
8473779f3dSLoGin /// ## 参数
8573779f3dSLoGin ///
8673779f3dSLoGin /// - `target_dir` - 目标目录
8773779f3dSLoGin ///
8873779f3dSLoGin /// ## 返回
8973779f3dSLoGin ///
9073779f3dSLoGin /// - `Ok(())` - 成功
9173779f3dSLoGin /// - `Err(String)` - 失败,错误信息
9273779f3dSLoGin pub fn prepare(&self, target_dir: &CacheDir) -> Result<(), String> {
9373779f3dSLoGin info!(
9473779f3dSLoGin "Preparing git repo: {}, branch: {:?}, revision: {:?}",
9573779f3dSLoGin self.url, self.branch, self.revision
9673779f3dSLoGin );
9773779f3dSLoGin
9873779f3dSLoGin target_dir.create().map_err(|e| {
9973779f3dSLoGin format!(
10073779f3dSLoGin "Failed to create target dir: {}, message: {e:?}",
10173779f3dSLoGin target_dir.path.display()
10273779f3dSLoGin )
10373779f3dSLoGin })?;
10473779f3dSLoGin
10573779f3dSLoGin if target_dir.is_empty().map_err(|e| {
10673779f3dSLoGin format!(
10773779f3dSLoGin "Failed to check if target dir is empty: {}, message: {e:?}",
10873779f3dSLoGin target_dir.path.display()
10973779f3dSLoGin )
11073779f3dSLoGin })? {
11173779f3dSLoGin info!("Target dir is empty, cloning repo");
11273779f3dSLoGin self.clone_repo(target_dir)?;
11373779f3dSLoGin }
11473779f3dSLoGin
11573779f3dSLoGin self.checkout(target_dir)?;
11673779f3dSLoGin
11773779f3dSLoGin self.pull(target_dir)?;
11873779f3dSLoGin
11973779f3dSLoGin return Ok(());
12073779f3dSLoGin }
12173779f3dSLoGin
12273779f3dSLoGin fn check_repo(&self, target_dir: &CacheDir) -> Result<bool, String> {
12373779f3dSLoGin let path: &PathBuf = &target_dir.path;
12473779f3dSLoGin let mut cmd = Command::new("git");
12573779f3dSLoGin cmd.arg("remote").arg("get-url").arg("origin");
12673779f3dSLoGin
12773779f3dSLoGin // 设置工作目录
12873779f3dSLoGin cmd.current_dir(path);
12973779f3dSLoGin
13073779f3dSLoGin // 创建子进程,执行命令
13173779f3dSLoGin let proc: std::process::Child = cmd
13273779f3dSLoGin .stderr(Stdio::piped())
13373779f3dSLoGin .stdout(Stdio::piped())
13473779f3dSLoGin .spawn()
13573779f3dSLoGin .map_err(|e| e.to_string())?;
13673779f3dSLoGin let output = proc.wait_with_output().map_err(|e| e.to_string())?;
13773779f3dSLoGin
13873779f3dSLoGin if output.status.success() {
13973779f3dSLoGin let mut r = String::from_utf8(output.stdout).unwrap();
14073779f3dSLoGin r.pop();
14173779f3dSLoGin Ok(r == self.url)
14273779f3dSLoGin } else {
14373779f3dSLoGin return Err(format!(
14473779f3dSLoGin "git remote get-url origin failed, status: {:?}, stderr: {:?}",
14573779f3dSLoGin output.status,
14673779f3dSLoGin StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
14773779f3dSLoGin ));
14873779f3dSLoGin }
14973779f3dSLoGin }
15073779f3dSLoGin
15173779f3dSLoGin fn set_url(&self, target_dir: &CacheDir) -> Result<(), String> {
15273779f3dSLoGin let path: &PathBuf = &target_dir.path;
15373779f3dSLoGin let mut cmd = Command::new("git");
15473779f3dSLoGin cmd.arg("remote")
15573779f3dSLoGin .arg("set-url")
15673779f3dSLoGin .arg("origin")
15773779f3dSLoGin .arg(self.url.as_str());
15873779f3dSLoGin
15973779f3dSLoGin // 设置工作目录
16073779f3dSLoGin cmd.current_dir(path);
16173779f3dSLoGin
16273779f3dSLoGin // 创建子进程,执行命令
16373779f3dSLoGin let proc: std::process::Child = cmd
16473779f3dSLoGin .stderr(Stdio::piped())
16573779f3dSLoGin .spawn()
16673779f3dSLoGin .map_err(|e| e.to_string())?;
16773779f3dSLoGin let output = proc.wait_with_output().map_err(|e| e.to_string())?;
16873779f3dSLoGin
16973779f3dSLoGin if !output.status.success() {
17073779f3dSLoGin return Err(format!(
17173779f3dSLoGin "git remote set-url origin failed, status: {:?}, stderr: {:?}",
17273779f3dSLoGin output.status,
17373779f3dSLoGin StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
17473779f3dSLoGin ));
17573779f3dSLoGin }
17673779f3dSLoGin Ok(())
17773779f3dSLoGin }
17873779f3dSLoGin
17973779f3dSLoGin fn checkout(&self, target_dir: &CacheDir) -> Result<(), String> {
18073779f3dSLoGin // 确保目标目录中的仓库为所指定仓库
18173779f3dSLoGin if !self.check_repo(target_dir).map_err(|e| {
18273779f3dSLoGin format!(
18373779f3dSLoGin "Failed to check repo: {}, message: {e:?}",
18473779f3dSLoGin target_dir.path.display()
18573779f3dSLoGin )
18673779f3dSLoGin })? {
18773779f3dSLoGin info!("Target dir isn't specified repo, change remote url");
18873779f3dSLoGin self.set_url(target_dir)?;
18973779f3dSLoGin }
19073779f3dSLoGin
19173779f3dSLoGin let do_checkout = || -> Result<(), String> {
19273779f3dSLoGin let mut cmd = Command::new("git");
19373779f3dSLoGin cmd.current_dir(&target_dir.path);
19473779f3dSLoGin cmd.arg("checkout");
19573779f3dSLoGin
19673779f3dSLoGin if let Some(branch) = &self.branch {
19773779f3dSLoGin cmd.arg(branch);
19873779f3dSLoGin }
19973779f3dSLoGin if let Some(revision) = &self.revision {
20073779f3dSLoGin cmd.arg(revision);
20173779f3dSLoGin }
20273779f3dSLoGin
20373779f3dSLoGin // 强制切换分支,且安静模式
20473779f3dSLoGin cmd.arg("-f").arg("-q");
20573779f3dSLoGin
20673779f3dSLoGin // 创建子进程,执行命令
20773779f3dSLoGin let proc: std::process::Child = cmd
20873779f3dSLoGin .stderr(Stdio::piped())
20973779f3dSLoGin .spawn()
21073779f3dSLoGin .map_err(|e| e.to_string())?;
21173779f3dSLoGin let output = proc.wait_with_output().map_err(|e| e.to_string())?;
21273779f3dSLoGin
21373779f3dSLoGin if !output.status.success() {
21473779f3dSLoGin return Err(format!(
21573779f3dSLoGin "Failed to checkout {}, message: {}",
21673779f3dSLoGin target_dir.path.display(),
21773779f3dSLoGin String::from_utf8_lossy(&output.stdout)
21873779f3dSLoGin ));
21973779f3dSLoGin }
22073779f3dSLoGin
22173779f3dSLoGin let mut subcmd = Command::new("git");
22273779f3dSLoGin subcmd.current_dir(&target_dir.path);
22373779f3dSLoGin subcmd.arg("submodule").arg("update").arg("--remote");
22473779f3dSLoGin
22573779f3dSLoGin //当checkout仓库的子进程结束后,启动checkout子模块的子进程
22673779f3dSLoGin let subproc: std::process::Child = subcmd
22773779f3dSLoGin .stderr(Stdio::piped())
22873779f3dSLoGin .spawn()
22973779f3dSLoGin .map_err(|e| e.to_string())?;
23073779f3dSLoGin let suboutput = subproc.wait_with_output().map_err(|e| e.to_string())?;
23173779f3dSLoGin
23273779f3dSLoGin if !suboutput.status.success() {
23373779f3dSLoGin return Err(format!(
23473779f3dSLoGin "Failed to checkout submodule {}, message: {}",
23573779f3dSLoGin target_dir.path.display(),
23673779f3dSLoGin String::from_utf8_lossy(&suboutput.stdout)
23773779f3dSLoGin ));
23873779f3dSLoGin }
23973779f3dSLoGin return Ok(());
24073779f3dSLoGin };
24173779f3dSLoGin
24273779f3dSLoGin if let Err(_) = do_checkout() {
24373779f3dSLoGin // 如果切换分支失败,则尝试重新fetch
24473779f3dSLoGin if self.revision.is_some() {
24573779f3dSLoGin self.set_fetch_config(target_dir)?;
24673779f3dSLoGin self.unshallow(target_dir)?
24773779f3dSLoGin };
24873779f3dSLoGin
24973779f3dSLoGin self.fetch_all(target_dir).ok();
25073779f3dSLoGin do_checkout()?;
25173779f3dSLoGin }
25273779f3dSLoGin
25373779f3dSLoGin return Ok(());
25473779f3dSLoGin }
25573779f3dSLoGin
25673779f3dSLoGin pub fn clone_repo(&self, cache_dir: &CacheDir) -> Result<(), String> {
25773779f3dSLoGin let path: &PathBuf = &cache_dir.path;
25873779f3dSLoGin let mut cmd = Command::new("git");
25973779f3dSLoGin cmd.arg("clone").arg(&self.url).arg(".").arg("--recursive");
26073779f3dSLoGin
26173779f3dSLoGin if let Some(branch) = &self.branch {
26273779f3dSLoGin cmd.arg("--branch").arg(branch).arg("--depth").arg("1");
26373779f3dSLoGin }
26473779f3dSLoGin
26573779f3dSLoGin // 对于克隆,如果指定了revision,则直接克隆整个仓库,稍后再切换到指定的revision
26673779f3dSLoGin
26773779f3dSLoGin // 设置工作目录
26873779f3dSLoGin cmd.current_dir(path);
26973779f3dSLoGin
27073779f3dSLoGin // 创建子进程,执行命令
27173779f3dSLoGin let proc: std::process::Child = cmd
27273779f3dSLoGin .stderr(Stdio::piped())
27373779f3dSLoGin .stdout(Stdio::inherit())
27473779f3dSLoGin .spawn()
27573779f3dSLoGin .map_err(|e| e.to_string())?;
27673779f3dSLoGin let output = proc.wait_with_output().map_err(|e| e.to_string())?;
27773779f3dSLoGin
27873779f3dSLoGin if !output.status.success() {
27973779f3dSLoGin return Err(format!(
28073779f3dSLoGin "clone git repo failed, status: {:?}, stderr: {:?}",
28173779f3dSLoGin output.status,
28273779f3dSLoGin StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
28373779f3dSLoGin ));
28473779f3dSLoGin }
28573779f3dSLoGin
28673779f3dSLoGin let mut subcmd = Command::new("git");
28773779f3dSLoGin subcmd
28873779f3dSLoGin .arg("submodule")
28973779f3dSLoGin .arg("update")
29073779f3dSLoGin .arg("--init")
29173779f3dSLoGin .arg("--recursive")
29273779f3dSLoGin .arg("--force");
29373779f3dSLoGin
29473779f3dSLoGin subcmd.current_dir(path);
29573779f3dSLoGin
29673779f3dSLoGin //当克隆仓库的子进程结束后,启动保证克隆子模块的子进程
29773779f3dSLoGin let subproc: std::process::Child = subcmd
29873779f3dSLoGin .stderr(Stdio::piped())
29973779f3dSLoGin .stdout(Stdio::inherit())
30073779f3dSLoGin .spawn()
30173779f3dSLoGin .map_err(|e| e.to_string())?;
30273779f3dSLoGin let suboutput = subproc.wait_with_output().map_err(|e| e.to_string())?;
30373779f3dSLoGin
30473779f3dSLoGin if !suboutput.status.success() {
30573779f3dSLoGin return Err(format!(
30673779f3dSLoGin "clone submodule failed, status: {:?}, stderr: {:?}",
30773779f3dSLoGin suboutput.status,
30873779f3dSLoGin StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&suboutput.stderr), 5)
30973779f3dSLoGin ));
31073779f3dSLoGin }
31173779f3dSLoGin return Ok(());
31273779f3dSLoGin }
31373779f3dSLoGin
31473779f3dSLoGin /// 设置fetch所有分支
31573779f3dSLoGin fn set_fetch_config(&self, target_dir: &CacheDir) -> Result<(), String> {
31673779f3dSLoGin let mut cmd = Command::new("git");
31773779f3dSLoGin cmd.current_dir(&target_dir.path);
31873779f3dSLoGin cmd.arg("config")
31973779f3dSLoGin .arg("remote.origin.fetch")
32073779f3dSLoGin .arg("+refs/heads/*:refs/remotes/origin/*");
32173779f3dSLoGin
32273779f3dSLoGin // 创建子进程,执行命令
32373779f3dSLoGin let proc: std::process::Child = cmd
32473779f3dSLoGin .stderr(Stdio::piped())
32573779f3dSLoGin .spawn()
32673779f3dSLoGin .map_err(|e| e.to_string())?;
32773779f3dSLoGin let output = proc.wait_with_output().map_err(|e| e.to_string())?;
32873779f3dSLoGin
32973779f3dSLoGin if !output.status.success() {
33073779f3dSLoGin return Err(format!(
33173779f3dSLoGin "Failed to set fetch config {}, message: {}",
33273779f3dSLoGin target_dir.path.display(),
33373779f3dSLoGin StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
33473779f3dSLoGin ));
33573779f3dSLoGin }
33673779f3dSLoGin return Ok(());
33773779f3dSLoGin }
33873779f3dSLoGin /// # 把浅克隆的仓库变成深克隆
33973779f3dSLoGin fn unshallow(&self, target_dir: &CacheDir) -> Result<(), String> {
34073779f3dSLoGin if self.is_shallow(target_dir)? == false {
34173779f3dSLoGin return Ok(());
34273779f3dSLoGin }
34373779f3dSLoGin
34473779f3dSLoGin let mut cmd = Command::new("git");
34573779f3dSLoGin cmd.current_dir(&target_dir.path);
34673779f3dSLoGin cmd.arg("fetch").arg("--unshallow");
34773779f3dSLoGin
34873779f3dSLoGin cmd.arg("-f");
34973779f3dSLoGin
35073779f3dSLoGin // 创建子进程,执行命令
35173779f3dSLoGin let proc: std::process::Child = cmd
35273779f3dSLoGin .stderr(Stdio::piped())
35373779f3dSLoGin .spawn()
35473779f3dSLoGin .map_err(|e| e.to_string())?;
35573779f3dSLoGin let output = proc.wait_with_output().map_err(|e| e.to_string())?;
35673779f3dSLoGin
35773779f3dSLoGin if !output.status.success() {
35873779f3dSLoGin return Err(format!(
35973779f3dSLoGin "Failed to unshallow {}, message: {}",
36073779f3dSLoGin target_dir.path.display(),
36173779f3dSLoGin StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
36273779f3dSLoGin ));
36373779f3dSLoGin }
36473779f3dSLoGin return Ok(());
36573779f3dSLoGin }
36673779f3dSLoGin
36773779f3dSLoGin /// 判断当前仓库是否是浅克隆
36873779f3dSLoGin fn is_shallow(&self, target_dir: &CacheDir) -> Result<bool, String> {
36973779f3dSLoGin let mut cmd = Command::new("git");
37073779f3dSLoGin cmd.current_dir(&target_dir.path);
37173779f3dSLoGin cmd.arg("rev-parse").arg("--is-shallow-repository");
37273779f3dSLoGin
37373779f3dSLoGin let proc: std::process::Child = cmd
37473779f3dSLoGin .stderr(Stdio::piped())
37573779f3dSLoGin .spawn()
37673779f3dSLoGin .map_err(|e| e.to_string())?;
37773779f3dSLoGin let output = proc.wait_with_output().map_err(|e| e.to_string())?;
37873779f3dSLoGin
37973779f3dSLoGin if !output.status.success() {
38073779f3dSLoGin return Err(format!(
38173779f3dSLoGin "Failed to check if shallow {}, message: {}",
38273779f3dSLoGin target_dir.path.display(),
38373779f3dSLoGin StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
38473779f3dSLoGin ));
38573779f3dSLoGin }
38673779f3dSLoGin
38773779f3dSLoGin let is_shallow = String::from_utf8_lossy(&output.stdout).trim() == "true";
38873779f3dSLoGin return Ok(is_shallow);
38973779f3dSLoGin }
39073779f3dSLoGin
39173779f3dSLoGin fn fetch_all(&self, target_dir: &CacheDir) -> Result<(), String> {
39273779f3dSLoGin self.set_fetch_config(target_dir)?;
39373779f3dSLoGin let mut cmd = Command::new("git");
39473779f3dSLoGin cmd.current_dir(&target_dir.path);
39573779f3dSLoGin cmd.arg("fetch").arg("--all");
39673779f3dSLoGin
39773779f3dSLoGin // 安静模式
39873779f3dSLoGin cmd.arg("-f").arg("-q");
39973779f3dSLoGin
40073779f3dSLoGin // 创建子进程,执行命令
40173779f3dSLoGin let proc: std::process::Child = cmd
40273779f3dSLoGin .stderr(Stdio::piped())
40373779f3dSLoGin .spawn()
40473779f3dSLoGin .map_err(|e| e.to_string())?;
40573779f3dSLoGin let output = proc.wait_with_output().map_err(|e| e.to_string())?;
40673779f3dSLoGin
40773779f3dSLoGin if !output.status.success() {
40873779f3dSLoGin return Err(format!(
40973779f3dSLoGin "Failed to fetch all {}, message: {}",
41073779f3dSLoGin target_dir.path.display(),
41173779f3dSLoGin StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
41273779f3dSLoGin ));
41373779f3dSLoGin }
41473779f3dSLoGin
41573779f3dSLoGin return Ok(());
41673779f3dSLoGin }
41773779f3dSLoGin
41873779f3dSLoGin fn pull(&self, target_dir: &CacheDir) -> Result<(), String> {
41973779f3dSLoGin // 如果没有指定branch,则不执行pull
42073779f3dSLoGin if !self.branch.is_some() {
42173779f3dSLoGin return Ok(());
42273779f3dSLoGin }
42373779f3dSLoGin info!("git pulling: {}", target_dir.path.display());
42473779f3dSLoGin
42573779f3dSLoGin let mut cmd = Command::new("git");
42673779f3dSLoGin cmd.current_dir(&target_dir.path);
42773779f3dSLoGin cmd.arg("pull");
42873779f3dSLoGin
42973779f3dSLoGin // 安静模式
43073779f3dSLoGin cmd.arg("-f").arg("-q");
43173779f3dSLoGin
43273779f3dSLoGin // 创建子进程,执行命令
43373779f3dSLoGin let proc: std::process::Child = cmd
43473779f3dSLoGin .stderr(Stdio::piped())
43573779f3dSLoGin .spawn()
43673779f3dSLoGin .map_err(|e| e.to_string())?;
43773779f3dSLoGin let output = proc.wait_with_output().map_err(|e| e.to_string())?;
43873779f3dSLoGin
43973779f3dSLoGin // 如果pull失败,且指定了branch,则报错
44073779f3dSLoGin if !output.status.success() {
44173779f3dSLoGin return Err(format!(
44273779f3dSLoGin "Failed to pull {}, message: {}",
44373779f3dSLoGin target_dir.path.display(),
44473779f3dSLoGin StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
44573779f3dSLoGin ));
44673779f3dSLoGin }
44773779f3dSLoGin
44873779f3dSLoGin return Ok(());
44973779f3dSLoGin }
45073779f3dSLoGin }
45173779f3dSLoGin
45273779f3dSLoGin /// # 本地源
45373779f3dSLoGin #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45473779f3dSLoGin pub struct LocalSource {
45573779f3dSLoGin /// 本地目录/文件的路径
45673779f3dSLoGin path: PathBuf,
45773779f3dSLoGin }
45873779f3dSLoGin
new(path: PathBuf) -> Self45973779f3dSLoGin impl LocalSource {
46073779f3dSLoGin #[allow(dead_code)]
46173779f3dSLoGin pub fn new(path: PathBuf) -> Self {
46273779f3dSLoGin Self { path }
46373779f3dSLoGin }
46473779f3dSLoGin
465*d2ade6efSJomo pub fn validate(&self, expect_file: Option<bool>) -> Result<()> {
46673779f3dSLoGin if !self.path.exists() {
467*d2ade6efSJomo return Err(Error::msg(format!("path {:?} not exists", self.path)));
46873779f3dSLoGin }
46973779f3dSLoGin
47073779f3dSLoGin if let Some(expect_file) = expect_file {
47173779f3dSLoGin if expect_file && !self.path.is_file() {
472*d2ade6efSJomo return Err(Error::msg(format!("path {:?} is not a file", self.path)));
47373779f3dSLoGin }
47473779f3dSLoGin
47573779f3dSLoGin if !expect_file && !self.path.is_dir() {
476*d2ade6efSJomo return Err(Error::msg(format!(
477*d2ade6efSJomo "path {:?} is not a directory",
478*d2ade6efSJomo self.path
479*d2ade6efSJomo )));
48073779f3dSLoGin }
48173779f3dSLoGin }
48273779f3dSLoGin
48373779f3dSLoGin return Ok(());
48473779f3dSLoGin }
48573779f3dSLoGin
48673779f3dSLoGin pub fn trim(&mut self) {}
48773779f3dSLoGin
48873779f3dSLoGin pub fn path(&self) -> &PathBuf {
48973779f3dSLoGin &self.path
49073779f3dSLoGin }
49173779f3dSLoGin }
49273779f3dSLoGin
49373779f3dSLoGin /// # 在线压缩包源
49473779f3dSLoGin #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49573779f3dSLoGin pub struct ArchiveSource {
49673779f3dSLoGin /// 压缩包的URL
new(url: String) -> Self49773779f3dSLoGin url: String,
49873779f3dSLoGin }
49973779f3dSLoGin
50073779f3dSLoGin impl ArchiveSource {
50173779f3dSLoGin #[allow(dead_code)]
50273779f3dSLoGin pub fn new(url: String) -> Self {
50373779f3dSLoGin Self { url }
50473779f3dSLoGin }
505*d2ade6efSJomo pub fn validate(&self) -> Result<()> {
50673779f3dSLoGin if self.url.is_empty() {
507*d2ade6efSJomo return Err(Error::msg("url is empty"));
50873779f3dSLoGin }
50973779f3dSLoGin
51073779f3dSLoGin // 判断是一个网址
51173779f3dSLoGin if let Ok(url) = Url::parse(&self.url) {
51273779f3dSLoGin if url.scheme() != "http" && url.scheme() != "https" {
513*d2ade6efSJomo return Err(Error::msg(format!(
514*d2ade6efSJomo "url {:?} is not a http/https url",
515*d2ade6efSJomo self.url
516*d2ade6efSJomo )));
51773779f3dSLoGin }
51873779f3dSLoGin } else {
519*d2ade6efSJomo return Err(Error::msg(format!("url {:?} is not a valid url", self.url)));
52073779f3dSLoGin }
52173779f3dSLoGin return Ok(());
52273779f3dSLoGin }
52373779f3dSLoGin
52473779f3dSLoGin pub fn trim(&mut self) {
52573779f3dSLoGin self.url = self.url.trim().to_string();
52673779f3dSLoGin }
52773779f3dSLoGin
52873779f3dSLoGin /// @brief 下载压缩包并把其中的文件提取至target_dir目录下
52973779f3dSLoGin ///
53073779f3dSLoGin ///从URL中下载压缩包到临时文件夹 target_dir/DRAGONOS_ARCHIVE_TEMP 后
53173779f3dSLoGin ///原地解压,提取文件后删除下载的压缩包。如果 target_dir 非空,就直接使用
53273779f3dSLoGin ///其中内容,不进行重复下载和覆盖
53373779f3dSLoGin ///
53473779f3dSLoGin /// @param target_dir 文件缓存目录
53573779f3dSLoGin ///
53673779f3dSLoGin /// @return 根据结果返回OK或Err
53773779f3dSLoGin pub fn download_unzip(&self, target_dir: &CacheDir) -> Result<(), String> {
53873779f3dSLoGin let url = Url::parse(&self.url).unwrap();
53973779f3dSLoGin let archive_name = url.path_segments().unwrap().last().unwrap();
54073779f3dSLoGin let path = &(target_dir.path.join("DRAGONOS_ARCHIVE_TEMP"));
54173779f3dSLoGin //如果source目录没有临时文件夹,且不为空,说明之前成功执行过一次,那么就直接使用之前的缓存
54273779f3dSLoGin if !path.exists()
54373779f3dSLoGin && !target_dir.is_empty().map_err(|e| {
54473779f3dSLoGin format!(
54573779f3dSLoGin "Failed to check if target dir is empty: {}, message: {e:?}",
54673779f3dSLoGin target_dir.path.display()
54773779f3dSLoGin )
54873779f3dSLoGin })?
54973779f3dSLoGin {
55073779f3dSLoGin //如果source文件夹非空,就直接使用,不再重复下载压缩文件,这里可以考虑加入交互
55173779f3dSLoGin info!("Source files already exist. Using previous source file cache. You should clean {:?} before re-download the archive ", target_dir.path);
55273779f3dSLoGin return Ok(());
55373779f3dSLoGin }
55473779f3dSLoGin
55573779f3dSLoGin if path.exists() {
55673779f3dSLoGin std::fs::remove_dir_all(path).map_err(|e| e.to_string())?;
55773779f3dSLoGin }
55873779f3dSLoGin //创建临时目录
55973779f3dSLoGin std::fs::create_dir(path).map_err(|e| e.to_string())?;
56073779f3dSLoGin info!("downloading {:?}", archive_name);
56173779f3dSLoGin FileUtils::download_file(&self.url, path).map_err(|e| e.to_string())?;
56273779f3dSLoGin //下载成功,开始尝试解压
56373779f3dSLoGin info!("download {:?} finished, start unzip", archive_name);
56473779f3dSLoGin let archive_file = ArchiveFile::new(&path.join(archive_name));
56573779f3dSLoGin archive_file.unzip()?;
56673779f3dSLoGin //删除创建的临时文件夹
56773779f3dSLoGin std::fs::remove_dir_all(path).map_err(|e| e.to_string())?;
56873779f3dSLoGin return Ok(());
56973779f3dSLoGin }
57073779f3dSLoGin }
new(archive_path: &PathBuf) -> Self57173779f3dSLoGin
57273779f3dSLoGin pub struct ArchiveFile {
57373779f3dSLoGin archive_path: PathBuf,
57473779f3dSLoGin archive_name: String,
57573779f3dSLoGin archive_type: ArchiveType,
57673779f3dSLoGin }
57773779f3dSLoGin
57873779f3dSLoGin impl ArchiveFile {
57973779f3dSLoGin pub fn new(archive_path: &PathBuf) -> Self {
58073779f3dSLoGin info!("archive_path: {:?}", archive_path);
58173779f3dSLoGin //匹配压缩文件类型
58273779f3dSLoGin let archive_name = archive_path.file_name().unwrap().to_str().unwrap();
58373779f3dSLoGin for (regex, archivetype) in [
58473779f3dSLoGin (Regex::new(r"^(.+)\.tar\.gz$").unwrap(), ArchiveType::TarGz),
58573779f3dSLoGin (Regex::new(r"^(.+)\.tar\.xz$").unwrap(), ArchiveType::TarXz),
58673779f3dSLoGin (Regex::new(r"^(.+)\.zip$").unwrap(), ArchiveType::Zip),
58773779f3dSLoGin ] {
58873779f3dSLoGin if regex.is_match(archive_name) {
58973779f3dSLoGin return Self {
59073779f3dSLoGin archive_path: archive_path.parent().unwrap().to_path_buf(),
59173779f3dSLoGin archive_name: archive_name.to_string(),
59273779f3dSLoGin archive_type: archivetype,
59373779f3dSLoGin };
59473779f3dSLoGin }
59573779f3dSLoGin }
59673779f3dSLoGin Self {
59773779f3dSLoGin archive_path: archive_path.parent().unwrap().to_path_buf(),
59873779f3dSLoGin archive_name: archive_name.to_string(),
59973779f3dSLoGin archive_type: ArchiveType::Undefined,
60073779f3dSLoGin }
60173779f3dSLoGin }
60273779f3dSLoGin
60373779f3dSLoGin /// @brief 对self.archive_path路径下名为self.archive_name的压缩文件(tar.gz或zip)进行解压缩
unzip(&self) -> Result<(), String>60473779f3dSLoGin ///
60573779f3dSLoGin /// 在此函数中进行路径和文件名有效性的判断,如果有效的话就开始解压缩,根据ArchiveType枚举类型来
60673779f3dSLoGin /// 生成不同的命令来对压缩文件进行解压缩,暂时只支持tar.gz和zip格式,并且都是通过调用bash来解压缩
60773779f3dSLoGin /// 没有引入第三方rust库
60873779f3dSLoGin ///
60973779f3dSLoGin ///
61073779f3dSLoGin /// @return 根据结果返回OK或Err
61173779f3dSLoGin
61273779f3dSLoGin pub fn unzip(&self) -> Result<(), String> {
61373779f3dSLoGin let path = &self.archive_path;
61473779f3dSLoGin if !path.is_dir() {
61573779f3dSLoGin return Err(format!("Archive directory {:?} is wrong", path));
61673779f3dSLoGin }
61773779f3dSLoGin if !path.join(&self.archive_name).is_file() {
61873779f3dSLoGin return Err(format!(
61973779f3dSLoGin " {:?} is not a file",
62073779f3dSLoGin path.join(&self.archive_name)
62173779f3dSLoGin ));
62273779f3dSLoGin }
62373779f3dSLoGin //根据压缩文件的类型生成cmd指令
62473779f3dSLoGin match &self.archive_type {
62573779f3dSLoGin ArchiveType::TarGz | ArchiveType::TarXz => {
62673779f3dSLoGin let mut cmd = Command::new("tar");
62773779f3dSLoGin cmd.arg("-xf").arg(&self.archive_name);
62873779f3dSLoGin let proc: std::process::Child = cmd
62973779f3dSLoGin .current_dir(path)
63073779f3dSLoGin .stderr(Stdio::piped())
63173779f3dSLoGin .stdout(Stdio::inherit())
63273779f3dSLoGin .spawn()
63373779f3dSLoGin .map_err(|e| e.to_string())?;
63473779f3dSLoGin let output = proc.wait_with_output().map_err(|e| e.to_string())?;
63573779f3dSLoGin if !output.status.success() {
63673779f3dSLoGin return Err(format!(
63773779f3dSLoGin "unzip failed, status: {:?}, stderr: {:?}",
63873779f3dSLoGin output.status,
63973779f3dSLoGin StdioUtils::tail_n_str(StdioUtils::stderr_to_lines(&output.stderr), 5)
64073779f3dSLoGin ));
64173779f3dSLoGin }
64273779f3dSLoGin }
64373779f3dSLoGin
64473779f3dSLoGin ArchiveType::Zip => {
64573779f3dSLoGin let file = File::open(&self.archive_path.join(&self.archive_name))
64673779f3dSLoGin .map_err(|e| e.to_string())?;
64773779f3dSLoGin let mut archive = ZipArchive::new(file).map_err(|e| e.to_string())?;
64873779f3dSLoGin for i in 0..archive.len() {
64973779f3dSLoGin let mut file = archive.by_index(i).map_err(|e| e.to_string())?;
65073779f3dSLoGin let outpath = match file.enclosed_name() {
65173779f3dSLoGin Some(path) => self.archive_path.join(path),
65273779f3dSLoGin None => continue,
65373779f3dSLoGin };
65473779f3dSLoGin if (*file.name()).ends_with('/') {
65573779f3dSLoGin std::fs::create_dir_all(&outpath).map_err(|e| e.to_string())?;
65673779f3dSLoGin } else {
65773779f3dSLoGin if let Some(p) = outpath.parent() {
65873779f3dSLoGin if !p.exists() {
65973779f3dSLoGin std::fs::create_dir_all(&p).map_err(|e| e.to_string())?;
66073779f3dSLoGin }
66173779f3dSLoGin }
66273779f3dSLoGin let mut outfile = File::create(&outpath).map_err(|e| e.to_string())?;
66373779f3dSLoGin std::io::copy(&mut file, &mut outfile).map_err(|e| e.to_string())?;
66473779f3dSLoGin }
66573779f3dSLoGin //设置解压后权限,在Linux中Unzip会丢失权限
66673779f3dSLoGin #[cfg(unix)]
66773779f3dSLoGin {
66873779f3dSLoGin if let Some(mode) = file.unix_mode() {
66973779f3dSLoGin std::fs::set_permissions(
67073779f3dSLoGin &outpath,
67173779f3dSLoGin std::fs::Permissions::from_mode(mode),
67273779f3dSLoGin )
67373779f3dSLoGin .map_err(|e| e.to_string())?;
67473779f3dSLoGin }
67573779f3dSLoGin }
67673779f3dSLoGin }
67773779f3dSLoGin }
67873779f3dSLoGin _ => {
67973779f3dSLoGin return Err("unsupported archive type".to_string());
68073779f3dSLoGin }
68173779f3dSLoGin }
68273779f3dSLoGin //删除下载的压缩包
68373779f3dSLoGin info!("unzip successfully, removing archive ");
68473779f3dSLoGin std::fs::remove_file(path.join(&self.archive_name)).map_err(|e| e.to_string())?;
68573779f3dSLoGin //从解压的文件夹中提取出文件并删除下载的压缩包等价于指令"cd *;mv ./* ../../"
68673779f3dSLoGin for entry in path.read_dir().map_err(|e| e.to_string())? {
68773779f3dSLoGin let entry = entry.map_err(|e| e.to_string())?;
68873779f3dSLoGin let path = entry.path();
68973779f3dSLoGin FileUtils::move_files(&path, &self.archive_path.parent().unwrap())
69073779f3dSLoGin .map_err(|e| e.to_string())?;
69173779f3dSLoGin //删除空的单独文件夹
69273779f3dSLoGin std::fs::remove_dir_all(&path).map_err(|e| e.to_string())?;
69373779f3dSLoGin }
69473779f3dSLoGin return Ok(());
69573779f3dSLoGin }
69673779f3dSLoGin }
69773779f3dSLoGin
69873779f3dSLoGin pub enum ArchiveType {
69973779f3dSLoGin TarGz,
70073779f3dSLoGin TarXz,
70173779f3dSLoGin Zip,
70273779f3dSLoGin Undefined,
70373779f3dSLoGin }
704