use crate::excel::IngestOptions;
use std::fmt;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Quota {
pub queries_per_day: Option<u64>,
pub bytes_stored: Option<u64>,
pub files: Option<usize>,
pub bytes_per_file: Option<u64>,
pub rows_per_file: Option<usize>,
}
impl Quota {
pub fn unlimited() -> Self {
Self::default()
}
pub fn ingest_options(&self, base: IngestOptions) -> IngestOptions {
IngestOptions {
max_rows: match self.rows_per_file {
Some(cap) => base.max_rows.min(cap),
None => base.max_rows,
},
..base
}
}
pub fn check_file_bytes(&self, bytes: u64, file: &str) -> Result<(), QuotaExceeded> {
match self.bytes_per_file {
Some(cap) if bytes > cap => Err(QuotaExceeded::BytesPerFile {
limit: cap,
bytes,
file: file.to_string(),
}),
_ => Ok(()),
}
}
pub fn check_rows(&self, rows: usize, file: &str) -> Result<(), QuotaExceeded> {
match self.rows_per_file {
Some(cap) if rows > cap => Err(QuotaExceeded::RowsPerFile {
limit: cap,
rows,
file: file.to_string(),
}),
_ => Ok(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QuotaExceeded {
QueriesPerDay {
limit: u64,
used: u64,
},
BytesStored {
limit: u64,
used: u64,
requested: u64,
},
Files {
limit: usize,
used: usize,
},
BytesPerFile {
limit: u64,
bytes: u64,
file: String,
},
RowsPerFile {
limit: usize,
rows: usize,
file: String,
},
}
impl fmt::Display for QuotaExceeded {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::QueriesPerDay { limit, used } => write!(
f,
"quota: {used} of {limit} queries used today; the allowance resets at midnight UTC"
),
Self::BytesStored {
limit,
used,
requested,
} => write!(
f,
"quota: storing {requested} more bytes would exceed {limit} bytes ({used} in use); delete a file first"
),
Self::Files { limit, used } => {
write!(
f,
"quota: {used} of {limit} files in use; delete a file first"
)
}
Self::BytesPerFile { limit, bytes, file } => {
write!(
f,
"quota: `{file}` is {bytes} bytes, the limit per file is {limit}"
)
}
Self::RowsPerFile { limit, rows, file } => {
write!(
f,
"quota: `{file}` has {rows} rows, the limit per file is {limit}"
)
}
}
}
}
impl std::error::Error for QuotaExceeded {}
pub fn today() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() / 86_400)
.unwrap_or(0)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize)]
pub struct Usage {
pub day: u64,
pub queries_today: u64,
pub bytes_stored: u64,
pub files: usize,
}
#[derive(Debug, Default)]
pub struct UsageMeter {
queries: Mutex<(u64, u64)>, bytes: AtomicU64,
files: AtomicUsize,
}
impl UsageMeter {
pub fn new() -> Self {
Self::default()
}
pub fn with_storage(bytes: u64, files: usize) -> Self {
Self {
queries: Mutex::new((0, 0)),
bytes: AtomicU64::new(bytes),
files: AtomicUsize::new(files),
}
}
pub fn prime_queries(&self, day: u64, count: u64) {
let mut q = self.queries.lock().unwrap_or_else(|e| e.into_inner());
*q = (day, count);
}
pub fn consume_query(&self, quota: &Quota) -> Result<(), QuotaExceeded> {
self.consume_query_on(today(), quota)
}
pub fn consume_query_on(&self, day: u64, quota: &Quota) -> Result<(), QuotaExceeded> {
let mut q = self.queries.lock().unwrap_or_else(|e| e.into_inner());
if q.0 != day {
*q = (day, 0);
}
if let Some(limit) = quota.queries_per_day
&& q.1 >= limit
{
return Err(QuotaExceeded::QueriesPerDay { limit, used: q.1 });
}
q.1 += 1;
Ok(())
}
pub fn add_file(&self, bytes: u64, file: &str, quota: &Quota) -> Result<(), QuotaExceeded> {
quota.check_file_bytes(bytes, file)?;
let files = self.files.load(Ordering::SeqCst);
if let Some(limit) = quota.files
&& files >= limit
{
return Err(QuotaExceeded::Files { limit, used: files });
}
let used = self.bytes.load(Ordering::SeqCst);
if let Some(limit) = quota.bytes_stored
&& used.saturating_add(bytes) > limit
{
return Err(QuotaExceeded::BytesStored {
limit,
used,
requested: bytes,
});
}
self.bytes.fetch_add(bytes, Ordering::SeqCst);
self.files.fetch_add(1, Ordering::SeqCst);
Ok(())
}
pub fn remove_file(&self, bytes: u64) {
let _ = self
.bytes
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |b| {
Some(b.saturating_sub(bytes))
});
let _ = self
.files
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |f| {
Some(f.saturating_sub(1))
});
}
pub fn usage(&self) -> Usage {
let q = *self.queries.lock().unwrap_or_else(|e| e.into_inner());
Usage {
day: q.0,
queries_today: q.1,
bytes_stored: self.bytes.load(Ordering::SeqCst),
files: self.files.load(Ordering::SeqCst),
}
}
}