Files
aria2-rust-pro/crates/aria2-rust-pro-protocol/src/http/progress.rs
T

178 lines
6.3 KiB
Rust

use super::{
checksum::{streamed_checksum_verification, usize_to_u64},
model::{
HttpCompletionModel, HttpCompletionState, HttpResponseModel, HttpRetryAttemptDetailModel,
HttpSegmentProgressModel, HttpTransferProgressModel, HttpTransferTaskModel, ResponseBody,
},
};
impl HttpTransferTaskModel {
#[must_use]
/// Returns the effective requested offset for the task.
pub fn requested_offset(&self) -> u64 {
self.resume_state
.as_ref()
.map(|state| state.requested_offset)
.or_else(|| self.request.range.as_ref().map(|range| range.start))
.unwrap_or_default()
}
#[must_use]
/// Returns retry attempts enriched with resume and range context.
pub fn retry_attempt_details(&self) -> Vec<HttpRetryAttemptDetailModel> {
let requested_offset = self.requested_offset();
let requested_range = self.request.range;
let accepted_offset = self
.resume_state
.as_ref()
.and_then(|state| state.accepted_offset);
let resume_state = self.resume_state;
self.retry_attempts
.iter()
.copied()
.map(|base| HttpRetryAttemptDetailModel {
base,
requested_range,
requested_offset,
accepted_offset,
resume_state,
})
.collect()
}
#[must_use]
/// Builds a progress snapshot from the current task and optional response.
pub fn progress_snapshot(
&self,
response: Option<&HttpResponseModel>,
) -> HttpTransferProgressModel {
let completion = response.map(HttpResponseModel::completion_model);
let completed_offset = response.map(HttpResponseModel::completed_length);
let (accepted_offset, resumed) = response.map_or_else(
|| {
(
self.resume_state
.as_ref()
.and_then(|state| state.accepted_offset),
self.resume_state
.as_ref()
.is_some_and(|state| state.resumed),
)
},
|response| {
if response.status == 206 {
let accepted_offset = response
.content_range
.as_ref()
.and_then(|range| (!range.is_unsatisfied()).then_some(range.start));
(
accepted_offset,
accepted_offset.is_some()
|| self
.resume_state
.as_ref()
.is_some_and(|state| state.resumed),
)
} else {
(None, false)
}
},
);
HttpTransferProgressModel {
task_id: self.task_id.clone(),
request: self.request.clone(),
segment: HttpSegmentProgressModel {
requested_range: self.request.range,
requested_offset: self.requested_offset(),
accepted_offset,
completed_offset,
resumed,
},
retry_attempts: self.retry_attempt_details(),
max_connections: self.max_connections,
checksum_hook: self.checksum_hook.clone(),
completion,
}
}
}
impl HttpResponseModel {
#[must_use]
/// Returns the total payload length when the response exposes it.
pub fn total_length(&self) -> Option<u64> {
self.content_range
.as_ref()
.and_then(|range| range.total_size)
.or_else(|| match &self.body {
ResponseBody::Inline(bytes) => Some(usize_to_u64(bytes.len())),
ResponseBody::Streamed {
expected_len,
observed_len,
..
} => expected_len.or(*observed_len),
ResponseBody::Empty => None,
})
}
#[must_use]
/// Returns the completed payload length represented by the response.
pub fn completed_length(&self) -> u64 {
self.content_range
.as_ref()
.map(super::model::ContentRangeSpec::completed_length)
.or_else(|| match &self.body {
ResponseBody::Inline(bytes) => Some(usize_to_u64(bytes.len())),
ResponseBody::Streamed { observed_len, .. } => *observed_len,
ResponseBody::Empty => Some(0),
})
.unwrap_or_default()
}
#[must_use]
/// Returns inline body bytes when they are retained in memory.
pub fn body_bytes(&self) -> Option<&[u8]> {
match &self.body {
ResponseBody::Empty => Some(&[]),
ResponseBody::Inline(bytes) => Some(bytes),
ResponseBody::Streamed { .. } => None,
}
}
#[must_use]
/// Derives a completion summary from the response payload and metadata.
pub fn completion_model(&self) -> HttpCompletionModel {
let total_length = self.total_length();
let completed_length = self.completed_length();
let checksum_seen = self.checksum.is_some();
let checksum_verified = self.checksum.as_ref().is_some_and(|checksum| {
self.body_bytes()
.and_then(|bytes| checksum.verify_payload(bytes))
.or_else(|| streamed_checksum_verification(checksum, &self.body))
.unwrap_or_else(|| checksum.is_verified())
});
let terminal_success = (200..300).contains(&self.status);
let complete_enough = total_length.is_none_or(|total| completed_length >= total);
let state = if !terminal_success {
HttpCompletionState::Incomplete
} else if checksum_verified && complete_enough {
HttpCompletionState::Verified
} else if complete_enough {
HttpCompletionState::Complete
} else {
HttpCompletionState::Partial
};
HttpCompletionModel {
state,
total_length,
completed_length,
partial_content: self.partial_content,
terminal_success,
checksum_seen,
checksum_verified,
}
}
}