Skip to content

Commit 9fc5ea4

Browse files
committed
Cache last known location to prevent network access
1 parent 314dae0 commit 9fc5ea4

File tree

4 files changed

+172
-99
lines changed

4 files changed

+172
-99
lines changed

backlightd/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ sunrise-next = "1.2"
1111
chrono = "0.4.38"
1212
ureq = { version = "2.10.1", features = ["json", "proxy-from-env"] }
1313
serde = "1.0"
14+
toml = "0.8.19"

backlightd/src/auto.rs

Lines changed: 2 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
use core::panic;
22
use std::{
3-
env::{self, VarError},
43
sync::mpsc::{Receiver, RecvTimeoutError},
54
time::{Duration, Instant},
65
};
76

8-
use anyhow::{anyhow, bail, Context};
7+
use anyhow::anyhow;
98
use backlight_ipc::BacklightMode;
109
use chrono::{DateTime, Datelike, Local, NaiveTime};
1110
use sunrise::sunrise_sunset;
12-
use ureq::serde::Deserialize;
1311

14-
use crate::monitors;
12+
use crate::{location::find_location, monitors};
1513

1614
const AUTO_ADJUST_INTERVAL: Duration = Duration::from_secs(600);
1715

@@ -60,101 +58,6 @@ pub fn auto_adjust(auto_adjust_receiver: Receiver<BacklightMode>) -> ! {
6058
}
6159
}
6260

