use std::
{
fs,
env,
time::Duration,
path::Path,
};
use ureq::{ Error, Agent };
use serde_json::Value;
use semver::Version;
use crate::consts;
#[cfg(feature = "client_base")]
use tokio::sync::mpsc::Sender;
#[cfg(feature = "client_base")]
use crate::network::client::ClientEvent;
#[cfg(feature = "server")]
use std::
{
fmt::Write,
path::PathBuf,
};
fn get_dir(dir: &str) -> String
{
dir.replace("{HOME}", dirs::home_dir().expect("Could not determine home directory").to_str().expect("Invalid home directory"))
}
pub fn get_version<'a>() -> &'a str {
env!("CARGO_PKG_VERSION")
}
pub fn get_identifier() -> String {
format!("WHY2/{}", get_version())
}
pub fn fetch_data(url: &str) -> Result<String, Error> {
let agent: Agent = Agent::config_builder()
.timeout_global(Some(Duration::from_millis(consts::FETCH_TIMEOUT)))
.build()
.into();
agent.get(url)
.header("User-Agent", &get_identifier())
.call()?
.body_mut()
.read_to_string()
}
pub async fn check_version(#[cfg(feature = "client_base")] tx: &Sender<ClientEvent>) {
let metadata_raw = match tokio::task::spawn_blocking(|| fetch_data(consts::METADATA_URL)).await
.expect("Fetching versions panicked")
{
Ok(m) => m,
Err(_) =>
{
#[cfg(feature = "client_base")]
{
tx.send(ClientEvent::VersionFailed).await.unwrap();
}
#[cfg(feature = "server")]
{
log::warn!("Fetching versions failed, this release could be unsafe!");
}
return;
}
};
let metadata: Value = serde_json::from_str(&metadata_raw).expect("Parsing versions failed"); let newest_version = metadata.get("crate") .and_then(|c| c.get("newest_version"))
.and_then(|v| v.as_str())
.unwrap();
let current_version = get_version();
if current_version != newest_version
{
let versions = metadata.get("versions").and_then(|v| v.as_array()).unwrap();
let mut newer_versions = 0usize;
let current_version = Version::parse(current_version).expect("Invalid version");
for version in versions
{
if Version::parse(version.get("num").and_then(|n| n.as_str()).unwrap()).unwrap() > current_version
{
newer_versions += 1;
}
}
#[cfg(feature = "client_base")]
{
tx.send(ClientEvent::UnsafeVersion(newer_versions, current_version, newest_version.to_owned())).await.unwrap();
}
#[cfg(feature = "server")]
{
log::warn!("This release could be unsafe! You are {newer_versions} versions behind! ({current_version}/{newest_version})");
}
}
}
pub fn get_why2_dir() -> String {
get_dir(consts::CONFIG_DIR)
}
pub fn check_directory() {
let config = get_why2_dir();
if !Path::new(&config).is_dir()
{
fs::create_dir_all(config).expect("Failed to create WHY2 config directory");
}
}
pub fn is_image(header: &[u8]) -> bool {
const MAGIC: [&[u8]; 13] =
[
b"\x89PNG\r\n\x1a\n", b"\xff\xd8\xff", b"GIF87a", b"GIF89a", b"BM", b"\x00\x00\x01\x00", b"II*\x00", b"MM\x00*", b"qoif", b"#?", b"farbfeld", b"DDS ", b"v/1\x01", ];
if MAGIC.iter().any(|magic| header.starts_with(magic)) { return true; }
(header.len() >= 12 && header.starts_with(b"RIFF") && &header[8..12] == b"WEBP") ||
(header.len() >= 3 && header[0] == b'P' && (b'1'..=b'7').contains(&header[1]) &&
header[2].is_ascii_whitespace())
}
#[cfg(feature = "server")]
pub fn get_upload_dir(username: &str) -> PathBuf {
env::temp_dir().join(consts::UPLOADS_DIR).join(username)
}
#[cfg(feature = "server")]
pub fn get_image_dir() -> PathBuf {
PathBuf::from(get_why2_dir() + consts::SERVER_IMAGES_DIR)
}
#[cfg(feature = "server")]
pub fn hex(bytes: &[u8]) -> String {
let mut string = String::with_capacity(bytes.len() * 2);
for byte in bytes
{
write!(string, "{byte:02x}").expect("Hex encoding failed");
}
string
}
#[cfg(feature = "server")]
pub fn restart() -> !
{
let executable = match env::current_exe()
{
Ok(path) => path,
Err(error) =>
{
log::error!("Restart failed (executable not found): {error}");
std::process::exit(1);
}
};
let arguments: Vec<String> = env::args().skip(1).collect();
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
let error = std::process::Command::new(&executable).args(&arguments).exec();
log::error!("Restart failed: {error}");
}
#[cfg(not(unix))]
match std::process::Command::new(&executable).args(&arguments).spawn()
{
Ok(_) => std::process::exit(0),
Err(error) => log::error!("Restart failed: {error}"),
}
std::process::exit(1);
}