use std::{
collections::HashSet,
env, fs,
path::{Path, PathBuf},
};
use directories::ProjectDirs;
use crate::constants::{APP_NAME, APP_QUALIFIER};
#[derive(Debug)]
pub struct Cache {
base_dir: PathBuf,
pub entries: HashSet<String>,
}
impl Cache {
pub fn find(name: &str) -> Self {
let cache_dir = if let Result::Ok(path_str) = env::var("TIMEWALL_CACHE_DIR") {
PathBuf::from(path_str)
} else {
match ProjectDirs::from(APP_QUALIFIER, "", APP_NAME) {
Some(app_dirs) => app_dirs.cache_dir().to_path_buf(),
None => panic!("couldn't determine user's home directory"),
}
};
Cache::in_dir(cache_dir.join(name))
}
fn in_dir<P: AsRef<Path>>(path: P) -> Self {
let path = path.as_ref();
if !path.exists() {
fs::create_dir_all(path).expect("couldn't create cache directory");
}
let entry_dirs = path
.read_dir()
.unwrap()
.flatten()
.filter(|e| e.file_type().unwrap().is_dir())
.flat_map(|e| e.file_name().into_string())
.collect();
Cache {
base_dir: path.to_path_buf(),
entries: entry_dirs,
}
}
pub fn entry(&mut self, key: &String) -> PathBuf {
if self.entries.contains(key) {
self.get_entry(key)
} else {
self.add_entry(key)
}
}
pub fn remove_entry(&mut self, key: &str) {
let entry_path = self.get_entry(key);
if entry_path.is_dir() {
fs::remove_dir_all(entry_path).expect("couldn't remove cache entry directory");
}
self.entries.remove(key);
}
fn add_entry(&mut self, key: &str) -> PathBuf {
let entry_path = self.base_dir.join(key);
fs::create_dir(&entry_path).expect("couldn't create cache entry directory");
self.entries.insert(key.to_owned());
entry_path
}
fn get_entry(&self, key: &str) -> PathBuf {
self.base_dir.join(key)
}
}
pub struct LastWallpaper {
link_path: PathBuf,
}
impl LastWallpaper {
pub fn find() -> Self {
let cache_dir = if let Result::Ok(path_str) = env::var("TIMEWALL_CACHE_DIR") {
PathBuf::from(path_str)
} else {
match ProjectDirs::from(APP_QUALIFIER, "", APP_NAME) {
Some(app_dirs) => app_dirs.cache_dir().to_path_buf(),
None => panic!("couldn't determine user's home directory"),
}
};
LastWallpaper::load(cache_dir.join("last_wall"))
}
fn load<P: AsRef<Path>>(link_path: P) -> Self {
let link_path = link_path.as_ref();
let parent_dir = link_path.parent().unwrap();
if !parent_dir.exists() {
fs::create_dir_all(parent_dir).expect("couldn't create cache directory");
}
LastWallpaper {
link_path: link_path.to_path_buf(),
}
}
pub fn save<P: AsRef<Path>>(&self, path: P) {
if fs::read_link(&self.link_path).is_ok() {
fs::remove_file(&self.link_path).ok();
}
std::os::unix::fs::symlink(path.as_ref().canonicalize().unwrap(), &self.link_path).ok();
}
pub fn get(&self) -> Option<PathBuf> {
if self.link_path.exists() {
Some(fs::read_link(&self.link_path).unwrap())
} else {
None
}
}
pub fn clear(&self) {
if fs::read_link(&self.link_path).is_ok() {
fs::remove_file(&self.link_path).ok();
}
}
}
#[cfg(test)]
mod tests {
use assert_fs::prelude::*;
use assert_fs::TempDir;
use predicates::prelude::*;
use rstest::*;
use super::*;
#[fixture]
fn tmp_dir() -> TempDir {
assert_fs::TempDir::new().unwrap()
}
#[rstest]
fn test_cache_in_dir_not_exists(tmp_dir: TempDir) {
let expected_dir = tmp_dir.child("random_dir");
Cache::in_dir(&expected_dir);
expected_dir.assert(predicate::path::is_dir());
}
#[rstest]
fn test_cache_in_dir_exists(tmp_dir: TempDir) {
let expected_entries = HashSet::from([String::from("first"), String::from("other")]);
for entry in &expected_entries {
tmp_dir.child(entry).create_dir_all().unwrap();
}
let cache = Cache::in_dir(&tmp_dir);
assert_eq!(cache.entries, expected_entries);
}
#[rstest]
fn test_cache_entry_not_exists(tmp_dir: TempDir) {
let entry_name = String::from("random-entry");
let expected_dir = tmp_dir.child(&entry_name);
let mut cache = Cache::in_dir(&tmp_dir);
assert_eq!(cache.entry(&entry_name), expected_dir.path());
expected_dir.assert(predicate::path::is_dir());
}
#[rstest]
fn test_cache_entry_exists(tmp_dir: TempDir) {
let entry_name = String::from("some_entry");
let expected_dir = tmp_dir.child(&entry_name);
expected_dir.create_dir_all().unwrap();
let mut cache = Cache::in_dir(&tmp_dir);
assert_eq!(cache.entry(&entry_name), expected_dir.path());
expected_dir.assert(predicate::path::is_dir());
cache.remove_entry(&entry_name);
expected_dir.assert(predicate::path::missing());
}
#[rstest]
fn test_cache_entry_remove_exists(tmp_dir: TempDir) {
let entry_name = String::from("some_entry");
let expected_dir = tmp_dir.child(&entry_name);
expected_dir.create_dir_all().unwrap();
let mut cache = Cache::in_dir(&tmp_dir);
expected_dir.assert(predicate::path::is_dir());
cache.remove_entry(&entry_name);
expected_dir.assert(predicate::path::missing());
}
#[rstest]
fn test_cache_entry_remove_not_exists(tmp_dir: TempDir) {
let entry_name = String::from("some_entry");
let expected_dir = tmp_dir.child(&entry_name);
let mut cache = Cache::in_dir(&tmp_dir);
expected_dir.assert(predicate::path::missing());
cache.remove_entry(&entry_name);
expected_dir.assert(predicate::path::missing());
}
#[rstest]
#[should_panic]
fn test_cache_entry_file_conflict(tmp_dir: TempDir) {
let entry_name = String::from("some_entry");
tmp_dir.child(&entry_name).touch().unwrap();
Cache::in_dir(&tmp_dir).entry(&entry_name);
}
#[rstest]
fn test_last_wallpaper_load_not_exists(tmp_dir: TempDir) {
let fake_cache_dir = tmp_dir.child("cache_dir");
let link_path = fake_cache_dir.child("test_link");
LastWallpaper::load(&link_path);
fake_cache_dir.assert(predicate::path::exists());
}
#[rstest]
fn test_last_wallpaper_save_get(tmp_dir: TempDir) {
let target_path_1 = tmp_dir.child("target.heic");
let target_path_2 = tmp_dir.child("other_target.heic");
let link_path = tmp_dir.child("test_link");
target_path_1.touch().unwrap();
target_path_2.touch().unwrap();
let last_wall = LastWallpaper::load(&link_path);
link_path.assert(predicate::path::missing());
assert_eq!(last_wall.get(), None);
last_wall.save(&target_path_1);
assert_eq!(last_wall.get(), Some(target_path_1.to_path_buf()));
fs::remove_file(target_path_1).unwrap();
last_wall.save(&target_path_2);
assert_eq!(last_wall.get(), Some(target_path_2.to_path_buf()));
}
#[rstest]
fn test_last_wallpaper_save_clear(tmp_dir: TempDir) {
let target_path = tmp_dir.child("target.heic");
let link_path = tmp_dir.child("test_link");
target_path.touch().unwrap();
let last_wall = LastWallpaper::load(&link_path);
last_wall.save(&target_path);
link_path.assert(predicate::path::exists());
last_wall.clear();
link_path.assert(predicate::path::missing());
}
}