2023-09-01 10:37:30 +09:00
|
|
|
//! Manipulate subdirectories of other storages, including directories.
|
|
|
|
|
2023-12-04 21:34:24 +09:00
|
|
|
use anyhow::{anyhow, Context, Result};
|
2023-09-01 10:37:30 +09:00
|
|
|
use serde::{Deserialize, Serialize};
|
2023-12-06 11:18:46 +09:00
|
|
|
use std::{collections::HashMap, fmt, path};
|
2023-12-04 21:34:24 +09:00
|
|
|
|
|
|
|
use crate::devices;
|
2023-09-01 10:37:30 +09:00
|
|
|
|
2023-12-05 03:24:49 +09:00
|
|
|
use super::{local_info::LocalInfo, Storage, StorageExt};
|
2023-09-01 10:37:30 +09:00
|
|
|
|
|
|
|
/// Subdirectory of other [Storage]s.
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct Directory {
|
|
|
|
name: String,
|
2023-12-04 21:34:24 +09:00
|
|
|
parent: String,
|
2023-09-01 10:37:30 +09:00
|
|
|
relative_path: path::PathBuf,
|
2024-03-03 06:11:25 +09:00
|
|
|
pub notes: String,
|
|
|
|
local_infos: HashMap<String, LocalInfo>,
|
2023-09-01 10:37:30 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Directory {
|
|
|
|
/// - `name`: id
|
|
|
|
/// - `parent`: where the directory locates.
|
|
|
|
/// - `relative_path`: path from root of the parent storage.
|
|
|
|
/// - `notes`: supplimental notes.
|
2023-12-05 03:24:49 +09:00
|
|
|
fn new(
|
2023-09-01 10:37:30 +09:00
|
|
|
name: String,
|
2023-12-06 11:18:46 +09:00
|
|
|
parent: String,
|
2023-09-01 10:37:30 +09:00
|
|
|
relative_path: path::PathBuf,
|
|
|
|
notes: String,
|
2024-03-03 06:11:25 +09:00
|
|
|
local_infos: HashMap<String, LocalInfo>,
|
2023-09-01 10:37:30 +09:00
|
|
|
) -> Directory {
|
2023-12-04 21:34:24 +09:00
|
|
|
Directory {
|
|
|
|
name,
|
|
|
|
parent,
|
|
|
|
relative_path,
|
|
|
|
notes,
|
2024-03-03 06:11:25 +09:00
|
|
|
local_infos,
|
2023-12-04 21:34:24 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-05 03:24:49 +09:00
|
|
|
pub fn try_from_device_path(
|
|
|
|
name: String,
|
|
|
|
path: path::PathBuf,
|
|
|
|
notes: String,
|
|
|
|
device: &devices::Device,
|
|
|
|
storages: &HashMap<String, Storage>,
|
|
|
|
) -> Result<Directory> {
|
|
|
|
let (parent_name, diff_path) = storages
|
|
|
|
.iter()
|
|
|
|
.filter(|(_k, v)| v.has_alias(&device))
|
|
|
|
.filter_map(|(k, v)| {
|
|
|
|
let diff = pathdiff::diff_paths(&path, v.mount_path(&device, &storages).unwrap())?;
|
|
|
|
trace!("Pathdiff: {:?}", diff);
|
2023-12-06 11:18:46 +09:00
|
|
|
if diff.components().any(|c| c == path::Component::ParentDir) {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some((k, diff))
|
|
|
|
}
|
2023-12-05 03:24:49 +09:00
|
|
|
})
|
|
|
|
.min_by_key(|(_k, v)| {
|
|
|
|
let diff_paths: Vec<path::Component<'_>> = v.components().collect();
|
|
|
|
diff_paths.len()
|
|
|
|
})
|
|
|
|
.context(format!("Failed to compare diff of paths"))?;
|
|
|
|
trace!("Selected parent: {}", parent_name);
|
|
|
|
let local_info = LocalInfo::new("".to_string(), path);
|
|
|
|
Ok(Directory::new(
|
|
|
|
name,
|
|
|
|
parent_name.clone(),
|
|
|
|
diff_path,
|
|
|
|
notes,
|
|
|
|
HashMap::from([(device.name(), local_info)]),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update_note(self, notes: String) -> Directory {
|
|
|
|
Directory::new(
|
|
|
|
self.name,
|
|
|
|
self.parent,
|
|
|
|
self.relative_path,
|
|
|
|
notes,
|
2024-03-03 06:11:25 +09:00
|
|
|
self.local_infos,
|
2023-12-05 03:24:49 +09:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-12-04 21:34:24 +09:00
|
|
|
/// Get parent `&Storage` of directory.
|
2024-03-03 06:11:25 +09:00
|
|
|
pub fn parent<'a>(&'a self, storages: &'a HashMap<String, Storage>) -> Result<&Storage> {
|
2023-12-04 21:34:24 +09:00
|
|
|
let parent = &self.parent;
|
2023-12-05 03:24:49 +09:00
|
|
|
storages.get(parent).context(format!(
|
2023-12-04 21:34:24 +09:00
|
|
|
"No parent {} exists for directory {}",
|
|
|
|
parent,
|
|
|
|
self.name()
|
|
|
|
))
|
2023-09-01 10:37:30 +09:00
|
|
|
}
|
2023-12-04 21:34:24 +09:00
|
|
|
|
2023-12-05 03:24:49 +09:00
|
|
|
/// Resolve mount path of directory with current device.
|
|
|
|
fn mount_path(
|
|
|
|
&self,
|
|
|
|
device: &devices::Device,
|
|
|
|
storages: &HashMap<String, Storage>,
|
|
|
|
) -> Result<path::PathBuf> {
|
|
|
|
let parent = self.parent(&storages)?;
|
|
|
|
let parent_mount_path = parent.mount_path(&device, &storages)?;
|
|
|
|
Ok(parent_mount_path.join(self.relative_path.clone()))
|
|
|
|
}
|
2023-09-01 10:37:30 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
impl StorageExt for Directory {
|
|
|
|
fn name(&self) -> &String {
|
|
|
|
&self.name
|
|
|
|
}
|
2023-12-04 21:34:24 +09:00
|
|
|
|
2024-03-03 06:11:25 +09:00
|
|
|
fn capacity(&self) -> Option<u64> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2023-12-08 03:18:31 +09:00
|
|
|
fn local_info(&self, device: &devices::Device) -> Option<&LocalInfo> {
|
2024-03-03 06:11:25 +09:00
|
|
|
self.local_infos.get(&device.name())
|
2023-12-05 03:24:49 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
fn mount_path(
|
|
|
|
&self,
|
|
|
|
device: &devices::Device,
|
|
|
|
storages: &HashMap<String, Storage>,
|
|
|
|
) -> Result<path::PathBuf> {
|
|
|
|
Ok(self.mount_path(device, storages)?)
|
|
|
|
}
|
2023-12-08 03:18:31 +09:00
|
|
|
|
|
|
|
/// This method doesn't use `mount_path`.
|
|
|
|
fn bound_on_device(
|
|
|
|
&mut self,
|
|
|
|
alias: String,
|
|
|
|
mount_point: path::PathBuf,
|
|
|
|
device: &devices::Device,
|
|
|
|
) -> Result<()> {
|
|
|
|
let new_local_info = LocalInfo::new(alias, mount_point);
|
2024-03-03 06:11:25 +09:00
|
|
|
match self.local_infos.insert(device.name(), new_local_info) {
|
2023-12-08 03:18:31 +09:00
|
|
|
Some(v) => println!("Value updated. old val is: {:?}", v),
|
|
|
|
None => println!("inserted new val"),
|
|
|
|
};
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-09-01 10:37:30 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for Directory {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"S {name:<10} < {parent:<10}{relative_path:<10} : {notes}",
|
|
|
|
name = self.name(),
|
|
|
|
parent = self.parent,
|
|
|
|
relative_path = self.relative_path.display(),
|
|
|
|
notes = self.notes,
|
|
|
|
)
|
|
|
|
}
|
2023-12-04 21:34:24 +09:00
|
|
|
}
|
2024-03-03 06:11:25 +09:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use std::{collections::HashMap, path::PathBuf};
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
devices::Device,
|
|
|
|
storages::{
|
|
|
|
self, local_info::LocalInfo, physical_drive_partition::PhysicalDrivePartition, Storage,
|
|
|
|
StorageExt,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
use super::Directory;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn name() {
|
|
|
|
let local_info_phys =
|
|
|
|
LocalInfo::new("phys_alias".to_string(), PathBuf::from("/mnt/sample"));
|
|
|
|
let local_info_dir =
|
|
|
|
LocalInfo::new("dir_alias".to_string(), PathBuf::from("/mnt/sample/subdir"));
|
|
|
|
let device = Device::new("test_device".to_string());
|
|
|
|
let mut local_infos = HashMap::new();
|
|
|
|
local_infos.insert(device.name(), local_info_dir);
|
|
|
|
let physical = PhysicalDrivePartition::new(
|
|
|
|
"parent".to_string(),
|
|
|
|
"SSD".to_string(),
|
|
|
|
1_000_000_000,
|
|
|
|
"btrfs".to_string(),
|
|
|
|
false,
|
|
|
|
local_info_phys,
|
|
|
|
&device,
|
|
|
|
);
|
|
|
|
let directory = Directory::new(
|
|
|
|
"test_name".to_owned(),
|
|
|
|
"parent".to_string(),
|
|
|
|
"subdir".into(),
|
|
|
|
"some note".to_string(),
|
|
|
|
local_infos,
|
|
|
|
);
|
|
|
|
let mut storages: HashMap<String, Storage> = HashMap::new();
|
|
|
|
storages.insert(
|
|
|
|
physical.name().to_string(),
|
|
|
|
Storage::PhysicalStorage(physical),
|
|
|
|
);
|
|
|
|
storages.insert(
|
|
|
|
directory.name().to_string(),
|
|
|
|
Storage::SubDirectory(directory),
|
|
|
|
);
|
|
|
|
// assert_eq!(directory.name(), "test_name");
|
|
|
|
assert_eq!(storages.get("test_name").unwrap().name(), "test_name");
|
|
|
|
assert_eq!(
|
|
|
|
storages
|
|
|
|
.get("test_name")
|
|
|
|
.unwrap()
|
|
|
|
.mount_path(&device, &storages)
|
|
|
|
.unwrap(),
|
|
|
|
PathBuf::from("/mnt/sample/subdir")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|