mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-04-01 00:55:28 +00:00
160 lines
4.5 KiB
Rust
160 lines
4.5 KiB
Rust
mod error;
|
|
mod feature;
|
|
mod gemini;
|
|
mod search;
|
|
|
|
use super::{Client, Response};
|
|
pub use error::Error;
|
|
use feature::Feature;
|
|
use gtk::{
|
|
gio::Cancellable,
|
|
glib::{Uri, UriFlags},
|
|
};
|
|
|
|
/// Single `Request` API for multiple `Client` drivers
|
|
pub enum Request {
|
|
Gemini {
|
|
feature: Feature,
|
|
referrer: Option<Box<Self>>,
|
|
uri: Uri,
|
|
},
|
|
Titan {
|
|
referrer: Option<Box<Self>>,
|
|
uri: Uri,
|
|
},
|
|
}
|
|
|
|
impl Request {
|
|
// Constructors
|
|
|
|
/// Create new `Self` from featured string
|
|
pub fn parse(query: &str, referrer: Option<Self>) -> Result<Self, Error> {
|
|
let (feature, request) = Feature::parse(query);
|
|
|
|
match Uri::parse(request, UriFlags::NONE) {
|
|
Ok(uri) => Self::from_uri(uri, Some(feature), referrer),
|
|
Err(e) => Err(Error::Glib(e)),
|
|
}
|
|
}
|
|
|
|
/// Create new `Self` from [Uri](https://docs.gtk.org/glib/struct.Uri.html)
|
|
pub fn from_uri(
|
|
uri: Uri,
|
|
feature: Option<Feature>,
|
|
referrer: Option<Self>,
|
|
) -> Result<Self, Error> {
|
|
match uri.scheme().as_str() {
|
|
"gemini" => Ok(Self::Gemini {
|
|
feature: feature.unwrap_or_default(),
|
|
referrer: referrer.map(Box::new),
|
|
uri,
|
|
}), // @TODO validate request len by constructor
|
|
"titan" => Ok(Self::Titan {
|
|
referrer: referrer.map(Box::new),
|
|
uri,
|
|
}),
|
|
_ => Err(Error::Unsupported),
|
|
}
|
|
}
|
|
|
|
// Actions
|
|
|
|
/// Handle `Self` request
|
|
pub fn handle(
|
|
self,
|
|
client: &Client,
|
|
cancellable: Cancellable,
|
|
callback: impl FnOnce(Response) + 'static,
|
|
) {
|
|
match &self {
|
|
Self::Gemini { .. } => gemini::request(client, self, cancellable, callback),
|
|
Self::Titan { .. } => todo!(),
|
|
}
|
|
}
|
|
|
|
// Getters
|
|
|
|
/// 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 { referrer: _, uri } => uri,
|
|
}
|
|
}
|
|
|
|
/// Get `Feature` reference for `Self`
|
|
pub fn feature(&self) -> &Feature {
|
|
match self {
|
|
Request::Gemini { feature, .. } => feature,
|
|
Request::Titan { .. } => &Feature::Default,
|
|
}
|
|
}
|
|
|
|
/// Recursively count referrers of `Self`
|
|
/// * useful to apply redirection rules by protocol driver selected
|
|
pub fn referrers(&self) -> usize {
|
|
let count = match self {
|
|
Request::Gemini { referrer, .. } => referrer,
|
|
Request::Titan { referrer, .. } => referrer,
|
|
}
|
|
.as_ref()
|
|
.map_or(0, |request| request.referrers());
|
|
1 + count
|
|
}
|
|
}
|
|
|
|
// Tools
|
|
|
|
/// Create new search `Request`
|
|
/// @TODO
|
|
// * implement DNS lookup before apply this option
|
|
// * make search provider optional
|
|
// * validate request len by gemini specifications
|
|
pub fn search(query: &str) -> Request {
|
|
Request::from_uri(search::tgls(query), None, None).unwrap() // no handler as unexpected
|
|
}
|
|
|
|
/// Asynchronously check request string contain resolvable query
|
|
/// * useful for scheme-less requests, before do search redirect
|
|
pub fn lookup(
|
|
query: &str,
|
|
cancellable: Option<&Cancellable>,
|
|
callback: impl FnOnce(Result<Request, Error>) + 'static,
|
|
) {
|
|
use gtk::{
|
|
gio::{NetworkAddress, Resolver},
|
|
prelude::{NetworkAddressExt, ResolverExt},
|
|
};
|
|
|
|
const DEFAULT_SCHEME: &str = "gemini://";
|
|
const DEFAULT_PORT: u16 = 1965;
|
|
const TIMEOUT: u32 = 1000;
|
|
|
|
let request = match query.trim().strip_prefix(DEFAULT_SCHEME) {
|
|
Some(postfix) => format!("{DEFAULT_SCHEME}{postfix}"),
|
|
None => query.to_string(),
|
|
};
|
|
|
|
let resolver = Resolver::default();
|
|
resolver.set_timeout(TIMEOUT);
|
|
|
|
match NetworkAddress::parse_uri(&request, DEFAULT_PORT) {
|
|
Ok(connectable) => {
|
|
resolver.lookup_by_name_async(&connectable.hostname(), cancellable, move |resolve| {
|
|
callback(if resolve.is_ok() {
|
|
match Uri::parse(&request, UriFlags::NONE) {
|
|
Ok(uri) => Request::from_uri(uri, None, None),
|
|
Err(e) => Err(Error::Glib(e)),
|
|
}
|
|
} else {
|
|
Ok(search(&request))
|
|
})
|
|
})
|
|
}
|
|
Err(_) => callback(Ok(search(&request))), // @TODO not completed yet, fix invalid URI issue
|
|
}
|
|
}
|