1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
//! # Standard Paths
//!
//! A Rust library providing methods for accessing standard paths
//! on the local filesystem (config, cache, user directories and etc.).
//!
//! It's a port of [QStandardPaths](https://doc.qt.io/qt-5/qstandardpaths.html)
//! class of the Qt framework.
//!
//! ### Usage
//! ```
//! extern crate standard_paths;
//!
//! use standard_paths::*;
//! use standard_paths::LocationType::*;
//!
//! fn main() {
//!     let sp = StandardPaths::new_with_names("app", "org");
//!     println!("{:?}", sp.writable_location(AppLocalDataLocation));
//! }
//! ```

#[cfg(target_os = "linux")]
mod linux;

#[cfg(target_os = "linux")]
use linux::*;

#[cfg(windows)]
mod windows;

#[cfg(windows)]
use windows::*;

use std::env;
use std::path::{Path, PathBuf};
use std::io::{Error, ErrorKind};


/// Enumerates the standard location type.
///
/// Is used to call
/// [StandardPaths::writable location](struct.StandardPaths.html#method.writable_location) and
/// [StandardPaths::find_executable_in_paths](struct.StandardPaths.html#method.find_executable_in_paths).
///
/// Some of the values are used to acquire user-specific paths,
/// some are application-specific and some are system-wide.
#[derive(Debug, Clone, PartialEq)]
pub enum LocationType {
    /// The user's home directory.
    ///
    /// * On Linux systems it's equal to the `$HOME` environment variable.
    /// * On the last Windows operating systems it's equal to the `%HomePath%`
    /// environment variable.
    HomeLocation,
    /// The user's desktop directory.
    DesktopLocation,
    /// The user's documents directory.
    DocumentsLocation,
    /// The directory for the user's downloaded files.
    ///
    /// This is a generic value. On Windows if no such directory exists,
    /// the directory for storing user documents is returned.
    DownloadLocation,
    /// The user's movies and videos directory.
    MoviesLocation,
    /// The user's music, recordings and other audio files directory.
    MusicLocation,
    /// The user's pictures, photos and screenshots directory.
    PicturesLocation,
    /// The user's applications directory.
    ///
    /// It might contain either executables, desktop files, or shortcuts.
    ///
    /// It's a platform-specific value.
    ApplicationsLocation,
    /// The user's fonts directory.
    FontsLocation,
    /// The directory for the runtime communication files (like Unix local sockets).
    ///
    /// This is a generic value. It could returns
    /// [None](https://doc.rust-lang.org/std/option/enum.Option.html#variant.None)
    /// on some systems.
    RuntimeLocation,
    /// A directory for storing temporary files.
    ///
    /// It might be application-specific, user-specific or system-wide.
    TempLocation,
    /// The directory for the persistent data shared across applications.
    ///
    /// This is a generic value.
    GenericDataLocation,
    /// The persistent application data directory.
    ///
    /// This is an application-specific directory.
    /// On the Windows operating system, this returns the roaming path.
    AppDataLocation,
    /// The local settings directory.
    ///
    /// This is a Windows-specific value.
    /// On all other platforms, it returns the same value as
    /// [AppDataLocation](enum.LocationType.html#variant.AppDataLocation).
    AppLocalDataLocation,
    /// The directory for the user-specific cached data shared across applications.
    ///
    /// This is a generic value. It could returns
    /// [None](https://doc.rust-lang.org/std/option/enum.Option.html#variant.None)
    /// from the appropriate methods if the system has no concept of shared cache.
    GenericCacheLocation,
    /// The user-specific cached data directory.
    ///
    /// This is an application-specific directory.
    AppCacheLocation,
    /// The user-specific configuration files directory.
    ///
    /// This may be either a generic value or application-specific.
    ConfigLocation,
    /// The user-specific configuration files directory.
    /// shared between multiple applications.
    ///
    /// This is a generic value.
    GenericConfigLocation,
    /// The user-specific configuration files directory.
    ///
    /// This is an application-specific value.
    AppConfigLocation
}

/// Enumerates the locate option type.
///
/// Is used to call
/// [StandardPaths::locate location](struct.StandardPaths.html#method.locate) and
/// [StandardPaths::locate_all](struct.StandardPaths.html#method.locate_all).
#[derive(Debug, Clone, PartialEq)]
pub enum LocateOption {
    /// Locate both files and directories (traversing symbolic links).
    LocateBoth,
    /// Locate only files.
    LocateFile,
    /// Locate only directories.
    LocateDirectory
}

/// Stores application and organization names and provides all the crate methods.
pub struct StandardPaths {
    /// Application name.
    app_name: String,
    /// organization name.
    org_name: String
}

impl StandardPaths {

    /// Constructs a new `StandardPaths` with the application name
    /// derived from the `CARGO_PKG_NAME` variable.
    pub fn new() -> StandardPaths {
        StandardPaths {
            app_name: match env::var("CARGO_PKG_NAME") {
                Ok(name) => name,
                _ => String::new()
            },
            org_name: String::new()
        }
    }

    /// Constructs a new `StandardPaths` with the provided `app` and `organization` names.
    pub fn new_with_names<S>(app: S, organization: S) -> StandardPaths
    where S: Into<String> {
        StandardPaths {
            app_name: app.into(),
            org_name: organization.into()
        }
    }

    /// Append application suffix to the `path`.
    ///
    /// For example `~/.config` -> `~/.config/org/app`.
    ///
    /// # Arguments
    /// * `path` - a mutable `PathBuf` to which the app suffix should be appended.
    fn append_organization_and_app(&self, path: &mut PathBuf) {
        if !self.org_name.is_empty() {
            path.push(&self.org_name);
        }
        if !self.app_name.is_empty() {
            path.push(&self.app_name);
        }
    }

