(&self, path: &WorkspacePath)
| 116 | } |
| 117 | |
| 118 | async fn list_dir(&self, path: &WorkspacePath) -> WorkspaceResult<Vec<WorkspaceDirEntry>> { |
| 119 | let target = self.local_path_for_read(path)?; |
| 120 | if !target.exists() { |
| 121 | return Err(WorkspaceError::NotFound { |
| 122 | path: target.display().to_string(), |
| 123 | }); |
| 124 | } |
| 125 | if !target.is_dir() { |
| 126 | return Err(WorkspaceError::InvalidArgument { |
| 127 | message: format!("Not a directory: {}", target.display()), |
| 128 | }); |
| 129 | } |
| 130 | |
| 131 | let mut dir = tokio::fs::read_dir(&target).await.map_err(|e| { |
| 132 | WorkspaceError::Backend(anyhow!( |
| 133 | "Failed to read directory {}: {}", |
| 134 | target.display(), |
| 135 | e |
| 136 | )) |
| 137 | })?; |
| 138 | let mut entries = Vec::new(); |
| 139 | |
| 140 | while let Some(entry) = dir |
| 141 | .next_entry() |
| 142 | .await |
| 143 | .map_err(|e| WorkspaceError::Backend(anyhow!("Failed to iterate directory: {}", e)))? |
| 144 | { |
| 145 | let name = entry.file_name().to_string_lossy().to_string(); |
| 146 | let file_type = entry.file_type().await; |
| 147 | let metadata = entry.metadata().await; |
| 148 | let (kind, size) = match (&file_type, &metadata) { |
| 149 | (Ok(ft), Ok(m)) => { |
| 150 | let kind = if ft.is_dir() { |
| 151 | WorkspaceFileType::Directory |
| 152 | } else if ft.is_symlink() { |
| 153 | WorkspaceFileType::Symlink |
| 154 | } else { |
| 155 | WorkspaceFileType::File |
| 156 | }; |
| 157 | (kind, m.len()) |
| 158 | } |
| 159 | _ => (WorkspaceFileType::Unknown, 0), |
| 160 | }; |
| 161 | entries.push(WorkspaceDirEntry { name, kind, size }); |
| 162 | } |
| 163 | |
| 164 | Ok(entries) |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | #[async_trait] |
no test coverage detected