move static driver members to separated gemini mod, rename ggemini, ggemtext dependencies

This commit is contained in:
yggverse 2025-01-16 23:39:46 +02:00
parent d3dfd6fb60
commit 786286dc3e
5 changed files with 140 additions and 140 deletions

View File

@ -15,14 +15,6 @@ package = "libadwaita"
version = "0.7.0"
features = ["v1_5"]
[dependencies.gemini]
package = "ggemini"
version = "0.14.0"
[dependencies.gemtext]
package = "ggemtext"
version = "0.3.0"
[dependencies.gtk]
package = "gtk4"
version = "0.9.4"
@ -38,6 +30,8 @@ version = "0.9.1"
[dependencies]
ansi-parser = "0.9.1"
ggemini = "0.14.0"
ggemtext = "0.3.0"
indexmap = "2.7.0"
itertools = "0.14.0"
libspelling = "0.3.0"

View File

@ -349,7 +349,7 @@ impl Page {
// to local [MemoryInputStream](https://docs.gtk.org/gio/class.MemoryInputStream.html)
// show bytes count in loading widget, validate max size for incoming data
// * no dependency of Gemini library here, feel free to use any other `IOStream` processor
gemini::gio::file_output_stream::move_all_from_stream_async(
ggemini::gio::file_output_stream::move_all_from_stream_async(
stream.clone(),
file_output_stream,
cancellable.clone(),
@ -408,7 +408,7 @@ impl Page {
// to local [MemoryInputStream](https://docs.gtk.org/gio/class.MemoryInputStream.html)
// show bytes count in loading widget, validate max size for incoming data
// * no dependency of Gemini library here, feel free to use any other `IOStream` processor
gemini::gio::memory_input_stream::from_stream_async(
ggemini::gio::memory_input_stream::from_stream_async(
stream,
cancellable.clone(),
Priority::DEFAULT,

View File

@ -1,6 +1,7 @@
//! At this moment, the `Driver` contain only one protocol library,
//! by extending it features with new protocol, please make sub-module implementation
mod gemini;
mod redirect;
pub mod status;
@ -13,7 +14,6 @@ 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;
@ -24,14 +24,14 @@ pub struct Driver {
/// Redirect resolver for different protocols
redirect: Rc<Redirect>,
/// Supported clients
gemini: gemini::Client,
gemini: ggemini::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();
let gemini = ggemini::Client::new();
// Translate driver status to `Status`
@ -71,7 +71,7 @@ impl Driver {
match feature {
Feature::Download { request } => match request {
feature::Request::Gemini { uri } => {
request_gemini_async(self, uri.clone(), cancellable.clone(), move |result| {
gemini::request_async(self, uri.clone(), cancellable.clone(), move |result| {
match result {
Ok(response) => callback(Response::Download {
base: uri.clone(),
@ -88,8 +88,8 @@ impl Driver {
},
Feature::Default { request } => match request {
feature::Request::Gemini { uri } => {
request_gemini_async(self, uri.clone(), cancellable.clone(), move |result| {
handle_gemini(
gemini::request_async(self, uri.clone(), cancellable.clone(), move |result| {
gemini::handle(
result,
uri.clone(),
cancellable.clone(),
@ -103,8 +103,8 @@ impl Driver {
},
Feature::Source { request } => match request {
feature::Request::Gemini { uri } => {
request_gemini_async(self, uri.clone(), cancellable.clone(), move |result| {
handle_gemini(
gemini::request_async(self, uri.clone(), cancellable.clone(), move |result| {
gemini::handle(
result,
uri.clone(),
cancellable.clone(),
@ -119,124 +119,3 @@ impl Driver {
}
}
}
/// 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::TextGemini {
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(),
cancellable,
})
}
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(),
})),
}
}

View File

@ -0,0 +1,127 @@
use super::{response, Driver, Response};
use gtk::{
gio::Cancellable,
glib::{gformat, Priority, Uri},
};
use std::rc::Rc;
/// Shared request interface for Gemini protocol
pub fn request_async(
driver: &Driver,
uri: Uri,
cancellable: Cancellable,
callback: impl Fn(Result<ggemini::client::Response, ggemini::client::Error>) + 'static,
) {
driver.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 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
pub fn handle(
result: Result<ggemini::client::connection::Response, ggemini::client::Error>,
base: Uri,
cancellable: Cancellable,
is_source_request: bool, // @TODO yet partial implementation
callback: Rc<impl Fn(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(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::TextGemini {
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(),
cancellable,
})
}
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(),
})),
}
}

View File

@ -11,7 +11,7 @@ use widget::Widget;
use super::{TabAction, WindowAction};
use crate::app::browser::window::action::Position;
use gemtext::line::{
use ggemtext::line::{
code::{Inline, Multiline},
header::{Header, Level},
link::Link,