aster_forge_cloud_files_macos_bridge/
item.rs

1//! Owned File Provider item data independent from Objective-C/Swift object lifetimes.
2
3use aster_forge_cloud_files_core::{CloudItem, CloudItemKey, CloudItemKind};
4
5use crate::{MacosBridgeError, MacosFileProviderIdentifier, MacosFileProviderItemVersion, Result};
6
7/// Portable item kind mapped by Swift to `NSFileProviderItemCapabilities` and content type.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum MacosFileProviderItemKind {
10    /// Regular file.
11    File,
12    /// Directory.
13    Directory,
14}
15
16/// Fully owned File Provider item snapshot returned across the bridge.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct MacosFileProviderItem {
19    identifier: MacosFileProviderIdentifier,
20    parent_identifier: MacosFileProviderIdentifier,
21    filename: String,
22    kind: MacosFileProviderItemKind,
23    size: u64,
24    version: MacosFileProviderItemVersion,
25}
26
27impl MacosFileProviderItem {
28    /// Converts a core item into an owned File Provider snapshot scoped to one exact root.
29    ///
30    /// `root_key` determines both the root item role and whether a direct child's parent must use
31    /// Apple's root system identifier instead of an ordinary encoded item identifier.
32    /// # Errors
33    ///
34    /// Returns an error when validation fails or an underlying backend, store, or platform
35    /// operation fails.
36    pub fn from_cloud_item(item: &CloudItem, root_key: &CloudItemKey) -> Result<Self> {
37        if item.key().scope() != root_key.scope() {
38            return Err(MacosBridgeError::InvalidBackendResponse {
39                reason: "item escaped the File Provider root scope",
40            });
41        }
42        if item.is_root() != (item.key() == root_key) {
43            return Err(MacosBridgeError::InvalidBackendResponse {
44                reason: "item root shape did not match the File Provider root role",
45            });
46        }
47        if item.name().contains('/') || item.name().contains('\0') {
48            return Err(MacosBridgeError::InvalidBackendResponse {
49                reason: "item filename is not one File Provider path component",
50            });
51        }
52        let identifier = if item.is_root() {
53            MacosFileProviderIdentifier::System(crate::MacosFileProviderSystemContainer::Root)
54        } else {
55            MacosFileProviderIdentifier::encode(item.key())?
56        };
57        let parent_identifier = match item.parent_id() {
58            Some(parent_id) => {
59                let parent_key = CloudItemKey::new(item.key().scope().clone(), parent_id.clone());
60                if &parent_key == root_key {
61                    MacosFileProviderIdentifier::System(
62                        crate::MacosFileProviderSystemContainer::Root,
63                    )
64                } else {
65                    MacosFileProviderIdentifier::encode(&parent_key)?
66                }
67            }
68            None => {
69                MacosFileProviderIdentifier::System(crate::MacosFileProviderSystemContainer::Root)
70            }
71        };
72        let (kind, size, version) = match item.kind() {
73            CloudItemKind::Directory => (
74                MacosFileProviderItemKind::Directory,
75                0,
76                MacosFileProviderItemVersion::directory(item.metadata_revision().clone())?,
77            ),
78            CloudItemKind::File => {
79                let content = item
80                    .content()
81                    .ok_or(MacosBridgeError::InvalidBackendResponse {
82                        reason: "regular file omitted content metadata",
83                    })?;
84                (
85                    MacosFileProviderItemKind::File,
86                    content.size(),
87                    MacosFileProviderItemVersion::new(
88                        item.metadata_revision().clone(),
89                        content.revision().clone(),
90                    )?,
91                )
92            }
93        };
94        Ok(Self {
95            identifier,
96            parent_identifier,
97            filename: item.name().to_owned(),
98            kind,
99            size,
100            version,
101        })
102    }
103
104    /// Returns the persistent item identifier.
105    #[must_use]
106    pub const fn identifier(&self) -> &MacosFileProviderIdentifier {
107        &self.identifier
108    }
109
110    /// Returns the current parent identifier.
111    #[must_use]
112    pub const fn parent_identifier(&self) -> &MacosFileProviderIdentifier {
113        &self.parent_identifier
114    }
115
116    /// Returns the current display filename.
117    #[must_use]
118    pub fn filename(&self) -> &str {
119        &self.filename
120    }
121
122    /// Returns the item kind.
123    #[must_use]
124    pub const fn kind(&self) -> MacosFileProviderItemKind {
125        self.kind
126    }
127
128    /// Returns the logical content size, or zero for directories.
129    #[must_use]
130    pub const fn size(&self) -> u64 {
131        self.size
132    }
133
134    /// Returns separate metadata/content versions for regular files.
135    #[must_use]
136    pub const fn version(&self) -> &MacosFileProviderItemVersion {
137        &self.version
138    }
139}