fast_down_api/core/download/plan/
fresh.rs1use super::{
2 DownloadPlan, Fallback, MismatchPlan, MismatchPolicy, PlanCommon, PlanError, PlanKind,
3 ResumeOutcome, ResumePlan, plan_common, probe, probe_resume,
4};
5use crate::{PartialConfig, StateError, TerminationReason, Tx};
6use path_helper::IterStemExt;
7use std::path::PathBuf;
8use tokio::fs;
9use tokio_util::sync::CancellationToken;
10use url::Url;
11
12pub(super) struct FreshPlan {
13 common: PlanCommon,
14 outcome: ResumeOutcome,
15}
16
17impl FreshPlan {
18 pub(super) const fn new(common: PlanCommon) -> Self {
19 Self {
20 common,
21 outcome: ResumeOutcome::Fresh,
22 }
23 }
24
25 pub(super) const fn common(&self) -> &PlanCommon {
26 &self.common
27 }
28
29 pub(super) const fn outcome(&self) -> &ResumeOutcome {
30 &self.outcome
31 }
32
33 pub(super) fn into_common(self) -> PlanCommon {
34 self.common
35 }
36
37 pub(super) async fn run(self) -> TerminationReason {
38 self.common.claim_and_run().await
39 }
40}
41
42#[allow(clippy::result_large_err)]
60pub async fn plan(
61 url: Url,
62 mut partial_config: PartialConfig,
63 tx: Tx,
64 token: CancellationToken,
65) -> Result<DownloadPlan, PlanError> {
66 partial_config.downloaded_chunk = None;
67 let probe = probe(&url, &partial_config, &tx).await?;
68 let can_resume = probe.config.resume && probe.info.fast_download;
69
70 if probe.config.overwrite {
71 plan_overwrite(url, partial_config, probe, tx, token, can_resume).await
72 } else {
73 plan_without_overwrite(url, partial_config, probe, tx, token, can_resume).await
74 }
75}
76
77#[allow(clippy::result_large_err)]
78async fn plan_overwrite(
79 url: Url,
80 partial_config: PartialConfig,
81 probe: super::Probe,
82 tx: Tx,
83 token: CancellationToken,
84 can_resume: bool,
85) -> Result<DownloadPlan, PlanError> {
86 let cfg_path = probe.final_path.with_added_extension("fd");
87 let tmp_path = probe.final_path.with_added_extension("part");
88 let resume_probe = if can_resume {
89 probe_resume(&url, &cfg_path, &tmp_path, &probe.info, &partial_config).await
90 } else {
91 (None, ResumeOutcome::Fresh)
92 };
93 let inner = match resume_probe {
94 (Some(state), _) => {
95 let common = plan_common(
96 url,
97 partial_config,
98 probe,
99 cfg_path,
100 tmp_path,
101 tx,
102 token,
103 Some(&state),
104 );
105 PlanKind::Resume(ResumePlan::new(common, &state, Fallback::Fresh))
106 }
107 (None, ResumeOutcome::Mismatch(error)) => {
108 let common = plan_common(
109 url,
110 partial_config,
111 probe,
112 cfg_path,
113 tmp_path,
114 tx,
115 token,
116 None,
117 );
118 PlanKind::Mismatch(MismatchPlan::new(common, error, MismatchPolicy::Fresh))
119 }
120 (None, _) => {
121 let common = plan_common(
122 url,
123 partial_config,
124 probe,
125 cfg_path,
126 tmp_path,
127 tx,
128 token,
129 None,
130 );
131 PlanKind::Fresh(FreshPlan::new(common))
132 }
133 };
134 Ok(DownloadPlan { inner })
135}
136
137#[allow(clippy::result_large_err)]
138async fn plan_without_overwrite(
139 url: Url,
140 partial_config: PartialConfig,
141 probe: super::Probe,
142 tx: Tx,
143 token: CancellationToken,
144 can_resume: bool,
145) -> Result<DownloadPlan, PlanError> {
146 let mut mismatch: Option<(StateError, PathBuf, PathBuf)> = None;
147 for base_path in probe.final_path.iter_stem() {
148 let tmp_path = base_path.with_added_extension("part");
149 let cfg_path = base_path.with_added_extension("fd");
150
151 let tmp_exists = fs::try_exists(&tmp_path).await.unwrap_or(false);
152 let cfg_exists = fs::try_exists(&cfg_path).await.unwrap_or(false);
153 if !tmp_exists && !cfg_exists {
154 let inner = if let Some((error, cfg_path, tmp_path)) = mismatch {
158 let common = plan_common(
159 url,
160 partial_config,
161 probe,
162 cfg_path,
163 tmp_path,
164 tx,
165 token,
166 None,
167 );
168 PlanKind::Mismatch(MismatchPlan::new(common, error, MismatchPolicy::Fresh))
169 } else {
170 let common = plan_common(
171 url,
172 partial_config,
173 probe,
174 cfg_path,
175 tmp_path,
176 tx,
177 token,
178 None,
179 );
180 PlanKind::Fresh(FreshPlan::new(common))
181 };
182 return Ok(DownloadPlan { inner });
183 }
184
185 if can_resume {
186 match super::super::try_load_resume_state(
187 &url,
188 &cfg_path,
189 &tmp_path,
190 &probe.info,
191 &partial_config,
192 )
193 .await
194 {
195 Ok(Some(state)) => {
196 let common = plan_common(
197 url,
198 partial_config,
199 probe,
200 cfg_path,
201 tmp_path,
202 tx,
203 token,
204 Some(&state),
205 );
206 return Ok(DownloadPlan {
207 inner: PlanKind::Resume(ResumePlan::new(common, &state, Fallback::Fresh)),
208 });
209 }
210 Err(e) if mismatch.is_none() => mismatch = Some((e, cfg_path, tmp_path)),
211 Ok(None) | Err(_) => {}
212 }
213 }
214 }
215 unreachable!()
216}