mod device_ext;
pub use device_ext::*;
use crate::device::storage::StorageId;
use crate::device::{Device, PtpIo};
use crate::error::{Error, MtpError};
use crate::object::properties::ObjectFileName;
use crate::object::{
DateTime, ObjectFormatCode, ObjectHandle, ObjectInfo, ProtectionStatus, PtpString,
};
use futures::StreamExt;
use mtp_spec::communication::event::Event;
use mtp_spec::communication::operation::{BaseOperation, Operation};
use mtp_spec::device::session::MtpSession;
use mtp_spec::object::properties::{ObjectPropertyCode, ObjectSize};
use std::collections::HashMap;
use std::fmt::{Debug, Display};
use std::io::{ErrorKind, Write};
use std::str::FromStr;
use std::sync::{Arc, Weak};
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::{RwLock, RwLockWriteGuard};
pub struct File<D> {
fs: Weak<FileSystem<D>>,
storage_id: StorageId,
id: ObjectHandle,
parent_id: ObjectHandle,
name: String,
size: u64,
format: ObjectFormatCode,
protection_status: ProtectionStatus,
date_modified: Option<DateTime>,
date_created: Option<DateTime>,
}
impl<D> Debug for File<D> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("File")
.field("storage_id", &self.storage_id)
.field("id", &self.id)
.field("parent_id", &self.parent_id)
.field("name", &self.name)
.field("size", &self.size)
.field("format", &self.format)
.field("protection_status", &self.protection_status)
.field("date_modified", &self.date_modified)
.field("date_created", &self.date_created)
.finish_non_exhaustive()
}
}
impl<D> File<D> {
pub fn storage_id(&self) -> StorageId {
self.storage_id
}
pub fn id(&self) -> ObjectHandle {
self.id
}
pub fn parent(&self) -> ObjectHandle {
self.parent_id
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn size(&self) -> u64 {
self.size
}
pub fn format(&self) -> ObjectFormatCode {
self.format
}
pub fn protection_status(&self) -> ProtectionStatus {
self.protection_status
}
pub fn date_modified(&self) -> Option<DateTime> {
self.date_modified
}
pub fn date_created(&self) -> Option<DateTime> {
self.date_created
}
}
impl<D> File<D>
where
D: Device,
{
pub async fn open(
&self,
) -> Result<std::fs::File, crate::error::Error<<D as PtpIo>::TransportError>>
where
D: Device,
{
let object = self.fs().session.get_object(self.id).await?;
let file_data = object.data.data;
let mut tmp = tempfile::tempfile()?;
tmp.write_all(&file_data)?;
Ok(tmp)
}
pub async fn read_at(
&self,
offset: u32,
len: u32,
) -> Result<Vec<u8>, Error<<D as PtpIo>::TransportError>>
where
D: Device,
{
let fs = self.fs();
if fs.capabilities.supports_partial_read {
let response = fs.session.get_partial_object(self.id, offset, len).await?;
return Ok(response.data.data);
}
Err(MtpError::UnsupportedOperation.into())
}
pub async fn rename<N>(
&self,
name: N,
) -> Result<Arc<Self>, MtpError<<D as PtpIo>::TransportError>>
where
N: AsRef<str>,
{
let name_str = name.as_ref();
if name_str == self.name {
return Ok(Arc::new(Self {
fs: self.fs.clone(),
storage_id: self.storage_id,
id: self.id,
parent_id: self.parent_id,
name: self.name.clone(),
size: self.size,
format: self.format,
protection_status: self.protection_status,
date_modified: self.date_modified,
date_created: self.date_created,
}));
}
let fs = self.fs();
if !fs
.session
.object_property_can_be_modified::<ObjectFileName>(self.format)
.await?
{
return Err(MtpError::UnsupportedOperation);
}
let name_ptp = PtpString::from_str(name_str)
.map_err(Into::<MtpError<<D as PtpIo>::TransportError>>::into)?;
let _ = fs
.session
.set_object_prop_value::<ObjectFileName>(self.id, name_ptp)
.await?;
Ok(Arc::new(Self {
fs: self.fs.clone(),
storage_id: self.storage_id,
id: self.id,
parent_id: self.parent_id,
name: name_str.to_string(),
size: self.size,
format: self.format,
protection_status: self.protection_status,
date_modified: self.date_modified,
date_created: self.date_created,
}))
}
pub async fn copy(
&self,
parent: Option<ObjectHandle>,
) -> Result<ObjectHandle, MtpError<<D as PtpIo>::TransportError>> {
let new_handle = self
.fs()
.session
.copy_object(self.id, self.storage_id, parent)
.await?
.data
.data;
Ok(new_handle)
}
pub async fn move_(
&self,
new_parent: &Folder<D>,
) -> Result<Arc<Self>, MtpError<<D as PtpIo>::TransportError>> {
let fs = self.fs();
fs.session
.move_object(self.id, self.storage_id, Some(new_parent.id))
.await?;
let updated_file = Arc::new(Self {
fs: self.fs.clone(),
storage_id: self.storage_id,
id: self.id,
parent_id: new_parent.id,
name: self.name.clone(),
size: self.size,
format: self.format,
protection_status: self.protection_status,
date_modified: self.date_modified,
date_created: self.date_created,
});
let entry = FolderEntry::File(updated_file.clone());
let inode_lock = fs.inode_map.read().await;
if let Some(FolderEntry::Folder(old_parent)) = inode_lock.get(&self.parent_id) {
old_parent.children.write().await.remove(&self.name);
}
if let Some(FolderEntry::Folder(new_parent_obj)) = inode_lock.get(&new_parent.id) {
new_parent_obj
.children
.write()
.await
.insert(self.name.clone(), entry.clone());
}
drop(inode_lock);
fs.inode_map.write().await.insert(self.id, entry);
Ok(updated_file)
}
pub fn object_info(&self) -> Result<ObjectInfo, <PtpString as FromStr>::Err> {
let filename = PtpString::from_str(&self.name)?;
Ok(ObjectInfo {
storage_id: self.storage_id,
object_format: self.format,
protection_status: self.protection_status,
compressed_size: self.size as u32,
thumbnail: None,
image_details: None,
parent_object: Some(self.parent_id),
association: None,
sequence_number: 0,
filename,
date_created: self.date_created,
date_modified: self.date_modified,
keywords: PtpString::default(),
})
}
fn fs(&self) -> Arc<FileSystem<D>> {
self.fs.upgrade().expect("FS dropped")
}
}
pub struct Folder<D> {
fs: Weak<FileSystem<D>>,
storage_id: StorageId,
id: ObjectHandle,
parent_id: ObjectHandle,
name: String,
format: ObjectFormatCode,
protection_status: ProtectionStatus,
date_created: Option<DateTime>,
date_modified: Option<DateTime>,
children: RwLock<HashMap<String, FolderEntry<D>>>,
}
impl<D> Debug for Folder<D> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Folder")
.field("storage_id", &self.storage_id)
.field("id", &self.id)
.field("parent_id", &self.parent_id)
.field("name", &self.name)
.field("format", &self.format)
.field("protection_status", &self.protection_status)
.field("date_created", &self.date_created)
.field("date_modified", &self.date_modified)
.finish_non_exhaustive()
}
}
impl<D> Folder<D> {
pub fn storage_id(&self) -> StorageId {
self.storage_id
}
pub fn id(&self) -> ObjectHandle {
self.id
}
pub fn parent(&self) -> ObjectHandle {
self.parent_id
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn format(&self) -> ObjectFormatCode {
self.format
}
pub fn protection_status(&self) -> ProtectionStatus {
self.protection_status
}
pub fn date_modified(&self) -> Option<DateTime> {
self.date_modified
}
pub fn date_created(&self) -> Option<DateTime> {
self.date_created
}
pub async fn with_children<F, R>(&self, f: F) -> R
where
F: FnOnce(&HashMap<String, FolderEntry<D>>) -> R,
{
let children = self.children.read().await;
f(&children)
}
}
impl<D> Folder<D>
where
D: Device,
{
pub async fn rename<N>(
&self,
name: N,
) -> Result<Arc<Self>, MtpError<<D as PtpIo>::TransportError>>
where
N: AsRef<str>,
{
let name_str = name.as_ref();
let name_ptp = PtpString::from_str(name_str)?;
let fs = self.fs();
fs.session
.set_object_prop_value::<ObjectFileName>(self.id, name_ptp)
.await?;
let updated_folder = Arc::new(Self {
fs: self.fs.clone(),
storage_id: self.storage_id,
id: self.id,
parent_id: self.parent_id,
name: name_str.to_string(),
format: self.format,
protection_status: self.protection_status,
date_created: self.date_created,
date_modified: self.date_modified,
children: RwLock::new(self.children.read().await.clone()),
});
let entry = FolderEntry::Folder(updated_folder.clone());
fs.inode_map.write().await.insert(self.id, entry.clone());
if let Some(FolderEntry::Folder(parent)) = fs.inode_map.read().await.get(&self.parent_id) {
let mut children = parent.children.write().await;
children.remove(&self.name);
children.insert(name_str.to_string(), entry);
}
Ok(updated_folder)
}
pub async fn copy(
&self,
parent: Option<ObjectHandle>,
) -> Result<ObjectHandle, MtpError<<D as PtpIo>::TransportError>>
where
D: Device,
{
let new_handle = self
.fs()
.session
.copy_object(self.id, self.storage_id, parent)
.await?
.data
.data;
Ok(new_handle)
}
pub async fn move_(
&self,
new_parent: &Folder<D>,
) -> Result<Arc<Self>, MtpError<<D as PtpIo>::TransportError>> {
let fs = self.fs();
fs.session
.move_object(self.id, self.storage_id, Some(new_parent.id))
.await?;
let updated_folder = Arc::new(Self {
fs: self.fs.clone(),
storage_id: self.storage_id,
id: self.id,
parent_id: new_parent.id,
name: self.name.clone(),
format: self.format,
protection_status: self.protection_status,
date_created: self.date_created,
date_modified: self.date_modified,
children: RwLock::new(self.children.read().await.clone()),
});
let entry = FolderEntry::Folder(updated_folder.clone());
let inode_lock = fs.inode_map.read().await;
if let Some(FolderEntry::Folder(old_parent)) = inode_lock.get(&self.parent_id) {
old_parent.children.write().await.remove(&self.name);
}
if let Some(FolderEntry::Folder(new_parent)) = inode_lock.get(&new_parent.id) {
new_parent
.children
.write()
.await
.insert(self.name.clone(), entry.clone());
}
drop(inode_lock);
fs.inode_map.write().await.insert(self.id, entry);
Ok(updated_folder)
}
pub async fn create_file<N>(
&self,
name: N,
format: ObjectFormatCode,
data: Vec<u8>,
) -> Result<Arc<File<D>>, MtpError<<D as PtpIo>::TransportError>>
where
N: AsRef<str> + Send,
{
let file = self
.fs()
.session
.create(Some(self), name.as_ref(), format, data)
.await?;
let file = Arc::new(file);
self.children
.write()
.await
.insert(name.as_ref().to_string(), FolderEntry::File(file.clone()));
Ok(file)
}
pub async fn remove_child(&self, name: &str) -> Result<(), FileSystemError<D>> {
let mut children = self.children.write().await;
let Some(entry) = children.remove(name) else {
return Err(FileSystemError::Io(std::io::Error::from(
ErrorKind::NotFound,
)));
};
match self.fs().session.delete_object(entry.handle(), None).await {
Ok(_) => Ok(()),
Err(e) => {
children.insert(name.to_string(), entry);
Err(FileSystemError::Mtp(e))
},
}
}
pub async fn find(&self, name: &str) -> Option<FolderEntry<D>> {
self.children.read().await.get(name).cloned()
}
fn fs(&self) -> Arc<FileSystem<D>> {
self.fs.upgrade().expect("FS dropped")
}
}
pub enum FolderEntry<D> {
File(Arc<File<D>>),
Folder(Arc<Folder<D>>),
}
impl<D> Debug for FolderEntry<D> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FolderEntry::File(file) => f.debug_tuple("File").field(&file).finish(),
FolderEntry::Folder(folder) => f.debug_tuple("Folder").field(&folder).finish(),
}
}
}
impl<D> Clone for FolderEntry<D> {
fn clone(&self) -> Self {
match self {
FolderEntry::File(file) => FolderEntry::File(Arc::clone(file)),
FolderEntry::Folder(folder) => FolderEntry::Folder(Arc::clone(folder)),
}
}
}
impl<D> FolderEntry<D>
where
D: Device,
{
fn new(
fs: Weak<FileSystem<D>>,
handle: ObjectHandle,
storage_id: StorageId,
info: &ObjectInfo,
) -> Self {
let name = info.filename.to_string();
let parent_id = info.parent_object.unwrap_or(ObjectHandle::NONE);
if info.object_format == ObjectFormatCode::Association {
FolderEntry::Folder(Arc::new(Folder {
fs,
id: handle,
storage_id,
parent_id,
name,
format: info.object_format,
protection_status: info.protection_status,
date_created: info.date_created,
date_modified: info.date_modified,
children: RwLock::new(HashMap::new()),
}))
} else {
FolderEntry::File(Arc::new(File {
fs,
id: handle,
storage_id,
parent_id,
name,
size: u64::from(info.compressed_size),
format: info.object_format,
protection_status: info.protection_status,
date_created: info.date_created,
date_modified: info.date_modified,
}))
}
}
fn fs(&self) -> Arc<FileSystem<D>> {
match self {
FolderEntry::File(f) => f.fs.upgrade().expect("FS dropped"),
FolderEntry::Folder(f) => f.fs.upgrade().expect("FS dropped"),
}
}
pub async fn path(&self) -> String {
let fs = self.fs();
let map = fs.inode_map.read().await;
self.path_inner(&map)
}
fn path_inner(&self, map: &INodeMap<D>) -> String {
let mut components = Vec::new();
let mut cur = Some(self.clone());
while let Some(entry) = cur {
components.push(entry.name().to_string());
let parent = entry.parent();
if parent == ObjectHandle::NONE {
break;
}
cur = map.get(&parent).cloned();
}
components.reverse();
components.join("/")
}
pub fn name(&self) -> &str {
match self {
FolderEntry::File(f) => &f.name,
FolderEntry::Folder(f) => &f.name,
}
}
fn set_name(&mut self, name: String) {
match self {
FolderEntry::File(f) => Arc::get_mut(f).unwrap().name = name,
FolderEntry::Folder(f) => Arc::get_mut(f).unwrap().name = name,
}
}
pub fn parent(&self) -> ObjectHandle {
match self {
FolderEntry::File(f) => f.parent_id,
FolderEntry::Folder(f) => f.parent_id,
}
}
pub fn handle(&self) -> ObjectHandle {
match self {
FolderEntry::File(f) => f.id,
FolderEntry::Folder(f) => f.id,
}
}
pub fn storage_id(&self) -> StorageId {
match self {
FolderEntry::File(f) => f.storage_id,
FolderEntry::Folder(f) => f.storage_id,
}
}
pub fn format(&self) -> ObjectFormatCode {
match self {
FolderEntry::File(f) => f.format,
FolderEntry::Folder(f) => f.format,
}
}
fn set_format(&mut self, format: ObjectFormatCode) {
match self {
FolderEntry::File(f) => Arc::get_mut(f).unwrap().format = format,
FolderEntry::Folder(f) => Arc::get_mut(f).unwrap().format = format,
}
}
pub fn protection_status(&self) -> ProtectionStatus {
match self {
FolderEntry::File(f) => f.protection_status,
FolderEntry::Folder(f) => f.protection_status,
}
}
fn set_protection_status(&mut self, protection_status: ProtectionStatus) {
match self {
FolderEntry::File(f) => Arc::get_mut(f).unwrap().protection_status = protection_status,
FolderEntry::Folder(f) => {
Arc::get_mut(f).unwrap().protection_status = protection_status
},
}
}
fn set_size(&mut self, size: u64) {
if let FolderEntry::File(f) = self {
Arc::get_mut(f).unwrap().size = size;
}
}
fn set_date_created(&mut self, date: Option<DateTime>) {
match self {
FolderEntry::File(f) => Arc::get_mut(f).unwrap().date_created = date,
FolderEntry::Folder(f) => Arc::get_mut(f).unwrap().date_created = date,
}
}
fn set_date_modified(&mut self, date: Option<DateTime>) {
match self {
FolderEntry::File(f) => Arc::get_mut(f).unwrap().date_modified = date,
FolderEntry::Folder(f) => Arc::get_mut(f).unwrap().date_modified = date,
}
}
pub async fn rename<N>(&self, name: N) -> Result<Self, MtpError<<D as PtpIo>::TransportError>>
where
N: AsRef<str>,
{
match self {
FolderEntry::File(f) => Ok(FolderEntry::File(f.rename(name).await?)),
FolderEntry::Folder(f) => Ok(FolderEntry::Folder(f.rename(name).await?)),
}
}
pub async fn copy(
&self,
parent: Option<ObjectHandle>,
) -> Result<ObjectHandle, MtpError<<D as PtpIo>::TransportError>> {
match self {
FolderEntry::File(f) => f.copy(parent).await,
FolderEntry::Folder(f) => f.copy(parent).await,
}
}
pub async fn move_(
&self,
new_parent: &Folder<D>,
) -> Result<FolderEntry<D>, MtpError<<D as PtpIo>::TransportError>> {
match self {
FolderEntry::File(f) => Ok(FolderEntry::File(f.move_(new_parent).await?)),
FolderEntry::Folder(f) => Ok(FolderEntry::Folder(f.move_(new_parent).await?)),
}
}
}
#[derive(Clone, Debug)]
struct DeviceCapabilities {
pub supports_partial_read: bool,
}
impl DeviceCapabilities {
async fn query<D: Device>(
session: &MtpSession<D>,
) -> Result<Self, MtpError<<D as PtpIo>::TransportError>> {
let info = session.get_device_info().await?;
let ops = info.data.data.operations_supported.as_slice();
Ok(Self {
supports_partial_read: ops.contains(&Operation::Base(BaseOperation::GetPartialObject)),
})
}
}
#[derive(Clone, Debug)]
pub enum FsEvent<D> {
Added(FolderEntry<D>),
Removed(FolderEntry<D>),
Refreshed(FolderEntry<D>),
}
type INodeMap<D> = HashMap<ObjectHandle, FolderEntry<D>>;
type INodeMapGuard<'a, D> = RwLockWriteGuard<'a, INodeMap<D>>;
pub struct FileSystem<D> {
capabilities: DeviceCapabilities,
storage_id: StorageId,
session: MtpSession<D>,
root: RwLock<Option<Arc<Folder<D>>>>,
inode_map: RwLock<INodeMap<D>>,
}
pub enum FileSystemError<D>
where
D: Device,
{
Mtp(MtpError<<D as PtpIo>::TransportError>),
Io(std::io::Error),
}
impl<D> From<std::io::Error> for FileSystemError<D>
where
D: Device,
{
fn from(err: std::io::Error) -> Self {
FileSystemError::Io(err)
}
}
impl<D> Debug for FileSystemError<D>
where
D: Device,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FileSystemError::Mtp(err) => Debug::fmt(err, f),
FileSystemError::Io(err) => Debug::fmt(err, f),
}
}
}
impl<D> Display for FileSystemError<D>
where
D: Device,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FileSystemError::Mtp(err) => Display::fmt(err, f),
FileSystemError::Io(err) => Display::fmt(err, f),
}
}
}
impl<D> std::error::Error for FileSystemError<D> where D: Device {}
impl<D> FileSystem<D>
where
D: Device,
{
pub async fn create(
&self,
path: impl AsRef<str>,
format: ObjectFormatCode,
data: Vec<u8>,
) -> Result<Arc<File<D>>, FileSystemError<D>> {
let path = path.as_ref();
let (parent_path, file_name) = path
.rsplit_once('/')
.ok_or(std::io::Error::from(ErrorKind::InvalidInput))?;
let lookup_path = if parent_path.is_empty() {
"/"
} else {
parent_path
};
let parent_entry = self
.find(lookup_path)
.await
.ok_or(std::io::Error::from(ErrorKind::NotFound))?;
let FolderEntry::Folder(parent) = parent_entry else {
return Err(std::io::Error::from(ErrorKind::NotADirectory).into());
};
parent
.create_file(file_name, format, data)
.await
.map_err(FileSystemError::Mtp)
}
pub async fn find(&self, path: impl AsRef<str>) -> Option<FolderEntry<D>> {
let path = path.as_ref();
if !path.starts_with('/') {
return None;
}
let root = self.root.read().await.clone()?;
let mut current = FolderEntry::Folder(root);
for component in path.split('/').filter(|c| !c.is_empty()) {
if let FolderEntry::Folder(folder) = current {
current = folder.find(component).await?;
} else {
return None;
}
}
Some(current)
}
pub async fn remove(&self, handle: ObjectHandle) -> bool {
self.remove_entry(handle).await.is_some()
}
pub async fn add(
self: &Arc<Self>,
handle: ObjectHandle,
) -> Result<Option<FolderEntry<D>>, MtpError<<D as PtpIo>::TransportError>> {
Self::object_added(self.clone(), &self.session, handle).await
}
#[allow(clippy::missing_panics_doc)]
pub async fn with_root<F, Fut>(&self, f: F) -> Fut::Output
where
F: FnOnce(Arc<Folder<D>>) -> Fut,
Fut: Future + Send + 'static,
Fut::Output: Send + 'static,
{
let guard = self.root.read().await;
f((*guard).clone().expect("root should exist")).await
}
pub async fn load(
session: MtpSession<D>,
storage_id: StorageId,
) -> Result<
(Arc<Self>, tokio::sync::mpsc::UnboundedReceiver<FsEvent<D>>),
MtpError<<D as PtpIo>::TransportError>,
>
where
D: Device + 'static,
{
Self::load_with_callback(session, storage_id, |_| {}).await
}
pub async fn load_with_callback<F>(
session: MtpSession<D>,
storage_id: StorageId,
callback: F,
) -> Result<
(Arc<Self>, tokio::sync::mpsc::UnboundedReceiver<FsEvent<D>>),
MtpError<<D as PtpIo>::TransportError>,
>
where
D: Device + 'static,
F: FnMut(ObjectHandle),
{
let capabilities = DeviceCapabilities::query(&session).await?;
let fs = Arc::new(Self {
session: session.clone(),
capabilities,
storage_id,
root: RwLock::new(None),
inode_map: RwLock::new(HashMap::new()),
});
let fs_weak = Arc::downgrade(&fs);
let handles_res = session.get_object_handles(storage_id, None, None).await?;
let handles = handles_res.data.data.as_slice();
let mut flat_map: HashMap<ObjectHandle, FolderEntry<D>> =
HashMap::with_capacity(handles.len());
Self::load_iterative(
&session,
fs_weak.clone(),
handles,
storage_id,
&mut flat_map,
callback,
)
.await?;
let root = Arc::new(Folder {
fs: fs_weak,
storage_id,
id: ObjectHandle::NONE,
parent_id: ObjectHandle::NONE,
name: "/".to_string(),
format: ObjectFormatCode::Association,
protection_status: ProtectionStatus::ReadOnly,
date_created: None,
date_modified: None,
children: RwLock::new(HashMap::new()),
});
flat_map.insert(ObjectHandle::NONE, FolderEntry::Folder(root.clone()));
let entries: Vec<FolderEntry<D>> = flat_map.values().cloned().collect();
for entry in entries {
if entry.handle() == ObjectHandle::NONE {
continue;
}
if let Some(FolderEntry::Folder(parent)) = flat_map.get(&entry.parent()) {
parent
.children
.write()
.await
.insert(entry.name().to_string(), entry);
} else {
root.children
.write()
.await
.insert(entry.name().to_string(), entry);
}
}
*fs.inode_map.write().await = flat_map;
*fs.root.write().await = Some(root);
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let session = session.clone();
let fs_weak = Arc::downgrade(&fs);
tokio::task::spawn(async move {
Self::event_loop(session, fs_weak, tx).await;
});
Ok((fs, rx))
}
async fn load_iterative<F>(
session: &MtpSession<D>,
fs: Weak<FileSystem<D>>,
handles: &[ObjectHandle],
storage_id: StorageId,
flat_map: &mut HashMap<ObjectHandle, FolderEntry<D>>,
mut callback: F,
) -> Result<(), MtpError<<D as PtpIo>::TransportError>>
where
D: Device,
F: FnMut(ObjectHandle),
{
for &handle in handles {
callback(handle);
let info = session.get_object_info(handle).await?.data.data;
let entry = FolderEntry::new(fs.clone(), handle, storage_id, &info);
flat_map.insert(handle, entry);
}
Ok(())
}
}
impl<D> FileSystem<D>
where
D: Device,
{
async fn event_loop(
session: MtpSession<D>,
fs_weak: Weak<Self>,
tx: UnboundedSender<FsEvent<D>>,
) {
let mut events = session.event_stream();
while let Some(event_res) = events.next().await {
let Some(fs) = fs_weak.upgrade() else { break };
match event_res {
Ok(Event::ObjectAdded { object_handle }) => {
match Self::object_added(fs, &session, object_handle).await {
Ok(Some(entry)) => {
let _ = tx.send(FsEvent::Added(entry));
},
Ok(None) => {},
Err(e) => tracing::error!("Failed to add new object: {e}"),
}
},
Ok(Event::ObjectRemoved { object_handle }) => {
let Some(entry) = fs.remove_entry(object_handle).await else {
continue;
};
let _ = tx.send(FsEvent::Removed(entry));
},
Ok(Event::ObjectPropChanged { object, prop_code }) => {
Self::object_prop_changed(&session, fs, &tx, object, prop_code).await
},
Ok(Event::ObjectInfoChanged { object: _ }) => {
tracing::warn!("TODO: Handle object info change event")
},
Ok(_) => {},
Err(e) => tracing::error!("MTP Event stream error: {e}"),
}
}
tracing::info!("MTP FileSystem event loop exited.");
}
async fn object_prop_changed(
session: &MtpSession<D>,
fs: Arc<Self>,
tx: &UnboundedSender<FsEvent<D>>,
handle: ObjectHandle,
prop: ObjectPropertyCode,
) {
{
let map = fs.inode_map.read().await;
if !map.contains_key(&handle) {
return;
}
}
let info = match session.get_object_info(handle).await {
Ok(info_res) => info_res.data.data,
Err(e) => {
tracing::error!("Failed to fetch info on prop change: {e}");
return;
},
};
let mut map = fs.inode_map.write().await;
if info.storage_id != fs.storage_id || prop == ObjectPropertyCode::StorageId {
drop(map);
if let Some(entry) = fs.remove_entry(handle).await {
let _ = tx.send(FsEvent::Removed(entry));
}
return;
}
let Some(entry) = map.get_mut(&handle) else {
return;
};
let entry_clone = entry.clone();
match prop {
ObjectPropertyCode::ObjectFileName => {
let _ = entry;
fs.rename_entry(&mut map, handle, info.filename.to_string())
.await;
},
ObjectPropertyCode::ObjectFormat => entry.set_format(info.object_format),
ObjectPropertyCode::ProtectionStatus => {
entry.set_protection_status(info.protection_status)
},
ObjectPropertyCode::DateCreated => entry.set_date_created(info.date_created),
ObjectPropertyCode::DateModified => entry.set_date_modified(info.date_modified),
ObjectPropertyCode::ObjectSize => {
match session.get_object_prop_value::<ObjectSize>(handle).await {
Ok(size) => entry.set_size(size.data.data),
Err(e) => {
tracing::warn!("Failed to get object size: {e}");
return;
},
}
},
_ => {},
}
tracing::debug!("Object property changed: {handle:?}, prop_code: {prop:?}");
let _ = tx.send(FsEvent::Refreshed(entry_clone));
}
async fn object_added(
fs: Arc<Self>,
session: &MtpSession<D>,
handle: ObjectHandle,
) -> Result<Option<FolderEntry<D>>, MtpError<<D as PtpIo>::TransportError>> {
let info = session.get_object_info(handle).await?.data.data;
if info.storage_id != fs.storage_id {
return Ok(None);
}
let entry = FolderEntry::new(Arc::downgrade(&fs), handle, fs.storage_id, &info);
let mut map = fs.inode_map.write().await;
tracing::debug!(
"Adding entry `{}` (handle: {:#X})",
entry.path_inner(&map),
Into::<u32>::into(entry.handle())
);
let parent_id = entry.parent();
if let Some(FolderEntry::Folder(parent)) = map.get(&parent_id) {
parent
.children
.write()
.await
.insert(entry.name().to_string(), entry.clone());
map.insert(entry.handle(), entry.clone());
return Ok(Some(entry));
} else if parent_id == ObjectHandle::NONE
&& let Some(root) = fs.root.read().await.as_ref()
{
root.children
.write()
.await
.insert(entry.name().to_string(), entry.clone());
map.insert(entry.handle(), entry.clone());
return Ok(Some(entry));
}
Ok(None)
}
async fn remove_entry(&self, handle: ObjectHandle) -> Option<FolderEntry<D>> {
let mut map = self.inode_map.write().await;
let entry = map.remove(&handle)?;
tracing::debug!(
"Removing entry `{}` (handle: {:#X})",
entry.path_inner(&map),
Into::<u32>::into(entry.handle())
);
let parent = entry.parent();
if let Some(FolderEntry::Folder(parent)) = map.get(&parent) {
parent.children.write().await.remove(entry.name());
} else if parent == ObjectHandle::NONE
&& let Some(root) = self.root.read().await.as_ref()
{
root.children.write().await.remove(entry.name());
}
Some(entry)
}
async fn rename_entry(
&self,
map: &mut INodeMapGuard<'_, D>,
handle: ObjectHandle,
new_name: String,
) -> Option<()> {
let entry = map.get(&handle)?;
if entry.name() == new_name {
return Some(());
}
let original_name = entry.name().to_string();
let parent = entry.parent();
if let Some(entry) = map.get_mut(&handle) {
entry.set_name(new_name.clone());
}
let parent_opt = if parent == ObjectHandle::NONE {
self.root.read().await.clone().map(FolderEntry::Folder)
} else {
map.get(&parent).cloned()
};
if let Some(FolderEntry::Folder(parent)) = parent_opt {
let mut children = parent.children.write().await;
if let Some(child_entry) = children.remove(&original_name) {
children.insert(new_name, child_entry);
}
}
Some(())
}
}