active_storage/drivers/
disk.rs

1use std::{
2    io::ErrorKind,
3    path::{Path, PathBuf},
4    time::SystemTime,
5};
6
7use async_trait::async_trait;
8use tokio::fs;
9
10use super::{Driver, DriverError, DriverResult};
11use crate::contents::Contents;
12
13/// Configuration parameters for initializing a `DiskDriver`.
14pub struct Config {
15    pub location: PathBuf,
16}
17
18/// The `DiskDriver` struct represents a disk-based implementation of the
19/// `Driver` trait.
20///
21/// It provides methods for interacting with files and directories on the disk.
22#[derive(Clone)]
23#[allow(clippy::module_name_repetitions)]
24pub struct DiskDriver {
25    /// The location on the disk where the `DiskDriver` will operate.
26    location: PathBuf,
27}
28
29impl From<ErrorKind> for DriverError {
30    fn from(kind: ErrorKind) -> Self {
31        match kind {
32            ErrorKind::NotFound => Self::ResourceNotFound,
33            _ => kind.into(),
34        }
35    }
36}
37
38impl DiskDriver {
39    /// Initializes a new `DiskDriver` instance with the specified
40    /// configuration.
41    ///
42    /// If the specified location does not exist, it creates the necessary
43    /// directories.
44    ///
45    /// # Errors
46    ///
47    /// Returns an error if the initialization fails, such as being unable to
48    /// create the required directories.
49    pub async fn new(config: Config) -> DriverResult<Self> {
50        if !config.location.exists() {
51            if let Err(err) = fs::create_dir_all(&config.location).await {
52                return Err(err.kind().into());
53            }
54        }
55
56        Ok(Self {
57            location: config.location,
58        })
59    }
60}
61
62#[async_trait]
63impl Driver for DiskDriver {
64    /// Reads the contents of a file at the specified path within the disk-based
65    /// storage.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if there is an issue reading from the file or decoding
70    /// its contents.
71    async fn read(&self, path: &Path) -> DriverResult<Vec<u8>> {
72        let path = self.location.join(path);
73
74        let content = match fs::read(path).await {
75            Ok(content) => content,
76            Err(err) => return Err(err.kind().into()),
77        };
78        Ok(Contents::from(content).into())
79    }
80
81    /// Checks if a file exists at the specified path within the disk-based
82    /// storage.
83    ///
84    /// If the path does not point to a file, the method returns `Ok(false)`.
85    /// Otherwise, it checks if the file exists and returns the result.
86    ///
87    /// # Errors
88    ///
89    /// Returns an error if there is an issue checking the existence of the
90    /// file.
91    async fn file_exists(&self, path: &Path) -> DriverResult<bool> {
92        if !path.is_file() {
93            return Ok(false);
94        }
95
96        Ok(path.exists())
97    }
98
99    /// Writes the provided content to a file at the specified path within the
100    /// disk-based storage.
101    ///
102    /// If the directory structure leading to the file does not exist, it
103    /// creates the necessary directories.
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if there is any issue creating directories, writing to
108    /// the file, or handling other I/O-related errors.
109    async fn write(&self, path: &Path, content: Vec<u8>) -> DriverResult<()> {
110        let path = self.location.join(path);
111        if let Some(parent) = path.parent() {
112            if !parent.exists() {
113                if let Err(err) = fs::create_dir_all(parent).await {
114                    return Err(err.kind().into());
115                }
116            }
117        }
118
119        match fs::write(path, content).await {
120            Ok(()) => Ok(()),
121            Err(err) => Err(err.kind().into()),
122        }
123    }
124
125    /// Deletes the file at the specified path within the disk-based storage.
126    ///
127    /// # Errors
128    ///
129    /// Returns an error if the file does not exist or if there is any issue
130    /// deleting the file.
131    ///
132    /// If the file does not exist, the error variant
133    /// `DriverError::ResourceNotFound` is returned.
134    async fn delete(&self, path: &Path) -> DriverResult<()> {
135        let path = self.location.join(path);
136        if !path.exists() {
137            return Err(DriverError::ResourceNotFound);
138        };
139
140        match fs::remove_file(path).await {
141            Ok(()) => Ok(()),
142            Err(err) => Err(err.kind().into()),
143        }
144    }
145
146    /// Deletes the directory and its contents at the specified path within the
147    /// disk-based storage.
148    ///
149    /// # Errors
150    ///
151    /// Returns an error if the directory does not exist or if there is any
152    /// issue deleting the directory.
153    ///
154    /// If the directory does not exist, the error variant
155    /// `DriverError::DirectoryNotFound` is returned.
156    async fn delete_directory(&self, path: &Path) -> DriverResult<()> {
157        let path = self.location.join(path);
158
159        if !path.exists() {
160            return Err(DriverError::ResourceNotFound);
161        };
162
163        match fs::remove_dir_all(path).await {
164            Ok(()) => Ok(()),
165            Err(err) => Err(err.kind().into()),
166        }
167    }
168
169    /// Retrieves the last modification time of the file at the specified path
170    /// within the disk-based storage. # Errors
171    ///
172    /// Returns an error if the file does not exist or if there is any issue
173    /// retrieving the last modification time.
174    ///
175    /// If the file does not exist, the error variant
176    /// `DriverError::ResourceNotFound` is returned.
177    async fn last_modified(&self, path: &Path) -> DriverResult<SystemTime> {
178        let path = self.location.join(path);
179        if !path.exists() {
180            return Err(DriverError::ResourceNotFound);
181        }
182
183        let metadata = match fs::metadata(path).await {
184            Ok(metadata) => metadata,
185            Err(err) => return Err(err.kind().into()),
186        };
187
188        match metadata.modified() {
189            Ok(modified) => Ok(modified),
190            Err(err) => Err(err.kind().into()),
191        }
192    }
193}