fast_down_api/core/download/plan/
manifest.rs1use super::super::open_create;
2use super::{
3 DownloadPlan, Fallback, PlanCommon, PlanError, PlanKind, ResumeOutcome, ResumePlan,
4 inherit_persisted_config, plan_common, probe, state_url,
5};
6use crate::{DownloadState, Event, PartialConfig, StateError, TerminationReason, Tx, tx_err};
7use std::ffi::OsStr;
8use std::io;
9use std::path::Path;
10use std::time::Duration;
11use tokio_util::sync::CancellationToken;
12use url::Url;
13
14pub(super) struct ManifestFreshPlan {
15 common: PlanCommon,
16 outcome: ResumeOutcome,
17}
18
19impl ManifestFreshPlan {
20 pub(super) const fn new(common: PlanCommon) -> Self {
21 Self {
22 common,
23 outcome: ResumeOutcome::Fresh,
24 }
25 }
26
27 pub(super) const fn common(&self) -> &PlanCommon {
28 &self.common
29 }
30
31 pub(super) const fn outcome(&self) -> &ResumeOutcome {
32 &self.outcome
33 }
34
35 pub(super) fn into_common(self) -> PlanCommon {
36 self.common
37 }
38
39 pub(super) async fn run(self) -> TerminationReason {
40 let state = match DownloadState::load(&self.common.cfg_path).await {
41 Ok(state) => state,
42 Err(e) => {
43 let _ = self.common.tx.send(Event::ResumeError(e));
44 return TerminationReason::Failed;
45 }
46 };
47 let state = fresh_from_loaded(
48 state,
49 &self.common.partial_config,
50 &self.common.url,
51 &self.common.info,
52 );
53 if !self.common.ensure_parent().await {
54 return TerminationReason::Failed;
55 }
56 tx_err!(
57 open_create().open(&self.common.tmp_path).await,
58 self.common.tx,
59 BuildPusherError,
60 TerminationReason::Failed
61 );
62 self.common.run_existing(state, false).await
63 }
64}
65
66fn fresh_from_loaded(
67 state: DownloadState,
68 partial_config: &crate::PartialConfig,
69 url: &url::Url,
70 info: &fast_down::UrlInfo,
71) -> DownloadState {
72 state.update(|inner| {
73 if let Some(c) = &mut inner.config {
74 c.downloaded_chunk = None;
75 }
76 inner.elapsed = Some(Duration::ZERO);
77 });
78 let mut fresh_config = partial_config.clone();
79 fresh_config.downloaded_chunk = None;
80 state.merge_config(&fresh_config);
81 state.refresh_identity(url, info);
82 state
83}
84
85#[allow(clippy::result_large_err)]
101pub async fn plan_from_fd(
102 fd_path: impl AsRef<Path>,
103 url: Option<Url>,
104 mut partial_config: PartialConfig,
105 tx: Tx,
106 token: CancellationToken,
107) -> Result<DownloadPlan, PlanError> {
108 let fd_path = fd_path.as_ref();
109 if fd_path.extension() != Some(OsStr::new("fd")) {
110 return Err(PlanError::Resume(StateError::Open(io::Error::new(
111 io::ErrorKind::InvalidInput,
112 "fd_path must end with .fd extension",
113 ))));
114 }
115 partial_config.overwrite = Some(false);
116
117 let loaded = DownloadState::load(fd_path)
118 .await
119 .map_err(PlanError::Resume)?;
120 let Some(url) = state_url(url, &loaded) else {
121 return Err(PlanError::Resume(StateError::NoUrl(fd_path.to_path_buf())));
122 };
123
124 inherit_persisted_config(&mut partial_config, &loaded);
125 partial_config.overwrite = Some(false);
126 partial_config.resume = Some(true);
127 let probe = probe(&url, &partial_config, &tx).await?;
128
129 let tmp_path = fd_path.with_extension("part");
130 let state = if probe.info.fast_download {
131 match super::super::try_load_resume_state(
132 &url,
133 fd_path,
134 &tmp_path,
135 &probe.info,
136 &partial_config,
137 )
138 .await
139 {
140 Ok(Some(state)) => Some(state),
141 _ => None,
142 }
143 } else {
144 None
145 };
146 let common = plan_common(
147 url,
148 partial_config,
149 probe,
150 fd_path.to_path_buf(),
151 tmp_path,
152 tx,
153 token,
154 state.as_ref(),
155 );
156 let inner = match state {
157 Some(state) => PlanKind::Resume(ResumePlan::new(common, &state, Fallback::ManifestFresh)),
158 None => PlanKind::ManifestFresh(ManifestFreshPlan::new(common)),
159 };
160 Ok(DownloadPlan { inner })
161}