loonfs_core/path/read/
current_files.rs1use super::materialized_view::LoadedMetadataView;
10use crate::error::{CoreError, Result};
11use crate::metadata::MetadataViewSession;
12use loonfs_api::{AbsolutePath, InodeId, InodeKind, RevisionNo, ROOT_INODE_ID};
13use loonfs_objectstore::ObjectStore;
14use std::collections::{HashMap, HashSet};
15
16pub const MAX_RESOLVE_CURRENT_FILES: usize = loonfs_api::DEFAULT_MAX_PAGE_LIMIT as usize;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct CurrentFileState {
30 pub inode_id: InodeId,
33 pub visible: bool,
37 pub current_revision_no: Option<RevisionNo>,
40 pub current_path: Option<AbsolutePath>,
42}
43
44pub(crate) fn ensure_resolve_batch_within_cap(requested: usize) -> Result<()> {
48 if requested > MAX_RESOLVE_CURRENT_FILES {
49 return Err(CoreError::BatchTooLarge {
50 requested,
51 max: MAX_RESOLVE_CURRENT_FILES,
52 });
53 }
54 Ok(())
55}
56
57pub(crate) async fn resolve_current_files<S: ObjectStore + ?Sized>(
66 view: &LoadedMetadataView<'_, S>,
67 inode_ids: &[InodeId],
68) -> Result<Vec<CurrentFileState>> {
69 ensure_resolve_batch_within_cap(inode_ids.len())?;
70 let mut session = view.metadata_view().session();
71 let mut ancestor_paths = HashMap::new();
72 let mut states = Vec::with_capacity(inode_ids.len());
73 for &inode_id in inode_ids {
74 states.push(resolve_one(&mut session, &mut ancestor_paths, inode_id).await?);
75 }
76 Ok(states)
77}
78
79async fn resolve_one<S: ObjectStore + ?Sized>(
80 session: &mut MetadataViewSession<'_, '_, S>,
81 ancestor_paths: &mut HashMap<InodeId, AbsolutePath>,
82 inode_id: InodeId,
83) -> Result<CurrentFileState> {
84 let Some(inode) = session.visible_inode(inode_id).await? else {
85 return Ok(missing(inode_id));
86 };
87 let Some(current_path) = current_path(session, ancestor_paths, inode_id).await? else {
88 return Ok(missing(inode_id));
89 };
90 let current_revision_no = if inode.inode_kind == InodeKind::File {
91 session
92 .latest_revision_head_of_visible(inode_id)
93 .await?
94 .map(|revision| revision.revision_no)
95 } else {
96 None
97 };
98 Ok(CurrentFileState {
99 inode_id,
100 visible: true,
101 current_revision_no,
102 current_path: Some(current_path),
103 })
104}
105
106fn missing(inode_id: InodeId) -> CurrentFileState {
107 CurrentFileState {
108 inode_id,
109 visible: false,
110 current_revision_no: None,
111 current_path: None,
112 }
113}
114
115async fn current_path<S: ObjectStore + ?Sized>(
124 session: &mut MetadataViewSession<'_, '_, S>,
125 ancestor_paths: &mut HashMap<InodeId, AbsolutePath>,
126 inode_id: InodeId,
127) -> Result<Option<AbsolutePath>> {
128 let mut climbed = Vec::new();
129 let mut visited = HashSet::new();
130 let mut current = inode_id;
131 let base = loop {
132 if current == ROOT_INODE_ID {
133 break AbsolutePath::root();
134 }
135 if let Some(known) = ancestor_paths.get(¤t) {
136 break known.clone();
137 }
138 if !visited.insert(current) {
139 return Ok(None);
140 }
141 let Some(binding) = session.current_parent_binding_for_child(current).await? else {
142 return Ok(None);
143 };
144 current = binding.parent_inode_id;
145 climbed.push(binding);
146 };
147
148 let mut path = base;
152 for binding in climbed.iter().rev() {
153 path = path.join(&binding.display_name);
154 ancestor_paths.insert(binding.child_inode_id, path.clone());
155 }
156 Ok(Some(path))
157}
158
159#[cfg(test)]
160mod tests {
161 use super::{ensure_resolve_batch_within_cap, MAX_RESOLVE_CURRENT_FILES};
162 use loonfs_api::{ErrorCode, PaginationPolicy};
163
164 #[test]
165 fn the_batch_cap_is_the_pagination_maximum() {
166 assert_eq!(
167 MAX_RESOLVE_CURRENT_FILES,
168 PaginationPolicy::default().max_limit().get() as usize,
169 "the batch cap is the page limit, not a second number"
170 );
171 }
172
173 #[test]
174 fn a_batch_at_the_cap_is_accepted_and_one_past_it_names_the_cap() {
175 assert!(ensure_resolve_batch_within_cap(MAX_RESOLVE_CURRENT_FILES).is_ok());
176 let error = ensure_resolve_batch_within_cap(MAX_RESOLVE_CURRENT_FILES + 1)
177 .expect_err("one past the cap is refused");
178 assert_eq!(error.code(), ErrorCode::InvalidRequest);
179 assert!(
180 error
181 .to_string()
182 .contains(&MAX_RESOLVE_CURRENT_FILES.to_string()),
183 "the refusal should name the cap: {error}"
184 );
185 }
186}