implement public api actions for download feature

This commit is contained in:
yggverse 2024-12-11 01:03:46 +02:00
parent 6f1761dcd7
commit 054afd270a
8 changed files with 230 additions and 53 deletions

View File

@ -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(
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(
Ok((_, total)) => action.complete.activate(
&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
}
),
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())
}
}
}

View File

@ -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<status::download::Action>) + 'static,
) -> Status {
self.clean();
let status = Status::new_download(initial_filename, cancellable, on_choose);

View File

@ -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<download::Action>) + '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<Action>) -> Self {
pub fn new_identity(action: Rc<crate::app::browser::window::tab::item::Action>) -> Self {
Self {
gobject: identity::new_gobject(action),
}

View File

@ -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<Action>) + '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

View File

@ -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<Cancel>,
pub complete: Rc<Complete>,
pub update: Rc<Update>,
}
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()),
}
}
}

View File

@ -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::<String>()
.expect("Parameter does not match `String` type"),
)
})
}
}

View File

@ -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::<String>()
.expect("Parameter does not match `String` type"),
)
})
}
}

View File

@ -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::<String>()
.expect("Parameter does not match `String` type"),
)
})
}
}