cargo fmt
This commit is contained in:
parent
3d5010806b
commit
ba3326ef24
|
@ -1,4 +1,4 @@
|
||||||
use std::{fs::File, task::Waker, io::Write, path::PathBuf};
|
use std::{fs::File, io::Write, path::PathBuf, task::Waker};
|
||||||
|
|
||||||
pub trait LiveWriter: Write {
|
pub trait LiveWriter: Write {
|
||||||
fn add_waker(&mut self, waker: Waker);
|
fn add_waker(&mut self, waker: Waker);
|
||||||
|
|
31
src/main.rs
31
src/main.rs
|
@ -3,12 +3,16 @@ mod file;
|
||||||
mod upload;
|
mod upload;
|
||||||
mod zip;
|
mod zip;
|
||||||
|
|
||||||
use std::{collections::HashMap, task::Waker, sync::{mpsc::Sender, RwLock}, path::PathBuf};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{mpsc::Sender, RwLock},
|
||||||
|
task::Waker,
|
||||||
|
};
|
||||||
|
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
get, middleware::Logger, web, App, HttpResponse, HttpServer,
|
get, middleware::Logger, web, App, HttpRequest, HttpResponse, HttpServer, Responder,
|
||||||
Responder, HttpRequest,
|
|
||||||
};
|
};
|
||||||
use actix_web_actors::ws;
|
use actix_web_actors::ws;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
@ -31,7 +35,6 @@ impl UploadedFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct DownloadableFile {
|
pub struct DownloadableFile {
|
||||||
name: String,
|
name: String,
|
||||||
size: usize,
|
size: usize,
|
||||||
|
@ -46,16 +49,8 @@ fn storage_dir() -> PathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/upload")]
|
#[get("/upload")]
|
||||||
async fn upload_socket(
|
async fn upload_socket(req: HttpRequest, stream: web::Payload, data: AppData) -> impl Responder {
|
||||||
req: HttpRequest,
|
ws::start(upload::Uploader::new(data), &req, stream)
|
||||||
stream: web::Payload,
|
|
||||||
data: AppData,
|
|
||||||
) -> impl Responder {
|
|
||||||
ws::start(
|
|
||||||
upload::Uploader::new(data),
|
|
||||||
&req,
|
|
||||||
stream
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
|
@ -64,8 +59,12 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
let data: AppData = web::Data::new(RwLock::new(HashMap::new()));
|
let data: AppData = web::Data::new(RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
let static_dir = PathBuf::from(std::env::var("STATIC_DIR").unwrap_or_else(|_| String::from("static")));
|
let static_dir =
|
||||||
let port = std::env::var("PORT").ok().and_then(|p| p.parse::<u16>().ok()).unwrap_or(8080);
|
PathBuf::from(std::env::var("STATIC_DIR").unwrap_or_else(|_| String::from("static")));
|
||||||
|
let port = std::env::var("PORT")
|
||||||
|
.ok()
|
||||||
|
.and_then(|p| p.parse::<u16>().ok())
|
||||||
|
.unwrap_or(8080);
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use std::{collections::HashSet, fmt::Display, io::Write};
|
use std::{collections::HashSet, fmt::Display, io::Write};
|
||||||
|
|
||||||
use actix::{Actor, StreamHandler, ActorContext, AsyncContext};
|
use actix::{Actor, ActorContext, AsyncContext, StreamHandler};
|
||||||
use actix_http::ws::{Item, CloseReason};
|
use actix_http::ws::{CloseReason, Item};
|
||||||
use actix_web_actors::ws::{self, CloseCode};
|
use actix_web_actors::ws::{self, CloseCode};
|
||||||
use log::{error, debug, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
use rand::distributions::{Alphanumeric, DistString};
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{UploadedFile, DownloadableFile, file::LiveWriter};
|
use crate::{file::LiveWriter, DownloadableFile, UploadedFile};
|
||||||
|
|
||||||
const FILENAME_DATE_FORMAT: &[time::format_description::FormatItem] =
|
const FILENAME_DATE_FORMAT: &[time::format_description::FormatItem] =
|
||||||
time::macros::format_description!("[year]-[month]-[day]-[hour][minute][second]");
|
time::macros::format_description!("[year]-[month]-[day]-[hour][minute][second]");
|
||||||
|
@ -97,7 +97,7 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Uploader {
|
||||||
error!("Websocket error: {:?}", e);
|
error!("Websocket error: {:?}", e);
|
||||||
ctx.stop();
|
ctx.stop();
|
||||||
return;
|
return;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.handle_message(msg, ctx) {
|
match self.handle_message(msg, ctx) {
|
||||||
|
@ -119,7 +119,7 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Uploader {
|
||||||
// self.app_data.write().unwrap().entry(
|
// self.app_data.write().unwrap().entry(
|
||||||
ctx.stop();
|
ctx.stop();
|
||||||
}
|
}
|
||||||
_ => ()
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,12 +129,16 @@ fn ack(ctx: &mut <Uploader as Actor>::Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Uploader {
|
impl Uploader {
|
||||||
fn handle_message(&mut self, msg: ws::Message, ctx: &mut <Self as Actor>::Context) -> Result<bool, Error>{
|
fn handle_message(
|
||||||
|
&mut self,
|
||||||
|
msg: ws::Message,
|
||||||
|
ctx: &mut <Self as Actor>::Context,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
trace!("Websocket message: {:?}", msg);
|
trace!("Websocket message: {:?}", msg);
|
||||||
match msg {
|
match msg {
|
||||||
ws::Message::Text(text) => {
|
ws::Message::Text(text) => {
|
||||||
if self.writer.is_some() {
|
if self.writer.is_some() {
|
||||||
return Err(Error::UnexpectedMessageType)
|
return Err(Error::UnexpectedMessageType);
|
||||||
}
|
}
|
||||||
let raw_files: Vec<RawUploadedFile> = serde_json::from_slice(text.as_bytes())?;
|
let raw_files: Vec<RawUploadedFile> = serde_json::from_slice(text.as_bytes())?;
|
||||||
info!("Received file list: {} files", raw_files.len());
|
info!("Received file list: {} files", raw_files.len());
|
||||||
|
@ -167,32 +171,42 @@ impl Uploader {
|
||||||
let writer = super::zip::ZipGenerator::new(files, Box::new(writer));
|
let writer = super::zip::ZipGenerator::new(files, Box::new(writer));
|
||||||
let size = writer.total_size();
|
let size = writer.total_size();
|
||||||
self.writer = Some(Box::new(writer));
|
self.writer = Some(Box::new(writer));
|
||||||
let download_filename = super::APP_NAME.to_owned()
|
let download_filename =
|
||||||
+ &now.format(FILENAME_DATE_FORMAT)?
|
super::APP_NAME.to_owned() + &now.format(FILENAME_DATE_FORMAT)? + ".zip";
|
||||||
+ ".zip";
|
|
||||||
let modtime = now;
|
let modtime = now;
|
||||||
self.app_data.write().map_err(|_| Error::LockPoisoned)?.insert(storage_filename, DownloadableFile {
|
self.app_data
|
||||||
|
.write()
|
||||||
|
.map_err(|_| Error::LockPoisoned)?
|
||||||
|
.insert(
|
||||||
|
storage_filename,
|
||||||
|
DownloadableFile {
|
||||||
name: download_filename,
|
name: download_filename,
|
||||||
size,
|
size,
|
||||||
modtime,
|
modtime,
|
||||||
uploader: Some(ctx.address()),
|
uploader: Some(ctx.address()),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
self.writer = Some(Box::new(writer));
|
self.writer = Some(Box::new(writer));
|
||||||
self.app_data.write().map_err(|_| Error::LockPoisoned)?.insert(storage_filename, DownloadableFile {
|
self.app_data
|
||||||
|
.write()
|
||||||
|
.map_err(|_| Error::LockPoisoned)?
|
||||||
|
.insert(
|
||||||
|
storage_filename,
|
||||||
|
DownloadableFile {
|
||||||
name: files[0].name.clone(),
|
name: files[0].name.clone(),
|
||||||
size: files[0].size,
|
size: files[0].size,
|
||||||
modtime: files[0].modtime,
|
modtime: files[0].modtime,
|
||||||
uploader: Some(ctx.address()),
|
uploader: Some(ctx.address()),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ack(ctx);
|
ack(ctx);
|
||||||
}
|
}
|
||||||
ws::Message::Binary(data)
|
ws::Message::Binary(data)
|
||||||
| ws::Message::Continuation(Item::FirstBinary(data))
|
| ws::Message::Continuation(Item::FirstBinary(data))
|
||||||
| ws::Message::Continuation(Item::Continue(data))
|
| ws::Message::Continuation(Item::Continue(data))
|
||||||
| ws::Message::Continuation(Item::Last(data)) =>
|
| ws::Message::Continuation(Item::Last(data)) => {
|
||||||
{
|
|
||||||
if let Some(ref mut writer) = self.writer {
|
if let Some(ref mut writer) = self.writer {
|
||||||
if data.len() > self.bytes_remaining {
|
if data.len() > self.bytes_remaining {
|
||||||
return Err(Error::TooMuchData);
|
return Err(Error::TooMuchData);
|
||||||
|
|
65
src/zip.rs
65
src/zip.rs
|
@ -5,17 +5,20 @@ use crc32fast::Hasher;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::UploadedFile;
|
|
||||||
use crate::file::LiveWriter;
|
use crate::file::LiveWriter;
|
||||||
|
use crate::UploadedFile;
|
||||||
|
|
||||||
const SIGNATURE_SIZE: usize = 4;
|
const SIGNATURE_SIZE: usize = 4;
|
||||||
const SHARED_FIELDS_SIZE: usize = 26;
|
const SHARED_FIELDS_SIZE: usize = 26;
|
||||||
const EXTRA_FIELD_SIZE: usize = 41;
|
const EXTRA_FIELD_SIZE: usize = 41;
|
||||||
const LOCAL_HEADER_SIZE_MINUS_FILENAME: usize = SIGNATURE_SIZE + SHARED_FIELDS_SIZE + EXTRA_FIELD_SIZE;
|
const LOCAL_HEADER_SIZE_MINUS_FILENAME: usize =
|
||||||
|
SIGNATURE_SIZE + SHARED_FIELDS_SIZE + EXTRA_FIELD_SIZE;
|
||||||
const DATA_DESCRIPTOR_SIZE: usize = 24;
|
const DATA_DESCRIPTOR_SIZE: usize = 24;
|
||||||
const FILE_ENTRY_SIZE_MINUS_FILENAME_AND_FILE: usize = LOCAL_HEADER_SIZE_MINUS_FILENAME + DATA_DESCRIPTOR_SIZE;
|
const FILE_ENTRY_SIZE_MINUS_FILENAME_AND_FILE: usize =
|
||||||
|
LOCAL_HEADER_SIZE_MINUS_FILENAME + DATA_DESCRIPTOR_SIZE;
|
||||||
|
|
||||||
const CENTRAL_DIRECTORY_HEADER_SIZE_MINUS_FILENAME: usize = SIGNATURE_SIZE + 2 + SHARED_FIELDS_SIZE + 14 + EXTRA_FIELD_SIZE;
|
const CENTRAL_DIRECTORY_HEADER_SIZE_MINUS_FILENAME: usize =
|
||||||
|
SIGNATURE_SIZE + 2 + SHARED_FIELDS_SIZE + 14 + EXTRA_FIELD_SIZE;
|
||||||
|
|
||||||
const EOCD64_RECORD_SIZE: usize = 56;
|
const EOCD64_RECORD_SIZE: usize = 56;
|
||||||
const EOCD64_LOCATOR_SIZE: usize = 20;
|
const EOCD64_LOCATOR_SIZE: usize = 20;
|
||||||
|
@ -59,7 +62,11 @@ fn fat_timestamp(time: OffsetDateTime) -> u32 {
|
||||||
|
|
||||||
/// Append a value to a byte vector as little-endian bytes
|
/// Append a value to a byte vector as little-endian bytes
|
||||||
fn append_value(data: &mut Vec<u8>, mut value: u64, len: usize) {
|
fn append_value(data: &mut Vec<u8>, mut value: u64, len: usize) {
|
||||||
data.resize_with(data.len() + len, || { let byte = value as u8; value >>= 8; byte });
|
data.resize_with(data.len() + len, || {
|
||||||
|
let byte = value as u8;
|
||||||
|
value >>= 8;
|
||||||
|
byte
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append_repeated_byte(data: &mut Vec<u8>, byte: u8, count: usize) {
|
fn append_repeated_byte(data: &mut Vec<u8>, byte: u8, count: usize) {
|
||||||
|
@ -144,7 +151,8 @@ impl UploadedFile {
|
||||||
0, 0, // Disk number where file starts: 0
|
0, 0, // Disk number where file starts: 0
|
||||||
0, 0, // Internal file attributes: nothing
|
0, 0, // Internal file attributes: nothing
|
||||||
0, 0, 0, 0, // External file attributes: nothing
|
0, 0, 0, 0, // External file attributes: nothing
|
||||||
0xff, 0xff, 0xff, 0xff, // Relative offset of local file header: placeholder, see ZIP64 data
|
0xff, 0xff, 0xff,
|
||||||
|
0xff, // Relative offset of local file header: placeholder, see ZIP64 data
|
||||||
]);
|
]);
|
||||||
header.append(&mut self.name.clone().into_bytes());
|
header.append(&mut self.name.clone().into_bytes());
|
||||||
header.append(&mut self.extra_field(local_header_offset));
|
header.append(&mut self.extra_field(local_header_offset));
|
||||||
|
@ -200,7 +208,7 @@ pub struct ZipGenerator<'a> {
|
||||||
pending_metadata: Vec<u8>,
|
pending_metadata: Vec<u8>,
|
||||||
hasher: Hasher,
|
hasher: Hasher,
|
||||||
hashes: Vec<u32>,
|
hashes: Vec<u32>,
|
||||||
output: Box<dyn LiveWriter + 'a>
|
output: Box<dyn LiveWriter + 'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ZipGenerator<'a> {
|
impl<'a> ZipGenerator<'a> {
|
||||||
|
@ -225,8 +233,12 @@ impl<'a> ZipGenerator<'a> {
|
||||||
fn finish_file(&mut self) {
|
fn finish_file(&mut self) {
|
||||||
let hash = std::mem::replace(&mut self.hasher, Hasher::new()).finalize();
|
let hash = std::mem::replace(&mut self.hasher, Hasher::new()).finalize();
|
||||||
self.hashes.push(hash);
|
self.hashes.push(hash);
|
||||||
self.pending_metadata.append(&mut self.files[self.file_index].data_descriptor(hash));
|
self.pending_metadata
|
||||||
debug!("Finishing file entry in zipfile: {}, hash {}", self.files[self.file_index].name, hash);
|
.append(&mut self.files[self.file_index].data_descriptor(hash));
|
||||||
|
debug!(
|
||||||
|
"Finishing file entry in zipfile: {}, hash {}",
|
||||||
|
self.files[self.file_index].name, hash
|
||||||
|
);
|
||||||
self.file_index += 1;
|
self.file_index += 1;
|
||||||
self.start_new_file();
|
self.start_new_file();
|
||||||
}
|
}
|
||||||
|
@ -234,19 +246,27 @@ impl<'a> ZipGenerator<'a> {
|
||||||
fn start_new_file(&mut self) {
|
fn start_new_file(&mut self) {
|
||||||
let mut offset = file_entries_size(&self.files[..self.file_index]);
|
let mut offset = file_entries_size(&self.files[..self.file_index]);
|
||||||
while self.file_index < self.files.len() && self.files[self.file_index].size == 0 {
|
while self.file_index < self.files.len() && self.files[self.file_index].size == 0 {
|
||||||
debug!("Empty file entry in zipfile: {}", self.files[self.file_index].name);
|
debug!(
|
||||||
|
"Empty file entry in zipfile: {}",
|
||||||
|
self.files[self.file_index].name
|
||||||
|
);
|
||||||
self.hashes.push(EMPTY_STRING_CRC32);
|
self.hashes.push(EMPTY_STRING_CRC32);
|
||||||
let mut local_header = self.files[self.file_index].local_header(offset);
|
let mut local_header = self.files[self.file_index].local_header(offset);
|
||||||
let mut data_descriptor = self.files[self.file_index].data_descriptor(EMPTY_STRING_CRC32);
|
let mut data_descriptor =
|
||||||
|
self.files[self.file_index].data_descriptor(EMPTY_STRING_CRC32);
|
||||||
offset += local_header.len() + data_descriptor.len();
|
offset += local_header.len() + data_descriptor.len();
|
||||||
self.file_index += 1;
|
self.file_index += 1;
|
||||||
self.pending_metadata.append(&mut local_header);
|
self.pending_metadata.append(&mut local_header);
|
||||||
self.pending_metadata.append(&mut data_descriptor);
|
self.pending_metadata.append(&mut data_descriptor);
|
||||||
}
|
}
|
||||||
if self.file_index < self.files.len() {
|
if self.file_index < self.files.len() {
|
||||||
debug!("Starting file entry in zipfile: {}", self.files[self.file_index].name);
|
debug!(
|
||||||
|
"Starting file entry in zipfile: {}",
|
||||||
|
self.files[self.file_index].name
|
||||||
|
);
|
||||||
self.byte_index = 0;
|
self.byte_index = 0;
|
||||||
self.pending_metadata.append(&mut self.files[self.file_index].local_header(offset));
|
self.pending_metadata
|
||||||
|
.append(&mut self.files[self.file_index].local_header(offset));
|
||||||
} else {
|
} else {
|
||||||
self.finish_zipfile();
|
self.finish_zipfile();
|
||||||
}
|
}
|
||||||
|
@ -256,12 +276,17 @@ impl<'a> ZipGenerator<'a> {
|
||||||
debug!("Writing zipfile central directory");
|
debug!("Writing zipfile central directory");
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
for (i, file) in self.files.iter().enumerate() {
|
for (i, file) in self.files.iter().enumerate() {
|
||||||
debug!("Writing central directory entry: {}, hash {}", file.name, self.hashes[i]);
|
debug!(
|
||||||
self.pending_metadata.append(&mut file.central_directory_header(offset, self.hashes[i]));
|
"Writing central directory entry: {}, hash {}",
|
||||||
|
file.name, self.hashes[i]
|
||||||
|
);
|
||||||
|
self.pending_metadata
|
||||||
|
.append(&mut file.central_directory_header(offset, self.hashes[i]));
|
||||||
offset += file_entry_size(file);
|
offset += file_entry_size(file);
|
||||||
}
|
}
|
||||||
debug!("Writing end of central directory");
|
debug!("Writing end of central directory");
|
||||||
self.pending_metadata.append(&mut end_of_central_directory(&self.files));
|
self.pending_metadata
|
||||||
|
.append(&mut end_of_central_directory(&self.files));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,8 +301,12 @@ impl<'a> Write for ZipGenerator<'a> {
|
||||||
while !self.pending_metadata.is_empty() {
|
while !self.pending_metadata.is_empty() {
|
||||||
let result = self.output.write(self.pending_metadata.as_slice());
|
let result = self.output.write(self.pending_metadata.as_slice());
|
||||||
match result {
|
match result {
|
||||||
Ok(0) | Err(_) => { return result; }
|
Ok(0) | Err(_) => {
|
||||||
Ok(n) => { self.pending_metadata.drain(..n); }
|
return result;
|
||||||
|
}
|
||||||
|
Ok(n) => {
|
||||||
|
self.pending_metadata.drain(..n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.file_index >= self.files.len() {
|
if self.file_index >= self.files.len() {
|
||||||
|
|
Loading…
Reference in a new issue