let uploader set a collection name for a multiple-file upload

This commit is contained in:
xenofem 2022-05-26 15:42:11 -04:00
parent 3125e1f4e7
commit 97f58bbbe3
5 changed files with 28 additions and 4 deletions

View file

@ -20,6 +20,10 @@ const MAX_FILES: usize = 256;
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]");
fn sanitise(name: &str) -> String {
sanitise_file_name::sanitise(&name.nfd().collect::<String>())
}
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
enum Error { enum Error {
#[error("Failed to parse file metadata")] #[error("Failed to parse file metadata")]
@ -104,7 +108,7 @@ pub struct UploadedFile {
impl UploadedFile { impl UploadedFile {
fn new(name: &str, size: u64, modtime: OffsetDateTime) -> Self { fn new(name: &str, size: u64, modtime: OffsetDateTime) -> Self {
Self { Self {
name: sanitise_file_name::sanitise(&name.nfd().collect::<String>()), name: sanitise(name),
size, size,
modtime, modtime,
} }
@ -134,6 +138,7 @@ struct UploadManifest {
files: Vec<RawUploadedFile>, files: Vec<RawUploadedFile>,
lifetime: u16, lifetime: u16,
password: String, password: String,
collection_name: Option<String>,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -229,6 +234,7 @@ impl Uploader {
files: raw_files, files: raw_files,
lifetime, lifetime,
password, password,
collection_name,
} = serde_json::from_slice(text.as_bytes())?; } = serde_json::from_slice(text.as_bytes())?;
if std::env::var("TRANSBEAM_UPLOAD_PASSWORD") != Ok(password) { if std::env::var("TRANSBEAM_UPLOAD_PASSWORD") != Ok(password) {
return Err(Error::IncorrectPassword); return Err(Error::IncorrectPassword);
@ -277,9 +283,13 @@ impl Uploader {
let now = OffsetDateTime::now_utc(); let now = OffsetDateTime::now_utc();
let zip_writer = super::zip::ZipGenerator::new(files.clone(), writer); let zip_writer = super::zip::ZipGenerator::new(files.clone(), writer);
let size = zip_writer.total_size(); let size = zip_writer.total_size();
let download_filename = super::APP_NAME.to_owned() let download_filename = collection_name
.map(|f| sanitise(&(f + ".zip")))
.unwrap_or_else(|| {
super::APP_NAME.to_owned()
+ &now.format(FILENAME_DATE_FORMAT).unwrap() + &now.format(FILENAME_DATE_FORMAT).unwrap()
+ ".zip"; + ".zip"
});
(Box::new(zip_writer), download_filename, size, now) (Box::new(zip_writer), download_filename, size, now)
} else { } else {
( (

View file

@ -4,6 +4,7 @@
* landing: haven't entered upload password yet * landing: haven't entered upload password yet
* uploads_closed: uploading is currently unavailable * uploads_closed: uploading is currently unavailable
* no_files: no files are selected * no_files: no files are selected
* one_file: exactly one file is selected
* selecting: upload hasn't started yet * selecting: upload hasn't started yet
* uploading: upload is in progress * uploading: upload is in progress
* completed: upload is done * completed: upload is done
@ -43,6 +44,8 @@ body.landing #upload_controls { display: none; }
body.selecting #upload_settings { display: revert; } body.selecting #upload_settings { display: revert; }
body.no_files #upload_settings { display: none; } body.no_files #upload_settings { display: none; }
body.one_file #collection_name { display: none; }
body.selecting #download_code_container { display: none; } body.selecting #download_code_container { display: none; }
#progress_container { display: none; } #progress_container { display: none; }

View file

@ -273,6 +273,7 @@ button:disabled, input:disabled + .fake_button, input[type="submit"]:disabled {
#lifetime_container { #lifetime_container {
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px;
} }
input[type="text"], input[type="password"] { input[type="text"], input[type="password"] {

View file

@ -21,6 +21,7 @@ let fileInput;
let fileList; let fileList;
let uploadButton; let uploadButton;
let lifetimeInput; let lifetimeInput;
let collectionNameInput;
let downloadCode; let downloadCode;
let progressPercentage; let progressPercentage;
@ -43,6 +44,7 @@ document.addEventListener('DOMContentLoaded', () => {
fileList = document.getElementById('file_list'); fileList = document.getElementById('file_list');
uploadButton = document.getElementById('upload_button'); uploadButton = document.getElementById('upload_button');
lifetimeInput = document.getElementById('lifetime'); lifetimeInput = document.getElementById('lifetime');
collectionNameInput = document.getElementById('collection_name');
downloadCode = document.getElementById('download_code'); downloadCode = document.getElementById('download_code');
progressPercentage = document.getElementById('progress_percentage'); progressPercentage = document.getElementById('progress_percentage');
progressSize = document.getElementById('progress_size'); progressSize = document.getElementById('progress_size');
@ -120,6 +122,9 @@ function updateFiles() {
fileInputMessage.textContent = 'Select files to upload...'; fileInputMessage.textContent = 'Select files to upload...';
document.body.className = 'no_files selecting' + extraClasses; document.body.className = 'no_files selecting' + extraClasses;
} else { } else {
if (files.length === 1) {
extraClasses += " one_file";
}
fileInputMessage.textContent = 'Select more files to upload...'; fileInputMessage.textContent = 'Select more files to upload...';
uploadButton.textContent = `Upload ${files.length} file${files.length > 1 ? 's' : ''} (${displaySize(totalBytes)})`; uploadButton.textContent = `Upload ${files.length} file${files.length > 1 ? 's' : ''} (${displaySize(totalBytes)})`;
document.body.className = 'selecting' + extraClasses; document.body.className = 'selecting' + extraClasses;
@ -184,6 +189,7 @@ function beginUpload() {
function sendManifest() { function sendManifest() {
const lifetime = parseInt(lifetimeInput.value); const lifetime = parseInt(lifetimeInput.value);
const collection_name = collectionNameInput.value || null;
const fileMetadata = files.map((file) => ({ const fileMetadata = files.map((file) => ({
name: file.name, name: file.name,
size: file.size, size: file.size,
@ -192,6 +198,7 @@ function sendManifest() {
socket.send(JSON.stringify({ socket.send(JSON.stringify({
files: fileMetadata, files: fileMetadata,
lifetime, lifetime,
collection_name,
password: uploadPassword, password: uploadPassword,
})); }));
} }

View file

@ -60,6 +60,9 @@
</select> </select>
</label> </label>
</div> </div>
<div>
<input type="text" id="collection_name" placeholder="Collection name"/>
</div>
</div> </div>
<div id="download_code_container"> <div id="download_code_container">
<div id="download_code_main"> <div id="download_code_main">