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
//! The transactional filesystem interface.

use std::fmt;
use std::fs::Metadata;
use std::io;
use std::path::PathBuf;

use futures::TryFutureExt;
use log::debug;
use tokio::fs;

use tc_error::*;
use tcgeneric::{label, Label, PathSegment};

mod cache;
mod dir;
mod file;

pub use cache::*;
pub use dir::*;
pub use file::*;

const VERSION: Label = label(".version");

type DirContents = Vec<(fs::DirEntry, Metadata)>;

pub async fn load(cache: Cache, path: PathBuf) -> TCResult<Dir> {
    let entries = dir_contents(&path).await?;
    dir::Dir::load(cache, path, entries).await
}

async fn create_parent(path: &PathBuf) -> TCResult<()> {
    if let Some(parent) = path.parent() {
        if !parent.exists() {
            tokio::fs::create_dir_all(parent)
                .map_err(|e| io_err(e, parent))
                .await?;
        }
    }

    Ok(())
}

async fn dir_contents(dir_path: &PathBuf) -> TCResult<Vec<(fs::DirEntry, Metadata)>> {
    let mut contents = vec![];
    let mut handles = fs::read_dir(dir_path)
        .map_err(|e| io_err(e, dir_path))
        .await?;

    while let Some(handle) = handles
        .next_entry()
        .map_err(|e| io_err(e, dir_path))
        .await?
    {
        if handle
            .path()
            .file_name()
            .expect("file name")
            .to_str()
            .expect("file name")
            .starts_with('.')
        {
            debug!("skip hidden file {:?}", handle.path());
            continue;
        }

        let meta = handle
            .metadata()
            .map_err(|e| io_err(e, handle.path()))
            .await?;

        contents.push((handle, meta));
    }

    Ok(contents)
}

#[inline]
fn file_ext(path: &'_ PathBuf) -> Option<&'_ str> {
    path.extension().and_then(|ext| ext.to_str())
}

fn file_name(handle: &fs::DirEntry) -> TCResult<PathSegment> {
    if let Some(name) = handle.path().file_stem() {
        let name = name.to_str().ok_or_else(|| {
            TCError::internal(format!("invalid file name at {:?}", handle.path()))
        })?;

        name.parse()
    } else {
        Err(TCError::internal("Cannot load file with no name!"))
    }
}

#[inline]
fn fs_path(mount_point: &PathBuf, name: &PathSegment) -> PathBuf {
    let mut path = mount_point.clone();
    path.push(name.to_string());
    path
}

fn io_err<I: fmt::Debug + Send>(err: io::Error, info: I) -> TCError {
    match err.kind() {
        io::ErrorKind::NotFound => {
            TCError::internal(format!("host filesystem has no such entry {:?}", info))
        }
        io::ErrorKind::PermissionDenied => TCError::internal(format!(
            "Tinychain does not have permission to access the host filesystem: {:?}",
            info
        )),
        other => TCError::internal(format!("host filesystem error: {:?}: {}", other, err)),
    }
}