mirror of
https://github.com/shiroyashik/sculptor.git
synced 2025-12-06 13:01:12 +03:00
Assets seeker more effective, directories changed, added assets auto check/update.
This commit is contained in:
parent
7a37ce8a6b
commit
325a73da5a
11 changed files with 550 additions and 60 deletions
|
|
@ -3,36 +3,47 @@ use std::path::PathBuf;
|
|||
use axum::{extract::Path, routing::get, Json, Router};
|
||||
use indexmap::IndexMap;
|
||||
use ring::digest::{digest, SHA256};
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::Value;
|
||||
use tokio::{fs, io::AsyncReadExt as _};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::{api::errors::internal_and_log, ApiError, ApiResult, AppState};
|
||||
use crate::{api::errors::internal_and_log, ApiError, ApiResult, AppState, ASSETS_ENV};
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(versions))
|
||||
.route("/v1", get(v1))
|
||||
.route("/v2", get(v2))
|
||||
.route("/*path", get(download))
|
||||
.route("/:version", get(hashes))
|
||||
.route("/:version/*key", get(download))
|
||||
}
|
||||
|
||||
async fn versions() -> Json<Value> {
|
||||
Json(json!(["v1", "v2"]))
|
||||
async fn versions() -> ApiResult<Json<Value>> {
|
||||
let dir_path = PathBuf::from(&std::env::var(ASSETS_ENV).unwrap());
|
||||
|
||||
let mut directories = Vec::new();
|
||||
|
||||
let mut entries = fs::read_dir(dir_path).await.map_err(|err| internal_and_log(err))?;
|
||||
|
||||
while let Some(entry) = entries.next_entry().await.map_err(|err| internal_and_log(err))? {
|
||||
if entry.metadata().await.map_err(|err| internal_and_log(err))?.is_dir() {
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
let name = name.to_string();
|
||||
if !name.starts_with('.') {
|
||||
directories.push(Value::String(name.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Json(serde_json::Value::Array(directories)))
|
||||
}
|
||||
|
||||
async fn v1() -> ApiResult<Json<IndexMap<String, Value>>> {
|
||||
let map = index_assets("v1").await.map_err(|err| internal_and_log(err))?;
|
||||
async fn hashes(Path(version): Path<String>) -> ApiResult<Json<IndexMap<String, Value>>> {
|
||||
let map = index_assets(&version).await.map_err(|err| internal_and_log(err))?;
|
||||
Ok(Json(map))
|
||||
}
|
||||
|
||||
async fn v2() -> ApiResult<Json<IndexMap<String, Value>>> {
|
||||
let map = index_assets("v2").await.map_err(|err| internal_and_log(err))?;
|
||||
Ok(Json(map))
|
||||
}
|
||||
|
||||
async fn download(Path(path): Path<String>) -> ApiResult<Vec<u8>> {
|
||||
let mut file = if let Ok(file) = fs::File::open(format!("assets/{path}")).await {
|
||||
async fn download(Path((version, path)): Path<(String, String)>) -> ApiResult<Vec<u8>> {
|
||||
let mut file = if let Ok(file) = fs::File::open(format!("{}/{version}/{path}", std::env::var(ASSETS_ENV).unwrap())).await {
|
||||
file
|
||||
} else {
|
||||
return Err(ApiError::NotFound)
|
||||
|
|
@ -46,7 +57,7 @@ async fn download(Path(path): Path<String>) -> ApiResult<Vec<u8>> {
|
|||
|
||||
async fn index_assets(version: &str) -> anyhow::Result<IndexMap<String, Value>> {
|
||||
let mut map = IndexMap::new();
|
||||
let version_path = PathBuf::from("assets/").join(version);
|
||||
let version_path = PathBuf::from(std::env::var(ASSETS_ENV).unwrap()).join(version);
|
||||
|
||||
for entry in WalkDir::new(version_path.clone()).into_iter().filter_map(|e| e.ok()) {
|
||||
let data = match fs::read(entry.path()).await {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::env::var;
|
||||
|
||||
use axum::{
|
||||
body::Bytes, extract::{Path, State}, Json
|
||||
};
|
||||
|
|
@ -12,7 +14,7 @@ use uuid::Uuid;
|
|||
use crate::{
|
||||
api::errors::internal_and_log,
|
||||
auth::Token, utils::{calculate_file_sha256, format_uuid},
|
||||
ApiError, ApiResult, AppState
|
||||
ApiError, ApiResult, AppState, AVATARS_ENV
|
||||
};
|
||||
use super::types::S2CMessage;
|
||||
|
||||
|
|
@ -24,7 +26,7 @@ pub async fn user_info(
|
|||
|
||||
let formatted_uuid = format_uuid(&uuid);
|
||||
|
||||
let avatar_file = format!("avatars/{}.moon", formatted_uuid);
|
||||
let avatar_file = format!("{}/{}.moon", var(AVATARS_ENV).unwrap(), formatted_uuid);
|
||||
|
||||
let userinfo = if let Some(info) = state.user_manager.get_by_uuid(&uuid) { info } else {
|
||||
return Err(ApiError::BadRequest) // NOTE: Not Found (404) shows badge
|
||||
|
|
@ -79,7 +81,7 @@ pub async fn user_info(
|
|||
pub async fn download_avatar(Path(uuid): Path<Uuid>) -> ApiResult<Vec<u8>> {
|
||||
let uuid = format_uuid(&uuid);
|
||||
tracing::info!("Requesting an avatar: {}", uuid);
|
||||
let mut file = if let Ok(file) = fs::File::open(format!("avatars/{}.moon", uuid)).await {
|
||||
let mut file = if let Ok(file) = fs::File::open(format!("{}/{}.moon", var(AVATARS_ENV).unwrap(), uuid)).await {
|
||||
file
|
||||
} else {
|
||||
return Err(ApiError::NotFound)
|
||||
|
|
@ -102,7 +104,7 @@ pub async fn upload_avatar(
|
|||
user_info.uuid,
|
||||
user_info.username
|
||||
);
|
||||
let avatar_file = format!("avatars/{}.moon", user_info.uuid);
|
||||
let avatar_file = format!("{}/{}.moon", var(AVATARS_ENV).unwrap(), user_info.uuid);
|
||||
let mut file = BufWriter::new(fs::File::create(&avatar_file).await.map_err(|err| internal_and_log(err))?);
|
||||
io::copy(&mut request_data.as_ref(), &mut file).await.map_err(|err| internal_and_log(err))?;
|
||||
}
|
||||
|
|
@ -123,11 +125,10 @@ pub async fn delete_avatar(Token(token): Token, State(state): State<AppState>) -
|
|||
user_info.uuid,
|
||||
user_info.username
|
||||
);
|
||||
let avatar_file = format!("avatars/{}.moon", user_info.uuid);
|
||||
let avatar_file = format!("{}/{}.moon", var(AVATARS_ENV).unwrap(), user_info.uuid);
|
||||
fs::remove_file(avatar_file).await.map_err(|err| internal_and_log(err))?;
|
||||
send_event(&state, &user_info.uuid).await;
|
||||
}
|
||||
// let avatar_file = format!("avatars/{}.moon",user_info.uuid);
|
||||
Ok("ok".to_string())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use std::env::var;
|
||||
|
||||
use axum::{body::Bytes, extract::{Path, State}};
|
||||
use tokio::{fs, io::{self, BufWriter}};
|
||||
use tracing::warn;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{api::figura::profile::send_event, auth::Token, ApiResult, AppState};
|
||||
use crate::{api::figura::profile::send_event, auth::Token, ApiResult, AppState, AVATARS_ENV};
|
||||
|
||||
pub async fn upload_avatar(
|
||||
Path(uuid): Path<Uuid>,
|
||||
|
|
@ -20,7 +22,7 @@ pub async fn upload_avatar(
|
|||
uuid,
|
||||
);
|
||||
|
||||
let avatar_file = format!("avatars/{}.moon", &uuid);
|
||||
let avatar_file = format!("{}/{}.moon", var(AVATARS_ENV).unwrap(), &uuid);
|
||||
let mut file = BufWriter::new(fs::File::create(&avatar_file).await.unwrap());
|
||||
io::copy(&mut request_data.as_ref(), &mut file).await.unwrap();
|
||||
send_event(&state, &uuid).await;
|
||||
|
|
@ -40,7 +42,7 @@ pub async fn delete_avatar(
|
|||
uuid,
|
||||
);
|
||||
|
||||
let avatar_file = format!("avatars/{}.moon", &uuid);
|
||||
let avatar_file = format!("{}/{}.moon", var(AVATARS_ENV).unwrap(), &uuid);
|
||||
match fs::remove_file(avatar_file).await {
|
||||
Ok(_) => {},
|
||||
Err(_) => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
// Environment
|
||||
pub const LOGGER_ENV: &'static str = "RUST_LOG";
|
||||
pub const CONFIG_ENV: &'static str = "RUST_CONFIG";
|
||||
pub const LOGS_ENV: &'static str = "LOGS_FOLDER";
|
||||
pub const ASSETS_ENV: &'static str = "ASSETS_FOLDER";
|
||||
pub const AVATARS_ENV: &'static str = "AVATARS_FOLDER";
|
||||
|
||||
// Sculptor update checker
|
||||
pub const SCULPTOR_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
pub const REPOSITORY: &'static str = "shiroyashik/sculptor";
|
||||
|
||||
// reqwest parameters
|
||||
pub const USER_AGENT: &'static str = "reqwest";
|
||||
pub const TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
|
||||
// Figura update checker
|
||||
pub const FIGURA_RELEASES_URL: &'static str = "https://api.github.com/repos/figuramc/figura/releases";
|
||||
pub const FIGURA_DEFAULT_VERSION: &'static str = "0.1.4";
|
||||
pub const FIGURA_DEFAULT_VERSION: &'static str = "0.1.4";
|
||||
|
||||
// Figura Assets
|
||||
pub const FIGURA_ASSETS_ZIP_URL: &'static str = "https://github.com/FiguraMC/Assets/archive/refs/heads/main.zip";
|
||||
pub const FIGURA_ASSETS_COMMIT_URL: &'static str = "https://api.github.com/repos/FiguraMC/Assets/commits/main";
|
||||
71
src/main.rs
71
src/main.rs
|
|
@ -5,10 +5,9 @@ use axum::{
|
|||
use dashmap::DashMap;
|
||||
use tracing_panic::panic_hook;
|
||||
use tracing_subscriber::{fmt::{self, time::ChronoLocal}, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use std::{path::PathBuf, sync::Arc, env::{set_var, var}};
|
||||
use tokio::{fs, sync::{broadcast, mpsc, RwLock}, time::Instant};
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
// Consts
|
||||
|
|
@ -35,7 +34,7 @@ use state::Config;
|
|||
|
||||
// Utils
|
||||
mod utils;
|
||||
use utils::{check_updates, get_log_file, update_advanced_users, update_bans_from_minecraft, FiguraVersions};
|
||||
use utils::{check_updates, download_assets, get_commit_sha, get_log_file, get_path_to_assets_hash, is_assets_outdated, remove_assets, update_advanced_users, update_bans_from_minecraft, write_sha_to_file, FiguraVersions};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppState {
|
||||
|
|
@ -49,19 +48,25 @@ pub struct AppState {
|
|||
broadcasts: Arc<DashMap<Uuid, broadcast::Sender<Vec<u8>>>>,
|
||||
/// Current configuration
|
||||
config: Arc<RwLock<state::Config>>,
|
||||
/// Figura Versions
|
||||
/// Caching Figura Versions
|
||||
figura_versions: Arc<RwLock<Option<FiguraVersions>>>,
|
||||
}
|
||||
|
||||
fn apply_default_environment() {
|
||||
if var(LOGGER_ENV).is_err() { set_var(LOGGER_ENV, "info") };
|
||||
if var(CONFIG_ENV).is_err() { set_var(CONFIG_ENV, "Config.toml") };
|
||||
if var(LOGS_ENV).is_err() { set_var(LOGS_ENV, "logs") };
|
||||
if var(ASSETS_ENV).is_err() { set_var(ASSETS_ENV, "data/assets") };
|
||||
if var(AVATARS_ENV).is_err() { set_var(ASSETS_ENV, "data/avatars") };
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let _ = dotenvy::dotenv();
|
||||
apply_default_environment();
|
||||
// "trace,axum=info,tower_http=info,tokio=info,tungstenite=info,tokio_tungstenite=info",
|
||||
let logger_env = std::env::var(LOGGER_ENV).unwrap_or_else(|_| "info".into());
|
||||
let config_file = std::env::var(CONFIG_ENV).unwrap_or_else(|_| "Config.toml".into());
|
||||
let logs_folder = std::env::var(LOGS_ENV).unwrap_or_else(|_| "logs".into());
|
||||
|
||||
let file_appender = tracing_appender::rolling::never(&logs_folder, get_log_file(&logs_folder));
|
||||
let file_appender = tracing_appender::rolling::never(&var(LOGS_ENV).unwrap(), get_log_file(&var(LOGS_ENV).unwrap()));
|
||||
let timer = ChronoLocal::new(String::from("%Y-%m-%dT%H:%M:%S%.3f%:z"));
|
||||
|
||||
let file_layer = fmt::layer()
|
||||
|
|
@ -79,7 +84,7 @@ async fn main() -> Result<()> {
|
|||
|
||||
// Combine the layers and set the global subscriber
|
||||
tracing_subscriber::registry()
|
||||
.with(EnvFilter::from(logger_env))
|
||||
.with(EnvFilter::from(var(LOGGER_ENV).unwrap()))
|
||||
.with(file_layer)
|
||||
.with(terminal_layer)
|
||||
.init();
|
||||
|
|
@ -91,20 +96,46 @@ async fn main() -> Result<()> {
|
|||
prev_hook(panic_info);
|
||||
}));
|
||||
|
||||
info!("The Sculptor v{}{}", SCULPTOR_VERSION, check_updates(REPOSITORY, &SCULPTOR_VERSION).await?);
|
||||
tracing::info!("The Sculptor v{}{}", SCULPTOR_VERSION, check_updates(REPOSITORY, &SCULPTOR_VERSION).await?);
|
||||
|
||||
// Preparing for launch
|
||||
{
|
||||
let path = PathBuf::from("avatars");
|
||||
let path = PathBuf::from(var(AVATARS_ENV).unwrap());
|
||||
if !path.exists() {
|
||||
fs::create_dir(path).await.expect("Can't create avatars folder!");
|
||||
info!("Created avatars directory");
|
||||
fs::create_dir_all(path).await.expect("Can't create avatars folder!");
|
||||
tracing::info!("Created avatars directory");
|
||||
}
|
||||
}
|
||||
|
||||
// Config
|
||||
let config = Arc::new(RwLock::new(Config::parse(config_file.clone().into())));
|
||||
let config = Arc::new(RwLock::new(Config::parse(var(CONFIG_ENV).unwrap().into())));
|
||||
let listen = config.read().await.listen.clone();
|
||||
|
||||
if config.read().await.assets_updater_enabled {
|
||||
// Force update assets if folder or hash file doesn't exists.
|
||||
if !(PathBuf::from(var(ASSETS_ENV).unwrap()).is_dir() && get_path_to_assets_hash().is_file()) {
|
||||
tracing::debug!("Removing broken assets...");
|
||||
remove_assets().await
|
||||
}
|
||||
match get_commit_sha(FIGURA_ASSETS_COMMIT_URL).await {
|
||||
Ok(sha) => {
|
||||
if is_assets_outdated(&sha).await.unwrap_or_else(|e| {tracing::error!("Can't check assets state due: {:?}", e); false}) {
|
||||
remove_assets().await;
|
||||
match tokio::task::spawn_blocking(|| { download_assets() }).await.unwrap() {
|
||||
Err(e) => tracing::error!("Assets outdated! Can't download new version due: {:?}", e),
|
||||
Ok(_) => {
|
||||
match write_sha_to_file(&sha).await {
|
||||
Ok(_) => tracing::info!("Assets successfully updated!"),
|
||||
Err(e) => tracing::error!("Assets successfully updated! Can't create assets hash file due: {:?}", e),
|
||||
}
|
||||
}
|
||||
};
|
||||
} else { tracing::info!("Assets are up to date!") }
|
||||
},
|
||||
Err(e) => tracing::error!("Can't get assets last commit! Assets update check aborted due {:?}", e)
|
||||
}
|
||||
}
|
||||
|
||||
// State
|
||||
let state = AppState {
|
||||
uptime: Instant::now(),
|
||||
|
|
@ -122,11 +153,11 @@ async fn main() -> Result<()> {
|
|||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
|
||||
let new_config = Config::parse(config_file.clone().into());
|
||||
let new_config = Config::parse(var(CONFIG_ENV).unwrap().into());
|
||||
let mut config = config_update.write().await;
|
||||
|
||||
if new_config != *config {
|
||||
info!("Server configuration modification detected!");
|
||||
tracing::info!("Server configuration modification detected!");
|
||||
*config = new_config;
|
||||
update_advanced_users(&config.advanced_users.clone(), &user_manager);
|
||||
}
|
||||
|
|
@ -161,11 +192,11 @@ async fn main() -> Result<()> {
|
|||
.route("/health", get(|| async { "ok" }));
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(listen).await?;
|
||||
info!("Listening on {}", listener.local_addr()?);
|
||||
tracing::info!("Listening on {}", listener.local_addr()?);
|
||||
axum::serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await?;
|
||||
info!("Serve stopped. Closing...");
|
||||
tracing::info!("Serve stopped. Closing...");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -187,11 +218,11 @@ async fn shutdown_signal() {
|
|||
tokio::select! {
|
||||
() = ctrl_c => {
|
||||
println!();
|
||||
info!("Ctrl+C signal received");
|
||||
tracing::info!("Ctrl+C signal received");
|
||||
},
|
||||
() = terminate => {
|
||||
println!();
|
||||
info!("Terminate signal received");
|
||||
tracing::info!("Terminate signal received");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use crate::auth::{default_authproviders, AuthProviders, Userinfo};
|
|||
pub struct Config {
|
||||
pub listen: String,
|
||||
pub token: Option<String>,
|
||||
pub assets_updater_enabled: bool, // FIXME: IN DEV BRANCH ONLY
|
||||
pub motd: CMotd,
|
||||
#[serde(default = "default_authproviders")]
|
||||
pub auth_providers: AuthProviders,
|
||||
|
|
@ -78,7 +79,7 @@ impl Config {
|
|||
let mut data = String::new();
|
||||
file.read_to_string(&mut data).unwrap();
|
||||
|
||||
toml::from_str(&data).unwrap()
|
||||
toml::from_str(&data).unwrap_or_else(|err| {tracing::error!("{err:#?}"); panic!("Panic occured! See log messages!")})
|
||||
}
|
||||
|
||||
pub fn verify_token(&self, suspicious: &str) -> crate::ApiResult<()> {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
use std::{env::{self, var}, path::{self, PathBuf}};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use reqwest::Client;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{fs::{self, File}, io::{AsyncReadExt as _, AsyncWriteExt as _}};
|
||||
use tracing::error;
|
||||
|
||||
use crate::{FIGURA_RELEASES_URL, TIMEOUT, USER_AGENT};
|
||||
use crate::{ASSETS_ENV, FIGURA_ASSETS_ZIP_URL, FIGURA_RELEASES_URL, TIMEOUT, USER_AGENT};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Tag {
|
||||
|
|
@ -103,4 +106,137 @@ pub async fn get_figura_versions() -> anyhow::Result<FiguraVersions> {
|
|||
pub struct FiguraVersions {
|
||||
pub release: String,
|
||||
pub prerelease: String
|
||||
}
|
||||
|
||||
// Assets
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Commit {
|
||||
sha: String
|
||||
}
|
||||
|
||||
pub fn get_path_to_assets_hash() -> PathBuf {
|
||||
path::PathBuf::from(&env::var(ASSETS_ENV).unwrap()).join("..").join("assets_last_commit")
|
||||
}
|
||||
|
||||
pub async fn get_commit_sha(url: &str) -> anyhow::Result<String> {
|
||||
let client = Client::builder().timeout(TIMEOUT).user_agent(USER_AGENT).build().unwrap();
|
||||
let response: reqwest::Response = client.get(url).send().await?;
|
||||
let commit: Commit = response.json().await?;
|
||||
Ok(commit.sha)
|
||||
}
|
||||
|
||||
pub async fn is_assets_outdated(last_sha: &str) -> anyhow::Result<bool> {
|
||||
let path = get_path_to_assets_hash();
|
||||
|
||||
match File::open(path.clone()).await {
|
||||
Ok(mut file) => {
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).await?;
|
||||
if contents.lines().count() != 1 {
|
||||
// Lines count in file abnormal
|
||||
Ok(true)
|
||||
} else {
|
||||
if contents == last_sha {
|
||||
Ok(false)
|
||||
} else {
|
||||
// SHA in file mismatches with provided SHA
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => if err.kind() == tokio::io::ErrorKind::NotFound {
|
||||
// Can't find file
|
||||
Ok(true)
|
||||
} else {
|
||||
anyhow::bail!("{:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn download_assets() -> anyhow::Result<()> {
|
||||
use std::{fs::{File, self}, io::Write as _};
|
||||
|
||||
let assets_folder = var(ASSETS_ENV).unwrap();
|
||||
|
||||
// Path to save the downloaded ZIP file
|
||||
let zip_file_path = path::PathBuf::from(&assets_folder).join("..").join("assets.zip");
|
||||
|
||||
// Download the ZIP file
|
||||
|
||||
let response = reqwest::blocking::get(FIGURA_ASSETS_ZIP_URL)?;
|
||||
let bytes = response.bytes()?;
|
||||
|
||||
// Save the downloaded ZIP file to disk
|
||||
let mut file = File::create(&zip_file_path)?;
|
||||
file.write_all(&bytes)?;
|
||||
|
||||
// Open the downloaded ZIP file
|
||||
let file = File::open(&zip_file_path)?;
|
||||
|
||||
let mut archive = zip::ZipArchive::new(file)?;
|
||||
let mut extraction_info = String::from("Extraction complete! More info:\n");
|
||||
let mut first_folder = String::new();
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i)?;
|
||||
let zipoutpath = match file.enclosed_name() {
|
||||
Some(path) => path,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// Folder name spoofing
|
||||
if i == 0 {
|
||||
if file.is_dir() {
|
||||
first_folder = zipoutpath.to_str().ok_or_else(|| anyhow::anyhow!("0 index doesn't contains path!"))?.to_string();
|
||||
} else {
|
||||
anyhow::bail!("0 index is not a folder!")
|
||||
}
|
||||
}
|
||||
let mut outpath = path::PathBuf::from(&assets_folder);
|
||||
outpath.push(zipoutpath.strip_prefix(first_folder.clone())?);
|
||||
// Spoof end
|
||||
|
||||
{
|
||||
let comment = file.comment();
|
||||
if !comment.is_empty() {
|
||||
extraction_info.push_str(&format!("File {i} comment: {comment}\n"));
|
||||
}
|
||||
}
|
||||
if file.is_dir() {
|
||||
extraction_info.push_str(&format!("Dir {} extracted to \"{}\"\n", i, outpath.display()));
|
||||
fs::create_dir_all(&outpath)?;
|
||||
} else {
|
||||
extraction_info.push_str(&format!(
|
||||
"File {} extracted to \"{}\" ({} bytes)\n",
|
||||
i,
|
||||
outpath.display(),
|
||||
file.size()
|
||||
));
|
||||
if let Some(p) = outpath.parent() {
|
||||
if !p.exists() {
|
||||
fs::create_dir_all(p)?;
|
||||
}
|
||||
}
|
||||
let mut outfile = fs::File::create(&outpath)?;
|
||||
std::io::copy(&mut file, &mut outfile)?;
|
||||
}
|
||||
}
|
||||
extraction_info.pop(); // Removes \n from end
|
||||
tracing::debug!("{extraction_info}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write_sha_to_file(sha: &str) -> anyhow::Result<()> {
|
||||
let path = get_path_to_assets_hash();
|
||||
|
||||
let mut file = File::create(path).await?;
|
||||
file.write_all(sha.as_bytes()).await?;
|
||||
file.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_assets() {
|
||||
fs::remove_dir_all(&var(ASSETS_ENV).unwrap()).await.unwrap_or_else(|err| tracing::debug!("Assets dir remove failed due {err:?}"));
|
||||
fs::remove_file(get_path_to_assets_hash()).await.unwrap_or_else(|err| tracing::debug!("Assets hash file remove failed due {err:?}"));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue