// IO error // A wrapper around the tokio IO functions that adds the path to the error message, instead of the uninformative std::io::Error. use std::{path::Path, io::Write}; use tempfile::NamedTempFile; use tauri::async_runtime::spawn_blocking; #[derive(Debug, thiserror::Error)] pub enum IOError { #[error("{source}, path: {path}")] IOPathError { #[source] source: std::io::Error, path: String, }, #[error(transparent)] IOError(#[from] std::io::Error), } impl IOError { pub fn from(source: std::io::Error) -> Self { Self::IOError(source) } pub fn with_path( source: std::io::Error, path: impl AsRef, ) -> Self { let path = path.as_ref(); Self::IOPathError { source, path: path.to_string_lossy().to_string(), } } } // dunce canonicalize pub fn canonicalize( path: impl AsRef, ) -> Result { let path = path.as_ref(); dunce::canonicalize(path).map_err(|e| IOError::IOPathError { source: e, path: path.to_string_lossy().to_string(), }) } // read_dir pub async fn read_dir( path: impl AsRef, ) -> Result { let path = path.as_ref(); tokio::fs::read_dir(path) .await .map_err(|e| IOError::IOPathError { source: e, path: path.to_string_lossy().to_string(), }) } // create_dir_all pub async fn create_dir_all( path: impl AsRef, ) -> Result<(), IOError> { let path = path.as_ref(); tokio::fs::create_dir_all(path) .await .map_err(|e| IOError::IOPathError { source: e, path: path.to_string_lossy().to_string(), }) } // remove_dir_all pub async fn remove_dir_all( path: impl AsRef, ) -> Result<(), IOError> { let path = path.as_ref(); tokio::fs::remove_dir_all(path) .await .map_err(|e| IOError::IOPathError { source: e, path: path.to_string_lossy().to_string(), }) } // read_to_string pub async fn read_to_string( path: impl AsRef, ) -> Result { let path = path.as_ref(); tokio::fs::read_to_string(path) .await .map_err(|e| IOError::IOPathError { source: e, path: path.to_string_lossy().to_string(), }) } // read pub async fn read( path: impl AsRef, ) -> Result, IOError> { let path = path.as_ref(); tokio::fs::read(path) .await .map_err(|e| IOError::IOPathError { source: e, path: path.to_string_lossy().to_string(), }) } // write pub async fn write( path: impl AsRef, data: impl AsRef<[u8]>, ) -> Result<(), IOError> { let path = path.as_ref().to_owned(); let data = data.as_ref().to_owned(); spawn_blocking(move || { let cloned_path = path.clone(); sync_write(data, path).map_err(|e| IOError::IOPathError { source: e, path: cloned_path.to_string_lossy().to_string(), }) }) .await .map_err(|_| { std::io::Error::new(std::io::ErrorKind::Other, "background task failed") })??; Ok(()) } fn sync_write( data: impl AsRef<[u8]>, path: impl AsRef, ) -> Result<(), std::io::Error> { let mut tempfile = NamedTempFile::new_in(path.as_ref().parent().ok_or_else(|| { std::io::Error::new( std::io::ErrorKind::Other, "could not get parent directory for temporary file", ) })?)?; tempfile.write_all(data.as_ref())?; let tmp_path = tempfile.into_temp_path(); let path = path.as_ref(); tmp_path.persist(path)?; std::io::Result::Ok(()) } // rename pub async fn rename( from: impl AsRef, to: impl AsRef, ) -> Result<(), IOError> { let from = from.as_ref(); let to = to.as_ref(); tokio::fs::rename(from, to) .await .map_err(|e| IOError::IOPathError { source: e, path: from.to_string_lossy().to_string(), }) } // copy pub async fn copy( from: impl AsRef, to: impl AsRef, ) -> Result { let from: &Path = from.as_ref(); let to = to.as_ref(); tokio::fs::copy(from, to) .await .map_err(|e| IOError::IOPathError { source: e, path: from.to_string_lossy().to_string(), }) } // remove file pub async fn remove_file( path: impl AsRef, ) -> Result<(), IOError> { let path = path.as_ref(); tokio::fs::remove_file(path) .await .map_err(|e| IOError::IOPathError { source: e, path: path.to_string_lossy().to_string(), }) }