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}