fix weird end-of-file bug by having downloaders use inotify to directly track changes

This commit is contained in:
xenofem 2022-04-29 22:36:44 -04:00
parent ba4c7bfcbe
commit cc0aaaab94
8 changed files with 246 additions and 283 deletions

View file

@ -1,6 +1,6 @@
use std::{collections::HashSet, io::Write, task::Waker};
use std::{collections::HashSet, fs::File, io::Write};
use actix::{fut::future::ActorFutureExt, Actor, ActorContext, AsyncContext, Handler, Message, StreamHandler};
use actix::{fut::future::ActorFutureExt, Actor, ActorContext, AsyncContext, 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, storage_dir};
use crate::{storage_dir, DownloadableFile, UploadedFile};
const MAX_FILES: usize = 256;
const FILENAME_DATE_FORMAT: &[time::format_description::FormatItem] =
@ -54,7 +54,7 @@ impl Error {
}
pub struct Uploader {
writer: Option<Box<dyn LiveWriter>>,
writer: Option<Box<dyn Write>>,
storage_filename: String,
app_data: super::AppData,
bytes_remaining: u64,
@ -75,21 +75,6 @@ impl Actor for Uploader {
type Context = ws::WebsocketContext<Self>;
}
#[derive(Message)]
#[rtype(result = "()")]
pub(crate) struct WakerMessage(pub Waker);
impl Handler<WakerMessage> for Uploader {
type Result = ();
fn handle(&mut self, msg: WakerMessage, _: &mut Self::Context) {
if let Some(w) = self.writer.as_mut() {
w.add_waker(msg.0);
} else {
error!("Got a wakeup request before creating a file");
}
}
}
#[derive(Debug, Deserialize)]
struct RawUploadedFile {
name: String,
@ -108,6 +93,15 @@ impl RawUploadedFile {
}
}
fn stop_and_flush<T>(_: T, u: &mut Uploader, ctx: &mut <Uploader as Actor>::Context) {
ctx.stop();
if let Some(w) = u.writer.as_mut() {
if let Err(e) = w.flush() {
error!("Failed to flush writer: {}", e);
}
}
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Uploader {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
let msg = match msg {
@ -130,17 +124,11 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Uploader {
}
Ok(true) => {
info!("Finished uploading data");
self.writer.as_mut().map(|w| w.flush());
ctx.close(Some(ws::CloseReason {
code: CloseCode::Normal,
description: None,
}));
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);
}).map(|_, _, ctx: &mut Self::Context| ctx.stop()));
stop_and_flush((), self, ctx);
}
_ => (),
}
@ -191,9 +179,11 @@ impl Uploader {
self.storage_filename = 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());
let (writer, downloadable_file): (Box<dyn LiveWriter>, _) = if files.len() > 1 {
let writer = File::options()
.write(true)
.create_new(true)
.open(&storage_path)?;
let (writer, downloadable_file): (Box<dyn Write>, _) = if files.len() > 1 {
info!("Wrapping in zipfile generator");
let now = OffsetDateTime::now_utc();
let zip_writer = super::zip::ZipGenerator::new(files, writer);
@ -206,7 +196,6 @@ impl Uploader {
name: download_filename,
size,
modtime: now,
uploader: addr,
},
)
} else {
@ -216,7 +205,6 @@ impl Uploader {
name: files[0].name.clone(),
size: files[0].size,
modtime: files[0].modtime,
uploader: addr,
},
)
};
@ -274,13 +262,19 @@ impl Uploader {
}
fn cleanup_after_error(&mut self, ctx: &mut <Self as Actor>::Context) {
info!("Cleaning up after failed upload of {}", self.storage_filename);
info!(
"Cleaning up after failed upload of {}",
self.storage_filename
);
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 entry {} from state", filename);
data.write().await.remove_file(&filename).await.unwrap();
}).map(|_, _, ctx: &mut <Self as Actor>::Context| ctx.stop()));
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(stop_and_flush),
);
if let Err(e) = std::fs::remove_file(storage_dir().join(&self.storage_filename)) {
error!("Failed to remove file {}: {}", self.storage_filename, e);
}