use crate::download_manager::application_download_error::ApplicationDownloadError; use crate::download_manager::download_thread_control_flag::{ DownloadThreadControl, DownloadThreadControlFlag, }; use crate::download_manager::progress_object::ProgressHandle; use crate::games::downloads::manifest::DropDownloadContext; use crate::remote::RemoteAccessError; use log::{error, warn}; use md5::{Context, Digest}; use reqwest::blocking::{RequestBuilder, Response}; use std::fs::{set_permissions, Permissions}; use std::io::Read; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::{ fs::{File, OpenOptions}, io::{self, BufWriter, Seek, SeekFrom, Write}, path::PathBuf, }; pub struct DropWriter { hasher: Context, destination: W, } impl DropWriter { fn new(path: PathBuf) -> Self { Self { destination: OpenOptions::new().write(true).open(path).unwrap(), hasher: Context::new(), } } fn finish(mut self) -> io::Result { self.flush().unwrap(); Ok(self.hasher.compute()) } } // Write automatically pushes to file and hasher impl Write for DropWriter { fn write(&mut self, buf: &[u8]) -> io::Result { /* self.hasher.write_all(buf).map_err(|e| { io::Error::new( ErrorKind::Other, format!("Unable to write to hasher: {}", e), ) })?; */ self.destination.write(buf) } fn flush(&mut self) -> io::Result<()> { // self.hasher.flush()?; self.destination.flush() } } // Seek moves around destination output impl Seek for DropWriter { fn seek(&mut self, pos: SeekFrom) -> io::Result { self.destination.seek(pos) } } pub struct DropDownloadPipeline<'a, R: Read, W: Write> { pub source: R, pub destination: DropWriter, pub control_flag: &'a DownloadThreadControl, pub progress: ProgressHandle, pub size: usize, } impl<'a> DropDownloadPipeline<'a, Response, File> { fn new( source: Response, destination: DropWriter, control_flag: &'a DownloadThreadControl, progress: ProgressHandle, size: usize, ) -> Self { Self { source, destination, control_flag, progress, size, } } fn copy(&mut self) -> Result { let copy_buf_size = 512; let mut copy_buf = vec![0; copy_buf_size]; let mut buf_writer = BufWriter::with_capacity(1024 * 1024, &mut self.destination); let mut current_size = 0; loop { if self.control_flag.get() == DownloadThreadControlFlag::Stop { return Ok(false); } let bytes_read = self.source.read(&mut copy_buf)?; current_size += bytes_read; buf_writer.write_all(©_buf[0..bytes_read])?; self.progress.add(bytes_read); if current_size == self.size { break; } } Ok(true) } fn finish(self) -> Result { let checksum = self.destination.finish()?; Ok(checksum) } } pub fn download_game_chunk( ctx: &DropDownloadContext, control_flag: &DownloadThreadControl, progress: ProgressHandle, request: RequestBuilder, ) -> Result { // If we're paused if control_flag.get() == DownloadThreadControlFlag::Stop { progress.set(0); return Ok(false); } let response = request .send() .map_err(|e| ApplicationDownloadError::Communication(e.into()))?; if response.status() != 200 { warn!("{}", response.text().unwrap()); return Err(ApplicationDownloadError::Communication( RemoteAccessError::InvalidCodeError(400), )); } let mut destination = DropWriter::new(ctx.path.clone()); if ctx.offset != 0 { destination .seek(SeekFrom::Start(ctx.offset)) .expect("Failed to seek to file offset"); } let content_length = response.content_length(); if content_length.is_none() { error!("Recieved 0 length content from server"); return Err(ApplicationDownloadError::Communication( RemoteAccessError::InvalidResponse(response.json().unwrap()), )); } let mut pipeline = DropDownloadPipeline::new( response, destination, control_flag, progress, content_length.unwrap().try_into().unwrap(), ); let completed = pipeline .copy() .map_err(|e| ApplicationDownloadError::IoError(e.kind()))?; if !completed { return Ok(false); }; // If we complete the file, set the permissions (if on Linux) #[cfg(unix)] { let permissions = Permissions::from_mode(ctx.permissions); set_permissions(ctx.path.clone(), permissions).unwrap(); } /* let checksum = pipeline .finish() .map_err(|e| GameDownloadError::IoError(e))?; let res = hex::encode(checksum.0); if res != ctx.checksum { return Err(GameDownloadError::Checksum); } */ Ok(true) }