Skip to content

Commit 9ab16e8

Browse files
committed
refactor: 重构 chromium 的下载逻辑。
1 parent 825412e commit 9ab16e8

File tree

9 files changed

+263
-162
lines changed

9 files changed

+263
-162
lines changed

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ version = "0.1.1"
44
edition = "2021"
55
authors = ["hamflx <[email protected]>"]
66
license = "MIT"
7-
license-file = "LICENSE"
87

98
[[bin]]
109
name = "fb"

src/builds.rs renamed to src/chromium/builds.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ impl Iterator for ChromiumBuildsPage {
9696
}
9797
}
9898

99+
pub(crate) fn fetch_build_detail(prefix: &str) -> Result<Vec<GoogleApiStorageObject>> {
100+
let url = format!("https://www.googleapis.com/storage/v1/b/chromium-browser-snapshots/o?delimiter=/&prefix={prefix}&fields=items(kind,mediaLink,metadata,name,size,updated),kind,prefixes,nextPageToken");
101+
println!("==> fetching history {url} ...");
102+
let response = reqwest::blocking::get(url)?;
103+
let build_detail: ChromiumBuildPage = serde_json::from_reader(response)?;
104+
println!("==> files:");
105+
for file in &build_detail.items {
106+
println!(" {}", file.name);
107+
}
108+
Ok(build_detail.items)
109+
}
110+
99111
#[derive(Debug, Serialize, Deserialize)]
100112
#[serde(rename_all = "camelCase")]
101113
pub(crate) struct ChromiumBuildPage {

src/chromium/download.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use std::{fs::OpenOptions, io::copy, path::Path};
2+
3+
use anyhow::anyhow;
4+
use zip::read::read_zipfile_from_stream;
5+
6+
use super::builds::GoogleApiStorageObject;
7+
8+
pub(crate) fn download_chromium_zip_file(
9+
zip_file: &GoogleApiStorageObject,
10+
base_path: &Path,
11+
) -> std::result::Result<(), anyhow::Error> {
12+
// 开始下载压缩文件。
13+
println!("==> downloading {}", zip_file.media_link);
14+
let mut win_zip_response = reqwest::blocking::get(&zip_file.media_link)?;
15+
16+
loop {
17+
let mut zip = match read_zipfile_from_stream(&mut win_zip_response) {
18+
Ok(Some(zip)) => zip,
19+
Ok(None) => break,
20+
Err(err) => return Err(anyhow!("读取压缩文件出错:{:?}", err)),
21+
};
22+
23+
let zip_name = zip.name();
24+
println!("==> unzip: {zip_name}");
25+
26+
if zip_name.starts_with("chrome-win/")
27+
|| zip_name.starts_with("chrome-win32/")
28+
|| zip_name.starts_with("chrome-mac/")
29+
|| zip_name.starts_with("chrome-linux/")
30+
{
31+
let prefix_len = zip_name.find('/').unwrap() + 1;
32+
let file_path = base_path.join(&zip_name[prefix_len..]);
33+
if zip.is_dir() {
34+
std::fs::create_dir_all(&file_path).map_err(|err| {
35+
anyhow!(
36+
"创建目录 {} 时出错:{:?}",
37+
file_path.to_str().unwrap_or_default(),
38+
err
39+
)
40+
})?;
41+
} else {
42+
if let Some(parent_dir) = file_path.parent() {
43+
let _ = std::fs::create_dir_all(parent_dir);
44+
}
45+
copy(
46+
&mut zip,
47+
&mut OpenOptions::new()
48+
.write(true)
49+
.truncate(true)
50+
.create(true)
51+
.open(&file_path)
52+
.map_err(|err| {
53+
anyhow!(
54+
"解压文件 {} 时出错:{:?}",
55+
file_path.to_str().unwrap_or_default(),
56+
err
57+
)
58+
})?,
59+
)
60+
.map_err(|err| {
61+
anyhow!(
62+
"解压文件 {} 时出错:{:?}",
63+
file_path.to_str().unwrap_or_default(),
64+
err
65+
)
66+
})?;
67+
}
68+
} else {
69+
return Err(anyhow!("压缩包文件结构不正确。"));
70+
}
71+
}
72+
73+
Ok(())
74+
}
File renamed without changes.

src/chromium/mod.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use std::vec::IntoIter;
2+
3+
use anyhow::{anyhow, Result};
4+
5+
use crate::{
6+
common::{BrowserReleaseItem, BrowserReleases},
7+
platform::Platform,
8+
};
9+
10+
use self::{
11+
builds::{fetch_build_detail, ChromiumBuilds},
12+
download::download_chromium_zip_file,
13+
history::{ChromiumHistory, ChromiumHistoryInfo},
14+
};
15+
16+
mod builds;
17+
mod download;
18+
mod history;
19+
mod version;
20+
21+
pub(crate) struct ChromiumReleases {
22+
platform: Platform,
23+
history: ChromiumHistory,
24+
builds: ChromiumBuilds,
25+
}
26+
27+
impl BrowserReleases for ChromiumReleases {
28+
type ReleaseItem = ChromiumReleaseItem;
29+
type Matches<'r> = ChromiumReleaseMatches<'r>;
30+
31+
fn init(platform: Platform) -> anyhow::Result<Self>
32+
where
33+
Self: Sized,
34+
{
35+
// history.json 包含了 base_position 和版本号。
36+
let history = ChromiumHistory::init(platform)?;
37+
// builds 包含了所有可下载的 position 信息。
38+
let builds = ChromiumBuilds::init(platform)?;
39+
Ok(Self {
40+
platform,
41+
history,
42+
builds,
43+
})
44+
}
45+
46+
fn match_version<'r>(&'r self, version: &str) -> Self::Matches<'r> {
47+
ChromiumReleaseMatches::new(self, self.history.find(version))
48+
}
49+
}
50+
51+
pub(crate) struct ChromiumReleaseMatches<'r> {
52+
iter: IntoIter<&'r ChromiumHistoryInfo>,
53+
releases: &'r ChromiumReleases,
54+
prefix: &'static str,
55+
}
56+
57+
impl<'r> ChromiumReleaseMatches<'r> {
58+
fn new(releases: &'r ChromiumReleases, items: Vec<&'r ChromiumHistoryInfo>) -> Self {
59+
let prefix = releases.platform.prefix();
60+
Self {
61+
releases,
62+
iter: items.into_iter(),
63+
prefix,
64+
}
65+
}
66+
}
67+
68+
impl<'r> Iterator for ChromiumReleaseMatches<'r> {
69+
type Item = Result<ChromiumReleaseItem>;
70+
71+
fn next(&mut self) -> Option<Self::Item> {
72+
for history in self.iter.by_ref() {
73+
let deps = match history.deps() {
74+
Ok(deps) => deps,
75+
Err(err) => return Some(Err(err)),
76+
};
77+
match deps.chromium_base_position {
78+
Some(pos) => match pos.parse::<usize>() {
79+
Ok(pos) => match self.releases.builds.find(pos, self.prefix) {
80+
Some(rev_prefix) => {
81+
return Some(Ok(ChromiumReleaseItem {
82+
rev_prefix: rev_prefix.clone(),
83+
version: deps.chromium_version,
84+
}))
85+
}
86+
None => println!("==> no build found for rev: {pos}"),
87+
},
88+
Err(err) => println!(
89+
"==> chromium {}: parse base_position error: {:?}",
90+
deps.chromium_version, err
91+
),
92+
},
93+
None => println!(
94+
"==> chromium {}: no chromium_base_position.",
95+
deps.chromium_version
96+
),
97+
}
98+
}
99+
None
100+
}
101+
}
102+
103+
pub(crate) struct ChromiumReleaseItem {
104+
rev_prefix: String,
105+
version: String,
106+
}
107+
108+
impl BrowserReleaseItem for ChromiumReleaseItem {
109+
fn download(&self) -> Result<()> {
110+
// 根据 prefix 找到该版本文件列表,以及 chrome-win.zip 文件信息。
111+
let build_files = fetch_build_detail(&self.rev_prefix)?;
112+
let zip_file = [
113+
"chrome-win.zip",
114+
"chrome-win32.zip",
115+
"chrome-mac.zip",
116+
"chrome-linux.zip",
117+
]
118+
.into_iter()
119+
.find_map(|f| build_files.iter().find(|file| file.name.ends_with(f)))
120+
.ok_or_else(|| {
121+
anyhow!(
122+
"在版本 {} 中,未找到 chrome-win.zip/chrome-win32-zip/chrome-mac.zip。",
123+
self.rev_prefix
124+
)
125+
})?;
126+
127+
// 先保存到临时目录里面,待解压的时候,找到里面的版本信息,再重命名一下文件夹。
128+
let base_path = std::env::current_dir()?.join(format!("chromium-{}", self.version));
129+
std::fs::create_dir_all(&base_path)?;
130+
download_chromium_zip_file(zip_file, &base_path)
131+
}
132+
}
File renamed without changes.

src/common.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use anyhow::Result;
2+
3+
use crate::platform::Platform;
4+
5+
pub(crate) trait BrowserReleases {
6+
type ReleaseItem: BrowserReleaseItem;
7+
type Matches<'r>: Iterator<Item = Result<Self::ReleaseItem>>
8+
where
9+
Self: 'r;
10+
11+
fn init(platform: Platform) -> Result<Self>
12+
where
13+
Self: Sized;
14+
15+
fn match_version<'r>(&'r self, version: &str) -> Self::Matches<'r>;
16+
}
17+
18+
pub(crate) trait BrowserReleaseItem {
19+
fn download(&self) -> Result<()>;
20+
}

src/ff/mod.rs renamed to src/firefox/mod.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use select::{
1010

1111
use crate::utils::{find_sequence, get_cached_file_path};
1212

13-
pub(crate) fn download_ff(version: &str) -> Result<()> {
13+
pub(crate) fn download_firefox(version: &str) -> Result<()> {
1414
let cur_dir = current_dir()?;
1515

1616
let spider = FirefoxVersionSpider::init()?;
@@ -19,9 +19,9 @@ pub(crate) fn download_ff(version: &str) -> Result<()> {
1919
.first()
2020
.ok_or_else(|| anyhow!("No matched version found"))?;
2121

22-
let zip_content = download_ff_zip(matched_version, "win64").or_else(|err| {
22+
let zip_content = download_firefox_zip(matched_version, "win64").or_else(|err| {
2323
println!("==> download firefox win64 failed: {err}, trying win32 ...");
24-
download_ff_zip(matched_version, "win32")
24+
download_firefox_zip(matched_version, "win32")
2525
})?;
2626

2727
let base_path = cur_dir.join(format!(".tmp-firefox-{matched_version}"));
@@ -46,7 +46,7 @@ pub(crate) fn download_ff(version: &str) -> Result<()> {
4646
Ok(())
4747
}
4848

49-
pub(crate) fn download_ff_zip(version: &str, arch: &str) -> Result<Bytes> {
49+
fn download_firefox_zip(version: &str, arch: &str) -> Result<Bytes> {
5050
let cur_dir = current_dir()?;
5151
let url = format!(
5252
"https://ftp.mozilla.org/pub/firefox/releases/{version}/{arch}/zh-CN/Firefox%20Setup%20{version}.exe"
@@ -72,10 +72,10 @@ pub(crate) fn download_ff_zip(version: &str, arch: &str) -> Result<Bytes> {
7272
}
7373

7474
#[derive(Debug)]
75-
pub(crate) struct FirefoxVersionSpider(Vec<String>);
75+
struct FirefoxVersionSpider(Vec<String>);
7676

7777
impl FirefoxVersionSpider {
78-
pub(crate) fn init() -> Result<Self> {
78+
fn init() -> Result<Self> {
7979
let cached_releases_path = get_cached_file_path("firefox-releases.json")?;
8080
if cached_releases_path.exists() {
8181
println!("==> using cached firefox releases");
@@ -93,7 +93,7 @@ impl FirefoxVersionSpider {
9393
.descendant(predicate::Name("a")),
9494
)
9595
.map(|node| node.text().trim_end_matches('/').to_owned())
96-
.filter(|name| is_valid_version(name.as_str()))
96+
.filter(|name| is_valid_ff_version(name.as_str()))
9797
.collect::<Vec<_>>();
9898

9999
std::fs::write(&cached_releases_path, serde_json::to_string(&releases)?)?;
@@ -102,7 +102,7 @@ impl FirefoxVersionSpider {
102102
}
103103
}
104104

105-
pub(crate) fn find(&self, version: &str) -> Vec<&String> {
105+
fn find(&self, version: &str) -> Vec<&String> {
106106
let mut matched_list = self
107107
.0
108108
.iter()
@@ -127,7 +127,7 @@ impl FirefoxVersionSpider {
127127
}
128128
}
129129

130-
pub(crate) fn is_valid_version(version: &str) -> bool {
130+
fn is_valid_ff_version(version: &str) -> bool {
131131
let mut split = version.split('.');
132132
match (split.next(), split.next()) {
133133
(Some(first), Some(second)) => {

0 commit comments

Comments
 (0)