use crate::cancel::{bail_if_cancelled, CancelToken};
use crate::mtp::backend::{
BackendListing, BackendListingError, ByteRange, ListingErrorDisposition, MtpBackend, ProgressFn,
};
use crate::mtp::object::NewObjectInfo;
use crate::mtp::stream::{FileDownload, Progress, WindowedDownload, DEFAULT_DOWNLOAD_WINDOW};
use crate::mtp::{Error, ObjectHandle, ObjectInfo, StorageId, StorageInfo, UploadError};
use bytes::Bytes;
use futures::{Stream, StreamExt};
use std::ops::ControlFlow;
use std::sync::Arc;
pub struct ObjectListing {
inner: BackendListing,
fetched: usize,
}
impl ObjectListing {
fn new(inner: BackendListing) -> Self {
Self { inner, fetched: 0 }
}
#[must_use]
pub fn total(&self) -> usize {
self.inner.total
}
#[must_use]
pub fn fetched(&self) -> usize {
self.fetched
}
async fn next_classified(&mut self) -> Option<Result<ObjectInfo, BackendListingError>> {
match self.inner.items.next().await {
Some(Ok(info)) => {
self.fetched += 1;
Some(Ok(info))
}
other => other,
}
}
pub async fn next(&mut self) -> Option<Result<ListingItem, Error>> {
match self.next_classified().await? {
Ok(info) => Some(Ok(ListingItem::Object(info))),
Err(error) if error.disposition == ListingErrorDisposition::SkipObject => {
Some(Ok(ListingItem::Skipped(SkippedObject {
handle: error.handle,
error: error.source,
})))
}
Err(error) => Some(Err(error.source)),
}
}
}
#[derive(Debug)]
pub enum ListingItem {
Object(ObjectInfo),
Skipped(SkippedObject),
}
impl ListingItem {
#[must_use]
pub fn object(self) -> Option<ObjectInfo> {
match self {
ListingItem::Object(info) => Some(info),
ListingItem::Skipped(_) => None,
}
}
}
#[derive(Debug)]
pub struct SkippedObject {
pub handle: ObjectHandle,
pub error: Error,
}
#[derive(Debug)]
pub struct ObjectCollection {
pub objects: Vec<ObjectInfo>,
pub skipped: Vec<SkippedObject>,
}
pub struct Storage {
backend: Arc<dyn MtpBackend>,
id: StorageId,
info: StorageInfo,
}
impl Storage {
pub(crate) fn new(backend: Arc<dyn MtpBackend>, id: StorageId, info: StorageInfo) -> Self {
Self { backend, id, info }
}
#[must_use]
pub fn id(&self) -> StorageId {
self.id
}
#[must_use]
pub fn info(&self) -> &StorageInfo {
&self.info
}
pub async fn refresh(&mut self) -> Result<(), Error> {
self.info = self.backend.storage_info(self.id).await?;
Ok(())
}
pub async fn list_objects(
&self,
parent: Option<ObjectHandle>,
) -> Result<Vec<ObjectInfo>, Error> {
self.list_objects_with_cancel(parent, None).await
}
pub async fn list_objects_with_cancel(
&self,
parent: Option<ObjectHandle>,
cancel: Option<&CancelToken>,
) -> Result<Vec<ObjectInfo>, Error> {
Ok(self
.collect_objects_with_cancel(parent, cancel)
.await?
.objects)
}
pub async fn collect_objects(
&self,
parent: Option<ObjectHandle>,
) -> Result<ObjectCollection, Error> {
self.collect_objects_with_cancel(parent, None).await
}
pub async fn collect_objects_with_cancel(
&self,
parent: Option<ObjectHandle>,
cancel: Option<&CancelToken>,
) -> Result<ObjectCollection, Error> {
let mut listing = self.list_objects_stream_with_cancel(parent, cancel).await?;
let mut objects = Vec::with_capacity(listing.total());
let mut skipped = Vec::new();
while let Some(result) = listing.next_classified().await {
match result {
Ok(object) => objects.push(object),
Err(error) if error.disposition == ListingErrorDisposition::SkipObject => {
diag_debug!(
"list_objects: skipping handle {} on storage {} after a completed per-object metadata error: {}",
error.handle.0,
self.id.0,
error.source
);
skipped.push(SkippedObject {
handle: error.handle,
error: error.source,
});
}
Err(error) => return Err(error.source),
}
}
if objects.is_empty() && !skipped.is_empty() {
let first = skipped.swap_remove(0);
diag_debug!(
"list_objects: every one of {} handles on storage {} failed its metadata lookup; \
reporting the failure rather than an empty folder",
skipped.len() + 1,
self.id.0
);
return Err(first.error);
}
Ok(ObjectCollection { objects, skipped })
}
pub async fn list_objects_stream(
&self,
parent: Option<ObjectHandle>,
) -> Result<ObjectListing, Error> {
self.list_objects_stream_with_cancel(parent, None).await
}
pub async fn list_objects_stream_with_cancel(
&self,
parent: Option<ObjectHandle>,
cancel: Option<&CancelToken>,
) -> Result<ObjectListing, Error> {
let listing = self.backend.list(self.id, parent, cancel).await?;
Ok(ObjectListing::new(listing))
}
pub async fn list_objects_recursive(
&self,
parent: Option<ObjectHandle>,
) -> Result<Vec<ObjectInfo>, Error> {
Ok(self.collect_objects_recursive(parent).await?.objects)
}
pub async fn collect_objects_recursive(
&self,
parent: Option<ObjectHandle>,
) -> Result<ObjectCollection, Error> {
let mut objects = Vec::new();
let mut skipped = Vec::new();
let mut folders_to_visit = vec![parent];
while let Some(current_parent) = folders_to_visit.pop() {
let collection = self.collect_objects(current_parent).await?;
skipped.extend(collection.skipped);
for obj in collection.objects {
if obj.is_folder() {
folders_to_visit.push(Some(obj.handle));
}
objects.push(obj);
}
}
Ok(ObjectCollection { objects, skipped })
}
pub async fn get_object_info(&self, handle: ObjectHandle) -> Result<ObjectInfo, Error> {
self.backend.object_info(handle).await
}
pub async fn download_to_vec(&self, handle: ObjectHandle) -> Result<Vec<u8>, Error> {
self.backend.read_range(handle, 0, None).await
}
pub async fn read_range(
&self,
handle: ObjectHandle,
offset: u64,
len: u32,
) -> Result<Vec<u8>, Error> {
self.backend.read_range(handle, offset, Some(len)).await
}
pub async fn thumbnail(&self, handle: ObjectHandle) -> Result<Vec<u8>, Error> {
self.backend.thumbnail(handle).await
}
pub async fn download(
&self,
handle: ObjectHandle,
range: ByteRange,
) -> Result<FileDownload, Error> {
let dl = self.backend.download(handle, range).await?;
Ok(FileDownload::new(dl.size, dl.body))
}
pub async fn download_windowed(
&self,
handle: ObjectHandle,
range: ByteRange,
window_size: u32,
) -> Result<WindowedDownload, Error> {
let size = self.backend.object_info(handle).await?.size;
let offset = range.offset();
if offset > size {
return Err(Error::invalid_data(format!(
"windowed download offset {offset} is past the object size {size}"
)));
}
Ok(WindowedDownload::new(
Arc::clone(&self.backend),
handle,
size,
offset,
window_size,
))
}
pub async fn download_windowed_default(
&self,
handle: ObjectHandle,
) -> Result<WindowedDownload, Error> {
self.download_windowed(handle, ByteRange::Full, DEFAULT_DOWNLOAD_WINDOW)
.await
}
pub async fn upload<'a, S>(
&'a self,
parent: Option<ObjectHandle>,
info: NewObjectInfo,
data: S,
) -> Result<ObjectHandle, UploadError>
where
S: Stream<Item = Result<Bytes, std::io::Error>> + Unpin + Send + 'a,
{
self.backend
.upload(self.id, parent, info, Box::pin(data), None)
.await
}
pub async fn upload_with_progress<'a, S, F>(
&'a self,
parent: Option<ObjectHandle>,
info: NewObjectInfo,
data: S,
on_progress: F,
) -> Result<ObjectHandle, UploadError>
where
S: Stream<Item = Result<Bytes, std::io::Error>> + Unpin + Send + 'a,
F: FnMut(Progress) -> ControlFlow<()> + Send + 'a,
{
let progress: ProgressFn<'a> = Box::new(on_progress);
self.backend
.upload(self.id, parent, info, Box::pin(data), Some(progress))
.await
}
pub async fn create_folder(
&self,
parent: Option<ObjectHandle>,
name: &str,
) -> Result<ObjectHandle, Error> {
self.backend.create_folder(self.id, parent, name).await
}
pub async fn delete(&self, handle: ObjectHandle) -> Result<(), Error> {
self.backend.delete(handle, None).await
}
pub async fn delete_with_cancel(
&self,
handle: ObjectHandle,
cancel: Option<&CancelToken>,
) -> Result<(), Error> {
bail_if_cancelled(cancel)?;
self.backend.delete(handle, cancel).await
}
pub async fn move_object(
&self,
handle: ObjectHandle,
new_parent: ObjectHandle,
new_storage: Option<StorageId>,
) -> Result<(), Error> {
let storage = new_storage.unwrap_or(self.id);
self.backend.move_object(handle, new_parent, storage).await
}
pub async fn copy_object(
&self,
handle: ObjectHandle,
new_parent: ObjectHandle,
new_storage: Option<StorageId>,
) -> Result<ObjectHandle, Error> {
let storage = new_storage.unwrap_or(self.id);
self.backend.copy_object(handle, new_parent, storage).await
}
pub async fn rename(&self, handle: ObjectHandle, new_name: &str) -> Result<(), Error> {
self.backend.rename(handle, new_name).await
}
}