gveditor_core_api/filesystems/
local.rs

1use async_trait::async_trait;
2use tokio::fs;
3use tokio_stream::wrappers::ReadDirStream;
4use tokio_stream::StreamExt;
5
6use crate::Errors;
7
8use super::{DirItemInfo, FileInfo, Filesystem, FilesystemErrors};
9use std::io::ErrorKind;
10
11/// Implementation of FileSystem methods for a local access
12#[derive(Default)]
13pub struct LocalFilesystem;
14
15impl LocalFilesystem {
16    pub fn new() -> Self {
17        Self
18    }
19}
20
21#[async_trait]
22impl Filesystem for LocalFilesystem {
23    /// Read a local file
24    async fn read_file_by_path(&self, path: &str) -> Result<FileInfo, Errors> {
25        fs::read_to_string(path)
26            .await
27            .map(|content| FileInfo::new(path, content))
28            .map_err(|err| match err.kind() {
29                ErrorKind::NotFound => Errors::Fs(FilesystemErrors::FileNotFound),
30                _ => Errors::Fs(FilesystemErrors::FileNotFound),
31            })
32    }
33
34    /// Write a local file
35    async fn write_file_by_path(&self, path: &str, content: &str) -> Result<(), Errors> {
36        fs::write(path, content)
37            .await
38            .map_err(|err| match err.kind() {
39                ErrorKind::NotFound => Errors::Fs(FilesystemErrors::FileNotFound),
40                _ => Errors::Fs(FilesystemErrors::FileNotFound),
41            })
42    }
43
44    // List a local directory
45    async fn list_dir_by_path(&self, path: &str) -> Result<Vec<DirItemInfo>, Errors> {
46        let dirs = fs::read_dir(path).await;
47
48        if let Ok(dirs) = dirs {
49            let mut result = Vec::new();
50            let mut items = ReadDirStream::new(dirs);
51
52            // Iterate over all the found extensions
53            while let Some(Ok(item)) = items.next().await {
54                let path = item.path();
55                let str_path = path.as_os_str().to_str().unwrap().to_string();
56                let item_name = path.file_name().unwrap().to_str().unwrap().to_string();
57                let is_file = path.is_file();
58                result.push(DirItemInfo {
59                    path: str_path,
60                    name: item_name,
61                    is_file,
62                });
63            }
64
65            result.sort_by_key(|item| item.is_file);
66
67            Ok(result)
68        } else {
69            let err = dirs.unwrap_err();
70            Err(match err.kind() {
71                ErrorKind::NotFound => Errors::Fs(FilesystemErrors::FileNotFound),
72                _ => Errors::Fs(FilesystemErrors::FileNotFound),
73            })
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80
81    use super::{Filesystem, LocalFilesystem};
82
83    #[tokio::test]
84    async fn read_files() {
85        let fs = LocalFilesystem::new();
86
87        let file_exists = fs.read_file_by_path("../readme.md").await.is_ok();
88        let doesnt_exist = fs.read_file_by_path("rust_>_*").await.is_err();
89
90        assert!(file_exists);
91        assert!(doesnt_exist);
92    }
93
94    #[tokio::test]
95    async fn list_dir() {
96        let fs = LocalFilesystem::new();
97
98        let items_in_dir = fs.list_dir_by_path(".").await;
99
100        assert!(items_in_dir.is_ok());
101
102        let items_in_dir = items_in_dir.unwrap();
103
104        assert!(items_in_dir.len() > 1);
105
106        assert!(!items_in_dir[0].is_file);
107    }
108}