1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use std::{
    error::Error,
    fs::{self, File},
    io::{BufReader, Cursor},
    path::Path,
    time::Instant,
};

use crate::{models::Version, storage::DataStorage};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use url::Url;
use zip::ZipArchive;

#[derive(Debug, Serialize, Deserialize)]
pub struct Hrdf {
    data_storage: DataStorage,
}

impl Hrdf {
    /// Loads and parses the data.<br>
    /// If an URL is provided, the HRDF archive (ZIP file) is downloaded automatically. If a path is provided, it must absolutely point to an HRDF archive (ZIP file).<br>
    /// The ZIP archive is automatically decompressed into the /tmp folder.
    pub async fn new(
        version: Version,
        url_or_path: &str,
        force_rebuild_cache: bool,
    ) -> Result<Self, Box<dyn Error>> {
        let now = Instant::now();

        let unique_filename = format!("{:x}", Sha256::digest(url_or_path.as_bytes()));
        let cache_path = format!("{unique_filename}.cache");

        let hrdf = if Path::new(&cache_path).exists() && !force_rebuild_cache {
            // Loading from cache.
            log::info!("Loading HRDF data from cache ({cache_path})...");

            // If loading from cache fails, None is returned.
            Hrdf::load_from_cache(&cache_path).ok()
        } else {
            // No loading from cache.
            None
        };

        let hrdf = if let Some(hrdf) = hrdf {
            // The cache has been loaded without error.
            hrdf
        } else {
            // The cache must be built.
            // If cache loading has failed, the cache must be rebuilt.
            let compressed_data_path = if Url::parse(url_or_path).is_ok() {
                let compressed_data_path = format!("/tmp/{unique_filename}.zip");

                if !Path::new(&compressed_data_path).exists() {
                    // The data must be downloaded.
                    log::info!("Downloading HRDF data to {compressed_data_path}...");
                    let response = reqwest::get(url_or_path).await?;
                    let mut file = std::fs::File::create(&compressed_data_path)?;
                    let mut content = Cursor::new(response.bytes().await?);
                    std::io::copy(&mut content, &mut file)?;
                }

                compressed_data_path
            } else {
                url_or_path.to_string()
            };

            let decompressed_data_path = format!("/tmp/{unique_filename}");

            if !Path::new(&decompressed_data_path).exists() {
                // The data must be decompressed.
                log::info!("Unzipping HRDF archive into {decompressed_data_path}...");
                let file = File::open(&compressed_data_path)?;
                let mut archive = ZipArchive::new(BufReader::new(file))?;
                archive.extract(&decompressed_data_path)?;
            }

            log::info!("Parsing HRDF data from {decompressed_data_path}...");

            let hrdf = Self {
                data_storage: DataStorage::new(version, &decompressed_data_path)?,
            };

            log::info!("Building cache...");
            hrdf.build_cache(&cache_path)?;
            hrdf
        };

        let elapsed = now.elapsed();

        log::info!("HRDF data loaded in {:.2?}!", elapsed);

        Ok(hrdf)
    }

    // Getters/Setters

    pub fn data_storage(&self) -> &DataStorage {
        &self.data_storage
    }

    // Functions

    pub fn build_cache(&self, path: &str) -> Result<(), Box<dyn Error>> {
        let data = bincode::serialize(&self)?;
        fs::write(path, data)?;
        Ok(())
    }

    pub fn load_from_cache(path: &str) -> Result<Self, Box<dyn Error>> {
        let data = fs::read(path)?;
        let hrdf: Self = bincode::deserialize(&data)?;
        Ok(hrdf)
    }
}