use super::builders::build_event;
use super::config::{VirtualDeviceConfig, VirtualStorageConfig};
use crate::ptp::{EventCode, ObjectHandle, StorageId};
use std::collections::{HashMap, HashSet, VecDeque};
use std::path::PathBuf;
#[derive(Debug)]
pub(super) struct VirtualStorageState {
pub config: VirtualStorageConfig,
pub storage_id: StorageId,
}
#[derive(Debug, Clone)]
pub(super) struct VirtualObject {
pub rel_path: PathBuf,
pub storage_id: StorageId,
pub parent: ObjectHandle,
}
#[derive(Debug, Clone)]
pub(super) struct PendingSendInfo {
pub storage_id: StorageId,
pub parent: ObjectHandle,
pub filename: String,
#[allow(dead_code)] pub size: u64,
pub is_folder: bool,
pub assigned_handle: ObjectHandle,
}
#[derive(Debug)]
pub(super) struct PendingCommand {
pub code: u16,
pub tx_id: u32,
pub params: Vec<u32>,
}
#[derive(Debug)]
pub(super) struct VirtualDeviceState {
pub config: VirtualDeviceConfig,
pub session_open: bool,
pub next_handle: u32,
pub objects: HashMap<u32, VirtualObject>,
pub storages: Vec<VirtualStorageState>,
pub pending_send: Option<PendingSendInfo>,
pub pending_command: Option<PendingCommand>,
pub event_queue: VecDeque<Vec<u8>>,
pub response_queue: VecDeque<Vec<u8>>,
pub(super) watcher_paused: bool,
}
impl VirtualDeviceState {
pub fn new(config: VirtualDeviceConfig) -> Self {
let storages: Vec<VirtualStorageState> = config
.storages
.iter()
.enumerate()
.map(|(i, sc)| {
let mut resolved_config = sc.clone();
if let Ok(canonical) = sc.backing_dir.canonicalize() {
resolved_config.backing_dir = canonical;
}
VirtualStorageState {
config: resolved_config,
storage_id: StorageId(0x0001_0001 + i as u32),
}
})
.collect();
Self {
config,
session_open: false,
next_handle: 1,
objects: HashMap::new(),
storages,
pending_send: None,
pending_command: None,
event_queue: VecDeque::new(),
response_queue: VecDeque::new(),
watcher_paused: false,
}
}
pub fn alloc_handle(&mut self) -> ObjectHandle {
let h = self.next_handle;
self.next_handle += 1;
ObjectHandle(h)
}
pub fn find_storage(&self, id: StorageId) -> Option<&VirtualStorageState> {
self.storages.iter().find(|s| s.storage_id == id)
}
pub fn scan_dir(
&mut self,
storage_id: StorageId,
parent: ObjectHandle,
) -> Result<Vec<ObjectHandle>, std::io::Error> {
let storage = self
.storages
.iter()
.find(|s| s.storage_id == storage_id)
.ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::NotFound, "storage not found")
})?;
let base_dir = &storage.config.backing_dir;
let dir_path = if parent == ObjectHandle::ROOT || parent.0 == 0 {
base_dir.clone()
} else {
match self.objects.get(&parent.0) {
Some(obj) if obj.storage_id == storage_id => base_dir.join(&obj.rel_path),
_ => return Ok(Vec::new()),
}
};
if !dir_path.is_dir() {
return Ok(Vec::new());
}
let mut handles = Vec::new();
let entries = std::fs::read_dir(&dir_path)?;
for entry in entries {
let entry = entry?;
let file_name = entry.file_name();
let rel = if parent == ObjectHandle::ROOT || parent.0 == 0 {
PathBuf::from(&file_name)
} else {
let parent_obj = self.objects.get(&parent.0).unwrap();
parent_obj.rel_path.join(&file_name)
};
let existing = self
.objects
.iter()
.find(|(_, obj)| obj.storage_id == storage_id && obj.rel_path == rel);
let handle = if let Some((&h, _)) = existing {
ObjectHandle(h)
} else {
let h = self.alloc_handle();
self.objects.insert(
h.0,
VirtualObject {
rel_path: rel,
storage_id,
parent,
},
);
h
};
handles.push(handle);
}
Ok(handles)
}
pub fn scan_all(&mut self, storage_id: StorageId) -> Result<Vec<ObjectHandle>, std::io::Error> {
let root_handles = self.scan_dir(storage_id, ObjectHandle::ROOT)?;
let mut all_handles = root_handles.clone();
let mut queue = root_handles;
while let Some(handle) = queue.pop() {
let obj = match self.objects.get(&handle.0) {
Some(o) => o.clone(),
None => continue,
};
let storage = match self.find_storage(storage_id) {
Some(s) => s,
None => continue,
};
let full_path = storage.config.backing_dir.join(&obj.rel_path);
if full_path.is_dir() {
let children = self.scan_dir(storage_id, handle)?;
all_handles.extend(&children);
queue.extend(children);
}
}
Ok(all_handles)
}
pub fn resolve_path(&self, handle: ObjectHandle) -> Option<PathBuf> {
let obj = self.objects.get(&handle.0)?;
let storage = self.find_storage(obj.storage_id)?;
Some(storage.config.backing_dir.join(&obj.rel_path))
}
pub fn is_read_only(&self, storage_id: StorageId) -> bool {
self.find_storage(storage_id)
.map(|s| s.config.read_only)
.unwrap_or(false)
}
pub fn rescan_backing_dirs(&mut self) -> RescanSummary {
let mut added = 0u32;
let mut removed = 0u32;
let mut changed_storages = HashSet::new();
let stale_handles: Vec<(u32, StorageId)> = self
.objects
.iter()
.filter_map(|(&handle, obj)| {
let storage = self
.storages
.iter()
.find(|s| s.storage_id == obj.storage_id)?;
let full_path = storage.config.backing_dir.join(&obj.rel_path);
if !full_path.exists() {
Some((handle, obj.storage_id))
} else {
None
}
})
.collect();
for (handle, storage_id) in stale_handles {
self.objects.remove(&handle);
self.event_queue
.push_back(build_event(EventCode::ObjectRemoved, &[handle]));
changed_storages.insert(storage_id);
removed += 1;
}
let storage_info: Vec<(StorageId, PathBuf)> = self
.storages
.iter()
.map(|s| (s.storage_id, s.config.backing_dir.clone()))
.collect();
for (storage_id, backing_dir) in &storage_info {
let storage_added =
self.rescan_dir(*storage_id, backing_dir, backing_dir, ObjectHandle::ROOT);
added += storage_added;
if storage_added > 0 {
changed_storages.insert(*storage_id);
}
}
for storage_id in &changed_storages {
self.event_queue
.push_back(build_event(EventCode::StorageInfoChanged, &[storage_id.0]));
}
RescanSummary { added, removed }
}
fn rescan_dir(
&mut self,
storage_id: StorageId,
backing_dir: &std::path::Path,
dir_path: &std::path::Path,
parent: ObjectHandle,
) -> u32 {
let entries = match std::fs::read_dir(dir_path) {
Ok(e) => e,
Err(_) => return 0,
};
let mut added = 0u32;
for entry in entries.flatten() {
let full_path = entry.path();
let rel_path = match full_path.strip_prefix(backing_dir) {
Ok(r) => r.to_path_buf(),
Err(_) => continue,
};
let existing = self
.objects
.iter()
.find(|(_, obj)| obj.storage_id == storage_id && obj.rel_path == rel_path)
.map(|(&h, _)| ObjectHandle(h));
let handle = if let Some(h) = existing {
h
} else {
let h = self.alloc_handle();
self.objects.insert(
h.0,
VirtualObject {
rel_path: rel_path.clone(),
storage_id,
parent,
},
);
self.event_queue
.push_back(build_event(EventCode::ObjectAdded, &[h.0]));
added += 1;
h
};
if full_path.is_dir() {
added += self.rescan_dir(storage_id, backing_dir, &full_path, handle);
}
}
added
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RescanSummary {
pub added: u32,
pub removed: u32,
}