use std::ffi::OsStr;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use anyhow::Result;
use chrono::offset::LocalResult;
use chrono::{DateTime, Local, NaiveDateTime};
use percent_encoding::percent_decode;
use serde::Serialize;
use crate::utils;
const DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:%S";
fn parse_key_value(line: &str) -> Option<(&str, &str)> {
let mut parts = line.split('=').peekable();
let key = if let Some(key) = parts.next() {
key
} else {
return None;
};
let value = &line[key.len() + 1..];
Some((key, value))
}
#[derive(Debug, Serialize)]
pub struct TrashInfo {
pub path: PathBuf,
pub deletion_date: DateTime<Local>,
pub deleted_path: PathBuf,
pub info_path: PathBuf,
}
impl TrashInfo {
pub fn from_files(
info_path: impl AsRef<Path>,
deleted_path: impl AsRef<Path>,
) -> Result<Self> {
let info_path = info_path.as_ref().to_path_buf();
let deleted_path = deleted_path.as_ref().to_path_buf();
let file = File::open(&info_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?;
if i == 0 {
if line != "[Trash Info]" {
bail!("Missing [Trash Info] header.");
} else {
continue;
}
}
if let Some((key, value)) = parse_key_value(&line) {
match key {
"Path" => {
let value = percent_decode(value.as_bytes()).collect::<Vec<_>>();
let value = PathBuf::from(OsStr::from_bytes(&value));
path = Some(value)
}
"DeletionDate" => {
let date = NaiveDateTime::parse_from_str(value, DATE_FORMAT)?
.and_local_timezone(Local);
let date = match date {
LocalResult::Single(date) => date,
LocalResult::Ambiguous(d1, d2) => {
bail!("ambiguous date parsed: {value}, could be {d1} or {d2}")
}
LocalResult::None => bail!("could not parse date"),
};
deletion_date = Some(date)
}
_ => continue,
}
} else {
continue;
}
}
let path = match path {
Some(path) => path,
None => bail!("Missing path key in trashinfo."),
};
let deletion_date = match deletion_date {
Some(deletion_date) => deletion_date,
None => bail!("Missing date key in trashinfo."),
};
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={}", utils::percent_encode(&self.path))?;
writeln!(
out,
"DeletionDate={}",
self.deletion_date.format(DATE_FORMAT)
)?;
Ok(())
}
}