Skip to main content

libimagstore/file_abstraction/
fs.rs

1//
2// imag - the personal information management suite for the commandline
3// Copyright (C) 2015-2020 Matthias Beyer <mail@beyermatthias.de> and contributors
4//
5// This library is free software; you can redistribute it and/or
6// modify it under the terms of the GNU Lesser General Public
7// License as published by the Free Software Foundation; version
8// 2.1 of the License.
9//
10// This library is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13// Lesser General Public License for more details.
14//
15// You should have received a copy of the GNU Lesser General Public
16// License along with this library; if not, write to the Free Software
17// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18//
19
20use std::fs::{File, OpenOptions, create_dir_all, remove_file, copy, rename};
21use std::io::{Seek, SeekFrom, Read};
22use std::path::{Path, PathBuf};
23use std::sync::Arc;
24
25use libimagerror::errors::ErrorMsg as EM;
26
27use super::FileAbstraction;
28use super::FileAbstractionInstance;
29use super::Drain;
30use crate::store::Entry;
31use crate::storeid::StoreIdWithBase;
32use crate::file_abstraction::iter::PathIterator;
33use crate::file_abstraction::iter::PathIterBuilder;
34
35use walkdir::WalkDir;
36use failure::ResultExt;
37use failure::Fallible as Result;
38use failure::Error;
39
40#[derive(Debug)]
41pub struct FSFileAbstractionInstance(PathBuf);
42
43impl FileAbstractionInstance for FSFileAbstractionInstance {
44
45    /**
46     * Get the content behind this file
47     */
48    fn get_file_content<'a>(&mut self, id: StoreIdWithBase<'a>) -> Result<Option<Entry>> {
49        debug!("Getting lazy file: {:?}", self);
50
51        let mut file = match open_file(&self.0) {
52            Err(err)       => return Err(Error::from(err)),
53            Ok(None)       => return Ok(None),
54            Ok(Some(file)) => file,
55        };
56
57        file.seek(SeekFrom::Start(0)).context(EM::FileNotSeeked)?;
58
59        let mut s = String::new();
60
61        file.read_to_string(&mut s)
62            .context(EM::IO)
63            .map_err(Error::from)
64            .map(|_| s)
65            .and_then(|s: String| Entry::from_str(id, &s))
66            .map(Some)
67    }
68
69    /**
70     * Write the content of this file
71     */
72    fn write_file_content(&mut self, buf: &Entry) -> Result<()> {
73        use std::io::Write;
74
75        let buf      = buf.to_str()?.into_bytes();
76        let mut file = create_file(&self.0).context(EM::FileNotCreated)?;
77
78        file.seek(SeekFrom::Start(0)).context(EM::FileNotCreated)?;
79        file.set_len(buf.len() as u64).context(EM::FileNotWritten)?;
80        file.write_all(&buf)
81            .context(EM::FileNotWritten)
82            .map_err(Error::from)
83    }
84}
85
86/// `FSFileAbstraction` state type
87///
88/// A lazy file is either absent, but a path to it is available, or it is present.
89#[derive(Debug, Default)]
90pub struct FSFileAbstraction {}
91
92impl FileAbstraction for FSFileAbstraction {
93
94    fn remove_file(&self, path: &PathBuf) -> Result<()> {
95        remove_file(path)
96            .context(EM::FileNotRemoved)
97            .map_err(Error::from)
98    }
99
100    fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<()> {
101        copy(from, to)
102            .map(|_| ())
103            .context(EM::FileNotCopied)
104            .map_err(Error::from)
105    }
106
107    fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<()> {
108        if let Some(p) = to.parent() {
109            if !p.exists() {
110                debug!("Creating: {:?}", p);
111                create_dir_all(&p).context(EM::DirNotCreated)?;
112            }
113        } else {
114            debug!("Failed to find parent. This looks like it will fail now");
115            //nothing
116        }
117
118        debug!("Renaming {:?} to {:?}", from, to);
119        rename(from, to)
120            .context(EM::FileNotRenamed)
121            .map_err(Error::from)
122    }
123
124    fn create_dir_all(&self, path: &PathBuf) -> Result<()> {
125        debug!("Creating: {:?}", path);
126        create_dir_all(path)
127            .context(EM::DirNotCreated)
128            .map_err(Error::from)
129    }
130
131    fn exists(&self, path: &PathBuf) -> Result<bool> {
132        Ok(path.exists())
133    }
134
135    fn is_file(&self, path: &PathBuf) -> Result<bool> {
136        Ok(path.is_file())
137    }
138
139    fn new_instance(&self, p: PathBuf) -> Box<dyn FileAbstractionInstance> {
140        Box::new(FSFileAbstractionInstance(p))
141    }
142
143    /// We return nothing from the FS here.
144    fn drain(&self) -> Result<Drain> {
145        Ok(Drain::empty())
146    }
147
148    /// FileAbstraction::fill implementation that consumes the Drain and writes everything to the
149    /// filesystem
150    fn fill(&mut self, mut d: Drain) -> Result<()> {
151        d.iter().fold(Ok(()), |acc, (path, element)| {
152            acc.and_then(|_| self.new_instance(path).write_file_content(&element))
153        })
154    }
155
156    fn pathes_recursively<'a>(&self,
157                          basepath: PathBuf,
158                          storepath: &'a PathBuf,
159                          backend: Arc<dyn FileAbstraction>)
160        -> Result<PathIterator<'a>>
161    {
162        trace!("Building PathIterator object");
163        Ok(PathIterator::new(Box::new(WalkDirPathIterBuilder { basepath }), storepath, backend))
164    }
165}
166
167#[derive(Debug)]
168pub struct WalkDirPathIterBuilder {
169    basepath: PathBuf
170}
171
172impl PathIterBuilder for WalkDirPathIterBuilder {
173    fn build_iter(&self) -> Box<dyn Iterator<Item = Result<PathBuf>>> {
174        trace!("Building iterator for {}", self.basepath.display());
175        Box::new(WalkDir::new(self.basepath.clone())
176            .min_depth(1)
177            .max_open(100)
178            .into_iter()
179            .filter(|r| match r {
180                Err(_) => true,
181                Ok(path) => path.file_type().is_file(),
182            })
183            .map(|r| {
184                trace!("Working in PathIterator with {:?}", r);
185                r.map(|e| PathBuf::from(e.path()))
186                    .context(format_err!("Error in Walkdir"))
187                    .map_err(Error::from)
188            }))
189    }
190
191    fn in_collection(&mut self, c: &str) -> Result<()> {
192        debug!("Altering PathIterBuilder path with: {:?}", c);
193        self.basepath.push(c);
194        debug!(" -> path : {:?}", self.basepath);
195
196        if !self.basepath.exists() {
197            Err(format_err!("Does not exist: {}", self.basepath.display()))
198        } else {
199            Ok(())
200        }
201    }
202}
203
204fn open_file<A: AsRef<Path>>(p: A) -> ::std::io::Result<Option<File>> {
205    match OpenOptions::new().write(true).read(true).open(p) {
206        Err(e) => match e.kind() {
207            ::std::io::ErrorKind::NotFound => Ok(None),
208            _ => Err(e),
209        },
210        Ok(file) => Ok(Some(file))
211    }
212}
213
214fn create_file<A: AsRef<Path>>(p: A) -> ::std::io::Result<File> {
215    if let Some(parent) = p.as_ref().parent() {
216        trace!("'{}' is directory = {}", parent.display(), parent.is_dir());
217        if !parent.is_dir() {
218            trace!("Implicitely creating directory: {:?}", parent);
219            create_dir_all(parent)?;
220        }
221    }
222    OpenOptions::new().write(true).read(true).create(true).open(p)
223}
224