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
use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
use std::path::{Path, PathBuf};

use chrono::{DateTime, Local, TimeZone};
use regex::Regex;

use crate::errors::{Error, TrashInfoError};

lazy_static! {
    static ref KEY_VALUE_PATTERN: Regex = Regex::new(r"([A-Za-z]+)\s*=\s*(.*)").unwrap();
}

const DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:%S";

#[derive(Debug)]
pub struct TrashInfo {
    /// The original path where this file was located before it was deleted.
    pub path: PathBuf,
    pub deletion_date: DateTime<Local>,

    /// The location of the deleted file after deletion.
    pub deleted_path: PathBuf,
    /// The location of the `info` description file.
    pub info_path: PathBuf,
}

impl TrashInfo {
    pub fn from_files(
        info_path: impl AsRef<Path>,
        deleted_path: impl AsRef<Path>,
    ) -> Result<Self, Error> {
        let path = info_path.as_ref();
        let info_path = path.to_path_buf();
        let deleted_path = deleted_path.as_ref().to_path_buf();
        let file = File::open(path)?;
        let reader = BufReader::new(file);

        let mut path = None;
        let mut deletion_date = None;

        for (i, line) in reader.lines().enumerate() {
            let line = line?;

            // first line must be "[Trash Info]"
            if i == 0 && line != "[Trash Info]" {
                return Err(Error::BadTrashInfo(TrashInfoError::MissingHeader));
            }

            // look for path and deletion date
            let captures = match KEY_VALUE_PATTERN.captures(&line) {
                Some(captures) => captures,
                None => continue,
            };

            // safe to unwrap because the parser confirmed their existence
            let key = captures.get(1).unwrap().as_str();
            let value = captures.get(2).unwrap().as_str();

            match key {
                "Path" => {
                    let value = PathBuf::from(value);
                    path = Some(value)
                }
                "DeletionDate" => {
                    let date = Local.datetime_from_str(value, DATE_FORMAT)?;
                    deletion_date = Some(date)
                }
                _ => continue,
            }
        }

        let path = match path {
            Some(path) => path,
            None => return Err(Error::BadTrashInfo(TrashInfoError::MissingPath)),
        };

        let deletion_date = match deletion_date {
            Some(deletion_date) => deletion_date,
            None => return Err(Error::BadTrashInfo(TrashInfoError::MissingDate)),
        };

        Ok(TrashInfo {
            path,
            deletion_date,
            deleted_path,
            info_path,
        })
    }

    pub fn write(&self, mut out: impl Write) -> Result<(), io::Error> {
        writeln!(out, "[Trash Info]")?;
        writeln!(out, "Path={}", self.path.to_str().unwrap())?;
        writeln!(
            out,
            "DeletionDate={}",
            self.deletion_date.format(DATE_FORMAT)
        )?;
        Ok(())
    }
}