#File System API (@moonbitlang/async/fs)

    Asynchronous file system operations for MoonBit. This package provides comprehensive APIs for working with files, directories, and file metadata.

    #Table of Contents

    #File Operations

    #Opening and Creating Files

    The open function provides flexible file opening with various modes and options:

    ///|
    #cfg(target="native")
    async test "open file for reading" {
    let test_file = "_build/test_open_read.txt"
    @fs.write_file(test_file, b"Hello, MoonBit!", create_mode=CreateOrTruncate)
    let file = @fs.open(test_file, mode=ReadOnly)
    defer file.close()
    let content = file.read_all().text()
    @fs.remove(test_file)
    inspect(content, content="Hello, MoonBit!")
    }

    ///|
    #cfg(target="native")
    async test "open file for writing" {
    let test_file = "_build/test_open_write.txt"
    let file = @fs.open(test_file, mode=WriteOnly, create_mode=CreateOrTruncate)
    defer file.close()
    file.write(b"Hello, World!")
    @fs.remove(test_file)
    }

    ///|
    #cfg(target="native")
    async test "open with append mode" {
    let test_file = "_build/test_append.txt"
    // Create initial file
    @fs.write_file(test_file, b"First line\n", create_mode=CreateOrTruncate)

    // Append to existing file
    let file = @fs.open(test_file, mode=WriteOnly, append=true)
    file.write(b"Second line\n")
    file.close()
    let content = @fs.read_file(test_file).text()
    @fs.remove(test_file)
    inspect(content, content="First line\nSecond line\n")
    }

    The create function is a convenience wrapper for creating new files:

    ///|
    #cfg(target="native")
    async test "create new file" {
    let test_file = "_build/test_create.txt"
    let file = @fs.create(test_file)
    file.write(b"New file content")
    file.close()
    let exists = @fs.exists(test_file)
    @fs.remove(test_file)
    inspect(exists, content="true")
    }

    #Reading Files

    Read entire files or read data in chunks:

    ///|
    #cfg(target="native")
    async test "read_file - read entire file" {
    let test_file = "_build/test_read_file.txt"
    @fs.write_file(test_file, b"Hello, MoonBit!", create_mode=CreateOrTruncate)
    let content = @fs.read_file(test_file)
    @fs.remove(test_file)
    inspect(content.text(), content="Hello, MoonBit!")
    }

    ///|
    #cfg(target="native")
    async test "read in chunks using File" {
    let test_file = "_build/test_chunk_read.txt"
    @fs.write_file(test_file, b"0123456789", create_mode=CreateOrTruncate)
    let file = @fs.open(test_file, mode=ReadOnly)
    defer file.close()
    let buf = FixedArray::make(5, b'0')
    let n = file.read(buf)
    @fs.remove(test_file)
    inspect(n, content="5")
    inspect(@utf8.decode(buf.unsafe_reinterpret_as_bytes()), content="01234")
    }

    ///|
    #cfg(target="native")
    async test "read_all from file" {
    let test_file = "_build/test_read_all.txt"
    @fs.write_file(test_file, b"Complete content", create_mode=CreateOrTruncate)
    let file = @fs.open(test_file, mode=ReadOnly)
    let data = file.read_all()
    file.close()
    @fs.remove(test_file)
    inspect(data.text(), content="Complete content")
    }

    ///|
    #cfg(target="native")
    async test "read_exactly specific bytes" {
    let test_file = "_build/test_read_exact.txt"
    @fs.write_file(test_file, b"1234567890", create_mode=CreateOrTruncate)
    let file = @fs.open(test_file, mode=ReadOnly)
    let bytes = file.read_exactly(5)
    file.close()
    @fs.remove(test_file)
    inspect(@utf8.decode(bytes), content="12345")
    }

    #Writing Files

    Write data to files using various methods:

    ///|
    #cfg(target="native")
    async test "write_file - write entire file" {
    let test_file = "_build/test_write.txt"
    @fs.write_file(test_file, b"File content", create_mode=CreateOrTruncate)
    let content = @fs.read_file(test_file).text()
    @fs.remove(test_file)
    inspect(content, content="File content")
    }

    ///|
    #cfg(target="native")
    async test "write with sync modes" {
    let test_file = "_build/test_sync.txt"
    // Write with data sync
    @fs.write_file(
    test_file,
    b"Synced data",
    sync=Data,
    create_mode=CreateOrTruncate,
    )
    let content = @fs.read_file(test_file).text()
    @fs.remove(test_file)
    inspect(content, content="Synced data")
    }

    ///|
    #cfg(target="native")
    async test "write using File methods" {
    let test_file = "_build/test_file_write.txt"
    let file = @fs.create(test_file)
    file.write(b"Line 1\n")
    file.write(b"Line 2\n")
    file.close()
    let content = @fs.read_file(test_file).text()
    @fs.remove(test_file)
    inspect(content, content="Line 1\nLine 2\n")
    }

    ///|
    #cfg(target="native")
    async test "write_once for single write operation" {
    let test_file = "_build/test_write_once.txt"
    let file = @fs.create(test_file)
    let data : Bytes = b"Single write"
    let written = file.write_once(data, offset=0, len=data.length())
    file.close()
    @fs.remove(test_file)
    inspect(written, content="12")
    }

    #Random access on files

    Read and write file from specified position:

    ///|
    #cfg(target="native")
    async test "read at specific position" {
    let test_file = "_build/read_at_test.txt"
    @fs.write_file(test_file, b"0123456789", create_mode=CreateOrTruncate)
    {
    let file = @fs.open(test_file, mode=ReadOnly)
    defer file.close()

    // read 3 bytes at position 5
    json_inspect(file.read_exactly_at(3, position=5), content="567")

    // use `read_at` to handle EOF robustly
    let buf = FixedArray::make(10, b'\x00')
    let n = file.read_at(buf, position=5)
    inspect(n, content="5")
    json_inspect(buf.unsafe_reinterpret_as_bytes()[:n], content="56789")
    }
    @fs.remove(test_file)
    }

    ///|
    #cfg(target="native")
    async test "write at specific position" {
    let test_file = "_build/write_at_test.txt"
    {
    let file = @fs.open(test_file, mode=WriteOnly, create_mode=CreateOrTruncate)
    defer file.close()
    file.write("abcdef")
    file.write_at(b"CD", position=2)
    }

    // read 3 bytes at position 5
    inspect(@fs.read_file(test_file).text(), content="abCDef")
    @fs.remove(test_file)
    }

    ///|
    #cfg(target="native")
    async test "size - get file size" {
    let test_file = "_build/test_size.txt"
    @fs.write_file(test_file, b"Hello", create_mode=CreateOrTruncate)
    let file = @fs.open(test_file, mode=ReadOnly)
    let size = file.size()
    file.close()
    @fs.remove(test_file)
    inspect(size, content="5")
    }

    Some important notes when using read_at and write_at:

    • only seekable files (i.e. regular files or block devices) support read_at and write_at. Calling read_at and write_at on unsupported file types result in error
    • read_at and write_at does not modify the cursor for reading/writing the file as a stream
    • read_at always read as much as possible. When its return value is smaller than requested length, it always indicates EOF

    #Directory Operations

    #Creating Directories

    ///|
    #cfg(target="native")
    async test "mkdir - create directory" {
    let dir_path = "_build/test_mkdir"
    @fs.mkdir(dir_path, permission=0o755)
    let exists = @fs.exists(dir_path)
    @fs.rmdir(dir_path)
    inspect(exists, content="true")
    }

    ///|
    #cfg(target="native")
    async test "mkdir - create with custom permissions" {
    let dir_path = "_build/test_mkdir_perm"
    @fs.mkdir(dir_path, permission=0o700)
    let kind = @fs.kind(dir_path)
    @fs.rmdir(dir_path)
    debug_inspect(kind, content="Directory")
    }

    #Reading Directories

    ///|
    #cfg(target="native")
    async test "readdir - read directory entries" {
    let dir_path = "_build/test_readdir"
    @fs.mkdir(dir_path, permission=0o755)
    @fs.write_file("\{dir_path}/test1.txt", b"", create_mode=CreateOrTruncate)
    @fs.write_file("\{dir_path}/test2.txt", b"", create_mode=CreateOrTruncate)
    let entries = @fs.readdir(
    dir_path,
    include_hidden=false,
    include_special=false,
    )
    @fs.remove("\{dir_path}/test1.txt")
    @fs.remove("\{dir_path}/test2.txt")
    @fs.rmdir(dir_path)
    inspect(entries.length(), content="2")
    }

    ///|
    #cfg(target="native")
    async test "readdir with sorting" {
    let dir_path = "_build/test_readdir_sort"
    @fs.mkdir(dir_path, permission=0o755)
    @fs.write_file("\{dir_path}/c.txt", b"", create_mode=CreateOrTruncate)
    @fs.write_file("\{dir_path}/a.txt", b"", create_mode=CreateOrTruncate)
    @fs.write_file("\{dir_path}/b.txt", b"", create_mode=CreateOrTruncate)
    let entries = @fs.readdir(dir_path, sort=true)
    @fs.remove("\{dir_path}/a.txt")
    @fs.remove("\{dir_path}/b.txt")
    @fs.remove("\{dir_path}/c.txt")
    @fs.rmdir(dir_path)
    guard entries.length() >= 3 else { println(@debug.to_string(entries)) }
    inspect(entries[0], content="a.txt")
    inspect(entries[1], content="b.txt")
    inspect(entries[2], content="c.txt")
    }

    ///|
    #cfg(target="native")
    async test "opendir and Directory::read_all" {
    let dir_path = "_build/test_opendir"
    @fs.mkdir(dir_path, permission=0o755)
    @fs.write_file("\{dir_path}/file1.txt", b"test", create_mode=CreateOrTruncate)
    @fs.write_file("\{dir_path}/file2.txt", b"test", create_mode=CreateOrTruncate)
    let dir = @fs.opendir(dir_path)
    let entries = dir.read_all(include_hidden=false, include_special=false)
    dir.close()
    @fs.remove("\{dir_path}/file1.txt")
    @fs.remove("\{dir_path}/file2.txt")
    @fs.rmdir(dir_path)
    inspect(entries.length(), content="2")
    inspect(entries.contains("file1.txt"), content="true")
    inspect(entries.contains("file2.txt"), content="true")
    }

    #Walking Directory Trees

    Recursively traverse directory hierarchies:

    ///|
    #cfg(target="native")
    async test "walk directory tree" {
    let base = "_build/test_walk"
    @fs.mkdir(base)
    @fs.mkdir("\{base}/sub1")
    @fs.mkdir("\{base}/sub2")
    @fs.write_file("\{base}/file.txt", b"", create_mode=CreateOrTruncate)
    @fs.write_file("\{base}/sub1/file1.txt", b"", create_mode=CreateOrTruncate)
    let visited : Ref[Int] = Ref(0)
    @fs.walk(base, fn(_path, _files) { visited.val = visited.val + 1 })
    @fs.remove("\{base}/file.txt")
    @fs.remove("\{base}/sub1/file1.txt")
    @fs.rmdir("\{base}/sub1")
    @fs.rmdir("\{base}/sub2")
    @fs.rmdir(base)
    inspect(visited.val >= 3, content="true")
    }

    ///|
    #cfg(target="native")
    async test "walk with max_concurrency" {
    let base = "_build/test_walk_concurrency"
    @fs.mkdir(base, permission=0o755)
    @fs.mkdir("\{base}/dir1", permission=0o755)
    @fs.mkdir("\{base}/dir2", permission=0o755)
    let count : Ref[Int] = Ref(0)
    @fs.walk(
    base,
    fn(_path, _files) { count.val = count.val + 1 },
    max_concurrency=1,
    )
    @fs.rmdir("\{base}/dir1")
    @fs.rmdir("\{base}/dir2")
    @fs.rmdir(base)
    inspect(count.val >= 3, content="true")
    }

    #Removing Directories

    ///|
    #cfg(target="native")
    async test "rmdir - remove empty directory" {
    let dir_path = "_build/test_rmdir"
    @fs.mkdir(dir_path)
    @fs.rmdir(dir_path)
    let exists = @fs.exists(dir_path)
    inspect(exists, content="false")
    }

    ///|
    #cfg(target="native")
    async test "rmdir recursive - remove directory tree" {
    let base = "_build/test_rmdir_recursive"
    @fs.mkdir(base)
    @fs.mkdir("\{base}/subdir")
    @fs.write_file("\{base}/file.txt", b"test", create_mode=CreateOrTruncate)
    @fs.write_file(
    "\{base}/subdir/nested.txt",
    b"test",
    create_mode=CreateOrTruncate,
    )
    @fs.rmdir(base, recursive=true)
    let exists = @fs.exists(base)
    inspect(exists, content="false")
    }

    #File Metadata

    #File Kind

    Determine the type of file system entries:

    ///|
    #cfg(target="native")
    async test "kind - regular file" {
    let test_file = "_build/test_kind_file.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateOrTruncate)
    let kind = @fs.kind(test_file)
    @fs.remove(test_file)
    debug_inspect(kind, content="Regular")
    }

    ///|
    #cfg(target="native")
    async test "kind - directory" {
    let dir_path = "_build/test_kind_dir"
    @fs.mkdir(dir_path, permission=0o755)
    let kind = @fs.kind(dir_path)
    @fs.rmdir(dir_path)
    debug_inspect(kind, content="Directory")
    }

    ///|
    #cfg(target="native")
    async test "File::kind method" {
    let test_file = "_build/test_file_kind.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateOrTruncate)
    let file = @fs.open(test_file, mode=ReadOnly)
    let kind = file.kind()
    file.close()
    @fs.remove(test_file)
    debug_inspect(kind, content="Regular")
    }

    #Timestamps

    Access file timestamps (atime, mtime, ctime):

    ///|
    #cfg(all(target="native", not(platform="windows")))
    async test "atime - access time" {
    let test_file = "_build/test_atime.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateOrTruncate)
    let (seconds, nanoseconds) = @fs.atime(test_file)
    @fs.remove(test_file)
    inspect(seconds > 0, content="true")
    inspect(nanoseconds >= 0, content="true")
    }

    ///|
    #cfg(target="native")
    async test "mtime - modification time" {
    let test_file = "_build/test_mtime.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateOrTruncate)
    let (seconds, nanoseconds) = @fs.mtime(test_file)
    @fs.remove(test_file)
    inspect(seconds > 0, content="true")
    inspect(nanoseconds >= 0, content="true")
    }

    ///|
    #cfg(target="native")
    async test "ctime - status change time" {
    let test_file = "_build/test_ctime.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateOrTruncate)
    let (seconds, nanoseconds) = @fs.ctime(test_file)
    @fs.remove(test_file)
    inspect(seconds > 0, content="true")
    inspect(nanoseconds >= 0, content="true")
    }

    ///|
    #cfg(target="native")
    async test "File timestamp methods" {
    let test_file = "_build/test_file_times.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateOrTruncate)
    let file = @fs.open(test_file, mode=ReadOnly)
    let (atime_s, _) = file.atime()
    let (mtime_s, _) = file.mtime()
    let (ctime_s, _) = file.ctime()
    file.close()
    @fs.remove(test_file)
    inspect(atime_s > 0, content="true")
    inspect(mtime_s > 0, content="true")
    inspect(ctime_s > 0, content="true")
    }

    #File Permissions

    Check file access permissions:

    ///|
    #cfg(target="native")
    async test "exists - check file existence" {
    let test_file = "_build/test_exists.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateOrTruncate)
    let exists = @fs.exists(test_file)
    @fs.remove(test_file)
    inspect(exists, content="true")
    }

    ///|
    #cfg(target="native")
    async test "exists - non-existent file" {
    let exists = @fs.exists("nonexistent_file_xyz.txt")
    inspect(exists, content="false")
    }

    ///|
    #cfg(target="native")
    async test "can_read - check read permission" {
    let test_file = "_build/test_can_read.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateOrTruncate)
    let can_read = @fs.can_read(test_file)
    @fs.remove(test_file)
    inspect(can_read, content="true")
    }

    ///|
    #cfg(target="native")
    async test "can_write - check write permission" {
    let test_file = "_build/test_can_write.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateOrTruncate)
    let can_write = @fs.can_write(test_file)
    @fs.remove(test_file)
    inspect(can_write, content="true")
    }

    ///|
    #cfg(target="native")
    async test "can_execute - check execute permission" {
    let test_file = "_build/test_can_execute.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateNew, permission=0o755)
    let can_execute = @fs.can_execute(test_file)
    @fs.remove(test_file)
    inspect(can_execute, content="true")
    }

    #Path Operations

    ///|
    #cfg(target="native")
    async test "realpath - resolve absolute path" {
    let test_dir = "_build/test_realpath"
    @fs.mkdir(test_dir)
    let real_path = @fs.realpath(test_dir)
    @fs.rmdir(test_dir)
    guard @env.current_dir() is Some(cwd)
    assert_true(real_path.has_prefix(cwd))
    inspect(
    // replace `\\` with `/` for Windows
    real_path[cwd.length():].replace_all(old="\\", new="/"),
    content="/_build/test_realpath",
    )
    }

    #File Removal

    ///|
    #cfg(target="native")
    async test "remove - delete file" {
    let test_file = "_build/test_remove.txt"
    @fs.write_file(test_file, b"test", create_mode=CreateOrTruncate)
    @fs.remove(test_file)
    let exists = @fs.exists(test_file)
    inspect(exists, content="false")
    }

    #Types Reference

    #FileKind

    Represents the type of a file system entry:

    • Regular - Regular file
    • Directory - Directory
    • SymLink - Symbolic link
    • Socket - Unix socket
    • Pipe - Named pipe (FIFO)
    • BlockDevice - Block device
    • CharDevice - Character device
    • Unknown - Unknown file type

    #Mode

    File opening mode:

    • ReadOnly - Open for reading only
    • WriteOnly - Open for writing only
    • ReadWrite - Open for both reading and writing

    #SyncMode

    Data synchronization mode:

    • NoSync - No synchronization (default)
    • Data - Sync data and essential metadata (like file size)
    • Full - Sync all data and metadata

    #SeekMode

    How to interpret seek offsets:

    • FromStart - Absolute offset from start of file
    • FromEnd - Offset relative to end of file
    • Relative - Offset relative to current position

    #File

    Main file handle type. Methods include:
    • fd() - Get file descriptor
    • close() - Close file
    • read() - Read data into buffer
    • read_all() - Read entire file content
    • read_exactly() - Read exact number of bytes
    • write() - Write data
    • write_once() - Single write operation
    • read_at() - Read file at specified position
    • read_exact_at() - Read exect number of bytes at specified position
    • write_at() - Write file at specified position
    • size() - Get file size
    • kind() - Get file kind
    • atime(), mtime(), ctime() - Get timestamps

    #Directory

    Directory handle for reading directory entries:
    • close() - Close directory
    • read_all() - Read all entries

    #Best Practices

    1. Always close files: Use defer file.close() after opening files
    2. Use appropriate sync modes: NoSync for performance, Data or Full for durability
    3. Handle permissions: Specify appropriate UNIX permissions (e.g., 0o644 for files, 0o755 for directories)
    4. Check existence: Use exists() before performing operations on files
    5. Use convenience functions: read_file() and write_file() are simpler for common cases
    6. Walk with limits: Use max_concurrency parameter when walking large directory trees

    #Error Handling

    All async file operations can raise errors. Use proper error handling:

    ///|
    #cfg(target="native")
    async test "error handling example" {
    let result = try! @fs.read_file("nonexistent.txt")
    inspect(result is Err(_), content="true")
    }

    For more examples and detailed usage, see the individual test files in this package.

    CreateMode

    pub(all) enum CreateMode {
    OpenExisting
    TruncateExisting
    OpenOrCreate
    CreateOrTruncate
    CreateNew
    }

    Specify how to handle file creation in @fs.open().

    • OpenExisting: open an existing file, fail if the file does not exist
    • TruncateExisting: open an existing file and truncate it, fail if the file does not exist
    • OpenOrCreate: if the file exists, open it without truncation. Otherwise create a new file
    • CreateOrTruncate: if the file exists, open it and truncate it. Otherwise create a new file
    • CreateNew: create a new file, fail if the file already exists

    Directory

    type Directory

    A directory in file system

    Directory::close

    fn Directory::close(self : Directory) -> Unit

    Directory::next

    async fn Directory::next(dir : Directory, include_hidden? : Bool, include_special? : Bool) -> DirectoryEntry?

    Fetch the next entry in the directory, including its name and some extra information, see DirectoryEntry. None indicates that the directory has no more files.

    • If include_special is true (false by default), . (current directory) and .. (parent directory) will be included too

    • If include_hidden is true (true by default), hidden files (on Unix: file name starts with ., on Windows: has the hidden attribute) will be included

    The file name in the returned directory entry is interpreted as UTF8-encoded on Unix-like systems.

    Directory::read_all

    async fn Directory::read_all(dir : Directory, include_hidden? : Bool, include_special? : Bool) -> Array[String]

    Read all entries in a directory.

    • If include_special is true (false by default), . (current directory) and .. (parent directory) will be included too

    • If include_hidden is true (true by default), hidden files (on Unix: file name starts with ., on Windows: has the hidden attribute) will be included

    The returned file names are interpreted as UTF8-encoded on Unix-like systems.

    DirectoryEntry

    pub struct DirectoryEntry {
    name : String
    is_dir : Bool
    // private fields
    }

    File

    type File

    impl Reader for File
    impl Writer for File

    File::as_dir

    fn File::as_dir(self : File) -> Directory raise

    Convert a file to directory. If the file is not a directory, an error will be raised.

    The ownership of the file will be transferred to as_dir. The input file will be automatically closed when the result directory object is closed. If as_dir fails, the input file will be closed automatically.

    File::atime

    async fn File::atime(self : File) -> (Int64, Int)

    Get the last access time of a file. The return value is a pair (s, ns), representing the time of s seconds + ns nanoseconds.

    File::close

    fn File::close(self : File) -> Unit

    File::ctime

    async fn File::ctime(self : File) -> (Int64, Int)

    Get the last status change time of a file. The return value is a pair (s, ns), representing the time of s seconds + ns nanoseconds.

    File::fd

    fn File::fd(self : File) -> Int

    File::kind

    fn File::kind(self : File) -> FileKind

    File::lock

    async fn File::lock(self : File, lock : Lock) -> Unit

    Lock a file, block until the lock is successfully acquired.

    The lock here is advisory, so normal read/write operations on the file are not affected by the lock, only another attempt to lock the same file may conflict with the lock here.

    The underlying syscall used to implement the lock is undefined. So the lock should only be used for synchronization between multiple process written using moonbitlang/async.

    File::mtime

    async fn File::mtime(self : File) -> (Int64, Int)

    Get the last modification time of a file. The return value is a pair (s, ns), representing the time of s seconds + ns nanoseconds.

    File::read_at

    async fn File::read_at(self : File, buf : FixedArray[Byte], position~ : Int64, offset? : Int, len? : Int) -> Int

    Read from a file at the offset given by position. Only seekable files (e.g. regular files and block devices) support this operation, calling read_at on unsupported file, such as pipe or socket, result in error.

    This function does not change the cursor for reading/writing the file as a stream (i.e. using API based on @io.Reader and @io.Writer), so it is safe to perform multiple read_at on the same file simutaneously.

    Up to len bytes of data will be read into buf[offset:]. The number of bytes actually read would be returned. The number of read bytes will be smaller than len only when EOF is reached.

    File::read_exactly_at

    async fn File::read_exactly_at(self : File, len : Int, position~ : Int64) -> Bytes

    File::size

    async fn File::size(self : File) -> Int64

    Get the size of the file. This method will not change position in the file. Can only be applied to a regular file.

    File::sync

    async fn File::sync(self : File, only_data? : Bool) -> Unit

    Flush all in-memory data of an opened file to disk. If only_data is true (false by default), some metadata such as timestamp may not be flushed. Buf file content and important metadata such as file size are always flushed.

    File::try_lock

    fn File::try_lock(self : File, lock : Lock) -> Bool raise

    Try to lock a file. If the lock is successfully acquired, true is returned. If the lock cannot be acquired immediately (because an incompatible lock is held by another process), false is returned immediately.

    The lock here is advisory, so normal read/write operations on the file are not affected by the lock, only another attempt to lock the same file may conflict with the lock here.

    The underlying syscall used to implement the lock is undefined. So the lock should only be used for synchronization between multiple process written using moonbitlang/async.

    File::unlock

    fn File::unlock(self : File) -> Unit

    Unlock a file that was previously locked by this process.

    File::write_at

    async fn File::write_at(self : File, buf : BytesView, position~ : Int64) -> Unit

    Write to a file at the offset given by position. Only seekable files (e.g. regular files and block devices) support this operation, calling write_at on unsupported file, such as pipe or socket, result in error.

    This function does not change the cursor for reading/writing the file as a stream (i.e. using API based on @io.Reader and @io.Writer), so it is safe to perform multiple write_at on the same file simutaneously, as long as the regions they write do not overlap.

    write_at would only return after all data is written.

    FileKind

    pub(all) enum FileKind {
    Unknown
    Regular
    Directory
    SymLink
    Socket
    Pipe
    BlockDevice
    CharDevice
    } derive(Eq,
    Debug
    )

    impl Show for FileKind

    Lock

    pub(all) enum Lock {
    Shared
    Exclusive
    }

    The kind of lock that can be acquired on a file. Exclusive lock must be unique, while multiple Shared lock can coexist. Exclusive lock and Shared lock are mutually exclusive.

    Mode

    pub(all) enum Mode {
    ReadOnly
    WriteOnly
    ReadWrite
    }

    SyncMode

    pub(all) enum SyncMode {
    NoSync
    Data
    Full
    }

    Determine how data is synchronized after writing:

    • NoSync means no synchronization is performed.

    • Data means the data part and relevant metadata such as file size will be synchronized immediately after a write call, but other metadata, such as last access time, will not. See man page of fdatasync(2) for more details.

    • Full means all data and metdata will be synchronized immediately after writing. See man page of fsync(2) for more details.

    Watcher

    pub struct Watcher {
    // private fields
    }
    async fn Watcher::Watcher(path : String, debounce_timeout? : Int, max_debounce_delay? : Int, ignored_paths? : (String) -> Bool) -> Watcher

    A file system watcher for watching changes in a directory.

    Watcher::close

    fn Watcher::close(self : Watcher) -> Unit

    Watcher::wait_any

    async fn Watcher::wait_any(self : Watcher) -> Unit

    Wait for any change in the watched tree since the last wait_any call or watcher creation.

    atime

    async fn atime(path : StringView, follow_symlink? : Bool) -> (Int64, Int)

    Get the last access time of a file at path. The return value is a pair (s, ns), representing the time of s seconds + ns nanoseconds.

    If follow_symlink is true (true by default) and path is a symbolic link, the timestamp of the target of path will be returned.

    can_execute

    async fn can_execute(path : StringView) -> Bool

    can_read

    async fn can_read(path : StringView) -> Bool

    can_write

    async fn can_write(path : StringView) -> Bool

    chmod

    async fn chmod(path : StringView, mode : Int) -> Unit

    Change the permission of a file. Permission is represented as an integer in UNIX permission style. For example, 0o640 means:
    • the owner of the file can read and write the file (6)
    • users in the owner group of the file can read the file (4)
    • other users can do nothing to the file

    create

    async fn create(filename : StringView, allow_existing? : Bool, permission? : Int, sync? : SyncMode) -> File

    Create a new file at pathname. pathname will be encoded using UTF8.

    • If the file already exists and allow_existing is true (true by default), the existing file will be truncated and opened. If the file already exists and allow_existing is fale, this function will fail.

    • User permission of the new file would be set to permission, which is an integer in UNIX style. For example, 0o640 means:
      • the owner of the file can read and write the file (6)
      • users in the owner group of the file can read the file (4)
      • other users can do nothing to the file The default value is 0o644 (anyone can read, only owner can write, not executable). permission is currently ignored on Windows.

    • sync determines how data will be synchronized to disk after writing, see SyncMode for more details. The default is NoSync

    ctime

    async fn ctime(path : StringView, follow_symlink? : Bool) -> (Int64, Int)

    Get the last status change time of a file at path. The return value is a pair (s, ns), representing the time of s seconds + ns nanoseconds.

    If follow_symlink is true (true by default) and path is a symbolic link, the timestamp of the target of path will be returned.

    exists

    async fn exists(path : StringView) -> Bool

    kind

    async fn kind(path : StringView, follow_symlink? : Bool) -> FileKind

    Get the kind of a file at path. If path is a symbolic link and follow_symlink is true (true by default), the kind of the target of the link will be returned.

    mkdir

    async fn mkdir(path : StringView, permission? : Int, recursive? : Bool) -> Unit

    Create a directory at path.

    The Unix-style permission of the created directory can be set in permission, the default value is 0o755 (anyone can read and traverse, only owner can write). The permission parameter is currently ignored on Windows.

    If recursive=true (false by default), non-existing parent directories of path will be recursively created, too.

    mtime

    async fn mtime(path : StringView, follow_symlink? : Bool) -> (Int64, Int)

    Get the last modification time of a file at path. The return value is a pair (s, ns), representing the time of s seconds + ns nanoseconds.

    If follow_symlink is true (true by default) and path is a symbolic link, the timestamp of the target of path will be returned.

    open

    async fn open(filename : StringView, mode~ : Mode, sync? : SyncMode, append? : Bool, create_mode? : CreateMode, permission? : Int, create? : Int, truncate? : Bool) -> File

    Open a file. filename will be encoded using UTF8.

    • mode determines what operations (read/write) are permitted for the file.

    • sync determines how data will be synchronized to disk after writing, see SyncMode for more details. The default is NoSync

    • if append is true (false by default), the file will be opened in append mode.

    • create_mode specify how to handle file creation, truncation, etc. See the type CreateMode for more details. The default value is OpenExisting.

    • permission specifies the user permission of the new file, if a new file is created by @fs.open. The permission is represented as an integer in UNIX permission style. For example, 0o640 means:

      • the owner of the file can read and write the file (6)
      • users in the owner group of the file can read the file (4)
      • other users can do nothing to the file

      The default value is 0o644 (anyone can read, only owner can write, not executable). permission is currently ignored on Windows.

    opendir

    async fn opendir(path : StringView) -> Directory

    Open the directory at path. path is encoded UTF8. If path is not a directory, an error will be raised

    read_file

    async fn read_file(path : StringView, sync_timestamp? : Bool) -> &
    Data

    Read the contents of the file located at path. If sync_timestamp is true (false by default), read_file will block until timestamp change is written to the file system.

    readdir

    async fn readdir(path : StringView, include_hidden? : Bool, include_special? : Bool, sort? : Bool) -> Array[String]

    Read all entries in the directory located at path.

    • If include_special is true (false by default), . (current directory) and .. (parent directory) will be included too

    • If include_hidden is true (true by default), hidden files (on Unix: file name starts with ., on Windows: has the hidden attribute) will be included

    • If sort is true (false by default), the result will be sorted using Array::sort.

    The returned file names are interpreted as UTF8-encoded on Unix-like systems.

    realpath

    async fn realpath(path : StringView) -> String

    Get the absolute real path of path, removing all .. and . in the path, and unfold all symbolic links.

    If the path contains cyclic symbolic link, an error will be raised.

    remove

    async fn remove(path : StringView) -> Unit

    Remove the file located at path. path is encoded using UTF8.

    rename

    async fn rename(old_path : StringView, new_path : StringView, replace? : Bool) -> Unit

    Rename (move) the file located at old_path to new_path.

    If replace is true (true by default), and a file already exist on new_path, that file will be replaced. In this case, open handles to the existing file remain valid, and still point to the old file.

    rmdir

    async fn rmdir(path : StringView, recursive? : Bool) -> Unit

    Remove a directory at path, If recursive is true (false by default), files and directories in path will be removed recursively. If recursive is false, path must be an empty directory, otherwise rmdir will fail.
    async fn symlink(target~ : StringView, path : StringView) -> Unit

    Create a symbolic link to old_path at new_path.

    tmpdir

    async fn tmpdir(prefix~ : StringView) -> String

    Create a temporary directory in the system-specific temporary file storage (such as /tmp/) with prefix prefix. tmpdir force-create the directory atomically, so it never reuse directory.

    The format of the directory name is:

    .<current_pid>.<random_number>

    The random number part cotains 32bits of information, so at most 2^32 temporary directories can be created for every process. However, due to collision, tmpdir will slow down when a lot of temporary directories are created by the same process.

    The temporary directory name is not cryptographically secure.

    walk

    async fn walk(path : StringView, f : async (String, Array[String]) -> Unit, exclude? : (String) -> Bool, max_concurrency? : Int, allow_failure? : Bool) -> Unit

    walk(path, f, ..) recursively walk the directory path, and call f on every sub-directory in path, including path itself.

    If exclude is present, it will be invoked with the path of every directory before traversing them. If exclude returns true, the directory and all its children will be skipped. The path passed to exclude has the following format:
    • always start with path
    • does not contain trailing directory separator

    The directories are walked in parallel. Use max_concurrency to limit the number of workers spawned in parallel. For example, setting max_concurrency=1 result in sequential walking. By default max_concurrency is set to a large value.

    If allow_failure is true (false by default), error raised by f will be silently ignored. Otherwise, failure in f will stop the whole walk, cancelling the handling of other sub-directories.

    write_file

    async fn write_file(path : StringView, content : &
    Data
    , create_mode? : CreateMode, permission? : Int, sync? : SyncMode, append? : Bool, create? : Int, truncate? : Bool) -> Unit

    Write data to a file located at path. The meaning of sync, append, create_mode and permission, is the same as open, except that create_mode is TruncateExisting by default. See open for more details.