diff --git a/resources/.gitignore b/resources/.gitignore new file mode 100644 index 0000000..c3405b6 --- /dev/null +++ b/resources/.gitignore @@ -0,0 +1,3 @@ +*.png +*.ico +*.svg diff --git a/resources/Makefile b/resources/Makefile new file mode 100644 index 0000000..372524e --- /dev/null +++ b/resources/Makefile @@ -0,0 +1,22 @@ +density := 600 + +icons := transbeam.svg favicon.ico transbeam-192.png transbeam-512.png transbeam-apple.png + +icons: $(icons) + +%.svg: %.asy transbeam-common.asy + asy -f svg $< + +transbeam-apple.png: transbeam.svg + convert -density $(density) -background white $< -resize 192x192 -extent 192x192 $@ + +transbeam-%.png: transbeam-%.svg + convert -density $(density) -background transparent $< -resize $*x$* -extent $*x$* $@ + +favicon.ico: transbeam-16.png transbeam-32.png transbeam-48.png transbeam-64.png + convert -background transparent $^ $@ + +install: icons + cp $(icons) ../static/ +clean: + rm *.svg *.png *.ico diff --git a/resources/transbeam-16.asy b/resources/transbeam-16.asy new file mode 100644 index 0000000..0bc25a4 --- /dev/null +++ b/resources/transbeam-16.asy @@ -0,0 +1,16 @@ +import "transbeam-common.asy" as transbeam; + +transbeamPrefs prefs; +prefs.triangleRadius = 100; +prefs.borderInset = 25; +prefs.circleRadius = 20; +prefs.beamCount = 0; +prefs.starterBeamAngle = 180; +// Not relevant with beamCount 0 +prefs.bigBeamRadius = 36; +prefs.smallBeamRadius = 36; +prefs.beamAngle = 40; + +drawTransbeam(prefs); + +fill(circle((0,0), prefs.circleRadius), transPink); diff --git a/resources/transbeam-192.asy b/resources/transbeam-192.asy new file mode 120000 index 0000000..cbbcb9c --- /dev/null +++ b/resources/transbeam-192.asy @@ -0,0 +1 @@ +transbeam.asy \ No newline at end of file diff --git a/resources/transbeam-favicon.asy b/resources/transbeam-32.asy similarity index 82% rename from resources/transbeam-favicon.asy rename to resources/transbeam-32.asy index 526b182..29ade49 100644 --- a/resources/transbeam-favicon.asy +++ b/resources/transbeam-32.asy @@ -5,9 +5,9 @@ prefs.triangleRadius = 100; prefs.borderInset = 14; prefs.circleRadius = 12; prefs.bigBeamRadius = 36; -prefs.smallBeamRadius = 36; +prefs.smallBeamRadius = 30; prefs.beamCount = 3; -prefs.beamAngle = 40; +prefs.beamAngle = 32; prefs.starterBeamAngle = 80; drawTransbeam(prefs); diff --git a/resources/transbeam-48.asy b/resources/transbeam-48.asy new file mode 100644 index 0000000..698ed23 --- /dev/null +++ b/resources/transbeam-48.asy @@ -0,0 +1,13 @@ +import "transbeam-common.asy" as transbeam; + +transbeamPrefs prefs; +prefs.triangleRadius = 100; +prefs.borderInset = 12; +prefs.circleRadius = 12; +prefs.bigBeamRadius = 36; +prefs.smallBeamRadius = 30; +prefs.beamCount = 7; +prefs.beamAngle = 15; +prefs.starterBeamAngle = 36; + +drawTransbeam(prefs); diff --git a/resources/transbeam-512.asy b/resources/transbeam-512.asy new file mode 120000 index 0000000..cbbcb9c --- /dev/null +++ b/resources/transbeam-512.asy @@ -0,0 +1 @@ +transbeam.asy \ No newline at end of file diff --git a/resources/transbeam-64.asy b/resources/transbeam-64.asy new file mode 100644 index 0000000..09725da --- /dev/null +++ b/resources/transbeam-64.asy @@ -0,0 +1,13 @@ +import "transbeam-common.asy" as transbeam; + +transbeamPrefs prefs; +prefs.triangleRadius = 100; +prefs.borderInset = 9; +prefs.circleRadius = 12; +prefs.bigBeamRadius = 33; +prefs.smallBeamRadius = 25; +prefs.beamCount = 9; +prefs.beamAngle = 10; +prefs.starterBeamAngle = 24; + +drawTransbeam(prefs); diff --git a/resources/transbeam-common.asy b/resources/transbeam-common.asy index 5e218a6..f657a7f 100644 --- a/resources/transbeam-common.asy +++ b/resources/transbeam-common.asy @@ -4,21 +4,22 @@ struct transbeamPrefs { real circleRadius; real bigBeamRadius; real smallBeamRadius; - real beamCount; + int beamCount; real beamAngle; real starterBeamAngle; } +pen transBlue = rgb("55cdfc"); +pen transPink = rgb("f7a8b8"); + +pen bigBeamColor = transPink; +pen smallBeamColor = transBlue; + +path borderTriangle = dir(90)--dir(210)--dir(330)--cycle; + void drawTransbeam(transbeamPrefs prefs) { unravel prefs; - pen transBlue = rgb("55cdfc"); - pen transPink = rgb("f7a8b8"); - - pen bigBeamColor = transPink; - pen smallBeamColor = transBlue; - - path borderTriangle = dir(90)--dir(210)--dir(330)--cycle; path outerBorder = scale(triangleRadius)*borderTriangle; fill(outerBorder, white); diff --git a/src/state.rs b/src/state.rs index 0405245..a71a050 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, io::ErrorKind}; -use log::{error, info}; +use log::{error, info, warn, debug}; use tokio::{ fs::File, io::{AsyncReadExt, AsyncWriteExt}, @@ -51,6 +51,36 @@ pub(crate) mod timestamp { } } +async fn is_valid_entry(key: &str, info: &DownloadableFile) -> bool { + if !crate::util::is_ascii_alphanumeric(&key) { + error!("Invalid key in persistent storage: {}", key); + return false; + } + let file = if let Ok(f) = File::open(storage_dir().join(&key)).await { + f + } else { + error!( + "Unable to open file {} referenced in persistent storage", + key + ); + return false; + }; + let metadata = if let Ok(md) = file.metadata().await { + md + } else { + error!( + "Unable to get metadata for file {} referenced in persistent storage", + key + ); + return false; + }; + if metadata.len() != info.size { + error!("Mismatched file size for file {} referenced in persistent storage: expected {}, found {}", key, info.size, metadata.len()); + return false; + } + true +} + pub(crate) struct PersistentState(HashMap); impl PersistentState { pub(crate) async fn load() -> std::io::Result { @@ -63,35 +93,18 @@ impl PersistentState { info!("Loaded {} file entries from persistent storage", map.len()); let mut filtered: HashMap = HashMap::new(); for (key, info) in map.into_iter() { - if !crate::util::is_ascii_alphanumeric(&key) { - error!("Invalid key in persistent storage: {}", key); - continue; - } - let file = if let Ok(f) = File::open(storage_dir().join(&key)).await { - f + if is_valid_entry(&key, &info).await { + filtered.insert(key, info); } else { - error!( - "Unable to open file {} referenced in persistent storage", - key - ); - continue; - }; - let metadata = if let Ok(md) = file.metadata().await { - md - } else { - error!( - "Unable to get metadata for file {} referenced in persistent storage", - key - ); - continue; - }; - if metadata.len() != info.size { - error!("Mismatched file size for file {} referenced in persistent storage: expected {}, found {}", key, info.size, metadata.len()); - continue; + info!("Deleting invalid file {}", key); + if let Err(e) = tokio::fs::remove_file(storage_dir().join(&key)).await { + warn!("Failed to delete invalid file {}: {}", key, e); + } } - filtered.insert(key, info); } - Ok(Self(filtered)) + let mut loaded = Self(filtered); + loaded.save().await?; + Ok(loaded) } Err(e) => { if let ErrorKind::NotFound = e.kind() { @@ -104,6 +117,7 @@ impl PersistentState { } async fn save(&mut self) -> std::io::Result<()> { + info!("saving updated state: {} entries", self.0.len()); File::create(storage_dir().join(STATE_FILE_NAME)) .await? .write_all(&serde_json::to_vec_pretty(&self.0)?) @@ -124,6 +138,7 @@ impl PersistentState { } pub(crate) async fn remove_file(&mut self, key: &str) -> std::io::Result<()> { + debug!("removing entry {} from state", key); self.0.remove(key); self.save().await } diff --git a/src/upload.rs b/src/upload.rs index a37efde..d8f3003 100644 --- a/src/upload.rs +++ b/src/upload.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, io::Write, task::Waker}; -use actix::{Actor, ActorContext, AsyncContext, Handler, Message, StreamHandler}; +use actix::{fut::future::ActorFutureExt, Actor, ActorContext, AsyncContext, Handler, Message, StreamHandler}; use actix_http::ws::{CloseReason, Item}; use actix_web_actors::ws::{self, CloseCode}; use bytes::Bytes; @@ -9,7 +9,7 @@ use rand::distributions::{Alphanumeric, DistString}; use serde::Deserialize; use time::OffsetDateTime; -use crate::{file::LiveWriter, DownloadableFile, UploadedFile}; +use crate::{file::LiveWriter, DownloadableFile, UploadedFile, storage_dir}; const MAX_FILES: usize = 256; const FILENAME_DATE_FORMAT: &[time::format_description::FormatItem] = @@ -115,7 +115,6 @@ impl StreamHandler> for Uploader { Err(e) => { error!("Websocket error: {}", e); self.cleanup_after_error(ctx); - ctx.stop(); return; } }; @@ -128,7 +127,6 @@ impl StreamHandler> for Uploader { description: Some(e.to_string()), })); self.cleanup_after_error(ctx); - ctx.stop(); } Ok(true) => { info!("Finished uploading data"); @@ -140,9 +138,9 @@ impl StreamHandler> for Uploader { let data = self.app_data.clone(); let filename = self.storage_filename.clone(); ctx.wait(actix::fut::wrap_future(async move { + debug!("Spawned future to remove uploader from entry {} before stopping", filename); data.write().await.remove_uploader(&filename); - })); - ctx.stop(); + }).map(|_, _, ctx: &mut Self::Context| ctx.stop())); } _ => (), } @@ -191,7 +189,7 @@ impl Uploader { } let storage_filename = Alphanumeric.sample_string(&mut rand::thread_rng(), 8); self.storage_filename = storage_filename.clone(); - let storage_path = super::storage_dir().join(storage_filename.clone()); + let storage_path = storage_dir().join(storage_filename.clone()); info!("storing to: {:?}", storage_path); let writer = super::file::LiveFileWriter::new(&storage_path)?; let addr = Some(ctx.address()); @@ -225,6 +223,7 @@ impl Uploader { self.writer = Some(writer); let data = self.app_data.clone(); ctx.spawn(actix::fut::wrap_future(async move { + debug!("Spawned future to add entry {} to state", storage_filename); data.write() .await .add_file(storage_filename, downloadable_file) @@ -275,10 +274,15 @@ impl Uploader { } fn cleanup_after_error(&mut self, ctx: &mut ::Context) { + info!("Cleaning up after failed upload of {}", self.storage_filename); let data = self.app_data.clone(); let filename = self.storage_filename.clone(); - ctx.spawn(actix::fut::wrap_future(async move { + ctx.wait(actix::fut::wrap_future(async move { + debug!("Spawned future to remove entry {} from state", filename); data.write().await.remove_file(&filename).await.unwrap(); - })); + }).map(|_, _, ctx: &mut ::Context| ctx.stop())); + if let Err(e) = std::fs::remove_file(storage_dir().join(&self.storage_filename)) { + error!("Failed to remove file {}: {}", self.storage_filename, e); + } } } diff --git a/static/favicon.ico b/static/favicon.ico index 4cd2d19..daf05d5 100644 Binary files a/static/favicon.ico and b/static/favicon.ico differ diff --git a/static/index.html b/static/index.html index 2c2e053..d70d060 100644 --- a/static/index.html +++ b/static/index.html @@ -4,6 +4,8 @@ + + transbeam diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 0000000..ef9b318 --- /dev/null +++ b/static/manifest.json @@ -0,0 +1,22 @@ +{ + "name": "transbeam", + "icons": [ + { + "src": "transbeam.svg", + "type": "image/svg+xml", + "sizes": "512x512" + }, + { + "src": "transbeam-192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "transbeam-512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": "./", + "background_color": "white" +} diff --git a/static/transbeam-192.png b/static/transbeam-192.png new file mode 100644 index 0000000..04564d9 Binary files /dev/null and b/static/transbeam-192.png differ diff --git a/static/transbeam-512.png b/static/transbeam-512.png new file mode 100644 index 0000000..3be191e Binary files /dev/null and b/static/transbeam-512.png differ diff --git a/static/transbeam-apple.png b/static/transbeam-apple.png new file mode 100644 index 0000000..d5d822f Binary files /dev/null and b/static/transbeam-apple.png differ