1use anyhow::{Context, Ok, Result, anyhow};
7use biocrs::biocache::BioCache;
8use biocrs::models::{NewResource, Resource};
9
10use std::fs::{File, create_dir_all, read_dir, remove_dir, remove_file};
11use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
12use std::path::{Path, PathBuf};
13use ureq::get;
14
15use super::consts::{
16 DEFAULT_BEDFILE_EXT, DEFAULT_BEDFILE_SUBFOLDER, DEFAULT_BEDSET_EXT, DEFAULT_BEDSET_SUBFOLDER,
17};
18use super::utils::{get_default_bedbase_api, get_default_cache_folder};
19use gtars_core::models::bed_set::BedSet;
20use gtars_core::models::region_set::RegionSet;
21
22#[derive(Default)]
42pub struct BBClientBuilder {
43 cache_folder: Option<PathBuf>,
44 bedbase_api: Option<String>,
45}
46
47impl BBClientBuilder {
48 pub fn new() -> Self {
50 Self::default()
51 }
52
53 pub fn with_cache_folder(mut self, path: PathBuf) -> Self {
55 self.cache_folder = Some(path);
56 self
57 }
58
59 pub fn with_bedbase_api(mut self, api: String) -> Self {
61 self.bedbase_api = Some(api);
62 self
63 }
64
65 pub fn finish(self) -> Result<BBClient> {
67 let raw_path_to_cache_folder = self.cache_folder.unwrap_or_else(get_default_cache_folder);
69 let raw_str_to_cache_folder = raw_path_to_cache_folder.to_string_lossy().into_owned();
70 let expanded_str = shellexpand::env(&raw_str_to_cache_folder)
71 .unwrap_or_else(|_| raw_str_to_cache_folder.clone().into())
72 .into_owned();
73 let abs_path_to_cache_folder = PathBuf::from(expanded_str);
74 create_dir_all(&abs_path_to_cache_folder)?;
75
76 let bedbase_api = self.bedbase_api.unwrap_or_else(get_default_bedbase_api);
78
79 let bedfile_subfolder = &abs_path_to_cache_folder.join(DEFAULT_BEDFILE_SUBFOLDER);
81 create_dir_all(bedfile_subfolder)?;
82 let bedfile_cache = BioCache::new(bedfile_subfolder);
83
84 let bedset_subfolder = &abs_path_to_cache_folder.join(DEFAULT_BEDSET_SUBFOLDER);
85 create_dir_all(bedset_subfolder)?;
86 let bedset_cache = BioCache::new(bedset_subfolder);
87
88 Ok(BBClient {
89 cache_folder: abs_path_to_cache_folder,
90 bedbase_api,
91 bedfile_cache,
92 bedset_cache,
93 })
94 }
95}
96
97pub struct BBClient {
136 pub cache_folder: PathBuf,
138 pub bedbase_api: String,
140 bedfile_cache: BioCache,
142 bedset_cache: BioCache,
144}
145
146impl BBClient {
147 pub fn builder() -> BBClientBuilder {
166 BBClientBuilder::default()
167 }
168
169 fn add_resource_to_cache(&mut self, cache_id: &str, cache_path: &str, bedfile: bool) {
176 let resource_to_add = NewResource::new(cache_id, cache_path).set_fpath(cache_path);
177 if bedfile {
178 self.bedfile_cache.add(&resource_to_add);
179 } else {
180 self.bedset_cache.add(&resource_to_add);
181 }
182 }
183
184 pub fn load_bed(&mut self, bed_id: &str) -> Result<RegionSet> {
191 let bedfile_path = self.bedfile_path(bed_id, Some(false));
192
193 if bedfile_path.exists() {
194 println!("Loading cached BED file from {:?}", bedfile_path.display());
195 return RegionSet::try_from(bedfile_path);
196 }
197
198 let region_set = RegionSet::try_from(bed_id)
199 .with_context(|| format!("Failed to create RegionSet from BEDbase id {}", bed_id))?;
200
201 self.add_resource_to_cache(
202 bed_id,
203 bedfile_path.to_str().expect("Invalid BED file path"),
204 true,
205 );
206
207 region_set.to_bed_gz(bedfile_path.clone())?;
208 println!(
209 "Downloaded BED file {} from BEDbase to path: {}",
210 bed_id,
211 bedfile_path.display()
212 );
213 Ok(region_set)
214 }
215
216 pub fn load_bedset(&mut self, bedset_id: &str) -> Result<BedSet> {
223 let bedset_path = self.bedset_path(bedset_id, Some(true));
224
225 if bedset_path.exists() {
226 println!("Loading cached BED file from {:?}", bedset_path.display());
227 return BedSet::try_from(bedset_path);
228 }
229
230 let bed_data = self.download_bedset_data(bedset_id).unwrap();
231 let mut file = File::create(bedset_path.clone())?;
232 let mut region_sets = Vec::new();
233 for bbid in bed_data {
234 writeln!(file, "{}", bbid)?;
235 let rs = self.load_bed(&bbid).unwrap();
236 region_sets.push(rs);
237 }
238 self.add_resource_to_cache(
239 bedset_id,
240 bedset_path.to_str().expect("Invalid BED set path"),
241 false,
242 );
243
244 Ok(BedSet::from(region_sets))
245 }
246
247 pub fn add_local_bed_to_cache(
255 &mut self,
256 bedfile: PathBuf,
257 force: Option<bool>,
258 ) -> Result<String> {
259 let regionset = RegionSet::try_from(bedfile.as_path())?;
260 self.add_regionset_to_cache(regionset, force)
261 }
262
263 pub fn add_regionset_to_cache(
271 &mut self,
272 regionset: RegionSet,
273 force: Option<bool>,
274 ) -> Result<String> {
275 let bedfile_id = regionset.identifier();
276 let cache_path = self.bedfile_path(&bedfile_id, Some(true));
277
278 let force = force.unwrap_or(false);
279 if !force && cache_path.exists() {
280 println!("{} already exists in cache", cache_path.display());
281 return Ok(bedfile_id);
282 }
283
284 regionset.to_bed_gz(cache_path.as_path())?;
285 self.add_resource_to_cache(
286 &bedfile_id,
287 cache_path
288 .to_str()
289 .expect("cache path cannot be convert to &str"),
290 true,
291 );
292 println!("BED file cached to {}", cache_path.display());
293
294 Ok(bedfile_id)
295 }
296
297 pub fn add_bedset_to_cache(&mut self, bedset: BedSet) -> Result<String> {
304 let bedset_id = bedset.identifier();
305 let bedset_path = self.bedset_path(&bedset_id, Some(true));
306 if bedset_path.exists() {
307 println!("{} already exists in cache", bedset_path.display());
308 } else {
309 let mut file = File::create(bedset_path.clone())?;
310 for rs in bedset.region_sets {
311 let bed_id = rs.identifier();
312 let _ = self.add_regionset_to_cache(rs, Some(false));
313 writeln!(file, "{}", bed_id)?;
314 }
315 }
316
317 self.add_resource_to_cache(
318 &bedset_id,
319 bedset_path
320 .to_str()
321 .expect("cache path cannot be convert to &str"),
322 false,
323 );
324 println!("BED set cached to {}", bedset_path.display());
325
326 Ok(bedset_id)
327 }
328
329 pub fn add_local_folder_as_bedset(&mut self, folder_path: PathBuf) -> Result<String> {
336 let mut region_sets = Vec::new();
337 for entry in read_dir(&folder_path).expect("Failed to read directory") {
338 let entry = entry.expect("Failed to read directory entry");
339 let file_path = entry.path();
340
341 if file_path.is_file() {
342 let rs = RegionSet::try_from(file_path).unwrap();
343 region_sets.push(rs);
344 }
345 }
346 let bedset = BedSet::from(region_sets);
347 Ok(self.add_bedset_to_cache(bedset).unwrap())
348 }
349
350 pub fn add_local_file_as_bedset(&mut self, file_path: PathBuf) -> Result<String> {
357 let bedset = BedSet::try_from(file_path).unwrap();
358 Ok(self.add_bedset_to_cache(bedset).unwrap())
359 }
360
361 fn download_bedset_data(&self, bedset_id: &str) -> Result<Vec<String>> {
368 let bedset_url = format!("{}/v1/bedset/{}/bedfiles", self.bedbase_api, bedset_id);
369
370 let response = get(&bedset_url)
371 .call()
372 .map_err(|e| anyhow!("Failed to GET {}: {}", bedset_url, e))?
373 .body_mut()
374 .read_to_string()
375 .map_err(|e| anyhow!("Failed to read response body for {}: {}", bedset_url, e))?;
376
377 let json: serde_json::Value = serde_json::from_str(&response)?;
378
379 let results = json["results"]
380 .as_array()
381 .ok_or_else(|| anyhow!("`results` is not an array"))?;
382
383 let extracted_ids: Vec<String> = results
384 .iter()
385 .filter_map(|entry| {
386 let id_val = entry.get("id");
387 id_val?.as_str().map(|s| s.to_string())
388 })
389 .collect();
390
391 Ok(extracted_ids)
392 }
393
394 fn bedfile_path(&self, bedfile_id: &str, create: Option<bool>) -> PathBuf {
402 let subfolder_name = DEFAULT_BEDFILE_SUBFOLDER;
403 let file_extension = DEFAULT_BEDFILE_EXT;
404 self.cache_path(bedfile_id, subfolder_name, file_extension, create)
405 }
406
407 fn bedset_path(&self, bedset_id: &str, create: Option<bool>) -> PathBuf {
415 let subfolder_name = DEFAULT_BEDSET_SUBFOLDER;
416 let file_extension = DEFAULT_BEDSET_EXT;
417 self.cache_path(bedset_id, subfolder_name, file_extension, create)
418 }
419
420 fn cache_path(
430 &self,
431 identifier: &str,
432 subfolder_name: &str,
433 file_extension: &str,
434 create: Option<bool>,
435 ) -> PathBuf {
436 let filename = format!("{}{}", identifier, file_extension);
437 let folder_path = self
438 .cache_folder
439 .join(subfolder_name)
440 .join(&identifier[0..1])
441 .join(&identifier[1..2]);
442
443 if create.unwrap_or(true) {
444 self.create_cache_folder(Some(&folder_path));
445 }
446 folder_path.join(filename)
447 }
448
449 fn create_cache_folder(&self, subfolder_path: Option<&Path>) {
453 let path = match subfolder_path {
454 Some(p) => p.to_path_buf(),
455 None => self.cache_folder.clone(),
456 };
457
458 if !path.exists() {
459 create_dir_all(&path).expect("Failed to create cache folder");
460 }
461 }
462
463 pub fn seek(&self, identifier: &str) -> Result<PathBuf> {
470 let file_path = self.bedfile_path(identifier, Some(false));
471 if file_path.exists() {
472 Ok(file_path)
473 } else {
474 let set_path = self.bedset_path(identifier, Some(false));
475 if set_path.exists() {
476 Ok(set_path)
477 } else {
478 Err(anyhow::anyhow!("{} does not exist in cache.", identifier))
479 }
480 }
481 }
482
483 pub fn remove(&mut self, identifier: &str) -> Result<()> {
487 let file_path = self.bedfile_path(identifier, Some(false));
488 if file_path.exists() {
489 let _ = self.local_removal(file_path.clone());
491 self.bedfile_cache.remove(identifier);
492
493 println!("{} is removed.", file_path.display());
494 Ok(())
495 } else {
496 let set_path = self.bedset_path(identifier, Some(false));
497 if set_path.exists() {
498 let bedset_file = File::open(set_path.clone())?;
499 let reader = BufReader::new(bedset_file);
500
501 let bed_ids: Vec<String> = reader.lines().collect::<Result<_, _>>()?;
502
503 for bed_id in bed_ids {
504 let _ = self.remove(&bed_id);
505 }
506
507 let _ = self.local_removal(set_path.clone());
508
509 self.bedset_cache.remove(identifier);
510
511 println!("{} is removed.", set_path.display());
512 Ok(())
513 } else {
514 Err(Error::new(
515 ErrorKind::NotFound,
516 format!("{} does not exist in cache.", file_path.display()),
517 )
518 .into())
519 }
520 }
521 }
522
523 fn local_removal(&self, file_path: PathBuf) -> Result<()> {
527 let sub_folder_2 = file_path.parent().map(PathBuf::from);
528 let sub_folder_1 = sub_folder_2
529 .as_ref()
530 .and_then(|p| p.parent().map(PathBuf::from));
531
532 remove_file(&file_path)?;
533
534 if let Some(sub2) = sub_folder_2
536 && read_dir(&sub2)?.next().is_none()
537 {
538 remove_dir(&sub2)?;
539 if let Some(sub1) = sub_folder_1
540 && read_dir(&sub1)?.next().is_none()
541 {
542 remove_dir(&sub1)?;
543 }
544 }
545
546 Ok(())
547 }
548
549 pub fn list_beds(&mut self) -> Result<Vec<Resource>> {
553 let bed_resources = self.bedfile_cache.list_resources(Some(20_000));
554 Ok(bed_resources)
555 }
556
557 pub fn list_bedsets(&mut self) -> Result<Vec<Resource>> {
561 let bedset_resources = self.bedset_cache.list_resources(Some(20_000));
562 Ok(bedset_resources)
563 }
564}