63-
fn find_location_from_config() -> anyhow::Result<(f64, f64)> {
64-
let location_str = match env::var("BACKLIGHTD_LOCATION") {
65-
Ok(loc) => loc,
66-
Err(err) => match err {
67-
VarError::NotPresent => return Err(anyhow::Error::new(VarError::NotPresent)),
68-
VarError::NotUnicode(str) => {
69-
bail!("Invalid environment variable value: {str:?}");
70-
}
71-
},
72-
};
73-
74-
let (lat, long) = location_str.split_once(',').with_context(|| {
75-
"Invalid BACKLIGHTD_LOCATION, expected a comma between latitude and longitude"
76-
})?;
77-
78-
let lat = lat
79-
.trim()
80-
.parse()
81-
.with_context(|| "Unable to parse latitude to float")?;
82-
83-
let long = long
84-
.trim()
85-
.parse()
86-
.with_context(|| "Unable to parse longitude to float")?;
87-
88-
Ok((lat, long))
89-
}
90-
91-
fn find_ip_location() -> anyhow::Result<(f64, f64)> {
92-
#[derive(Deserialize)]
93-
struct IpApiResponse {
94-
status: String,
95-
message: Option<String>,
96-
country: String,
97-
city: String,
98-
lat: f64,
99-
lon: f64,
100-
}
101-
102-
println!("Trying to get location from public API...");
103-
let resp: IpApiResponse =
104-
ureq::get("http://ip-api.com/json/?fields=status,message,country,city,lat,lon")
105-
.call()?
106-
.into_json()?;
107-
108-
if resp.status != "success" {
109-
bail!("Unable to find location by IP: {:?}", resp.message);
110-
}
111-
112-
println!(
113-
"Found your location using your IP: {}/{} [{}, {}]",
114-
resp.country, resp.city, resp.lat, resp.lon
115-
);
116-
117-
Ok((resp.lat, resp.lon))
118-
}
119-
120-
fn find_location() -> anyhow::Result<Option<(f64, f64)>> {
121-
match find_location_from_config() {
122-
Ok(location) => {
123-
println!("Using location from configuration: {location:?}");
124-
return Ok(Some(location));
125-
}
126-
Err(err) => match err.downcast_ref::<VarError>() {
127-
Some(VarError::NotPresent) => {}
128-
_ => bail!("{err}"),
129-
},
130-
}
131-
132-
let allow_api_call = match env::var("BACKLIGHTD_ENABLE_LOCATION_API") {
133-
Ok(value) => ["1", "y", "yes"].contains(&value.as_ref()),
134-
Err(err) => match err {
135-
VarError::NotPresent => true,
136-
VarError::NotUnicode(str) => {
137-
bail!("Invalid environment variable value: {str:?}");
138-
}
139-
},
140-
};
141-
142-
Ok(if allow_api_call {
143-
match find_ip_location() {
144-
Ok(location) => Some(location),
145-
Err(err) => {
146-
eprintln!("Failed to get location using public API: {err}");
147-
println!("Fallback to clock based brightness adjustement");
148-
None
149-
}
150-
}
151-
} else {
152-
println!("Unable to find your location, you might want to configure backlightd or to allow the usage of the public API");
153-
println!("Fallback to clock based brightness adjustement");
154-
None
155-
})
156-
}
157-
15861
fn get_brightness_based_on_location(latitude: f64, longitude: f64) -> u8 {
15962
let now = Local::now();
16063
let (sunrise_timestamp, sunset_timestamp) =

backlightd/src/location.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
use core::f64;
2+
use std::{
3+
env::{self, VarError},
4+
fs,
5+
path::PathBuf,
6+
sync::Mutex,
7+
time::{Duration, SystemTime, UNIX_EPOCH},
8+
};
9+
10+
use anyhow::{bail, Context};
11+
use log::{debug, error, info, warn};
12+
use serde::{Deserialize, Serialize};
13+
14+
#[derive(Deserialize)]
15+
struct IpApiResponse {
16+
status: String,
17+
message: Option<String>,
18+
country: String,
19+
city: String,
20+
lat: f64,
21+
lon: f64,
22+
}
23+
24+
#[derive(Serialize, Deserialize)]
25+
struct LastKnownLocation {
26+
latitude: f64,
27+
longitude: f64,
28+
timestamp: f64,
29+
}
30+
31+
impl LastKnownLocation {
32+
const fn default() -> Self {
33+
Self {
34+
latitude: 0.,
35+
longitude: 0.,
36+
timestamp: 0.,
37+
}
38+
}
39+
}
40+
41+
static LAST_LOCATION_REFRESH: Mutex<LastKnownLocation> = Mutex::new(LastKnownLocation::default());
42+
const IP_BASED_LOCATION_REFRESH_INTERVAL: Duration = Duration::from_secs(12 * 60 * 60);
43+
const LOCATION_CACHE_FILE: &str = "/var/cache/backlightd/last_known_location.toml";
44+
45+
fn find_location_from_config() -> anyhow::Result<(f64, f64)> {
46+
let location_str = match env::var("BACKLIGHTD_LOCATION") {
47+
Ok(loc) => loc,
48+
Err(err) => match err {
49+
VarError::NotPresent => return Err(anyhow::Error::new(VarError::NotPresent)),
50+
VarError::NotUnicode(str) => {
51+
bail!("Invalid environment variable value: {str:?}");
52+
}
53+
},
54+
};
55+
56+
let (lat, long) = location_str.split_once(',').with_context(|| {
57+
"Invalid BACKLIGHTD_LOCATION, expected a comma between latitude and longitude"
58+
})?;
59+
60+
let lat = lat
61+
.trim()
62+
.parse()
63+
.with_context(|| "Unable to parse latitude to float")?;
64+
65+
let long = long
66+
.trim()
67+
.parse()
68+
.with_context(|| "Unable to parse longitude to float")?;
69+
70+
Ok((lat, long))
71+
}
72+
73+
fn find_ip_location() -> anyhow::Result<(f64, f64)> {
74+
let mut last_known_location = LAST_LOCATION_REFRESH
75+
.lock()
76+
.expect("Unable to acquire LAST_LOCATION_REFRESH mutex");
77+
78+
if last_known_location.timestamp == 0. {
79+
// On first run (timestamp == 0), try to read from the cache file
80+
match fs::read_to_string(LOCATION_CACHE_FILE) {
81+
Ok(cache_str) => match toml::from_str::<LastKnownLocation>(&cache_str) {
82+
Ok(cache) => {
83+
last_known_location.latitude = cache.latitude;
84+
last_known_location.longitude = cache.longitude;
85+
last_known_location.timestamp = cache.timestamp;
86+
}
87+
Err(err) => warn!("Invalid cache file detected: {err}"),
88+
},
89+
Err(err) => match err.kind() {
90+
std::io::ErrorKind::NotFound => {}
91+
_ => {
92+
error!("Unable to read from location cache file: {err}");
93+
}
94+
},
95+
};
96+
}
97+
98+
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs_f64();
99+
100+
if now - last_known_location.timestamp > IP_BASED_LOCATION_REFRESH_INTERVAL.as_secs_f64() {
101+
debug!("Trying to get location from public API...");
102+
let resp: IpApiResponse =
103+
ureq::get("http://ip-api.com/json/?fields=status,message,country,city,lat,lon")
104+
.call()?
105+
.into_json()?;
106+
107+
if resp.status != "success" {
108+
bail!("Unable to find location by IP: {:?}", resp.message);
109+
}
110+
111+
info!(
112+
"Found your location using your IP: {}/{} [{}, {}]",
113+
resp.country, resp.city, resp.lat, resp.lon
114+
);
115+
116+
last_known_location.timestamp = now;
117+
last_known_location.latitude = resp.lat;
118+
last_known_location.longitude = resp.lon;
119+
120+
let last_known_location_as_toml = toml::to_string(&*last_known_location)
121+
.expect("unable to convert LastKnownLocation to toml");
122+
let cache_location = PathBuf::from(LOCATION_CACHE_FILE);
123+
fs::create_dir_all(cache_location.parent().unwrap())?;
124+
if let Err(err) = fs::write(LOCATION_CACHE_FILE, last_known_location_as_toml) {
125+
error!("Failed to write to location cache: {err}");
126+
}
127+
}
128+
129+
Ok((last_known_location.latitude, last_known_location.longitude))
130+
}
131+
132+
pub(crate) fn find_location() -> anyhow::Result<Option<(f64, f64)>> {
133+
match find_location_from_config() {
134+
Ok(location) => {
135+
info!("Using location from configuration: {location:?}");
136+
return Ok(Some(location));
137+
}
138+
Err(err) => match err.downcast_ref::<VarError>() {
139+
Some(VarError::NotPresent) => {}
140+
_ => bail!("{err}"),
141+
},
142+
}
143+
144+
let allow_api_call = match env::var("BACKLIGHTD_ENABLE_LOCATION_API") {
145+
Ok(value) => ["1", "y", "yes"].contains(&value.as_ref()),
146+
Err(err) => match err {
147+
VarError::NotPresent => true,
148+
VarError::NotUnicode(str) => {
149+
bail!("Invalid environment variable value: {str:?}");
150+
}
151+
},
152+
};
153+
154+
Ok(if allow_api_call {
155+
match find_ip_location() {
156+
Ok(location) => Some(location),
157+
Err(err) => {
158+
warn!("Failed to get location using public API: {err}");
159+
info!("Fallback to clock based brightness adjustement");
160+
None
161+
}
162+
}
163+
} else {
164+
warn!("Unable to find your location, you might want to configure backlightd or to allow the usage of the public API");
165+
info!("Fallback to clock based brightness adjustement");
166+
None
167+
})
168+
}

backlightd/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use monitors::auto_refresh_monitors_list;
1515
mod acpi;
1616
mod auto;
1717
mod ddc;
18+
mod location;
1819
mod monitors;
1920

2021
fn handle_commands(

0 commit comments

Comments
 (0)