205 lines
4.9 KiB
Rust
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)
|
|
}
|
|
}
|