mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-02-10 18:34:14 +00:00
begin multi-protocol page driver implementation
This commit is contained in:
parent
5e2f9d6de0
commit
9d45688314
File diff suppressed because it is too large
Load Diff
@ -1,63 +1,61 @@
|
|||||||
|
pub mod driver;
|
||||||
mod feature;
|
mod feature;
|
||||||
mod redirect;
|
pub mod response;
|
||||||
mod status;
|
pub mod status;
|
||||||
|
|
||||||
// Children dependencies
|
// Children dependencies
|
||||||
|
pub use driver::Driver;
|
||||||
use feature::Feature;
|
use feature::Feature;
|
||||||
use redirect::Redirect;
|
pub use response::Response;
|
||||||
use status::Status;
|
pub use status::Status;
|
||||||
|
|
||||||
// Global dependencies
|
// Global dependencies
|
||||||
|
use crate::{tool::now, Profile};
|
||||||
use gtk::{gio::Cancellable, prelude::CancellableExt};
|
use gtk::{gio::Cancellable, prelude::CancellableExt};
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Multi-client holder for single `Page` object
|
/// Multi-protocol client API for `Page` object
|
||||||
///
|
|
||||||
/// Unlike init new client instance on every page load,
|
|
||||||
/// this struct creates single holder for different protocol drivers;
|
|
||||||
/// it also provides additional client-side features
|
|
||||||
/// e.g. session resumption or multi-thread connection management (depending of client type selected)
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
// Shared reference to cancel async operations
|
|
||||||
// * keep it private to make sure that `status` member tracked properly
|
|
||||||
cancellable: Cell<Cancellable>,
|
cancellable: Cell<Cancellable>,
|
||||||
// Redirect resolver for different protocols
|
|
||||||
pub redirect: Rc<Redirect>,
|
|
||||||
// Track update status
|
|
||||||
status: Rc<RefCell<Status>>,
|
status: Rc<RefCell<Status>>,
|
||||||
// Drivers
|
driver: Driver,
|
||||||
pub gemini: gemini::Client,
|
|
||||||
// other clients..
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Client {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new() -> Self {
|
pub fn init(profile: &Rc<Profile>, callback: impl Fn(Status) + 'static) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cancellable: Cell::new(Cancellable::new()),
|
cancellable: Cell::new(Cancellable::new()),
|
||||||
redirect: Rc::new(Redirect::new()),
|
driver: Driver::init(profile, move |status| callback(Status::Driver(status))),
|
||||||
status: Rc::new(RefCell::new(Status::cancellable())), // e.g. "ready to use"
|
status: Rc::new(RefCell::new(Status::Cancellable { time: now() })), // e.g. "ready to use"
|
||||||
gemini: gemini::Client::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
|
/// Begin new request
|
||||||
|
/// * the `query` as string, to support system routes (e.g. `source:` prefix)
|
||||||
|
pub fn request_async(&self, request: &str, callback: impl Fn(Response) + 'static) {
|
||||||
|
// Update client status
|
||||||
|
self.status.replace(Status::Request {
|
||||||
|
time: now(),
|
||||||
|
value: request.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.driver.feature_async(
|
||||||
|
Feature::from_string(request),
|
||||||
|
self.new_cancellable(),
|
||||||
|
Rc::new(callback),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Get new [Cancellable](https://docs.gtk.org/gio/class.Cancellable.html) by cancel previous one
|
/// Get new [Cancellable](https://docs.gtk.org/gio/class.Cancellable.html) by cancel previous one
|
||||||
/// * this action wanted just because of `Cancelable` member constructed privately,
|
fn new_cancellable(&self) -> Cancellable {
|
||||||
/// where some external components may depend to sync their related processes
|
|
||||||
pub fn cancellable(&self) -> Cancellable {
|
|
||||||
// Init new Cancellable
|
// Init new Cancellable
|
||||||
let cancellable = Cancellable::new();
|
let cancellable = Cancellable::new();
|
||||||
|
|
||||||
@ -65,33 +63,12 @@ impl Client {
|
|||||||
let previous = self.cancellable.replace(cancellable.clone());
|
let previous = self.cancellable.replace(cancellable.clone());
|
||||||
if !previous.is_cancelled() {
|
if !previous.is_cancelled() {
|
||||||
previous.cancel();
|
previous.cancel();
|
||||||
self.status.replace(Status::cancelled());
|
self.status.replace(Status::Cancelled { time: now() });
|
||||||
} else {
|
} else {
|
||||||
self.status.replace(Status::cancellable());
|
self.status.replace(Status::Cancellable { time: now() });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
cancellable
|
cancellable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Begin new request
|
|
||||||
/// * the `query` as string, to support system routing requests (e.g. `source:`)
|
|
||||||
pub fn request(&self, query: &str) {
|
|
||||||
self.status.replace(Status::request(query.to_string()));
|
|
||||||
|
|
||||||
// Forcefully prevent infinitive redirection
|
|
||||||
// * this condition just to make sure that client will never stuck by driver implementation issue
|
|
||||||
if self.redirect.count() > redirect::LIMIT {
|
|
||||||
self.status
|
|
||||||
.replace(Status::failure_redirect_limit(redirect::LIMIT, true));
|
|
||||||
// @TODO return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route request by protocol
|
|
||||||
match Feature::from_string(query) {
|
|
||||||
Feature::Default { request }
|
|
||||||
| Feature::Download { request }
|
|
||||||
| Feature::Source { request } => request.send(), // @TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
240
src/app/browser/window/tab/item/page/client/driver.rs
Normal file
240
src/app/browser/window/tab/item/page/client/driver.rs
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
//! At this moment, the `Driver` contain only one protocol library,
|
||||||
|
//! by extending it features with new protocol, please make sub-module implementation
|
||||||
|
|
||||||
|
mod redirect;
|
||||||
|
pub mod status;
|
||||||
|
|
||||||
|
// Local dependencies
|
||||||
|
use redirect::Redirect;
|
||||||
|
pub use status::Status;
|
||||||
|
|
||||||
|
// Global dependencies
|
||||||
|
use super::{feature, response, Feature, Response};
|
||||||
|
use crate::{tool::now, Profile};
|
||||||
|
use gtk::{
|
||||||
|
gio::{Cancellable, SocketClientEvent},
|
||||||
|
glib::{gformat, Priority, Uri},
|
||||||
|
prelude::SocketClientExt,
|
||||||
|
};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub struct Driver {
|
||||||
|
/// Profile reference required for Gemini protocol auth (match request)
|
||||||
|
profile: Rc<Profile>,
|
||||||
|
/// Redirect resolver for different protocols
|
||||||
|
redirect: Rc<Redirect>,
|
||||||
|
/// Supported clients
|
||||||
|
gemini: gemini::Client,
|
||||||
|
// other clients here..
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Driver {
|
||||||
|
pub fn init(profile: &Rc<Profile>, callback: impl Fn(Status) + 'static) -> Self {
|
||||||
|
// Init protocol driver libraries
|
||||||
|
let gemini = gemini::Client::new();
|
||||||
|
|
||||||
|
// Translate driver status to `Status`
|
||||||
|
|
||||||
|
// Gemini
|
||||||
|
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: profile.clone(),
|
||||||
|
redirect: Rc::new(Redirect::new()),
|
||||||
|
gemini,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn feature_async(
|
||||||
|
&self,
|
||||||
|
feature: Feature,
|
||||||
|
cancellable: Cancellable,
|
||||||
|
callback: Rc<impl Fn(Response) + 'static>,
|
||||||
|
) {
|
||||||
|
match feature {
|
||||||
|
Feature::Download { request } => match request {
|
||||||
|
feature::Request::Gemini { uri } => {
|
||||||
|
request_gemini_async(self, uri.clone(), cancellable.clone(), move |result| {
|
||||||
|
match result {
|
||||||
|
Ok(response) => callback(Response::Download {
|
||||||
|
base: uri.clone(),
|
||||||
|
stream: response.connection.stream(),
|
||||||
|
}),
|
||||||
|
Err(e) => callback(Response::Failure(response::Failure::Error {
|
||||||
|
message: e.to_string(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
},
|
||||||
|
Feature::Default { request } => match request {
|
||||||
|
feature::Request::Gemini { uri } => {
|
||||||
|
request_gemini_async(self, uri.clone(), cancellable.clone(), move |result| {
|
||||||
|
handle_gemini(
|
||||||
|
result,
|
||||||
|
uri.clone(),
|
||||||
|
cancellable.clone(),
|
||||||
|
false,
|
||||||
|
callback.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
feature::Request::Titan { .. } => todo!(),
|
||||||
|
feature::Request::Undefined => todo!(),
|
||||||
|
},
|
||||||
|
Feature::Source { request } => match request {
|
||||||
|
feature::Request::Gemini { uri } => {
|
||||||
|
request_gemini_async(self, uri.clone(), cancellable.clone(), move |result| {
|
||||||
|
handle_gemini(
|
||||||
|
result,
|
||||||
|
uri.clone(),
|
||||||
|
cancellable.clone(),
|
||||||
|
true,
|
||||||
|
callback.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
feature::Request::Titan { .. } => todo!(),
|
||||||
|
feature::Request::Undefined => todo!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared request interface for Gemini protocol
|
||||||
|
fn request_gemini_async(
|
||||||
|
driver: &Driver,
|
||||||
|
uri: Uri,
|
||||||
|
cancellable: Cancellable,
|
||||||
|
callback: impl Fn(Result<gemini::client::Response, gemini::client::Error>) + 'static,
|
||||||
|
) {
|
||||||
|
driver.gemini.request_async(
|
||||||
|
gemini::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 driver.profile.identity.gemini.match_scope(&uri.to_string()) {
|
||||||
|
Some(identity) => match identity.to_tls_certificate() {
|
||||||
|
Ok(certificate) => Some(certificate),
|
||||||
|
Err(_) => todo!(),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
move |result| callback(result),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared handler for Gemini `Result`
|
||||||
|
/// * same implementation for Gemini and Titan protocols response
|
||||||
|
fn handle_gemini(
|
||||||
|
result: Result<gemini::client::connection::Response, gemini::client::Error>,
|
||||||
|
base: Uri,
|
||||||
|
cancellable: Cancellable,
|
||||||
|
is_source_request: bool, // @TODO yet partial implementation
|
||||||
|
callback: Rc<impl Fn(Response) + 'static>,
|
||||||
|
) {
|
||||||
|
use gemini::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(response::Input::Response {
|
||||||
|
base,
|
||||||
|
title: match response.meta.data {
|
||||||
|
Some(data) => data.value,
|
||||||
|
None => gformat!("Input expected"),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
Status::SensitiveInput => callback(Response::Input(response::Input::Sensitive {
|
||||||
|
base,
|
||||||
|
title: match response.meta.data {
|
||||||
|
Some(data) => data.value,
|
||||||
|
None => gformat!("Input expected"),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
|
||||||
|
Status::Success => {
|
||||||
|
let mime = response.meta.mime.unwrap().value.to_lowercase();
|
||||||
|
match mime.as_str() {
|
||||||
|
"text/gemini" => Text::from_stream_async(
|
||||||
|
response.connection.stream(),
|
||||||
|
Priority::DEFAULT,
|
||||||
|
cancellable,
|
||||||
|
move |result| match result {
|
||||||
|
Ok(text) => callback(Response::Gemtext {
|
||||||
|
base,
|
||||||
|
source: text.data,
|
||||||
|
is_source_request,
|
||||||
|
}),
|
||||||
|
Err(_) => todo!(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"image/png" | "image/gif" | "image/jpeg" | "image/webp" => {
|
||||||
|
callback(Response::Stream {
|
||||||
|
base,
|
||||||
|
mime,
|
||||||
|
stream: response.connection.stream(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
mime => callback(Response::Failure(response::Failure::Mime {
|
||||||
|
message: format!("Undefined content type `{mime}`"),
|
||||||
|
})),
|
||||||
|
} // @TODO handle `None`
|
||||||
|
}
|
||||||
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-30-temporary-redirection
|
||||||
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-31-permanent-redirection
|
||||||
|
Status::Redirect | Status::PermanentRedirect => todo!(),
|
||||||
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-60
|
||||||
|
Status::CertificateRequest => {
|
||||||
|
callback(Response::Certificate(response::Certificate::Request {
|
||||||
|
title: match response.meta.data {
|
||||||
|
Some(data) => data.value,
|
||||||
|
None => gformat!("Client certificate required"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-61-certificate-not-authorized
|
||||||
|
Status::CertificateUnauthorized => {
|
||||||
|
callback(Response::Certificate(response::Certificate::Request {
|
||||||
|
title: match response.meta.data {
|
||||||
|
Some(data) => data.value,
|
||||||
|
None => gformat!("Certificate not authorized"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-62-certificate-not-valid
|
||||||
|
Status::CertificateInvalid => {
|
||||||
|
callback(Response::Certificate(response::Certificate::Request {
|
||||||
|
title: match response.meta.data {
|
||||||
|
Some(data) => data.value,
|
||||||
|
None => gformat!("Certificate not valid"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
status => callback(Response::Failure(response::Failure::Status {
|
||||||
|
message: format!("Undefined status code `{:?}`", status), // @TODO implement display trait for `ggemini` lib
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
Err(e) => callback(Response::Failure(response::Failure::Error {
|
||||||
|
message: e.to_string(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
51
src/app/browser/window/tab/item/page/client/driver/status.rs
Normal file
51
src/app/browser/window/tab/item/page/client/driver/status.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Global dependencies
|
||||||
|
use crate::tool::format_time;
|
||||||
|
use gtk::glib::DateTime;
|
||||||
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
/// Shared asset for `Driver` statuses
|
||||||
|
pub enum Status {
|
||||||
|
Resolving { time: DateTime },
|
||||||
|
Resolved { time: DateTime },
|
||||||
|
Connecting { time: DateTime },
|
||||||
|
Connected { time: DateTime },
|
||||||
|
ProxyNegotiating { time: DateTime },
|
||||||
|
ProxyNegotiated { time: DateTime },
|
||||||
|
TlsHandshaking { time: DateTime },
|
||||||
|
TlsHandshaked { time: DateTime },
|
||||||
|
Complete { time: DateTime },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Status {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
|
match self {
|
||||||
|
Self::Resolving { time } => {
|
||||||
|
write!(f, "[{}] Resolving", format_time(time))
|
||||||
|
}
|
||||||
|
Self::Resolved { time } => {
|
||||||
|
write!(f, "[{}] Resolved", format_time(time))
|
||||||
|
}
|
||||||
|
Self::Connecting { time } => {
|
||||||
|
write!(f, "[{}] Connecting", format_time(time))
|
||||||
|
}
|
||||||
|
Self::Connected { time } => {
|
||||||
|
write!(f, "[{}] Connected", format_time(time))
|
||||||
|
}
|
||||||
|
Self::ProxyNegotiating { time } => {
|
||||||
|
write!(f, "[{}] Proxy negotiating", format_time(time))
|
||||||
|
}
|
||||||
|
Self::ProxyNegotiated { time } => {
|
||||||
|
write!(f, "[{}] Proxy negotiated", format_time(time))
|
||||||
|
}
|
||||||
|
Self::TlsHandshaking { time } => {
|
||||||
|
write!(f, "[{}] TLS handshaking", format_time(time))
|
||||||
|
}
|
||||||
|
Self::TlsHandshaked { time } => {
|
||||||
|
write!(f, "[{}] TLS handshaked", format_time(time))
|
||||||
|
}
|
||||||
|
Self::Complete { time } => {
|
||||||
|
write!(f, "[{}] Completed", format_time(time))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,7 @@
|
|||||||
//! Feature components in development,
|
pub mod request;
|
||||||
//! this asset initiated as the attempt to reduce current `Page` code size
|
pub use request::Request;
|
||||||
//! and delegate different protocol features to specified drivers under this location with itself implementation
|
|
||||||
// @TODO cleanup this message on complete
|
|
||||||
|
|
||||||
mod request;
|
/// Feature wrapper for client `Request`
|
||||||
|
|
||||||
// Local dependencies
|
|
||||||
use request::Request;
|
|
||||||
|
|
||||||
/// Features route for `Client`
|
|
||||||
pub enum Feature {
|
pub enum Feature {
|
||||||
/// Common feature for protocol selected (e.g. browser view)
|
/// Common feature for protocol selected (e.g. browser view)
|
||||||
Default { request: Request },
|
Default { request: Request },
|
||||||
@ -22,21 +15,21 @@ impl Feature {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Parse new `Self` from string
|
/// Parse new `Self` from string
|
||||||
pub fn from_string(request: &str) -> Self {
|
pub fn from_string(query: &str) -> Self {
|
||||||
if let Some(postfix) = request.strip_prefix("download:") {
|
if let Some(postfix) = query.strip_prefix("download:") {
|
||||||
return Self::Download {
|
return Self::Download {
|
||||||
request: Request::from_string(postfix),
|
request: Request::from_string(postfix),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(postfix) = request.strip_prefix("source:") {
|
if let Some(postfix) = query.strip_prefix("source:") {
|
||||||
return Self::Source {
|
return Self::Source {
|
||||||
request: Request::from_string(postfix),
|
request: Request::from_string(postfix),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::Default {
|
Self::Default {
|
||||||
request: Request::from_string(request),
|
request: Request::from_string(query),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,15 +27,4 @@ impl Request {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
|
||||||
|
|
||||||
/// Send request using protocol driver constructed
|
|
||||||
pub fn send(&self) {
|
|
||||||
match self {
|
|
||||||
Request::Gemini { uri } => todo!("{uri}"),
|
|
||||||
Request::Titan { uri } => todo!("{uri}"),
|
|
||||||
Request::Undefined => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
36
src/app/browser/window/tab/item/page/client/response.rs
Normal file
36
src/app/browser/window/tab/item/page/client/response.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
pub mod certificate;
|
||||||
|
pub mod failure;
|
||||||
|
pub mod input;
|
||||||
|
|
||||||
|
pub use certificate::Certificate;
|
||||||
|
pub use failure::Failure;
|
||||||
|
pub use input::Input;
|
||||||
|
|
||||||
|
use gtk::{
|
||||||
|
gio::IOStream,
|
||||||
|
glib::{GString, Uri},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum Response {
|
||||||
|
Certificate(Certificate),
|
||||||
|
Download {
|
||||||
|
base: Uri,
|
||||||
|
stream: IOStream,
|
||||||
|
},
|
||||||
|
Failure(Failure),
|
||||||
|
Gemtext {
|
||||||
|
base: Uri,
|
||||||
|
source: GString,
|
||||||
|
is_source_request: bool,
|
||||||
|
},
|
||||||
|
Input(Input),
|
||||||
|
Redirect {
|
||||||
|
request: Uri,
|
||||||
|
is_foreground: bool,
|
||||||
|
},
|
||||||
|
Stream {
|
||||||
|
base: Uri,
|
||||||
|
mime: String,
|
||||||
|
stream: IOStream,
|
||||||
|
},
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
use gtk::glib::GString;
|
||||||
|
|
||||||
|
pub enum Certificate {
|
||||||
|
Invalid { title: GString },
|
||||||
|
Request { title: GString },
|
||||||
|
Unauthorized { title: GString },
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
pub enum Failure {
|
||||||
|
Status { message: String },
|
||||||
|
Mime { message: String },
|
||||||
|
Error { message: String },
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
use gtk::glib::{GString, Uri};
|
||||||
|
|
||||||
|
pub enum Input {
|
||||||
|
Response { base: Uri, title: GString },
|
||||||
|
Sensitive { base: Uri, title: GString },
|
||||||
|
Titan { base: Uri },
|
||||||
|
}
|
@ -1,88 +1,54 @@
|
|||||||
mod failure;
|
pub mod failure;
|
||||||
|
|
||||||
// Children dependencies
|
// Children dependencies
|
||||||
use failure::Failure;
|
pub use failure::Failure;
|
||||||
|
|
||||||
// Global dependencies
|
// Global dependencies
|
||||||
use gtk::glib::{DateTime, GString};
|
use crate::tool::format_time;
|
||||||
|
use gtk::glib::DateTime;
|
||||||
use std::fmt::{Display, Formatter, Result};
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
/// Local `Client` status
|
/// Local `Client` status
|
||||||
/// * not same as the Gemini status!
|
/// * not same as the Gemini status!
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
/// Ready to use (or cancel from outside)
|
/// Ready to use (or cancel from outside)
|
||||||
Cancellable { event: DateTime },
|
Cancellable { time: DateTime },
|
||||||
/// Operation cancelled, new `Cancellable` required to continue
|
/// Operation cancelled, new `Cancellable` required to continue
|
||||||
Cancelled { event: DateTime },
|
Cancelled { time: DateTime },
|
||||||
|
/// Protocol driver updates
|
||||||
|
Driver(super::driver::Status),
|
||||||
/// Something went wrong
|
/// Something went wrong
|
||||||
Failure { event: DateTime, failure: Failure },
|
Failure { time: DateTime, failure: Failure },
|
||||||
/// New `request` begin
|
/// New `request` begin
|
||||||
Request { event: DateTime, value: String },
|
Request { time: DateTime, value: String },
|
||||||
}
|
|
||||||
|
|
||||||
impl Status {
|
|
||||||
// Constructors
|
|
||||||
|
|
||||||
/// Create new `Self::Cancellable`
|
|
||||||
pub fn cancellable() -> Self {
|
|
||||||
Self::Cancellable { event: now() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create new `Self::Cancelled`
|
|
||||||
pub fn cancelled() -> Self {
|
|
||||||
Self::Cancelled { event: now() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create new `Self::Failure` as `Failure::RedirectCount`
|
|
||||||
pub fn failure_redirect_limit(count: usize, is_global: bool) -> Self {
|
|
||||||
Self::Failure {
|
|
||||||
event: now(),
|
|
||||||
failure: Failure::redirect_count(count, is_global),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create new `Self::Request`
|
|
||||||
pub fn request(value: String) -> Self {
|
|
||||||
Self::Request {
|
|
||||||
event: now(),
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Status {
|
impl Display for Status {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Cancellable { event } => {
|
Self::Cancellable { time } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"[{}] Ready to use (or cancel from outside)",
|
"[{}] Ready to use (or cancel from outside)",
|
||||||
format_time(event)
|
format_time(time)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Self::Cancelled { event } => {
|
Self::Cancelled { time } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"[{}] Operation cancelled, new `Cancellable` required to continue",
|
"[{}] Operation cancelled, new `Cancellable` required to continue",
|
||||||
format_time(event)
|
format_time(time)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Self::Failure { event, failure } => {
|
Self::Driver(status) => {
|
||||||
write!(f, "[{}] Failure: {failure}", format_time(event))
|
write!(f, "{status}")
|
||||||
}
|
}
|
||||||
Self::Request { event, value } => {
|
Self::Failure { time, failure } => {
|
||||||
write!(f, "[{}] Request `{value}`...", format_time(event))
|
write!(f, "[{}] Failure: {failure}", format_time(time))
|
||||||
|
}
|
||||||
|
Self::Request { time, value } => {
|
||||||
|
write!(f, "[{}] Request `{value}`...", format_time(time))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format given [DateTime](https://docs.gtk.org/glib/struct.DateTime.html)
|
|
||||||
fn format_time(t: &DateTime) -> GString {
|
|
||||||
t.format_iso8601().unwrap() // @TODO handle?
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get current [DateTime](https://docs.gtk.org/glib/struct.DateTime.html)
|
|
||||||
fn now() -> DateTime {
|
|
||||||
DateTime::now_local().unwrap() // @TODO handle?
|
|
||||||
}
|
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
use gtk::glib::{GString, Uri, UriFlags};
|
|
||||||
|
|
||||||
/// Page type for `Page` with optional value parsed
|
|
||||||
pub enum Mode {
|
|
||||||
Default(Uri),
|
|
||||||
Download(Uri),
|
|
||||||
Source(Uri),
|
|
||||||
Search(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mode {
|
|
||||||
// Constructors
|
|
||||||
|
|
||||||
/// Create new `Self` from `request` string
|
|
||||||
/// * if some `referrer` given, make additional check in previous request
|
|
||||||
pub fn from(request: &str, referrer: Option<&GString>) -> Self {
|
|
||||||
// check in request
|
|
||||||
if let Some(postfix) = request.strip_prefix("source:") {
|
|
||||||
if let Ok(uri) = Uri::parse(postfix, UriFlags::NONE) {
|
|
||||||
return Self::Source(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(postfix) = request.strip_prefix("download:") {
|
|
||||||
if let Ok(uri) = Uri::parse(postfix, UriFlags::NONE) {
|
|
||||||
return Self::Download(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check in referrer @TODO tmp
|
|
||||||
if referrer.is_some_and(|this| this.starts_with("source:")) {
|
|
||||||
if let Ok(uri) = Uri::parse(request, UriFlags::NONE) {
|
|
||||||
return Self::Source(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if referrer.is_some_and(|this| this.starts_with("download:")) {
|
|
||||||
if let Ok(uri) = Uri::parse(request, UriFlags::NONE) {
|
|
||||||
return Self::Download(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// is default
|
|
||||||
if let Ok(uri) = Uri::parse(request, UriFlags::NONE) {
|
|
||||||
return Self::Default(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
// is search
|
|
||||||
Self::Search(request.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +1,50 @@
|
|||||||
|
/// Global dependencies
|
||||||
|
use super::client::{driver::Status as Driver, Status as Client};
|
||||||
|
use gtk::glib::DateTime;
|
||||||
|
|
||||||
/// `Page` status
|
/// `Page` status
|
||||||
/// * not same as the Gemini status!
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
Complete,
|
Client(Client),
|
||||||
Connected,
|
Failure { time: DateTime },
|
||||||
Connecting,
|
Input { time: DateTime },
|
||||||
Failure,
|
Loading { time: DateTime },
|
||||||
Input,
|
New { time: DateTime },
|
||||||
New,
|
Redirect { time: DateTime },
|
||||||
ProxyNegotiated,
|
SessionRestore { time: DateTime },
|
||||||
ProxyNegotiating,
|
SessionRestored { time: DateTime },
|
||||||
Redirect,
|
Success { time: DateTime },
|
||||||
Reload,
|
}
|
||||||
Resolved,
|
|
||||||
Resolving,
|
impl Status {
|
||||||
SessionRestore,
|
// Getters
|
||||||
SessionRestored,
|
|
||||||
Success,
|
/// Translate `Self` to `progress-fraction` presentation
|
||||||
TlsHandshaked,
|
/// * see also: [Entry](https://docs.gtk.org/gtk4/property.Entry.progress-fraction.html)
|
||||||
TlsHandshaking,
|
pub fn to_progress_fraction(&self) -> Option<f64> {
|
||||||
|
match self {
|
||||||
|
Self::Loading { .. } | Self::SessionRestore { .. } => Some(0.0),
|
||||||
|
Self::Client(status) => match status {
|
||||||
|
Client::Cancellable { .. }
|
||||||
|
| 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),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Self::Failure { .. }
|
||||||
|
| Self::Success { .. }
|
||||||
|
| Self::Redirect { .. }
|
||||||
|
| Self::Input { .. } => Some(1.0),
|
||||||
|
Self::New { .. } | Self::SessionRestored { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
24
src/tool.rs
24
src/tool.rs
@ -1,5 +1,8 @@
|
|||||||
//! Some shared helpers collection
|
//! Some shared helpers collection
|
||||||
|
|
||||||
|
// Global dependencies
|
||||||
|
use gtk::glib::{DateTime, GString, Uri};
|
||||||
|
|
||||||
/// Format bytes to KB/MB/GB presentation
|
/// Format bytes to KB/MB/GB presentation
|
||||||
pub fn format_bytes(value: usize) -> String {
|
pub fn format_bytes(value: usize) -> String {
|
||||||
const KB: f32 = 1024.0;
|
const KB: f32 = 1024.0;
|
||||||
@ -21,3 +24,24 @@ pub fn format_bytes(value: usize) -> String {
|
|||||||
format!("{:.2} GB", f / GB)
|
format!("{:.2} GB", f / GB)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format given [DateTime](https://docs.gtk.org/glib/struct.DateTime.html)
|
||||||
|
pub fn format_time(t: &DateTime) -> GString {
|
||||||
|
t.format_iso8601().unwrap() // @TODO handle?
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get current [DateTime](https://docs.gtk.org/glib/struct.DateTime.html)
|
||||||
|
pub fn now() -> DateTime {
|
||||||
|
DateTime::now_local().unwrap() // @TODO handle?
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare `subject` with `base`
|
||||||
|
pub fn _is_external(subject: &Uri, base: &Uri) -> bool {
|
||||||
|
if subject.scheme() != base.scheme() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if subject.port() != base.port() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
subject.host() != base.host()
|
||||||
|
} // @TODO not in use
|
||||||
|
Loading…
x
Reference in New Issue
Block a user