reorganize request routing

This commit is contained in:
yggverse 2025-01-18 12:53:23 +02:00
parent d87057a544
commit b6e1ae4e6a
11 changed files with 329 additions and 527 deletions

View File

@ -23,7 +23,7 @@ use crate::tool::now;
use gtk::{
gdk::Texture,
gdk_pixbuf::Pixbuf,
glib::{gformat, GString, Priority, Uri},
glib::{GString, Priority, Uri},
prelude::{EditableExt, FileExt},
};
use sqlite::Transaction;
@ -307,7 +307,7 @@ impl Page {
.request
.widget
.entry
.set_text(&request.uri().unwrap().to_string())} // @TODO handle
.set_text(&request.as_uri().to_string())} // @TODO handle
}
Response::TextGemini { base, source, is_source_request } => {
let widget = if is_source_request {

View File

@ -1,17 +1,18 @@
pub mod driver;
pub mod request;
pub mod response;
pub mod status;
// Children dependencies
pub use driver::Driver;
pub use request::Request;
pub use response::Response;
pub use status::Status;
// Global dependencies
use crate::{tool::now, Profile};
use gtk::{gio::Cancellable, glib::Priority, prelude::CancellableExt};
use gtk::{
gio::{Cancellable, SocketClientEvent},
prelude::{CancellableExt, SocketClientExt},
};
use std::{
cell::{Cell, RefCell},
rc::Rc,
@ -21,7 +22,12 @@ use std::{
pub struct Client {
cancellable: Cell<Cancellable>,
status: Rc<RefCell<Status>>,
driver: Driver,
/// Profile reference required for Gemini protocol auth (match scope)
profile: Rc<Profile>,
/// Supported clients
/// * gemini driver should be initiated once (on page object init)
/// to process all it connection features properly
gemini: Rc<ggemini::Client>,
}
impl Client {
@ -29,12 +35,32 @@ impl Client {
/// Create new `Self`
pub fn init(profile: &Rc<Profile>, callback: impl Fn(Status) + 'static) -> Self {
use status::Gemini;
// Init supported protocol libraries
let gemini = Rc::new(ggemini::Client::new());
// Retransmit gemini [SocketClient](https://docs.gtk.org/gio/class.SocketClient.html) updates
gemini.socket.connect_event(move |_, event, _, _| {
callback(Status::Gemini(match event {
SocketClientEvent::Resolving => Gemini::Resolving { time: now() },
SocketClientEvent::Resolved => Gemini::Resolved { time: now() },
SocketClientEvent::Connecting => Gemini::Connecting { time: now() },
SocketClientEvent::Connected => Gemini::Connected { time: now() },
SocketClientEvent::ProxyNegotiating => Gemini::ProxyNegotiating { time: now() },
SocketClientEvent::ProxyNegotiated => Gemini::ProxyNegotiated { time: now() },
// * `TlsHandshaking` | `TlsHandshaked` has effect only for guest connections!
SocketClientEvent::TlsHandshaking => Gemini::TlsHandshaking { time: now() },
SocketClientEvent::TlsHandshaked => Gemini::TlsHandshaked { time: now() },
SocketClientEvent::Complete => Gemini::Complete { time: now() },
_ => todo!(), // alert on API change
}))
});
Self {
cancellable: Cell::new(Cancellable::new()),
driver: Driver::init(profile.clone(), move |status| {
callback(Status::Driver(status))
}),
status: Rc::new(RefCell::new(Status::Cancellable { time: now() })), // e.g. "ready to use"
profile: profile.clone(),
gemini,
}
}
@ -43,16 +69,11 @@ impl Client {
/// Begin new request
/// * the `query` as string, to support system routes (e.g. `source:` prefix)
pub fn request_async(&self, query: &str, callback: impl FnOnce(Response) + 'static) {
// Update client status
self.status.replace(Status::Request {
time: now(),
value: query.to_string(),
});
self.driver.request_async(
Request::build(query, None, self.new_cancellable(), Priority::DEFAULT),
callback,
);
Request::route(self, query, None, self.new_cancellable(), callback);
}
/// Get new [Cancellable](https://docs.gtk.org/gio/class.Cancellable.html) by cancel previous one

View File

@ -1,166 +0,0 @@
//! At this moment, the `Driver` contain only one protocol library,
//! by extending it features with new protocol, please make sub-module implementation
mod gemini;
pub mod status;
// Local dependencies
pub use status::Status;
// Global dependencies
use super::{
request::{feature::Protocol, Feature},
response,
response::Failure,
Request, Response,
};
use crate::{tool::now, Profile};
use gtk::{gio::SocketClientEvent, prelude::SocketClientExt};
use std::rc::Rc;
pub struct Driver {
/// Profile reference required for Gemini protocol auth (match scope)
profile: Rc<Profile>,
/// Supported clients
/// * gemini driver should be initiated once (on page object init)
/// to process all it connection features properly
gemini: Rc<ggemini::Client>,
// other clients here..
}
impl Driver {
// Constructors
/// Init new `Self`
pub fn init(profile: Rc<Profile>, callback: impl Fn(Status) + 'static) -> Self {
// Init supported protocol libraries
let gemini = Rc::new(ggemini::Client::new());
// Retransmit gemini [SocketClient](https://docs.gtk.org/gio/class.SocketClient.html) updates
gemini.socket.connect_event(move |_, event, _, _| {
callback(match event {
SocketClientEvent::Resolving => Status::Resolving { time: now() },
SocketClientEvent::Resolved => Status::Resolved { time: now() },
SocketClientEvent::Connecting => Status::Connecting { time: now() },
SocketClientEvent::Connected => Status::Connected { time: now() },
SocketClientEvent::ProxyNegotiating => Status::ProxyNegotiating { time: now() },
SocketClientEvent::ProxyNegotiated => Status::ProxyNegotiated { time: now() },
// * `TlsHandshaking` | `TlsHandshaked` has effect only for guest connections!
SocketClientEvent::TlsHandshaking => Status::TlsHandshaking { time: now() },
SocketClientEvent::TlsHandshaked => Status::TlsHandshaked { time: now() },
SocketClientEvent::Complete => Status::Complete { time: now() },
_ => todo!(), // alert on API change
})
});
// other client listeners here..
// Done
Self { profile, gemini }
}
// Actions
/// Make new async `Feature` request
/// * return `Response` in callback function
pub fn request_async(&self, request: Request, callback: impl FnOnce(Response) + 'static) {
let referrer = request.to_referrer();
match request.feature {
Feature::Download(protocol) => match protocol {
Protocol::Gemini {
uri,
cancellable,
priority,
} => gemini::request_async(
&self.profile,
&self.gemini,
&uri,
&cancellable,
&priority,
{
let base = uri.clone();
let cancellable = cancellable.clone();
move |result| {
callback(match result {
Ok(response) => Response::Download {
base,
stream: response.connection.stream(),
cancellable,
},
Err(e) => Response::Failure(Failure::Error {
message: e.to_string(),
}),
})
}
},
),
_ => callback(Response::Failure(Failure::Error {
message: "Download feature yet not supported for this request".to_string(),
})), // @TODO or maybe panic as unexpected
},
Feature::Default(protocol) => match protocol {
Protocol::Gemini {
uri,
cancellable,
priority,
} => gemini::request_async(
&self.profile,
&self.gemini,
&uri,
&cancellable,
&priority,
{
let cancellable = cancellable.clone();
let uri = uri.clone();
move |result| {
gemini::handle(
result,
uri,
cancellable,
priority,
referrer,
false,
callback,
)
}
},
),
Protocol::Titan { .. } => todo!(),
Protocol::Undefined => todo!(),
},
Feature::Source(ref protocol) => match protocol {
Protocol::Gemini {
uri,
cancellable,
priority,
} => gemini::request_async(
&self.profile,
&self.gemini,
uri,
cancellable,
priority,
{
let cancellable = cancellable.clone();
let priority = *priority;
let uri = uri.clone();
move |result| {
gemini::handle(
result,
uri,
cancellable,
priority,
referrer,
true,
callback,
)
}
},
),
_ => callback(Response::Failure(Failure::Error {
message: "Source view feature yet not supported for this request".to_string(),
})), // @TODO or maybe panic as unexpected
},
}
}
}

View File

@ -1,205 +0,0 @@
use super::{
response::{Certificate, Failure, Input, Redirect},
Profile, Request, Response,
};
use gtk::{
gio::Cancellable,
glib::{Priority, Uri, UriFlags},
};
use std::rc::Rc;
/// Shared request interface for Gemini protocol
pub fn request_async(
profile: &Rc<Profile>,
client: &Rc<ggemini::Client>,
uri: &Uri,
cancellable: &Cancellable,
priority: &Priority,
callback: impl FnOnce(Result<ggemini::client::Response, ggemini::client::Error>) + 'static,
) {
let request = uri.to_string();
client.request_async(
ggemini::client::Request::gemini(uri.clone()),
priority.clone(),
cancellable.clone(),
// Search for user certificate match request
// * @TODO this feature does not support multi-protocol yet
match profile.identity.gemini.match_scope(&request) {
Some(identity) => match identity.to_tls_certificate() {
Ok(certificate) => Some(certificate),
Err(_) => todo!(),
},
None => None,
},
callback,
)
}
/// Shared handler for Gemini `Result`
/// * same implementation for Gemini and Titan protocols response
pub fn handle(
result: Result<ggemini::client::connection::Response, ggemini::client::Error>,
base: Uri,
cancellable: Cancellable,
priority: Priority,
referrer: Vec<Request>,
is_source_request: bool, // @TODO yet partial implementation
callback: impl FnOnce(Response) + 'static,
) {
use ggemini::client::connection::response::{data::Text, meta::Status};
match result {
Ok(response) => match response.meta.status {
// https://geminiprotocol.net/docs/protocol-specification.gmi#input-expected
Status::Input => callback(Response::Input(Input::Response {
base,
title: match response.meta.data {
Some(data) => data.to_gstring(),
None => "Input expected".into(),
},
})),
Status::SensitiveInput => callback(Response::Input(Input::Sensitive {
base,
title: match response.meta.data {
Some(data) => data.to_gstring(),
None => "Input expected".into(),
},
})),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
Status::Success => match response.meta.mime {
Some(mime) => match mime.as_str() {
"text/gemini" => Text::from_stream_async(
response.connection.stream(),
priority.clone(),
cancellable.clone(),
move |result| match result {
Ok(text) => callback(Response::TextGemini {
base,
source: text.data,
is_source_request,
}),
Err(_) => todo!(),
},
),
"image/png" | "image/gif" | "image/jpeg" | "image/webp" => {
callback(Response::Stream {
base,
mime: mime.to_string(),
stream: response.connection.stream(),
cancellable,
})
}
mime => callback(Response::Failure(Failure::Mime {
base,
mime: mime.to_string(),
message: format!("Content type `{mime}` yet not supported"),
})),
},
None => callback(Response::Failure(Failure::Error {
message: "MIME type not found".to_string(),
})),
},
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-30-temporary-redirection
Status::Redirect => callback(redirect(
response.meta.data,
base,
referrer,
cancellable,
priority,
false,
)),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-31-permanent-redirection
Status::PermanentRedirect => callback(redirect(
response.meta.data,
base,
referrer,
cancellable,
priority,
true,
)),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-60
Status::CertificateRequest => callback(Response::Certificate(Certificate::Request {
title: match response.meta.data {
Some(data) => data.to_gstring(),
None => "Client certificate required".into(),
},
})),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-61-certificate-not-authorized
Status::CertificateUnauthorized => {
callback(Response::Certificate(Certificate::Request {
title: match response.meta.data {
Some(data) => data.to_gstring(),
None => "Certificate not authorized".into(),
},
}))
}
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-62-certificate-not-valid
Status::CertificateInvalid => callback(Response::Certificate(Certificate::Request {
title: match response.meta.data {
Some(data) => data.to_gstring(),
None => "Certificate not valid".into(),
},
})),
status => callback(Response::Failure(Failure::Status {
message: format!("Undefined status code `{status}`"),
})),
},
Err(e) => callback(Response::Failure(Failure::Error {
message: e.to_string(),
})),
}
}
/// Shared redirection `Response` builder
fn redirect(
data: Option<ggemini::client::connection::response::meta::Data>,
base: Uri,
referrer: Vec<Request>,
cancellable: Cancellable,
priority: Priority,
is_foreground: bool,
) -> Response {
// Validate redirection attempt
// [Gemini protocol specifications](https://geminiprotocol.net/docs/protocol-specification.gmi#redirection)
if referrer.len() > 5 {
return Response::Failure(Failure::Error {
message: format!("Max redirection count reached"),
});
}
match data {
// Target address could be relative, parse using base Uri
Some(target) => match Uri::parse_relative(&base, target.as_str(), UriFlags::NONE) {
Ok(target) => {
// Disallow external redirection
if base.scheme() != target.scheme()
|| base.port() != target.port()
|| base.host() != target.host()
{
return Response::Failure(Failure::Error {
message: format!(
"External redirects not allowed by protocol specification"
),
}); // @TODO placeholder page with optional link open button
}
// Build new `Request` for redirection `Response`
// * make sure that `referrer` already contain current `Request`
// (to validate redirection count in chain)
let request =
Request::build(&target.to_string(), Some(referrer), cancellable, priority);
Response::Redirect(if is_foreground {
Redirect::Foreground(request)
} else {
Redirect::Background(request)
})
}
Err(e) => Response::Failure(Failure::Error {
message: format!("Could not parse target address: {e}"),
}),
},
None => Response::Failure(Failure::Error {
message: "Target address not found".to_string(),
}),
}
}

View File

@ -1,45 +1,59 @@
pub mod feature;
pub use feature::Feature;
mod feature;
mod gemini;
use super::{Client, Response};
use feature::Feature;
use gtk::{
gio::Cancellable,
glib::{Priority, Uri},
glib::{Uri, UriFlags},
};
/// Request data wrapper for `Client`
#[derive(Clone)]
pub struct Request {
pub feature: Feature,
/// Requests chain in order to process redirection rules
pub referrer: Vec<Request>,
/// Single `Request` API for multiple `Client` drivers
pub enum Request {
Gemini {
feature: Feature,
referrer: Vec<Self>,
uri: Uri,
},
Titan(Uri),
}
impl Request {
// Constructors
// Actions
/// Build new `Self`
pub fn build(
/// Process request by routed driver
pub fn route(
client: &Client,
query: &str,
referrer: Option<Vec<Request>>,
referrer: Option<Vec<Self>>,
cancellable: Cancellable,
priority: Priority,
) -> Self {
Self {
feature: Feature::build(query, cancellable, priority),
referrer: referrer.unwrap_or_default(),
callback: impl FnOnce(Response) + 'static,
) {
let (feature, request) = Feature::parse(query);
match Uri::parse(request, UriFlags::NONE) {
Ok(uri) => match uri.scheme().as_str() {
"gemini" => gemini::route(client, feature, uri, referrer, cancellable, callback),
"titan" => todo!(),
_ => callback(Response::Redirect(
todo!(), //super::response::Redirect::Foreground(()),
)),
},
Err(_) => todo!(),
}
}
// Getters
/// Copy `Self` to new `referrer` vector
pub fn to_referrer(&self) -> Vec<Request> {
let mut referrer = self.referrer.to_vec();
referrer.push(self.clone());
referrer
}
pub fn uri(&self) -> Option<&Uri> {
self.feature.uri()
/// Get reference to `Self` [URI](https://docs.gtk.org/glib/struct.Uri.html)
pub fn as_uri(&self) -> &Uri {
match self {
Self::Gemini {
feature: _,
referrer: _,
uri,
}
| Self::Titan(uri) => &uri,
}
}
}

View File

@ -1,43 +1,40 @@
pub mod protocol;
pub use protocol::Protocol;
use gtk::{
gio::Cancellable,
glib::{Priority, Uri},
};
// Feature conversion prefixes
const DOWNLOAD: &str = "download:";
const SOURCE: &str = "source:";
/// Feature wrapper for client `Request`
#[derive(Clone)]
pub enum Feature {
Default(Protocol),
Download(Protocol),
Source(Protocol),
Default,
Download,
Source,
// @TODO System(Action)
}
impl Feature {
// Constructors
/// Parse new `Self` from string
pub fn build(query: &str, cancellable: Cancellable, priority: Priority) -> Self {
if let Some(postfix) = query.strip_prefix("download:") {
return Self::Download(Protocol::build(postfix, cancellable, priority));
/// Parse new `Self` from navigation entry request
pub fn parse(request: &str) -> (Self, &str) {
if let Some(postfix) = request.strip_prefix(DOWNLOAD) {
return (Self::Download, postfix);
}
if let Some(postfix) = query.strip_prefix("source:") {
return Self::Source(Protocol::build(postfix, cancellable, priority));
if let Some(postfix) = request.strip_prefix(SOURCE) {
return (Self::Source, postfix);
}
Self::Default(Protocol::build(query, cancellable, priority))
(Self::Default, request)
}
// Getters
pub fn uri(&self) -> Option<&Uri> {
/// Get `Self` as prefix
pub fn as_prefix(&self) -> Option<&str> {
match self {
Self::Default(protocol) | Self::Download(protocol) | Self::Source(protocol) => {
protocol.uri()
}
Self::Download => Some(DOWNLOAD),
Self::Source => Some(SOURCE),
Self::Default => None,
}
}
}

View File

@ -1,77 +0,0 @@
// Global dependencies
use gtk::{
gio::Cancellable,
glib::{Priority, Uri, UriFlags},
};
#[derive(Clone)]
pub enum Protocol {
Gemini {
uri: Uri,
cancellable: Cancellable,
priority: Priority,
},
Titan {
uri: Uri,
cancellable: Cancellable,
priority: Priority,
},
Undefined,
}
impl Protocol {
// Constructors
/// Create new `Self` from parsable request string
pub fn build(query: &str, cancellable: Cancellable, priority: Priority) -> Self {
match Uri::parse(query, UriFlags::NONE) {
Ok(uri) => match uri.scheme().as_str() {
"gemini" => Self::Gemini {
uri,
cancellable,
priority,
},
"titan" => Self::Titan {
uri,
cancellable,
priority,
},
_ => Self::Undefined,
},
// Search request if the request could not be parsed as the valid [URI](https://docs.gtk.org/glib/struct.Uri.html)
// * @TODO implement DNS lookup before apply this option
Err(_) => Self::Gemini {
uri: Uri::build(
UriFlags::NONE,
"gemini",
None,
Some("tlgs.one"),
-1,
"/search", // beginning slash required to prevent assertion panic on construct
Some(&Uri::escape_string(query, None, false)), // @TODO is `escape_string` really wanted in `build` context?
None,
),
cancellable,
priority,
},
}
}
// Getters
pub fn uri(&self) -> Option<&Uri> {
match self {
Self::Gemini {
uri,
cancellable: _,
priority: _,
}
| Self::Titan {
uri,
cancellable: _,
priority: _,
} => Some(&uri),
Self::Undefined => None,
}
}
}

View File

@ -0,0 +1,216 @@
use super::{super::response::*, Client, Feature, Request, Response};
use gtk::{
gio::Cancellable,
glib::{Priority, Uri, UriFlags},
};
pub fn route(
client: &Client,
feature: Feature,
uri: Uri,
referrer: Option<Vec<Request>>,
cancellable: Cancellable,
callback: impl FnOnce(Response) + 'static,
) {
request(
client,
uri.clone(),
cancellable.clone(),
move |result| match result {
Ok(response) => handle(response, uri, cancellable, referrer, feature, callback),
Err(e) => callback(Response::Failure(Failure::Error {
message: e.to_string(),
})),
},
)
}
/// Shared request interface for Gemini protocol
fn request(
client: &Client,
uri: Uri,
cancellable: Cancellable,
callback: impl FnOnce(Result<ggemini::client::Response, ggemini::client::Error>) + 'static,
) {
let request = uri.to_string();
client.gemini.request_async(
ggemini::client::Request::gemini(uri.clone()),
Priority::DEFAULT,
cancellable.clone(),
// Search for user certificate match request
// * @TODO this feature does not support multi-protocol yet
match client.profile.identity.gemini.match_scope(&request) {
Some(identity) => match identity.to_tls_certificate() {
Ok(certificate) => Some(certificate),
Err(_) => todo!(),
},
None => None,
},
callback,
)
}
/// Shared handler for Gemini `Result`
/// * same implementation for Gemini and Titan protocols response
fn handle(
response: ggemini::client::connection::Response,
base: Uri,
cancellable: Cancellable,
referrer: Option<Vec<Request>>,
feature: Feature,
callback: impl FnOnce(Response) + 'static,
) {
use ggemini::client::connection::response::{data::Text, meta::Status};
match response.meta.status {
// https://geminiprotocol.net/docs/protocol-specification.gmi#input-expected
Status::Input => callback(Response::Input(Input::Response {
base,
title: match response.meta.data {
Some(data) => data.to_gstring(),
None => "Input expected".into(),
},
})),
Status::SensitiveInput => callback(Response::Input(Input::Sensitive {
base,
title: match response.meta.data {
Some(data) => data.to_gstring(),
None => "Input expected".into(),
},
})),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
Status::Success => match response.meta.mime {
Some(mime) => match mime.as_str() {
"text/gemini" => Text::from_stream_async(
response.connection.stream(),
Priority::DEFAULT,
cancellable.clone(),
move |result| match result {
Ok(text) => callback(Response::TextGemini {
base,
source: text.data,
is_source_request: match feature {
Feature::Source => true,
_ => false,
},
}),
Err(_) => todo!(),
},
),
"image/png" | "image/gif" | "image/jpeg" | "image/webp" => {
callback(Response::Stream {
base,
mime: mime.to_string(),
stream: response.connection.stream(),
cancellable,
})
}
mime => callback(Response::Failure(Failure::Mime {
base,
mime: mime.to_string(),
message: format!("Content type `{mime}` yet not supported"),
})),
},
None => callback(Response::Failure(Failure::Error {
message: "MIME type not found".to_string(),
})),
},
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-30-temporary-redirection
Status::Redirect => callback(redirect(
response.meta.data,
base,
referrer.unwrap_or_default(), // @TODO
cancellable,
Priority::DEFAULT,
false,
)),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-31-permanent-redirection
Status::PermanentRedirect => callback(redirect(
response.meta.data,
base,
referrer.unwrap_or_default(), // @TODO
cancellable,
Priority::DEFAULT,
true,
)),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-60
Status::CertificateRequest => callback(Response::Certificate(Certificate::Request {
title: match response.meta.data {
Some(data) => data.to_gstring(),
None => "Client certificate required".into(),
},
})),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-61-certificate-not-authorized
Status::CertificateUnauthorized => callback(Response::Certificate(Certificate::Request {
title: match response.meta.data {
Some(data) => data.to_gstring(),
None => "Certificate not authorized".into(),
},
})),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-62-certificate-not-valid
Status::CertificateInvalid => callback(Response::Certificate(Certificate::Request {
title: match response.meta.data {
Some(data) => data.to_gstring(),
None => "Certificate not valid".into(),
},
})),
status => callback(Response::Failure(Failure::Status {
message: format!("Undefined status code `{status}`"),
})),
}
}
/// Shared redirection `Response` builder
fn redirect(
data: Option<ggemini::client::connection::response::meta::Data>,
base: Uri,
referrer: Vec<Request>,
cancellable: Cancellable,
priority: Priority,
is_foreground: bool,
) -> Response {
// Validate redirection attempt
// [Gemini protocol specifications](https://geminiprotocol.net/docs/protocol-specification.gmi#redirection)
if referrer.len() > 5 {
return Response::Failure(Failure::Error {
message: format!("Max redirection count reached"),
});
}
match data {
// Target address could be relative, parse using base Uri
Some(target) => match Uri::parse_relative(&base, target.as_str(), UriFlags::NONE) {
Ok(target) => {
// Disallow external redirection
if base.scheme() != target.scheme()
|| base.port() != target.port()
|| base.host() != target.host()
{
return Response::Failure(Failure::Error {
message: format!(
"External redirects not allowed by protocol specification"
),
}); // @TODO placeholder page with optional link open button
}
// Build new `Request` for redirection `Response`
// * make sure that `referrer` already contain current `Request`
// (to validate redirection count in chain)
todo!()
/*let request =
Request::build(&target.to_string(), Some(referrer), cancellable, priority);
Response::Redirect(if is_foreground {
Redirect::Foreground(request)
} else {
Redirect::Background(request)
})*/
}
Err(e) => Response::Failure(Failure::Error {
message: format!("Could not parse target address: {e}"),
}),
},
None => Response::Failure(Failure::Error {
message: "Target address not found".to_string(),
}),
}
}

View File

@ -1,7 +1,9 @@
pub mod failure;
pub mod gemini;
// Children dependencies
pub use failure::Failure;
pub use gemini::Gemini;
// Global dependencies
use crate::tool::format_time;
@ -16,7 +18,7 @@ pub enum Status {
/// Operation cancelled, new `Cancellable` required to continue
Cancelled { time: DateTime },
/// Protocol driver updates
Driver(super::driver::Status),
Gemini(Gemini),
/// Something went wrong
Failure { time: DateTime, failure: Failure },
/// New `request` begin
@ -40,7 +42,7 @@ impl Display for Status {
format_time(time)
)
}
Self::Driver(status) => {
Self::Gemini(status) => {
write!(f, "{status}")
}
Self::Failure { time, failure } => {

View File

@ -3,8 +3,8 @@ use crate::tool::format_time;
use gtk::glib::DateTime;
use std::fmt::{Display, Formatter, Result};
/// Shared asset for `Driver` statuses
pub enum Status {
/// Shared asset for `Gemini` statuses
pub enum Gemini {
Resolving { time: DateTime },
Resolved { time: DateTime },
Connecting { time: DateTime },
@ -16,7 +16,7 @@ pub enum Status {
Complete { time: DateTime },
}
impl Display for Status {
impl Display for Gemini {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Self::Resolving { time } => {

View File

@ -1,5 +1,5 @@
/// Global dependencies
use super::client::{driver::Status as Driver, Status as Client};
use super::client::{status::Gemini, Status as Client};
use gtk::glib::DateTime;
/// `Page` status
@ -28,16 +28,16 @@ impl Status {
| Client::Cancelled { .. }
| Client::Failure { .. }
| Client::Request { .. } => Some(0.0),
Client::Driver(status) => match status {
Driver::Resolving { .. } => Some(0.1),
Driver::Resolved { .. } => Some(0.2),
Driver::Connecting { .. } => Some(0.3),
Driver::Connected { .. } => Some(0.4),
Driver::ProxyNegotiating { .. } => Some(0.5),
Driver::ProxyNegotiated { .. } => Some(0.6),
Driver::TlsHandshaking { .. } => Some(0.7),
Driver::TlsHandshaked { .. } => Some(0.8),
Driver::Complete { .. } => Some(0.9),
Client::Gemini(status) => match status {
Gemini::Resolving { .. } => Some(0.1),
Gemini::Resolved { .. } => Some(0.2),
Gemini::Connecting { .. } => Some(0.3),
Gemini::Connected { .. } => Some(0.4),
Gemini::ProxyNegotiating { .. } => Some(0.5),
Gemini::ProxyNegotiated { .. } => Some(0.6),
Gemini::TlsHandshaking { .. } => Some(0.7),
Gemini::TlsHandshaked { .. } => Some(0.8),
Gemini::Complete { .. } => Some(0.9),
},
},
Self::Failure { .. }