v0.3.2 // fencepost fixes
This commit is contained in:
parent
4ce98de0e1
commit
2d429c75b7
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "breeze"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
edition = "2024"
|
||||
|
||||
[profile.dev.package]
|
||||
|
|
@ -9,22 +9,23 @@ tikv-jemalloc-sys = { opt-level = 3 }
|
|||
[dependencies]
|
||||
argh = "0.1.12"
|
||||
atomic-time = "0.1.4"
|
||||
axum = { version = "0.8.1", features = ["macros", "http2"] }
|
||||
axum-extra = { version = "0.10.0", default-features = false, features = [
|
||||
axum = { version = "0.8.9", features = ["macros", "http2"] }
|
||||
axum-extra = { version = "0.12.6", default-features = false, features = [
|
||||
"tracing",
|
||||
"typed-header",
|
||||
] }
|
||||
base64 = "0.21"
|
||||
base64 = "0.22"
|
||||
bytes = "1"
|
||||
color-eyre = "0.6"
|
||||
dashmap = { version = "6.1.0", features = ["inline"] }
|
||||
headers = "0.4"
|
||||
heed = "0.22.1"
|
||||
hmac = "0.12.1"
|
||||
http = "1.2"
|
||||
img-parts = "0.3"
|
||||
rand = "0.9"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_with = "3.12"
|
||||
serde_with = "3.19"
|
||||
sha2 = "0.10.9"
|
||||
tokio = { version = "1", features = [
|
||||
"rt-multi-thread",
|
||||
|
|
@ -32,6 +33,7 @@ tokio = { version = "1", features = [
|
|||
"net",
|
||||
"fs",
|
||||
"signal",
|
||||
"test-util",
|
||||
] }
|
||||
tokio-stream = "0.1"
|
||||
tokio-util = { version = "0.7", features = ["io"] }
|
||||
|
|
|
|||
|
|
@ -210,9 +210,9 @@ impl Cache {
|
|||
|
||||
/// Returns if an upload is able to be cached
|
||||
/// with the current caching rules
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
pub fn will_use(&self, length: u64) -> bool {
|
||||
length <= self.cfg.max_length
|
||||
length <= (self.cfg.max_length as u64)
|
||||
}
|
||||
|
||||
/// The background job that scans through the cache and removes inactive elements.
|
||||
|
|
@ -221,6 +221,7 @@ impl Cache {
|
|||
/// letting each entry keep track of expiry with its own task
|
||||
pub async fn scanner(&self) {
|
||||
let mut interval = time::interval(self.cfg.scan_freq);
|
||||
interval.tick().await; // Skip first tick
|
||||
|
||||
loop {
|
||||
// We put this first so that it doesn't scan the instant the server starts
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ fn default_motd() -> String {
|
|||
pub struct EngineConfig {
|
||||
/// The url that the instance of breeze is meant to be accessed from.
|
||||
///
|
||||
/// ex: https://picture.wtf would generate links like https://picture.wtf/p/abcdef.png
|
||||
/// ex: `https://picture.wtf` would generate links like `https://picture.wtf/p/abcdef.png`
|
||||
pub base_url: String,
|
||||
|
||||
/// Authentication key for new uploads, will be required if this is specified. (optional)
|
||||
|
|
@ -69,8 +69,8 @@ pub struct DiskConfig {
|
|||
#[derive(Deserialize, Clone)]
|
||||
pub struct CacheConfig {
|
||||
/// The maximum length in bytes that a file can be
|
||||
/// before it skips cache (in seconds)
|
||||
pub max_length: u64,
|
||||
/// before it skips cache (in bytes)
|
||||
pub max_length: usize,
|
||||
|
||||
/// The amount of time a file can last inside the cache (in seconds)
|
||||
#[serde_as(as = "DurationSeconds")]
|
||||
|
|
|
|||
100
src/engine.rs
100
src/engine.rs
|
|
@ -1,6 +1,6 @@
|
|||
use std::{
|
||||
io::SeekFrom,
|
||||
ops::Bound,
|
||||
ops::{Bound, RangeBounds},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
|
|
@ -95,32 +95,53 @@ pub struct Engine {
|
|||
|
||||
/// Try to parse a `Range` header into an easier format to work with
|
||||
fn resolve_range(range: Option<headers::Range>, full_len: u64) -> Option<(u64, u64)> {
|
||||
let last_byte = full_len - 1;
|
||||
// Prepare default range
|
||||
let default = Some((0, full_len));
|
||||
|
||||
let (start, end) =
|
||||
if let Some((start, end)) = range.and_then(|r| r.satisfiable_ranges(full_len).next()) {
|
||||
// satisfiable_ranges will never return Excluded so this is ok
|
||||
let start = if let Bound::Included(start_incl) = start {
|
||||
start_incl
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let end = if let Bound::Included(end_incl) = end {
|
||||
end_incl
|
||||
} else {
|
||||
last_byte
|
||||
};
|
||||
// Take range, otherwise return
|
||||
let Some(range) = range else {
|
||||
return default; // unspecified; use default
|
||||
};
|
||||
|
||||
(start, end)
|
||||
} else {
|
||||
(0, last_byte)
|
||||
};
|
||||
// Get iterator of satisfiable ranges
|
||||
let mut ranges = range.satisfiable_ranges(full_len);
|
||||
|
||||
// catch ranges we can't satisfy
|
||||
if end > last_byte || start > end {
|
||||
// Take first range
|
||||
let Some(range) = ranges.next() else {
|
||||
return default; // empty; use default
|
||||
};
|
||||
|
||||
// If there are multiple ranges, we will
|
||||
// not process the request
|
||||
if ranges.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Convert into a..b range
|
||||
let start = match range.start_bound() {
|
||||
Bound::Included(&x) => x,
|
||||
Bound::Excluded(&x) => x.checked_add(1)?,
|
||||
Bound::Unbounded => 0,
|
||||
};
|
||||
let end = match range.end_bound() {
|
||||
Bound::Included(&x) => x.checked_add(1)?,
|
||||
Bound::Excluded(&x) => x,
|
||||
Bound::Unbounded => full_len,
|
||||
};
|
||||
|
||||
// We can't handle bounds
|
||||
// out of order
|
||||
if start > end {
|
||||
return None;
|
||||
}
|
||||
|
||||
// We can't return more bytes
|
||||
// than we have
|
||||
if end > full_len {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Return
|
||||
Some((start, end))
|
||||
}
|
||||
|
||||
|
|
@ -199,6 +220,7 @@ impl Engine {
|
|||
return Ok(GetOutcome::NotFound);
|
||||
};
|
||||
|
||||
// read length from disk
|
||||
let full_len = self.disk.len(&f).await?;
|
||||
|
||||
// if possible, recache and send a cache response
|
||||
|
|
@ -230,11 +252,11 @@ impl Engine {
|
|||
return Ok(GetOutcome::RangeNotSatisfiable);
|
||||
};
|
||||
|
||||
let range_len = (end - start) + 1;
|
||||
|
||||
// Set up file handle
|
||||
f.seek(SeekFrom::Start(start)).await?;
|
||||
let f = f.take(range_len);
|
||||
let f = f.take(end - start);
|
||||
|
||||
// Return
|
||||
let res = UploadResponse {
|
||||
full_len,
|
||||
range: (start, end),
|
||||
|
|
@ -244,15 +266,27 @@ impl Engine {
|
|||
}
|
||||
};
|
||||
|
||||
// Resolve a..b range
|
||||
let full_len = data.len() as u64;
|
||||
let Some((start, end)) = resolve_range(range, full_len) else {
|
||||
return Ok(GetOutcome::RangeNotSatisfiable);
|
||||
};
|
||||
|
||||
// cut down to range
|
||||
let data = data.slice((start as usize)..=(end as usize));
|
||||
// Cut down to range
|
||||
let data = {
|
||||
// Convert types.
|
||||
// These should never be greater than usize::MAX
|
||||
// if I recall, because max cache length is a usize.
|
||||
let (start, end): (usize, usize) = (
|
||||
start.try_into().expect("start bound"),
|
||||
end.try_into().expect("end bound"),
|
||||
);
|
||||
|
||||
// build response
|
||||
// Slice bytes
|
||||
data.slice(start..end)
|
||||
};
|
||||
|
||||
// Build response
|
||||
let res = UploadResponse {
|
||||
full_len,
|
||||
range: (start, end),
|
||||
|
|
@ -286,9 +320,10 @@ impl Engine {
|
|||
// we found it in cache! take as many bytes as we can
|
||||
let taking = full_data.len().min(SAMPLE_WANTED_BYTES);
|
||||
let data = full_data.slice(0..taking);
|
||||
|
||||
// get len
|
||||
let len = full_data.len() as u64;
|
||||
|
||||
// return
|
||||
(data, len)
|
||||
} else {
|
||||
// not in cache, so try disk
|
||||
|
|
@ -332,10 +367,10 @@ impl Engine {
|
|||
|
||||
if !self.has(&saved_name).await {
|
||||
break saved_name;
|
||||
} else {
|
||||
// there was a name collision. loop and try again
|
||||
info!("name collision! saved_name= {}", saved_name);
|
||||
}
|
||||
|
||||
// there was a name collision. loop and try again
|
||||
info!("name collision! saved_name= {}", saved_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -483,6 +518,7 @@ impl Engine {
|
|||
};
|
||||
}
|
||||
|
||||
// return w/ info for hash calculation
|
||||
Ok((hash_sample.freeze(), observed_len))
|
||||
}
|
||||
|
||||
|
|
@ -533,7 +569,7 @@ impl Engine {
|
|||
Ok(m) => m,
|
||||
// If anything fails, delete the upload and return the error
|
||||
Err(err) => {
|
||||
error!("failed processing upload!");
|
||||
error!(?err, "failed processing upload!");
|
||||
|
||||
self.remove(&saved_name).await?;
|
||||
return Err(err);
|
||||
|
|
|
|||
19
src/main.rs
19
src/main.rs
|
|
@ -35,6 +35,17 @@ struct Args {
|
|||
config: PathBuf,
|
||||
}
|
||||
|
||||
/// Instantiates router.
|
||||
fn router(engine: Engine) -> Router {
|
||||
Router::new()
|
||||
.route("/new", post(new::new))
|
||||
.route("/p/{saved_name}", get(view::view))
|
||||
.route("/del", get(delete::delete))
|
||||
.route("/", get(index::index))
|
||||
.route("/robots.txt", get(index::robots_txt))
|
||||
.with_state(Arc::new(engine))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
// Install color-eyre
|
||||
|
|
@ -74,13 +85,7 @@ async fn main() -> eyre::Result<()> {
|
|||
let engine = Engine::with_config(cfg.engine);
|
||||
|
||||
// Build main router
|
||||
let app = Router::new()
|
||||
.route("/new", post(new::new))
|
||||
.route("/p/{saved_name}", get(view::view))
|
||||
.route("/del", get(delete::delete))
|
||||
.route("/", get(index::index))
|
||||
.route("/robots.txt", get(index::robots_txt))
|
||||
.with_state(Arc::new(engine));
|
||||
let app = router(engine);
|
||||
|
||||
// Start web server
|
||||
info!("starting server.");
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ impl IntoResponse for ViewError {
|
|||
impl IntoResponse for UploadResponse {
|
||||
fn into_response(self) -> Response {
|
||||
let (start, end) = self.range;
|
||||
let range_len = (end - start) + 1;
|
||||
let range_len = end - start;
|
||||
|
||||
let mut res = match self.data {
|
||||
UploadData::Cache(data) => data.into_response(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue