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

use std::{
    env,
    fs,
    path::{Path, PathBuf},
};

use super::{
    iff,
    util_os,
    util_io,
    new_box_ioerror,
    XResult,
};

pub fn locate_file(files: &[String]) -> Option<PathBuf> {
    for f in files {
        match PathBuf::from(&resolve_file_path(f)) {
            pb if pb.is_file() => return Some(pb),
            _ => (),
        }
    }
    None
}

pub fn get_home_str() -> Option<String> {
    iff!(util_os::is_macos_or_linux(), env::var("HOME").ok(), None)
}

pub fn resolve_file_path(path: &str) -> String {
    let home_path = match get_home_str() {
        Some(p) => p, None => return path.to_owned(),
    };
    match path {
        "~" => home_path,
        p if p.starts_with("~/") => home_path + &path.chars().skip(1).collect::<String>(),
        p => p.to_owned(),
    }
}

pub fn get_home_path() -> Option<PathBuf> {
    Some(PathBuf::from(get_home_str()?))
}

pub fn get_absolute_path(path: &str) -> Option<PathBuf> {
    match path {
        "~" => Some(PathBuf::from(get_home_str()?)),
        path if path.starts_with("~/")  => Some(PathBuf::from(&format!("{}/{}", get_home_str()?, &path[2..]))),
        path => fs::canonicalize(path).ok(),
    }
}

pub fn read_file_content(file: &str) -> XResult<String> {
    match get_absolute_path(file) {
        None => Err(new_box_ioerror(&format!("File not found: {}", file))),
        Some(p) => util_io::read_to_string(&mut fs::File::open(p)?),
    }
}

pub fn is_symlink(path: &Path) -> bool {
    path.symlink_metadata().map(|meta| meta.file_type().is_symlink()).unwrap_or(false)
}

pub fn walk_dir<FError, FProcess, FFilter>(dir: &Path,
        func_walk_error: &FError,
        func_process_file: &FProcess,
        func_filter_dir: &FFilter) -> XResult<()>
        where FError: Fn(&Path, Box<dyn std::error::Error>) -> (),
              FProcess: Fn(&Path) -> (),
              FFilter: Fn(&Path) -> bool {
    walk_dir_with_depth_check(&mut 0u32, dir, func_walk_error, func_process_file, func_filter_dir)
}

fn walk_dir_with_depth_check<FError, FProcess, FFilter>(depth: &mut u32, dir: &Path,
        func_walk_error: &FError,
        func_process_file: &FProcess,
        func_filter_dir: &FFilter) -> XResult<()>
        where FError: Fn(&Path, Box<dyn std::error::Error>) -> (),
              FProcess: Fn(&Path) -> (),
              FFilter: Fn(&Path) -> bool {
    if *depth > 100u32 {
        return Err(new_box_ioerror(&format!("Depth exceed, depth: {}, path: {:?}", *depth, dir)));
    }
    let read_dir = match dir.read_dir() {
        Ok(rd) => rd, Err(err) => {
            func_walk_error(&dir, Box::new(err));
            return Ok(());
        },
    };
    for dir_entry_item in read_dir {
        let dir_entry = match dir_entry_item {
            Ok(item) => item, Err(err) => {
                func_walk_error(&dir, Box::new(err));
                continue; // Ok?
            },
        };

        let path_buf = dir_entry.path();
        let sub_dir = path_buf.as_path();
        if sub_dir.is_file() {
            func_process_file(&sub_dir);
        } else if sub_dir.is_dir() && func_filter_dir(&sub_dir) {
            *depth += 1;
            if let Err(err) = walk_dir_with_depth_check(depth, &sub_dir, func_walk_error, func_process_file, func_filter_dir) {
                func_walk_error(&sub_dir, err);
            }
            *depth -= 1;
        } // should process else ? not file, dir
    }
    Ok(())
}