dictate 0.9.0

CLI utility for looking up words in online dictionary
Documentation
use std::fs as std_fs;
use std::path::PathBuf;

use directories::BaseDirs;
use tokio::fs::{self, File, OpenOptions};
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, Error, ErrorKind, Result};

use crate::entry::Entry;

pub struct Cache {
    file: File,
}

impl Cache {
    fn get_path() -> Result<PathBuf> {
        let dirs = BaseDirs::new()
            .ok_or(Error::new(ErrorKind::Other, "Cannot determine cache directory because no valid home directory path could be retrieved from the operating system."))?
            .cache_dir()
            .join("dictate");
        std_fs::create_dir_all(&dirs)?;
        Ok(dirs.join("entries.json"))
    }

    pub async fn open(opts: &mut OpenOptions) -> Result<Self> {
        Ok(Self {
            file: opts.append(false).open(&Self::get_path()?).await?,
        })
    }

    async fn get_entries(&mut self) -> Result<Vec<Entry>> {
        let mut json = String::new();
        self.file.seek(std::io::SeekFrom::Start(0)).await?;
        self.file.read_to_string(&mut json).await?;
        if json.is_empty() {
            json = "[]".to_string();
        }

        Ok(serde_json::from_str(&json)?)
    }

    pub async fn lookup_word(&mut self, word: &str) -> Result<Vec<Entry>> {
        let entries_cache = self.get_entries().await?;

        Ok(entries_cache
            .into_iter()
            .filter(|e| e.word == word)
            .collect())
    }

    pub async fn append(&mut self, entries: &mut Vec<Entry>) -> Result<()> {
        let mut entries_cache = self.get_entries().await?;
        entries_cache.append(entries);

        let json = serde_json::to_string(&entries_cache)?;
        self.file.seek(std::io::SeekFrom::Start(0)).await?;
        self.file.write_all(json.as_bytes()).await?;

        Ok(())
    }

    pub async fn clean(self) -> Result<()> {
        drop(self.file);

        let mut parent = Self::get_path()?;
        parent.pop();
        fs::remove_dir_all(parent).await?;

        Ok(())
    }
}