    /// Returns the directory where files of type `location` should be written to.
    ///
    /// Note: the returned path can be a directory that does not exist.
    ///
    /// Returns [Error](https://doc.rust-lang.org/std/io/struct.Error.html)
    /// if the location cannot be determined.
    ///
    /// # Arguments
    /// * `location` - location type.
    pub fn writable_location(&self, location: LocationType) -> Result<PathBuf, Error> {
        self.writable_location_impl(location)
    }

    /// Returns all the directories of type `location`.
    ///
    /// The vector of locations is sorted by priority, starting with
    /// [self.writable location](struct.StandardPaths.html#method.writable_location)
    /// if it can be determined.
    ///
    /// Returns [Error](https://doc.rust-lang.org/std/io/struct.Error.html)
    /// if the locations cannot be determined or an empty vector if no locations
    /// for the provided type are defined.
    ///
    /// # Arguments
    /// * `location` - location type.
    pub fn standard_locations(&self, location: LocationType) -> Result<Vec<PathBuf>, Error> {
        self.standard_locations_impl(location)
    }

    /// Returns the absolute file path to the executable with `name` in the system path.
    ///
    /// It also could be used to check a path to be an executable.
    ///
    /// Internally it calls the
    /// [self.find_executable_in_paths](struct.StandardPaths.html#method.find_executable_in_paths)
    /// method with the system path as the `paths` argument. On most operating systems
    /// the system path is determined by the `PATH` environment variable.
    ///
    /// Note: on Windows the executable extensions from the `PATHEXT` environment variable
    /// are automatically appended to the `name` if it doesn't contain any extension.
    ///
    /// Returns [None](https://doc.rust-lang.org/std/option/enum.Option.html#variant.None)
    /// if no executables are found or if the provided path is not executable.
    ///
    /// # Arguments
    /// * `name` - the name of the searched executable or an absolute path
    /// which should be checked to be executable.
    pub fn find_executable<S>(name: S) -> Option<Vec<PathBuf>>
    where S: Into<String> {
        // Read system paths
        let path_var = match env::var("PATH") {
            Ok(var) => var,
            _ => return None
        };
        let paths: Vec<PathBuf> = env::split_paths(&path_var).collect();
        StandardPaths::find_executable_in_paths(name, paths)
    }

    /// Returns the absolute file path to the executable with `name` in the provided `paths`.
    ///
    /// Note: on Windows the executable extensions from the `PATHEXT` environment variable
    /// are automatically appended to the `name` if it doesn't contain any extension.
    ///
    /// Returns [None](https://doc.rust-lang.org/std/option/enum.Option.html#variant.None)
    /// if no executables are found or if the provided path is not executable.
    ///
    /// # Arguments
    /// * `name` - the name of the searched executable or an absolute path
    /// which should be checked to be executable.
    /// * `paths` - the directories where to search for the executable.
    pub fn find_executable_in_paths<S, P>(name: S, paths: P) -> Option<Vec<PathBuf>>
    where S: Into<String>, P: AsRef<Vec<PathBuf>> {
        find_executable_in_paths_impl(name, &paths)
    }

    /// Search for a file or directory called 'name' in the standard locations.
    ///
    /// Returns a full path to the first file or directory found.
    ///
    /// Returns [Error](https://doc.rust-lang.org/std/io/struct.Error.html)
    /// if accessing the `location` failed or
    /// [None](https://doc.rust-lang.org/std/option/enum.Option.html#variant.None)
    /// if no such file or directory can be found.
    ///
    /// # Arguments
    /// * `location` - the location type where to search.
    /// * `name` - the name of the file or directory to search.
    /// * `option` - the type of entry to search.
    pub fn locate<P>(&self, location: LocationType, name: P, option: LocateOption) -> Result<Option<PathBuf>, Error>
    where P: AsRef<Path> {
        let paths = self.standard_locations(location)?;
        for mut path in paths {
            path.push(&name);
            match &option {
                &LocateOption::LocateBoth => if path.exists() { return Ok(Some(path)) },
                &LocateOption::LocateFile => if path.is_file() { return Ok(Some(path)) },
                &LocateOption::LocateDirectory => if path.is_dir() { return Ok(Some(path)) }
            }
        }
        Ok(None)
    }

    /// Search for all files or directories called 'name' in the standard locations.
    ///
    /// Returns a vector of full paths to the all files or directories found.
    ///
    /// Returns [Error](https://doc.rust-lang.org/std/io/struct.Error.html)
    /// if accessing the `location` failed or
    /// [None](https://doc.rust-lang.org/std/option/enum.Option.html#variant.None)
    /// if no such files or directories can be found.
    ///
    /// # Arguments
    /// * `location` - the location type where to search.
    /// * `name` - the name of the files or directories to search.
    /// * `option` - the type of entries to search.
    pub fn locate_all<P>(&self, location: LocationType, name: P, option: LocateOption) -> Result<Option<Vec<PathBuf>>, Error>
    where P: AsRef<Path> {
        let paths = self.standard_locations(location)?;
        let mut res = Vec::new();
        for mut path in paths {
            path.push(&name);
            match &option {
                &LocateOption::LocateBoth => if path.exists() { res.push(path); },
                &LocateOption::LocateFile => if path.is_file() { res.push(path); },
                &LocateOption::LocateDirectory => if path.is_dir() { res.push(path); }
            }
        }
        if res.is_empty() { Ok(None) } else { Ok(Some(res)) }
    }

    #[inline]
    fn home_dir_err() -> Error {
        Error::new(ErrorKind::Other, "Error getting HOME directory")
    }
}