implement maxmind geo-location for page connection info

This commit is contained in:
yggverse 2025-03-23 00:15:42 +02:00
parent c35db8c1ff
commit eb6da7a362
4 changed files with 67 additions and 20 deletions

View File

@ -36,6 +36,7 @@ ggemtext = "0.6.0"
indexmap = "2.7.0"
itertools = "0.14.0"
libspelling = "0.3.0"
maxminddb = "0.25.0"
openssl = "0.10.70"
plurify = "0.2.0"
r2d2 = "0.8.10"

View File

@ -105,7 +105,7 @@ impl Request {
if is_focused(e) {
e.emit_activate()
} else {
i.borrow().dialog(Some(e));
i.borrow().dialog(&p, Some(e));
}
}
_ => panic!(),

View File

@ -2,6 +2,7 @@ mod dialog;
mod event;
mod socket;
use super::Profile;
use dialog::Dialog;
use event::Event;
use gtk::{gio::SocketAddress, prelude::IsA};
@ -48,9 +49,9 @@ impl Info {
// Actions
pub fn dialog(&self, parent: Option<&impl IsA<gtk::Widget>>) {
pub fn dialog(&self, profile: &Profile, parent: Option<&impl IsA<gtk::Widget>>) {
use adw::{PreferencesDialog, prelude::AdwDialogExt};
PreferencesDialog::info(self).present(parent)
PreferencesDialog::info(self, profile).present(parent)
}
/// Actualize `Self`

View File

@ -1,17 +1,16 @@
use super::Info;
use super::{Info, Profile};
use adw::{
ActionRow, PreferencesDialog, PreferencesGroup, PreferencesPage,
prelude::{ActionRowExt, PreferencesDialogExt, PreferencesGroupExt, PreferencesPageExt},
};
use gtk::glib::gformat;
use sourceview::prelude::{SocketAddressExt, SocketConnectableExt};
pub trait Dialog {
fn info(info: &Info) -> Self;
fn info(this: &Info, profile: &Profile) -> Self;
}
impl Dialog for PreferencesDialog {
fn info(info: &Info) -> Self {
fn info(this: &Info, profile: &Profile) -> Self {
let d = PreferencesDialog::builder()
.search_enabled(true)
.title("Page info")
@ -21,10 +20,10 @@ impl Dialog for PreferencesDialog {
.title("General")
.icon_name("help-about-symbolic")
.build();
if info.mime.is_some() {
if this.mime.is_some() {
p.add(&{
let g = PreferencesGroup::builder().title("Meta").build();
if let Some(ref mime) = info.mime {
if let Some(ref mime) = this.mime {
g.add(
&ActionRow::builder()
.css_classes(["property"])
@ -38,10 +37,10 @@ impl Dialog for PreferencesDialog {
g
});
} // @TODO content language, header size, etc.
if info.size.is_some() {
if this.size.is_some() {
p.add(&{
let g = PreferencesGroup::builder().title("Size").build();
if let Some(ref size) = info.size {
if let Some(ref size) = this.size {
g.add(&{
use crate::tool::Format;
ActionRow::builder()
@ -63,8 +62,10 @@ impl Dialog for PreferencesDialog {
.title("Connection")
.icon_name("network-transmit-receive")
.build();
if let Some(ref socket) = info.socket {
use gtk::gio::SocketFamily;
if let Some(ref socket) = this.socket {
use gtk::gio::{SocketAddress, SocketFamily};
use gtk::prelude::{SocketAddressExt, SocketConnectableExt};
/// Convert socket family to string
fn f2s(socket_family: &SocketFamily) -> &str {
match socket_family {
SocketFamily::Invalid => "Invalid",
@ -74,6 +75,7 @@ impl Dialog for PreferencesDialog {
_ => panic!(),
}
}
/// Build common `ActionRow` widget
fn r(title: &str, subtitle: &str) -> ActionRow {
ActionRow::builder()
.css_classes(["property"])
@ -83,24 +85,67 @@ impl Dialog for PreferencesDialog {
.title(title)
.build()
}
/// Lookup [MaxMind](https://www.maxmind.com) database
fn l(profile: &Profile, socket_address: &SocketAddress) -> Option<String> {
use maxminddb::{
MaxMindDBError, Reader,
geoip2::{/*City,*/ Country},
};
if !matches!(
socket_address.family(),
SocketFamily::Ipv4 | SocketFamily::Ipv6,
) {
return None;
}
let db = {
let mut c = profile.config_path.clone();
c.push("GeoLite2-Country.mmdb");
Reader::open_readfile(c)
}
.ok()?;
let lookup = {
let a: std::net::SocketAddr = socket_address.to_string().parse().unwrap();
let lookup: std::result::Result<Country, MaxMindDBError> =
db.lookup(a.ip());
lookup
}
.ok()?;
lookup.country.map(|c| {
let mut b = Vec::new();
if let Some(iso_code) = c.iso_code {
b.push(iso_code)
}
if let Some(n) = c.names {
if let Some(s) = n.get("en") {
b.push(s)
} // @TODO multi-lang
}
// @TODO city DB
b.join(", ")
})
}
p.add(&{
let g = PreferencesGroup::builder().title("Remote").build();
g.add(&r("Address", &socket.remote_address.to_string()));
g.add(&r("Family", f2s(&socket.remote_address.family())));
g.add(&r("Location", "-")); // @TODO optional, MaxMind DB
if let Some(location) = l(profile, &socket.remote_address) {
g.add(&r("Location", &location));
}
g
});
p.add(&{
let g = PreferencesGroup::builder().title("Local").build();
g.add(&r("Address", &socket.local_address.to_string()));
g.add(&r("Family", f2s(&socket.local_address.family())));
g.add(&r("Location", "-")); // @TODO optional, MaxMind DB
if let Some(location) = l(profile, &socket.local_address) {
g.add(&r("Location", &location));
}
g
});
}
p
});
if info.redirect.is_some() {
if this.redirect.is_some() {
d.add(&{
let g = PreferencesGroup::new();
let p = PreferencesPage::builder()
@ -119,7 +164,7 @@ impl Dialog for PreferencesDialog {
chain(b, r)
}
}
chain(&mut b, info);
chain(&mut b, this);
b.reverse();
let l = b.len(); // calculate once
let t = b[0].event[0].time(); // first event time to count from
@ -165,7 +210,7 @@ impl Dialog for PreferencesDialog {
p
}) // @TODO clickable navigation, test time values
}
if !info.event.is_empty() {
if !this.event.is_empty() {
d.add(&{
let p = PreferencesPage::builder()
.title("Events")
@ -173,7 +218,7 @@ impl Dialog for PreferencesDialog {
.build();
p.add(&{
let g = PreferencesGroup::new();
let e = &info.event[0];
let e = &this.event[0];
let t = e.time();
let n = e.name();
g.add(
@ -184,7 +229,7 @@ impl Dialog for PreferencesDialog {
.title(n)
.build(),
);
for e in &info.event[1..] {
for e in &this.event[1..] {
g.add(
&ActionRow::builder()
.subtitle(gformat!(