diff --git a/Cargo.toml b/Cargo.toml
index 7b5f865..939d7c7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,7 +15,7 @@ path = "src/bin/main.rs"
name = "route96"
[features]
-default = ["nip96", "blossom", "analytics"]
+default = ["nip96", "blossom", "analytics", "ranges"]
media-compression = ["dep:ffmpeg-rs-raw", "dep:libc"]
labels = ["nip96", "dep:candle-core", "dep:candle-nn", "dep:candle-transformers"]
nip96 = ["media-compression"]
@@ -24,6 +24,7 @@ bin-void-cat-migrate = ["dep:sqlx-postgres"]
torrent-v2 = []
analytics = []
void-cat-redirects = ["dep:sqlx-postgres"]
+ranges = ["dep:http-range-header"]
[dependencies]
log = "0.4.21"
@@ -44,6 +45,7 @@ url = "2.5.0"
serde_with = { version = "3.8.1", features = ["hex"] }
reqwest = "0.12.8"
clap = { version = "4.5.18", features = ["derive"] }
+mime2ext = "0.1.53"
libc = { version = "0.2.153", optional = true }
ffmpeg-rs-raw = { git = "https://git.v0l.io/Kieran/ffmpeg-rs-raw.git", rev = "b358b3e4209da827e021d979c7d35876594d0285", optional = true }
@@ -51,5 +53,5 @@ candle-core = { git = "https://git.v0l.io/huggingface/candle.git", tag = "0.8.1"
candle-nn = { git = "https://git.v0l.io/huggingface/candle.git", tag = "0.8.1", optional = true }
candle-transformers = { git = "https://git.v0l.io/huggingface/candle.git", tag = "0.8.1", optional = true }
sqlx-postgres = { version = "0.8.2", optional = true, features = ["chrono", "uuid"] }
-mime2ext = "0.1.53"
-http-range-header = "0.4.2"
+http-range-header = { version = "0.4.2", optional = true }
+
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..abab362
--- /dev/null
+++ b/index.html
@@ -0,0 +1,184 @@
+
+
+
+
+ route96
+
+
+
+
+
+ Welcome to route96
+
+
+
+
+
+
+
+ You must have a nostr extension for this to work
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index 250ec82..e351be5 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -12,6 +12,7 @@ use anyhow::Error;
use http_range_header::{
parse_range_header, EndPosition, StartPosition, SyntacticallyCorrectRange,
};
+use log::{debug, warn};
use nostr::Event;
use rocket::fs::NamedFile;
use rocket::http::{ContentType, Header, Status};
@@ -100,13 +101,27 @@ impl Nip94Event {
}
}
+/// Range request handler over file handle
struct RangeBody {
- pub file: File,
- pub file_size: u64,
- pub ranges: Vec,
-
+ file: File,
+ file_size: u64,
+ ranges: Vec,
current_range_index: usize,
current_offset: u64,
+ poll_complete: bool,
+}
+
+impl RangeBody {
+ pub fn new(file: File, file_size: u64, ranges: Vec) -> Self {
+ Self {
+ file,
+ file_size,
+ ranges,
+ current_offset: 0,
+ current_range_index: 0,
+ poll_complete: false,
+ }
+ }
}
impl AsyncRead for RangeBody {
@@ -138,23 +153,32 @@ impl AsyncRead for RangeBody {
return self.poll_read(cx, buf);
}
- let pinned = pin!(&mut self.file);
- pinned.start_seek(SeekFrom::Start(range_start))?;
+ if !self.poll_complete {
+ // start seeking to our read position
+ let pinned = pin!(&mut self.file);
+ pinned.start_seek(SeekFrom::Start(range_start))?;
+ self.poll_complete = true;
+ }
- let pinned = pin!(&mut self.file);
- match pinned.poll_complete(cx) {
- Poll::Ready(Ok(_)) => {}
- Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
- Poll::Pending => return Poll::Pending,
+ if self.poll_complete {
+ let pinned = pin!(&mut self.file);
+ match pinned.poll_complete(cx) {
+ Poll::Ready(Ok(_)) => {
+ self.poll_complete = false;
+ }
+ Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
+ Poll::Pending => return Poll::Pending,
+ }
}
// Read data from the file
let pinned = pin!(&mut self.file);
- let n = pinned.poll_read(cx, &mut buf.take(bytes_to_read as usize));
+ let n = pinned.poll_read(cx, buf);
if let Poll::Ready(Ok(())) = n {
self.current_offset += bytes_to_read;
Poll::Ready(Ok(()))
} else {
+ self.poll_complete = true;
Poll::Pending
}
}
@@ -170,30 +194,51 @@ impl<'r> Responder<'r, 'static> for FilePayload {
response.set_header(Header::new("accept-ranges", "bytes"));
if let Some(r) = request.headers().get("range").next() {
if let Ok(ranges) = parse_range_header(r) {
- let r_body = RangeBody {
- file_size: self.info.size, // TODO: handle filesize mismatch
- file: self.file,
- ranges: ranges.ranges,
- current_range_index: 0,
- current_offset: 0,
- };
- response.set_streamed_body(Box::pin(r_body));
+ if ranges.ranges.len() > 1 {
+ warn!("Multipart ranges are not supported, fallback to non-range request");
+ response.set_streamed_body(self.file);
+ } else {
+ let single_range = ranges.ranges.first().unwrap();
+ let range_start = match single_range.start {
+ StartPosition::Index(i) => i,
+ StartPosition::FromLast(i) => self.info.size - i,
+ };
+ let range_end = match single_range.end {
+ EndPosition::Index(i) => i,
+ EndPosition::LastByte => self.info.size,
+ };
+ debug!("Range: {:?} {:?}", range_start..range_end, single_range);
+ let r_len = range_end - range_start;
+ let r_body = RangeBody::new(self.file, self.info.size, ranges.ranges);
+
+ response.set_status(Status::PartialContent);
+ response.set_header(Header::new("content-length", r_len.to_string()));
+ response.set_header(Header::new(
+ "content-range",
+ format!("bytes {}-{}/{}", range_start, range_end, self.info.size),
+ ));
+ response.set_streamed_body(Box::pin(r_body));
+ }
}
} else {
response.set_streamed_body(self.file);
}
}
#[cfg(not(feature = "ranges"))]
- response.set_streamed_body(self.file);
- response.set_header(Header::new("content-length", self.info.size.to_string()));
+ {
+ response.set_streamed_body(self.file);
+ response.set_header(Header::new("content-length", self.info.size.to_string()));
+ }
if let Ok(ct) = ContentType::from_str(&self.info.mime_type) {
response.set_header(ct);
}
- response.set_header(Header::new(
- "content-disposition",
- format!("inline; filename=\"{}\"", self.info.name),
- ));
+ if !self.info.name.is_empty() {
+ response.set_header(Header::new(
+ "content-disposition",
+ format!("inline; filename=\"{}\"", self.info.name),
+ ));
+ }
Ok(response)
}
}
@@ -247,7 +292,7 @@ async fn delete_file(
#[rocket::get("/")]
pub async fn root() -> Result {
#[cfg(debug_assertions)]
- let index = "./ui_src/dist/index.html";
+ let index = "./index.html";
#[cfg(not(debug_assertions))]
let index = "./ui/index.html";
if let Ok(f) = NamedFile::open(index).await {