1use std::sync::OnceLock;
8
9use anyhow::Result;
10use serde::{Deserialize, Serialize};
11
12use abstutil::basename;
13
14use crate::{file_exists, list_all_objects, Manifest};
15
16static ROOT_DIR: OnceLock<String> = OnceLock::new();
17static ROOT_PLAYER_DIR: OnceLock<String> = OnceLock::new();
18
19pub fn path<I: AsRef<str>>(p: I) -> String {
20 let p = p.as_ref();
21 if p.starts_with("player/") {
22 let dir = ROOT_PLAYER_DIR.get_or_init(|| {
23 if option_env!("ABST_PLAYER_HOME_DIR").is_some() {
26 match std::env::var("HOME") {
27 Ok(dir) => format!("{}/.abstreet", dir.trim_end_matches('/')),
28 Err(err) => panic!("This build of A/B Street stores player data in $HOME/.abstreet, but $HOME isn't set: {}", err),
29 }
30 } else if cfg!(target_arch = "wasm32") {
31 "../data".to_string()
32 } else if file_exists("data/".to_string()) {
33 "data".to_string()
34 } else if file_exists("../data/".to_string()) {
35 "../data".to_string()
36 } else if file_exists("../../data/".to_string()) {
37 "../../data".to_string()
38 } else if file_exists("../../../data/".to_string()) {
39 "../../../data".to_string()
40 } else {
41 panic!("Can't find the data/ directory");
42 }
43 });
44 format!("{dir}/{p}")
45 } else {
46 let dir = ROOT_DIR.get_or_init(|| {
47 if let Some(dir) = option_env!("ABST_DATA_DIR") {
50 dir.trim_end_matches('/').to_string()
51 } else if cfg!(target_arch = "wasm32") {
52 "../data".to_string()
53 } else if file_exists("data/".to_string()) {
54 "data".to_string()
55 } else if file_exists("../data/".to_string()) {
56 "../data".to_string()
57 } else if file_exists("../../data/".to_string()) {
58 "../../data".to_string()
59 } else if file_exists("../../../data/".to_string()) {
60 "../../../data".to_string()
61 } else {
62 panic!("Can't find the data/ directory");
63 }
64 });
65 format!("{dir}/{p}")
66 }
67}
68
69#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
71pub struct CityName {
72 pub country: String,
75 pub city: String,
77}
78
79impl CityName {
80 pub fn new(country: &str, city: &str) -> CityName {
82 if country.len() != 2 {
83 panic!(
84 "CityName::new({}, {}) has a country code that isn't two letters",
85 country, city
86 );
87 }
88 CityName {
89 country: country.to_string(),
90 city: city.to_string(),
91 }
92 }
93
94 pub fn seattle() -> CityName {
96 CityName::new("us", "seattle")
97 }
98
99 fn list_all_cities_locally() -> Vec<CityName> {
101 let mut cities = Vec::new();
102 for country in list_all_objects(path("system")) {
103 if country == "assets"
104 || country == "extra_fonts"
105 || country == "ltn_proposals"
106 || country == "proposals"
107 || country == "study_areas"
108 {
109 continue;
110 }
111 for city in list_all_objects(path(format!("system/{}", country))) {
112 cities.push(CityName::new(&country, &city));
113 }
114 }
115 cities
116 }
117
118 fn list_all_cities_from_manifest(manifest: &Manifest) -> Vec<CityName> {
120 let mut cities = Vec::new();
121 for path in manifest.entries.keys() {
122 if let Some(city) = Manifest::path_to_city(path) {
123 cities.push(city);
124 }
125 }
126 cities.dedup();
128 cities
129 }
130
131 pub fn list_all_cities_merged(manifest: &Manifest) -> Vec<CityName> {
133 let mut all = CityName::list_all_cities_locally();
134 all.extend(CityName::list_all_cities_from_manifest(manifest));
135 all.sort();
136 all.dedup();
137 all
138 }
139
140 pub fn list_all_cities_from_importer_config() -> Vec<CityName> {
142 let mut cities = Vec::new();
143 for country in list_all_objects("importer/config".to_string()) {
144 for city in list_all_objects(format!("importer/config/{}", country)) {
145 cities.push(CityName::new(&country, &city));
146 }
147 }
148 cities
149 }
150
151 pub fn list_all_maps_in_city_from_importer_config(&self) -> Vec<MapName> {
153 crate::list_dir(format!("importer/config/{}/{}", self.country, self.city))
154 .into_iter()
155 .filter(|path| path.ends_with(".geojson"))
156 .map(|path| MapName::from_city(self, &basename(path)))
157 .collect()
158 }
159
160 pub fn parse(x: &str) -> Result<CityName> {
162 let parts = x.split('/').collect::<Vec<_>>();
163 if parts.len() != 2 || parts[0].len() != 2 {
164 bail!("Bad CityName {}", x);
165 }
166 Ok(CityName::new(parts[0], parts[1]))
167 }
168
169 pub fn to_path(&self) -> String {
171 format!("{}/{}", self.country, self.city)
172 }
173
174 pub fn describe(&self) -> String {
177 format!("{} ({})", self.city, self.country)
178 }
179
180 pub fn input_path<I: AsRef<str>>(&self, file: I) -> String {
182 path(format!(
183 "input/{}/{}/{}",
184 self.country,
185 self.city,
186 file.as_ref()
187 ))
188 }
189
190 pub fn uses_metric(&self) -> bool {
192 self.country != "us"
195 }
196}
197
198#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
200pub struct MapName {
201 pub city: CityName,
202 pub map: String,
204}
205
206impl abstutil::CloneableAny for MapName {}
207
208impl MapName {
209 pub fn new(country: &str, city: &str, map: &str) -> MapName {
211 MapName {
212 city: CityName::new(country, city),
213 map: map.to_string(),
214 }
215 }
216
217 pub fn blank() -> Self {
218 Self::new("zz", "blank city", "blank")
219 }
220
221 pub fn from_city(city: &CityName, map: &str) -> MapName {
223 MapName::new(&city.country, &city.city, map)
224 }
225
226 pub fn seattle(map: &str) -> MapName {
228 MapName::new("us", "seattle", map)
229 }
230
231 pub fn describe(&self) -> String {
234 format!(
235 "{} (in {} ({}))",
236 self.map, self.city.city, self.city.country
237 )
238 }
239
240 pub fn as_filename(&self) -> String {
242 format!("{}_{}_{}", self.city.country, self.city.city, self.map)
243 }
244
245 pub fn from_path(path: &str) -> Option<MapName> {
247 let parts = path.split('/').collect::<Vec<_>>();
248 if parts.len() < 5 || parts[parts.len() - 5] != "system" || parts[parts.len() - 2] != "maps"
250 {
251 return None;
252 }
253 let country = parts[parts.len() - 4];
254 let city = parts[parts.len() - 3];
255 let map = basename(parts[parts.len() - 1]);
256 Some(MapName::new(country, city, &map))
257 }
258
259 pub fn path(&self) -> String {
261 path(format!(
262 "system/{}/{}/maps/{}.bin",
263 self.city.country, self.city.city, self.map
264 ))
265 }
266
267 fn list_all_maps_in_city_locally(city: &CityName) -> Vec<MapName> {
269 let mut names = Vec::new();
270 for map in list_all_objects(path(format!("system/{}/{}/maps", city.country, city.city))) {
271 names.push(MapName {
272 city: city.clone(),
273 map,
274 });
275 }
276 names
277 }
278
279 pub fn list_all_maps_locally() -> Vec<MapName> {
281 let mut names = Vec::new();
282 for city in CityName::list_all_cities_locally() {
283 names.extend(MapName::list_all_maps_in_city_locally(&city));
284 }
285 names
286 }
287
288 fn list_all_maps_from_manifest(manifest: &Manifest) -> Vec<MapName> {
290 let mut names = Vec::new();
291 for path in manifest.entries.keys() {
292 if let Some(name) = MapName::from_path(path) {
293 names.push(name);
294 }
295 }
296 names
297 }
298
299 pub fn list_all_maps_merged(manifest: &Manifest) -> Vec<MapName> {
301 let mut all = MapName::list_all_maps_locally();
302 all.extend(MapName::list_all_maps_from_manifest(manifest));
303 all.sort();
304 all.dedup();
305 all
306 }
307
308 fn list_all_maps_in_city_from_manifest(city: &CityName, manifest: &Manifest) -> Vec<MapName> {
310 MapName::list_all_maps_from_manifest(manifest)
311 .into_iter()
312 .filter(|name| &name.city == city)
313 .collect()
314 }
315
316 pub fn list_all_maps_in_city_merged(city: &CityName, manifest: &Manifest) -> Vec<MapName> {
319 let mut all = MapName::list_all_maps_in_city_locally(city);
320 all.extend(MapName::list_all_maps_in_city_from_manifest(city, manifest));
321 all.sort();
322 all.dedup();
323 all
324 }
325
326 pub fn to_data_pack_name(&self) -> String {
328 if Manifest::is_file_part_of_huge_seattle(&self.path()) {
329 return "us/huge_seattle".to_string();
330 }
331 self.city.to_path()
332 }
333}
334
335pub fn path_prebaked_results(name: &MapName, scenario_name: &str) -> String {
338 path(format!(
339 "system/{}/{}/prebaked_results/{}/{}.bin",
340 name.city.country, name.city.city, name.map, scenario_name
341 ))
342}
343
344pub fn path_scenario(name: &MapName, scenario_name: &str) -> String {
345 let bin = path(format!(
348 "system/{}/{}/scenarios/{}/{}.bin",
349 name.city.country, name.city.city, name.map, scenario_name
350 ));
351 let json = path(format!(
352 "system/{}/{}/scenarios/{}/{}.json",
353 name.city.country, name.city.city, name.map, scenario_name
354 ));
355 if file_exists(&bin) {
356 return bin;
357 }
358 if file_exists(&json) {
359 return json;
360 }
361 bin
362}
363pub fn path_all_scenarios(name: &MapName) -> String {
364 path(format!(
365 "system/{}/{}/scenarios/{}",
366 name.city.country, name.city.city, name.map
367 ))
368}
369
370pub fn parse_scenario_path(path: &str) -> (MapName, String) {
372 let parts = path.split('/').collect::<Vec<_>>();
374 let country = parts[parts.len() - 5];
375 let city = parts[parts.len() - 4];
376 let map = parts[parts.len() - 2];
377 let scenario = basename(parts[parts.len() - 1]);
378 let map_name = MapName::new(country, city, map);
379 (map_name, scenario)
380}
381
382pub fn path_player<I: AsRef<str>>(p: I) -> String {
385 path(format!("player/{}", p.as_ref()))
386}
387
388pub fn path_camera_state(name: &MapName) -> String {
389 path(format!(
390 "player/camera_state/{}/{}/{}.json",
391 name.city.country, name.city.city, name.map
392 ))
393}
394
395pub fn path_edits(name: &MapName, edits_name: &str) -> String {
396 path(format!(
397 "player/edits/{}/{}/{}/{}.json",
398 name.city.country, name.city.city, name.map, edits_name
399 ))
400}
401pub fn path_all_edits(name: &MapName) -> String {
402 path(format!(
403 "player/edits/{}/{}/{}",
404 name.city.country, name.city.city, name.map
405 ))
406}
407
408pub fn path_ltn_proposals(name: &MapName, proposal_name: &str) -> String {
409 path(format!(
410 "player/ltn_proposals/{}/{}/{}/{}.json.gz",
411 name.city.country, name.city.city, name.map, proposal_name
412 ))
413}
414pub fn path_all_ltn_proposals(name: &MapName) -> String {
415 path(format!(
416 "player/ltn_proposals/{}/{}/{}",
417 name.city.country, name.city.city, name.map
418 ))
419}
420
421pub fn path_save(name: &MapName, edits_name: &str, run_name: &str, time: String) -> String {
422 path(format!(
423 "player/saves/{}/{}/{}/{}_{}/{}.bin",
424 name.city.country, name.city.city, name.map, edits_name, run_name, time
425 ))
426}
427pub fn path_all_saves(name: &MapName, edits_name: &str, run_name: &str) -> String {
428 path(format!(
429 "player/saves/{}/{}/{}/{}_{}",
430 name.city.country, name.city.city, name.map, edits_name, run_name
431 ))
432}
433
434pub fn path_trips(name: &MapName) -> String {
435 path(format!(
436 "player/routes/{}/{}/{}.json",
437 name.city.country, name.city.city, name.map
438 ))
439}
440
441pub fn path_popdat() -> String {
444 path("input/us/seattle/popdat.bin")
445}
446
447pub fn path_raw_map(name: &MapName) -> String {
448 path(format!(
449 "input/{}/{}/raw_maps/{}.bin",
450 name.city.country, name.city.city, name.map
451 ))
452}
453
454pub fn path_shared_input<I: AsRef<str>>(i: I) -> String {
455 path(format!("input/shared/{}", i.as_ref()))
456}