tyrolienne/src/zipline.rs

205 lines
4.9 KiB
Rust

use std::{path::Path, sync::LazyLock};
use anyhow::{Result, bail};
use futures::StreamExt;
use relm4::Sender;
use reqwest::{
Body, Client, StatusCode,
header::AUTHORIZATION,
multipart::{Form, Part},
};
use tokio::fs::File;
use tokio_util::io::ReaderStream;
use crate::{Config, ProgressMessage};
static CLIENT: LazyLock<Client> = LazyLock::new(Client::new);
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ZiplineFolder {
pub id: String,
pub name: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct ZiplineUploadResponse {
pub files: Vec<ZiplineFile>,
}
#[derive(Debug, serde::Deserialize)]
pub struct ZiplineFile {
pub id: String,
pub url: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct ZiplineFileInfo {
pub thumbnail: Option<ZiplineThumbnail>,
}
#[derive(Debug, serde::Deserialize)]
pub struct ZiplineThumbnail {
pub path: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct ZiplineShortUrl {
url: String,
}
impl ZiplineFileInfo {
pub fn thumbnail_url(&self, config: &Config) -> Option<String> {
self.thumbnail
.as_ref()
.map(|t| format!("{}raw/{}", config.fixed_url(), t.path))
}
}
async fn wrap_file(path: &Path, sender: Sender<ProgressMessage>) -> Result<Part> {
let file_name = path
.file_name()
.map(|filename| filename.to_string_lossy().into_owned());
let file = File::open(path).await?;
let len = file.metadata().await?.len();
sender.emit(ProgressMessage::SetTotal(len as usize));
let stream = ReaderStream::new(file).map(move |b| {
if let Ok(ref bytes) = b {
sender.emit(ProgressMessage::IncProgress(bytes.len()));
}
b
});
let field = Part::stream_with_length(Body::wrap_stream(stream), len).mime_str("video/webm")?;
Ok(if let Some(file_name) = file_name {
field.file_name(file_name)
} else {
field
})
}
pub async fn get_folders(config: &Config) -> Result<Vec<ZiplineFolder>> {
let url = format!("{}api/user/folders?noincl=true", config.fixed_url());
let res = CLIENT
.get(url)
.header(AUTHORIZATION, &config.zipline_token)
.send()
.await?;
if res.status() != StatusCode::OK {
bail!(
"an error occurred ({}): {}",
res.status(),
res.text().await?
);
} else {
res.json().await.map_err(Into::into)
}
}
pub async fn upload_file(
config: &Config,
sender: &Sender<ProgressMessage>,
folder: Option<&ZiplineFolder>,
file_path: &Path,
) -> Result<ZiplineUploadResponse> {
let url = format!("{}api/upload", config.fixed_url());
let wrapped_file = wrap_file(file_path, sender.clone()).await?;
let form = Form::new().part("file", wrapped_file);
let mut req = CLIENT
.post(url)
.header(AUTHORIZATION, &config.zipline_token)
.header("x-zipline-format", "name")
.multipart(form);
if let Some(folder) = folder {
req = req.header("x-zipline-folder", &folder.id);
}
let res = req.send().await?;
if res.status() != StatusCode::OK {
bail!(
"an error occurred ({}): {}",
res.status(),
res.text().await?
);
} else {
res.json().await.map_err(Into::into)
}
}
pub async fn recalc_thumbnails(config: &Config) -> Result<()> {
let url = format!("{}api/server/thumbnails", config.fixed_url());
let res = CLIENT
.post(url)
.header(AUTHORIZATION, &config.zipline_token)
.json(&[("rerun", false)])
.send()
.await?;
if res.status() != StatusCode::OK {
bail!(
"an error occurred ({}): {}",
res.status(),
res.text().await?
);
} else {
Ok(())
}
}
pub async fn get_file_details(config: &Config, id: &str) -> Result<ZiplineFileInfo> {
let url = format!("{}api/user/files/{id}", config.fixed_url());
let res = CLIENT
.get(url)
.header(AUTHORIZATION, &config.zipline_token)
.send()
.await?;
if res.status() != StatusCode::OK {
bail!(
"an error occurred ({}): {}",
res.status(),
res.text().await?
);
} else {
res.json().await.map_err(Into::into)
}
}
pub async fn shorten_url(config: &Config, to_shorten: &str) -> Result<String> {
let url = format!("{}api/user/urls", config.fixed_url());
let body = serde_json::json!({
"destination": to_shorten,
"enabled": true,
"vanity": null
});
let res = CLIENT
.post(url)
.json(&body)
.header(AUTHORIZATION, &config.zipline_token)
.send()
.await?;
if res.status() != StatusCode::OK {
bail!(
"an error occurred ({}): {}",
res.status(),
res.text().await?
);
} else {
let out: ZiplineShortUrl = res.json().await?;
Ok(out.url)
}
}