///|
fn relative_to_root(root : String, full : String) -> String {
let normalized_root = root.replace_all(old="\\", new="/")
let normalized_full = full.replace_all(old="\\", new="/")
if normalized_full == normalized_root {
""
} else if normalized_full.has_prefix(normalized_root + "/") {
normalized_full[normalized_root.length() + 1:].to_owned()
} else {
normalize_relative_path(normalized_full)
}
}
///|
fn scanner_should_exclude(relative : String, options : ScanOptions) -> Bool {
path_depth(relative) > options.max_depth ||
(!options.include_hidden && is_hidden_path(relative)) ||
matches_any_pattern(relative, options.ignore_patterns)
}
///|
fn scanner_kind(kind : @fs.FileKind) -> EntryKind {
match kind {
Regular => EntryKind::Regular
Directory => EntryKind::Directory
SymLink => EntryKind::Symlink
_ => EntryKind::Other
}
}
///|
/// Scan a real directory using MoonBit's native asynchronous filesystem API.
/// Symbolic links are recorded but not followed unless the caller explicitly opts in.
pub async fn scan_directory(
root : String,
created_at : Int64,
options? : ScanOptions = ScanOptions::default(),
) -> Snapshot {
let canonical = @fs.realpath(root)
let entries : Array[Entry] = []
let errors : Array[ScanError] = []
let mut stopped = false
@fs.walk(
canonical,
max_concurrency=options.max_concurrency,
allow_failure=true,
exclude=fn(path) {
let relative = relative_to_root(canonical, path)
relative != "" && scanner_should_exclude(relative, options)
},
async fn(directory, names) {
if stopped {
return
}
for name in names {
if entries.length() >= options.max_entries {
stopped = true
errors.push(ScanError::{
path: relative_to_root(canonical, directory),
operation: "scan",
message: "The configured maximum entry count was reached.",
})
return
}
let full = directory + "/" + name
let relative = relative_to_root(canonical, full)
if scanner_should_exclude(relative, options) {
continue
}
try {
let kind = @fs.kind(full, follow_symlink=options.follow_symlinks)
let entry_kind = scanner_kind(kind)
let (seconds, _) = @fs.mtime(
full,
follow_symlink=options.follow_symlinks,
)
let size = if entry_kind == EntryKind::Regular {
let file = @fs.open(full, mode=ReadOnly)
defer file.close()
file.size()
} else {
0L
}
entries.push(Entry::new(relative, entry_kind, size, seconds))
} catch {
error =>
errors.push(ScanError::{
path: relative,
operation: "metadata",
message: @debug.to_string(error),
})
}
}
},
)
entries.sort_by(fn(a, b) {
if a.path < b.path {
-1
} else if a.path > b.path {
1
} else {
0
}
})
Snapshot::new(canonical, created_at, entries, errors~)
}