///|
pub(all) enum QuerySort {
SizeDescending
SizeAscending
PathAscending
ModifiedNewest
ModifiedOldest
} derive(Eq, Debug, ToJson, FromJson)
///|
pub(all) struct SnapshotQuery {
path_pattern : String
extensions : Array[String]
categories : Array[FileCategory]
kinds : Array[EntryKind]
minimum_size : Int64
maximum_size : Int64
modified_after : Int64
modified_before : Int64
sort : QuerySort
limit : Int
} derive(Eq, Debug, ToJson, FromJson)
///|
pub fn SnapshotQuery::default() -> SnapshotQuery {
SnapshotQuery::{
path_pattern: "*",
extensions: [],
categories: [],
kinds: [Regular],
minimum_size: 0L,
maximum_size: -1L,
modified_after: -1L,
modified_before: -1L,
sort: SizeDescending,
limit: 100,
}
}
///|
pub fn query_sort_from_name(name : String) -> QuerySort raise {
match name.to_lower() {
"size-desc" => SizeDescending
"size-asc" => SizeAscending
"path" => PathAscending
"newest" => ModifiedNewest
"oldest" => ModifiedOldest
value => raise Failure("Unknown query sort: " + value)
}
}
///|
fn normalized_query_extensions(extensions : Array[String]) -> Array[String] {
extensions.map(fn(extension) {
let lower = extension.to_lower()
if lower.has_prefix(".") {
lower[1:].to_owned()
} else {
lower
}
})
}
///|
fn query_has_kind(kinds : Array[EntryKind], kind : EntryKind) -> Bool {
kinds.is_empty() || kinds.any(fn(candidate) { candidate == kind })
}
///|
fn query_has_category(
categories : Array[FileCategory],
category : FileCategory,
) -> Bool {
categories.is_empty() ||
categories.any(fn(candidate) { candidate == category })
}
///|
pub fn entry_matches_query(entry : Entry, query : SnapshotQuery) -> Bool {
if !query_has_kind(query.kinds, entry.kind) {
return false
}
let pattern = normalize_relative_path(query.path_pattern)
if pattern != "" &&
pattern != "*" &&
!wildcard_match(pattern, entry.path) &&
!wildcard_match(pattern, entry.name()) {
return false
}
let extensions = normalized_query_extensions(query.extensions)
if !extensions.is_empty() &&
!extensions.any(fn(extension) { extension == entry.extension() }) {
return false
}
if entry.is_file() &&
!query_has_category(query.categories, file_category(entry)) {
return false
}
if entry.size < query.minimum_size {
return false
}
if query.maximum_size >= 0L && entry.size > query.maximum_size {
return false
}
if query.modified_after >= 0L && entry.modified_at <= query.modified_after {
return false
}
if query.modified_before >= 0L && entry.modified_at >= query.modified_before {
return false
}
true
}
///|
fn compare_query_entries(a : Entry, b : Entry, sort : QuerySort) -> Int {
match sort {
SizeDescending =>
if a.size > b.size {
-1
} else if a.size < b.size {
1
} else if a.path < b.path {
-1
} else {
1
}
SizeAscending =>
if a.size < b.size {
-1
} else if a.size > b.size {
1
} else if a.path < b.path {
-1
} else {
1
}
PathAscending =>
if a.path < b.path {
-1
} else if a.path > b.path {
1
} else {
0
}
ModifiedNewest =>
if a.modified_at > b.modified_at {
-1
} else if a.modified_at < b.modified_at {
1
} else if a.path < b.path {
-1
} else {
1
}
ModifiedOldest =>
if a.modified_at < b.modified_at {
-1
} else if a.modified_at > b.modified_at {
1
} else if a.path < b.path {
-1
} else {
1
}
}
}
///|
pub fn query_snapshot(
snapshot : Snapshot,
query : SnapshotQuery,
) -> Array[Entry] {
let result = snapshot.entries.filter(fn(entry) {
entry_matches_query(entry, query)
})
result.sort_by(fn(a, b) { compare_query_entries(a, b, query.sort) })
let limit = if query.limit < 0 { 0 } else { query.limit }
if result.length() > limit {
result[:limit].to_owned()
} else {
result
}
}
///|
pub fn query_total_size(entries : Array[Entry]) -> Int64 {
let mut total = 0L
for entry in entries {
if entry.is_file() {
total += entry.size
}
}
total
}
///|
pub fn render_query_results(entries : Array[Entry]) -> String {
let lines : Array[String] = [
"MoonDisk snapshot query",
"Matches: " + entries.length().to_string(),
"Matched size: " + format_bytes(query_total_size(entries)),
"",
]
for entry in entries {
lines.push(
pad_left(format_bytes(entry.size), 12) +
" " +
pad_right(entry_kind_name(entry.kind), 9) +
" " +
entry.path,
)
}
lines.join("\n")
}