mirror of
https://github.com/Dangoware/confetti-box.git
synced 2025-06-22 14:42:59 -05:00
Consolidated many types into the confetti-box
crate for use in imu
This commit is contained in:
parent
5a8a19cb64
commit
d2f73a7371
7 changed files with 46 additions and 196 deletions
|
@ -2,7 +2,7 @@
|
|||
resolver = "2"
|
||||
members = [
|
||||
"confetti-box",
|
||||
"confetti-cli",
|
||||
"imu",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
|
|
|
@ -8,10 +8,11 @@ use maud::{html, Markup, DOCTYPE};
|
|||
use rocket::{
|
||||
get, http::ContentType, response::{self, Redirect, Responder, Response}, serde::{self, json::Json}, tokio::{self, fs::File}, uri, Request, State
|
||||
};
|
||||
use ::serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
database::{Mmid, MochiFile, Mochibase}, settings::Settings, strings::{to_pretty_size, to_pretty_time, BreakStyle, TimeGranularity}
|
||||
database::{Mmid, MochiFile, Mochibase}, settings::Settings, strings::{to_pretty_size, pretty_time, BreakStyle, TimeGranularity}
|
||||
};
|
||||
|
||||
/// An endpoint to obtain information about the server's capabilities
|
||||
|
@ -56,7 +57,7 @@ pub async fn file_info_opengraph(
|
|||
let size = to_pretty_size(file.metadata().await.ok()?.len());
|
||||
|
||||
let seconds_till_expiry = entry.expiry().signed_duration_since(Utc::now()).num_seconds();
|
||||
let expiry = to_pretty_time(seconds_till_expiry as u32, BreakStyle::Space, TimeGranularity::Minutes);
|
||||
let expiry = pretty_time(seconds_till_expiry, BreakStyle::Space, TimeGranularity::Minutes);
|
||||
|
||||
let title = entry.name().clone() + " - " + &size + " - " + &expiry;
|
||||
|
||||
|
@ -82,14 +83,14 @@ pub async fn file_info_opengraph(
|
|||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct ServerInfo {
|
||||
max_filesize: u64,
|
||||
max_duration: u32,
|
||||
default_duration: u32,
|
||||
pub max_filesize: u64,
|
||||
pub max_duration: u32,
|
||||
pub default_duration: u32,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
allowed_durations: Vec<u32>,
|
||||
pub allowed_durations: Vec<u32>,
|
||||
}
|
||||
|
||||
#[get("/f/<mmid>")]
|
||||
|
|
|
@ -14,7 +14,7 @@ use std::{
|
|||
use crate::{
|
||||
pages::{footer, head},
|
||||
settings::Settings,
|
||||
strings::to_pretty_time,
|
||||
strings::pretty_time,
|
||||
};
|
||||
use chrono::{TimeDelta, Utc};
|
||||
use database::{Chunkbase, ChunkedInfo, Mmid, MochiFile, Mochibase};
|
||||
|
@ -48,7 +48,7 @@ pub fn home(settings: &State<Settings>) -> Markup {
|
|||
button.button.{@if settings.duration.default == *d { "selected" }}
|
||||
data-duration-seconds=(d.num_seconds())
|
||||
{
|
||||
(PreEscaped(to_pretty_time(d.num_seconds() as u32, BreakStyle::Break, TimeGranularity::Seconds)))
|
||||
(PreEscaped(pretty_time(d.num_seconds() as u32, BreakStyle::Break, TimeGranularity::Seconds)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,23 @@ pub enum TimeGranularity {
|
|||
Seconds,
|
||||
}
|
||||
|
||||
pub fn to_pretty_time(seconds: u32, breaks: BreakStyle, granularity: TimeGranularity) -> String {
|
||||
pub fn pretty_time_short(seconds: i64) -> String {
|
||||
let days = (seconds as f32 / 86400.0).floor();
|
||||
let hour = ((seconds as f32 - (days * 86400.0)) / 3600.0).floor();
|
||||
let mins = ((seconds as f32 - (hour * 3600.0) - (days * 86400.0)) / 60.0).floor();
|
||||
let secs = seconds as f32 - (hour * 3600.0) - (mins * 60.0) - (days * 86400.0);
|
||||
|
||||
let days = if days > 0. {days.to_string() + "d"} else { "".into() };
|
||||
let hour = if hour > 0. {hour.to_string() + "h"} else { "".into() };
|
||||
let mins = if mins > 0. {mins.to_string() + "m"} else { "".into() };
|
||||
let secs = if secs > 0. {secs.to_string() + "s"} else { "".into() };
|
||||
|
||||
(days + " " + &hour + " " + &mins + " " + &secs)
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn pretty_time(seconds: i64, breaks: BreakStyle, granularity: TimeGranularity) -> String {
|
||||
let days = (seconds as f32 / 86400.0).floor();
|
||||
let hour = ((seconds as f32 - (days * 86400.0)) / 3600.0).floor();
|
||||
let mins = ((seconds as f32 - (hour * 3600.0) - (days * 86400.0)) / 60.0).floor();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "confetti_cli"
|
||||
name = "imu"
|
||||
version = "0.1.1"
|
||||
description = "A simple command line interface to interact with a Confetti-Box instance."
|
||||
repository = "https://github.com/Dangoware/confetti-box"
|
||||
|
@ -9,10 +9,6 @@ authors.workspace = true
|
|||
license = "AGPL-3.0-or-later"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "imu"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
|
@ -22,7 +18,7 @@ base64 = "0.22.1"
|
|||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.5", features = ["derive", "unicode"] }
|
||||
directories = "6.0"
|
||||
futures-util = "0.3.31"
|
||||
futures-util = "0.3"
|
||||
indicatif = { version = "0.17", features = ["improved_unicode"] }
|
||||
owo-colors = { version = "4.1", features = ["supports-colors"] }
|
||||
reqwest = { version = "0.12", features = ["json", "stream"] }
|
||||
|
@ -30,8 +26,10 @@ serde = { version = "1.0", features = ["derive"] }
|
|||
serde_json = "1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.41", features = ["fs", "macros", "rt-multi-thread"] }
|
||||
tokio-tungstenite = { version = "0.26.2", features = ["native-tls"] }
|
||||
tokio-tungstenite = { version = "0.26", features = ["native-tls"] }
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
toml = "0.8"
|
||||
url = { version = "2.5.4", features = ["serde"] }
|
||||
url = { version = "2.5", features = ["serde"] }
|
||||
uuid = { version = "1.11", features = ["serde", "v4"] }
|
||||
|
||||
confetti_box = { path = "../confetti-box" }
|
|
@ -1,18 +1,18 @@
|
|||
use std::{error::Error, fs, io::{self, Read, Write}, path::{Path, PathBuf}};
|
||||
use std::{error::Error, fs, io::{Read, Write}, path::{Path, PathBuf}};
|
||||
|
||||
use base64::{prelude::BASE64_URL_SAFE, Engine};
|
||||
use chrono::{DateTime, Datelike, Local, Month, TimeDelta, Timelike, Utc};
|
||||
|
||||
use confetti_box::{database::MochiFile, endpoints::ServerInfo, strings::{pretty_time, pretty_time_short, BreakStyle, TimeGranularity}};
|
||||
use futures_util::{stream::FusedStream as _, SinkExt as _, StreamExt as _};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use owo_colors::OwoColorize;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}, join, task::JoinSet};
|
||||
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}, join};
|
||||
use tokio_tungstenite::{connect_async, tungstenite::{client::IntoClientRequest as _, Message}};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
use clap::{arg, builder::{styling::RgbColor, Styles}, Parser, Subcommand};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
|
||||
|
@ -101,13 +101,13 @@ async fn main() -> Result<()> {
|
|||
Err(e) => return Err(anyhow!("Invalid duration: {e}")),
|
||||
};
|
||||
|
||||
if !config.info.as_ref().unwrap().allowed_durations.contains(&duration.num_seconds()) {
|
||||
if !config.info.as_ref().unwrap().allowed_durations.contains(&(duration.num_seconds() as u32)) {
|
||||
let pretty_durations: Vec<String> = config.info.as_ref()
|
||||
.unwrap()
|
||||
.allowed_durations
|
||||
.clone()
|
||||
.iter()
|
||||
.map(|d| pretty_time_short(*d))
|
||||
.map(|d| pretty_time_short(*d as i64))
|
||||
.collect();
|
||||
|
||||
exit_error(
|
||||
|
@ -133,7 +133,7 @@ async fn main() -> Result<()> {
|
|||
&config.login
|
||||
).await.with_context(|| "Failed to upload").unwrap();
|
||||
|
||||
let datetime: DateTime<Local> = DateTime::from(response.expiry_datetime);
|
||||
let datetime: DateTime<Local> = DateTime::from(response.expiry());
|
||||
let date = format!(
|
||||
"{} {}",
|
||||
Month::try_from(u8::try_from(datetime.month()).unwrap()).unwrap().name(),
|
||||
|
@ -142,8 +142,11 @@ async fn main() -> Result<()> {
|
|||
let time = format!("{:02}:{:02}", datetime.hour(), datetime.minute());
|
||||
println!(
|
||||
"{:>8} {}, {} (in {})\n{:>8} {}",
|
||||
"Expires:".truecolor(174,196,223).bold(), date, time, pretty_time_long(duration.num_seconds()),
|
||||
"URL:".truecolor(174,196,223).bold(), (url.to_string() + "/f/" + &response.mmid.0).underline()
|
||||
"Expires:".truecolor(174,196,223).bold(),
|
||||
date,
|
||||
time,
|
||||
pretty_time(duration.num_seconds(), BreakStyle::Space, TimeGranularity::Seconds),
|
||||
"URL:".truecolor(174,196,223).bold(), (url.to_string() + "/f/" + &response.mmid().to_string()).underline()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +224,7 @@ async fn main() -> Result<()> {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let out_directory = out_directory.join(info.name);
|
||||
let out_directory = out_directory.join(info.name());
|
||||
let mut out_file: File = tokio::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
|
@ -527,82 +530,6 @@ async fn get_info(config: &Config) -> Result<ServerInfo> {
|
|||
Ok(info)
|
||||
}
|
||||
|
||||
/// Attempts to fill a buffer completely from a stream, but if it cannot do so,
|
||||
/// it will only fill what it can read. If it has reached the end of a file, 0
|
||||
/// bytes will be read into the buffer.
|
||||
async fn fill_buffer<S: AsyncReadExt + Unpin>(buffer: &mut [u8], mut stream: S) -> Result<usize, io::Error> {
|
||||
let mut bytes_read = 0;
|
||||
while bytes_read < buffer.len() {
|
||||
let len = stream.read(&mut buffer[bytes_read..]).await?;
|
||||
|
||||
if len == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_read += len;
|
||||
}
|
||||
Ok(bytes_read)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Upload {
|
||||
file: File,
|
||||
name: String,
|
||||
duration: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct ServerInfo {
|
||||
max_filesize: u64,
|
||||
max_duration: i64,
|
||||
default_duration: i64,
|
||||
allowed_durations: Vec<i64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct ChunkedInfo {
|
||||
pub name: String,
|
||||
pub size: u64,
|
||||
pub expire_duration: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
||||
pub struct ChunkedResponse {
|
||||
status: bool,
|
||||
message: String,
|
||||
|
||||
/// UUID used for associating the chunk with the final file
|
||||
uuid: Option<Uuid>,
|
||||
|
||||
/// Valid max chunk size in bytes
|
||||
chunk_size: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct MochiFile {
|
||||
/// A unique identifier describing this file
|
||||
mmid: Mmid,
|
||||
|
||||
/// The original name of the file
|
||||
name: String,
|
||||
|
||||
/// The MIME type of the file
|
||||
mime_type: String,
|
||||
|
||||
/// The Blake3 hash of the file
|
||||
hash: String,
|
||||
|
||||
/// The datetime when the file was uploaded
|
||||
upload_datetime: DateTime<Utc>,
|
||||
|
||||
/// The datetime when the file is set to expire
|
||||
expiry_datetime: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Mmid(String);
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
struct Login {
|
||||
user: String,
|
||||
|
@ -706,98 +633,6 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_time_string(string: &str) -> Result<TimeDelta, Box<dyn Error>> {
|
||||
if string.len() > 7 {
|
||||
return Err("Not valid time string".into());
|
||||
}
|
||||
|
||||
let unit = string.chars().last();
|
||||
let multiplier = if let Some(u) = unit {
|
||||
if !u.is_ascii_alphabetic() {
|
||||
return Err("Not valid time string".into());
|
||||
}
|
||||
|
||||
match u {
|
||||
'D' | 'd' => TimeDelta::days(1),
|
||||
'H' | 'h' => TimeDelta::hours(1),
|
||||
'M' | 'm' => TimeDelta::minutes(1),
|
||||
'S' | 's' => TimeDelta::seconds(1),
|
||||
_ => return Err("Not valid time string".into()),
|
||||
}
|
||||
} else {
|
||||
return Err("Not valid time string".into());
|
||||
};
|
||||
|
||||
let time = if let Ok(n) = string[..string.len() - 1].parse::<i32>() {
|
||||
n
|
||||
} else {
|
||||
return Err("Not valid time string".into());
|
||||
};
|
||||
|
||||
let final_time = multiplier * time;
|
||||
|
||||
Ok(final_time)
|
||||
}
|
||||
|
||||
fn pretty_time_short(seconds: i64) -> String {
|
||||
let days = (seconds as f32 / 86400.0).floor();
|
||||
let hour = ((seconds as f32 - (days * 86400.0)) / 3600.0).floor();
|
||||
let mins = ((seconds as f32 - (hour * 3600.0) - (days * 86400.0)) / 60.0).floor();
|
||||
let secs = seconds as f32 - (hour * 3600.0) - (mins * 60.0) - (days * 86400.0);
|
||||
|
||||
let days = if days > 0. {days.to_string() + "d"} else { "".into() };
|
||||
let hour = if hour > 0. {hour.to_string() + "h"} else { "".into() };
|
||||
let mins = if mins > 0. {mins.to_string() + "m"} else { "".into() };
|
||||
let secs = if secs > 0. {secs.to_string() + "s"} else { "".into() };
|
||||
|
||||
(days + " " + &hour + " " + &mins + " " + &secs)
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn pretty_time_long(seconds: i64) -> String {
|
||||
let days = (seconds as f32 / 86400.0).floor();
|
||||
let hour = ((seconds as f32 - (days * 86400.0)) / 3600.0).floor();
|
||||
let mins = ((seconds as f32 - (hour * 3600.0) - (days * 86400.0)) / 60.0).floor();
|
||||
let secs = seconds as f32 - (hour * 3600.0) - (mins * 60.0) - (days * 86400.0);
|
||||
|
||||
let days = if days == 0.0 {
|
||||
"".to_string()
|
||||
} else if days == 1.0 {
|
||||
days.to_string() + " day"
|
||||
} else {
|
||||
days.to_string() + " days"
|
||||
};
|
||||
|
||||
let hour = if hour == 0.0 {
|
||||
"".to_string()
|
||||
} else if hour == 1.0 {
|
||||
hour.to_string() + " hour"
|
||||
} else {
|
||||
hour.to_string() + " hours"
|
||||
};
|
||||
|
||||
let mins = if mins == 0.0 {
|
||||
"".to_string()
|
||||
} else if mins == 1.0 {
|
||||
mins.to_string() + " minute"
|
||||
} else {
|
||||
mins.to_string() + " minutes"
|
||||
};
|
||||
|
||||
let secs = if secs == 0.0 {
|
||||
"".to_string()
|
||||
} else if secs == 1.0 {
|
||||
secs.to_string() + " second"
|
||||
} else {
|
||||
secs.to_string() + " seconds"
|
||||
};
|
||||
|
||||
(days + " " + &hour + " " + &mins + " " + &secs)
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn exit_error(main_message: String, fix: Option<String>, fix_values: Option<Vec<String>>) -> ! {
|
||||
print_error_line(main_message);
|
||||
|
Loading…
Reference in a new issue