CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-squareup-okio--okio

A modern I/O library for Android, Java, and Kotlin Multiplatform

Pending
Overview
Eval results
Files

filesystem.mddocs/

File System Operations

This document covers Okio's cross-platform file system APIs, including path manipulation, file I/O operations, directory management, and metadata handling.

Path

Path represents a hierarchical address on a file system, supporting both UNIX and Windows path conventions.

Path Creation and Properties

expect class Path {
    // Core properties
    val root: Path?                // Root path (null for relative paths)
    val segments: List<String>     // Path components
    val isAbsolute: Boolean        // True if path is absolute
    val isRelative: Boolean        // True if path is relative
    val volumeLetter: Char?        // Windows volume letter (e.g., 'C')
    
    // Path components
    val name: String               // Last segment (file/directory name)
    val parent: Path?              // Parent path (null for roots)
    val isRoot: Boolean            // True if this equals root
    
    // Path operations
    operator fun div(child: String): Path
    operator fun div(child: ByteString): Path
    operator fun div(child: Path): Path
    
    fun resolve(child: String, normalize: Boolean = false): Path
    fun resolve(child: ByteString, normalize: Boolean = false): Path
    fun resolve(child: Path, normalize: Boolean = false): Path
    
    fun relativeTo(other: Path): Path
    fun normalized(): Path
    
    // Comparison and string representation
    override fun equals(other: Any?): Boolean
    override fun hashCode(): Int
    override fun toString(): String

    companion object {
        val DIRECTORY_SEPARATOR: String
    }
}

Path Extensions

// String to Path conversion
fun String.toPath(normalize: Boolean = false): Path

Usage Examples

// Creating paths
val absolutePath = "/Users/john/documents/file.txt".toPath()
val relativePath = "documents/file.txt".toPath()

// Path properties
println("Is absolute: ${absolutePath.isAbsolute}") // true
println("Root: ${absolutePath.root}")              // "/"
println("Name: ${absolutePath.name}")              // "file.txt"
println("Parent: ${absolutePath.parent}")          // "/Users/john/documents"
println("Segments: ${absolutePath.segments}")      // ["Users", "john", "documents", "file.txt"]

// Path manipulation
val documentsDir = "/Users/john".toPath()
val fileInDocs = documentsDir / "documents" / "file.txt"
val backupFile = fileInDocs.parent!! / "backup" / fileInDocs.name

// Relative paths
val base = "/Users/john".toPath()
val target = "/Users/john/documents/file.txt".toPath()
val relative = target.relativeTo(base) // "documents/file.txt"

// Normalization
val messyPath = "/Users/john/../john/./documents/../documents/file.txt".toPath()
val cleanPath = messyPath.normalized() // "/Users/john/documents/file.txt"

FileSystem

FileSystem provides read and write access to a hierarchical collection of files and directories.

Core FileSystem Operations

expect abstract class FileSystem {
    // Path resolution
    abstract fun canonicalize(path: Path): Path
    
    // Metadata operations
    fun metadata(path: Path): FileMetadata
    abstract fun metadataOrNull(path: Path): FileMetadata?
    fun exists(path: Path): Boolean
    
    // Directory listing
    abstract fun list(dir: Path): List<Path>
    abstract fun listOrNull(dir: Path): List<Path>?
    open fun listRecursively(dir: Path, followSymlinks: Boolean = false): Sequence<Path>
    
    // File I/O
    abstract fun openReadOnly(file: Path): FileHandle
    abstract fun openReadWrite(
        file: Path, 
        mustCreate: Boolean = false, 
        mustExist: Boolean = false
    ): FileHandle
    
    abstract fun source(file: Path): Source
    inline fun <T> read(file: Path, readerAction: BufferedSource.() -> T): T
    
    abstract fun sink(file: Path, mustCreate: Boolean = false): Sink
    inline fun <T> write(
        file: Path, 
        mustCreate: Boolean = false, 
        writerAction: BufferedSink.() -> T
    ): T
    
    abstract fun appendingSink(file: Path, mustExist: Boolean = false): Sink
    
    // Directory operations
    abstract fun createDirectory(dir: Path, mustCreate: Boolean = false)
    fun createDirectories(dir: Path, mustCreate: Boolean = false)
    
    // File management
    abstract fun atomicMove(source: Path, target: Path)
    open fun copy(source: Path, target: Path)
    abstract fun delete(path: Path, mustExist: Boolean = false)
    open fun deleteRecursively(fileOrDirectory: Path, mustExist: Boolean = false)
    
    // Symbolic links
    abstract fun createSymlink(source: Path, target: Path)
    
    companion object {
        val SYSTEM_TEMPORARY_DIRECTORY: Path
        val SYSTEM: FileSystem
    }
}

