1use crate::backend::{QtThread, Shared, json, load_config, tell, tr};
9use crate::work::loaded;
10use serde::Serialize;
11use sioul_core::places::{Place, Places};
12use std::sync::Arc;
13use std::sync::atomic::Ordering;
14
15#[derive(Serialize)]
16struct Pin {
17 key: String,
18 name: String,
19 address: String,
20 lat: f64,
21 lon: f64,
22}
23
24#[derive(Serialize)]
25struct MapView {
26 allowed: bool,
28 tiles: String,
30 pins: Vec<Pin>,
31 waiting: usize,
33 locating: bool,
35}
36
37fn tiles() -> String {
39 let configured = load_config().map.tiles.unwrap_or_default();
40 let host = configured.split("{z}").next().unwrap_or("").trim().to_string();
41 if host.is_empty() { "https://tile.openstreetmap.org/".to_string() } else { host }
42}
43
44pub(crate) fn view(shared: &Shared) -> String {
46 let loaded = loaded(shared);
47 let places = Places::load(&Places::default_path());
48 let mut pins = Vec::new();
49 let mut waiting = 0;
50 for contact in &loaded.contacts {
51 for address in contact.addresses.iter().filter(|a| !a.value.trim().is_empty()) {
52 match places.get(&address.value) {
53 Some(Place { lat, lon }) => pins.push(Pin { key: contact.key.clone(), name: contact.name.clone(), address: address.value.clone(), lat, lon }),
54 None if !places.asked(&address.value) => waiting += 1,
55 None => {}
56 }
57 }
58 }
59 json(&MapView { allowed: load_config().map.geocode, tiles: tiles(), pins, waiting, locating: shared.locating.load(Ordering::Relaxed) })
60}
61
62pub(crate) fn place_of(address: &str) -> String {
64 Places::load(&Places::default_path()).get(address).map(|p| serde_json::json!({ "lat": p.lat, "lon": p.lon }).to_string()).unwrap_or_default()
65}
66
67pub(crate) fn locate(qt: &QtThread, shared: &Arc<Shared>) {
70 if !load_config().map.geocode || crate::backend::offline() || shared.locating.swap(true, Ordering::Relaxed) {
71 return;
72 }
73 let loaded = loaded(shared);
74 let mut addresses: Vec<String> = loaded.contacts.iter().flat_map(|c| c.addresses.iter().map(|a| a.value.clone())).filter(|a| !a.trim().is_empty()).collect();
75 addresses.sort();
76 addresses.dedup();
77 let (qt, shared) = (qt.clone(), Arc::clone(shared));
78 let changed = |qt: &QtThread| {
79 let _ = qt.queue(|mut sioul| sioul.as_mut().places_changed());
80 };
81 changed(&qt);
82 std::thread::spawn(move || {
83 let path = Places::default_path();
84 let language = tr().text("qt-locale", None).replace('_', "-");
85 let mut asked = 0;
86 for address in addresses {
87 let mut places = Places::load(&path);
88 if places.asked(&address) {
89 continue;
90 }
91 if asked > 0 {
92 std::thread::sleep(sioul_sync::geocode::PAUSE);
93 }
94 asked += 1;
95 match sioul_sync::geocode::locate(&address, &language) {
96 Ok(place) => {
97 places.set(&address, place);
98 if let Err(e) = places.save(&path) {
99 tell(&qt, &shared, e);
100 break;
101 }
102 }
103 Err(e) => {
105 tell(&qt, &shared, e.sentence(tr(), "OpenStreetMap"));
106 break;
107 }
108 }
109 if asked % 5 == 0 {
110 changed(&qt);
111 }
112 }
113 shared.locating.store(false, Ordering::Relaxed);
114 changed(&qt);
115 });
116}