use anyhow::Result;
use serde::{Deserialize, Serialize};
use abstutil::basename;
use crate::{file_exists, list_all_objects, Manifest};
lazy_static::lazy_static! {
static ref ROOT_DIR: String = {
if let Some(dir) = option_env!("ABST_DATA_DIR") {
dir.trim_end_matches('/').to_string()
} else if cfg!(target_arch = "wasm32") {
"../data".to_string()
} else if file_exists("data/".to_string()) {
"data".to_string()
} else if file_exists("../data/".to_string()) {
"../data".to_string()
} else if file_exists("../../data/".to_string()) {
"../../data".to_string()
} else {
panic!("Can't find the data/ directory");
}
};
static ref ROOT_PLAYER_DIR: String = {
if option_env!("ABST_PLAYER_HOME_DIR").is_some() {
match std::env::var("HOME") {
Ok(dir) => format!("{}/.abstreet", dir.trim_end_matches('/')),
Err(err) => panic!("This build of A/B Street stores player data in $HOME/.abstreet, but $HOME isn't set: {}", err),
}
} else if cfg!(target_arch = "wasm32") {
"../data".to_string()
} else if file_exists("data/".to_string()) {
"data".to_string()
} else if file_exists("../data/".to_string()) {
"../data".to_string()
} else if file_exists("../../data/".to_string()) {
"../../data".to_string()
} else {
panic!("Can't find the data/ directory");
}
};
}
pub fn path<I: AsRef<str>>(p: I) -> String {
let p = p.as_ref();
if p.starts_with("player/") {
format!("{}/{}", *ROOT_PLAYER_DIR, p)
} else {
format!("{}/{}", *ROOT_DIR, p)
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct CityName {
pub country: String,
pub city: String,
}
impl CityName {
pub fn new(country: &str, city: &str) -> CityName {
if country.len() != 2 {
panic!(
"CityName::new({}, {}) has a country code that isn't two letters",
country, city
);
}
CityName {
country: country.to_string(),
city: city.to_string(),
}
}
pub fn seattle() -> CityName {
CityName::new("us", "seattle")
}
fn list_all_cities_locally() -> Vec<CityName> {
let mut cities = Vec::new();
for country in list_all_objects(path("system")) {
if country == "assets"
|| country == "extra_fonts"
|| country == "proposals"
|| country == "study_areas"
{
continue;
}
for city in list_all_objects(path(format!("system/{}", country))) {
cities.push(CityName::new(&country, &city));
}
}
cities
}
fn list_all_cities_from_manifest(manifest: &Manifest) -> Vec<CityName> {
let mut cities = Vec::new();
for path in manifest.entries.keys() {
if let Some(city) = Manifest::path_to_city(path) {
cities.push(city);
}
}
cities.dedup();
cities
}
pub fn list_all_cities_merged(manifest: &Manifest) -> Vec<CityName> {
let mut all = CityName::list_all_cities_locally();
all.extend(CityName::list_all_cities_from_manifest(manifest));
all.sort();
all.dedup();
all
}
pub fn list_all_cities_from_importer_config() -> Vec<CityName> {
let mut cities = Vec::new();
for country in list_all_objects("importer/config".to_string()) {
for city in list_all_objects(format!("importer/config/{}", country)) {
cities.push(CityName::new(&country, &city));
}
}
cities
}
pub fn parse(x: &str) -> Result<CityName> {
let parts = x.split('/').collect::<Vec<_>>();
if parts.len() != 2 || parts[0].len() != 2 {
bail!("Bad CityName {}", x);
}
Ok(CityName::new(parts[0], parts[1]))
}
pub fn to_path(&self) -> String {
format!("{}/{}", self.country, self.city)
}
pub fn describe(&self) -> String {
format!("{} ({})", self.city, self.country)
}
pub fn input_path<I: AsRef<str>>(&self, file: I) -> String {
path(format!(
"input/{}/{}/{}",
self.country,
self.city,
file.as_ref()
))
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct MapName {
pub city: CityName,
pub map: String,
}
impl MapName {
pub fn new(country: &str, city: &str, map: &str) -> MapName {
MapName {
city: CityName::new(country, city),
map: map.to_string(),
}
}
pub fn from_city(city: &CityName, map: &str) -> MapName {
MapName::new(&city.country, &city.city, map)
}
pub fn seattle(map: &str) -> MapName {
MapName::new("us", "seattle", map)
}
pub fn describe(&self) -> String {
format!(
"{} (in {} ({}))",
self.map, self.city.city, self.city.country
)
}
pub fn as_filename(&self) -> String {
format!("{}_{}_{}", self.city.country, self.city.city, self.map)
}
pub fn from_path(path: &str) -> Option<MapName> {
let parts = path.split('/').collect::<Vec<_>>();
if parts.len() < 5 || parts[parts.len() - 5] != "system" || parts[parts.len() - 2] != "maps"
{
return None;
}
let country = parts[parts.len() - 4];
let city = parts[parts.len() - 3];
let map = basename(parts[parts.len() - 1]);
Some(MapName::new(country, city, &map))
}
pub fn path(&self) -> String {
path(format!(
"system/{}/{}/maps/{}.bin",
self.city.country, self.city.city, self.map
))
}
fn list_all_maps_in_city_locally(city: &CityName) -> Vec<MapName> {
let mut names = Vec::new();
for map in list_all_objects(path(format!("system/{}/{}/maps", city.country, city.city))) {
names.push(MapName {
city: city.clone(),
map,
});
}
names
}
pub fn list_all_maps_locally() -> Vec<MapName> {
let mut names = Vec::new();
for city in CityName::list_all_cities_locally() {
names.extend(MapName::list_all_maps_in_city_locally(&city));
}
names
}
fn list_all_maps_from_manifest(manifest: &Manifest) -> Vec<MapName> {
let mut names = Vec::new();
for path in manifest.entries.keys() {
if let Some(name) = MapName::from_path(path) {
names.push(name);
}
}
names
}
pub fn list_all_maps_merged(manifest: &Manifest) -> Vec<MapName> {
let mut all = MapName::list_all_maps_locally();
all.extend(MapName::list_all_maps_from_manifest(manifest));
all.sort();
all.dedup();
all
}
fn list_all_maps_in_city_from_manifest(city: &CityName, manifest: &Manifest) -> Vec<MapName> {
MapName::list_all_maps_from_manifest(manifest)
.into_iter()
.filter(|name| &name.city == city)
.collect()
}
pub fn list_all_maps_in_city_merged(city: &CityName, manifest: &Manifest) -> Vec<MapName> {
let mut all = MapName::list_all_maps_in_city_locally(city);
all.extend(MapName::list_all_maps_in_city_from_manifest(city, manifest));
all.sort();
all.dedup();
all
}
pub fn to_data_pack_name(&self) -> String {
if Manifest::is_file_part_of_huge_seattle(&self.path()) {
return "us/huge_seattle".to_string();
}
self.city.to_path()
}
}
pub fn path_prebaked_results(name: &MapName, scenario_name: &str) -> String {
path(format!(
"system/{}/{}/prebaked_results/{}/{}.bin",
name.city.country, name.city.city, name.map, scenario_name
))
}
pub fn path_scenario(name: &MapName, scenario_name: &str) -> String {
let bin = path(format!(
"system/{}/{}/scenarios/{}/{}.bin",
name.city.country, name.city.city, name.map, scenario_name
));
let json = path(format!(
"system/{}/{}/scenarios/{}/{}.json",
name.city.country, name.city.city, name.map, scenario_name
));
if file_exists(&bin) {
return bin;
}
if file_exists(&json) {
return json;
}
bin
}
pub fn path_all_scenarios(name: &MapName) -> String {
path(format!(
"system/{}/{}/scenarios/{}",
name.city.country, name.city.city, name.map
))
}
pub fn parse_scenario_path(path: &str) -> (MapName, String) {
let parts = path.split('/').collect::<Vec<_>>();
let country = parts[parts.len() - 5];
let city = parts[parts.len() - 4];
let map = parts[parts.len() - 2];
let scenario = basename(parts[parts.len() - 1]);
let map_name = MapName::new(country, city, map);
(map_name, scenario)
}
pub fn path_player<I: AsRef<str>>(p: I) -> String {
path(format!("player/{}", p.as_ref()))
}
pub fn path_camera_state(name: &MapName) -> String {
path(format!(
"player/camera_state/{}/{}/{}.json",
name.city.country, name.city.city, name.map
))
}
pub fn path_edits(name: &MapName, edits_name: &str) -> String {
path(format!(
"player/edits/{}/{}/{}/{}.json",
name.city.country, name.city.city, name.map, edits_name
))
}
pub fn path_all_edits(name: &MapName) -> String {
path(format!(
"player/edits/{}/{}/{}",
name.city.country, name.city.city, name.map
))
}
pub fn path_save(name: &MapName, edits_name: &str, run_name: &str, time: String) -> String {
path(format!(
"player/saves/{}/{}/{}/{}_{}/{}.bin",
name.city.country, name.city.city, name.map, edits_name, run_name, time
))
}
pub fn path_all_saves(name: &MapName, edits_name: &str, run_name: &str) -> String {
path(format!(
"player/saves/{}/{}/{}/{}_{}",
name.city.country, name.city.city, name.map, edits_name, run_name
))
}
pub fn path_popdat() -> String {
path("input/us/seattle/popdat.bin")
}
pub fn path_raw_map(name: &MapName) -> String {
path(format!(
"input/{}/{}/raw_maps/{}.bin",
name.city.country, name.city.city, name.map
))
}
pub fn path_shared_input<I: AsRef<str>>(i: I) -> String {
path(format!("input/shared/{}", i.as_ref()))
}