use base64::Engine;
use super::compress;
use super::toiallocator::Toi;
use crate::common::{fdtinstance, lct, oti};
use crate::error::FluteError;
use crate::tools;
use crate::tools::error::Result;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::io::BufReader;
use std::io::{Read, Seek};
use std::sync::Mutex;
use std::time::SystemTime;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum CacheControl {
NoCache,
MaxStale,
Expires(std::time::Duration),
ExpiresAt(SystemTime),
}
pub fn create_fdt_cache_control(cc: &CacheControl, now: SystemTime) -> fdtinstance::CacheControl {
match cc {
CacheControl::NoCache => fdtinstance::CacheControl {
value: fdtinstance::CacheControlChoice::NoCache(Some(true)),
},
CacheControl::MaxStale => fdtinstance::CacheControl {
value: fdtinstance::CacheControlChoice::MaxStale(Some(true)),
},
CacheControl::Expires(duration) => {
let expires = now + *duration;
let ntp = tools::system_time_to_ntp(expires).unwrap_or_default();
fdtinstance::CacheControl {
value: fdtinstance::CacheControlChoice::Expires((ntp >> 32) as u32),
}
}
CacheControl::ExpiresAt(timestamp) => {
let ntp = tools::system_time_to_ntp(*timestamp).unwrap_or_default();
fdtinstance::CacheControl {
value: fdtinstance::CacheControlChoice::Expires((ntp >> 32) as u32),
}
}
}
}
#[derive(Debug, Clone)]
pub enum TargetAcquisition {
AsFastAsPossible,
WithinDuration(std::time::Duration),
WithinTime(std::time::SystemTime),
}
pub trait ObjectDataStreamTrait:
std::io::Read + std::io::Seek + Send + Sync + std::fmt::Debug
{
}
impl<T: std::io::Read + std::io::Seek + Send + Sync + std::fmt::Debug> ObjectDataStreamTrait for T {}
impl dyn ObjectDataStreamTrait + '_ {
pub fn md5_base64(&mut self) -> Result<String> {
let md5 = self.md5()?;
Ok(base64::engine::general_purpose::STANDARD.encode(md5.0))
}
fn md5(&mut self) -> Result<md5::Digest> {
self.seek(std::io::SeekFrom::Start(0))?;
let mut reader = BufReader::new(self);
let mut context = md5::Context::new();
let mut buffer = vec![0; 102400];
loop {
let count = reader.read(&mut buffer)?;
if count == 0 {
break;
}
context.consume(&buffer[0..count]);
}
reader.seek(std::io::SeekFrom::Start(0))?;
Ok(context.finalize())
}
}
pub type ObjectDataStream = Box<dyn ObjectDataStreamTrait>;
#[derive(Debug)]
pub enum ObjectDataSource {
Stream(Mutex<ObjectDataStream>),
Buffer(Vec<u8>),
}
impl ObjectDataSource {
pub fn from_buffer(buffer: &[u8], cenc: lct::Cenc) -> Result<Self> {
let data = match cenc {
lct::Cenc::Null => Ok(buffer.to_vec()),
_ => compress::compress_buffer(buffer, cenc),
}?;
Ok(ObjectDataSource::Buffer(data))
}
pub fn from_vec(buffer: Vec<u8>, cenc: lct::Cenc) -> Result<Self> {
let data = match cenc {
lct::Cenc::Null => Ok(buffer.to_vec()),
_ => compress::compress_buffer(&buffer, cenc),
}?;
Ok(ObjectDataSource::Buffer(data))
}
pub fn from_stream(stream: ObjectDataStream) -> Self {
ObjectDataSource::Stream(Mutex::new(stream))
}
fn len(&mut self) -> Result<u64> {
match self {
ObjectDataSource::Buffer(buffer) => Ok(buffer.len() as u64),
ObjectDataSource::Stream(stream) => {
let mut stream = stream.lock().unwrap();
let current_pos = stream.stream_position()?;
let end_pos = stream.seek(std::io::SeekFrom::End(0))?;
stream.seek(std::io::SeekFrom::Start(current_pos))?;
Ok(end_pos)
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum CarouselRepeatMode {
DelayBetweenTransfers(std::time::Duration),
IntervalBetweenStartTimes(std::time::Duration),
}
#[derive(Debug)]
pub struct ObjectDesc {
pub content_location: url::Url,
pub source: ObjectDataSource,
pub content_type: String,
pub content_length: u64,
pub transfer_length: u64,
pub md5: Option<String>,
pub config: TransferConfig,
}
#[derive(Debug, typed_builder::TypedBuilder)]
pub struct TransferConfig {
#[builder(default = 1)]
pub max_transfer_count: u32,
#[builder(default, setter(strip_option))]
pub carousel_mode: Option<CarouselRepeatMode>,
#[builder(default, setter(strip_option))]
pub target_acquisition: Option<TargetAcquisition>,
#[builder(default, setter(strip_option))]
pub cache_control: Option<CacheControl>,
#[builder(default, setter(strip_option))]
pub groups: Option<Vec<String>>,
#[builder(default = lct::Cenc::Null)]
pub cenc: lct::Cenc,
#[builder(default = false)]
pub inband_cenc: bool,
#[builder(default, setter(strip_option))]
pub oti: Option<oti::Oti>,
#[builder(default, setter(strip_option))]
pub transfer_start_time: Option<SystemTime>,
#[builder(default, setter(strip_option))]
pub toi: Option<Box<Toi>>,
#[builder(default, setter(strip_option))]
pub optel_propagator: Option<HashMap<String, String>>,
#[builder(default, setter(strip_option))]
pub e_tag: Option<String>,
#[builder(default, setter(strip_option))]
pub allow_immediate_stop_before_first_transfer: Option<bool>,
}
impl Default for TransferConfig {
fn default() -> Self {
TransferConfig {
max_transfer_count: 1,
carousel_mode: None,
target_acquisition: None,
cache_control: None,
groups: None,
cenc: lct::Cenc::Null,
inband_cenc: false,
oti: None,
transfer_start_time: None,
toi: None,
optel_propagator: None,
e_tag: None,
allow_immediate_stop_before_first_transfer: None,
}
}
}
#[derive(Debug, typed_builder::TypedBuilder)]
pub struct CreateFromFile {
pub path: std::path::PathBuf,
#[builder(default)]
pub content_location: Option<url::Url>,
pub content_type: String,
#[builder(default = true)]
pub cache_in_ram: bool,
#[builder(default = true)]
pub compute_md5: bool,
#[builder(default)]
pub config: TransferConfig,
}
impl CreateFromFile {
pub fn create(self) -> Result<Box<ObjectDesc>> {
ObjectDesc::create_from_file(
&self.path,
self.content_location.as_ref(),
&self.content_type,
self.cache_in_ram,
self.compute_md5,
self.config,
)
}
}
#[derive(Debug, typed_builder::TypedBuilder)]
pub struct CreateFromStream {
pub stream: ObjectDataStream,
pub content_type: String,
pub content_location: url::Url,
#[builder(default = true)]
pub compute_md5: bool,
#[builder(default)]
pub config: TransferConfig,
}
impl CreateFromStream {
pub fn create(self) -> Result<Box<ObjectDesc>> {
ObjectDesc::create_from_stream(
self.stream,
&self.content_type,
&self.content_location,
self.compute_md5,
self.config,
)
}
}
#[derive(Debug, typed_builder::TypedBuilder)]
pub struct CreateFromBuffer {
pub content: Vec<u8>,
pub content_type: String,
pub content_location: url::Url,
#[builder(default = true)]
pub compute_md5: bool,
#[builder(default)]
pub config: TransferConfig,
}
impl CreateFromBuffer {
pub fn create(self) -> Result<Box<ObjectDesc>> {
ObjectDesc::create_from_buffer(
self.content,
&self.content_type,
&self.content_location,
self.compute_md5,
self.config,
)
}
}
impl ObjectDesc {
pub fn set_toi(&mut self, toi: Box<Toi>) {
self.config.toi = Some(toi);
}
pub fn create_from_file(
path: &std::path::Path,
content_location: Option<&url::Url>,
content_type: &str,
cache_in_ram: bool,
compute_md5: bool,
config: TransferConfig,
) -> Result<Box<ObjectDesc>> {
let content_location = match content_location {
Some(cl) => cl.clone(),
None => url::Url::parse(&format!(
"file:///{}",
path.file_name()
.unwrap_or(OsStr::new(""))
.to_str()
.unwrap_or("")
))
.unwrap_or(url::Url::parse("file:///").unwrap()),
};
if cache_in_ram {
let content = std::fs::read(path)?;
Self::create_with_content(
content,
content_type.to_string(),
content_location,
compute_md5,
config,
)
} else {
if config.cenc != lct::Cenc::Null {
return Err(FluteError::new(
"Compressed object is not compatible with file path",
));
}
let file = std::fs::File::open(path)?;
Self::create_from_stream(
Box::new(file),
content_type,
&content_location,
compute_md5,
config,
)
}
}
pub fn create_from_stream(
mut stream: ObjectDataStream,
content_type: &str,
content_location: &url::Url,
compute_md5: bool,
config: TransferConfig,
) -> Result<Box<ObjectDesc>> {
let md5 = match compute_md5 {
true => Some(stream.md5_base64()?),
false => None,
};
let mut source = ObjectDataSource::from_stream(stream);
let transfer_length = source.len()?;
Ok(Box::new(ObjectDesc {
content_location: content_location.clone(),
source,
content_type: content_type.to_string(),
content_length: transfer_length,
transfer_length,
md5,
config,
}))
}
pub fn create_from_buffer(
content: Vec<u8>,
content_type: &str,
content_location: &url::Url,
compute_md5: bool,
config: TransferConfig,
) -> Result<Box<ObjectDesc>> {
ObjectDesc::create_with_content(
content,
content_type.to_string(),
content_location.clone(),
compute_md5,
config,
)
}
fn create_with_content(
content: Vec<u8>,
content_type: String,
content_location: url::Url,
compute_md5: bool,
config: TransferConfig,
) -> Result<Box<ObjectDesc>> {
let content_length = content.len();
let md5 = match compute_md5 {
true => {
Some(base64::engine::general_purpose::STANDARD.encode(md5::compute(&content).0))
}
false => None,
};
let mut source = ObjectDataSource::from_vec(content, config.cenc)?;
let transfer_length = source.len()?;
Ok(Box::new(ObjectDesc {
content_location,
source,
content_type,
content_length: content_length as u64,
transfer_length,
md5,
config,
}))
}
}