mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-09-12 23:12:00 +00:00
remove Subject
struct, use public Page
API for Client
driver, remove widget
mod
This commit is contained in:
parent
6945aaebc5
commit
ed1dbd421c
@ -142,7 +142,7 @@ impl Tab {
|
||||
// Register dynamically created tab components in the HashMap index
|
||||
self.index
|
||||
.borrow_mut()
|
||||
.insert(item.widget.tab_page.clone(), item.clone());
|
||||
.insert(item.page.tab_page.clone(), item.clone());
|
||||
|
||||
update_actions(
|
||||
&self.tab_view,
|
||||
@ -305,7 +305,7 @@ impl Tab {
|
||||
// Register dynamically created tab item in the HashMap index
|
||||
self.index
|
||||
.borrow_mut()
|
||||
.insert(item.widget.tab_page.clone(), item.clone());
|
||||
.insert(item.page.tab_page.clone(), item.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e.to_string()),
|
||||
@ -333,10 +333,10 @@ impl Tab {
|
||||
item.save(
|
||||
transaction,
|
||||
id,
|
||||
self.tab_view.page_position(&item.widget.tab_page),
|
||||
item.widget.tab_page.is_pinned(),
|
||||
item.widget.tab_page.is_selected(),
|
||||
item.widget.tab_page.needs_attention(),
|
||||
self.tab_view.page_position(&item.page.tab_page),
|
||||
item.page.tab_page.is_pinned(),
|
||||
item.page.tab_page.is_selected(),
|
||||
item.page.tab_page.needs_attention(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ mod client;
|
||||
mod database;
|
||||
mod identity;
|
||||
mod page;
|
||||
mod widget;
|
||||
|
||||
use super::{Action as TabAction, BrowserAction, Position, WindowAction};
|
||||
use crate::Profile;
|
||||
@ -14,14 +13,12 @@ use gtk::prelude::{ActionMapExt, Cast};
|
||||
use page::Page;
|
||||
use sqlite::Transaction;
|
||||
use std::rc::Rc;
|
||||
use widget::Widget;
|
||||
|
||||
pub struct Item {
|
||||
// Multi-protocol handler
|
||||
pub client: Rc<Client>,
|
||||
// Components
|
||||
pub page: Rc<Page>,
|
||||
pub widget: Rc<Widget>,
|
||||
pub action: Rc<Action>,
|
||||
}
|
||||
|
||||
@ -37,7 +34,7 @@ impl Item {
|
||||
&Rc<WindowAction>,
|
||||
&Rc<TabAction>,
|
||||
),
|
||||
(position, request, is_pinned, is_selected, is_attention, is_load): (
|
||||
(position, request, is_pinned, is_selected, is_needs_attention, is_load): (
|
||||
Position,
|
||||
Option<&str>,
|
||||
bool,
|
||||
@ -63,18 +60,12 @@ impl Item {
|
||||
let page = Rc::new(Page::build(
|
||||
profile,
|
||||
(browser_action, window_action, tab_action, &action),
|
||||
));
|
||||
|
||||
let widget = Rc::new(Widget::build(
|
||||
tab_view,
|
||||
&page.g_box,
|
||||
None,
|
||||
position,
|
||||
(is_pinned, is_selected, is_attention),
|
||||
(position, is_pinned, is_selected, is_needs_attention),
|
||||
));
|
||||
|
||||
// Update tab loading indicator
|
||||
let client = Rc::new(Client::init(&page, &widget.tab_page));
|
||||
let client = Rc::new(Client::init(&page));
|
||||
|
||||
// Connect events
|
||||
action.home.connect_activate({
|
||||
@ -143,7 +134,6 @@ impl Item {
|
||||
Self {
|
||||
client,
|
||||
page,
|
||||
widget,
|
||||
action,
|
||||
}
|
||||
}
|
||||
@ -160,7 +150,6 @@ impl Item {
|
||||
Ok(_) => {
|
||||
// Delegate clean action to the item childs
|
||||
self.page.clean(transaction, record.id)?;
|
||||
self.widget.clean(transaction, record.id)?;
|
||||
}
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
@ -203,14 +192,13 @@ impl Item {
|
||||
None,
|
||||
record.is_pinned,
|
||||
record.is_selected,
|
||||
record.is_attention,
|
||||
record.is_needs_attention,
|
||||
false,
|
||||
),
|
||||
));
|
||||
|
||||
// Delegate restore action to the item childs
|
||||
item.page.restore(transaction, record.id)?;
|
||||
item.widget.restore(transaction, record.id)?;
|
||||
|
||||
// Result
|
||||
items.push(item);
|
||||
@ -229,7 +217,7 @@ impl Item {
|
||||
page_position: i32,
|
||||
is_pinned: bool,
|
||||
is_selected: bool,
|
||||
is_attention: bool,
|
||||
is_needs_attention: bool,
|
||||
) -> Result<(), String> {
|
||||
match database::insert(
|
||||
transaction,
|
||||
@ -237,14 +225,13 @@ impl Item {
|
||||
page_position,
|
||||
is_pinned,
|
||||
is_selected,
|
||||
is_attention,
|
||||
is_needs_attention,
|
||||
) {
|
||||
Ok(_) => {
|
||||
let id = database::last_insert_id(transaction);
|
||||
|
||||
// Delegate save action to childs
|
||||
self.page.save(transaction, id)?;
|
||||
self.widget.save(transaction, id)?;
|
||||
}
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
@ -262,7 +249,6 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> {
|
||||
|
||||
// Delegate migration to childs
|
||||
page::migrate(tx)?;
|
||||
widget::migrate(tx)?;
|
||||
|
||||
// Success
|
||||
Ok(())
|
||||
|
@ -1,9 +1,7 @@
|
||||
mod driver;
|
||||
mod feature;
|
||||
mod subject;
|
||||
|
||||
use super::Page;
|
||||
use adw::TabPage;
|
||||
use driver::Driver;
|
||||
use feature::Feature;
|
||||
use gtk::{
|
||||
@ -12,29 +10,23 @@ use gtk::{
|
||||
prelude::{ActionExt, CancellableExt},
|
||||
};
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
use subject::Subject;
|
||||
|
||||
/// Multi-protocol client API for tab `Item`
|
||||
pub struct Client {
|
||||
cancellable: Cell<Cancellable>,
|
||||
driver: Rc<Driver>,
|
||||
subject: Rc<Subject>,
|
||||
page: Rc<Page>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn init(page: &Rc<Page>, tab_page: &TabPage) -> Self {
|
||||
let subject = Rc::new(Subject {
|
||||
page: page.clone(),
|
||||
tab_page: tab_page.clone(),
|
||||
});
|
||||
|
||||
pub fn init(page: &Rc<Page>) -> Self {
|
||||
Self {
|
||||
cancellable: Cell::new(Cancellable::new()),
|
||||
driver: Rc::new(Driver::build(&subject)),
|
||||
subject,
|
||||
driver: Rc::new(Driver::build(page)),
|
||||
page: page.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,32 +36,29 @@ impl Client {
|
||||
/// * or `navigation` entry if the value not provided
|
||||
pub fn handle(&self, request: &str, is_snap_history: bool) {
|
||||
// Move focus out from navigation entry @TODO
|
||||
self.subject.page.browser_action.escape.activate(None);
|
||||
self.page.browser_action.escape.activate(None);
|
||||
|
||||
// Initially disable find action
|
||||
self.subject
|
||||
.page
|
||||
self.page
|
||||
.window_action
|
||||
.find
|
||||
.simple_action
|
||||
.set_enabled(false);
|
||||
|
||||
// Reset widgets
|
||||
self.subject.page.search.unset();
|
||||
self.subject.page.input.unset();
|
||||
self.subject.tab_page.set_title("Loading..");
|
||||
self.subject.page.navigation.set_progress_fraction(0.1);
|
||||
|
||||
self.subject.tab_page.set_loading(true);
|
||||
self.page.search.unset();
|
||||
self.page.input.unset();
|
||||
self.page.set_title("Loading..");
|
||||
self.page.set_progress(0.1);
|
||||
|
||||
if is_snap_history {
|
||||
snap_history(&self.subject, None);
|
||||
snap_history(&self.page, None);
|
||||
}
|
||||
|
||||
// run async resolver to detect Uri, scheme-less host, or search query
|
||||
lookup(request, self.cancellable(), {
|
||||
let driver = self.driver.clone();
|
||||
let subject = self.subject.clone();
|
||||
let page = self.page.clone();
|
||||
move |feature, cancellable, result| {
|
||||
match result {
|
||||
// route by scheme
|
||||
@ -77,18 +66,16 @@ impl Client {
|
||||
"gemini" | "titan" => driver.gemini.handle(uri, feature, cancellable),
|
||||
scheme => {
|
||||
// no scheme match driver, complete with failure message
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some(&format!(
|
||||
"Scheme `{scheme}` yet not supported"
|
||||
)));
|
||||
subject.tab_page.set_title(&status.title());
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
}
|
||||
},
|
||||
// begin redirection to new address suggested
|
||||
Err(uri) => subject
|
||||
.page
|
||||
Err(uri) => page
|
||||
.item_action
|
||||
.load
|
||||
.activate(Some(&uri.to_string()), false),
|
||||
@ -190,22 +177,22 @@ fn search(query: &str) -> Uri {
|
||||
|
||||
/// Make new history record in related components
|
||||
/// * optional [Uri](https://docs.gtk.org/glib/struct.Uri.html) reference wanted only for performance reasons, to not parse it twice
|
||||
fn snap_history(subject: &Rc<Subject>, uri: Option<&Uri>) {
|
||||
let request = subject.page.navigation.request();
|
||||
fn snap_history(page: &Page, uri: Option<&Uri>) {
|
||||
let request = page.navigation.request();
|
||||
|
||||
// Add new record into the global memory index (used in global menu)
|
||||
// * if the `Uri` is `None`, try parse it from `request`
|
||||
match uri {
|
||||
Some(uri) => subject.page.profile.history.memory.request.set(uri.clone()),
|
||||
Some(uri) => page.profile.history.memory.request.set(uri.clone()),
|
||||
None => {
|
||||
// this case especially useful for some routes that contain redirects
|
||||
// maybe some parental optimization wanted @TODO
|
||||
if let Some(uri) = subject.page.navigation.uri() {
|
||||
subject.page.profile.history.memory.request.set(uri);
|
||||
if let Some(uri) = page.navigation.uri() {
|
||||
page.profile.history.memory.request.set(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add new record into the page navigation history
|
||||
subject.page.item_action.history.add(request, true)
|
||||
page.item_action.history.add(request, true)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
mod gemini;
|
||||
|
||||
use super::{Feature, Subject};
|
||||
use super::{Feature, Page};
|
||||
use gemini::Gemini;
|
||||
use std::rc::Rc;
|
||||
|
||||
@ -13,9 +13,9 @@ impl Driver {
|
||||
// Constructors
|
||||
|
||||
/// Build new `Self`
|
||||
pub fn build(subject: &Rc<Subject>) -> Self {
|
||||
pub fn build(page: &Rc<Page>) -> Self {
|
||||
Driver {
|
||||
gemini: Gemini::init(subject),
|
||||
gemini: Gemini::init(page),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
use super::{Feature, Subject};
|
||||
use super::{Feature, Page};
|
||||
use ggemini::client::{
|
||||
connection::response::{data::Text, meta::Status},
|
||||
Client, Request,
|
||||
};
|
||||
use gtk::glib::Bytes;
|
||||
use gtk::glib::{GString, UriFlags};
|
||||
use gtk::{
|
||||
gdk::Texture,
|
||||
gdk_pixbuf::Pixbuf,
|
||||
gio::{Cancellable, SocketClientEvent},
|
||||
glib::{Priority, Uri},
|
||||
prelude::SocketClientExt,
|
||||
prelude::{FileExt, SocketClientExt},
|
||||
};
|
||||
use gtk::{glib::Bytes, prelude::FileExt};
|
||||
use std::{cell::Cell, path::MAIN_SEPARATOR, rc::Rc, time::Duration};
|
||||
|
||||
/// Multi-protocol client API for `Page` object
|
||||
@ -21,22 +21,22 @@ pub struct Gemini {
|
||||
/// Validate redirection count by Gemini protocol specification
|
||||
redirects: Rc<Cell<usize>>,
|
||||
/// Handle target
|
||||
subject: Rc<Subject>,
|
||||
page: Rc<Page>,
|
||||
}
|
||||
|
||||
impl Gemini {
|
||||
// Constructors
|
||||
|
||||
/// Create new `Self`
|
||||
pub fn init(subject: &Rc<Subject>) -> Self {
|
||||
pub fn init(page: &Rc<Page>) -> Self {
|
||||
// Init supported protocol libraries
|
||||
let client = Rc::new(ggemini::Client::new());
|
||||
|
||||
// Listen for [SocketClient](https://docs.gtk.org/gio/class.SocketClient.html) updates
|
||||
client.socket.connect_event({
|
||||
let subject = subject.clone();
|
||||
let page = page.clone();
|
||||
move |_, event, _, _| {
|
||||
let progress_fraction = match event {
|
||||
page.set_progress(match event {
|
||||
// 0.1 reserved for handle begin
|
||||
SocketClientEvent::Resolving => 0.2,
|
||||
SocketClientEvent::Resolved => 0.3,
|
||||
@ -49,21 +49,14 @@ impl Gemini {
|
||||
SocketClientEvent::TlsHandshaked => 0.9,
|
||||
SocketClientEvent::Complete => 1.0,
|
||||
_ => todo!(), // alert on API change
|
||||
};
|
||||
|
||||
subject.tab_page.set_loading(progress_fraction > 0.0);
|
||||
|
||||
subject
|
||||
.page
|
||||
.navigation
|
||||
.set_progress_fraction(progress_fraction);
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
client,
|
||||
redirects: Rc::new(Cell::new(0)),
|
||||
subject: subject.clone(),
|
||||
page: page.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,15 +69,15 @@ impl Gemini {
|
||||
"gemini" => handle(
|
||||
Request::Gemini { uri },
|
||||
self.client.clone(),
|
||||
self.subject.clone(),
|
||||
self.page.clone(),
|
||||
self.redirects.clone(),
|
||||
feature,
|
||||
cancellable,
|
||||
),
|
||||
"titan" => {
|
||||
self.subject.page.input.set_new_titan({
|
||||
self.page.input.set_new_titan({
|
||||
let client = self.client.clone();
|
||||
let subject = self.subject.clone();
|
||||
let page = self.page.clone();
|
||||
let redirects = self.redirects.clone();
|
||||
move |data, _label| {
|
||||
handle(
|
||||
@ -96,7 +89,7 @@ impl Gemini {
|
||||
token: None, // @TODO
|
||||
},
|
||||
client.clone(),
|
||||
subject.clone(),
|
||||
page.clone(),
|
||||
redirects.clone(),
|
||||
feature.clone(),
|
||||
cancellable.clone(),
|
||||
@ -123,9 +116,8 @@ impl Gemini {
|
||||
todo!()*/
|
||||
}
|
||||
});
|
||||
self.subject.tab_page.set_title("Titan input");
|
||||
self.subject.page.navigation.set_progress_fraction(0.0);
|
||||
self.subject.tab_page.set_loading(false);
|
||||
self.page.set_title("Titan input");
|
||||
self.page.set_progress(0.0);
|
||||
}
|
||||
_ => panic!(), // unexpected
|
||||
}
|
||||
@ -135,7 +127,7 @@ impl Gemini {
|
||||
fn handle(
|
||||
request: Request,
|
||||
client: Rc<Client>,
|
||||
subject: Rc<Subject>,
|
||||
page: Rc<Page>,
|
||||
redirects: Rc<Cell<usize>>,
|
||||
feature: Rc<Feature>,
|
||||
cancellable: Cancellable,
|
||||
@ -147,8 +139,7 @@ fn handle(
|
||||
cancellable.clone(),
|
||||
// Search for user certificate match request
|
||||
// * @TODO this feature does not support multi-protocol yet
|
||||
match subject
|
||||
.page
|
||||
match page
|
||||
.profile
|
||||
.identity
|
||||
.get(&uri.to_string())
|
||||
@ -160,12 +151,12 @@ fn handle(
|
||||
None => None,
|
||||
},
|
||||
{
|
||||
let subject = subject.clone();
|
||||
let page = page.clone();
|
||||
let redirects = redirects.clone();
|
||||
move |result| {
|
||||
// Remove input forms when redirection expected has not been applied (e.g. failure status)
|
||||
// @TODO implement input data recovery on error (it's also available before unset, but reference lost at this point)
|
||||
subject.page.input.unset();
|
||||
page.input.unset();
|
||||
|
||||
// Begin result handle
|
||||
match result {
|
||||
@ -178,30 +169,29 @@ fn handle(
|
||||
None => Status::Input.to_string(),
|
||||
};
|
||||
if matches!(response.meta.status, Status::SensitiveInput) {
|
||||
subject.page.input.set_new_sensitive(
|
||||
subject.page.item_action.clone(),
|
||||
page.input.set_new_sensitive(
|
||||
page.item_action.clone(),
|
||||
uri,
|
||||
Some(&title),
|
||||
Some(1024),
|
||||
);
|
||||
} else {
|
||||
subject.page.input.set_new_response(
|
||||
subject.page.item_action.clone(),
|
||||
page.input.set_new_response(
|
||||
page.item_action.clone(),
|
||||
uri,
|
||||
Some(&title),
|
||||
Some(1024),
|
||||
);
|
||||
}
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&title);
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&title);
|
||||
redirects.replace(0); // reset
|
||||
}
|
||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
|
||||
Status::Success => match *feature {
|
||||
Feature::Download => {
|
||||
// Init download widget
|
||||
let status = subject.page.content.to_status_download(
|
||||
let status = page.content.to_status_download(
|
||||
uri_to_title(&uri).trim_matches(MAIN_SEPARATOR), // grab default filename from base URI,
|
||||
// format FS entities
|
||||
&cancellable,
|
||||
@ -262,9 +252,8 @@ fn handle(
|
||||
}
|
||||
},
|
||||
);
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
},
|
||||
_ => match response.meta.mime {
|
||||
@ -276,36 +265,34 @@ fn handle(
|
||||
move |result| match result {
|
||||
Ok(text) => {
|
||||
let widget = if matches!(*feature, Feature::Source) {
|
||||
subject.page.content.to_text_source(&text.to_string())
|
||||
page.content.to_text_source(&text.to_string())
|
||||
} else {
|
||||
subject.page.content.to_text_gemini(&uri, &text.to_string())
|
||||
page.content.to_text_gemini(&uri, &text.to_string())
|
||||
};
|
||||
subject.page.search.set(Some(widget.text_view));
|
||||
subject.tab_page.set_title(&match widget.meta.title {
|
||||
page.search.set(Some(widget.text_view));
|
||||
page.set_title(&match widget.meta.title {
|
||||
Some(title) => title.into(), // @TODO
|
||||
None => uri_to_title(&uri),
|
||||
});
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.page.window_action
|
||||
page.set_progress(0.0);
|
||||
page.window_action
|
||||
.find
|
||||
.simple_action
|
||||
.set_enabled(true);
|
||||
redirects.replace(0); // reset
|
||||
}
|
||||
Err(e) => {
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some(&e.to_string()));
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
},
|
||||
},
|
||||
),
|
||||
"image/png" | "image/gif" | "image/jpeg" | "image/webp" => {
|
||||
// Final image size unknown, show loading widget
|
||||
let status = subject.page.content.to_status_loading(
|
||||
let status = page.content.to_status_loading(
|
||||
Some(Duration::from_secs(1)), // show if download time > 1 second
|
||||
);
|
||||
|
||||
@ -322,7 +309,7 @@ fn handle(
|
||||
move |_, total|
|
||||
status.set_description(Some(&format!("Download: {total} bytes"))),
|
||||
{
|
||||
let subject = subject.clone();
|
||||
let page = page.clone();
|
||||
move |result| match result {
|
||||
Ok((memory_input_stream, _)) => {
|
||||
Pixbuf::from_stream_async(
|
||||
@ -331,27 +318,25 @@ fn handle(
|
||||
move |result| {
|
||||
match result {
|
||||
Ok(buffer) => {
|
||||
subject.tab_page.set_title(&uri_to_title(&uri));
|
||||
subject.page.content.to_image(&Texture::for_pixbuf(&buffer));
|
||||
page.set_title(&uri_to_title(&uri));
|
||||
page.content.to_image(&Texture::for_pixbuf(&buffer));
|
||||
}
|
||||
Err(e) => {
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some(e.message()));
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_title(&status.title());
|
||||
}
|
||||
}
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
page.set_progress(0.0);
|
||||
redirects.replace(0); // reset
|
||||
},
|
||||
)
|
||||
}
|
||||
Err(e) => {
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some(&e.to_string()));
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
}
|
||||
}
|
||||
@ -359,22 +344,20 @@ fn handle(
|
||||
)
|
||||
}
|
||||
mime => {
|
||||
let status = subject.page
|
||||
let status = page
|
||||
.content
|
||||
.to_status_mime(mime, Some((&subject.page.item_action, &uri)));
|
||||
.to_status_mime(mime, Some((&page.item_action, &uri)));
|
||||
status.set_description(Some(&format!("Content type `{mime}` yet not supported")));
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
},
|
||||
},
|
||||
None => {
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some("MIME type not found"));
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
},
|
||||
}
|
||||
@ -408,48 +391,43 @@ fn handle(
|
||||
let total = redirects.take() + 1;
|
||||
// Validate total redirects by protocol specification
|
||||
if total > 5 {
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some("Redirection limit reached"));
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
|
||||
// Disallow external redirection by protocol restrictions
|
||||
} else if "gemini" != target.scheme()
|
||||
|| uri.port() != target.port()
|
||||
|| uri.host() != target.host() {
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some("External redirects not allowed by protocol specification"));
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
// Valid
|
||||
} else {
|
||||
if matches!(response.meta.status, Status::PermanentRedirect) {
|
||||
subject.page.navigation
|
||||
.set_request(&uri.to_string());
|
||||
page.navigation.set_request(&uri.to_string());
|
||||
}
|
||||
redirects.replace(total);
|
||||
subject.page.item_action.load.activate(Some(&target.to_string()), false);
|
||||
page.item_action.load.activate(Some(&target.to_string()), false);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some(&e.to_string()));
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some("Redirection target not found"));
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
}
|
||||
}
|
||||
@ -460,33 +438,30 @@ fn handle(
|
||||
Status::CertificateUnauthorized |
|
||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-62-certificate-not-valid
|
||||
Status::CertificateInvalid => {
|
||||
let status = subject.page.content.to_status_identity();
|
||||
let status = page.content.to_status_identity();
|
||||
status.set_description(Some(&match response.meta.data {
|
||||
Some(data) => data.to_string(),
|
||||
None => response.meta.status.to_string(),
|
||||
}));
|
||||
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
}
|
||||
error => {
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some(&error.to_string()));
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
},
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let status = subject.page.content.to_status_failure();
|
||||
let status = page.content.to_status_failure();
|
||||
status.set_description(Some(&e.to_string()));
|
||||
subject.page.navigation.set_progress_fraction(0.0);
|
||||
subject.tab_page.set_loading(false);
|
||||
subject.tab_page.set_title(&status.title());
|
||||
page.set_progress(0.0);
|
||||
page.set_title(&status.title());
|
||||
redirects.replace(0); // reset
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
use super::Page;
|
||||
use adw::TabPage;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// The subject for `Client` handler
|
||||
pub struct Subject {
|
||||
pub page: Rc<Page>,
|
||||
pub tab_page: TabPage,
|
||||
}
|
@ -5,7 +5,7 @@ pub struct Table {
|
||||
// pub app_browser_window_tab_id: i64, not in use
|
||||
pub is_pinned: bool,
|
||||
pub is_selected: bool,
|
||||
pub is_attention: bool,
|
||||
pub is_needs_attention: bool,
|
||||
}
|
||||
|
||||
pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
||||
@ -17,7 +17,7 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
||||
`page_position` INTEGER NOT NULL,
|
||||
`is_pinned` INTEGER NOT NULL,
|
||||
`is_selected` INTEGER NOT NULL,
|
||||
`is_attention` INTEGER NOT NULL,
|
||||
`is_needs_attention` INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (`app_browser_window_tab_id`) REFERENCES `app_browser_window_tab`(`id`)
|
||||
)",
|
||||
@ -31,7 +31,7 @@ pub fn insert(
|
||||
page_position: i32,
|
||||
is_pinned: bool,
|
||||
is_selected: bool,
|
||||
is_attention: bool,
|
||||
is_needs_attention: bool,
|
||||
) -> Result<usize, Error> {
|
||||
tx.execute(
|
||||
"INSERT INTO `app_browser_window_tab_item` (
|
||||
@ -39,14 +39,14 @@ pub fn insert(
|
||||
`page_position`,
|
||||
`is_pinned`,
|
||||
`is_selected`,
|
||||
`is_attention`
|
||||
`is_needs_attention`
|
||||
) VALUES (?, ?, ?, ?, ?)",
|
||||
[
|
||||
app_browser_window_tab_id,
|
||||
page_position as i64,
|
||||
is_pinned as i64,
|
||||
is_selected as i64,
|
||||
is_attention as i64,
|
||||
is_needs_attention as i64,
|
||||
],
|
||||
)
|
||||
}
|
||||
@ -57,7 +57,7 @@ pub fn select(tx: &Transaction, app_browser_window_tab_id: i64) -> Result<Vec<Ta
|
||||
`app_browser_window_tab_id`,
|
||||
`is_pinned`,
|
||||
`is_selected`,
|
||||
`is_attention`
|
||||
`is_needs_attention`
|
||||
FROM `app_browser_window_tab_item`
|
||||
WHERE `app_browser_window_tab_id` = ?
|
||||
ORDER BY `page_position` ASC", // just order by, no store in struct wanted
|
||||
@ -69,7 +69,7 @@ pub fn select(tx: &Transaction, app_browser_window_tab_id: i64) -> Result<Vec<Ta
|
||||
// app_browser_window_tab_id: row.get(1)?, not in use
|
||||
is_pinned: row.get(2)?,
|
||||
is_selected: row.get(3)?,
|
||||
is_attention: row.get(4)?,
|
||||
is_needs_attention: row.get(4)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
|
@ -5,13 +5,15 @@ mod input;
|
||||
mod navigation;
|
||||
mod search;
|
||||
|
||||
use super::{Action as ItemAction, BrowserAction, Profile, TabAction, WindowAction};
|
||||
use super::{Action as ItemAction, BrowserAction, Position, Profile, TabAction, WindowAction};
|
||||
use adw::{TabPage, TabView};
|
||||
use content::Content;
|
||||
use error::Error;
|
||||
use gtk::{prelude::BoxExt, Box, Orientation};
|
||||
use input::Input;
|
||||
use navigation::Navigation;
|
||||
use search::Search;
|
||||
use sourceview::prelude::IsA;
|
||||
use sqlite::Transaction;
|
||||
use std::rc::Rc;
|
||||
|
||||
@ -26,7 +28,8 @@ pub struct Page {
|
||||
pub search: Rc<Search>,
|
||||
pub input: Rc<Input>,
|
||||
pub navigation: Rc<Navigation>,
|
||||
pub g_box: Box,
|
||||
// System
|
||||
pub tab_page: TabPage,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
@ -40,17 +43,16 @@ impl Page {
|
||||
&Rc<TabAction>,
|
||||
&Rc<ItemAction>,
|
||||
),
|
||||
tab_view: &TabView,
|
||||
(position, is_pinned, is_selected, is_needs_attention): (Position, bool, bool, bool),
|
||||
) -> Self {
|
||||
// Init components
|
||||
let content = Rc::new(Content::build((window_action, item_action)));
|
||||
|
||||
let search = Rc::new(Search::new());
|
||||
|
||||
let navigation = Rc::new(Navigation::build(
|
||||
profile,
|
||||
(window_action, tab_action, item_action),
|
||||
));
|
||||
|
||||
let input = Rc::new(Input::new());
|
||||
|
||||
// Init main widget
|
||||
@ -61,9 +63,29 @@ impl Page {
|
||||
g_box.append(&search.g_box);
|
||||
g_box.append(&input.clamp);
|
||||
|
||||
// Generate `TabPage` by append widget into given `TabView`
|
||||
let tab_page = match position {
|
||||
Position::After => match tab_view.selected_page() {
|
||||
Some(page) => add(tab_view, &g_box, tab_view.page_position(&page) + 1),
|
||||
None => tab_view.append(&g_box),
|
||||
},
|
||||
Position::End => tab_view.append(&g_box),
|
||||
Position::Number(value) => add(tab_view, &g_box, value),
|
||||
};
|
||||
|
||||
// Setup
|
||||
tab_page.set_needs_attention(is_needs_attention);
|
||||
tab_page.set_title("New page");
|
||||
|
||||
tab_view.set_page_pinned(&tab_page, is_pinned);
|
||||
if is_selected {
|
||||
tab_view.set_selected_page(&tab_page);
|
||||
}
|
||||
|
||||
// Done
|
||||
Self {
|
||||
profile: profile.clone(),
|
||||
tab_page: tab_page.clone(),
|
||||
// Actions
|
||||
browser_action: browser_action.clone(),
|
||||
item_action: item_action.clone(),
|
||||
@ -73,8 +95,6 @@ impl Page {
|
||||
search,
|
||||
input,
|
||||
navigation,
|
||||
// Widget
|
||||
g_box,
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,6 +157,10 @@ impl Page {
|
||||
match database::select(transaction, app_browser_window_tab_item_id) {
|
||||
Ok(records) => {
|
||||
for record in records {
|
||||
// Restore main widget
|
||||
if let Some(title) = record.title {
|
||||
self.set_title(title.as_str());
|
||||
}
|
||||
// Restore self by last record
|
||||
// Delegate restore action to the item childs
|
||||
self.navigation.restore(transaction, &record.id)?;
|
||||
@ -158,7 +182,17 @@ impl Page {
|
||||
transaction: &Transaction,
|
||||
app_browser_window_tab_item_id: i64,
|
||||
) -> Result<(), String> {
|
||||
match database::insert(transaction, app_browser_window_tab_item_id) {
|
||||
// Keep value in memory until operation complete
|
||||
let title = self.tab_page.title();
|
||||
|
||||
match database::insert(
|
||||
transaction,
|
||||
app_browser_window_tab_item_id,
|
||||
match title.is_empty() {
|
||||
true => None,
|
||||
false => Some(title.as_str()),
|
||||
},
|
||||
) {
|
||||
Ok(_) => {
|
||||
let id = database::last_insert_id(transaction);
|
||||
|
||||
@ -170,6 +204,19 @@ impl Page {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Setters
|
||||
|
||||
/// Set title for `Self`
|
||||
/// * this method allows to keep `tab_page` isolated from driver implementation
|
||||
pub fn set_title(&self, title: &str) {
|
||||
self.tab_page.set_title(title);
|
||||
}
|
||||
|
||||
pub fn set_progress(&self, progress_fraction: f64) {
|
||||
self.navigation.set_progress_fraction(progress_fraction);
|
||||
self.tab_page.set_loading(progress_fraction > 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Tools
|
||||
@ -186,3 +233,17 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> {
|
||||
// Success
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create new [TabPage](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabPage.html)
|
||||
/// in [TabView](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabView.html) at given position
|
||||
///
|
||||
/// * if given `position` match pinned tab, GTK will panic with notice:
|
||||
/// adw_tab_view_insert: assertion 'position >= self->n_pinned_pages'\
|
||||
/// as the solution, prepend new page after pinned tabs in this case
|
||||
fn add(tab_view: &TabView, child: &impl IsA<gtk::Widget>, position: i32) -> TabPage {
|
||||
if position > tab_view.n_pinned_pages() {
|
||||
tab_view.insert(child, position)
|
||||
} else {
|
||||
tab_view.prepend(child)
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use sqlite::{Error, Transaction};
|
||||
pub struct Table {
|
||||
pub id: i64,
|
||||
// pub app_browser_window_tab_item_id: i64, not in use,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
||||
@ -11,6 +12,7 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
||||
(
|
||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`app_browser_window_tab_item_id` INTEGER NOT NULL,
|
||||
`title` VARCHAR(1024),
|
||||
|
||||
FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item`(`id`)
|
||||
)",
|
||||
@ -18,19 +20,25 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn insert(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<usize, Error> {
|
||||
pub fn insert(
|
||||
tx: &Transaction,
|
||||
app_browser_window_tab_item_id: i64,
|
||||
title: Option<&str>,
|
||||
) -> Result<usize, Error> {
|
||||
tx.execute(
|
||||
"INSERT INTO `app_browser_window_tab_item_page` (
|
||||
`app_browser_window_tab_item_id`
|
||||
) VALUES (?)",
|
||||
[app_browser_window_tab_item_id],
|
||||
`app_browser_window_tab_item_id`,
|
||||
`title`
|
||||
) VALUES (?, ?)",
|
||||
(app_browser_window_tab_item_id, title),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<Vec<Table>, Error> {
|
||||
let mut stmt = tx.prepare(
|
||||
"SELECT `id`,
|
||||
`app_browser_window_tab_item_id`
|
||||
`app_browser_window_tab_item_id`,
|
||||
`title`
|
||||
FROM `app_browser_window_tab_item_page`
|
||||
WHERE `app_browser_window_tab_item_id` = ?",
|
||||
)?;
|
||||
@ -39,6 +47,7 @@ pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<V
|
||||
Ok(Table {
|
||||
id: row.get(0)?,
|
||||
// app_browser_window_tab_item_id: row.get(1)?, not in use
|
||||
title: row.get(2)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
|
@ -1,159 +0,0 @@
|
||||
mod database;
|
||||
|
||||
use crate::app::browser::window::action::Position;
|
||||
use adw::{TabPage, TabView};
|
||||
use gtk::prelude::IsA;
|
||||
use sqlite::Transaction;
|
||||
|
||||
const DEFAULT_TITLE: &str = "New page";
|
||||
|
||||
pub struct Widget {
|
||||
pub tab_page: TabPage,
|
||||
}
|
||||
|
||||
impl Widget {
|
||||
// Constructors
|
||||
|
||||
/// Build new `Self`
|
||||
pub fn build(
|
||||
tab_view: &TabView,
|
||||
child: &impl IsA<gtk::Widget>,
|
||||
title: Option<&str>,
|
||||
position: Position,
|
||||
state: (bool, bool, bool),
|
||||
) -> Self {
|
||||
// Define state variables
|
||||
let (is_pinned, is_selected, is_attention) = state;
|
||||
|
||||
// Create new `TabPage` for given `TabView`
|
||||
let tab_page = match position {
|
||||
Position::After => match tab_view.selected_page() {
|
||||
Some(page) => add(tab_view, child, tab_view.page_position(&page) + 1),
|
||||
None => tab_view.append(child),
|
||||
},
|
||||
Position::End => tab_view.append(child),
|
||||
Position::Number(value) => add(tab_view, child, value),
|
||||
};
|
||||
|
||||
// Setup
|
||||
tab_page.set_needs_attention(is_attention);
|
||||
tab_page.set_title(match title {
|
||||
Some(value) => value,
|
||||
None => DEFAULT_TITLE,
|
||||
});
|
||||
|
||||
tab_view.set_page_pinned(&tab_page, is_pinned);
|
||||
|
||||
if is_selected {
|
||||
tab_view.set_selected_page(&tab_page);
|
||||
}
|
||||
|
||||
// Done
|
||||
Self { tab_page }
|
||||
}
|
||||
|
||||
// Actions
|
||||
|
||||
pub fn clean(
|
||||
&self,
|
||||
transaction: &Transaction,
|
||||
app_browser_window_tab_item_id: i64,
|
||||
) -> Result<(), String> {
|
||||
match database::select(transaction, app_browser_window_tab_item_id) {
|
||||
Ok(records) => {
|
||||
for record in records {
|
||||
match database::delete(transaction, record.id) {
|
||||
Ok(_) => {
|
||||
// Delegate clean action to the item childs
|
||||
// nothing yet..
|
||||
}
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restore(
|
||||
&self,
|
||||
transaction: &Transaction,
|
||||
app_browser_window_tab_item_id: i64,
|
||||
) -> Result<(), String> {
|
||||
match database::select(transaction, app_browser_window_tab_item_id) {
|
||||
Ok(records) => {
|
||||
for record in records {
|
||||
// Record value can be stored as NULL
|
||||
if let Some(title) = record.title {
|
||||
self.tab_page.set_title(title.as_str());
|
||||
}
|
||||
|
||||
// Delegate restore action to the item childs
|
||||
// nothing yet..
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save(
|
||||
&self,
|
||||
transaction: &Transaction,
|
||||
app_browser_window_tab_item_id: i64,
|
||||
) -> Result<(), String> {
|
||||
// Keep value in memory until operation complete
|
||||
let title = self.tab_page.title();
|
||||
|
||||
match database::insert(
|
||||
transaction,
|
||||
app_browser_window_tab_item_id,
|
||||
match title.is_empty() {
|
||||
true => None,
|
||||
false => Some(title.as_str()),
|
||||
},
|
||||
) {
|
||||
Ok(_) => {
|
||||
// let id = database::last_insert_id(transaction);
|
||||
|
||||
// Delegate save action to childs
|
||||
// nothing yet..
|
||||
}
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Tools
|
||||
|
||||
pub fn migrate(tx: &Transaction) -> Result<(), String> {
|
||||
// Migrate self components
|
||||
if let Err(e) = database::init(tx) {
|
||||
return Err(e.to_string());
|
||||
}
|
||||
|
||||
// Delegate migration to childs
|
||||
// nothing yet..
|
||||
|
||||
// Success
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create new [TabPage](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabPage.html)
|
||||
/// in [TabView](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.TabView.html) at given position
|
||||
///
|
||||
/// * if given `position` match pinned tab, GTK will panic with notice:
|
||||
/// adw_tab_view_insert: assertion 'position >= self->n_pinned_pages'\
|
||||
/// as the solution, prepend new page after pinned tabs in this case
|
||||
fn add(tab_view: &TabView, child: &impl IsA<gtk::Widget>, position: i32) -> TabPage {
|
||||
if position > tab_view.n_pinned_pages() {
|
||||
tab_view.insert(child, position)
|
||||
} else {
|
||||
tab_view.prepend(child)
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
use sqlite::{Error, Transaction};
|
||||
|
||||
pub struct Table {
|
||||
pub id: i64,
|
||||
// pub app_browser_window_tab_item_id: i64, not in use
|
||||
pub title: Option<String>, // can be stored as NULL
|
||||
}
|
||||
|
||||
pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
||||
tx.execute(
|
||||
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_widget`
|
||||
(
|
||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`app_browser_window_tab_item_id` INTEGER NOT NULL,
|
||||
`title` VARCHAR(1024),
|
||||
|
||||
FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item`(`id`)
|
||||
)",
|
||||
[],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
tx: &Transaction,
|
||||
app_browser_window_tab_item_id: i64,
|
||||
title: Option<&str>,
|
||||
) -> Result<usize, Error> {
|
||||
tx.execute(
|
||||
"INSERT INTO `app_browser_window_tab_item_widget` (
|
||||
`app_browser_window_tab_item_id`,
|
||||
`title`
|
||||
) VALUES (?, ?)",
|
||||
(app_browser_window_tab_item_id, title),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<Vec<Table>, Error> {
|
||||
let mut stmt = tx.prepare(
|
||||
"SELECT `id`,
|
||||
`app_browser_window_tab_item_id`,
|
||||
`title`
|
||||
FROM `app_browser_window_tab_item_widget`
|
||||
WHERE `app_browser_window_tab_item_id` = ?",
|
||||
)?;
|
||||
|
||||
let result = stmt.query_map([app_browser_window_tab_item_id], |row| {
|
||||
Ok(Table {
|
||||
id: row.get(0)?,
|
||||
// app_browser_window_tab_item_id: row.get(1)?, not in use
|
||||
title: row.get(2)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut records = Vec::new();
|
||||
|
||||
for record in result {
|
||||
let table = record?;
|
||||
records.push(table);
|
||||
}
|
||||
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
|
||||
tx.execute(
|
||||
"DELETE FROM `app_browser_window_tab_item_widget` WHERE `id` = ?",
|
||||
[id],
|
||||
)
|
||||
}
|
||||
|
||||
/* not in use
|
||||
pub fn last_insert_id(tx: &Transaction) -> i64 {
|
||||
tx.last_insert_rowid()
|
||||
} */
|
Loading…
x
Reference in New Issue
Block a user