From 054afd270a972433a8ef58787ef5d8670f69744d Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 11 Dec 2024 01:03:46 +0200 Subject: [PATCH] implement public api actions for download feature --- src/app/browser/window/tab/item/page.rs | 54 ++++++------------- .../browser/window/tab/item/page/content.rs | 4 +- .../window/tab/item/page/content/status.rs | 12 ++--- .../tab/item/page/content/status/download.rs | 49 +++++++++++++++-- .../page/content/status/download/action.rs | 29 ++++++++++ .../content/status/download/action/cancel.rs | 45 ++++++++++++++++ .../status/download/action/complete.rs | 45 ++++++++++++++++ .../content/status/download/action/update.rs | 45 ++++++++++++++++ 8 files changed, 230 insertions(+), 53 deletions(-) create mode 100644 src/app/browser/window/tab/item/page/content/status/download/action.rs create mode 100644 src/app/browser/window/tab/item/page/content/status/download/action/cancel.rs create mode 100644 src/app/browser/window/tab/item/page/content/status/download/action/complete.rs create mode 100644 src/app/browser/window/tab/item/page/content/status/download/action/update.rs diff --git a/src/app/browser/window/tab/item/page.rs b/src/app/browser/window/tab/item/page.rs index 09400f9b..54911848 100644 --- a/src/app/browser/window/tab/item/page.rs +++ b/src/app/browser/window/tab/item/page.rs @@ -25,12 +25,12 @@ use crate::Profile; use gtk::{ gdk::Texture, gdk_pixbuf::Pixbuf, - gio::{Cancellable, SocketClientEvent}, + gio::SocketClientEvent, glib::{ gformat, GString, Priority, Regex, RegexCompileFlags, RegexMatchFlags, Uri, UriFlags, UriHideFlags, }, - prelude::{CancellableExt, EditableExt, FileExt, SocketClientExt, WidgetExt}, + prelude::{EditableExt, FileExt, SocketClientExt}, }; use sqlite::Transaction; use std::{rc::Rc, time::Duration}; @@ -480,7 +480,7 @@ impl Page { &cancellable, { let cancellable = cancellable.clone(); - move |file, download_status| { + move |file, action| { match file.replace( None, false, @@ -499,52 +499,28 @@ impl Page { 0 // initial totals ), ( + // on chunk { - let download_status = download_status.clone(); - move |_, total| { - // Update loading progress - download_status.set_label( - &format!("Received {total} bytes...") - ); - } + let action = action.clone(); + move |_, total| action.update.activate( + &format!("Received {total} bytes...") + ) }, + // on complete { - let cancellable = cancellable.clone(); + let action = action.clone(); move |result| match result { - Ok((_, total)) => { - // Update loading progress - download_status.set_label( - &format!("Download ({total} bytes) completed!") - ); - } - Err(e) => { - // cancel uncompleted async operations - // * this will also toggle download widget actions - cancellable.cancel(); - - // update status message - download_status.set_label(&e.to_string()); - download_status.set_css_classes(&["error"]); - - // cleanup - let _ = file.delete(Cancellable::NONE); // @TODO - } + Ok((_, total)) => action.complete.activate( + &format!("Download ({total} bytes) completed!") + ), + Err(e) => action.cancel.activate(&e.to_string()) } } ) ); }, Err(e) => { - // cancel uncompleted async operations - // * this will also toggle download widget actions - cancellable.cancel(); - - // update status message - download_status.set_label(&e.to_string()); - download_status.set_css_classes(&["error"]); - - // cleanup - let _ = file.delete(Cancellable::NONE); // @TODO + action.cancel.activate(&e.to_string()) } } } diff --git a/src/app/browser/window/tab/item/page/content.rs b/src/app/browser/window/tab/item/page/content.rs index aa73e719..23660727 100644 --- a/src/app/browser/window/tab/item/page/content.rs +++ b/src/app/browser/window/tab/item/page/content.rs @@ -12,7 +12,7 @@ use gtk::{ gio::{Cancellable, File}, glib::Uri, prelude::{BoxExt, IsA, WidgetExt}, - Box, Label, Orientation, + Box, Orientation, }; use std::{rc::Rc, time::Duration}; @@ -53,7 +53,7 @@ impl Content { &self, initial_filename: &str, cancellable: &Cancellable, - on_choose: impl Fn(File, Label) + 'static, + on_choose: impl Fn(File, Rc) + 'static, ) -> Status { self.clean(); let status = Status::new_download(initial_filename, cancellable, on_choose); diff --git a/src/app/browser/window/tab/item/page/content/status.rs b/src/app/browser/window/tab/item/page/content/status.rs index e3cd1983..17119b43 100644 --- a/src/app/browser/window/tab/item/page/content/status.rs +++ b/src/app/browser/window/tab/item/page/content/status.rs @@ -1,14 +1,10 @@ -mod download; +pub mod download; mod failure; mod identity; mod loading; -use crate::app::browser::window::tab::item::Action; use adw::StatusPage; -use gtk::{ - gio::{Cancellable, File}, - Label, -}; +use gtk::gio::{Cancellable, File}; use std::{rc::Rc, time::Duration}; pub struct Status { @@ -22,7 +18,7 @@ impl Status { pub fn new_download( initial_filename: &str, cancellable: &Cancellable, - on_choose: impl Fn(File, Label) + 'static, + on_choose: impl Fn(File, Rc) + 'static, ) -> Self { Self { gobject: download::new(initial_filename, cancellable, on_choose), @@ -42,7 +38,7 @@ impl Status { /// /// Useful as placeholder for 60 status code /// https://geminiprotocol.net/docs/protocol-specification.gmi#status-60 - pub fn new_identity(action: Rc) -> Self { + pub fn new_identity(action: Rc) -> Self { Self { gobject: identity::new_gobject(action), } diff --git a/src/app/browser/window/tab/item/page/content/status/download.rs b/src/app/browser/window/tab/item/page/content/status/download.rs index bf2fe8fc..f1a7598a 100644 --- a/src/app/browser/window/tab/item/page/content/status/download.rs +++ b/src/app/browser/window/tab/item/page/content/status/download.rs @@ -1,9 +1,11 @@ +mod action; mod cancel; mod choose; mod open; mod progress; mod status; +pub use action::Action; use cancel::Cancel; use choose::Choose; use open::Open; @@ -14,7 +16,7 @@ use adw::StatusPage; use gtk::{ gio::{Cancellable, File}, prelude::{BoxExt, CancellableExt, WidgetExt}, - Box, FileDialog, FileLauncher, Label, Orientation, Window, + Box, FileDialog, FileLauncher, Orientation, Window, }; use std::rc::Rc; @@ -32,19 +34,57 @@ const TITLE: &str = "Download"; pub fn new( initial_filename: &str, cancellable: &Cancellable, - on_choose: impl Fn(File, Label) + 'static, + on_choose: impl Fn(File, Rc) + 'static, ) -> StatusPage { // Init components let dialog = FileDialog::builder().initial_name(initial_filename).build(); let file_launcher = FileLauncher::new(File::NONE); + let action = Rc::new(Action::new()); // public callback API + let cancel = Rc::new(Cancel::new()); let choose = Rc::new(Choose::new()); let open = Rc::new(Open::new()); let progress = Rc::new(Progress::new()); let status = Rc::new(Status::new()); - // Init events + // Init action events + action.cancel.on_activate({ + let cancel = cancel.clone(); + let cancellable = cancellable.clone(); + let progress = progress.clone(); + let status = status.clone(); + move |_, message| { + cancellable.cancel(); + progress.disable(); + status.set_warning(&message); + cancel.button.set_visible(false); + } + }); + + action.complete.on_activate({ + let cancel = cancel.clone(); + let cancellable = cancellable.clone(); + let open = open.clone(); + let progress = progress.clone(); + let status = status.clone(); + move |_, message| { + cancellable.cancel(); + progress.disable(); + status.set_success(&message); + cancel.button.set_visible(false); + open.button.set_visible(true); + } + }); + + action.update.on_activate({ + let status = status.clone(); + move |_, message| { + status.set_default(&message); + } + }); + + // Init widget events cancel.on_activate({ let cancellable = cancellable.clone(); let progress = progress.clone(); @@ -75,6 +115,7 @@ pub fn new( button.set_sensitive(false); dialog.save(Window::NONE, Some(&cancellable), { // delegate shared references + let action = action.clone(); let cancel = cancel.clone(); let file_launcher = file_launcher.clone(); let progress = progress.clone(); @@ -96,7 +137,7 @@ pub fn new( // hide self button.set_visible(false); // callback - on_choose(file, status.label.clone()) + on_choose(file, action) } Err(e) => { // update destination file diff --git a/src/app/browser/window/tab/item/page/content/status/download/action.rs b/src/app/browser/window/tab/item/page/content/status/download/action.rs new file mode 100644 index 00000000..efafdd39 --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/status/download/action.rs @@ -0,0 +1,29 @@ +mod cancel; +mod complete; +mod update; + +use cancel::Cancel; +use complete::Complete; +use update::Update; + +use std::rc::Rc; + +/// Callback API for `Download` widget +pub struct Action { + pub cancel: Rc, + pub complete: Rc, + pub update: Rc, +} + +impl Action { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self { + cancel: Rc::new(Cancel::new()), + complete: Rc::new(Complete::new()), + update: Rc::new(Update::new()), + } + } +} diff --git a/src/app/browser/window/tab/item/page/content/status/download/action/cancel.rs b/src/app/browser/window/tab/item/page/content/status/download/action/cancel.rs new file mode 100644 index 00000000..d6667f98 --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/status/download/action/cancel.rs @@ -0,0 +1,45 @@ +// Defaults + +use gtk::{ + gio::SimpleAction, + glib::{uuid_string_random, SignalHandlerId}, + prelude::{ActionExt, StaticVariantType, ToVariant}, +}; + +/// Cancel [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) +pub struct Cancel { + pub action: SimpleAction, +} + +impl Cancel { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self { + action: SimpleAction::new(&uuid_string_random(), Some(&String::static_variant_type())), + } + } + + // Actions + + pub fn activate(&self, message: &str) { + self.action.activate(Some(&message.to_variant())); + } + + /// Formatted action connector for external implementation + pub fn on_activate( + &self, + callback: impl Fn(&SimpleAction, String) + 'static, + ) -> SignalHandlerId { + self.action.connect_activate(move |this, message| { + callback( + this, + message + .expect("Variant required to call this action") + .get::() + .expect("Parameter does not match `String` type"), + ) + }) + } +} diff --git a/src/app/browser/window/tab/item/page/content/status/download/action/complete.rs b/src/app/browser/window/tab/item/page/content/status/download/action/complete.rs new file mode 100644 index 00000000..84fde709 --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/status/download/action/complete.rs @@ -0,0 +1,45 @@ +// Defaults + +use gtk::{ + gio::SimpleAction, + glib::{uuid_string_random, SignalHandlerId}, + prelude::{ActionExt, StaticVariantType, ToVariant}, +}; + +/// Complete [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) +pub struct Complete { + pub action: SimpleAction, +} + +impl Complete { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self { + action: SimpleAction::new(&uuid_string_random(), Some(&String::static_variant_type())), + } + } + + // Actions + + pub fn activate(&self, message: &str) { + self.action.activate(Some(&message.to_variant())); + } + + /// Formatted action connector for external implementation + pub fn on_activate( + &self, + callback: impl Fn(&SimpleAction, String) + 'static, + ) -> SignalHandlerId { + self.action.connect_activate(move |this, message| { + callback( + this, + message + .expect("Variant required to call this action") + .get::() + .expect("Parameter does not match `String` type"), + ) + }) + } +} diff --git a/src/app/browser/window/tab/item/page/content/status/download/action/update.rs b/src/app/browser/window/tab/item/page/content/status/download/action/update.rs new file mode 100644 index 00000000..6995fe0f --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/status/download/action/update.rs @@ -0,0 +1,45 @@ +// Defaults + +use gtk::{ + gio::SimpleAction, + glib::{uuid_string_random, SignalHandlerId}, + prelude::{ActionExt, StaticVariantType, ToVariant}, +}; + +/// Update [SimpleAction](https://docs.gtk.org/gio/class.SimpleAction.html) +pub struct Update { + pub action: SimpleAction, +} + +impl Update { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self { + action: SimpleAction::new(&uuid_string_random(), Some(&String::static_variant_type())), + } + } + + // Actions + + pub fn activate(&self, message: &str) { + self.action.activate(Some(&message.to_variant())); + } + + /// Formatted action connector for external implementation + pub fn on_activate( + &self, + callback: impl Fn(&SimpleAction, String) + 'static, + ) -> SignalHandlerId { + self.action.connect_activate(move |this, message| { + callback( + this, + message + .expect("Variant required to call this action") + .get::() + .expect("Parameter does not match `String` type"), + ) + }) + } +}