use crate::app_config::AppConfig; use crate::server::custom_error::HttpResult; use crate::utils::time_utils::current_day; use actix_web::HttpResponse; use anyhow::Context; use std::fs::File; use std::io::{Cursor, Read, Write}; use walkdir::WalkDir; use zip::write::SimpleFileOptions; /// Download a full copy of the storage data pub async fn download_storage() -> HttpResult { let mut zip_buff = Cursor::new(Vec::new()); let mut zip = zip::ZipWriter::new(&mut zip_buff); let options = SimpleFileOptions::default() .compression_method(zip::CompressionMethod::Bzip2) .unix_permissions(0o700); let storage = AppConfig::get().storage_path(); let mut file_buff = Vec::new(); for entry in WalkDir::new(&storage) { let entry = entry?; let path = entry.path(); let name = path.strip_prefix(&storage).unwrap(); let path_as_string = name .to_str() .map(str::to_owned) .with_context(|| format!("{name:?} Is a Non UTF-8 Path"))?; // Write file or directory explicitly // Some unzip tools unzip files with directory paths correctly, some do not! if path.is_file() { log::debug!("adding file {path:?} as {name:?} ..."); zip.start_file(path_as_string, options)?; let mut f = File::open(path)?; f.read_to_end(&mut file_buff)?; zip.write_all(&file_buff)?; file_buff.clear(); } else if !name.as_os_str().is_empty() { // Only if not root! Avoids path spec / warning // and mapname conversion failed error on unzip log::debug!("adding dir {path_as_string:?} as {name:?} ..."); zip.add_directory(path_as_string, options)?; } } // Inject runtime configuration zip.start_file("/app_config.json", options)?; zip.write_all(&serde_json::to_vec_pretty(&AppConfig::get())?)?; zip.finish()?; let filename = format!("storage-{}.zip", current_day()); Ok(HttpResponse::Ok() .content_type("application/zip") .insert_header(( "content-disposition", format!("attachment; filename=\"{filename}\""), )) .body(zip_buff.into_inner())) }