mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-04-01 17:15:28 +00:00
reorganize page loading methods
This commit is contained in:
parent
8d3c4319c6
commit
0ff182f15d
4 changed files with 354 additions and 369 deletions
|
|
@ -219,7 +219,7 @@ impl Tab {
|
||||||
pub fn save_as(&self, page_position: Option<i32>) {
|
pub fn save_as(&self, page_position: Option<i32>) {
|
||||||
if let Some(item) = self.item(page_position) {
|
if let Some(item) = self.item(page_position) {
|
||||||
item.page.navigation.request.to_download();
|
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>) {
|
pub fn source(&self, page_position: Option<i32>) {
|
||||||
if let Some(item) = self.item(page_position) {
|
if let Some(item) = self.item(page_position) {
|
||||||
item.page.navigation.request.to_source();
|
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>) {
|
pub fn page_home(&self, page_position: Option<i32>) {
|
||||||
if let Some(item) = self.item(page_position) {
|
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>) {
|
pub fn page_history_back(&self, page_position: Option<i32>) {
|
||||||
if let Some(item) = self.item(page_position) {
|
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>) {
|
pub fn page_history_forward(&self, page_position: Option<i32>) {
|
||||||
if let Some(item) = self.item(page_position) {
|
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
|
/// Reload page at `i32` position or selected page on `None` given
|
||||||
pub fn page_reload(&self, page_position: Option<i32>) {
|
pub fn page_reload(&self, page_position: Option<i32>) {
|
||||||
if let Some(item) = self.item(page_position) {
|
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 action;
|
||||||
mod database;
|
mod database;
|
||||||
mod identity;
|
mod identity;
|
||||||
mod page;
|
pub mod page;
|
||||||
mod widget;
|
mod widget;
|
||||||
|
|
||||||
use action::Action;
|
use action::Action;
|
||||||
|
|
@ -75,7 +75,7 @@ impl Item {
|
||||||
page.navigation.request.widget.entry.set_text(&text);
|
page.navigation.request.widget.entry.set_text(&text);
|
||||||
|
|
||||||
if is_load {
|
if is_load {
|
||||||
page.load(true);
|
page::load(&page, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +111,7 @@ impl Item {
|
||||||
if let Some(text) = request {
|
if let Some(text) = request {
|
||||||
page.navigation.request.widget.entry.set_text(&text);
|
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()
|
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
|
/// Update `Self` witch children components
|
||||||
pub fn update(&self) {
|
pub fn update(&self) {
|
||||||
// Update components
|
// Update components
|
||||||
|
|
@ -652,3 +297,347 @@ fn snap_history(profile: &Profile, navigation: &Navigation, uri: Option<&Uri>) {
|
||||||
navigation.history.add(request, true)
|
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 },
|
Input { time: DateTime },
|
||||||
Loading { time: DateTime },
|
Loading { time: DateTime },
|
||||||
New { time: DateTime },
|
New { time: DateTime },
|
||||||
Redirect { time: DateTime },
|
|
||||||
SessionRestore { time: DateTime },
|
SessionRestore { time: DateTime },
|
||||||
SessionRestored { time: DateTime },
|
SessionRestored { time: DateTime },
|
||||||
Success { time: DateTime },
|
Success { time: DateTime },
|
||||||
|
|
@ -40,10 +39,7 @@ impl Status {
|
||||||
Gemini::Complete { .. } => Some(0.9),
|
Gemini::Complete { .. } => Some(0.9),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Self::Failure { .. }
|
Self::Failure { .. } | Self::Success { .. } | Self::Input { .. } => Some(1.0),
|
||||||
| Self::Success { .. }
|
|
||||||
| Self::Redirect { .. }
|
|
||||||
| Self::Input { .. } => Some(1.0),
|
|
||||||
Self::New { .. } | Self::SessionRestored { .. } => None,
|
Self::New { .. } | Self::SessionRestored { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue