i3status_rs/
geolocator.rs1use crate::errors::{Error, ErrorContext as _, Result, StdError};
49use std::borrow::Cow;
50use std::fmt;
51use std::sync::{Arc, Mutex};
52use std::time::{Duration, Instant};
53
54use serde::Deserialize;
55use smart_default::SmartDefault;
56
57mod ip2location;
58mod ipapi;
59
60#[derive(Debug)]
61struct AutolocateResult {
62 location: IPAddressInfo,
63 timestamp: Instant,
64}
65
66#[derive(Deserialize, Clone, Default, Debug)]
67pub struct IPAddressInfo {
68 pub ip: String,
70 pub latitude: f64,
71 pub longitude: f64,
72 pub city: String,
73
74 pub version: Option<String>,
76 pub region: Option<String>,
77 pub region_code: Option<String>,
78 pub country: Option<String>,
79 pub country_name: Option<String>,
80 pub country_code: Option<String>,
81 pub country_code_iso3: Option<String>,
82 pub country_capital: Option<String>,
83 pub country_tld: Option<String>,
84 pub continent_code: Option<String>,
85 pub in_eu: Option<bool>,
86 pub postal: Option<String>,
87 pub timezone: Option<String>,
88 pub utc_offset: Option<String>,
89 pub country_calling_code: Option<String>,
90 pub currency: Option<String>,
91 pub currency_name: Option<String>,
92 pub languages: Option<String>,
93 pub country_area: Option<f64>,
94 pub country_population: Option<f64>,
95 pub asn: Option<String>,
96 pub org: Option<String>,
97}
98
99#[derive(Default, Debug, Deserialize)]
100#[serde(from = "GeolocatorBackend")]
101pub struct Geolocator {
102 backend: GeolocatorBackend,
103 last_autolocate: Mutex<Option<AutolocateResult>>,
104}
105
106impl Geolocator {
107 pub fn name(&self) -> Cow<'static, str> {
108 self.backend.name()
109 }
110
111 pub async fn find_ip_location(
113 &self,
114 client: &reqwest::Client,
115 interval: Duration,
116 ) -> Result<IPAddressInfo> {
117 {
118 let guard = self.last_autolocate.lock().unwrap();
119 if let Some(cached) = &*guard {
120 if cached.timestamp.elapsed() < interval {
121 return Ok(cached.location.clone());
122 }
123 }
124 }
125
126 let location = self.backend.get_info(client).await?;
127
128 {
129 let mut guard = self.last_autolocate.lock().unwrap();
130 *guard = Some(AutolocateResult {
131 location: location.clone(),
132 timestamp: Instant::now(),
133 });
134 }
135
136 Ok(location)
137 }
138}
139
140#[derive(Deserialize, Debug, SmartDefault, Clone)]
141#[serde(tag = "geolocator", rename_all = "lowercase", deny_unknown_fields)]
142pub enum GeolocatorBackend {
143 #[default]
144 Ipapi(ipapi::Config),
145 Ip2Location(ip2location::Config),
146}
147
148impl GeolocatorBackend {
149 fn name(&self) -> Cow<'static, str> {
150 match self {
151 GeolocatorBackend::Ipapi(_) => ipapi::Ipapi.name(),
152 GeolocatorBackend::Ip2Location(_) => ip2location::Ip2Location.name(),
153 }
154 }
155
156 async fn get_info(&self, client: &reqwest::Client) -> Result<IPAddressInfo> {
157 match self {
158 GeolocatorBackend::Ipapi(_) => ipapi::Ipapi.get_info(client).await,
159 GeolocatorBackend::Ip2Location(config) => {
160 ip2location::Ip2Location
161 .get_info(client, &config.api_key)
162 .await
163 }
164 }
165 }
166}
167
168impl From<GeolocatorBackend> for Geolocator {
169 fn from(backend: GeolocatorBackend) -> Self {
170 Self {
171 backend,
172 last_autolocate: Mutex::new(None),
173 }
174 }
175}