Created
November 7, 2024 13:00
-
-
Save VirtualPirate/bbaaf2b8d34991b6c7ad8e3694bb6dca to your computer and use it in GitHub Desktop.
This is a rust program that downloads a file in multi segments. Making multiple connections to the url, download all the segments in parallel and merge the downloaded segments to the final file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "multipart-downloader" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
reqwest = { version = "0.12.9", features = ["json", "stream"] } | |
tokio = { version = "1", features = ["full"] } | |
futures = "0.3" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use futures::future::join_all; | |
use futures::TryStreamExt; | |
use std::path::Path; | |
use tokio::fs as async_fs; | |
use tokio::io::{AsyncWriteExt, BufWriter}; | |
struct DownloadSegment { | |
start: u64, | |
end: u64, | |
index: usize, | |
} | |
async fn get_file_size(url: &str) -> Result<u64, reqwest::Error> { | |
let client = reqwest::Client::new(); | |
let response = client.head(url).send().await?; | |
let content_length = response | |
.headers() | |
.get(reqwest::header::CONTENT_LENGTH) | |
.and_then(|val| val.to_str().ok()) | |
.and_then(|val| val.parse::<u64>().ok()) | |
.unwrap_or(0); | |
Ok(content_length) | |
} | |
async fn download_segment( | |
url: &str, | |
segment: DownloadSegment, | |
output_dir: &str, | |
) -> Result<String, Box<dyn std::error::Error>> { | |
let DownloadSegment { start, end, index } = segment; | |
println!("Segment Download started start: {} end: {}", start, end); | |
let output_path = format!("{}/part_{}", output_dir, index); | |
let mut file = BufWriter::new(async_fs::File::create(&output_path).await?); | |
let client = reqwest::Client::new(); | |
let range_header = format!("bytes={}-{}", start, end); | |
let response = client.get(url).header("Range", range_header).send().await?; | |
let mut stream = response.bytes_stream(); | |
while let Some(chunk) = stream.try_next().await? { | |
file.write_all(&chunk).await?; | |
} | |
file.flush().await?; | |
println!("Downloaded segment {} (bytes {}-{})", index, start, end); | |
Ok(output_path) | |
} | |
async fn download_file( | |
url: &str, | |
segments: usize, | |
output_file_path: &str, | |
) -> Result<(), Box<dyn std::error::Error>> { | |
let file_size = get_file_size(url).await?; | |
let segment_size = (file_size as f64 / segments as f64).ceil() as u64; | |
let output_dir = Path::new(output_file_path).parent().unwrap(); | |
let mut segment_paths = Vec::new(); | |
let mut segment_futures = Vec::new(); | |
for index in 0..segments { | |
let start = index as u64 * segment_size; | |
let end = if index == segments - 1 { | |
file_size - 1 | |
} else { | |
start + segment_size - 1 | |
}; | |
let segment = DownloadSegment { start, end, index }; | |
let url = url.to_string(); | |
let output_dir = output_dir.to_str().unwrap().to_string(); | |
segment_futures.push(async move { download_segment(&url, segment, &output_dir).await }); | |
} | |
let results = join_all(segment_futures).await; | |
for result in results { | |
match result { | |
Ok(path) => segment_paths.push(path), | |
Err(e) => return Err(e), | |
} | |
} | |
// Merge all parts | |
segment_paths.sort(); | |
let mut write_stream = async_fs::File::create(output_file_path).await?; | |
for segment_path in segment_paths { | |
let data = async_fs::read(&segment_path).await?; | |
write_stream.write_all(&data).await?; | |
async_fs::remove_file(segment_path).await?; // Delete segment after merging | |
} | |
println!("Download complete: {}", output_file_path); | |
Ok(()) | |
} | |
#[tokio::main] | |
async fn main() { | |
let url = "https://rr4---sn-qxaeenl6.googlevideo.com/videoplayback?expire=1731000837&ei=pKUsZ_OXNpKJ9fwP3NO2kAc&ip=34.96.33.87&id=o-AKkWUuNwaTJIgO01fTpJnQs7F2_458w2KqLEX4xhDQjF&itag=137&source=youtube&requiressl=yes&xpc=EgVo2aDSNQ%3D%3D&met=1730979237%2C&mh=DQ&mm=31%2C29&mn=sn-qxaeenl6%2Csn-qxaelned&ms=au%2Crdu&mv=u&mvi=4&pl=27&rms=au%2Cau&vprv=1&svpuc=1&mime=video%2Fmp4&rqh=1&gir=yes&clen=101803581&dur=635.720&lmt=1726430152436097&mt=1730979090&fvip=2&keepalive=yes&fexp=51312688%2C51326932&c=IOS&txp=5535434&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cxpc%2Cvprv%2Csvpuc%2Cmime%2Crqh%2Cgir%2Cclen%2Cdur%2Clmt&sig=AJfQdSswRAIgWLZzJ20EWzch7xywvZuuRRk5sxubZli9CNkRIRMx6QICIENKy_k_PjSWKYVF0tV7eSyzbEIlebNcA4qGkMqepbKN&lsparams=met%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Crms&lsig=ACJ0pHgwRAIgC3OHLLVbbxFtUgic47v2ENYwkJYbSt0lrNWpIveaHOQCIF4wLMfSZmaCmYPrytRux6dBgRpCo_Cqdq3riZNNl-am"; | |
let output_file_path = Path::new("./largefile.mp4"); | |
match download_file(url, 10, output_file_path.to_str().unwrap()).await { | |
Ok(_) => println!("File downloaded successfully"), | |
Err(error) => eprintln!("Download failed: {:?}", error), | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment