use super::config::{VirtualDeviceConfig, VirtualStorageConfig};
use crate::ptp::{ObjectHandle, StorageId};
use std::collections::{HashMap, 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>>,
}
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(),
}
}
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)
}
}