Usage Examples

// Basic file operations
val fs = FileSystem.SYSTEM
val filePath = "/tmp/example.txt".toPath()

// Write to file
fs.write(filePath) {
    writeUtf8("Hello, Okio FileSystem!")
    writeUtf8("\n")
    writeUtf8("This is a test file.")
}

// Read from file
val content = fs.read(filePath) {
    readUtf8()
}
println("File content: $content")

// Check if file exists
if (fs.exists(filePath)) {
    val metadata = fs.metadata(filePath)
    println("File size: ${metadata.size} bytes")
    println("Last modified: ${metadata.lastModifiedAtMillis}")
}

// Directory operations
val dirPath = "/tmp/test-directory".toPath()
fs.createDirectory(dirPath)

// List directory contents
val files = fs.list(dirPath)
files.forEach { path ->
    println("Found: ${path.name}")
}

// Recursive directory listing
val homeDir = System.getProperty("user.home").toPath()
fs.listRecursively(homeDir / "Documents")
    .take(10)
    .forEach { path ->
        println("File: $path")
    }

// Copy and move operations
val sourceFile = "/tmp/source.txt".toPath()
val targetFile = "/tmp/target.txt".toPath()

fs.write(sourceFile) { writeUtf8("Source content") }
fs.copy(sourceFile, targetFile)
fs.atomicMove(targetFile, "/tmp/moved.txt".toPath())

// Cleanup
fs.delete(sourceFile)
fs.delete("/tmp/moved.txt".toPath())
fs.deleteRecursively(dirPath)

FileHandle

FileHandle provides both streaming and random access I/O for open files.

FileHandle API

abstract class FileHandle : Closeable {
    // Properties
    val readWrite: Boolean
    
    // Random access I/O
    fun read(fileOffset: Long, array: ByteArray, arrayOffset: Int = 0, byteCount: Int = array.size): Int
    fun read(fileOffset: Long, sink: Buffer, byteCount: Long): Long
    
    fun write(fileOffset: Long, array: ByteArray, arrayOffset: Int = 0, byteCount: Int = array.size)
    fun write(fileOffset: Long, source: Buffer, byteCount: Long)
    
    // File properties
    fun size(): Long
    fun resize(size: Long)
    fun flush()
    
    // Streaming I/O
    fun source(fileOffset: Long = 0L): Source
    fun sink(fileOffset: Long = 0L): Sink
    fun appendingSink(): Sink
    
    // Position management
    fun position(source: Source): Long
    fun reposition(source: Source, position: Long)
    fun position(sink: Sink): Long
    fun reposition(sink: Sink, position: Long)
    
    override fun close()
}

Usage Examples

// Random access file I/O
val fs = FileSystem.SYSTEM
val file = "/tmp/random-access.txt".toPath()

// Create file with some content
fs.write(file) {
    writeUtf8("0123456789ABCDEF")
}

fs.openReadWrite(file).use { handle ->
    // Read from specific position
    val buffer = Buffer()
    val bytesRead = handle.read(5L, buffer, 5L) // Read 5 bytes starting at offset 5
    println("Read: ${buffer.readUtf8()}") // "56789"
    
    // Write at specific position
    val writeBuffer = Buffer().writeUtf8("HELLO")
    handle.write(10L, writeBuffer, 5L) // Write "HELLO" at offset 10
    
    // Get file size
    println("File size: ${handle.size()}")
    
    // Flush changes
    handle.flush()
}

