fix: make request sending asynchronous too
This commit is contained in:
parent
2f96459f58
commit
fb227660bb
4 changed files with 100 additions and 70 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -1654,7 +1654,6 @@ dependencies = [
|
|||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
|
@ -1682,11 +1681,13 @@ dependencies = [
|
|||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 0.26.11",
|
||||
"windows-registry",
|
||||
|
@ -2451,6 +2452,19 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.77"
|
||||
|
|
|
@ -7,7 +7,7 @@ edition = "2024"
|
|||
color-eyre = "0.6.4"
|
||||
dirs = "6.0.0"
|
||||
relm4 = { version = "0.9.1", features = ["gnome_47", "libadwaita"] }
|
||||
reqwest = { version = "0.12.15", default-features = false, features = ["http2", "charset", "rustls-tls", "blocking", "json", "multipart"] }
|
||||
reqwest = { version = "0.12.15", default-features = false, features = ["http2", "charset", "rustls-tls", "json", "multipart", "stream"] }
|
||||
rfd = { version = "0.15.3", default-features = false, features = ["tokio", "xdg-portal"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
toml = "0.8.22"
|
||||
|
|
93
src/main.rs
93
src/main.rs
|
@ -8,16 +8,18 @@ use color_eyre::eyre::{OptionExt, Result, bail};
|
|||
use gobject::GtkZiplineFolder;
|
||||
use relm::{Dialog, DialogInput};
|
||||
use relm4::{
|
||||
Component, ComponentController, ComponentParts, Controller, RelmApp,
|
||||
Component, ComponentController, Controller, RelmApp,
|
||||
adw::{self, prelude::*},
|
||||
gtk::{self, gio, glib::clone},
|
||||
prelude::{AsyncComponent, AsyncComponentParts},
|
||||
tokio,
|
||||
};
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use urlencoding::Encoded;
|
||||
use zipline::ZiplineFolder;
|
||||
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||
struct Config {
|
||||
zipline_url: String,
|
||||
zipline_token: String,
|
||||
|
@ -45,11 +47,6 @@ enum Message {
|
|||
Nothing,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CommandMessage {
|
||||
SelectedFile(Option<PathBuf>),
|
||||
}
|
||||
|
||||
struct Widgets {
|
||||
file_picker_row: adw::ActionRow,
|
||||
send_button: gtk::Button,
|
||||
|
@ -71,11 +68,11 @@ impl Tyrolienne {
|
|||
}
|
||||
}
|
||||
|
||||
impl Component for Tyrolienne {
|
||||
impl AsyncComponent for Tyrolienne {
|
||||
type Input = Message;
|
||||
type Output = ();
|
||||
type CommandOutput = CommandMessage;
|
||||
type Init = (Config, Vec<ZiplineFolder>);
|
||||
type CommandOutput = ();
|
||||
type Init = Config;
|
||||
type Root = adw::ApplicationWindow;
|
||||
type Widgets = Widgets;
|
||||
|
||||
|
@ -86,11 +83,11 @@ impl Component for Tyrolienne {
|
|||
.build()
|
||||
}
|
||||
|
||||
fn init(
|
||||
(config, folders): Self::Init,
|
||||
async fn init(
|
||||
config: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: relm4::ComponentSender<Self>,
|
||||
) -> relm4::ComponentParts<Self> {
|
||||
sender: relm4::AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let model = Tyrolienne {
|
||||
config,
|
||||
video_path: None,
|
||||
|
@ -100,6 +97,19 @@ impl Component for Tyrolienne {
|
|||
.forward(sender.input_sender(), |_| Message::Nothing),
|
||||
};
|
||||
|
||||
// TODO consider using the "loading" (?) mechanism from relm4
|
||||
// https://relm4.org/book/stable/threads_and_async/async.html
|
||||
let folders = match zipline::get_folders(&model.config).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
model.dialog.emit(DialogInput::Show {
|
||||
heading: "Could not fetch folders".into(),
|
||||
body: e.to_string(),
|
||||
});
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
let file_picker_row = adw::ActionRow::builder()
|
||||
.activatable(true)
|
||||
.title("Video file")
|
||||
|
@ -179,13 +189,13 @@ impl Component for Tyrolienne {
|
|||
send_button,
|
||||
};
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
AsyncComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(
|
||||
async fn update(
|
||||
&mut self,
|
||||
message: Self::Input,
|
||||
sender: relm4::ComponentSender<Self>,
|
||||
_sender: relm4::AsyncComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
match message {
|
||||
|
@ -193,38 +203,27 @@ impl Component for Tyrolienne {
|
|||
Message::SetFolder(folder) => {
|
||||
self.folder = (folder.id != GtkZiplineFolder::NONE_ID).then_some(folder)
|
||||
}
|
||||
Message::StartTheProcess => match the_process(self) {
|
||||
Message::StartTheProcess => match the_process(self).await {
|
||||
Ok(url) => tracing::info!("{url}"),
|
||||
Err(e) => self.dialog.emit(DialogInput::Show {
|
||||
heading: "An error occurred".into(),
|
||||
body: e.to_string(),
|
||||
}),
|
||||
},
|
||||
Message::OpenFilePicker => sender.oneshot_command(async {
|
||||
CommandMessage::SelectedFile(
|
||||
rfd::AsyncFileDialog::new()
|
||||
.add_filter("Video file", &["mp4", "mkv", "webm"])
|
||||
.pick_file()
|
||||
.await
|
||||
.map(|h| h.path().to_owned()),
|
||||
)
|
||||
}),
|
||||
Message::OpenFilePicker => {
|
||||
let file = rfd::AsyncFileDialog::new()
|
||||
.add_filter("Video file", &["mp4", "mkv", "webm"])
|
||||
.pick_file()
|
||||
.await;
|
||||
|
||||
if let Some(file) = file {
|
||||
self.video_path = Some(file.path().to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_cmd(
|
||||
&mut self,
|
||||
message: Self::CommandOutput,
|
||||
_sender: relm4::ComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
match message {
|
||||
CommandMessage::SelectedFile(Some(path)) => self.video_path = Some(path),
|
||||
CommandMessage::SelectedFile(None) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_view(&self, widgets: &mut Self::Widgets, _sender: relm4::ComponentSender<Self>) {
|
||||
fn update_view(&self, widgets: &mut Self::Widgets, _sender: relm4::AsyncComponentSender<Self>) {
|
||||
widgets
|
||||
.file_picker_row
|
||||
.set_subtitle(&self.display_video_path());
|
||||
|
@ -242,10 +241,9 @@ fn main() -> Result<()> {
|
|||
|
||||
// TODO: show dialog in case these error
|
||||
let config = get_config()?;
|
||||
let folders = zipline::get_folders(&config)?;
|
||||
|
||||
let app = RelmApp::new("net.uku3lig.Tyrolienne");
|
||||
app.run::<Tyrolienne>((config, folders));
|
||||
app.run_async::<Tyrolienne>(config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -279,7 +277,7 @@ fn get_config() -> Result<Config> {
|
|||
Ok(config)
|
||||
}
|
||||
|
||||
fn the_process(app: &Tyrolienne) -> Result<String> {
|
||||
async fn the_process(app: &Tyrolienne) -> Result<String> {
|
||||
if let Some(folder) = app.folder.as_ref() {
|
||||
tracing::info!("uploading to folder '{}'...", folder.name);
|
||||
} else {
|
||||
|
@ -290,18 +288,19 @@ fn the_process(app: &Tyrolienne) -> Result<String> {
|
|||
&app.config,
|
||||
app.folder.as_ref(),
|
||||
app.video_path.as_ref().unwrap(),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
let zp_file = &res.files[0];
|
||||
|
||||
tracing::info!("recalculating thumbnails...");
|
||||
|
||||
zipline::recalc_thumbnails(&app.config)?;
|
||||
zipline::recalc_thumbnails(&app.config).await?;
|
||||
|
||||
std::thread::sleep(Duration::from_secs(2));
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
|
||||
tracing::info!("fetching thumbnail url...");
|
||||
|
||||
let res = zipline::get_file_details(&app.config, &zp_file.id)?;
|
||||
let res = zipline::get_file_details(&app.config, &zp_file.id).await?;
|
||||
let thumbnail_url = res
|
||||
.thumbnail_url(&app.config)
|
||||
.ok_or_eyre("could not get thumbnail url")?;
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
use std::{path::Path, sync::LazyLock};
|
||||
|
||||
use color_eyre::eyre::{Result, bail};
|
||||
use reqwest::{
|
||||
StatusCode,
|
||||
blocking::{Client, multipart::Form},
|
||||
header::AUTHORIZATION,
|
||||
};
|
||||
use reqwest::{Client, StatusCode, header::AUTHORIZATION, multipart::Form};
|
||||
|
||||
use crate::Config;
|
||||
|
||||
|
@ -46,29 +42,36 @@ impl ZiplineFileInfo {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_folders(config: &Config) -> Result<Vec<ZiplineFolder>> {
|
||||
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()?;
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if res.status() != StatusCode::OK {
|
||||
bail!("an error occurred ({}): {}", res.status(), res.text()?);
|
||||
bail!(
|
||||
"an error occurred ({}): {}",
|
||||
res.status(),
|
||||
res.text().await?
|
||||
);
|
||||
} else {
|
||||
res.json().map_err(Into::into)
|
||||
res.json().await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_file(
|
||||
pub async fn upload_file(
|
||||
config: &Config,
|
||||
folder: Option<&ZiplineFolder>,
|
||||
file_path: &Path,
|
||||
) -> Result<ZiplineUploadResponse> {
|
||||
let url = format!("{}api/upload", config.fixed_url());
|
||||
|
||||
let form = Form::new().file("file", file_path)?;
|
||||
// TODO use Part::stream to provide a wrapped file with a custom stream impl to send progress
|
||||
// (i hope it works)
|
||||
let form = Form::new().file("file", file_path).await?;
|
||||
|
||||
let mut req = CLIENT
|
||||
.post(url)
|
||||
|
@ -80,42 +83,56 @@ pub fn upload_file(
|
|||
req = req.header("x-zipline-folder", &folder.id);
|
||||
}
|
||||
|
||||
let res = req.send()?;
|
||||
let res = req.send().await?;
|
||||
|
||||
if res.status() != StatusCode::OK {
|
||||
bail!("an error occurred ({}): {}", res.status(), res.text()?);
|
||||
bail!(
|
||||
"an error occurred ({}): {}",
|
||||
res.status(),
|
||||
res.text().await?
|
||||
);
|
||||
} else {
|
||||
res.json().map_err(Into::into)
|
||||
res.json().await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recalc_thumbnails(config: &Config) -> Result<()> {
|
||||
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()?;
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if res.status() != StatusCode::OK {
|
||||
bail!("an error occurred ({}): {}", res.status(), res.text()?);
|
||||
bail!(
|
||||
"an error occurred ({}): {}",
|
||||
res.status(),
|
||||
res.text().await?
|
||||
);
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_file_details(config: &Config, id: &str) -> Result<ZiplineFileInfo> {
|
||||
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()?;
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if res.status() != StatusCode::OK {
|
||||
bail!("an error occurred ({}): {}", res.status(), res.text()?);
|
||||
bail!(
|
||||
"an error occurred ({}): {}",
|
||||
res.status(),
|
||||
res.text().await?
|
||||
);
|
||||
} else {
|
||||
res.json().map_err(Into::into)
|
||||
res.json().await.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue