1use std::path::Path;
2
3use anyhow::Result;
4#[cfg(feature = "cache")]
5use anyhow::{anyhow, Context};
6use data_request_spec::DataRequestSpec;
7use log::{debug, error};
8use metadata::Metadata;
9use polars::frame::DataFrame;
10use search::{Params, SearchParams, SearchResults};
11
12use crate::config::Config;
13
14pub use column_names as COL;
16
17pub mod column_names;
19pub mod config;
20pub mod data_request_spec;
21pub mod error;
22#[cfg(feature = "formatters")]
23pub mod formatters;
24pub mod geo;
25pub mod metadata;
26pub mod parquet;
27pub mod search;
28
29#[derive(Debug, PartialEq)]
31pub struct Popgetter {
32 pub metadata: Metadata,
33 pub config: Config,
34}
35
36impl Popgetter {
37 pub async fn new() -> Result<Self> {
39 Self::new_with_config(Config::default()).await
40 }
41
42 pub async fn new_with_config(config: Config) -> Result<Self> {
44 debug!("config: {config:?}");
45 let metadata = metadata::load_all(&config).await?;
46 Ok(Self { metadata, config })
47 }
48
49 #[cfg(feature = "cache")]
51 pub async fn new_with_config_and_cache(config: Config) -> Result<Self> {
53 let path = dirs::cache_dir()
55 .ok_or(anyhow!("Failed to get cache directory"))?
56 .join("popgetter");
57 Popgetter::new_with_config_and_cache_path(config, path).await
58 }
59
60 #[cfg(feature = "cache")]
62 async fn new_with_config_and_cache_path<P: AsRef<Path>>(
63 config: Config,
64 path: P,
65 ) -> Result<Self> {
66 if path.as_ref().exists() {
68 match Popgetter::new_from_cache_path(config.clone(), &path) {
69 Ok(popgetter) => return Ok(popgetter),
70 Err(err) => {
71 error!("Failed to read metadata from cache with error: {err}");
73 }
74 }
75 }
76 std::fs::create_dir_all(&path)?;
78 let popgetter = Popgetter::new_with_config(config).await?;
79 if let Err(err) = popgetter.metadata.write_cache(&path) {
81 std::fs::remove_dir_all(&path).with_context(|| {
82 "Failed to remove cache dir following error writing cache: {err}"
83 })?;
84 Err(err)?
85 }
86 Ok(popgetter)
87 }
88
89 #[cfg(feature = "cache")]
91 fn new_from_cache_path<P: AsRef<Path>>(config: Config, path: P) -> Result<Self> {
92 let metadata = Metadata::from_cache(path)?;
93 Ok(Self { metadata, config })
94 }
95
96 pub fn search(&self, search_params: &SearchParams) -> SearchResults {
99 search_params
100 .clone()
101 .search(&self.metadata.combined_metric_source_geometry())
102 }
103
104 pub async fn download_data_request_spec(
106 &self,
107 data_request_spec: &DataRequestSpec,
108 ) -> Result<DataFrame> {
109 let params: Params = data_request_spec.clone().try_into()?;
110 let search_results = self.search(¶ms.search);
111 search_results
112 .download(&self.config, ¶ms.download)
113 .await
114 }
115
116 pub async fn download_params(&self, params: &Params) -> Result<DataFrame> {
118 self.search(¶ms.search)
119 .download(&self.config, ¶ms.download)
120 .await
121 }
122}
123
124#[cfg(test)]
125#[cfg(feature = "cache")]
126mod tests {
127
128 use tempfile::TempDir;
129
130 use super::*;
131
132 #[tokio::test]
133 async fn test_popgetter_cache() -> anyhow::Result<()> {
134 let tempdir = TempDir::new()?;
135 let config = Config::default();
136 let popgetter = Popgetter::new_with_config(config.clone()).await?;
137 popgetter.metadata.write_cache(&tempdir)?;
138 let popgetter_from_cache =
139 Popgetter::new_with_config_and_cache_path(config, tempdir).await?;
140 assert_eq!(popgetter, popgetter_from_cache);
141 Ok(())
142 }
143}