1use anyhow::{Context, Ok, Result, anyhow};
2use biocrs::biocache::BioCache;
3use biocrs::models::{NewResource, Resource};
4
5use reqwest::blocking::get;
6use std::fs::{File, create_dir_all, read_dir, remove_dir, remove_file};
7use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
8use std::path::{Path, PathBuf};
9
10use super::consts::{
11 DEFAULT_BEDFILE_EXT, DEFAULT_BEDFILE_SUBFOLDER, DEFAULT_BEDSET_EXT, DEFAULT_BEDSET_SUBFOLDER,
12};
13use super::utils::{get_abs_path, get_bedbase_api};
14use gtars_core::models::bed_set::BedSet;
15use gtars_core::models::region_set::RegionSet;
16
17pub struct BBClient {
18 pub cache_folder: PathBuf,
19 pub bedbase_api: String,
20 bedfile_cache: BioCache,
21 bedset_cache: BioCache,
22}
23
24impl BBClient {
25 pub fn new(cache_folder: Option<PathBuf>, bedbase_api: Option<String>) -> Result<Self> {
31 let cache_folder = get_abs_path(cache_folder, Some(true));
32 let bedbase_api = bedbase_api.unwrap_or_else(get_bedbase_api);
33
34 let bedfile_subfolder = &cache_folder.join(DEFAULT_BEDFILE_SUBFOLDER);
35 create_dir_all(bedfile_subfolder)?;
36 let bedfile_cache = BioCache::new(bedfile_subfolder);
37
38 let bedset_subfolder = &cache_folder.join(DEFAULT_BEDSET_SUBFOLDER);
39 create_dir_all(bedset_subfolder)?;
40 let bedset_cache = BioCache::new(bedset_subfolder);
41
42 Ok(BBClient {
43 cache_folder,
44 bedbase_api,
45 bedfile_cache,
46 bedset_cache,
47 })
48 }
49
50 fn add_resource_to_cache(&mut self, cache_id: &str, cache_path: &str, bedfile: bool) {
57 let resource_to_add = NewResource::new(cache_id, cache_path).set_fpath(cache_path);
58 if bedfile {
59 self.bedfile_cache.add(&resource_to_add);
60 } else {
61 self.bedset_cache.add(&resource_to_add);
62 }
63 }
64
65 pub fn load_bed(&mut self, bed_id: &str) -> Result<RegionSet> {
72 let bedfile_path = self.bedfile_path(bed_id, Some(false));
73
74 if bedfile_path.exists() {
75 println!("Loading cached BED file from {:?}", bedfile_path.display());
76 return RegionSet::try_from(bedfile_path);
77 }
78
79 let region_set = RegionSet::try_from(bed_id)
80 .with_context(|| format!("Failed to create RegionSet from BEDbase id {}", bed_id))?;
81
82 self.add_resource_to_cache(
83 bed_id,
84 bedfile_path.to_str().expect("Invalid BED file path"),
85 true,
86 );
87
88 region_set.to_bed_gz(bedfile_path.clone())?;
89 println!(
90 "Downloaded BED file {} from BEDbase to path: {}",
91 bed_id,
92 bedfile_path.display()
93 );
94 Ok(region_set)
95 }
96
97 pub fn load_bedset(&mut self, bedset_id: &str) -> Result<BedSet> {
104 let bedset_path = self.bedset_path(bedset_id, Some(true));
105
106 if bedset_path.exists() {
107 println!("Loading cached BED file from {:?}", bedset_path.display());
108 return BedSet::try_from(bedset_path);
109 }
110
111 let bed_data = self.download_bedset_data(bedset_id).unwrap();
112 let mut file = File::create(bedset_path.clone())?;
113 let mut region_sets = Vec::new();
114 for bbid in bed_data {
115 writeln!(file, "{}", bbid)?;
116 let rs = self.load_bed(&bbid).unwrap();
117 region_sets.push(rs);
118 }
119 self.add_resource_to_cache(
120 bedset_id,
121 bedset_path.to_str().expect("Invalid BED set path"),
122 false,
123 );
124
125 Ok(BedSet::from(region_sets))
126 }
127
128 pub fn add_local_bed_to_cache(
136 &mut self,
137 bedfile: PathBuf,
138 force: Option<bool>,
139 ) -> Result<String> {
140 let regionset = RegionSet::try_from(bedfile.as_path())?;
141 self.add_regionset_to_cache(regionset, force)
142 }
143
144 pub fn add_regionset_to_cache(
152 &mut self,
153 regionset: RegionSet,
154 force: Option<bool>,
155 ) -> Result<String> {
156 let bedfile_id = regionset.identifier();
157 let cache_path = self.bedfile_path(&bedfile_id, Some(true));
158
159 let force = force.unwrap_or(false);
160 if !force && cache_path.exists() {
161 println!("{} already exists in cache", cache_path.display());
162 return Ok(bedfile_id);
163 }
164
165 regionset.to_bed_gz(cache_path.as_path())?;
166 self.add_resource_to_cache(
167 &bedfile_id,
168 cache_path
169 .to_str()
170 .expect("cache path cannot be convert to &str"),
171 true,
172 );
173 println!("BED file cached to {}", cache_path.display());
174
175 Ok(bedfile_id)
176 }
177
178 pub fn add_bedset_to_cache(&mut self, bedset: BedSet) -> Result<String> {
185 let bedset_id = bedset.identifier();
186 let bedset_path = self.bedset_path(&bedset_id, Some(true));
187 if bedset_path.exists() {
188 println!("{} already exists in cache", bedset_path.display());
189 } else {
190 let mut file = File::create(bedset_path.clone())?;
191 for rs in bedset.region_sets {
192 let bed_id = rs.identifier();
193 let _ = self.add_regionset_to_cache(rs, Some(false));
194 writeln!(file, "{}", bed_id)?;
195 }
196 }
197
198 self.add_resource_to_cache(
199 &bedset_id,
200 bedset_path
201 .to_str()
202 .expect("cache path cannot be convert to &str"),
203 false,
204 );
205 println!("BED set cached to {}", bedset_path.display());
206
207 Ok(bedset_id)
208 }
209
210 pub fn add_local_folder_as_bedset(&mut self, folder_path: PathBuf) -> Result<String> {
217 let mut region_sets = Vec::new();
218 for entry in read_dir(&folder_path).expect("Failed to read directory") {
219 let entry = entry.expect("Failed to read directory entry");
220 let file_path = entry.path();
221
222 if file_path.is_file() {
223 let rs = RegionSet::try_from(file_path).unwrap();
224 region_sets.push(rs);
225 }
226 }
227 let bedset = BedSet::from(region_sets);
228 Ok(self.add_bedset_to_cache(bedset).unwrap())
229 }
230
231 pub fn add_local_file_as_bedset(&mut self, file_path: PathBuf) -> Result<String> {
238 let bedset = BedSet::try_from(file_path).unwrap();
239 Ok(self.add_bedset_to_cache(bedset).unwrap())
240 }
241
242 fn download_bedset_data(&self, bedset_id: &str) -> Result<Vec<String>> {
249 let bedset_url = format!("{}/v1/bedset/{}/bedfiles", self.bedbase_api, bedset_id);
250
251 let response = get(&bedset_url)?.text()?;
252
253 let json: serde_json::Value = serde_json::from_str(&response)?;
254
255 let results = json["results"]
256 .as_array()
257 .ok_or_else(|| anyhow!("`results` is not an array"))?;
258
259 let extracted_ids: Vec<String> = results
260 .iter()
261 .filter_map(|entry| {
262 let id_val = entry.get("id");
263 id_val?.as_str().map(|s| s.to_string())
264 })
265 .collect();
266
267 Ok(extracted_ids)
268 }
269
270 fn bedfile_path(&self, bedfile_id: &str, create: Option<bool>) -> PathBuf {
278 let subfolder_name = DEFAULT_BEDFILE_SUBFOLDER;
279 let file_extension = DEFAULT_BEDFILE_EXT;
280 self.cache_path(bedfile_id, subfolder_name, file_extension, create)
281 }
282
283 fn bedset_path(&self, bedset_id: &str, create: Option<bool>) -> PathBuf {
291 let subfolder_name = DEFAULT_BEDSET_SUBFOLDER;
292 let file_extension = DEFAULT_BEDSET_EXT;
293 self.cache_path(bedset_id, subfolder_name, file_extension, create)
294 }
295
296 fn cache_path(
306 &self,
307 identifier: &str,
308 subfolder_name: &str,
309 file_extension: &str,
310 create: Option<bool>,
311 ) -> PathBuf {
312 let filename = format!("{}{}", identifier, file_extension);
313 let folder_path = self
314 .cache_folder
315 .join(subfolder_name)
316 .join(&identifier[0..1])
317 .join(&identifier[1..2]);
318
319 if create.unwrap_or(true) {
320 self.create_cache_folder(Some(&folder_path));
321 }
322 folder_path.join(filename)
323 }
324
325 fn create_cache_folder(&self, subfolder_path: Option<&Path>) {
329 let path = match subfolder_path {
330 Some(p) => p.to_path_buf(),
331 None => self.cache_folder.clone(),
332 };
333
334 if !path.exists() {
335 create_dir_all(&path).expect("Failed to create cache folder");
336 }
337 }
338
339 pub fn seek(&self, identifier: &str) -> Result<PathBuf> {
346 let file_path = self.bedfile_path(identifier, Some(false));
347 if file_path.exists() {
348 Ok(file_path)
349 } else {
350 let set_path = self.bedset_path(identifier, Some(false));
351 if set_path.exists() {
352 Ok(set_path)
353 } else {
354 Err(anyhow::anyhow!("{} does not exist in cache.", identifier))
355 }
356 }
357 }
358
359 pub fn remove(&mut self, identifier: &str) -> Result<()> {
363 let file_path = self.bedfile_path(identifier, Some(false));
364 if file_path.exists() {
365 let _ = self.local_removal(file_path.clone());
367 self.bedfile_cache.remove(identifier);
368
369 println!("{} is removed.", file_path.display());
370 Ok(())
371 } else {
372 let set_path = self.bedset_path(identifier, Some(false));
373 if set_path.exists() {
374 let bedset_file = File::open(set_path.clone())?;
375 let reader = BufReader::new(bedset_file);
376
377 let bed_ids: Vec<String> = reader.lines().collect::<Result<_, _>>()?;
378
379 for bed_id in bed_ids {
380 let _ = self.remove(&bed_id);
381 }
382
383 let _ = self.local_removal(set_path.clone());
384
385 self.bedset_cache.remove(identifier);
386
387 println!("{} is removed.", set_path.display());
388 Ok(())
389 } else {
390 Err(Error::new(
391 ErrorKind::NotFound,
392 format!("{} does not exist in cache.", file_path.display()),
393 )
394 .into())
395 }
396 }
397 }
398
399 fn local_removal(&self, file_path: PathBuf) -> Result<()> {
403 let sub_folder_2 = file_path.parent().map(PathBuf::from);
404 let sub_folder_1 = sub_folder_2
405 .as_ref()
406 .and_then(|p| p.parent().map(PathBuf::from));
407
408 remove_file(&file_path)?;
409
410 if let Some(sub2) = sub_folder_2
412 && read_dir(&sub2)?.next().is_none()
413 {
414 remove_dir(&sub2)?;
415 if let Some(sub1) = sub_folder_1
416 && read_dir(&sub1)?.next().is_none()
417 {
418 remove_dir(&sub1)?;
419 }
420 }
421
422 Ok(())
423 }
424
425 pub fn list_beds(&mut self) -> Result<Vec<Resource>> {
429 let bed_resources = self.bedfile_cache.list_resources(Some(20_000));
430 Ok(bed_resources)
431 }
432
433 pub fn list_bedsets(&mut self) -> Result<Vec<Resource>> {
437 let bedset_resources = self.bedset_cache.list_resources(Some(20_000));
438 Ok(bedset_resources)
439 }
440}