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 {
|
impl GtkZiplineFolder {
|
||||||
|
pub const NONE_ID: &str = "tyroliennesupersecretnoneid";
|
||||||
|
|
||||||
pub fn r#type() -> Type {
|
pub fn r#type() -> Type {
|
||||||
GtkZiplineFolderImpl::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 {
|
pub fn from_folder(folder: &ZiplineFolder) -> Self {
|
||||||
glib::Object::builder()
|
glib::Object::builder()
|
||||||
.property("id", folder.id.clone())
|
.property("id", folder.id.clone())
|
||||||
|
|
53
src/main.rs
53
src/main.rs
|
@ -1,12 +1,14 @@
|
||||||
mod gobject;
|
mod gobject;
|
||||||
|
mod relm;
|
||||||
mod zipline;
|
mod zipline;
|
||||||
|
|
||||||
use std::{borrow::Cow, path::PathBuf, time::Duration};
|
use std::{borrow::Cow, path::PathBuf, time::Duration};
|
||||||
|
|
||||||
use color_eyre::eyre::{OptionExt, Result, bail};
|
use color_eyre::eyre::{OptionExt, Result, bail};
|
||||||
use gobject::GtkZiplineFolder;
|
use gobject::GtkZiplineFolder;
|
||||||
|
use relm::{Dialog, DialogInput};
|
||||||
use relm4::{
|
use relm4::{
|
||||||
ComponentParts, RelmApp, SimpleComponent,
|
Component, ComponentController, ComponentParts, Controller, RelmApp, SimpleComponent,
|
||||||
adw::{self, prelude::*},
|
adw::{self, prelude::*},
|
||||||
gtk::{self, gio, glib::clone},
|
gtk::{self, gio, glib::clone},
|
||||||
};
|
};
|
||||||
|
@ -40,6 +42,7 @@ enum Message {
|
||||||
SetPath(PathBuf),
|
SetPath(PathBuf),
|
||||||
SetFolder(ZiplineFolder),
|
SetFolder(ZiplineFolder),
|
||||||
StartTheProcess,
|
StartTheProcess,
|
||||||
|
Nothing,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Widgets {
|
struct Widgets {
|
||||||
|
@ -50,7 +53,8 @@ struct Widgets {
|
||||||
struct Tyrolienne {
|
struct Tyrolienne {
|
||||||
config: Config,
|
config: Config,
|
||||||
video_path: Option<PathBuf>,
|
video_path: Option<PathBuf>,
|
||||||
folder: ZiplineFolder,
|
folder: Option<ZiplineFolder>,
|
||||||
|
dialog: Controller<Dialog>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tyrolienne {
|
impl Tyrolienne {
|
||||||
|
@ -84,7 +88,10 @@ impl SimpleComponent for Tyrolienne {
|
||||||
let model = Tyrolienne {
|
let model = Tyrolienne {
|
||||||
config,
|
config,
|
||||||
video_path: None,
|
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()
|
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()
|
.iter()
|
||||||
.map(GtkZiplineFolder::from_folder)
|
.map(GtkZiplineFolder::from_folder)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
gtk_folders.insert(0, GtkZiplineFolder::none());
|
||||||
|
|
||||||
let store = gio::ListStore::new::<GtkZiplineFolder>();
|
let store = gio::ListStore::new::<GtkZiplineFolder>();
|
||||||
store.extend_from_slice(>k_folders);
|
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>) {
|
fn update(&mut self, message: Self::Input, _sender: relm4::ComponentSender<Self>) {
|
||||||
match message {
|
match message {
|
||||||
|
Message::Nothing => {}
|
||||||
Message::SetPath(path) => self.video_path = Some(path),
|
Message::SetPath(path) => self.video_path = Some(path),
|
||||||
Message::SetFolder(folder) => self.folder = folder,
|
Message::SetFolder(folder) => {
|
||||||
Message::StartTheProcess => the_process(self).unwrap(),
|
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)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn the_process(app: &Tyrolienne) -> Result<()> {
|
fn the_process(app: &Tyrolienne) -> Result<String> {
|
||||||
tracing::info!("uploading to folder '{}'...", app.folder.name);
|
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];
|
let zp_file = &res.files[0];
|
||||||
|
|
||||||
tracing::info!("recalculating thumbnails...");
|
tracing::info!("recalculating thumbnails...");
|
||||||
|
|
||||||
zipline::recalc_thumbnails(&app.config)?;
|
zipline::recalc_thumbnails(&app.config)?;
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_secs(5));
|
std::thread::sleep(Duration::from_secs(2));
|
||||||
|
|
||||||
tracing::info!("fetching thumbnail url...");
|
tracing::info!("fetching thumbnail url...");
|
||||||
|
|
||||||
|
@ -267,12 +293,9 @@ fn the_process(app: &Tyrolienne) -> Result<()> {
|
||||||
.ok_or_eyre("could not get thumbnail url")?;
|
.ok_or_eyre("could not get thumbnail url")?;
|
||||||
|
|
||||||
// TODO get w&h from video
|
// TODO get w&h from video
|
||||||
let autocomp_url = format!(
|
Ok(format!(
|
||||||
"https://autocompressor.net/av1?v={}&i={}&w=1920&h=1080",
|
"https://autocompressor.net/av1?v={}&i={}&w=1920&h=1080",
|
||||||
Encoded(&zp_file.url),
|
Encoded(&zp_file.url),
|
||||||
Encoded(&thumbnail_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 std::{path::Path, sync::LazyLock};
|
||||||
|
|
||||||
|
use color_eyre::eyre::{Result, bail};
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
|
StatusCode,
|
||||||
blocking::{Client, multipart::Form},
|
blocking::{Client, multipart::Form},
|
||||||
header::AUTHORIZATION,
|
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());
|
let url = format!("{}api/user/folders?noincl=true", config.fixed_url());
|
||||||
|
|
||||||
CLIENT
|
let res = CLIENT
|
||||||
.get(url)
|
.get(url)
|
||||||
.header(AUTHORIZATION, &config.zipline_token)
|
.header(AUTHORIZATION, &config.zipline_token)
|
||||||
.send()?
|
.send()?;
|
||||||
.json()
|
|
||||||
|
if res.status() != StatusCode::OK {
|
||||||
|
bail!("an error occurred ({}): {}", res.status(), res.text()?);
|
||||||
|
} else {
|
||||||
|
res.json().map_err(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upload_file(
|
pub fn upload_file(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
folder: &ZiplineFolder,
|
folder: Option<&ZiplineFolder>,
|
||||||
file_path: &Path,
|
file_path: &Path,
|
||||||
) -> Result<ZiplineUploadResponse, reqwest::Error> {
|
) -> Result<ZiplineUploadResponse> {
|
||||||
let url = format!("{}api/upload", config.fixed_url());
|
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)
|
.post(url)
|
||||||
.header(AUTHORIZATION, &config.zipline_token)
|
.header(AUTHORIZATION, &config.zipline_token)
|
||||||
.header("x-zipline-folder", &folder.id)
|
|
||||||
.header("x-zipline-format", "name")
|
.header("x-zipline-format", "name")
|
||||||
.multipart(form)
|
.multipart(form);
|
||||||
.send()?
|
|
||||||
.json()
|
if let Some(folder) = folder {
|
||||||
|
req = req.header("x-zipline-folder", &folder.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recalc_thumbnails(config: &Config) -> Result<(), reqwest::Error> {
|
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<()> {
|
||||||
let url = format!("{}api/server/thumbnails", config.fixed_url());
|
let url = format!("{}api/server/thumbnails", config.fixed_url());
|
||||||
|
|
||||||
CLIENT
|
let res = CLIENT
|
||||||
.post(url)
|
.post(url)
|
||||||
.header(AUTHORIZATION, &config.zipline_token)
|
.header(AUTHORIZATION, &config.zipline_token)
|
||||||
.json(&[("rerun", false)])
|
.json(&[("rerun", false)])
|
||||||
.send()?
|
.send()?;
|
||||||
.error_for_status()?;
|
|
||||||
|
|
||||||
|
if res.status() != StatusCode::OK {
|
||||||
|
bail!("an error occurred ({}): {}", res.status(), res.text()?);
|
||||||
|
} else {
|
||||||
Ok(())
|
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());
|
let url = format!("{}api/user/files/{id}", config.fixed_url());
|
||||||
|
|
||||||
CLIENT
|
let res = CLIENT
|
||||||
.get(url)
|
.get(url)
|
||||||
.header(AUTHORIZATION, &config.zipline_token)
|
.header(AUTHORIZATION, &config.zipline_token)
|
||||||
.send()?
|
.send()?;
|
||||||
.json()
|
|
||||||
|
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