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()
}