draft ggemini 0.11.0 version api

This commit is contained in:
yggverse 2024-11-27 04:20:56 +02:00
parent 5152d915d1
commit 88a37c545b
4 changed files with 404 additions and 609 deletions

View File

@ -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"

View File

@ -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(),
)?; )?;
} }
} }

View File

@ -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

View File

@ -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