feat: better error handling
errors during the upload are now shown in a separate user-friendly alert dialog, and you can also choose to upload the file to no folder
This commit is contained in:
parent
601ee85b5f
commit
94562b995a
4 changed files with 174 additions and 36 deletions
|
@ -15,10 +15,19 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl GtkZiplineFolder {
|
||||
pub const NONE_ID: &str = "tyroliennesupersecretnoneid";
|
||||
|
||||
pub fn r#type() -> Type {
|
||||
GtkZiplineFolderImpl::type_()
|
||||
}
|
||||
|
||||
pub fn none() -> Self {
|
||||
glib::Object::builder()
|
||||
.property("id", String::from(Self::NONE_ID))
|
||||
.property("name", String::from("None"))
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn from_folder(folder: &ZiplineFolder) -> Self {
|
||||
glib::Object::builder()
|
||||
.property("id", folder.id.clone())
|
||||
|
|
53
src/main.rs
53
src/main.rs
|
@ -1,12 +1,14 @@
|
|||
mod gobject;
|
||||
mod relm;
|
||||
mod zipline;
|
||||
|
||||
use std::{borrow::Cow, path::PathBuf, time::Duration};
|
||||
|
||||
use color_eyre::eyre::{OptionExt, Result, bail};
|
||||
use gobject::GtkZiplineFolder;
|
||||
use relm::{Dialog, DialogInput};
|
||||
use relm4::{
|
||||
ComponentParts, RelmApp, SimpleComponent,
|
||||
Component, ComponentController, ComponentParts, Controller, RelmApp, SimpleComponent,
|
||||
adw::{self, prelude::*},
|
||||
gtk::{self, gio, glib::clone},
|
||||
};
|
||||
|
@ -40,6 +42,7 @@ enum Message {
|
|||
SetPath(PathBuf),
|
||||
SetFolder(ZiplineFolder),
|
||||
StartTheProcess,
|
||||
Nothing,
|
||||
}
|
||||
|
||||
struct Widgets {
|
||||
|
@ -50,7 +53,8 @@ struct Widgets {
|
|||
struct Tyrolienne {
|
||||
config: Config,
|
||||
video_path: Option<PathBuf>,
|
||||
folder: ZiplineFolder,
|
||||
folder: Option<ZiplineFolder>,
|
||||
dialog: Controller<Dialog>,
|
||||
}
|
||||
|
||||
impl Tyrolienne {
|
||||
|
@ -84,7 +88,10 @@ impl SimpleComponent for Tyrolienne {
|
|||
let model = Tyrolienne {
|
||||
config,
|
||||
video_path: None,
|
||||
folder: folders[0].clone(),
|
||||
folder: None,
|
||||
dialog: Dialog::builder()
|
||||
.launch(root.clone())
|
||||
.forward(sender.input_sender(), |_| Message::Nothing),
|
||||
};
|
||||
|
||||
let file_picker_row = adw::ActionRow::builder()
|
||||
|
@ -116,10 +123,12 @@ impl SimpleComponent for Tyrolienne {
|
|||
}
|
||||
));
|
||||
|
||||
let gtk_folders = folders
|
||||
let mut gtk_folders = folders
|
||||
.iter()
|
||||
.map(GtkZiplineFolder::from_folder)
|
||||
.collect::<Vec<_>>();
|
||||
gtk_folders.insert(0, GtkZiplineFolder::none());
|
||||
|
||||
let store = gio::ListStore::new::<GtkZiplineFolder>();
|
||||
store.extend_from_slice(>k_folders);
|
||||
|
||||
|
@ -186,9 +195,18 @@ impl SimpleComponent for Tyrolienne {
|
|||
|
||||
fn update(&mut self, message: Self::Input, _sender: relm4::ComponentSender<Self>) {
|
||||
match message {
|
||||
Message::Nothing => {}
|
||||
Message::SetPath(path) => self.video_path = Some(path),
|
||||
Message::SetFolder(folder) => self.folder = folder,
|
||||
Message::StartTheProcess => the_process(self).unwrap(),
|
||||
Message::SetFolder(folder) => {
|
||||
self.folder = (folder.id != GtkZiplineFolder::NONE_ID).then_some(folder)
|
||||
}
|
||||
Message::StartTheProcess => match the_process(self) {
|
||||
Ok(url) => tracing::info!("{url}"),
|
||||
Err(e) => self.dialog.emit(DialogInput::Show {
|
||||
heading: "An error occurred".into(),
|
||||
body: e.to_string(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,17 +265,25 @@ fn get_config() -> Result<Config> {
|
|||
Ok(config)
|
||||
}
|
||||
|
||||
fn the_process(app: &Tyrolienne) -> Result<()> {
|
||||
tracing::info!("uploading to folder '{}'...", app.folder.name);
|
||||
fn the_process(app: &Tyrolienne) -> Result<String> {
|
||||
if let Some(folder) = app.folder.as_ref() {
|
||||
tracing::info!("uploading to folder '{}'...", folder.name);
|
||||
} else {
|
||||
tracing::info!("uploading video...");
|
||||
}
|
||||
|
||||
let res = zipline::upload_file(&app.config, &app.folder, app.video_path.as_ref().unwrap())?;
|
||||
let res = zipline::upload_file(
|
||||
&app.config,
|
||||
app.folder.as_ref(),
|
||||
app.video_path.as_ref().unwrap(),
|
||||
)?;
|
||||
let zp_file = &res.files[0];
|
||||
|
||||
tracing::info!("recalculating thumbnails...");
|
||||
|
||||
zipline::recalc_thumbnails(&app.config)?;
|
||||
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
std::thread::sleep(Duration::from_secs(2));
|
||||
|
||||
tracing::info!("fetching thumbnail url...");
|
||||
|
||||
|
@ -267,12 +293,9 @@ fn the_process(app: &Tyrolienne) -> Result<()> {
|
|||
.ok_or_eyre("could not get thumbnail url")?;
|
||||
|
||||
// TODO get w&h from video
|
||||
let autocomp_url = format!(
|
||||
Ok(format!(
|
||||
"https://autocompressor.net/av1?v={}&i={}&w=1920&h=1080",
|
||||
Encoded(&zp_file.url),
|
||||
Encoded(&thumbnail_url)
|
||||
);
|
||||
tracing::info!("url: {autocomp_url}");
|
||||
|
||||
Ok(())
|
||||
))
|
||||
}
|
||||
|
|
82
src/relm.rs
Normal file
82
src/relm.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
use relm4::{
|
||||
SimpleComponent,
|
||||
adw::{self, prelude::*},
|
||||
gtk::glib::clone,
|
||||
};
|
||||
|
||||
pub struct Dialog {
|
||||
window: adw::ApplicationWindow,
|
||||
visible: bool,
|
||||
heading: String,
|
||||
body: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DialogInput {
|
||||
Show { heading: String, body: String },
|
||||
Dismiss,
|
||||
}
|
||||
|
||||
pub struct DialogWidgets {
|
||||
dialog: adw::AlertDialog,
|
||||
}
|
||||
|
||||
impl SimpleComponent for Dialog {
|
||||
type Init = adw::ApplicationWindow;
|
||||
type Root = adw::AlertDialog;
|
||||
type Widgets = DialogWidgets;
|
||||
type Input = DialogInput;
|
||||
type Output = ();
|
||||
|
||||
fn init_root() -> Self::Root {
|
||||
let dialog = adw::AlertDialog::builder().close_response("ok").build();
|
||||
dialog.add_response("ok", "OK");
|
||||
dialog
|
||||
}
|
||||
|
||||
fn init(
|
||||
window: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: relm4::ComponentSender<Self>,
|
||||
) -> relm4::ComponentParts<Self> {
|
||||
let model = Self {
|
||||
window,
|
||||
visible: false,
|
||||
heading: String::new(),
|
||||
body: String::new(),
|
||||
};
|
||||
|
||||
root.connect_response(
|
||||
None,
|
||||
clone!(
|
||||
#[strong]
|
||||
sender,
|
||||
move |_, _| sender.input(DialogInput::Dismiss)
|
||||
),
|
||||
);
|
||||
|
||||
let widgets = DialogWidgets { dialog: root };
|
||||
|
||||
relm4::ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, _sender: relm4::ComponentSender<Self>) {
|
||||
match message {
|
||||
DialogInput::Show { heading, body } => {
|
||||
self.heading = heading;
|
||||
self.body = body;
|
||||
self.visible = true;
|
||||
}
|
||||
DialogInput::Dismiss => self.visible = false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_view(&self, widgets: &mut Self::Widgets, _sender: relm4::ComponentSender<Self>) {
|
||||
widgets.dialog.set_heading(Some(&self.heading));
|
||||
widgets.dialog.set_body(&self.body);
|
||||
|
||||
if self.visible {
|
||||
widgets.dialog.present(Some(&self.window));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
use std::{path::Path, sync::LazyLock};
|
||||
|
||||
use color_eyre::eyre::{Result, bail};
|
||||
use reqwest::{
|
||||
StatusCode,
|
||||
blocking::{Client, multipart::Form},
|
||||
header::AUTHORIZATION,
|
||||
};
|
||||
|
@ -44,54 +46,76 @@ impl ZiplineFileInfo {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_folders(config: &Config) -> Result<Vec<ZiplineFolder>, reqwest::Error> {
|
||||
pub fn get_folders(config: &Config) -> Result<Vec<ZiplineFolder>> {
|
||||
let url = format!("{}api/user/folders?noincl=true", config.fixed_url());
|
||||
|
||||
CLIENT
|
||||
let res = CLIENT
|
||||
.get(url)
|
||||
.header(AUTHORIZATION, &config.zipline_token)
|
||||
.send()?
|
||||
.json()
|
||||
.send()?;
|
||||
|
||||
if res.status() != StatusCode::OK {
|
||||
bail!("an error occurred ({}): {}", res.status(), res.text()?);
|
||||
} else {
|
||||
res.json().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_file(
|
||||
config: &Config,
|
||||
folder: &ZiplineFolder,
|
||||
folder: Option<&ZiplineFolder>,
|
||||
file_path: &Path,
|
||||
) -> Result<ZiplineUploadResponse, reqwest::Error> {
|
||||
) -> Result<ZiplineUploadResponse> {
|
||||
let url = format!("{}api/upload", config.fixed_url());
|
||||
|
||||
let form = Form::new().file("file", file_path).unwrap(); // FIXME
|
||||
let form = Form::new().file("file", file_path)?;
|
||||
|
||||
CLIENT
|
||||
let mut req = CLIENT
|
||||
.post(url)
|
||||
.header(AUTHORIZATION, &config.zipline_token)
|
||||
.header("x-zipline-folder", &folder.id)
|
||||
.header("x-zipline-format", "name")
|
||||
.multipart(form)
|
||||
.send()?
|
||||
.json()
|
||||
.multipart(form);
|
||||
|
||||
if let Some(folder) = folder {
|
||||
req = req.header("x-zipline-folder", &folder.id);
|
||||
}
|
||||
|
||||
let res = req.send()?;
|
||||
|
||||
if res.status() != StatusCode::OK {
|
||||
bail!("an error occurred ({}): {}", res.status(), res.text()?);
|
||||
} else {
|
||||
res.json().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recalc_thumbnails(config: &Config) -> Result<(), reqwest::Error> {
|
||||
pub fn recalc_thumbnails(config: &Config) -> Result<()> {
|
||||
let url = format!("{}api/server/thumbnails", config.fixed_url());
|
||||
|
||||
CLIENT
|
||||
let res = CLIENT
|
||||
.post(url)
|
||||
.header(AUTHORIZATION, &config.zipline_token)
|
||||
.json(&[("rerun", false)])
|
||||
.send()?
|
||||
.error_for_status()?;
|
||||
.send()?;
|
||||
|
||||
Ok(())
|
||||
if res.status() != StatusCode::OK {
|
||||
bail!("an error occurred ({}): {}", res.status(), res.text()?);
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_file_details(config: &Config, id: &str) -> Result<ZiplineFileInfo, reqwest::Error> {
|
||||
pub fn get_file_details(config: &Config, id: &str) -> Result<ZiplineFileInfo> {
|
||||
let url = format!("{}api/user/files/{id}", config.fixed_url());
|
||||
|
||||
CLIENT
|
||||
let res = CLIENT
|
||||
.get(url)
|
||||
.header(AUTHORIZATION, &config.zipline_token)
|
||||
.send()?
|
||||
.json()
|
||||
.send()?;
|
||||
|
||||
if res.status() != StatusCode::OK {
|
||||
bail!("an error occurred ({}): {}", res.status(), res.text()?);
|
||||
} else {
|
||||
res.json().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue