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}