2023-04-11 03:29:21 -07:00
|
|
|
use async_zip::tokio::write::ZipFileWriter;
|
|
|
|
use async_zip::{Compression, ZipEntryBuilder};
|
2023-04-07 06:59:26 -07:00
|
|
|
|
2023-04-12 04:59:43 -07:00
|
|
|
use axum::extract::State;
|
2023-04-08 08:12:14 -07:00
|
|
|
use axum::http::StatusCode;
|
|
|
|
use axum::routing::post;
|
2023-04-07 06:59:26 -07:00
|
|
|
use axum::{
|
|
|
|
extract::{DefaultBodyLimit, Multipart},
|
2023-04-08 08:12:14 -07:00
|
|
|
response::Redirect,
|
2023-04-07 06:59:26 -07:00
|
|
|
Router,
|
|
|
|
};
|
2023-04-11 03:29:21 -07:00
|
|
|
|
|
|
|
use futures::TryStreamExt;
|
|
|
|
|
2023-04-08 08:12:14 -07:00
|
|
|
use rand::distributions::{Alphanumeric, DistString};
|
|
|
|
use rand::rngs::SmallRng;
|
|
|
|
use rand::SeedableRng;
|
2023-04-11 03:29:21 -07:00
|
|
|
|
|
|
|
use sanitize_filename_reader_friendly::sanitize;
|
2023-04-12 05:07:37 -07:00
|
|
|
|
|
|
|
use serde::Serialize;
|
|
|
|
|
2023-04-12 04:59:43 -07:00
|
|
|
use tokio::io::AsyncReadExt;
|
2023-04-12 05:07:37 -07:00
|
|
|
use tokio_util::compat::FuturesAsyncWriteCompatExt;
|
2023-04-11 03:29:21 -07:00
|
|
|
|
2023-04-12 04:59:43 -07:00
|
|
|
use std::collections::HashMap;
|
2023-04-11 03:29:21 -07:00
|
|
|
use std::io;
|
|
|
|
use std::net::SocketAddr;
|
2023-04-12 05:07:37 -07:00
|
|
|
use std::path::Path;
|
2023-04-11 03:29:21 -07:00
|
|
|
|
2023-04-08 08:12:14 -07:00
|
|
|
use tokio_util::io::StreamReader;
|
2023-04-11 03:29:21 -07:00
|
|
|
|
2023-04-07 06:59:26 -07:00
|
|
|
use tower_http::{limit::RequestBodyLimitLayer, services::ServeDir, trace::TraceLayer};
|
|
|
|
|
|
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|
|
|
|
2023-04-12 05:07:37 -07:00
|
|
|
mod state;
|
2023-04-12 04:59:43 -07:00
|
|
|
|
2023-04-12 05:07:37 -07:00
|
|
|
use state::{AppState, UploadRecord};
|
2023-04-12 04:59:43 -07:00
|
|
|
|
2023-04-07 06:59:26 -07:00
|
|
|
#[tokio::main]
|
2023-04-08 08:12:14 -07:00
|
|
|
async fn main() -> io::Result<()> {
|
2023-04-12 04:59:43 -07:00
|
|
|
// Set up logging
|
2023-04-07 06:59:26 -07:00
|
|
|
tracing_subscriber::registry()
|
|
|
|
.with(
|
|
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
2023-04-08 08:12:14 -07:00
|
|
|
.unwrap_or_else(|_| "nyazoom=debug,tower_http=debug".into()),
|
2023-04-07 06:59:26 -07:00
|
|
|
)
|
|
|
|
.with(tracing_subscriber::fmt::layer())
|
|
|
|
.init();
|
|
|
|
|
2023-04-12 04:59:43 -07:00
|
|
|
// uses create_dir_all to create both .cache and serve inside it in one go
|
2023-04-08 20:58:38 -07:00
|
|
|
make_dir(".cache/serve").await?;
|
2023-04-08 08:12:14 -07:00
|
|
|
|
2023-04-12 04:59:43 -07:00
|
|
|
let state = fetch_cache().await;
|
|
|
|
|
2023-04-08 08:12:14 -07:00
|
|
|
// Router Setup
|
2023-04-07 06:59:26 -07:00
|
|
|
let with_big_body = Router::new()
|
2023-04-11 03:29:21 -07:00
|
|
|
.route("/upload", post(upload_to_zip))
|
2023-04-07 06:59:26 -07:00
|
|
|
.layer(DefaultBodyLimit::disable())
|
|
|
|
.layer(RequestBodyLimitLayer::new(
|
2023-04-08 08:12:14 -07:00
|
|
|
10 * 1024 * 1024 * 1024, // 10GiB
|
2023-04-12 04:59:43 -07:00
|
|
|
))
|
|
|
|
.with_state(state);
|
2023-04-07 06:59:26 -07:00
|
|
|
|
2023-04-08 08:12:14 -07:00
|
|
|
let base = Router::new()
|
|
|
|
.nest_service("/", ServeDir::new("dist"))
|
2023-04-08 20:58:38 -07:00
|
|
|
.nest_service("/download", ServeDir::new(".cache/serve"));
|
2023-04-07 06:59:26 -07:00
|
|
|
|
2023-04-08 08:12:14 -07:00
|
|
|
let app = Router::new()
|
|
|
|
.merge(with_big_body)
|
|
|
|
.merge(base)
|
|
|
|
.layer(TraceLayer::new_for_http());
|
2023-04-07 06:59:26 -07:00
|
|
|
|
2023-04-08 08:12:14 -07:00
|
|
|
// Server creation
|
2023-04-07 20:37:20 -07:00
|
|
|
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
|
2023-04-09 01:22:44 -07:00
|
|
|
tracing::debug!("listening on http://{}/", addr);
|
2023-04-07 06:59:26 -07:00
|
|
|
axum::Server::bind(&addr)
|
2023-04-08 08:12:14 -07:00
|
|
|
.serve(app.into_make_service())
|
2023-04-07 06:59:26 -07:00
|
|
|
.await
|
|
|
|
.unwrap();
|
2023-04-08 08:12:14 -07:00
|
|
|
|
|
|
|
Ok(())
|
2023-04-07 06:59:26 -07:00
|
|
|
}
|
|
|
|
|
2023-04-12 04:59:43 -07:00
|
|
|
async fn upload_to_zip(
|
|
|
|
State(state): State<AppState>,
|
|
|
|
mut body: Multipart,
|
|
|
|
) -> Result<Redirect, (StatusCode, String)> {
|
|
|
|
tracing::debug!("{:?}", *state.records.lock().await);
|
|
|
|
|
2023-04-08 20:58:38 -07:00
|
|
|
let cache_name = get_random_name(10);
|
2023-04-08 08:12:14 -07:00
|
|
|
|
2023-04-11 03:29:21 -07:00
|
|
|
let archive_path = Path::new(".cache/serve").join(&format!("{}.zip", &cache_name));
|
2023-04-12 04:59:43 -07:00
|
|
|
|
2023-04-11 03:29:21 -07:00
|
|
|
tracing::debug!("Zipping: {:?}", &archive_path);
|
|
|
|
|
2023-04-12 04:59:43 -07:00
|
|
|
let mut archive = tokio::fs::File::create(&archive_path)
|
2023-04-08 08:12:14 -07:00
|
|
|
.await
|
|
|
|
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
|
2023-04-11 03:29:21 -07:00
|
|
|
let mut writer = ZipFileWriter::new(&mut archive);
|
2023-04-08 08:12:14 -07:00
|
|
|
|
2023-04-07 06:59:26 -07:00
|
|
|
while let Some(field) = body.next_field().await.unwrap() {
|
2023-04-11 03:29:21 -07:00
|
|
|
let file_name = match field.file_name() {
|
|
|
|
Some(file_name) => sanitize(file_name),
|
|
|
|
_ => continue,
|
2023-04-08 08:12:14 -07:00
|
|
|
};
|
|
|
|
|
2023-04-11 03:29:21 -07:00
|
|
|
tracing::debug!("Downloading to Zip: {file_name:?}");
|
2023-04-08 08:12:14 -07:00
|
|
|
|
2023-04-11 03:29:21 -07:00
|
|
|
let stream = field;
|
2023-04-08 08:12:14 -07:00
|
|
|
let body_with_io_error = stream.map_err(|err| io::Error::new(io::ErrorKind::Other, err));
|
2023-04-12 04:59:43 -07:00
|
|
|
let mut body_reader = StreamReader::new(body_with_io_error);
|
2023-04-08 08:12:14 -07:00
|
|
|
|
2023-04-11 03:29:21 -07:00
|
|
|
let builder = ZipEntryBuilder::new(file_name, Compression::Deflate);
|
|
|
|
let mut entry_writer = writer
|
|
|
|
.write_entry_stream(builder)
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.compat_write();
|
|
|
|
|
|
|
|
tokio::io::copy(&mut body_reader, &mut entry_writer)
|
|
|
|
.await
|
|
|
|
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
|
|
|
|
|
|
|
|
entry_writer
|
|
|
|
.into_inner()
|
|
|
|
.close()
|
|
|
|
.await
|
|
|
|
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
|
2023-04-08 08:12:14 -07:00
|
|
|
}
|
2023-04-09 01:22:44 -07:00
|
|
|
|
2023-04-12 04:59:43 -07:00
|
|
|
let mut records = state.records.lock().await;
|
|
|
|
records.insert(cache_name.clone(), UploadRecord::new(archive_path));
|
|
|
|
|
|
|
|
write_to_cache(&records)
|
|
|
|
.await
|
|
|
|
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
|
|
|
|
|
2023-04-11 03:29:21 -07:00
|
|
|
writer.close().await.unwrap();
|
2023-04-09 00:03:05 -07:00
|
|
|
|
2023-04-11 03:29:21 -07:00
|
|
|
Ok(Redirect::to(&format!("/link.html?link={}.zip", cache_name)))
|
2023-04-08 08:12:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
async fn make_dir<T>(name: T) -> io::Result<()>
|
|
|
|
where
|
|
|
|
T: AsRef<Path>,
|
|
|
|
{
|
|
|
|
tokio::fs::create_dir_all(name)
|
|
|
|
.await
|
|
|
|
.or_else(|err| match err.kind() {
|
|
|
|
io::ErrorKind::AlreadyExists => Ok(()),
|
|
|
|
_ => Err(err),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn get_random_name(len: usize) -> String {
|
|
|
|
let mut rng = SmallRng::from_entropy();
|
|
|
|
|
|
|
|
Alphanumeric.sample_string(&mut rng, len)
|
2023-04-07 02:28:23 -07:00
|
|
|
}
|
2023-04-09 01:22:44 -07:00
|
|
|
|
2023-04-11 03:29:21 -07:00
|
|
|
#[allow(dead_code)]
|
2023-04-09 10:26:39 -07:00
|
|
|
static UNITS: [&str; 6] = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
|
|
|
|
// This function is actually rather interesting to me, I understand that rust is
|
|
|
|
// very powerful, and its very safe, but i find it rather amusing that the [] operator
|
|
|
|
// doesn't check bounds, meaning it can panic at runtime. Usually rust is very
|
|
|
|
// very careful about possible panics
|
|
|
|
//
|
|
|
|
// although this function shouldn't be able to panic at runtime due to known bounds
|
|
|
|
// being listened to
|
2023-04-09 01:22:44 -07:00
|
|
|
#[inline]
|
2023-04-11 03:29:21 -07:00
|
|
|
fn _bytes_to_human_readable(bytes: u64) -> String {
|
2023-04-09 01:22:44 -07:00
|
|
|
let mut running = bytes as f64;
|
|
|
|
let mut count = 0;
|
|
|
|
while running > 1024.0 && count <= 6 {
|
|
|
|
running /= 1024.0;
|
|
|
|
count += 1;
|
|
|
|
}
|
|
|
|
|
2023-04-09 10:26:39 -07:00
|
|
|
format!("{:.2} {}", running, UNITS[count - 1])
|
2023-04-09 01:22:44 -07:00
|
|
|
}
|
2023-04-11 03:29:21 -07:00
|
|
|
|
|
|
|
pub mod error {
|
|
|
|
use std::io::{Error, ErrorKind};
|
|
|
|
|
|
|
|
pub fn io_other(s: &str) -> Error {
|
|
|
|
Error::new(ErrorKind::Other, s)
|
|
|
|
}
|
|
|
|
}
|
2023-04-12 04:59:43 -07:00
|
|
|
|
|
|
|
async fn write_to_cache<T, Y>(records: &HashMap<T, Y>) -> io::Result<()>
|
|
|
|
where
|
|
|
|
T: Serialize,
|
|
|
|
Y: Serialize,
|
|
|
|
{
|
|
|
|
let mut records_cache = tokio::fs::File::create(".cache/data").await.unwrap();
|
|
|
|
|
|
|
|
let mut buf: Vec<u8> = Vec::with_capacity(200);
|
|
|
|
bincode::serialize_into(&mut buf, &*records)
|
|
|
|
.map_err(|err| error::io_other(&err.to_string()))?;
|
|
|
|
|
|
|
|
let bytes_written = tokio::io::copy(&mut buf.as_slice(), &mut records_cache).await?;
|
|
|
|
|
|
|
|
tracing::debug!("state cache size: {}", bytes_written);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn fetch_cache() -> AppState {
|
|
|
|
let records = if let Ok(file) = tokio::fs::File::open(".cache/data").await.as_mut() {
|
|
|
|
let mut buf: Vec<u8> = Vec::with_capacity(200);
|
|
|
|
file.read_to_end(&mut buf).await.unwrap();
|
|
|
|
|
|
|
|
bincode::deserialize_from(&mut buf.as_slice()).unwrap()
|
|
|
|
} else {
|
|
|
|
HashMap::new()
|
|
|
|
};
|
|
|
|
|
|
|
|
AppState::new(records)
|
|
|
|
}
|