vsm/utils/fs.rs
1//! Wrapper around the standard file-system module.
2
3use std::path::{Path, PathBuf};
4use std::{fs, io};
5
6use derive_getters::Getters;
7use log::debug;
8use serde::Serialize;
9
10use crate::config::TomlConfigFile;
11use crate::error::VsmRuntimeFault;
12
13/// Provides a simplified constrained interface to locations on disk and actions
14/// for directories and files that vsm requires to work.
15#[derive(Debug, Getters)]
16pub struct FilesystemManager {
17 /// Absolute path to configuration file storage directory
18 config_dir: String,
19 /// Absolute path to configure file
20 config_file: String,
21 /// Absolute path to session storage directory
22 vim_session_dir: String,
23}
24
25impl FilesystemManager {
26 /// Builds a new FilesystemManager object, relies on the static ENVIRONMENT
27 /// state object to get parameters.
28 ///
29 /// # Arguments
30 /// * config_dir Absolute path to the configuration directory.
31 /// * config_file Absolute path to the configuration file.
32 /// * vim_session_dir Absolute path to the vim sessions directory.
33 pub fn new(config_dir: &str, config_file: &str, vim_session_dir: &str) -> Self {
34 Self {
35 config_dir: String::from(config_dir),
36 config_file: String::from(config_file),
37 vim_session_dir: String::from(vim_session_dir),
38 }
39 }
40
41 /// Check if the configuration directory exists on disk.
42 ///
43 /// # Returns
44 /// * true if the directory exists.
45 /// * false if the path points to a broken symlink,
46 /// or the program does not have proper permissions.
47 fn config_dir_exists(&self) -> bool {
48 Path::new(self.config_dir()).is_dir()
49 }
50
51 /// Check if the configuration file exists on disk.
52 ///
53 /// # Returns
54 /// * true if the file exists.
55 /// * false if the path points to a broken symlink, or the
56 /// program does not have proper permissions.
57 pub fn config_file_exists(&self) -> bool {
58 Path::new(self.config_file()).is_file()
59 }
60
61 /// Check if the vim_sessions directory exists on disk.
62 ///
63 /// # Returns
64 /// * true if the directory exists.
65 /// * false if the path points to a broken symlink,
66 /// or the program does not have proper permissions.
67 pub fn vim_session_dir_exists(&self) -> bool {
68 Path::new(self.vim_session_dir()).is_dir()
69 }
70
71 /// Collects all vim session files into a Vector of Path Buffers.
72 ///
73 /// # Returns
74 /// * Option<Vec<PathBuf>> the vector
75 /// of paths is an option in-case the directory is empty,
76 /// this is how we can tell if we have session files, to list,
77 /// or remove or open.
78 ///
79 /// # Errors
80 /// * io::Error
81 pub fn load_vim_session_files(&self) -> Result<Option<Vec<PathBuf>>, io::Error> {
82 // if the sessions directory doesn't exist, create it and all parent directories before
83 // it, and return a result of None, since we know there aren't any session files to load from a
84 // directory we just created.
85 if !self.vim_session_dir_exists() {
86 fs::create_dir_all(self.vim_session_dir())?;
87 return Ok(None);
88 }
89
90 // the directory exists, so lets read it.
91 let mut session_files = fs::read_dir(self.vim_session_dir())?
92 .map(|res| res.map(|session| session.path()))
93 .collect::<Result<Vec<PathBuf>, io::Error>>()?;
94
95 // this gaurds against the case where the directory existed already,
96 // but there were no session files found.
97 if session_files.is_empty() {
98 return Ok(None);
99 }
100
101 // Now we know we have sessions.
102 // `session_files` is just a vector of PathBuf's loaded from the VIM_SESSIONS directory.
103 // We can't be sure that the user hasn't put files other than .vim in there, or
104 // created directories, the existence of either would create unwanted bugs when opening
105 // sessions with vim variants. Therefore, we only keep paths that are files, and have a "vim" file extension.
106 // anything else gets dropped.
107 session_files.retain(|path| {
108 if path.is_file() {
109 path.extension().map_or_else(|| false, |ext| ext == "vim")
110 } else {
111 false
112 }
113 });
114
115 session_files.sort();
116 Ok(Some(session_files))
117 }
118
119 /// Serializes a config structure and writes it to disk as .. Does not check
120 /// if the configure file already exists. Assumes it is being used in tandem
121 /// with config_file_exists().
122 ///
123 /// # Arguments
124 /// * config_struct Any type which implements the
125 /// serde::Serialize + Sized traits.
126 ///
127 /// # Returns
128 /// * Result<()> on success.
129 ///
130 /// # Errors
131 /// * VsmRuntimeFault::TomlConfigFileWrite captures all
132 /// possible errors during io and serialization.
133 pub fn write_config<T>(&self, config_struct: T) -> Result<(), VsmRuntimeFault>
134 where
135 T: Serialize + Sized,
136 {
137 if !self.config_dir_exists() {
138 if let Err(e) = fs::create_dir_all(self.config_dir()) {
139 return Err(VsmRuntimeFault::TomlConfigFileWrite { msg: e.to_string() });
140 }
141 }
142 match toml::to_string(&config_struct) {
143 Ok(serialized_string) => {
144 debug!("Writing config file => {}", self.config_file());
145 if let Err(e) = fs::write(self.config_file(), serialized_string) {
146 Err(VsmRuntimeFault::TomlConfigFileWrite { msg: e.to_string() })
147 } else {
148 Ok(())
149 }
150 }
151 Err(e) => Err(VsmRuntimeFault::TomlConfigFileWrite { msg: e.to_string() }),
152 }
153 }
154
155 /// Deserializes the config.toml from disk.
156 ///
157 /// # Returns
158 /// * Ok(TomlConfigFile) if nothing went wrong.
159 ///
160 /// # Errors
161 /// * Err(VsmRuntimeFault::TomlConfigFileRead) containing
162 /// the error message generated from either the serde or toml
163 /// libraries respectively.
164 pub fn read_config(&self) -> Result<TomlConfigFile, VsmRuntimeFault> {
165 debug!("Reading {}", self.config_file());
166 match fs::read_to_string(self.config_file()) {
167 Ok(contents) => {
168 let mut error_string = String::from("");
169 let config_option: Option<TomlConfigFile> = match toml::from_str(&contents) {
170 Ok(c) => Some(c),
171 Err(e) => {
172 error_string = e.to_string();
173 None
174 }
175 };
176 config_option.map_or(
177 Err(VsmRuntimeFault::TomlConfigFileRead { msg: error_string }),
178 Ok,
179 )
180 }
181 Err(e) => Err(VsmRuntimeFault::TomlConfigFileRead { msg: e.to_string() }),
182 }
183 }
184
185 /// Wrapper around the built in filesystem remove file function.
186 ///
187 /// # Errors
188 /// * VsmRuntimeFault::SessionFileRemoval if fs::remove_file fails.
189 pub fn remove_file(&self, session: &PathBuf) -> Result<(), VsmRuntimeFault> {
190 if let Err(e) = fs::remove_file(session) {
191 let msg = format!("Failed to remove {}\n{}", session.to_string_lossy(), e);
192 return Err(VsmRuntimeFault::SessionFileRemoval { msg });
193 }
194 Ok(())
195 }
196}