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
//! Filesystem I/O abstractions. //! //! This module should be used only in `heim` sub-crates, //! do not use it directly. //! //! ## Why in a hell there is a sync FS operations in the async crate?! //! //! At the moment `runtime` crate does not provides the abstractions for the files IO. //! Yet, in our case, FS opts are needed only for Linux and only `procfs` is used there. //! Since `procfs` stores data in the memory, it would not be very terrible to read this //! data synchronously -- it still will be quick enough. //! //! When `runtime` crate will provide fully async FS abstractions, //! we will switch to them. use std::fs; use std::io::{self, BufRead}; use std::path::Path; use std::str::FromStr; use crate::prelude::*; /// Returns future which checks if path `T` points to some file. pub fn path_exists<T>(path: T) -> impl Future<Output = bool> where T: AsRef<Path> + Send + 'static, { // TODO: It is using a "sync" API // but since it is used only at Linux and only for in-memory located // filesystems, it should not be a big problem, since that kind of IO // will not block so much. // // In any way, this thing should be refactored when `runtime` crate // will have the proper async FS I/O support. future::ready(path.as_ref().exists()) } /// Read `path` file asynchronously and convert it contents into a string. pub fn read_to_string<T>(path: T) -> impl Future<Output = Result<String>> where T: AsRef<Path> + Send + 'static, { // TODO: It is using a "sync" API // but since it is used only at Linux and only for in-memory located // filesystems, it should not be a big problem, since that kind of IO // will not block so much. // // In any way, this thing should be refactored when `runtime` crate // will have the proper async FS I/O support. let res = fs::read_to_string(path).map_err(From::from); future::ready(res) } // TODO: Probably should be renamed into `try_read_into` /// Reads file and attempts to parse it's contents into `R` type. pub fn read_into<T, R, E>(path: T) -> impl Future<Output = Result<R>> where T: AsRef<Path> + Send + 'static, // TODO: Use `try_from::TryFrom` here too R: FromStr<Err = E>, Error: From<E>, { read_to_string(path).then(|try_content| match try_content { Ok(content) => future::ready(R::from_str(&content).map_err(Error::from)), Err(e) => future::err(e), }) } /// Returns stream of lines yielded from file with `path` path. pub fn read_lines<T>(path: T) -> impl TryStream<Ok = String, Error = Error> where T: AsRef<Path> + Send + 'static, { future::ready(fs::File::open(path)) .map_err(Error::from) .map_ok(|file| { // TODO: It is using a "sync" API // but since it is used only at Linux and only for in-memory located // filesystems, it should not be a big problem, since that kind of IO // will not block so much. // // In any way, this thing should be refactored when `runtime` crate // will have the proper async FS I/O support. let reader = io::BufReader::new(file); stream::iter(reader.lines()).map_err(Error::from) }) .try_flatten_stream() } /// Returns stream which reads lines from file and tries to parse them with help of `FromStr` trait. pub fn read_lines_into<T, R, E>(path: T) -> impl TryStream<Ok = R, Error = Error> where T: AsRef<Path> + Send + 'static, R: FromStr<Err = E>, Error: From<E>, { read_lines(path).into_stream().then(|result| { let res = result.and_then(|line| R::from_str(&line).map_err(Error::from)); future::ready(res) }) } /// Returns future which tries to read the first line from file. pub fn read_first_line<T>(path: T) -> impl TryFuture<Ok = String, Error = Error> where T: AsRef<Path> + Send + 'static, { // TODO: Looks dumb read_lines(path) .into_stream() .into_future() .map(|(try_line, _)| match try_line { Some(Ok(line)) => Ok(line), Some(Err(e)) => Err(e), None => Err(Error::missing_entity("line")), }) } /// Returns stream of files and directories contained in the `T` directory. pub fn read_dir<T>(path: T) -> impl TryStream<Ok = fs::DirEntry, Error = Error> where T: AsRef<Path> + Send + 'static, { // TODO: It is using a "sync" API // but since it is used only at Linux and only for in-memory located // filesystems, it should not be a big problem, since that kind of IO // will not block so much. // // In any way, this thing should be refactored when `runtime` crate // will have the proper async FS I/O support. future::ready(fs::read_dir(path)) .map_err(Error::from) .map_ok(|iter| stream::iter(iter).map_err(Error::from)) .try_flatten_stream() }