├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-BSD ├── LICENSE-MIT ├── README.md ├── appveyor.yml ├── codeowners ├── img └── flowchart.png ├── rustfmt.toml └── src ├── error.rs ├── file_handler.rs ├── global_mutex.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.lock 3 | *.swp 4 | *.rsproj 5 | tags* 6 | build/ 7 | build-tests/ 8 | target/ 9 | *~ 10 | /.idea 11 | tests/tmp* 12 | src/tmp* 13 | /.project 14 | *.sublime-* 15 | /bin/ 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - PATH=$PATH:$HOME/.cargo/bin 4 | - RUST_BACKTRACE=1 5 | matrix: 6 | include: 7 | - os: linux 8 | env: TARGET=x86_64-unknown-linux-musl 9 | - os: osx 10 | language: rust 11 | rust: 12 | - stable 13 | sudo: false 14 | branches: 15 | only: 16 | - master 17 | cache: 18 | cargo: true 19 | before_script: 20 | - curl -sSL https://github.com/maidsafe/QA/raw/master/travis/cargo_install.sh > cargo_install.sh 21 | - bash cargo_install.sh cargo-prune; 22 | - if [[ -n "$TARGET" ]]; then 23 | rustup target add $TARGET; 24 | fi 25 | - rustup component add rustfmt clippy 26 | script: 27 | - set -x; 28 | if [[ -n "$TARGET" ]]; then 29 | TARGET_FLAG="--target $TARGET"; 30 | fi && 31 | cargo fmt -- --check && 32 | cargo check --verbose --all-targets $TARGET_FLAG && 33 | cargo clippy --verbose --all-targets $TARGET_FLAG && 34 | cargo test --verbose --release $TARGET_FLAG 35 | before_cache: 36 | - cargo prune 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Config File Handler - Change Log 2 | 3 | ## [0.11.0] 4 | - Use rust 1.28.0 stable / 2018-07-07 nightly 5 | - Use unwrap 1.2.1 6 | - rustfmt 0.99.2 and clippy-0.0.212 7 | 8 | ## [0.10.0] 9 | - Use rust 1.24.0 stable / 2018-02-05 nightly 10 | - rustfmt 0.9.0 and clippy-0.0.186 11 | 12 | ## [0.9.0] 13 | - Use rust 1.22.1 stable / 2017-11-23 nightly 14 | - rustfmt 0.9.0 and clippy-0.0.174 15 | 16 | ## [0.8.3] 17 | - `exe_file_stem` falls back to a default value in case of error 18 | 19 | ## [0.8.2] 20 | - Feature to specify additional search paths externally. 21 | 22 | ## [0.8.1] 23 | - change inline style link to reference style link definitions 24 | 25 | ## [0.8.0] 26 | - Use rust 1.19 stable / 2017-07-20 nightly 27 | - rustfmt 0.9.0 and clippy-0.0.144 28 | - Replace -Zno-trans with cargo check 29 | - Make appveyor script using fixed version of stable 30 | 31 | ## [0.7.0] 32 | - Use rust 1.17 stable 33 | - Update serde serialisations 34 | - Update CI script to run cargo_install from QA. 35 | 36 | ## [0.6.0] 37 | - Switch to serde insted of rustc-serialize 38 | - rustfmt 0.8.1 and clippy-0.0.120 39 | 40 | ## [0.5.0] 41 | - Cleaned up and improved CI scripts and README.md. 42 | - Renamed some public error variants. 43 | 44 | ## [0.4.0] 45 | - Modify file search paths for various paltforms. The path returned would either be the potential path where files can be read from or created, or will contain the default file already created by this crate, depending on function invoked. 46 | 47 | ## [0.3.1] 48 | - Migrate quick-error to 1.1.0. 49 | - various docs update 50 | 51 | ## [0.3.0] 52 | - Implemented std::fmt::Display and std::error::Error for Error. 53 | 54 | ## [0.2.1] 55 | - Fix: existing files are now overwritten, not appended to. 56 | - Added file locks to protect concurrent access. 57 | 58 | ## [0.2.0] 59 | - Added `open` function and made `cleanup` function public. 60 | 61 | ## [0.1.0] 62 | - Removed dependency on CBOR. 63 | - Updated dependencies. 64 | 65 | ## [0.0.1] 66 | - Initial implementation. 67 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["MaidSafe Developers "] 3 | description = "Create, read and write JSON-encoded config files." 4 | documentation = "https://docs.rs/config_file_handler" 5 | homepage = "https://maidsafe.net" 6 | license = "MIT OR BSD-3-Clause" 7 | name = "config_file_handler" 8 | readme = "README.md" 9 | repository = "https://github.com/maidsafe/config_file_handler" 10 | version = "0.11.0" 11 | edition = "2018" 12 | 13 | [dependencies] 14 | fs2 = "~0.4.2" 15 | lazy_static = "~0.2.8" 16 | quick-error = "~1.2.0" 17 | serde = "~1.0.27" 18 | serde_json = "~1.0.9" 19 | unwrap = "~1.2.1" 20 | dirs = "~1.0.4" 21 | -------------------------------------------------------------------------------- /LICENSE-BSD: -------------------------------------------------------------------------------- 1 | Copyright 2018 MaidSafe.net limited. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2018 MaidSafe.net limited. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # config_file_handler 2 | 3 | # UNMAINTAINED 4 | 5 | We no longer maintain this crate as it requires OS specific components and we find the more widely used [dirs-rs](https://github.com/soc/dirs-rs) to be a more appropriate mechanism for managing user dirs. 6 | 7 | |Crate|Documentation|Linux/macOS|Windows|Issues| 8 | |:---:|:-----------:|:--------:|:-----:|:----:| 9 | |[![](http://meritbadge.herokuapp.com/config_file_handler)](https://crates.io/crates/config_file_handler)|[![Documentation](https://docs.rs/config_file_handler/badge.svg)](https://docs.rs/config_file_handler)|[![Build Status](https://travis-ci.com/maidsafe/config_file_handler.svg?branch=master)](https://travis-ci.com/maidsafe/config_file_handler)|[![Build status](https://ci.appveyor.com/api/projects/status/22gb4w9fhvhv3hn4/branch/master?svg=true)](https://ci.appveyor.com/project/MaidSafe-QA/config-file-handler/branch/master)|[![Stories in Ready](https://badge.waffle.io/maidsafe/config_file_handler.png?label=ready&title=Ready)](https://waffle.io/maidsafe/config_file_handler)| 10 | 11 | | [MaidSafe website](https://maidsafe.net) | [SAFE Dev Forum](https://forum.safedev.org) | [SAFE Network Forum](https://safenetforum.org) | 12 | |:----------------------------------------:|:-------------------------------------------:|:----------------------------------------------:| 13 | 14 | ## Overview 15 | 16 | Create, read and write JSON-encoded config files. 17 | 18 | ![Flow chart](https://github.com/maidsafe/config_file_handler/blob/master/img/flowchart.png) 19 | 20 | ## License 21 | 22 | This SAFE Network library is dual-licensed under the Modified BSD ([LICENSE-BSD](LICENSE-BSD) https://opensource.org/licenses/BSD-3-Clause) or the MIT license ([LICENSE-MIT](LICENSE-MIT) http://opensource.org/licenses/MIT) at your option. 23 | 24 | ## Contribution 25 | 26 | Copyrights in the SAFE Network are retained by their contributors. No copyright assignment is required to contribute to this project. 27 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | global: 3 | RUST_BACKTRACE: 1 4 | matrix: 5 | - RUST_TOOLCHAIN: stable 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | cache: 12 | - '%USERPROFILE%\.cargo' 13 | - '%APPVEYOR_BUILD_FOLDER%\target' 14 | 15 | clone_depth: 1 16 | 17 | install: 18 | - ps: | 19 | $url = "https://github.com/maidsafe/QA/raw/master/appveyor/install_rustup.ps1" 20 | Invoke-WebRequest $url -OutFile "install_rustup.ps1" 21 | . ".\install_rustup.ps1" 22 | 23 | platform: 24 | - x86 25 | - x64 26 | 27 | configuration: 28 | - Release 29 | 30 | build_script: 31 | - cargo check --verbose --release --lib --tests 32 | 33 | test_script: 34 | - cargo test --verbose --release 35 | -------------------------------------------------------------------------------- /codeowners: -------------------------------------------------------------------------------- 1 | * @ustulation 2 | -------------------------------------------------------------------------------- /img/flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maidsafe-archive/config_file_handler/ca4fc95163b3de180bb3fec04b8f4b5937484b83/img/flowchart.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_try_shorthand = true 2 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | 10 | use serde_json::Error as JsonError; 11 | use std::env::VarError; 12 | use std::io::Error as IoError; 13 | 14 | quick_error! { 15 | /// Error types. 16 | #[derive(Debug)] 17 | pub enum Error { 18 | /// Wrapper for a `::std::env::VarError` 19 | Env(err: VarError) { 20 | description("Environment error") 21 | display("Environment error: {}", err) 22 | cause(err) 23 | from() 24 | } 25 | /// Wrapper for a `::std::io::Error` 26 | Io(err: IoError) { 27 | description("IO error") 28 | display("IO error: {}", err) 29 | cause(err) 30 | from() 31 | } 32 | /// Wrapper for a `::serde_json::Error` 33 | JsonParser(err: JsonError) { 34 | description("Json parse error") 35 | display("Json parse error: {}", err) 36 | cause(err) 37 | from() 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/file_handler.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | 10 | use crate::error::Error; 11 | use crate::global_mutex; 12 | use fs2::FileExt; 13 | use serde::de::DeserializeOwned; 14 | use serde::Serialize; 15 | use serde_json::{from_reader, to_string_pretty}; 16 | use std::env; 17 | use std::ffi::{OsStr, OsString}; 18 | use std::fs::{self, File, OpenOptions}; 19 | use std::io::{self, Write}; 20 | use std::marker::PhantomData; 21 | use std::path::{Path, PathBuf}; 22 | use std::sync::Mutex; 23 | 24 | lazy_static! { 25 | static ref ADDITIONAL_SEARCH_PATH: Mutex> = Mutex::new(None); 26 | } 27 | 28 | /// Set an additional search path. This, if set, will be tried before the other default ones. 29 | pub fn set_additional_search_path + ?Sized>(path: &P) { 30 | *unwrap!(ADDITIONAL_SEARCH_PATH.lock()) = Some(From::from(path)); 31 | } 32 | 33 | /// Struct for reading and writing config files. 34 | /// 35 | /// # Thread- and Process-Safety 36 | /// 37 | /// It is safe to read and write the same file using `FileHandler`concurrently 38 | /// in multiple threads and/or processes. 39 | pub struct FileHandler { 40 | path: PathBuf, 41 | _ph: PhantomData, 42 | } 43 | 44 | impl FileHandler { 45 | /// Constructor taking the required file name (not the full path) 46 | /// This function will return an error if the file does not exist. 47 | /// 48 | /// This function tests whether it has write access to the file in the following locations in 49 | /// this order (see also [an example config file flowchart] 50 | /// (https://github.com/maidsafe/crust/blob/master/docs/vault_config_file_flowchart.pdf)): 51 | /// 52 | /// 1. [`current_bin_dir()`](fn.current_bin_dir.html) 53 | /// 2. [`user_app_dir()`](fn.user_app_dir.html) 54 | /// 3. [`system_cache_dir()`](fn.system_cache_dir.html) 55 | /// 56 | /// Parameter `assert_writable` dictates if the file should be writable or not. 57 | /// 58 | /// See [Thread- and Process-Safety](#thread--and-process-safety) for notes on thread- and 59 | /// process-safety. 60 | pub fn open + ?Sized>( 61 | name: &S, 62 | assert_writable: bool, 63 | ) -> Result, Error> { 64 | let name = name.as_ref(); 65 | 66 | if let Some(mut path) = unwrap!(ADDITIONAL_SEARCH_PATH.lock()).clone() { 67 | path.push(name); 68 | if OpenOptions::new() 69 | .read(true) 70 | .write(assert_writable) 71 | .open(&path) 72 | .is_ok() 73 | { 74 | return Ok(FileHandler { 75 | path, 76 | _ph: PhantomData, 77 | }); 78 | } 79 | } 80 | 81 | if let Ok(mut path) = current_bin_dir() { 82 | path.push(name); 83 | if OpenOptions::new() 84 | .read(true) 85 | .write(assert_writable) 86 | .open(&path) 87 | .is_ok() 88 | { 89 | return Ok(FileHandler { 90 | path, 91 | _ph: PhantomData, 92 | }); 93 | } 94 | } 95 | 96 | if let Ok(mut path) = bundle_resource_dir() { 97 | path.push(name); 98 | if OpenOptions::new() 99 | .read(true) 100 | .write(assert_writable) 101 | .open(&path) 102 | .is_ok() 103 | { 104 | return Ok(FileHandler { 105 | path, 106 | _ph: PhantomData, 107 | }); 108 | } 109 | } 110 | 111 | if let Ok(mut path) = user_app_dir() { 112 | path.push(name); 113 | if OpenOptions::new() 114 | .read(true) 115 | .write(assert_writable) 116 | .open(&path) 117 | .is_ok() 118 | { 119 | return Ok(FileHandler { 120 | path, 121 | _ph: PhantomData, 122 | }); 123 | } 124 | } 125 | 126 | let mut path = system_cache_dir()?; 127 | path.push(name); 128 | match OpenOptions::new() 129 | .read(true) 130 | .write(assert_writable) 131 | .open(&path) 132 | { 133 | Ok(_) => Ok(FileHandler { 134 | path, 135 | _ph: PhantomData, 136 | }), 137 | Err(e) => Err(From::from(e)), 138 | } 139 | } 140 | 141 | /// Get the full path to the file. 142 | pub fn path(&self) -> &Path { 143 | &self.path 144 | } 145 | } 146 | 147 | impl FileHandler 148 | where 149 | T: Default + Serialize, 150 | { 151 | /// Constructor taking the required file name (not the full path) 152 | /// The config file will be initialised to a default if it does not exist. 153 | /// 154 | /// This function tests whether it has read access to the file in the following locations in 155 | /// this order (see also [an example config file flowchart] 156 | /// (https://github.com/maidsafe/crust/blob/master/docs/vault_config_file_flowchart.pdf)): 157 | /// 158 | /// 1. [`current_bin_dir()`](fn.current_bin_dir.html) 159 | /// 2. [`user_app_dir()`](fn.user_app_dir.html) 160 | /// 3. [`system_cache_dir()`](fn.system_cache_dir.html) 161 | /// 162 | /// Parameter `is_existing_file_writable` will assert that if the file pre-exists should it be 163 | /// also writable or not. (E.g. it is enough for `crust-config` file to merely exist as 164 | /// readable, but `bootstrap-cache` must be writable too if it exists, else no updation can 165 | /// happen). 166 | /// 167 | /// See [Thread- and Process-Safety](#thread--and-process-safety) for notes on thread- and 168 | /// process-safety. 169 | #[allow(clippy::new_ret_no_self)] 170 | pub fn new + ?Sized>( 171 | name: &S, 172 | is_existing_file_writable: bool, 173 | ) -> Result, Error> { 174 | if let Ok(fh) = Self::open(name, is_existing_file_writable) { 175 | return Ok(fh); 176 | } 177 | 178 | let contents = to_string_pretty(&T::default())?.into_bytes(); 179 | let name = name.as_ref(); 180 | 181 | let _guard = global_mutex::get_mutex() 182 | .lock() 183 | .expect("Could not lock mutex"); 184 | 185 | if let Some(mut path) = unwrap!(ADDITIONAL_SEARCH_PATH.lock()).clone() { 186 | path.push(name); 187 | if let Ok(mut f) = OpenOptions::new() 188 | .write(true) 189 | .create(true) 190 | .truncate(true) 191 | .open(&path) 192 | { 193 | write_with_lock(&mut f, &contents)?; 194 | return Ok(FileHandler { 195 | path, 196 | _ph: PhantomData, 197 | }); 198 | } 199 | } 200 | 201 | if let Ok(mut path) = current_bin_dir() { 202 | path.push(name); 203 | if let Ok(mut f) = OpenOptions::new() 204 | .write(true) 205 | .create(true) 206 | .truncate(true) 207 | .open(&path) 208 | { 209 | write_with_lock(&mut f, &contents)?; 210 | return Ok(FileHandler { 211 | path, 212 | _ph: PhantomData, 213 | }); 214 | } 215 | } 216 | 217 | if let Ok(mut path) = user_app_dir() { 218 | let avoid = if path.is_dir() { 219 | false 220 | } else { 221 | fs::create_dir(&path).is_err() 222 | }; 223 | if !avoid { 224 | path.push(name); 225 | if let Ok(mut f) = OpenOptions::new() 226 | .write(true) 227 | .create(true) 228 | .truncate(true) 229 | .open(&path) 230 | { 231 | write_with_lock(&mut f, &contents)?; 232 | return Ok(FileHandler { 233 | path, 234 | _ph: PhantomData, 235 | }); 236 | } 237 | } 238 | } 239 | 240 | let mut path = system_cache_dir()?; 241 | if !path.is_dir() { 242 | fs::create_dir(&path)?; 243 | } 244 | path.push(name); 245 | match OpenOptions::new() 246 | .write(true) 247 | .create(true) 248 | .truncate(true) 249 | .open(&path) 250 | { 251 | Ok(mut f) => { 252 | write_with_lock(&mut f, &contents)?; 253 | Ok(FileHandler { 254 | path, 255 | _ph: PhantomData, 256 | }) 257 | } 258 | Err(e) => Err(From::from(e)), 259 | } 260 | } 261 | } 262 | 263 | impl FileHandler 264 | where 265 | T: DeserializeOwned, 266 | { 267 | /// Read the contents of the file and decode it as JSON. 268 | #[allow(clippy::redundant_closure)] // because of lifetimes 269 | pub fn read_file(&self) -> Result { 270 | let mut file = File::open(&self.path)?; 271 | let contents = shared_lock(&mut file, |file| from_reader(file))?; 272 | Ok(contents) 273 | } 274 | } 275 | 276 | impl FileHandler 277 | where 278 | T: Serialize, 279 | { 280 | /// Write `contents` to the file as JSON. 281 | pub fn write_file(&self, contents: &T) -> Result<(), Error> { 282 | let contents = to_string_pretty(contents)?.into_bytes(); 283 | 284 | let _guard = global_mutex::get_mutex() 285 | .lock() 286 | .expect("Could not lock mutex"); 287 | 288 | let mut file = OpenOptions::new() 289 | .write(true) 290 | .create(true) 291 | .truncate(true) 292 | .open(&self.path)?; 293 | write_with_lock(&mut file, &contents)?; 294 | Ok(()) 295 | } 296 | } 297 | 298 | /// Remove the file from every location where it can be read. 299 | pub fn cleanup>(name: &S) -> io::Result<()> { 300 | let name = name.as_ref(); 301 | let i1 = current_bin_dir().into_iter(); 302 | let i2 = user_app_dir().into_iter(); 303 | let i3 = system_cache_dir().into_iter(); 304 | 305 | let dirs = i1.chain(i2.chain(i3)); 306 | 307 | for mut path in dirs { 308 | path.push(name); 309 | if path.exists() { 310 | fs::remove_file(path)?; 311 | } 312 | } 313 | 314 | Ok(()) 315 | } 316 | 317 | fn exclusive_lock(file: &mut File, f: F) -> Result 318 | where 319 | F: FnOnce(&mut File) -> Result, 320 | Error: From, 321 | { 322 | file.lock_exclusive()?; 323 | let result = f(file); 324 | file.unlock()?; 325 | result.map_err(From::from) 326 | } 327 | 328 | fn shared_lock(file: &mut File, f: F) -> Result 329 | where 330 | F: FnOnce(&mut File) -> Result, 331 | Error: From, 332 | { 333 | file.lock_shared()?; 334 | let result = f(file); 335 | file.unlock()?; 336 | result.map_err(From::from) 337 | } 338 | 339 | fn write_with_lock(file: &mut File, contents: &[u8]) -> Result<(), Error> { 340 | exclusive_lock(file, |file| file.write_all(contents)) 341 | } 342 | 343 | /// The full path to the directory containing the currently-running binary. See also [an example 344 | /// config file flowchart][1]. 345 | /// 346 | /// [1]: https://github.com/maidsafe/crust/blob/master/docs/vault_config_file_flowchart.pdf 347 | pub fn current_bin_dir() -> Result { 348 | match env::current_exe()?.parent() { 349 | Some(path) => Ok(path.to_path_buf()), 350 | None => Err(Error::Io(io::Error::new( 351 | io::ErrorKind::NotFound, 352 | "Current bin dir", 353 | ))), 354 | } 355 | } 356 | 357 | /// The full path to the directory containing the resources to currently-running binary. 358 | /// For OSX this is special directory. For others it's an error. 359 | #[cfg(not(target_os = "macos"))] 360 | pub fn bundle_resource_dir() -> Result { 361 | Err(Error::Io(io::Error::new( 362 | io::ErrorKind::NotFound, 363 | "Bundle resource directory only applicable to MacOs", 364 | ))) 365 | } 366 | 367 | /// The full path to the directory containing the resources to currently-running binary. 368 | /// For OSX this is special directory. For others it's an error. 369 | #[cfg(target_os = "macos")] 370 | pub fn bundle_resource_dir() -> Result { 371 | let mut bundle_dir = env::current_exe()? 372 | .parent() 373 | .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Bundle resources directory"))? 374 | .to_path_buf(); 375 | 376 | let is_inside_bundle = bundle_dir 377 | .to_str() 378 | .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Path is not unicode"))? 379 | .ends_with(".app/Contents/MacOS"); 380 | 381 | if !is_inside_bundle { 382 | return Err(Error::Io(io::Error::new( 383 | io::ErrorKind::NotFound, 384 | "Not inside an Application Bundle", 385 | ))); 386 | } 387 | 388 | bundle_dir = bundle_dir 389 | .parent() 390 | .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Bundle resource directory"))? 391 | .to_path_buf(); 392 | bundle_dir.push("Resources"); 393 | 394 | Ok(bundle_dir) 395 | } 396 | 397 | /// The full path to an application support directory for the current user. See also [an example 398 | /// config file flowchart][1]. 399 | /// 400 | /// [1]: https://github.com/maidsafe/crust/blob/master/docs/vault_config_file_flowchart.pdf 401 | #[cfg(windows)] 402 | pub fn user_app_dir() -> Result { 403 | let path = env::var("APPDATA")?; 404 | let app_dir = Path::new(&path); 405 | 406 | if app_dir.is_dir() { 407 | Ok(join_exe_file_stem(app_dir)?) 408 | } else { 409 | Err(Error::Io(io::Error::new( 410 | io::ErrorKind::NotFound, 411 | "Global user app directory not found.", 412 | ))) 413 | } 414 | } 415 | 416 | /// The full path to an application support directory for the current user. See also [an example 417 | /// config file flowchart][1]. 418 | /// 419 | /// [1]: https://github.com/maidsafe/crust/blob/master/docs/vault_config_file_flowchart.pdf 420 | #[cfg(all(unix, not(target_os = "macos")))] 421 | pub fn user_app_dir() -> Result { 422 | let mut home_dir = dirs::home_dir() 423 | .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Home directory not found."))?; 424 | home_dir.push(".config"); 425 | 426 | if home_dir.is_dir() { 427 | Ok(join_exe_file_stem(&home_dir)?) 428 | } else { 429 | Err(Error::Io(io::Error::new( 430 | io::ErrorKind::NotFound, 431 | "Global user app directory not found.", 432 | ))) 433 | } 434 | } 435 | 436 | /// The full path to an application support directory for the current user. See also [an example 437 | /// config file flowchart][1]. 438 | /// 439 | /// [1]: https://github.com/maidsafe/crust/blob/master/docs/vault_config_file_flowchart.pdf 440 | #[cfg(target_os = "macos")] 441 | pub fn user_app_dir() -> Result { 442 | let mut app_dir = dirs::home_dir() 443 | .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Home directory not found."))?; 444 | app_dir.push("Library/Application Support"); 445 | 446 | if app_dir.is_dir() { 447 | Ok(join_exe_file_stem(&app_dir)?) 448 | } else { 449 | Err(Error::Io(io::Error::new( 450 | io::ErrorKind::NotFound, 451 | "Global user app directory not found.", 452 | ))) 453 | } 454 | } 455 | 456 | /// The full path to a system cache directory available for all users. See also [an example config 457 | /// file flowchart][1]. 458 | /// 459 | /// [1]: https://github.com/maidsafe/crust/blob/master/docs/vault_config_file_flowchart.pdf 460 | #[cfg(windows)] 461 | pub fn system_cache_dir() -> Result { 462 | let path = env::var("ALLUSERSPROFILE")?; 463 | let sys_cache_dir = Path::new(&path); 464 | 465 | if sys_cache_dir.is_dir() { 466 | Ok(join_exe_file_stem(sys_cache_dir)?) 467 | } else { 468 | Err(Error::Io(io::Error::new( 469 | io::ErrorKind::NotFound, 470 | "Global system cache directory not found.", 471 | ))) 472 | } 473 | } 474 | 475 | /// The full path to a system cache directory available for all users. See also [an example config 476 | /// file flowchart][1]. 477 | /// 478 | /// [1]: https://github.com/maidsafe/crust/blob/master/docs/vault_config_file_flowchart.pdf 479 | #[cfg(all(unix, not(target_os = "macos")))] 480 | pub fn system_cache_dir() -> Result { 481 | let sys_cache_dir = Path::new("/var/cache"); 482 | 483 | if sys_cache_dir.is_dir() { 484 | Ok(join_exe_file_stem(sys_cache_dir)?) 485 | } else { 486 | Err(Error::Io(io::Error::new( 487 | io::ErrorKind::NotFound, 488 | "Global system cache directory not found.", 489 | ))) 490 | } 491 | } 492 | 493 | /// The full path to a system cache directory available for all users. See also [an example config 494 | /// file flowchart][1]. 495 | /// 496 | /// [1]: https://github.com/maidsafe/crust/blob/master/docs/vault_config_file_flowchart.pdf 497 | #[cfg(target_os = "macos")] 498 | pub fn system_cache_dir() -> Result { 499 | let sys_cache_dir = Path::new("/Library/Application Support"); 500 | 501 | if sys_cache_dir.is_dir() { 502 | Ok(join_exe_file_stem(sys_cache_dir)?) 503 | } else { 504 | Err(Error::Io(io::Error::new( 505 | io::ErrorKind::NotFound, 506 | "Global system cache directory not found.", 507 | ))) 508 | } 509 | } 510 | 511 | /// The file name of the currently-running binary without any suffix or extension. For example, if 512 | /// the binary is "C:\\Abc.exe" this function will return `Ok("Abc")`. 513 | pub fn exe_file_stem() -> Result { 514 | if let Ok(exe_path) = env::current_exe() { 515 | let file_stem = exe_path.file_stem(); 516 | Ok(file_stem 517 | .ok_or_else(|| not_found_error(&exe_path))? 518 | .to_os_string()) 519 | } else { 520 | Ok(From::from("default")) 521 | } 522 | } 523 | 524 | /// RAII object which removes the [`user_app_dir()`](fn.user_app_dir.html) when an instance is 525 | /// dropped. 526 | /// 527 | /// Since the `user_app_dir` is frequently created by tests or examples which use Crust, this is a 528 | /// convenience object which tries to remove the directory when it is destroyed. 529 | /// 530 | /// # Examples 531 | /// 532 | /// ``` 533 | /// use config_file_handler::{FileHandler, ScopedUserAppDirRemover}; 534 | /// 535 | /// { 536 | /// let _cleaner = ScopedUserAppDirRemover; 537 | /// let file_handler = FileHandler::new("test.json", true).unwrap(); 538 | /// // User app dir is possibly created by this call. 539 | /// let _ = file_handler.write_file(&111u64); 540 | /// } 541 | /// // User app dir is now removed since '_cleaner' has gone out of scope. 542 | /// ``` 543 | pub struct ScopedUserAppDirRemover; 544 | 545 | impl ScopedUserAppDirRemover { 546 | fn remove_dir(&mut self) { 547 | let _ = user_app_dir() 548 | .and_then(|user_app_dir| fs::remove_dir_all(user_app_dir).map_err(Error::Io)); 549 | } 550 | } 551 | 552 | impl Drop for ScopedUserAppDirRemover { 553 | fn drop(&mut self) { 554 | self.remove_dir(); 555 | } 556 | } 557 | 558 | fn not_found_error(file_name: &Path) -> io::Error { 559 | let mut msg: String = From::from("No file name component: "); 560 | msg.push_str(&file_name.to_string_lossy()); 561 | io::Error::new(io::ErrorKind::NotFound, msg) 562 | } 563 | 564 | fn join_exe_file_stem(path: &Path) -> Result { 565 | Ok(path.join(exe_file_stem()?)) 566 | } 567 | 568 | #[cfg(test)] 569 | mod test { 570 | use super::*; 571 | 572 | #[test] 573 | fn read_write_file_test() { 574 | let _cleaner = ScopedUserAppDirRemover; 575 | let file_handler = match FileHandler::new("test0.json", true) { 576 | Ok(result) => result, 577 | Err(err) => panic!("failed accessing file with error {:?}", err), 578 | }; 579 | let test_value = 123_456_789u64; 580 | 581 | let _ = file_handler.write_file(&test_value); 582 | let read_value = match file_handler.read_file() { 583 | Ok(result) => result, 584 | Err(err) => panic!("failed reading file with error {:?}", err), 585 | }; 586 | assert_eq!(test_value, read_value); 587 | } 588 | 589 | #[test] 590 | fn existing_file_is_overwritten() { 591 | let _cleaner = ScopedUserAppDirRemover; 592 | let file_handler = FileHandler::new("test1.json", true).expect("failed accessing file"); 593 | 594 | let write_value0 = vec![1, 2, 3]; 595 | file_handler 596 | .write_file(&write_value0) 597 | .expect("failed writing file"); 598 | 599 | let write_value1 = vec![4, 5, 6]; 600 | file_handler 601 | .write_file(&write_value1) 602 | .expect("failed writing file"); 603 | 604 | let read_value = file_handler.read_file().expect("failed reading file"); 605 | assert_eq!(read_value, write_value1); 606 | } 607 | 608 | #[test] 609 | fn concurrent_writes() { 610 | use std::iter; 611 | use std::sync::{Arc, Barrier}; 612 | use std::thread; 613 | 614 | const NUM_THREADS: usize = 100; 615 | const DATA_SIZE: usize = 10_000; 616 | const FILE_NAME: &str = "test2.json"; 617 | 618 | let _cleaner = ScopedUserAppDirRemover; 619 | let barrier = Arc::new(Barrier::new(NUM_THREADS)); 620 | 621 | let handles = (0..NUM_THREADS) 622 | .map(|i| { 623 | let barrier = Arc::clone(&barrier); 624 | 625 | thread::spawn(move || { 626 | let data = iter::repeat(i).take(DATA_SIZE).collect::>(); 627 | 628 | let _ = barrier.wait(); 629 | 630 | let file_handler = 631 | FileHandler::new(FILE_NAME, true).expect("failed accessing file"); 632 | file_handler.write_file(&data).expect("failed writing file"); 633 | }) 634 | }) 635 | .collect::>(); 636 | 637 | for handle in handles { 638 | unwrap!(handle.join()); 639 | } 640 | 641 | let file_handler = FileHandler::new(FILE_NAME, true).expect("failed accessing file"); 642 | let mut data: Vec = file_handler.read_file().expect("failed reading file"); 643 | 644 | // Test that all elements in the vector are the same, to verify no 645 | // interleaving took place. 646 | data.sort(); 647 | data.dedup(); 648 | assert_eq!(data.len(), 1); 649 | } 650 | 651 | // Run as `cargo test -- --ignored --nocapture` to print the paths 652 | #[test] 653 | #[ignore] 654 | #[allow(clippy::ifs_same_cond)] 655 | fn print_paths() { 656 | let os = if cfg!(target_os = "macos") { 657 | "macOS".to_string() 658 | } else if cfg!(target_os = "linux") { 659 | "Linux".to_string() 660 | } else if cfg!(unix) { 661 | "Unix (family)".to_string() 662 | } else if cfg!(windows) { 663 | "Windows".to_string() 664 | } else { 665 | "Unknown".to_string() 666 | }; 667 | 668 | let current_bin_dir = match current_bin_dir() { 669 | Ok(x) => format!("{:?}", x), 670 | Err(x) => format!("{:?}", x), 671 | }; 672 | 673 | let bundle_resource_dir = match bundle_resource_dir() { 674 | Ok(x) => format!("{:?}", x), 675 | Err(x) => format!("{:?}", x), 676 | }; 677 | 678 | let user_app_dir = match user_app_dir() { 679 | Ok(x) => format!("{:?}", x), 680 | Err(x) => format!("{:?}", x), 681 | }; 682 | 683 | let system_cache_dir = match system_cache_dir() { 684 | Ok(x) => format!("{:?}", x), 685 | Err(x) => format!("{:?}", x), 686 | }; 687 | 688 | println!("================================="); 689 | println!("Current bin dir in {}: {}", os, current_bin_dir); 690 | println!("Current bin resource in {}: {}", os, bundle_resource_dir); 691 | println!("Current use-app-dir in {}: {}", os, user_app_dir); 692 | println!("Current system-cache-dir in {}: {}", os, system_cache_dir); 693 | println!("================================="); 694 | } 695 | } 696 | -------------------------------------------------------------------------------- /src/global_mutex.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | 10 | use std::sync::{Mutex, Once, ONCE_INIT}; 11 | 12 | pub type GlobalMutex = Mutex<()>; 13 | 14 | #[allow(unsafe_code)] 15 | pub fn get_mutex<'a>() -> &'a GlobalMutex { 16 | static mut GLOBAL_MUTEX: *const GlobalMutex = 0 as *const GlobalMutex; 17 | static ONCE: Once = ONCE_INIT; 18 | 19 | unsafe { 20 | ONCE.call_once(|| { 21 | GLOBAL_MUTEX = Box::into_raw(Box::new(GlobalMutex::new(()))); 22 | }); 23 | 24 | &*GLOBAL_MUTEX 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | 10 | //! # Config File Handler 11 | //! 12 | //! Create, read and write JSON-encoded config files. 13 | 14 | #![doc( 15 | html_logo_url = "https://raw.githubusercontent.com/maidsafe/QA/master/Images/maidsafe_logo.png", 16 | html_favicon_url = "https://maidsafe.net/img/favicon.ico", 17 | test(attr(forbid(warnings))) 18 | )] 19 | // For explanation of lint checks, run `rustc -W help` or see 20 | // https://github.com/maidsafe/QA/blob/master/Documentation/Rust%20Lint%20Checks.md 21 | #![forbid( 22 | exceeding_bitshifts, 23 | mutable_transmutes, 24 | no_mangle_const_items, 25 | unknown_crate_types, 26 | warnings 27 | )] 28 | #![deny( 29 | bad_style, 30 | deprecated, 31 | improper_ctypes, 32 | missing_docs, 33 | non_shorthand_field_patterns, 34 | overflowing_literals, 35 | plugin_as_library, 36 | stable_features, 37 | unconditional_recursion, 38 | unknown_lints, 39 | unsafe_code, 40 | unused, 41 | unused_allocation, 42 | unused_attributes, 43 | unused_comparisons, 44 | unused_features, 45 | unused_parens, 46 | while_true 47 | )] 48 | #![warn( 49 | trivial_casts, 50 | trivial_numeric_casts, 51 | unused_extern_crates, 52 | unused_import_braces, 53 | unused_qualifications, 54 | unused_results 55 | )] 56 | #![allow( 57 | box_pointers, 58 | missing_copy_implementations, 59 | missing_debug_implementations, 60 | variant_size_differences 61 | )] 62 | 63 | #[macro_use] 64 | extern crate lazy_static; 65 | #[macro_use] 66 | extern crate quick_error; 67 | #[macro_use] 68 | extern crate unwrap; 69 | 70 | mod error; 71 | mod file_handler; 72 | mod global_mutex; 73 | 74 | pub use crate::error::Error; 75 | pub use crate::file_handler::{ 76 | cleanup, current_bin_dir, exe_file_stem, set_additional_search_path, system_cache_dir, 77 | user_app_dir, FileHandler, ScopedUserAppDirRemover, 78 | }; 79 | --------------------------------------------------------------------------------