use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
use dirs;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PathType {
Directory,
File,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PathManager {
path: PathBuf,
path_type: PathType,
}
impl PathManager {
pub fn get_data_dir() -> Result<Self> {
let path = dirs::data_dir().ok_or(anyhow!("无法获取数据目录"))?;
Ok(Self::dir(path))
}
pub fn dir<P: AsRef<Path>>(path: P) -> Self {
Self {
path: path.as_ref().to_path_buf(),
path_type: PathType::Directory
}
}
pub fn file<P: AsRef<Path>>(path: P) -> Self {
Self {
path: path.as_ref().to_path_buf(),
path_type: PathType::File
}
}
pub fn join_dir<P: AsRef<Path>>(mut self, dir: P) -> Result<Self> {
if self.path_type == PathType::File {
return Err(anyhow!("无法在文件路径上进行join操作"));
}
self.path = self.path.join(dir);
self.path_type = PathType::Directory;
Ok(self)
}
pub fn join_file<P: AsRef<Path>>(mut self, filename: P) -> Result<Self> {
if self.path_type == PathType::File {
return Err(anyhow!("无法在文件路径上进行join操作"));
}
self.path = self.path.join(filename);
self.path_type = PathType::File;
Ok(self)
}
pub fn ensure(self) -> Result<Self> {
match self.path_type {
PathType::Directory => self.ensure_dir(),
PathType::File => self.ensure_file(),
}
}
fn ensure_dir(self) -> Result<Self> {
if self.path.exists() {
if !self.path.is_dir() {
return Err(anyhow!("路径 '{}' 已存在但不是目录",
self.path.to_string_lossy()));
}
} else {
std::fs::create_dir_all(&self.path)?;
}
Ok(self)
}
fn ensure_file(self) -> Result<Self> {
if self.path.exists() {
if !self.path.is_file() {
return Err(anyhow!("路径 '{}' 已存在但不是文件",
self.path.to_string_lossy()));
}
} else {
if let Some(parent) = self.path.parent() {
if !parent.exists() {
std::fs::create_dir_all(parent)?;
} else if !parent.is_dir() {
return Err(anyhow!("父路径 '{}' 存在但不是目录",
parent.to_string_lossy()));
}
}
std::fs::File::create(&self.path)?;
}
Ok(self)
}
pub fn path(self) -> PathBuf {
self.path
}
pub fn string(self) -> Result<String> {
self.path.to_str()
.map(String::from)
.ok_or(anyhow!("无法将路径转换为字符串"))
}
}
pub fn get_data_file_path(app_name: &str, filename: &str) -> Result<PathManager> {
PathManager::get_data_dir()?.join_dir(app_name)?.join_file(filename)
}
pub fn get_data_child_dir_path(app_name: &str, child_dir: Option<String>) -> Result<PathManager> {
let path_manager = PathManager::get_data_dir()?.join_dir(app_name)?;
let path_manager = match child_dir {
Some(child_dir) => path_manager.join_dir(&child_dir)?,
None => path_manager
};
Ok(path_manager)
}