// Streaming with position control
fs.openReadOnly(file).use { handle ->
    val source = handle.source(0L) // Start from beginning
    val bufferedSource = source.buffer()
    
    // Read first part
    val firstPart = bufferedSource.readUtf8(5)
    println("First part: $firstPart")
    
    // Change position
    handle.reposition(source, 10L)
    val secondPart = bufferedSource.readUtf8(5)
    println("Second part: $secondPart")
}

FileMetadata

FileMetadata contains information about files and directories.

FileMetadata Structure

class FileMetadata(
    val isRegularFile: Boolean = false,
    val isDirectory: Boolean = false,
    val symlinkTarget: Path? = null,
    val size: Long? = null,
    val createdAtMillis: Long? = null,
    val lastModifiedAtMillis: Long? = null,
    val lastAccessedAtMillis: Long? = null,
    val extras: Map<KClass<*>, Any> = emptyMap()
) {
    fun <T : Any> extra(type: KClass<out T>): T?
    
    fun copy(
        isRegularFile: Boolean = this.isRegularFile,
        isDirectory: Boolean = this.isDirectory,
        symlinkTarget: Path? = this.symlinkTarget,
        size: Long? = this.size,
        createdAtMillis: Long? = this.createdAtMillis,
        lastModifiedAtMillis: Long? = this.lastModifiedAtMillis,
        lastAccessedAtMillis: Long? = this.lastAccessedAtMillis,
        extras: Map<KClass<*>, Any> = this.extras
    ): FileMetadata
}

Usage Examples

val fs = FileSystem.SYSTEM
val file = "/tmp/metadata-test.txt".toPath()

// Create a test file
fs.write(file) {
    writeUtf8("Test content for metadata")
}

// Get file metadata
val metadata = fs.metadata(file)

println("Is regular file: ${metadata.isRegularFile}")
println("Is directory: ${metadata.isDirectory}")
println("File size: ${metadata.size} bytes")

metadata.lastModifiedAtMillis?.let { timestamp ->
    val date = java.util.Date(timestamp)
    println("Last modified: $date")
}

metadata.createdAtMillis?.let { timestamp ->
    val date = java.util.Date(timestamp)
    println("Created: $date")
}

// Check for symbolic links
if (metadata.symlinkTarget != null) {
    println("Symbolic link target: ${metadata.symlinkTarget}")
}

// Directory metadata
val dirPath = "/tmp".toPath()
val dirMetadata = fs.metadata(dirPath)
println("Is directory: ${dirMetadata.isDirectory}")

ForwardingFileSystem

ForwardingFileSystem is an abstract decorator for applying monitoring, encryption, compression, or filtering to file operations.

ForwardingFileSystem Base

abstract class ForwardingFileSystem(
    protected val delegate: FileSystem
) : FileSystem {
    // All methods delegate to the wrapped FileSystem by default
    // Override specific methods to add custom behavior
    
    override fun canonicalize(path: Path): Path = delegate.canonicalize(path)
    override fun metadataOrNull(path: Path): FileMetadata? = delegate.metadataOrNull(path)
    override fun list(dir: Path): List<Path> = delegate.list(dir)
    // ... other delegating methods
}

Custom FileSystem Example

// Example: Logging FileSystem
class LoggingFileSystem(delegate: FileSystem) : ForwardingFileSystem(delegate) {
    override fun source(file: Path): Source {
        println("Opening source for: $file")
        return delegate.source(file)
    }
    
    override fun sink(file: Path, mustCreate: Boolean): Sink {
        println("Opening sink for: $file (mustCreate: $mustCreate)")
        return delegate.sink(file, mustCreate)
    }
    
    override fun delete(path: Path, mustExist: Boolean) {
        println("Deleting: $path")
        delegate.delete(path, mustExist)
    }
}

// Usage
val loggingFs = LoggingFileSystem(FileSystem.SYSTEM)
loggingFs.write("/tmp/logged-file.txt".toPath()) {
    writeUtf8("This write operation will be logged")
}

Advanced File System Operations

Temporary Files and Directories

// Working with temporary directory
val tempDir = FileSystem.SYSTEM_TEMPORARY_DIRECTORY
val tempFile = tempDir / "okio-temp-${System.currentTimeMillis()}.txt"

