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
use dirs::config_dir;
use serde::Deserialize;
use toml;
extern crate dirs;
use anyhow::Context;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tokio::fs::{metadata, read_to_string};

pub type AllMappings = HashMap<String, String>;

const DEFAULT_MAPPING: &str = include_str!("../../../../config/mapping.toml");

#[derive(Debug, Default)]
pub struct MappingManager {
    mappings: HashMap<String, Mapping>,
    path: Option<PathBuf>,
}

#[derive(Deserialize, Debug, Default, Clone)]
pub struct Mapping {
    fields: Vec<FieldMapping>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct FieldMapping {
    pub from: String,
    pub to: String,
}

impl MappingManager {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_file(f: &str) -> Self {
        Self {
            mappings: Default::default(),
            path: Some(f.to_string().into()),
        }
    }

    pub fn to_field_hashmap(&self) -> HashMap<String, String> {
        let map: HashMap<String, String> = self
            .mappings
            .clone()
            .into_iter()
            .map(|(_namespace, mapping)| mapping.fields.into_iter())
            .flatten()
            .map(|field| (field.from, field.to))
            .collect();
        map
    }

    pub async fn init(&mut self) -> anyhow::Result<()> {
        // Use path that was passed in (via command line arguments)
        if self.path == None {
            self.path = mapping_path().await;
        }
        // Use a default path if it exists.
        // This checks for user system-dependent config path, e.g. on linux:
        // ~/.config/openaudiosearch/mapping.toml and /etc/openaudiosearch/mapping.toml
        if let Some(path) = &self.path {
            let contents = read_to_string(&path)
                .await
                .with_context(|| format!("File not found: {}", path.as_path().to_str().unwrap()))?;
            let mapping: HashMap<String, Mapping> = toml::from_str(&contents)?;
            self.mappings = mapping;
        // Use default mapping (included at compile time)
        } else {
            let mapping: HashMap<String, Mapping> = toml::from_str(DEFAULT_MAPPING)?;
            self.mappings = mapping;
        }
        Ok(())
    }
}

async fn mapping_path() -> Option<PathBuf> {
    let suffix = PathBuf::from(r"openaudiosearch/mapping.toml");
    if let Some(config_path) = config_dir() {
        let path = config_path.join(&suffix);
        if path_exists(&path).await {
            return Some(path);
        }
    }
    let path = PathBuf::from(r"/etc/openaudiosearch/mapping.toml").join(&suffix);
    if path_exists(&path).await {
        return Some(path);
    }

    None
}

async fn path_exists(path: &Path) -> bool {
    let metadata = match metadata(&path).await {
        Ok(metadata) => metadata.is_file(),
        Err(e) => {
            log::debug!(
                "{} on path: {}",
                e,
                path.as_os_str().to_str().unwrap_or_default()
            );
            false
        }
    };
    metadata
}