use crate::download_metadata::DownloadEngine;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct EngineCapabilities {
pub multipart: bool,
pub server_checksums: bool,
pub response_headers: bool,
pub exact_size: bool,
pub multi_file: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Engine {
HttpMultipart,
Ytdlp,
Unresolved,
}
impl From<Engine> for DownloadEngine {
fn from(engine: Engine) -> Self {
match engine {
Engine::HttpMultipart => DownloadEngine::HttpMultipart,
Engine::Ytdlp => DownloadEngine::Ytdlp,
Engine::Unresolved => DownloadEngine::Unresolved,
}
}
}
impl From<DownloadEngine> for Engine {
fn from(engine: DownloadEngine) -> Self {
match engine {
DownloadEngine::HttpMultipart => Engine::HttpMultipart,
DownloadEngine::Ytdlp => Engine::Ytdlp,
DownloadEngine::Unresolved => Engine::Unresolved,
}
}
}
impl fmt::Display for Engine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl Engine {
fn capabilities_of(&self) -> EngineCapabilities {
match self {
Engine::HttpMultipart => EngineCapabilities {
multipart: true,
server_checksums: true,
response_headers: true,
exact_size: true,
multi_file: false,
},
Engine::Ytdlp => EngineCapabilities {
multipart: false,
server_checksums: false,
response_headers: false,
exact_size: false,
multi_file: false,
},
Engine::Unresolved => EngineCapabilities {
multipart: false,
server_checksums: false,
response_headers: false,
exact_size: false,
multi_file: false,
},
}
}
pub fn capabilities(&self) -> EngineCapabilities {
self.capabilities_of()
}
pub fn as_str(&self) -> &'static str {
match self {
Engine::HttpMultipart => "http_multipart",
Engine::Ytdlp => "ytdlp",
Engine::Unresolved => "unresolved",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum EnginePreference {
#[default]
Auto,
Engine(Engine),
}
impl EnginePreference {
pub fn forced(&self) -> Option<Engine> {
match self {
EnginePreference::Auto => None,
EnginePreference::Engine(e) => Some(*e),
}
}
}
impl fmt::Display for EnginePreference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EnginePreference::Auto => f.write_str("auto"),
EnginePreference::Engine(e) => f.write_str(e.as_str()),
}
}
}
impl FromStr for EnginePreference {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"auto" => Ok(EnginePreference::Auto),
"http" | "http_multipart" => Ok(EnginePreference::Engine(Engine::HttpMultipart)),
"ytdlp" | "yt-dlp" => Ok(EnginePreference::Engine(Engine::Ytdlp)),
other => Err(format!(
"unknown engine {other:?} (expected one of: auto, http, ytdlp)"
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn absent_proto_value_is_the_legacy_engine() {
assert_eq!(
DownloadEngine::try_from(0).unwrap(),
DownloadEngine::HttpMultipart
);
}
#[test]
fn preference_parses_accepted_spellings() {
assert_eq!(
"auto".parse::<EnginePreference>().unwrap(),
EnginePreference::Auto
);
assert_eq!(
" HTTP ".parse::<EnginePreference>().unwrap(),
EnginePreference::Engine(Engine::HttpMultipart)
);
assert_eq!(
"yt-dlp".parse::<EnginePreference>().unwrap(),
EnginePreference::Engine(Engine::Ytdlp)
);
assert!("bittorrent".parse::<EnginePreference>().is_err());
}
#[test]
fn preference_round_trips_through_display() {
for s in ["auto", "http_multipart", "ytdlp"] {
let p: EnginePreference = s.parse().unwrap();
assert_eq!(p.to_string(), s);
}
}
#[test]
fn delegated_engine_cannot_report_server_metadata() {
let caps = Engine::Ytdlp.capabilities();
assert!(!caps.server_checksums);
assert!(!caps.response_headers);
assert!(!caps.multipart);
assert!(Engine::HttpMultipart.capabilities().multipart);
}
#[test]
fn the_public_engine_round_trips_through_the_persisted_one() {
for engine in [Engine::HttpMultipart, Engine::Ytdlp, Engine::Unresolved] {
let stored: DownloadEngine = engine.into();
assert_eq!(Engine::from(stored), engine);
assert_eq!(stored.as_str_name(), engine.as_str());
}
}
}