mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-07 08:08:50 +00:00
os/os2: recursive directory walker, expose errors in read_directory, file clone
Adds a directory walker, a method of exposing and retrieving errors from the existing read directory iterator, allows reusing of the existing read directory iterator, and adds a file clone procedure
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
package os2
|
||||
|
||||
import "core:container/queue"
|
||||
|
||||
/*
|
||||
A recursive directory walker.
|
||||
|
||||
Note that none of the fields should be accessed directly.
|
||||
*/
|
||||
Walker :: struct {
|
||||
todo: queue.Queue(string),
|
||||
skip_dir: bool,
|
||||
err: struct {
|
||||
path: [dynamic]byte,
|
||||
err: Error,
|
||||
},
|
||||
iter: Read_Directory_Iterator,
|
||||
}
|
||||
|
||||
walker_init_path :: proc(w: ^Walker, path: string) {
|
||||
cloned_path, err := clone_string(path, file_allocator())
|
||||
if err != nil {
|
||||
walker_set_error(w, path, err)
|
||||
return
|
||||
}
|
||||
|
||||
walker_clear(w)
|
||||
|
||||
if _, err = queue.push(&w.todo, cloned_path); err != nil {
|
||||
walker_set_error(w, cloned_path, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
walker_init_file :: proc(w: ^Walker, f: ^File) {
|
||||
handle, err := clone(f)
|
||||
if err != nil {
|
||||
path, _ := clone_string(name(f), file_allocator())
|
||||
walker_set_error(w, path, err)
|
||||
return
|
||||
}
|
||||
|
||||
walker_clear(w)
|
||||
|
||||
read_directory_iterator_init(&w.iter, handle)
|
||||
}
|
||||
|
||||
/*
|
||||
Initializes a walker, either using a path or a file pointer to a directory the walker will start at.
|
||||
|
||||
You are allowed to repeatedly call this to reuse it for later walks.
|
||||
|
||||
For an example on how to use the walker, see `walker_walk`.
|
||||
*/
|
||||
walker_init :: proc {
|
||||
walker_init_path,
|
||||
walker_init_file,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
walker_create_path :: proc(path: string) -> (w: Walker) {
|
||||
walker_init_path(&w, path)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
walker_create_file :: proc(f: ^File) -> (w: Walker) {
|
||||
walker_init_file(&w, f)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Creates a walker, either using a path or a file pointer to a directory the walker will start at.
|
||||
|
||||
For an example on how to use the walker, see `walker_walk`.
|
||||
*/
|
||||
walker_create :: proc {
|
||||
walker_create_path,
|
||||
walker_create_file,
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the last error that occurred during the walker's operations.
|
||||
|
||||
Can be called while iterating, or only at the end to check if anything failed.
|
||||
*/
|
||||
@(require_results)
|
||||
walker_error :: proc(w: ^Walker) -> (path: string, err: Error) {
|
||||
return string(w.err.path[:]), w.err.err
|
||||
}
|
||||
|
||||
@(private)
|
||||
walker_set_error :: proc(w: ^Walker, path: string, err: Error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
resize(&w.err.path, len(path))
|
||||
copy(w.err.path[:], path)
|
||||
|
||||
w.err.err = err
|
||||
}
|
||||
|
||||
@(private)
|
||||
walker_clear :: proc(w: ^Walker) {
|
||||
w.iter.f = nil
|
||||
w.skip_dir = false
|
||||
|
||||
w.err.path.allocator = file_allocator()
|
||||
clear(&w.err.path)
|
||||
|
||||
w.todo.data.allocator = file_allocator()
|
||||
for path in queue.pop_front_safe(&w.todo) {
|
||||
delete(path, file_allocator())
|
||||
}
|
||||
}
|
||||
|
||||
walker_destroy :: proc(w: ^Walker) {
|
||||
walker_clear(w)
|
||||
queue.destroy(&w.todo)
|
||||
delete(w.err.path)
|
||||
read_directory_iterator_destroy(&w.iter)
|
||||
}
|
||||
|
||||
// Marks the current directory to be skipped (not entered into).
|
||||
walker_skip_dir :: proc(w: ^Walker) {
|
||||
w.skip_dir = true
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the next file info in the iterator, files are iterated in breadth-first order.
|
||||
|
||||
If an error occurred opening a directory, you may get zero'd info struct and
|
||||
`walker_error` will return the error.
|
||||
|
||||
Example:
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
import os "core:os/os2"
|
||||
|
||||
main :: proc() {
|
||||
w := os.walker_create("core")
|
||||
defer os.walker_destroy(&w)
|
||||
|
||||
for info in os.walker_walk(&w) {
|
||||
// Optionally break on the first error:
|
||||
// _ = walker_error(&w) or_break
|
||||
|
||||
// Or, handle error as we go:
|
||||
if path, err := os.walker_error(&w); err != nil {
|
||||
fmt.eprintfln("failed walking %s: %s", path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Or, do not handle errors during iteration, and just check the error at the end.
|
||||
|
||||
|
||||
|
||||
// Skip a directory:
|
||||
if strings.has_suffix(info.fullpath, ".git") {
|
||||
os.walker_skip_dir(&w)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.printfln("%#v", info)
|
||||
}
|
||||
|
||||
// Handle error if one happened during iteration at the end:
|
||||
if path, err := os.walker_error(&w); err != nil {
|
||||
fmt.eprintfln("failed walking %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
*/
|
||||
@(require_results)
|
||||
walker_walk :: proc(w: ^Walker) -> (fi: File_Info, ok: bool) {
|
||||
if w.skip_dir {
|
||||
w.skip_dir = false
|
||||
if skip, sok := queue.pop_back_safe(&w.todo); sok {
|
||||
delete(skip, file_allocator())
|
||||
}
|
||||
}
|
||||
|
||||
if w.iter.f == nil {
|
||||
if queue.len(w.todo) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
next := queue.pop_front(&w.todo)
|
||||
|
||||
handle, err := open(next)
|
||||
if err != nil {
|
||||
walker_set_error(w, next, err)
|
||||
return {}, true
|
||||
}
|
||||
|
||||
read_directory_iterator_init(&w.iter, handle)
|
||||
|
||||
delete(next, file_allocator())
|
||||
}
|
||||
|
||||
info, _, iter_ok := read_directory_iterator(&w.iter)
|
||||
|
||||
if path, err := read_directory_iterator_error(&w.iter); err != nil {
|
||||
walker_set_error(w, path, err)
|
||||
}
|
||||
|
||||
if !iter_ok {
|
||||
close(w.iter.f)
|
||||
w.iter.f = nil
|
||||
return walker_walk(w)
|
||||
}
|
||||
|
||||
if info.type == .Directory {
|
||||
path, err := clone_string(info.fullpath, file_allocator())
|
||||
if err != nil {
|
||||
walker_set_error(w, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = queue.push_back(&w.todo, path)
|
||||
if err != nil {
|
||||
walker_set_error(w, path, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return info, iter_ok
|
||||
}
|
||||
Reference in New Issue
Block a user