implement Redirect struct as the redirection wrapper with additional API features

This commit is contained in:
yggverse 2025-03-26 21:50:07 +02:00
parent 0a5e837140
commit a832936054
5 changed files with 111 additions and 18 deletions

View file

@ -566,7 +566,10 @@ fn handle(
.set_size(None) .set_size(None)
.commit(); .commit();
page.navigation.request.info.replace(i.into_redirect()); page.navigation.request.info.replace(match redirect {
Redirect::Permanent { .. } => i.into_permanent_redirect(),
Redirect::Temporary { .. } => i.into_temporary_redirect(),
});
} }
page.item_action.load.activate(Some(&t), false); page.item_action.load.activate(Some(&t), false);
} }

View file

@ -1,11 +1,13 @@
mod dialog; mod dialog;
mod event; mod event;
mod redirect;
mod socket; mod socket;
use super::Profile; use super::Profile;
use dialog::Dialog; use dialog::Dialog;
use event::Event; use event::Event;
use gtk::{gio::SocketAddress, prelude::IsA}; use gtk::{gio::SocketAddress, prelude::IsA};
use redirect::Redirect;
use socket::Socket; use socket::Socket;
/// Common, shared `Page` information holder /// Common, shared `Page` information holder
@ -24,7 +26,7 @@ pub struct Info {
mime: Option<String>, mime: Option<String>,
/// Hold redirections chain with handled details /// Hold redirections chain with handled details
/// * the `referrer` member name is reserved for other protocols /// * the `referrer` member name is reserved for other protocols
redirect: Option<Box<Self>>, redirect: Option<Box<Redirect>>,
/// Key to relate data collected with the specific request /// Key to relate data collected with the specific request
request: Option<String>, request: Option<String>,
/// Hold size info /// Hold size info
@ -74,13 +76,20 @@ impl Info {
/// Take `Self`, convert it into the redirect member, /// Take `Self`, convert it into the redirect member,
/// then, return new `Self` back /// then, return new `Self` back
/// * tip: use on driver redirection events pub fn into_redirect(self, method: redirect::Method) -> Self {
pub fn into_redirect(self) -> Self {
let mut this = Self::new(); let mut this = Self::new();
this.redirect = Some(Box::new(self)); this.redirect = Some(Box::new(Redirect { info: self, method }));
this this
} }
pub fn into_permanent_redirect(self) -> Self {
self.into_redirect(redirect::Method::Permanent)
}
pub fn into_temporary_redirect(self) -> Self {
self.into_redirect(redirect::Method::Temporary)
}
pub fn add_event(&mut self, name: String) -> &mut Self { pub fn add_event(&mut self, name: String) -> &mut Self {
self.event.push(Event::now(name)); self.event.push(Event::now(name));
self self

View file

@ -185,36 +185,60 @@ impl Dialog for PreferencesDialog {
.icon_name("insert-link-symbolic") .icon_name("insert-link-symbolic")
.build(); .build();
p.add(&{ p.add(&{
// Collect redirections into the buffer, use gtk::Button;
// to reverse chain before add its members to widget /// Common suffix widget pattern
// * capacity optimized for Gemini protocol (as default) fn suffix(
let mut b = Vec::with_capacity(5); icon_name: impl Into<GString>,
tooltip_text: impl Into<GString>,
) -> Button {
Button::builder()
.css_classes(["flat"])
.icon_name(icon_name)
.tooltip_text(tooltip_text)
.sensitive(false)
.valign(Align::Center)
.halign(Align::Center)
.build()
}
/// Recursively collect redirection members into the given vector /// Recursively collect redirection members into the given vector
fn chain<'a>(b: &mut Vec<&'a Info>, i: &'a Info) { fn chain<'a>(b: &mut Vec<&'a Info>, i: &'a Info) {
b.push(i); b.push(i);
if let Some(ref r) = i.redirect { if let Some(ref r) = i.redirect {
chain(b, r) chain(b, &r.info)
} }
} }
// Collect redirections into the buffer,
// to reverse chain before add its members to widget
// * capacity optimized for Gemini protocol (as default)
let mut b = Vec::with_capacity(5);
chain(&mut b, info); chain(&mut b, info);
b.reverse(); b.reverse();
let l = b.len(); // calculate once let l = b.len(); // calculate once
let t = b[0].event[0].time(); // first event time to count from let t = b[0].event[0].time(); // first event time to count from
for (i, r) in b.iter().enumerate() { for (i, r) in b.iter().enumerate() {
g.add(&{ g.add(&{
let is_external = r
.redirect
.as_ref()
.is_some_and(|this| this.is_external(r).is_some_and(|v| v));
let a = ActionRow::builder() let a = ActionRow::builder()
.css_classes(["property"]) .css_classes(["property"])
.subtitle_selectable(true) .subtitle_selectable(true)
.title_selectable(true) .title_selectable(true)
.title(r.request().unwrap()) .title(r.request().unwrap())
.build(); .build();
// show redirections counter
a.add_prefix(&{ a.add_prefix(&{
let c = i + 1; let c = i + 1;
gtk::Button::builder() Button::builder()
.css_classes([ .css_classes([
"circular", "circular",
if c == l { "success" } else { "accent" }, if is_external {
"warning"
} else if c == l {
"success"
} else {
"accent"
},
]) ])
.label(c.to_string()) .label(c.to_string())
.sensitive(false) .sensitive(false)
@ -222,6 +246,15 @@ impl Dialog for PreferencesDialog {
.halign(Align::Center) .halign(Align::Center)
.build() .build()
}); });
if let Some(ref redirect) = r.redirect {
a.add_suffix(&suffix(
redirect.method.icon_name(),
redirect.method.to_string(),
))
}
if is_external {
a.add_suffix(&suffix("application-exit-symbolic", "External")) // @TODO links contain ⇖ text label indication
}
// show total redirection time in ms // show total redirection time in ms
a.set_subtitle(&if i == 0 { a.set_subtitle(&if i == 0 {
t.format_iso8601().unwrap() t.format_iso8601().unwrap()
@ -237,7 +270,7 @@ impl Dialog for PreferencesDialog {
) )
}); });
a a
}); })
} }
g g
}); });
@ -266,10 +299,7 @@ impl Dialog for PreferencesDialog {
); );
a.add_suffix( a.add_suffix(
&Label::builder() &Label::builder()
.css_classes([ .css_classes([if c == 0 { "success" } else { "warning" }])
"flat",
if c == 0 { "success" } else { "warning" },
])
.halign(Align::End) .halign(Align::End)
.label(if c > 0 { .label(if c > 0 {
format!("+{c} ms") format!("+{c} ms")

View file

@ -0,0 +1,26 @@
pub mod method;
pub use method::Method;
use super::Info;
/// Unified redirection info wrapper for the application page
pub struct Redirect {
pub info: Info,
pub method: Method,
}
impl Redirect {
// Getters
/// Check redirection has external target
/// * return `None` when at least one request value could not be parsed to
/// the valid [Uri](https://docs.gtk.org/glib/struct.Uri.html) host
pub fn is_external(&self, cmp: &Info) -> Option<bool> {
fn parse(info: &Info) -> Option<gtk::glib::GString> {
gtk::glib::Uri::parse(info.request.as_ref()?, gtk::glib::UriFlags::NONE)
.ok()?
.host()
}
Some(parse(&self.info)? != parse(cmp)?)
}
}

View file

@ -0,0 +1,25 @@
/// Common redirection type enumeration for different protocol drivers
pub enum Method {
Permanent,
Temporary,
}
impl Method {
pub fn icon_name(&self) -> &str {
match self {
Self::Permanent => "network-transmit-symbolic",
Self::Temporary => "network-transmit-receive-symbolic",
}
}
}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Permanent => write!(f, "Permanent"),
Self::Temporary => {
write!(f, "Temporary")
}
}
}
}