char_coal/app/
cache.rs

1use std::{
2    collections::hash_map::DefaultHasher,
3    fs::{self, File, OpenOptions},
4    hash::{Hash, Hasher},
5    io,
6    path::{Path, PathBuf},
7};
8
9#[derive(Clone)]
10pub struct Cache {
11    cache_dir: PathBuf,
12    vault_dir: PathBuf,
13    tmp_dir: PathBuf,
14}
15
16enum CacheFile {
17    Normal(u8, String),
18    Absurd(u64),
19}
20
21impl CacheFile {
22    fn str_hash(s: impl AsRef<str>) -> u64 {
23        let mut hasher = DefaultHasher::new();
24        s.as_ref().hash(&mut hasher);
25        hasher.finish()
26    }
27    fn generate(s: String) -> Self {
28        let hash_num = CacheFile::str_hash(&s);
29        if s.contains(' ') || !s.is_ascii() {
30            CacheFile::Absurd(hash_num)
31        } else {
32            CacheFile::Normal((hash_num % 256) as u8, s)
33        }
34    }
35    fn consume(self, cache: &Cache, suffix: &'static str) -> io::Result<PathBuf> {
36        match self {
37            CacheFile::Normal(dir, file) => {
38                let mut path = cache.cache_dir.clone();
39                path.push(format!("{:02x}", dir));
40                fs::create_dir_all(&path)?;
41                path.push(format!("{}.{}", file, suffix));
42                Ok(path)
43            }
44            CacheFile::Absurd(file) => {
45                let mut path = cache.vault_dir.clone();
46                path.push(format!("{:x}.{}", file, suffix));
47                Ok(path)
48            }
49        }
50    }
51}
52
53impl Cache {
54    pub fn new(cache_dir: PathBuf, vault_dir: PathBuf, tmp_dir: PathBuf) -> Self {
55        Self {
56            cache_dir,
57            vault_dir,
58            tmp_dir,
59        }
60    }
61
62    fn get_file_path(&self, word: impl AsRef<str>, suffix: &'static str) -> io::Result<PathBuf> {
63        CacheFile::generate(word.as_ref().to_owned()).consume(self, suffix)
64    }
65
66    pub fn query(&self, word: impl AsRef<str>, suffix: &'static str) -> io::Result<File> {
67        let path = self.get_file_path(&word, suffix)?;
68        let file = OpenOptions::new().read(true).open(path)?;
69        Ok(file)
70    }
71
72    pub fn store(&self, word: impl AsRef<str>, suffix: &'static str) -> io::Result<File> {
73        let path = self.get_file_path(&word, suffix)?;
74        let file = OpenOptions::new().create(true).write(true).open(path)?;
75        Ok(file)
76    }
77
78    pub fn show(&self) -> &PathBuf {
79        &self.cache_dir
80    }
81
82    pub fn clean(&self) -> io::Result<()> {
83        fs::remove_dir_all(&self.cache_dir)?;
84        fs::remove_dir_all(&self.vault_dir)?;
85
86        Ok(())
87    }
88
89    fn tilde_expand(dir: impl AsRef<Path>) -> io::Result<PathBuf> {
90        let mut path = (dir.as_ref().iter().take(1))
91            .map(|s| -> io::Result<_> {
92                if s == "~" {
93                    Ok(directories_next::UserDirs::new()
94                        .ok_or(io::Error::from(io::ErrorKind::Unsupported))?
95                        .home_dir()
96                        .to_path_buf())
97                } else {
98                    Ok(PathBuf::from(s))
99                }
100            })
101            .collect::<io::Result<PathBuf>>()?;
102        for s in dir.as_ref().iter().skip(1) {
103            path.push(s)
104        }
105        Ok(path)
106    }
107
108    fn ensure_dir(dir: &PathBuf) -> io::Result<()> {
109        if (dir.parent())
110            .and_then(|p| if p.exists() { Some(()) } else { None })
111            .is_none()
112        {
113            println!("Parent dir of target not exist.");
114            Err(io::Error::from(io::ErrorKind::NotFound))?
115        }
116        Ok(())
117    }
118
119    pub fn import(&self, dir: PathBuf) -> io::Result<()> {
120        fs::remove_dir_all(&self.tmp_dir)?;
121        fs::create_dir_all(&self.tmp_dir)?;
122
123        let dir = Self::tilde_expand(dir)?;
124        Self::ensure_dir(&dir)?;
125
126        let i_file = File::open(dir)?;
127        let mut archive = tar::Archive::new(i_file);
128        archive.unpack(&self.tmp_dir)?;
129
130        for direntry in fs::read_dir(&self.tmp_dir)? {
131            let direntry = direntry?;
132            let src = direntry.path();
133
134            let (src_name, src_suffix) = {
135                fn split_file_at_dot(file: String) -> Result<(String, String), io::Error> {
136                    if let Some((a, b)) = file.rsplit_once('.') {
137                        Ok((a.to_owned(), b.to_owned()))
138                    } else {
139                        Err(io::Error::from(io::ErrorKind::InvalidInput))?
140                    }
141                }
142                src.file_name()
143                    .map(|s| s.to_str().unwrap().to_owned())
144                    .map(split_file_at_dot)
145                    .ok_or(io::Error::from(io::ErrorKind::InvalidInput))??
146            };
147            let src_suffix = match src_suffix.as_str() {
148                "bin" => "bin",
149                "mp3" => "mp3",
150                _ => Err(io::Error::from(io::ErrorKind::InvalidInput))?,
151            };
152            let mut src = OpenOptions::new().read(true).open(src)?;
153            let mut dest = self.store(src_name, src_suffix)?;
154            io::copy(&mut src, &mut dest)?;
155        }
156        fs::remove_dir_all(&self.tmp_dir)?;
157        Ok(())
158    }
159
160    pub fn export(&self, dir: PathBuf) -> io::Result<()> {
161        let dir = Self::tilde_expand(dir)?;
162        Self::ensure_dir(&dir)?;
163        if dir.exists() {
164            println!("Target exists.")
165        }
166        let o_file = File::create(dir)?;
167        let mut builder = tar::Builder::new(o_file);
168        (self.cache_dir.read_dir()?)
169            .flat_map(|sub| -> io::Result<_> {
170                let iter = sub?.path().read_dir()?;
171                Ok(iter)
172            })
173            .flatten()
174            .flat_map(|file| -> io::Result<_> {
175                let p = file?.path();
176                Ok(p)
177            })
178            .try_for_each(|path| -> io::Result<_> {
179                builder.append_path_with_name(&path, path.file_name().unwrap())?;
180                Ok(())
181            })?;
182        builder.finish()?;
183        directories_next::UserDirs::new().unwrap().home_dir();
184        Ok(())
185    }
186}