FileSystem.SYSTEM.write(tempFile) {
    writeUtf8("Temporary file content")
}

// Cleanup temporary file
FileSystem.SYSTEM.delete(tempFile)

Atomic Operations

val fs = FileSystem.SYSTEM
val sourceFile = "/tmp/source.txt".toPath()
val targetFile = "/tmp/target.txt".toPath()

// Create source file
fs.write(sourceFile) {
    writeUtf8("Important data that must be moved atomically")
}

// Atomic move ensures no partial state
fs.atomicMove(sourceFile, targetFile)

// Source file no longer exists, target file has the content
println("Source exists: ${fs.exists(sourceFile)}")  // false
println("Target exists: ${fs.exists(targetFile)}")  // true

Recursive Operations

val fs = FileSystem.SYSTEM
val baseDir = "/tmp/recursive-test".toPath()

// Create directory structure
fs.createDirectories(baseDir / "level1" / "level2" / "level3")

// Create files at various levels
fs.write(baseDir / "root-file.txt") { writeUtf8("Root level") }
fs.write(baseDir / "level1" / "level1-file.txt") { writeUtf8("Level 1") }
fs.write(baseDir / "level1" / "level2" / "level2-file.txt") { writeUtf8("Level 2") }

// List all files recursively
println("All files in directory tree:")
fs.listRecursively(baseDir).forEach { path ->
    val metadata = fs.metadataOrNull(path)
    val type = when {
        metadata?.isDirectory == true -> "[DIR]"
        metadata?.isRegularFile == true -> "[FILE]"
        else -> "[OTHER]"
    }
    println("$type $path")
}

// Delete entire directory tree
fs.deleteRecursively(baseDir)

Error Handling

File system operations can throw various exceptions:

expect open class IOException : Exception
expect class FileNotFoundException : IOException
expect class FileAlreadyExistsException : IOException
expect class DirectoryNotEmptyException : IOException
expect class AccessDeniedException : IOException

Common Error Scenarios

val fs = FileSystem.SYSTEM
val nonExistentFile = "/path/that/does/not/exist.txt".toPath()

try {
    // This will throw FileNotFoundException
    fs.source(nonExistentFile)
} catch (e: FileNotFoundException) {
    println("File not found: ${e.message}")
}

try {
    // This will throw DirectoryNotEmptyException if directory has contents
    fs.delete("/tmp".toPath())
} catch (e: DirectoryNotEmptyException) {
    println("Directory not empty: ${e.message}")
}

// Safer operations using null-returning variants
val metadata = fs.metadataOrNull(nonExistentFile)
if (metadata != null) {
    println("File exists with size: ${metadata.size}")
} else {
    println("File does not exist")
}

val files = fs.listOrNull(nonExistentFile)
if (files != null) {
    println("Directory contains ${files.size} items")
} else {
    println("Directory does not exist or is not accessible")
}

Platform-Specific Considerations

POSIX File Systems (Unix/Linux/macOS/iOS)

  • Support for symbolic links
  • POSIX permissions in metadata extras
  • Case-sensitive file names (usually)
  • / as path separator

File System Features by Platform

// Check platform capabilities
val fs = FileSystem.SYSTEM
val testDir = "/tmp/platform-test".toPath()

fs.createDirectory(testDir)

try {
    // Symbolic links (POSIX systems)
    val linkTarget = testDir / "target.txt"
    val linkPath = testDir / "symlink.txt"
    
    fs.write(linkTarget) { writeUtf8("Target content") }
    fs.createSymlink(linkPath, linkTarget)
    
    val linkMetadata = fs.metadata(linkPath)
    if (linkMetadata.symlinkTarget != null) {
        println("Symbolic link supported, points to: ${linkMetadata.symlinkTarget}")
    }
} catch (e: Exception) {
    println("Symbolic links not supported: ${e.message}")
} finally {
    fs.deleteRecursively(testDir)
}

Install with Tessl CLI

npx tessl i tessl/maven-com-squareup-okio--okio

docs

compression.md

core-io.md

filesystem.md

hashing.md

index.md

utilities.md

tile.json