|
|
|
@ -1,12 +1,12 @@ |
|
|
|
use std::{path::Path, time::Duration};
|
|
|
|
use std::{io::ErrorKind, path::Path, time::Duration};
|
|
|
|
|
|
|
|
use anyhow::anyhow;
|
|
|
|
use futures_util::StreamExt as _;
|
|
|
|
use http::{header::{CONTENT_TYPE, RANGE}, uri::{Authority, Scheme}, Request, Response, Uri, request::Builder as RequestBuilder};
|
|
|
|
use http::{header::{CONTENT_TYPE, RANGE}, request::Builder as RequestBuilder, uri::{Authority, Scheme}, Request, Response, StatusCode, Uri};
|
|
|
|
use http_body_util::BodyDataStream;
|
|
|
|
use m3u8_rs::{MediaPlaylist, MediaSegment, Playlist};
|
|
|
|
use reqwest::{redirect::Policy, Body};
|
|
|
|
use tokio::{fs::File, io::AsyncWriteExt as _, time::timeout};
|
|
|
|
use tokio::{fs::{symlink_metadata, File}, io::AsyncWriteExt as _, time::timeout};
|
|
|
|
use tower::{ServiceBuilder, ServiceExt as _};
|
|
|
|
use tower_http_client::{client::BodyReader, ServiceExt as _};
|
|
|
|
use tower_reqwest::HttpClientLayer;
|
|
|
|
@ -96,7 +96,22 @@ impl State { |
|
|
|
pub(super) async fn get_m3u8_segment(&mut self, uri: &Uri)
|
|
|
|
-> Result<(), DownloadError>
|
|
|
|
{
|
|
|
|
let mut response = self.request(uri, 0).await
|
|
|
|
// I consider a missing path as fatal... there is absolutely nothing we can do about it
|
|
|
|
// and we need all files from the playlist.
|
|
|
|
let filename = Path::new(uri.path())
|
|
|
|
. file_name()
|
|
|
|
. expect("no filename in path_and_query");
|
|
|
|
let metadata = match symlink_metadata(filename).await {
|
|
|
|
Ok(metadata) => Some(metadata),
|
|
|
|
Err(error) => match error.kind() {
|
|
|
|
ErrorKind::PermissionDenied => panic!("Permission denied on: {:?}", filename),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut response = self.request( uri
|
|
|
|
, metadata.map_or(0, |m| m.len()) )
|
|
|
|
. await
|
|
|
|
. map_err(|e| DownloadError::new(uri.clone(), Some(e)))?;
|
|
|
|
|
|
|
|
// We always need the content-type to be able to decide
|
|
|
|
@ -110,14 +125,23 @@ impl State { |
|
|
|
, Some(anyhow!(message)) ));
|
|
|
|
}
|
|
|
|
|
|
|
|
// I consider a missing path as fatal... there is absolutely nothing we can do about it
|
|
|
|
// and we need all files from the playlist.
|
|
|
|
let path = uri.path();
|
|
|
|
let filename = Path::new(path)
|
|
|
|
. file_name()
|
|
|
|
. expect("no filename in path_and_query");
|
|
|
|
let mut file = File::create(filename).await
|
|
|
|
. expect("can not create file for writing");
|
|
|
|
let mut file = match response.status() {
|
|
|
|
StatusCode::PARTIAL_CONTENT =>
|
|
|
|
// Here we assume that this response only comes if the requested
|
|
|
|
// range was fullfillable and thus is the data range in the
|
|
|
|
// response. Thats why I do not check the content-range header.
|
|
|
|
// If that assumption does not hold this needs to be fixec.
|
|
|
|
File::options()
|
|
|
|
. create(true)
|
|
|
|
. append(true)
|
|
|
|
. open(filename)
|
|
|
|
. await
|
|
|
|
. expect("can not create file for writing"),
|
|
|
|
|
|
|
|
_ =>
|
|
|
|
File::create(filename).await
|
|
|
|
. expect("can not create file for writing"),
|
|
|
|
};
|
|
|
|
|
|
|
|
// read body into file as stream
|
|
|
|
let mut body_stream = BodyDataStream::new(response.body_mut());
|
|
|
|
@ -133,6 +157,9 @@ impl State { |
|
|
|
file.write_all(data.as_ref()).await
|
|
|
|
. map_err(|e|
|
|
|
|
DownloadError::new(uri.clone(), Some(e.into())) )?;
|
|
|
|
file.flush().await
|
|
|
|
. map_err(|e|
|
|
|
|
DownloadError::new(uri.clone(), Some(e.into())) )?;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
};
|
|
|
|
@ -179,11 +206,11 @@ impl State { |
|
|
|
Ok(filename.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn request(&mut self, uri: &Uri, from: usize) -> anyhow::Result<Response<Body>>
|
|
|
|
async fn request(&mut self, uri: &Uri, from: u64) -> anyhow::Result<Response<Body>>
|
|
|
|
{
|
|
|
|
let request = RequestBuilder::new()
|
|
|
|
. uri(uri)
|
|
|
|
. header(RANGE, format!("{}-", from))
|
|
|
|
. header(RANGE, format!("bytes={}-", from))
|
|
|
|
. body(Body::default())?;
|
|
|
|
|
|
|
|
log!(Level::Debug, "{:?}", request);
|
|
|
|
|