mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-01-15 17:20:08 +00:00
draft ggemini 0.11.0 version api
This commit is contained in:
parent
5152d915d1
commit
88a37c545b
@ -17,7 +17,7 @@ features = ["v1_6"]
|
|||||||
|
|
||||||
[dependencies.gemini]
|
[dependencies.gemini]
|
||||||
package = "ggemini"
|
package = "ggemini"
|
||||||
version = "0.10.0"
|
version = "0.11.0"
|
||||||
|
|
||||||
[dependencies.gemtext]
|
[dependencies.gemtext]
|
||||||
package = "ggemtext"
|
package = "ggemtext"
|
||||||
|
@ -135,10 +135,10 @@ impl Tab {
|
|||||||
// Register dynamically created tab components in the HashMap index
|
// Register dynamically created tab components in the HashMap index
|
||||||
self.index
|
self.index
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(item.id().clone(), item.clone());
|
.insert(item.id.clone(), item.clone());
|
||||||
|
|
||||||
item.page()
|
item.page
|
||||||
.navigation()
|
.navigation
|
||||||
.request()
|
.request()
|
||||||
.widget()
|
.widget()
|
||||||
.gobject()
|
.gobject()
|
||||||
@ -163,7 +163,7 @@ impl Tab {
|
|||||||
if let Some(page) = self.widget.page(page_position) {
|
if let Some(page) = self.widget.page(page_position) {
|
||||||
if let Some(id) = page.keyword() {
|
if let Some(id) = page.keyword() {
|
||||||
if let Some(item) = self.index.borrow().get(&id) {
|
if let Some(item) = self.index.borrow().get(&id) {
|
||||||
return match item.page().bookmark() {
|
return match item.page.bookmark() {
|
||||||
Ok(result) => Ok(result),
|
Ok(result) => Ok(result),
|
||||||
Err(_) => Err(Error::Bookmark),
|
Err(_) => Err(Error::Bookmark),
|
||||||
};
|
};
|
||||||
@ -182,7 +182,7 @@ impl Tab {
|
|||||||
if let Some(page) = self.widget.page(page_position) {
|
if let Some(page) = self.widget.page(page_position) {
|
||||||
if let Some(id) = page.keyword() {
|
if let Some(id) = page.keyword() {
|
||||||
if let Some(item) = self.index.borrow().get(&id) {
|
if let Some(item) = self.index.borrow().get(&id) {
|
||||||
item.page().home();
|
item.page.home();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,7 +192,7 @@ impl Tab {
|
|||||||
if let Some(page) = self.widget.page(page_position) {
|
if let Some(page) = self.widget.page(page_position) {
|
||||||
if let Some(id) = page.keyword() {
|
if let Some(id) = page.keyword() {
|
||||||
if let Some(item) = self.index.borrow().get(&id) {
|
if let Some(item) = self.index.borrow().get(&id) {
|
||||||
item.page().history_back();
|
item.page.history_back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,7 +202,7 @@ impl Tab {
|
|||||||
if let Some(page) = self.widget.page(page_position) {
|
if let Some(page) = self.widget.page(page_position) {
|
||||||
if let Some(id) = page.keyword() {
|
if let Some(id) = page.keyword() {
|
||||||
if let Some(item) = self.index.borrow().get(&id) {
|
if let Some(item) = self.index.borrow().get(&id) {
|
||||||
item.page().history_forward();
|
item.page.history_forward();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,7 +213,7 @@ impl Tab {
|
|||||||
if let Some(page) = self.widget.page(page_position) {
|
if let Some(page) = self.widget.page(page_position) {
|
||||||
if let Some(id) = page.keyword() {
|
if let Some(id) = page.keyword() {
|
||||||
if let Some(item) = self.index.borrow().get(&id) {
|
if let Some(item) = self.index.borrow().get(&id) {
|
||||||
item.page().reload();
|
item.page.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,10 +231,10 @@ impl Tab {
|
|||||||
item.update();
|
item.update();
|
||||||
|
|
||||||
// Update tab title on loading indicator inactive
|
// Update tab title on loading indicator inactive
|
||||||
if !item.page().is_loading() {
|
if !item.page.is_loading() {
|
||||||
item.widget()
|
item.widget
|
||||||
.gobject()
|
.gobject()
|
||||||
.set_title(item.page().meta().title().as_str())
|
.set_title(item.page.meta.title().as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update all tabs on ID not found @TODO change initial update method
|
// Update all tabs on ID not found @TODO change initial update method
|
||||||
@ -244,10 +244,10 @@ impl Tab {
|
|||||||
item.update();
|
item.update();
|
||||||
|
|
||||||
// Update tab title on loading indicator inactive
|
// Update tab title on loading indicator inactive
|
||||||
if !item.page().is_loading() {
|
if !item.page.is_loading() {
|
||||||
item.widget()
|
item.widget
|
||||||
.gobject()
|
.gobject()
|
||||||
.set_title(item.page().meta().title().as_str())
|
.set_title(item.page.meta.title().as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,7 +299,7 @@ impl Tab {
|
|||||||
// Register dynamically created tab item in the HashMap index
|
// Register dynamically created tab item in the HashMap index
|
||||||
self.index
|
self.index
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(item.id().clone(), item.clone());
|
.insert(item.id.clone(), item.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e.to_string()),
|
Err(e) => return Err(e.to_string()),
|
||||||
@ -327,10 +327,10 @@ impl Tab {
|
|||||||
item.save(
|
item.save(
|
||||||
transaction,
|
transaction,
|
||||||
&id,
|
&id,
|
||||||
&self.widget.gobject().page_position(item.widget().gobject()),
|
&self.widget.gobject().page_position(item.widget.gobject()),
|
||||||
&item.widget().gobject().is_pinned(),
|
&item.widget.gobject().is_pinned(),
|
||||||
&item.widget().gobject().is_selected(),
|
&item.widget.gobject().is_selected(),
|
||||||
&item.widget().gobject().needs_attention(),
|
&item.widget.gobject().needs_attention(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,10 @@ use std::rc::Rc;
|
|||||||
pub struct Item {
|
pub struct Item {
|
||||||
// Auto-generated unique item ID
|
// Auto-generated unique item ID
|
||||||
// useful as widget name in GTK actions callback
|
// useful as widget name in GTK actions callback
|
||||||
id: GString,
|
pub id: GString,
|
||||||
// Components
|
// Components
|
||||||
page: Rc<Page>,
|
pub page: Rc<Page>,
|
||||||
widget: Rc<Widget>,
|
pub widget: Rc<Widget>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
@ -57,7 +57,7 @@ impl Item {
|
|||||||
let widget = Rc::new(Widget::new(
|
let widget = Rc::new(Widget::new(
|
||||||
id.as_str(),
|
id.as_str(),
|
||||||
tab_view,
|
tab_view,
|
||||||
page.widget().gobject(),
|
page.widget.gobject(),
|
||||||
None,
|
None,
|
||||||
position,
|
position,
|
||||||
(is_pinned, is_selected, is_attention),
|
(is_pinned, is_selected, is_attention),
|
||||||
@ -66,11 +66,7 @@ impl Item {
|
|||||||
// Init events
|
// Init events
|
||||||
|
|
||||||
if let Some(text) = request {
|
if let Some(text) = request {
|
||||||
page.navigation()
|
page.navigation.request().widget().gobject().set_text(&text);
|
||||||
.request()
|
|
||||||
.widget()
|
|
||||||
.gobject()
|
|
||||||
.set_text(&text);
|
|
||||||
|
|
||||||
if is_load {
|
if is_load {
|
||||||
page.load(true);
|
page.load(true);
|
||||||
@ -83,7 +79,7 @@ impl Item {
|
|||||||
let parent = tab_view.clone().upcast::<gtk::Widget>();
|
let parent = tab_view.clone().upcast::<gtk::Widget>();
|
||||||
move || {
|
move || {
|
||||||
// Request should match valid URI for all drivers supported
|
// Request should match valid URI for all drivers supported
|
||||||
if let Some(uri) = page.navigation().request().uri() {
|
if let Some(uri) = page.navigation.request().uri() {
|
||||||
// Rout by scheme
|
// Rout by scheme
|
||||||
if uri.scheme().to_lowercase() == "gemini" {
|
if uri.scheme().to_lowercase() == "gemini" {
|
||||||
return identity::new_gemini(profile.clone(), actions.1.clone(), uri)
|
return identity::new_gemini(profile.clone(), actions.1.clone(), uri)
|
||||||
@ -100,11 +96,7 @@ impl Item {
|
|||||||
let page = page.clone();
|
let page = page.clone();
|
||||||
move |request, is_history| {
|
move |request, is_history| {
|
||||||
if let Some(text) = request {
|
if let Some(text) = request {
|
||||||
page.navigation()
|
page.navigation.request().widget().gobject().set_text(&text);
|
||||||
.request()
|
|
||||||
.widget()
|
|
||||||
.gobject()
|
|
||||||
.set_text(&text);
|
|
||||||
}
|
}
|
||||||
page.load(is_history);
|
page.load(is_history);
|
||||||
}
|
}
|
||||||
@ -120,7 +112,7 @@ impl Item {
|
|||||||
self.page.update();
|
self.page.update();
|
||||||
|
|
||||||
// Update tab loading indicator
|
// Update tab loading indicator
|
||||||
self.widget.gobject().set_loading(self.page().is_loading());
|
self.widget.gobject().set_loading(self.page.is_loading());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clean(
|
pub fn clean(
|
||||||
@ -222,20 +214,6 @@ impl Item {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
|
||||||
|
|
||||||
pub fn id(&self) -> &GString {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn page(&self) -> &Rc<Page> {
|
|
||||||
&self.page
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn widget(&self) -> &Rc<Widget> {
|
|
||||||
&self.widget
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
|
@ -21,18 +21,12 @@ use crate::Profile;
|
|||||||
use gtk::{
|
use gtk::{
|
||||||
gdk::Texture,
|
gdk::Texture,
|
||||||
gdk_pixbuf::Pixbuf,
|
gdk_pixbuf::Pixbuf,
|
||||||
gio::{
|
gio::{Cancellable, SocketClientEvent, TlsCertificate, TlsClientConnection},
|
||||||
Cancellable, IOStream, NetworkAddress, SocketClient, SocketClientEvent, SocketConnectable,
|
|
||||||
SocketProtocol, TlsCertificate, TlsClientConnection,
|
|
||||||
},
|
|
||||||
glib::{
|
glib::{
|
||||||
gformat, Bytes, GString, Priority, Regex, RegexCompileFlags, RegexMatchFlags, Uri,
|
gformat, GString, Priority, Regex, RegexCompileFlags, RegexMatchFlags, Uri, UriFlags,
|
||||||
UriFlags, UriHideFlags,
|
UriHideFlags,
|
||||||
},
|
|
||||||
prelude::{
|
|
||||||
CancellableExt, Cast, EditableExt, IOStreamExt, IsA, OutputStreamExt, SocketClientExt,
|
|
||||||
TlsConnectionExt,
|
|
||||||
},
|
},
|
||||||
|
prelude::{CancellableExt, Cast, EditableExt, SocketClientExt, TlsConnectionExt},
|
||||||
};
|
};
|
||||||
use sqlite::Transaction;
|
use sqlite::Transaction;
|
||||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||||
@ -45,11 +39,11 @@ pub struct Page {
|
|||||||
browser_action: Rc<BrowserAction>,
|
browser_action: Rc<BrowserAction>,
|
||||||
tab_action: Rc<TabAction>,
|
tab_action: Rc<TabAction>,
|
||||||
// Components
|
// Components
|
||||||
navigation: Rc<Navigation>,
|
pub navigation: Rc<Navigation>,
|
||||||
content: Rc<Content>,
|
pub content: Rc<Content>,
|
||||||
input: Rc<Input>,
|
pub input: Rc<Input>,
|
||||||
meta: Rc<Meta>,
|
pub meta: Rc<Meta>,
|
||||||
widget: Rc<Widget>,
|
pub widget: Rc<Widget>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
@ -107,7 +101,7 @@ impl Page {
|
|||||||
.toggle(self.navigation.request().widget().gobject().text().as_str())
|
.toggle(self.navigation.request().widget().gobject().text().as_str())
|
||||||
{
|
{
|
||||||
Ok(result) => Ok(result),
|
Ok(result) => Ok(result),
|
||||||
Err(_) => Err(Error::Bookmark),
|
Err(_) => Err(Error::Bookmark), // @TODO
|
||||||
};
|
};
|
||||||
self.update();
|
self.update();
|
||||||
result
|
result
|
||||||
@ -405,53 +399,11 @@ impl Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn meta(&self) -> &Rc<Meta> {
|
|
||||||
&self.meta
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn navigation(&self) -> &Rc<Navigation> {
|
|
||||||
&self.navigation
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn widget(&self) -> &Rc<Widget> {
|
|
||||||
&self.widget
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private helpers
|
// Private helpers
|
||||||
|
|
||||||
// @TODO move outside
|
// @TODO move outside
|
||||||
fn load_gemini(&self, uri: Uri, is_history: bool) {
|
fn load_gemini(&self, uri: Uri, is_history: bool) {
|
||||||
// Stream wrapper for TLS connections
|
// Init shared clones
|
||||||
fn auth(
|
|
||||||
connection: impl IsA<IOStream>,
|
|
||||||
connectable: impl IsA<SocketConnectable>,
|
|
||||||
certificate: Option<TlsCertificate>,
|
|
||||||
) -> impl IsA<IOStream> {
|
|
||||||
if let Some(certificate) = certificate {
|
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#the-use-of-tls
|
|
||||||
let tls_connection =
|
|
||||||
TlsClientConnection::new(&connection, Some(&connectable)).unwrap(); // @TODO handle
|
|
||||||
|
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates
|
|
||||||
tls_connection.set_certificate(&certificate);
|
|
||||||
|
|
||||||
// @TODO handle exceptions
|
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#closing-connections
|
|
||||||
tls_connection.set_require_close_notify(true);
|
|
||||||
|
|
||||||
// @TODO host validation
|
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#tls-server-certificate-validation
|
|
||||||
tls_connection.connect_accept_certificate(move |_, _, _| true);
|
|
||||||
|
|
||||||
// Take encrypted I/O stream
|
|
||||||
tls_connection.upcast::<IOStream>()
|
|
||||||
} else {
|
|
||||||
// Take default I/O stream
|
|
||||||
connection.upcast::<IOStream>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init shared objects (async)
|
|
||||||
let cancellable = self.cancellable.borrow().clone();
|
let cancellable = self.cancellable.borrow().clone();
|
||||||
let update = self.browser_action.update().clone();
|
let update = self.browser_action.update().clone();
|
||||||
let tab_action = self.tab_action.clone();
|
let tab_action = self.tab_action.clone();
|
||||||
@ -460,12 +412,9 @@ impl Page {
|
|||||||
let id = self.id.clone();
|
let id = self.id.clone();
|
||||||
let input = self.input.clone();
|
let input = self.input.clone();
|
||||||
let meta = self.meta.clone();
|
let meta = self.meta.clone();
|
||||||
let url = uri.clone().to_str();
|
|
||||||
|
|
||||||
// Init socket
|
// Init socket
|
||||||
let client = SocketClient::new();
|
let client = gemini::Client::new();
|
||||||
client.set_protocol(SocketProtocol::Tcp);
|
|
||||||
client.set_timeout(10); // @TODO
|
|
||||||
|
|
||||||
// Return PEM string match request
|
// Return PEM string match request
|
||||||
let certificate = match self
|
let certificate = match self
|
||||||
@ -479,16 +428,16 @@ impl Page {
|
|||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
// Use unauthorized (random) TLS connection
|
// Use unauthorized (random) TLS connection
|
||||||
client.set_tls(true);
|
client.socket.set_tls(true);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen for connection status updates
|
// Listen for connection status updates
|
||||||
client.connect_event({
|
client.socket.connect_event({
|
||||||
let update = update.clone();
|
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
let meta = meta.clone();
|
let meta = meta.clone();
|
||||||
|
let update = update.clone();
|
||||||
move |_, event, _, stream| {
|
move |_, event, _, stream| {
|
||||||
meta.set_status(match event {
|
meta.set_status(match event {
|
||||||
SocketClientEvent::Resolving => Status::Resolving,
|
SocketClientEvent::Resolving => Status::Resolving,
|
||||||
@ -497,8 +446,7 @@ impl Page {
|
|||||||
SocketClientEvent::Connected => Status::Connected,
|
SocketClientEvent::Connected => Status::Connected,
|
||||||
SocketClientEvent::ProxyNegotiating => Status::ProxyNegotiating,
|
SocketClientEvent::ProxyNegotiating => Status::ProxyNegotiating,
|
||||||
SocketClientEvent::ProxyNegotiated => Status::ProxyNegotiated,
|
SocketClientEvent::ProxyNegotiated => Status::ProxyNegotiated,
|
||||||
// This case have effect only for unauthorized (random) TLS connection
|
// This match have effect only for unauthorized (random) TLS connection
|
||||||
// * see `fn auth` above to handle custom certificates
|
|
||||||
SocketClientEvent::TlsHandshaking => {
|
SocketClientEvent::TlsHandshaking => {
|
||||||
// Handle certificate errors @TODO
|
// Handle certificate errors @TODO
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#tls-server-certificate-validation
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#tls-server-certificate-validation
|
||||||
@ -517,75 +465,47 @@ impl Page {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Implement shared [SocketConnectable](https://docs.gtk.org/gio/iface.SocketConnectable.html) interface
|
|
||||||
// * required also on `auth` step ([SNI](https://geminiprotocol.net/docs/protocol-specification.gmi#server-name-indication))
|
|
||||||
let connectable = NetworkAddress::new(
|
|
||||||
&uri.host().unwrap(),
|
|
||||||
if uri.port().is_positive() {
|
|
||||||
uri.port() as u16
|
|
||||||
} else {
|
|
||||||
1965
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create connection
|
// Create connection
|
||||||
client.connect_async(
|
client.request_async(
|
||||||
&connectable.clone(),
|
uri.clone(),
|
||||||
Some(&cancellable.clone()),
|
None,
|
||||||
move |connect| match connect {
|
|
||||||
Ok(connection) => {
|
|
||||||
|
|
||||||
// Encrypt stream using authorization TLS
|
|
||||||
let stream = auth(connection, connectable, certificate);
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
stream.output_stream().write_bytes_async(
|
|
||||||
&Bytes::from(gformat!("{url}\r\n").as_bytes()),
|
|
||||||
Priority::DEFAULT,
|
|
||||||
Some(&cancellable.clone()),
|
|
||||||
move |request| match request {
|
|
||||||
Ok(_) => {
|
|
||||||
// Read meta from input stream
|
|
||||||
gemini::client::response::Meta::from_stream_async(
|
|
||||||
stream.clone(),
|
|
||||||
Some(Priority::DEFAULT),
|
|
||||||
Some(cancellable.clone()),
|
Some(cancellable.clone()),
|
||||||
move |result| match result
|
certificate,
|
||||||
{
|
move |result| match result {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
// Route by status
|
// Route by status
|
||||||
match response.status() {
|
match response.meta.status {
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#input-expected
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#input-expected
|
||||||
gemini::client::response::meta::Status::Input |
|
gemini::client::response::meta::Status::Input |
|
||||||
gemini::client::response::meta::Status::SensitiveInput => {
|
gemini::client::response::meta::Status::SensitiveInput => {
|
||||||
// Format response
|
// Format response
|
||||||
let status = Status::Input;
|
let status = Status::Input;
|
||||||
let title = match response.data() {
|
let title = match response.meta.data {
|
||||||
Some(data) => data.value().as_str(),
|
Some(data) => data.value,
|
||||||
None => "Input expected",
|
None => gformat!("Input expected"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toggle input form variant
|
// Toggle input form variant
|
||||||
match response.status() {
|
match response.meta.status {
|
||||||
gemini::client::response::meta::Status::SensitiveInput =>
|
gemini::client::response::meta::Status::SensitiveInput =>
|
||||||
input.set_new_sensitive(
|
input.set_new_sensitive(
|
||||||
tab_action,
|
tab_action.clone(),
|
||||||
uri,
|
uri.clone(),
|
||||||
Some(title),
|
Some(&title),
|
||||||
Some(1024),
|
Some(1024),
|
||||||
),
|
),
|
||||||
_ =>
|
_ =>
|
||||||
input.set_new_response(
|
input.set_new_response(
|
||||||
tab_action,
|
tab_action.clone(),
|
||||||
uri,
|
uri.clone(),
|
||||||
Some(title),
|
Some(&title),
|
||||||
Some(1024),
|
Some(1024),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update meta
|
// Update meta
|
||||||
meta.set_status(status)
|
meta.set_status(status)
|
||||||
.set_title(title);
|
.set_title(&title);
|
||||||
|
|
||||||
// Update page
|
// Update page
|
||||||
update.activate(Some(&id));
|
update.activate(Some(&id));
|
||||||
@ -594,24 +514,30 @@ impl Page {
|
|||||||
gemini::client::response::meta::Status::Success => {
|
gemini::client::response::meta::Status::Success => {
|
||||||
// Add history record
|
// Add history record
|
||||||
if is_history {
|
if is_history {
|
||||||
snap_history(navigation);
|
snap_history(navigation.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route by MIME
|
// Route by MIME
|
||||||
match response.mime() {
|
match response.meta.mime {
|
||||||
Some(gemini::client::response::meta::Mime::TextGemini) => {
|
Some(gemini::client::response::meta::Mime::TextGemini) => {
|
||||||
// Read entire input stream to buffer
|
// Read entire input stream to buffer
|
||||||
gemini::client::response::data::Text::from_stream_async(
|
gemini::client::response::data::Text::from_stream_async(
|
||||||
stream,
|
response.connection.stream(),
|
||||||
Some(Priority::DEFAULT),
|
Some(Priority::DEFAULT),
|
||||||
Some(cancellable.clone()),
|
Some(cancellable.clone()),
|
||||||
|
{
|
||||||
|
let content = content.clone();
|
||||||
|
let id = id.clone();
|
||||||
|
let meta = meta.clone();
|
||||||
|
let update = update.clone();
|
||||||
|
let uri = uri.clone();
|
||||||
move |result|{
|
move |result|{
|
||||||
match result {
|
match result {
|
||||||
Ok(buffer) => {
|
Ok(buffer) => {
|
||||||
// Set children component
|
// Set children component
|
||||||
let text_gemini = content.to_text_gemini(
|
let text_gemini = content.to_text_gemini(
|
||||||
&uri,
|
&uri,
|
||||||
buffer.data()
|
&buffer.data
|
||||||
);
|
);
|
||||||
|
|
||||||
let title = match text_gemini.meta_title() {
|
let title = match text_gemini.meta_title() {
|
||||||
@ -626,24 +552,17 @@ impl Page {
|
|||||||
// Update window components
|
// Update window components
|
||||||
update.activate(Some(&id));
|
update.activate(Some(&id));
|
||||||
}
|
}
|
||||||
Err((reason, message)) => {
|
Err(reason) => {
|
||||||
// Define common data
|
// Define common data
|
||||||
let status = Status::Failure;
|
let status = Status::Failure;
|
||||||
let title = "Oops";
|
let title = "Oops";
|
||||||
let description = match reason {
|
let description = reason.to_string();
|
||||||
gemini::client::response::data::text::Error::InputStream => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("Undefined connection error")
|
|
||||||
} ,
|
|
||||||
gemini::client::response::data::text::Error::BufferOverflow => gformat!("Buffer overflow"),
|
|
||||||
gemini::client::response::data::text::Error::Decode => gformat!("Buffer decode error"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update widget
|
// Update widget
|
||||||
content
|
content
|
||||||
.to_status_failure()
|
.to_status_failure()
|
||||||
.set_title(title)
|
.set_title(title)
|
||||||
.set_description(Some(description.as_str()));
|
.set_description(Some(&description));
|
||||||
|
|
||||||
// Update meta
|
// Update meta
|
||||||
meta.set_status(status)
|
meta.set_status(status)
|
||||||
@ -654,6 +573,7 @@ impl Page {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Some(
|
Some(
|
||||||
@ -670,7 +590,7 @@ impl Page {
|
|||||||
// Asynchronously move `InputStream` data from `SocketConnection` into the local `MemoryInputStream`
|
// Asynchronously move `InputStream` data from `SocketConnection` into the local `MemoryInputStream`
|
||||||
// this action allows to count the bytes for loading widget and validate max size for incoming data
|
// this action allows to count the bytes for loading widget and validate max size for incoming data
|
||||||
gemini::gio::memory_input_stream::from_stream_async(
|
gemini::gio::memory_input_stream::from_stream_async(
|
||||||
stream,
|
response.connection.stream(),
|
||||||
Some(cancellable.clone()),
|
Some(cancellable.clone()),
|
||||||
Priority::DEFAULT,
|
Priority::DEFAULT,
|
||||||
0x400, // 1024 bytes per chunk, optional step for images download tracking
|
0x400, // 1024 bytes per chunk, optional step for images download tracking
|
||||||
@ -681,6 +601,13 @@ impl Page {
|
|||||||
Some(&gformat!("Download: {total} bytes"))
|
Some(&gformat!("Download: {total} bytes"))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
let cancellable = cancellable.clone();
|
||||||
|
let content = content.clone();
|
||||||
|
let id = id.clone();
|
||||||
|
let meta = meta.clone();
|
||||||
|
let update = update.clone();
|
||||||
|
let uri = uri.clone();
|
||||||
move |result| match result {
|
move |result| match result {
|
||||||
Ok(memory_input_stream) => {
|
Ok(memory_input_stream) => {
|
||||||
Pixbuf::from_stream_async(
|
Pixbuf::from_stream_async(
|
||||||
@ -734,29 +661,16 @@ impl Page {
|
|||||||
content
|
content
|
||||||
.to_status_failure()
|
.to_status_failure()
|
||||||
.set_title(title)
|
.set_title(title)
|
||||||
.set_description(Some(description.as_str()));
|
.set_description(Some(&description));
|
||||||
|
|
||||||
// Update meta
|
// Update meta
|
||||||
meta.set_status(status)
|
meta.set_status(status)
|
||||||
.set_title(title);
|
.set_title(title);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
/* @TODO stream or download
|
|
||||||
Some(
|
|
||||||
ClientMime::AudioFlac | ClientMime::AudioMpeg | ClientMime::AudioOgg
|
|
||||||
) => {
|
|
||||||
// Update page meta
|
|
||||||
meta.borrow_mut().status = Some(Status::Success);
|
|
||||||
meta.borrow_mut().title = Some(gformat!("Stream"));
|
|
||||||
|
|
||||||
// Update page content
|
|
||||||
// content.to_stream();
|
|
||||||
|
|
||||||
// Update window components
|
|
||||||
update.activate(Some(&id));
|
|
||||||
}, */
|
|
||||||
_ => {
|
_ => {
|
||||||
// Define common data
|
// Define common data
|
||||||
let status = Status::Failure;
|
let status = Status::Failure;
|
||||||
@ -767,7 +681,7 @@ impl Page {
|
|||||||
content
|
content
|
||||||
.to_status_failure()
|
.to_status_failure()
|
||||||
.set_title(title)
|
.set_title(title)
|
||||||
.set_description(Some(description.as_str()));
|
.set_description(Some(&description));
|
||||||
|
|
||||||
// Update meta
|
// Update meta
|
||||||
meta.set_status(status)
|
meta.set_status(status)
|
||||||
@ -783,14 +697,14 @@ impl Page {
|
|||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-31-permanent-redirection
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-31-permanent-redirection
|
||||||
gemini::client::response::meta::Status::PermanentRedirect => {
|
gemini::client::response::meta::Status::PermanentRedirect => {
|
||||||
// Extract redirection URL from response data
|
// Extract redirection URL from response data
|
||||||
match response.data() {
|
match response.meta.data {
|
||||||
Some(unresolved_url) => {
|
Some(unresolved_url) => {
|
||||||
// New URL from server MAY to be relative (according to the protocol specification),
|
// New URL from server MAY to be relative (according to the protocol specification),
|
||||||
// resolve to absolute URI gobject using current request as the base for parser:
|
// resolve to absolute URI gobject using current request as the base for parser:
|
||||||
// https://docs.gtk.org/glib/type_func.Uri.resolve_relative.html
|
// https://docs.gtk.org/glib/type_func.Uri.resolve_relative.html
|
||||||
match Uri::resolve_relative(
|
match Uri::resolve_relative(
|
||||||
Some(&uri.to_string()),
|
Some(&uri.to_string()),
|
||||||
unresolved_url.value(),
|
unresolved_url.value.as_str(),
|
||||||
UriFlags::NONE,
|
UriFlags::NONE,
|
||||||
) {
|
) {
|
||||||
Ok(resolved_url) => {
|
Ok(resolved_url) => {
|
||||||
@ -836,7 +750,7 @@ impl Page {
|
|||||||
UriHideFlags::FRAGMENT | UriHideFlags::QUERY
|
UriHideFlags::FRAGMENT | UriHideFlags::QUERY
|
||||||
),
|
),
|
||||||
// Set follow policy based on status code
|
// Set follow policy based on status code
|
||||||
matches!(response.status(), gemini::client::response::meta::Status::PermanentRedirect),
|
matches!(response.meta.status, gemini::client::response::meta::Status::PermanentRedirect),
|
||||||
)
|
)
|
||||||
.set_status(Status::Redirect) // @TODO is this status really wanted?
|
.set_status(Status::Redirect) // @TODO is this status really wanted?
|
||||||
.set_title("Redirect");
|
.set_title("Redirect");
|
||||||
@ -901,21 +815,21 @@ impl Page {
|
|||||||
|
|
||||||
// Add history record
|
// Add history record
|
||||||
if is_history {
|
if is_history {
|
||||||
snap_history(navigation);
|
snap_history(navigation.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update widget
|
// Update widget
|
||||||
content
|
content
|
||||||
.to_status_identity()
|
.to_status_identity()
|
||||||
.set_title(title)
|
.set_title(title)
|
||||||
.set_description(match response.data() {
|
.set_description(Some(&match response.meta.data {
|
||||||
Some(data) => Some(data.value().as_str()),
|
Some(data) => data.value,
|
||||||
None => match response.status() {
|
None => match response.meta.status {
|
||||||
gemini::client::response::meta::Status::CertificateUnauthorized => Some("Certificate not authorized"),
|
gemini::client::response::meta::Status::CertificateUnauthorized => gformat!("Certificate not authorized"),
|
||||||
gemini::client::response::meta::Status::CertificateInvalid => Some("Certificate not valid"),
|
gemini::client::response::meta::Status::CertificateInvalid => gformat!("Certificate not valid"),
|
||||||
_ => Some("Client certificate required")
|
_ => gformat!("Client certificate required")
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
|
|
||||||
// Update meta
|
// Update meta
|
||||||
meta.set_status(status)
|
meta.set_status(status)
|
||||||
@ -931,16 +845,16 @@ impl Page {
|
|||||||
|
|
||||||
// Add history record
|
// Add history record
|
||||||
if is_history {
|
if is_history {
|
||||||
snap_history(navigation);
|
snap_history(navigation.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update widget
|
// Update widget
|
||||||
content
|
content
|
||||||
.to_status_failure()
|
.to_status_failure()
|
||||||
.set_title(title)
|
.set_title(title)
|
||||||
.set_description(Some(match response.data() {
|
.set_description(Some(&match response.meta.data {
|
||||||
Some(data) => data.value().as_str(),
|
Some(data) => data.value,
|
||||||
None => "Status code yet not supported", // @TODO
|
None => gformat!("Status code yet not supported"),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Update meta
|
// Update meta
|
||||||
@ -952,67 +866,22 @@ impl Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err((reason, message)) => {
|
Err(reason) => {
|
||||||
// Define common data
|
// Define common data
|
||||||
let status = Status::Failure;
|
let status = Status::Failure;
|
||||||
let title = "Oops";
|
let title = "Oops";
|
||||||
let description = match reason {
|
let description = reason.to_string();
|
||||||
// Common
|
|
||||||
gemini::client::response::meta::Error::InputStream => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("Input stream reading error")
|
|
||||||
},
|
|
||||||
gemini::client::response::meta::Error::Protocol => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("Incorrect protocol")
|
|
||||||
},
|
|
||||||
// Status
|
|
||||||
gemini::client::response::meta::Error::StatusDecode => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("Could not detect status code")
|
|
||||||
},
|
|
||||||
gemini::client::response::meta::Error::StatusUndefined => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("Status code yet not supported")
|
|
||||||
},
|
|
||||||
gemini::client::response::meta::Error::StatusProtocol => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("Incorrect status code protocol")
|
|
||||||
},
|
|
||||||
// Data
|
|
||||||
gemini::client::response::meta::Error::DataDecode => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("Incorrect data encoding")
|
|
||||||
},
|
|
||||||
gemini::client::response::meta::Error::DataProtocol => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("Incorrect data protocol")
|
|
||||||
},
|
|
||||||
// MIME
|
|
||||||
gemini::client::response::meta::Error::MimeDecode => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("Incorrect MIME encoding")
|
|
||||||
},
|
|
||||||
gemini::client::response::meta::Error::MimeProtocol => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("Incorrect MIME protocol")
|
|
||||||
},
|
|
||||||
gemini::client::response::meta::Error::MimeUndefined => match message {
|
|
||||||
Some(error) => gformat!("{error}"),
|
|
||||||
None => gformat!("MIME type yet not supported (by library)")
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add history record
|
// Add history record
|
||||||
if is_history {
|
if is_history {
|
||||||
snap_history(navigation);
|
snap_history(navigation.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update widget
|
// Update widget
|
||||||
content
|
content
|
||||||
.to_status_failure()
|
.to_status_failure()
|
||||||
.set_title(title)
|
.set_title(title)
|
||||||
.set_description(Some(description.as_str()));
|
.set_description(Some(&description));
|
||||||
|
|
||||||
// Update meta
|
// Update meta
|
||||||
meta.set_status(status)
|
meta.set_status(status)
|
||||||
@ -1024,58 +893,6 @@ impl Page {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(reason) => {
|
|
||||||
// Define common data
|
|
||||||
let status = Status::Failure;
|
|
||||||
let title = "Oops";
|
|
||||||
|
|
||||||
// Add history record
|
|
||||||
if is_history {
|
|
||||||
snap_history(navigation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update widget
|
|
||||||
content
|
|
||||||
.to_status_failure()
|
|
||||||
.set_title(title)
|
|
||||||
.set_description(Some(reason.message()));
|
|
||||||
|
|
||||||
// Update meta
|
|
||||||
meta.set_status(status)
|
|
||||||
.set_title(title);
|
|
||||||
|
|
||||||
// Update window
|
|
||||||
update.activate(Some(&id));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(reason) => {
|
|
||||||
// Define common data
|
|
||||||
let status = Status::Failure;
|
|
||||||
let title = "Oops";
|
|
||||||
|
|
||||||
// Add history record
|
|
||||||
if is_history {
|
|
||||||
snap_history(navigation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update widget
|
|
||||||
content
|
|
||||||
.to_status_failure()
|
|
||||||
.set_title(title)
|
|
||||||
.set_description(Some(reason.message()));
|
|
||||||
|
|
||||||
// Update meta
|
|
||||||
meta.set_status(status)
|
|
||||||
.set_title(title);
|
|
||||||
|
|
||||||
// Update window
|
|
||||||
update.activate(Some(&id));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
|
Loading…
x
Reference in New Issue
Block a user