mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-02-02 22:44:13 +00:00
move static driver members to separated gemini mod, rename ggemini, ggemtext dependencies
This commit is contained in:
parent
d3dfd6fb60
commit
786286dc3e
10
Cargo.toml
10
Cargo.toml
@ -15,14 +15,6 @@ package = "libadwaita"
|
|||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
features = ["v1_5"]
|
features = ["v1_5"]
|
||||||
|
|
||||||
[dependencies.gemini]
|
|
||||||
package = "ggemini"
|
|
||||||
version = "0.14.0"
|
|
||||||
|
|
||||||
[dependencies.gemtext]
|
|
||||||
package = "ggemtext"
|
|
||||||
version = "0.3.0"
|
|
||||||
|
|
||||||
[dependencies.gtk]
|
[dependencies.gtk]
|
||||||
package = "gtk4"
|
package = "gtk4"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@ -38,6 +30,8 @@ version = "0.9.1"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ansi-parser = "0.9.1"
|
ansi-parser = "0.9.1"
|
||||||
|
ggemini = "0.14.0"
|
||||||
|
ggemtext = "0.3.0"
|
||||||
indexmap = "2.7.0"
|
indexmap = "2.7.0"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
libspelling = "0.3.0"
|
libspelling = "0.3.0"
|
||||||
|
@ -349,7 +349,7 @@ impl Page {
|
|||||||
// to local [MemoryInputStream](https://docs.gtk.org/gio/class.MemoryInputStream.html)
|
// to local [MemoryInputStream](https://docs.gtk.org/gio/class.MemoryInputStream.html)
|
||||||
// show bytes count in loading widget, validate max size for incoming data
|
// 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
|
// * 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(),
|
stream.clone(),
|
||||||
file_output_stream,
|
file_output_stream,
|
||||||
cancellable.clone(),
|
cancellable.clone(),
|
||||||
@ -408,7 +408,7 @@ impl Page {
|
|||||||
// to local [MemoryInputStream](https://docs.gtk.org/gio/class.MemoryInputStream.html)
|
// to local [MemoryInputStream](https://docs.gtk.org/gio/class.MemoryInputStream.html)
|
||||||
// show bytes count in loading widget, validate max size for incoming data
|
// 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
|
// * 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,
|
stream,
|
||||||
cancellable.clone(),
|
cancellable.clone(),
|
||||||
Priority::DEFAULT,
|
Priority::DEFAULT,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! At this moment, the `Driver` contain only one protocol library,
|
//! At this moment, the `Driver` contain only one protocol library,
|
||||||
//! by extending it features with new protocol, please make sub-module implementation
|
//! by extending it features with new protocol, please make sub-module implementation
|
||||||
|
|
||||||
|
mod gemini;
|
||||||
mod redirect;
|
mod redirect;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
@ -13,7 +14,6 @@ use super::{feature, response, Feature, Response};
|
|||||||
use crate::{tool::now, Profile};
|
use crate::{tool::now, Profile};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
gio::{Cancellable, SocketClientEvent},
|
gio::{Cancellable, SocketClientEvent},
|
||||||
glib::{gformat, Priority, Uri},
|
|
||||||
prelude::SocketClientExt,
|
prelude::SocketClientExt,
|
||||||
};
|
};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@ -24,14 +24,14 @@ pub struct Driver {
|
|||||||
/// Redirect resolver for different protocols
|
/// Redirect resolver for different protocols
|
||||||
redirect: Rc<Redirect>,
|
redirect: Rc<Redirect>,
|
||||||
/// Supported clients
|
/// Supported clients
|
||||||
gemini: gemini::Client,
|
gemini: ggemini::Client,
|
||||||
// other clients here..
|
// other clients here..
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Driver {
|
impl Driver {
|
||||||
pub fn init(profile: &Rc<Profile>, callback: impl Fn(Status) + 'static) -> Self {
|
pub fn init(profile: &Rc<Profile>, callback: impl Fn(Status) + 'static) -> Self {
|
||||||
// Init protocol driver libraries
|
// Init protocol driver libraries
|
||||||
let gemini = gemini::Client::new();
|
let gemini = ggemini::Client::new();
|
||||||
|
|
||||||
// Translate driver status to `Status`
|
// Translate driver status to `Status`
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ impl Driver {
|
|||||||
match feature {
|
match feature {
|
||||||
Feature::Download { request } => match request {
|
Feature::Download { request } => match request {
|
||||||
feature::Request::Gemini { uri } => {
|
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 {
|
match result {
|
||||||
Ok(response) => callback(Response::Download {
|
Ok(response) => callback(Response::Download {
|
||||||
base: uri.clone(),
|
base: uri.clone(),
|
||||||
@ -88,8 +88,8 @@ impl Driver {
|
|||||||
},
|
},
|
||||||
Feature::Default { request } => match request {
|
Feature::Default { request } => match request {
|
||||||
feature::Request::Gemini { uri } => {
|
feature::Request::Gemini { uri } => {
|
||||||
request_gemini_async(self, uri.clone(), cancellable.clone(), move |result| {
|
gemini::request_async(self, uri.clone(), cancellable.clone(), move |result| {
|
||||||
handle_gemini(
|
gemini::handle(
|
||||||
result,
|
result,
|
||||||
uri.clone(),
|
uri.clone(),
|
||||||
cancellable.clone(),
|
cancellable.clone(),
|
||||||
@ -103,8 +103,8 @@ impl Driver {
|
|||||||
},
|
},
|
||||||
Feature::Source { request } => match request {
|
Feature::Source { request } => match request {
|
||||||
feature::Request::Gemini { uri } => {
|
feature::Request::Gemini { uri } => {
|
||||||
request_gemini_async(self, uri.clone(), cancellable.clone(), move |result| {
|
gemini::request_async(self, uri.clone(), cancellable.clone(), move |result| {
|
||||||
handle_gemini(
|
gemini::handle(
|
||||||
result,
|
result,
|
||||||
uri.clone(),
|
uri.clone(),
|
||||||
cancellable.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(),
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
127
src/app/browser/window/tab/item/page/client/driver/gemini.rs
Normal file
127
src/app/browser/window/tab/item/page/client/driver/gemini.rs
Normal 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(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ use widget::Widget;
|
|||||||
|
|
||||||
use super::{TabAction, WindowAction};
|
use super::{TabAction, WindowAction};
|
||||||
use crate::app::browser::window::action::Position;
|
use crate::app::browser::window::action::Position;
|
||||||
use gemtext::line::{
|
use ggemtext::line::{
|
||||||
code::{Inline, Multiline},
|
code::{Inline, Multiline},
|
||||||
header::{Header, Level},
|
header::{Header, Level},
|
||||||
link::Link,
|
link::Link,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user