mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-01-27 11:34:14 +00:00
reorganize page loading methods
This commit is contained in:
parent
8d3c4319c6
commit
0ff182f15d
@ -219,7 +219,7 @@ impl Tab {
|
||||
pub fn save_as(&self, page_position: Option<i32>) {
|
||||
if let Some(item) = self.item(page_position) {
|
||||
item.page.navigation.request.to_download();
|
||||
item.page.load(true);
|
||||
item::page::load(&item.page, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +227,7 @@ impl Tab {
|
||||
pub fn source(&self, page_position: Option<i32>) {
|
||||
if let Some(item) = self.item(page_position) {
|
||||
item.page.navigation.request.to_source();
|
||||
item.page.load(true);
|
||||
item::page::load(&item.page, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,26 +250,26 @@ impl Tab {
|
||||
|
||||
pub fn page_home(&self, page_position: Option<i32>) {
|
||||
if let Some(item) = self.item(page_position) {
|
||||
item.page.home();
|
||||
item::page::home(&item.page);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn page_history_back(&self, page_position: Option<i32>) {
|
||||
if let Some(item) = self.item(page_position) {
|
||||
item.page.history_back();
|
||||
item::page::history_back(&item.page);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn page_history_forward(&self, page_position: Option<i32>) {
|
||||
if let Some(item) = self.item(page_position) {
|
||||
item.page.history_forward();
|
||||
item::page::history_forward(&item.page);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reload page at `i32` position or selected page on `None` given
|
||||
pub fn page_reload(&self, page_position: Option<i32>) {
|
||||
if let Some(item) = self.item(page_position) {
|
||||
item.page.load(true);
|
||||
item::page::load(&item.page, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
mod action;
|
||||
mod database;
|
||||
mod identity;
|
||||
mod page;
|
||||
pub mod page;
|
||||
mod widget;
|
||||
|
||||
use action::Action;
|
||||
@ -75,7 +75,7 @@ impl Item {
|
||||
page.navigation.request.widget.entry.set_text(&text);
|
||||
|
||||
if is_load {
|
||||
page.load(true);
|
||||
page::load(&page, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ impl Item {
|
||||
if let Some(text) = request {
|
||||
page.navigation.request.widget.entry.set_text(&text);
|
||||
}
|
||||
page.load(is_history);
|
||||
page::load(&page, is_history);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -138,361 +138,6 @@ impl Page {
|
||||
self.search.show()
|
||||
}
|
||||
|
||||
/// Navigate home URL (parsed from current navigation entry)
|
||||
/// * this method create new history record in memory as defined in `action_page_open` action
|
||||
pub fn home(&self) {
|
||||
if let Some(url) = self.navigation.home.url() {
|
||||
// Update with history record
|
||||
self.tab_action.load.activate(Some(&url), true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigate back in history
|
||||
/// * this method does not create new history record in memory
|
||||
pub fn history_back(&self) {
|
||||
if let Some(request) = self.navigation.history.back(true) {
|
||||
// Update navigation entry
|
||||
self.navigation.request.widget.entry.set_text(&request);
|
||||
|
||||
// Load page (without history record)
|
||||
self.load(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigate forward in history
|
||||
/// * this method does not create new history record in memory
|
||||
pub fn history_forward(&self) {
|
||||
if let Some(request) = self.navigation.history.forward(true) {
|
||||
// Update navigation entry
|
||||
self.navigation.request.widget.entry.set_text(&request);
|
||||
|
||||
// Load page (without history record)
|
||||
self.load(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Main loader for current `navigation` value
|
||||
pub fn load(&self, is_history: bool) {
|
||||
// Move focus out from navigation entry
|
||||
self.browser_action
|
||||
.escape
|
||||
.activate_stateful_once(Some(self.id.as_str().into()));
|
||||
|
||||
// Initially disable find action
|
||||
self.window_action.find.simple_action.set_enabled(false);
|
||||
|
||||
// Reset widgets
|
||||
self.search.unset();
|
||||
self.input.unset();
|
||||
|
||||
// Update
|
||||
self.status.replace(Status::Loading { time: now() });
|
||||
self.title.replace("Loading..".into());
|
||||
self.browser_action.update.activate(Some(&self.id));
|
||||
|
||||
if is_history {
|
||||
snap_history(&self.profile, &self.navigation, None); // @TODO
|
||||
}
|
||||
|
||||
use client::response::{Certificate, Failure, Input, Redirect};
|
||||
use client::Response;
|
||||
|
||||
self.client
|
||||
.request(&self.navigation.request.widget.entry.text(), {
|
||||
let browser_action = self.browser_action.clone();
|
||||
let content = self.content.clone();
|
||||
let id = self.id.clone();
|
||||
let input = self.input.clone();
|
||||
let navigation = self.navigation.clone();
|
||||
let search = self.search.clone();
|
||||
let status = self.status.clone();
|
||||
let tab_action = self.tab_action.clone();
|
||||
let title = self.title.clone();
|
||||
let window_action = self.window_action.clone();
|
||||
move |response| {
|
||||
match response {
|
||||
Response::Certificate(this) => match this {
|
||||
Certificate::Invalid {
|
||||
title: certificate_title,
|
||||
}
|
||||
| Certificate::Request {
|
||||
title: certificate_title,
|
||||
}
|
||||
| Certificate::Unauthorized {
|
||||
title: certificate_title,
|
||||
} => {
|
||||
// Update widget
|
||||
let status_page = content.to_status_identity();
|
||||
status_page.set_description(Some(&certificate_title));
|
||||
|
||||
// Update meta
|
||||
status.replace(Status::Success { time: now() });
|
||||
title.replace(status_page.title());
|
||||
|
||||
// Update window
|
||||
browser_action.update.activate(Some(&id));
|
||||
}
|
||||
},
|
||||
Response::Failure(this) => match this {
|
||||
Failure::Status { message } | Failure::Error { message } => {
|
||||
// Update widget
|
||||
let status_page = content.to_status_failure();
|
||||
status_page.set_description(Some(&message));
|
||||
|
||||
// Update meta
|
||||
status.replace(Status::Failure { time: now() });
|
||||
title.replace(status_page.title());
|
||||
|
||||
// Update window
|
||||
browser_action.update.activate(Some(&id));
|
||||
}
|
||||
Failure::Mime { base, mime, message } => {
|
||||
// Update widget
|
||||
let status_page = content.to_status_mime(&mime, Some((&tab_action, &base)));
|
||||
status_page.set_description(Some(&message));
|
||||
|
||||
// Update meta
|
||||
status.replace(Status::Failure { time: now() });
|
||||
title.replace(status_page.title());
|
||||
|
||||
// Update window
|
||||
browser_action.update.activate(Some(&id));
|
||||
}
|
||||
},
|
||||
Response::Input(this) => match this {
|
||||
Input::Response {
|
||||
base,
|
||||
title: response_title,
|
||||
} => {
|
||||
input.set_new_response(
|
||||
tab_action.clone(),
|
||||
base,
|
||||
Some(&response_title),
|
||||
Some(1024),
|
||||
);
|
||||
|
||||
status.replace(Status::Input { time: now() });
|
||||
title.replace(response_title);
|
||||
|
||||
browser_action.update.activate(Some(&id));
|
||||
}
|
||||
Input::Sensitive {
|
||||
base,
|
||||
title: response_title,
|
||||
} => {
|
||||
input.set_new_sensitive(
|
||||
tab_action.clone(),
|
||||
base,
|
||||
Some(&response_title),
|
||||
Some(1024),
|
||||
);
|
||||
|
||||
status.replace(Status::Input { time: now() });
|
||||
title.replace(response_title);
|
||||
|
||||
browser_action.update.activate(Some(&id));
|
||||
}
|
||||
Input::Titan { base } => {
|
||||
input.set_new_titan(move |data| {}); // @TODO
|
||||
|
||||
status.replace(Status::Input { time: now() });
|
||||
title.replace("Titan input".into()); // @TODO
|
||||
|
||||
browser_action.update.activate(Some(&id));
|
||||
}
|
||||
},
|
||||
Response::Redirect(this) => match this {
|
||||
Redirect::Background(request) => todo!(), // @TODO
|
||||
Redirect::Foreground(request) => {navigation
|
||||
.request
|
||||
.widget
|
||||
.entry
|
||||
.set_text(&request.as_uri().to_string())} // @TODO handle
|
||||
}
|
||||
Response::TextGemini { base, source, is_source_request } => {
|
||||
let widget = if is_source_request {
|
||||
content.to_text_source(&source)
|
||||
} else {
|
||||
content.to_text_gemini(&base, &source)
|
||||
};
|
||||
|
||||
// Connect `TextView` widget, update `search` model
|
||||
search.set(Some(widget.text_view));
|
||||
|
||||
// Update page meta
|
||||
status.replace(Status::Success { time: now() });
|
||||
title.replace(match widget.meta.title {
|
||||
Some(title) => title.into(), // @TODO
|
||||
None => uri_to_title(&base),
|
||||
});
|
||||
|
||||
// Update window components
|
||||
window_action.find.simple_action.set_enabled(true);
|
||||
browser_action.update.activate(Some(&id));
|
||||
}
|
||||
Response::Download { base, cancellable, stream } => {
|
||||
// Init download widget
|
||||
let status_page = content.to_status_download(
|
||||
uri_to_title(&base).trim_matches(MAIN_SEPARATOR), // grab default filename from base URI,
|
||||
// format FS entities
|
||||
&cancellable,
|
||||
{
|
||||
let cancellable = cancellable.clone();
|
||||
let stream = stream.clone();
|
||||
move |file, action| {
|
||||
match file.replace(
|
||||
None,
|
||||
false,
|
||||
gtk::gio::FileCreateFlags::NONE,
|
||||
Some(&cancellable)
|
||||
) {
|
||||
Ok(file_output_stream) => {
|
||||
// Asynchronously read [IOStream](https://docs.gtk.org/gio/class.IOStream.html)
|
||||
// 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
|
||||
ggemini::gio::file_output_stream::move_all_from_stream_async(
|
||||
stream.clone(),
|
||||
file_output_stream,
|
||||
cancellable.clone(),
|
||||
Priority::DEFAULT,
|
||||
(
|
||||
0x100000, // 1M bytes per chunk
|
||||
None, // unlimited
|
||||
0 // initial totals
|
||||
),
|
||||
(
|
||||
// on chunk
|
||||
{
|
||||
let action = action.clone();
|
||||
move |_, total| action.update.activate(
|
||||
&format!(
|
||||
"Received {}...",
|
||||
crate::tool::format_bytes(total)
|
||||
)
|
||||
)
|
||||
},
|
||||
// on complete
|
||||
{
|
||||
let action = action.clone();
|
||||
move |result| match result {
|
||||
Ok((_, total)) => action.complete.activate(
|
||||
&format!("Saved to {} ({total} bytes total)", file.parse_name())
|
||||
),
|
||||
Err(e) => action.cancel.activate(&e.to_string())
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
Err(e) => action.cancel.activate(&e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Update meta
|
||||
status.replace(Status::Success { time: now() });
|
||||
title.replace(status_page.title());
|
||||
|
||||
// Update window
|
||||
browser_action.update.activate(Some(&id));
|
||||
}
|
||||
Response::Stream { base, mime, stream, cancellable } => match mime.as_str() {
|
||||
// @TODO use client-side const or enum?
|
||||
"image/png" | "image/gif" | "image/jpeg" | "image/webp" => {
|
||||
// Final image size unknown, show loading widget
|
||||
let status_page = content.to_status_loading(
|
||||
Some(Duration::from_secs(1)), // show if download time > 1 second
|
||||
);
|
||||
|
||||
// Asynchronously read [IOStream](https://docs.gtk.org/gio/class.IOStream.html)
|
||||
// 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
|
||||
ggemini::gio::memory_input_stream::from_stream_async(
|
||||
stream,
|
||||
cancellable.clone(),
|
||||
Priority::DEFAULT,
|
||||
0x400, // 1024 bytes per chunk, optional step for images download tracking
|
||||
0xA00000, // 10M bytes max to prevent memory overflow if server play with promises
|
||||
move |_, total| {
|
||||
// Update loading progress
|
||||
status_page.set_description(Some(&format!(
|
||||
"Download: {total} bytes"
|
||||
)));
|
||||
},
|
||||
{
|
||||
let browser_action = browser_action.clone();
|
||||
let cancellable = cancellable.clone();
|
||||
let content = content.clone();
|
||||
let id = id.clone();
|
||||
let status = status.clone();
|
||||
let title = title.clone();
|
||||
let base = base.clone();
|
||||
move |result| match result {
|
||||
Ok((memory_input_stream, _)) => {
|
||||
Pixbuf::from_stream_async(
|
||||
&memory_input_stream,
|
||||
Some(&cancellable),
|
||||
move |result| {
|
||||
// Process buffer data
|
||||
match result {
|
||||
Ok(buffer) => {
|
||||
// Update page meta
|
||||
status.replace(Status::Success {
|
||||
time: now(),
|
||||
});
|
||||
title.replace(uri_to_title(&base));
|
||||
|
||||
// Update page content
|
||||
content.to_image(
|
||||
&Texture::for_pixbuf(&buffer),
|
||||
);
|
||||
|
||||
// Update window components
|
||||
browser_action
|
||||
.update
|
||||
.activate(Some(&id));
|
||||
}
|
||||
Err(e) => {
|
||||
// Update widget
|
||||
let status_page =
|
||||
content.to_status_failure();
|
||||
status_page.set_description(Some(
|
||||
e.message(),
|
||||
));
|
||||
|
||||
// Update meta
|
||||
status.replace(Status::Failure {
|
||||
time: now(),
|
||||
});
|
||||
title.replace(status_page.title());
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
// Update widget
|
||||
let status_page = content.to_status_failure();
|
||||
status_page.set_description(Some(&e.to_string()));
|
||||
|
||||
// Update meta
|
||||
status.replace(Status::Failure { time: now() });
|
||||
title.replace(status_page.title());
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => todo!(), // unexpected
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Update `Self` witch children components
|
||||
pub fn update(&self) {
|
||||
// Update components
|
||||
@ -652,3 +297,347 @@ fn snap_history(profile: &Profile, navigation: &Navigation, uri: Option<&Uri>) {
|
||||
navigation.history.add(request, true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigate home URL (parsed from current navigation entry)
|
||||
/// * this method create new history record in memory as defined in `action_page_open` action
|
||||
pub fn home(page: &Rc<Page>) {
|
||||
if let Some(url) = page.navigation.home.url() {
|
||||
// Update navigation entry
|
||||
page.navigation.request.widget.entry.set_text(&url);
|
||||
|
||||
// Load page (with history record)
|
||||
load(page, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigate back in history
|
||||
/// * this method does not create new history record in memory
|
||||
pub fn history_back(page: &Rc<Page>) {
|
||||
if let Some(request) = page.navigation.history.back(true) {
|
||||
// Update navigation entry
|
||||
page.navigation.request.widget.entry.set_text(&request);
|
||||
|
||||
// Load page (without history record)
|
||||
load(page, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigate forward in history
|
||||
/// * this method does not create new history record in memory
|
||||
pub fn history_forward(page: &Rc<Page>) {
|
||||
if let Some(request) = page.navigation.history.forward(true) {
|
||||
// Update navigation entry
|
||||
page.navigation.request.widget.entry.set_text(&request);
|
||||
|
||||
// Load page (without history record)
|
||||
load(page, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Page load function with recursive redirection support
|
||||
pub fn load(page: &Rc<Page>, is_history: bool) {
|
||||
// Move focus out from navigation entry
|
||||
page.browser_action
|
||||
.escape
|
||||
.activate_stateful_once(Some(page.id.as_str().into()));
|
||||
|
||||
// Initially disable find action
|
||||
page.window_action.find.simple_action.set_enabled(false);
|
||||
|
||||
// Reset widgets
|
||||
page.search.unset();
|
||||
page.input.unset();
|
||||
|
||||
// Update
|
||||
page.status.replace(Status::Loading { time: now() });
|
||||
page.title.replace("Loading..".into());
|
||||
page.browser_action.update.activate(Some(&page.id));
|
||||
|
||||
if is_history {
|
||||
snap_history(&page.profile, &page.navigation, None); // @TODO
|
||||
}
|
||||
|
||||
use client::response::{Certificate, Failure, Input, Redirect};
|
||||
use client::Response;
|
||||
|
||||
page.client
|
||||
.request(&page.navigation.request.widget.entry.text(), {
|
||||
let page = page.clone();
|
||||
move |response| {
|
||||
match response {
|
||||
Response::Certificate(this) => match this {
|
||||
Certificate::Invalid {
|
||||
title: certificate_title,
|
||||
}
|
||||
| Certificate::Request {
|
||||
title: certificate_title,
|
||||
}
|
||||
| Certificate::Unauthorized {
|
||||
title: certificate_title,
|
||||
} => {
|
||||
// Update widget
|
||||
let status = page.content.to_status_identity();
|
||||
status.set_description(Some(&certificate_title));
|
||||
|
||||
// Update meta
|
||||
page.status.replace(Status::Success { time: now() });
|
||||
page.title.replace(status.title());
|
||||
|
||||
// Update window
|
||||
page.browser_action.update.activate(Some(&page.id));
|
||||
}
|
||||
},
|
||||
Response::Failure(this) => match this {
|
||||
Failure::Status { message } | Failure::Error { message } => {
|
||||
// Update widget
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some(&message));
|
||||
|
||||
// Update meta
|
||||
page.status.replace(Status::Failure { time: now() });
|
||||
page.title.replace(status.title());
|
||||
|
||||
// Update window
|
||||
page.browser_action.update.activate(Some(&page.id));
|
||||
}
|
||||
Failure::Mime { base, mime, message } => {
|
||||
// Update widget
|
||||
let status = page.content.to_status_mime(&mime, Some((&page.tab_action, &base)));
|
||||
status.set_description(Some(&message));
|
||||
|
||||
// Update meta
|
||||
page.status.replace(Status::Failure { time: now() });
|
||||
page.title.replace(status.title());
|
||||
|
||||
// Update window
|
||||
page.browser_action.update.activate(Some(&page.id));
|
||||
}
|
||||
},
|
||||
Response::Input(this) => match this {
|
||||
Input::Response {
|
||||
base,
|
||||
title: response_title,
|
||||
} => {
|
||||
page.input.set_new_response(
|
||||
page.tab_action.clone(),
|
||||
base,
|
||||
Some(&response_title),
|
||||
Some(1024),
|
||||
);
|
||||
|
||||
page.status.replace(Status::Input { time: now() });
|
||||
page.title.replace(response_title);
|
||||
|
||||
page.browser_action.update.activate(Some(&page.id));
|
||||
}
|
||||
Input::Sensitive {
|
||||
base,
|
||||
title: response_title,
|
||||
} => {
|
||||
page.input.set_new_sensitive(
|
||||
page.tab_action.clone(),
|
||||
base,
|
||||
Some(&response_title),
|
||||
Some(1024),
|
||||
);
|
||||
|
||||
page.status.replace(Status::Input { time: now() });
|
||||
page.title.replace(response_title);
|
||||
|
||||
page.browser_action.update.activate(Some(&page.id));
|
||||
}
|
||||
Input::Titan { .. } => {
|
||||
page.input.set_new_titan(move |_| todo!());
|
||||
|
||||
page.status.replace(Status::Input { time: now() });
|
||||
page.title.replace("Titan input".into());
|
||||
|
||||
page.browser_action.update.activate(Some(&page.id));
|
||||
}
|
||||
},
|
||||
Response::Redirect(this) => match this {
|
||||
Redirect::Background(request) => {
|
||||
println!("{}",request.as_uri());load(&page, false)
|
||||
}, // @TODO
|
||||
Redirect::Foreground(request) => {page.navigation
|
||||
.request
|
||||
.widget
|
||||
.entry
|
||||
.set_text(&request.as_uri().to_string())} // @TODO handle
|
||||
}
|
||||
Response::TextGemini { base, source, is_source_request } => {
|
||||
let widget = if is_source_request {
|
||||
page.content.to_text_source(&source)
|
||||
} else {
|
||||
page.content.to_text_gemini(&base, &source)
|
||||
};
|
||||
|
||||
// Connect `TextView` widget, update `search` model
|
||||
page.search.set(Some(widget.text_view));
|
||||
|
||||
// Update page meta
|
||||
page.status.replace(Status::Success { time: now() });
|
||||
page.title.replace(match widget.meta.title {
|
||||
Some(title) => title.into(), // @TODO
|
||||
None => uri_to_title(&base),
|
||||
});
|
||||
|
||||
// Update window components
|
||||
page.window_action.find.simple_action.set_enabled(true);
|
||||
page.browser_action.update.activate(Some(&page.id));
|
||||
}
|
||||
Response::Download { base, cancellable, stream } => {
|
||||
// Init download widget
|
||||
let status = page.content.to_status_download(
|
||||
uri_to_title(&base).trim_matches(MAIN_SEPARATOR), // grab default filename from base URI,
|
||||
// format FS entities
|
||||
&cancellable,
|
||||
{
|
||||
let cancellable = cancellable.clone();
|
||||
let stream = stream.clone();
|
||||
move |file, action| {
|
||||
match file.replace(
|
||||
None,
|
||||
false,
|
||||
gtk::gio::FileCreateFlags::NONE,
|
||||
Some(&cancellable)
|
||||
) {
|
||||
Ok(file_output_stream) => {
|
||||
// Asynchronously read [IOStream](https://docs.gtk.org/gio/class.IOStream.html)
|
||||
// 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
|
||||
ggemini::gio::file_output_stream::move_all_from_stream_async(
|
||||
stream.clone(),
|
||||
file_output_stream,
|
||||
cancellable.clone(),
|
||||
Priority::DEFAULT,
|
||||
(
|
||||
0x100000, // 1M bytes per chunk
|
||||
None, // unlimited
|
||||
0 // initial totals
|
||||
),
|
||||
(
|
||||
// on chunk
|
||||
{
|
||||
let action = action.clone();
|
||||
move |_, total| action.update.activate(
|
||||
&format!(
|
||||
"Received {}...",
|
||||
crate::tool::format_bytes(total)
|
||||
)
|
||||
)
|
||||
},
|
||||
// on complete
|
||||
{
|
||||
let action = action.clone();
|
||||
move |result| match result {
|
||||
Ok((_, total)) => action.complete.activate(
|
||||
&format!("Saved to {} ({total} bytes total)", file.parse_name())
|
||||
),
|
||||
Err(e) => action.cancel.activate(&e.to_string())
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
Err(e) => action.cancel.activate(&e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Update meta
|
||||
page.status.replace(Status::Success { time: now() });
|
||||
page.title.replace(status.title());
|
||||
|
||||
// Update window
|
||||
page.browser_action.update.activate(Some(&page.id));
|
||||
}
|
||||
Response::Stream { base, mime, stream, cancellable } => match mime.as_str() {
|
||||
// @TODO use client-side const or enum?
|
||||
"image/png" | "image/gif" | "image/jpeg" | "image/webp" => {
|
||||
// Final image size unknown, show loading widget
|
||||
let status = page.content.to_status_loading(
|
||||
Some(Duration::from_secs(1)), // show if download time > 1 second
|
||||
);
|
||||
|
||||
// Asynchronously read [IOStream](https://docs.gtk.org/gio/class.IOStream.html)
|
||||
// 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
|
||||
ggemini::gio::memory_input_stream::from_stream_async(
|
||||
stream,
|
||||
cancellable.clone(),
|
||||
Priority::DEFAULT,
|
||||
0x400, // 1024 bytes per chunk, optional step for images download tracking
|
||||
0xA00000, // 10M bytes max to prevent memory overflow if server play with promises
|
||||
move |_, total| {
|
||||
// Update loading progress
|
||||
status.set_description(Some(&format!(
|
||||
"Download: {total} bytes"
|
||||
)));
|
||||
},
|
||||
{
|
||||
let page = page.clone();
|
||||
move |result| match result {
|
||||
Ok((memory_input_stream, _)) => {
|
||||
Pixbuf::from_stream_async(
|
||||
&memory_input_stream,
|
||||
Some(&cancellable),
|
||||
move |result| {
|
||||
// Process buffer data
|
||||
match result {
|
||||
Ok(buffer) => {
|
||||
// Update page meta
|
||||
page.status.replace(Status::Success {
|
||||
time: now(),
|
||||
});
|
||||
page.title.replace(uri_to_title(&base));
|
||||
|
||||
// Update page content
|
||||
page.content.to_image(
|
||||
&Texture::for_pixbuf(&buffer),
|
||||
);
|
||||
|
||||
// Update window components
|
||||
page.browser_action
|
||||
.update
|
||||
.activate(Some(&page.id));
|
||||
}
|
||||
Err(e) => {
|
||||
// Update widget
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some(
|
||||
e.message(),
|
||||
));
|
||||
|
||||
// Update meta
|
||||
page.status.replace(Status::Failure {
|
||||
time: now(),
|
||||
});
|
||||
page.title.replace(status.title());
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
// Update widget
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some(&e.to_string()));
|
||||
|
||||
// Update meta
|
||||
page.status.replace(Status::Failure { time: now() });
|
||||
page.title.replace(status.title());
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => todo!(), // unexpected
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ pub enum Status {
|
||||
Input { time: DateTime },
|
||||
Loading { time: DateTime },
|
||||
New { time: DateTime },
|
||||
Redirect { time: DateTime },
|
||||
SessionRestore { time: DateTime },
|
||||
SessionRestored { time: DateTime },
|
||||
Success { time: DateTime },
|
||||
@ -40,10 +39,7 @@ impl Status {
|
||||
Gemini::Complete { .. } => Some(0.9),
|
||||
},
|
||||
},
|
||||
Self::Failure { .. }
|
||||
| Self::Success { .. }
|
||||
| Self::Redirect { .. }
|
||||
| Self::Input { .. } => Some(1.0),
|
||||
Self::Failure { .. } | Self::Success { .. } | Self::Input { .. } => Some(1.0),
|
||||
Self::New { .. } | Self::SessionRestored { .. } => None,
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user