0
# File System Operations
1
2
This document covers Okio's cross-platform file system APIs, including path manipulation, file I/O operations, directory management, and metadata handling.
3
4
## Path
5
6
Path represents a hierarchical address on a file system, supporting both UNIX and Windows path conventions.
7
8
### Path Creation and Properties
9
10
```kotlin { .api }
11
expect class Path {
12
// Core properties
13
val root: Path? // Root path (null for relative paths)
14
val segments: List<String> // Path components
15
val isAbsolute: Boolean // True if path is absolute
16
val isRelative: Boolean // True if path is relative
17
val volumeLetter: Char? // Windows volume letter (e.g., 'C')
18
19
// Path components
20
val name: String // Last segment (file/directory name)
21
val parent: Path? // Parent path (null for roots)
22
val isRoot: Boolean // True if this equals root
23
24
// Path operations
25
operator fun div(child: String): Path
26
operator fun div(child: ByteString): Path
27
operator fun div(child: Path): Path
28
29
fun resolve(child: String, normalize: Boolean = false): Path
30
fun resolve(child: ByteString, normalize: Boolean = false): Path
31
fun resolve(child: Path, normalize: Boolean = false): Path
32
33
fun relativeTo(other: Path): Path
34
fun normalized(): Path
35
36
// Comparison and string representation
37
override fun equals(other: Any?): Boolean
38
override fun hashCode(): Int
39
override fun toString(): String
40
41
companion object {
42
val DIRECTORY_SEPARATOR: String
43
}
44
}
45
```
46
47
### Path Extensions
48
49
```kotlin { .api }
50
// String to Path conversion
51
fun String.toPath(normalize: Boolean = false): Path
52
```
53
54
### Usage Examples
55
56
```kotlin
57
// Creating paths
58
val absolutePath = "/Users/john/documents/file.txt".toPath()
59
val relativePath = "documents/file.txt".toPath()
60
61
// Path properties
62
println("Is absolute: ${absolutePath.isAbsolute}") // true
63
println("Root: ${absolutePath.root}") // "/"
64
println("Name: ${absolutePath.name}") // "file.txt"
65
println("Parent: ${absolutePath.parent}") // "/Users/john/documents"
66
println("Segments: ${absolutePath.segments}") // ["Users", "john", "documents", "file.txt"]
67
68
// Path manipulation
69
val documentsDir = "/Users/john".toPath()
70
val fileInDocs = documentsDir / "documents" / "file.txt"
71
val backupFile = fileInDocs.parent!! / "backup" / fileInDocs.name
72
73
// Relative paths
74
val base = "/Users/john".toPath()
75
val target = "/Users/john/documents/file.txt".toPath()
76
val relative = target.relativeTo(base) // "documents/file.txt"
77
78
// Normalization
79
val messyPath = "/Users/john/../john/./documents/../documents/file.txt".toPath()
80
val cleanPath = messyPath.normalized() // "/Users/john/documents/file.txt"
81
```
82
83
## FileSystem
84
85
FileSystem provides read and write access to a hierarchical collection of files and directories.
86
87
### Core FileSystem Operations
88
89
```kotlin { .api }
90
expect abstract class FileSystem {
91
// Path resolution
92
abstract fun canonicalize(path: Path): Path
93
94
// Metadata operations
95
fun metadata(path: Path): FileMetadata
96
abstract fun metadataOrNull(path: Path): FileMetadata?
97
fun exists(path: Path): Boolean
98
99
// Directory listing
100
abstract fun list(dir: Path): List<Path>
101
abstract fun listOrNull(dir: Path): List<Path>?
102
open fun listRecursively(dir: Path, followSymlinks: Boolean = false): Sequence<Path>
103
104
// File I/O
105
abstract fun openReadOnly(file: Path): FileHandle
106
abstract fun openReadWrite(
107
file: Path,
108
mustCreate: Boolean = false,
109
mustExist: Boolean = false
110
): FileHandle
111
112
abstract fun source(file: Path): Source
113
inline fun <T> read(file: Path, readerAction: BufferedSource.() -> T): T
114
115
abstract fun sink(file: Path, mustCreate: Boolean = false): Sink
116
inline fun <T> write(
117
file: Path,
118
mustCreate: Boolean = false,
119
writerAction: BufferedSink.() -> T
120
): T
121
122
abstract fun appendingSink(file: Path, mustExist: Boolean = false): Sink
123
124
// Directory operations
125
abstract fun createDirectory(dir: Path, mustCreate: Boolean = false)
126
fun createDirectories(dir: Path, mustCreate: Boolean = false)
127
128
// File management
129
abstract fun atomicMove(source: Path, target: Path)
130
open fun copy(source: Path, target: Path)
131
abstract fun delete(path: Path, mustExist: Boolean = false)
132
open fun deleteRecursively(fileOrDirectory: Path, mustExist: Boolean = false)
133
134
// Symbolic links
135
abstract fun createSymlink(source: Path, target: Path)
136
137
companion object {
138
val SYSTEM_TEMPORARY_DIRECTORY: Path
139
val SYSTEM: FileSystem
140
}
141
}
142
```
143
144
### Usage Examples
145
146
```kotlin
147
// Basic file operations
148
val fs = FileSystem.SYSTEM
149
val filePath = "/tmp/example.txt".toPath()
150
151
// Write to file
152
fs.write(filePath) {
153
writeUtf8("Hello, Okio FileSystem!")
154
writeUtf8("\n")
155
writeUtf8("This is a test file.")
156
}
157
158
// Read from file
159
val content = fs.read(filePath) {
160
readUtf8()
161
}
162
println("File content: $content")
163
164
// Check if file exists
165
if (fs.exists(filePath)) {
166
val metadata = fs.metadata(filePath)
167
println("File size: ${metadata.size} bytes")
168
println("Last modified: ${metadata.lastModifiedAtMillis}")
169
}
170
171
// Directory operations
172
val dirPath = "/tmp/test-directory".toPath()
173
fs.createDirectory(dirPath)
174
175
// List directory contents
176
val files = fs.list(dirPath)
177
files.forEach { path ->
178
println("Found: ${path.name}")
179
}
180
181
// Recursive directory listing
182
val homeDir = System.getProperty("user.home").toPath()
183
fs.listRecursively(homeDir / "Documents")
184
.take(10)
185
.forEach { path ->
186
println("File: $path")
187
}
188
189
// Copy and move operations
190
val sourceFile = "/tmp/source.txt".toPath()
191
val targetFile = "/tmp/target.txt".toPath()
192
193
fs.write(sourceFile) { writeUtf8("Source content") }
194
fs.copy(sourceFile, targetFile)
195
fs.atomicMove(targetFile, "/tmp/moved.txt".toPath())
196
197
// Cleanup
198
fs.delete(sourceFile)
199
fs.delete("/tmp/moved.txt".toPath())
200
fs.deleteRecursively(dirPath)
201
```
202
203
## FileHandle
204
205
FileHandle provides both streaming and random access I/O for open files.
206
207
### FileHandle API
208
209
```kotlin { .api }
210
abstract class FileHandle : Closeable {
211
// Properties
212
val readWrite: Boolean
213
214
// Random access I/O
215
fun read(fileOffset: Long, array: ByteArray, arrayOffset: Int = 0, byteCount: Int = array.size): Int
216
fun read(fileOffset: Long, sink: Buffer, byteCount: Long): Long
217
218
fun write(fileOffset: Long, array: ByteArray, arrayOffset: Int = 0, byteCount: Int = array.size)
219
fun write(fileOffset: Long, source: Buffer, byteCount: Long)
220
221
// File properties
222
fun size(): Long
223
fun resize(size: Long)
224
fun flush()
225
226
// Streaming I/O
227
fun source(fileOffset: Long = 0L): Source
228
fun sink(fileOffset: Long = 0L): Sink
229
fun appendingSink(): Sink
230
231
// Position management
232
fun position(source: Source): Long
233
fun reposition(source: Source, position: Long)
234
fun position(sink: Sink): Long
235
fun reposition(sink: Sink, position: Long)
236
237
override fun close()
238
}
239
```
240
241
### Usage Examples
242
243
```kotlin
244
// Random access file I/O
245
val fs = FileSystem.SYSTEM
246
val file = "/tmp/random-access.txt".toPath()
247
248
// Create file with some content
249
fs.write(file) {
250
writeUtf8("0123456789ABCDEF")
251
}
252
253
fs.openReadWrite(file).use { handle ->
254
// Read from specific position
255
val buffer = Buffer()
256
val bytesRead = handle.read(5L, buffer, 5L) // Read 5 bytes starting at offset 5
257
println("Read: ${buffer.readUtf8()}") // "56789"
258
259
// Write at specific position
260
val writeBuffer = Buffer().writeUtf8("HELLO")
261
handle.write(10L, writeBuffer, 5L) // Write "HELLO" at offset 10
262
263
// Get file size
264
println("File size: ${handle.size()}")
265
266
// Flush changes
267
handle.flush()
268
}
269
270
// Streaming with position control
271
fs.openReadOnly(file).use { handle ->
272
val source = handle.source(0L) // Start from beginning
273
val bufferedSource = source.buffer()
274
275
// Read first part
276
val firstPart = bufferedSource.readUtf8(5)
277
println("First part: $firstPart")
278
279
// Change position
280
handle.reposition(source, 10L)
281
val secondPart = bufferedSource.readUtf8(5)
282
println("Second part: $secondPart")
283
}
284
```
285
286
## FileMetadata
287
288
FileMetadata contains information about files and directories.
289
290
### FileMetadata Structure
291
292
```kotlin { .api }
293
class FileMetadata(
294
val isRegularFile: Boolean = false,
295
val isDirectory: Boolean = false,
296
val symlinkTarget: Path? = null,
297
val size: Long? = null,
298
val createdAtMillis: Long? = null,
299
val lastModifiedAtMillis: Long? = null,
300
val lastAccessedAtMillis: Long? = null,
301
val extras: Map<KClass<*>, Any> = emptyMap()
302
) {
303
fun <T : Any> extra(type: KClass<out T>): T?
304
305
fun copy(
306
isRegularFile: Boolean = this.isRegularFile,
307
isDirectory: Boolean = this.isDirectory,
308
symlinkTarget: Path? = this.symlinkTarget,
309
size: Long? = this.size,
310
createdAtMillis: Long? = this.createdAtMillis,
311
lastModifiedAtMillis: Long? = this.lastModifiedAtMillis,
312
lastAccessedAtMillis: Long? = this.lastAccessedAtMillis,
313
extras: Map<KClass<*>, Any> = this.extras
314
): FileMetadata
315
}
316
```
317
318
### Usage Examples
319
320
```kotlin
321
val fs = FileSystem.SYSTEM
322
val file = "/tmp/metadata-test.txt".toPath()
323
324
// Create a test file
325
fs.write(file) {
326
writeUtf8("Test content for metadata")
327
}
328
329
// Get file metadata
330
val metadata = fs.metadata(file)
331
332
println("Is regular file: ${metadata.isRegularFile}")
333
println("Is directory: ${metadata.isDirectory}")
334
println("File size: ${metadata.size} bytes")
335
336
metadata.lastModifiedAtMillis?.let { timestamp ->
337
val date = java.util.Date(timestamp)
338
println("Last modified: $date")
339
}
340
341
metadata.createdAtMillis?.let { timestamp ->
342
val date = java.util.Date(timestamp)
343
println("Created: $date")
344
}
345
346
// Check for symbolic links
347
if (metadata.symlinkTarget != null) {
348
println("Symbolic link target: ${metadata.symlinkTarget}")
349
}
350
351
// Directory metadata
352
val dirPath = "/tmp".toPath()
353
val dirMetadata = fs.metadata(dirPath)
354
println("Is directory: ${dirMetadata.isDirectory}")
355
```
356
357
## ForwardingFileSystem
358
359
ForwardingFileSystem is an abstract decorator for applying monitoring, encryption, compression, or filtering to file operations.
360
361
### ForwardingFileSystem Base
362
363
```kotlin { .api }
364
abstract class ForwardingFileSystem(
365
protected val delegate: FileSystem
366
) : FileSystem {
367
// All methods delegate to the wrapped FileSystem by default
368
// Override specific methods to add custom behavior
369
370
override fun canonicalize(path: Path): Path = delegate.canonicalize(path)
371
override fun metadataOrNull(path: Path): FileMetadata? = delegate.metadataOrNull(path)
372
override fun list(dir: Path): List<Path> = delegate.list(dir)
373
// ... other delegating methods
374
}
375
```
376
377
### Custom FileSystem Example
378
379
```kotlin
380
// Example: Logging FileSystem
381
class LoggingFileSystem(delegate: FileSystem) : ForwardingFileSystem(delegate) {
382
override fun source(file: Path): Source {
383
println("Opening source for: $file")
384
return delegate.source(file)
385
}
386
387
override fun sink(file: Path, mustCreate: Boolean): Sink {
388
println("Opening sink for: $file (mustCreate: $mustCreate)")
389
return delegate.sink(file, mustCreate)
390
}
391
392
override fun delete(path: Path, mustExist: Boolean) {
393
println("Deleting: $path")
394
delegate.delete(path, mustExist)
395
}
396
}
397
398
// Usage
399
val loggingFs = LoggingFileSystem(FileSystem.SYSTEM)
400
loggingFs.write("/tmp/logged-file.txt".toPath()) {
401
writeUtf8("This write operation will be logged")
402
}
403
```
404
405
## Advanced File System Operations
406
407
### Temporary Files and Directories
408
409
```kotlin
410
// Working with temporary directory
411
val tempDir = FileSystem.SYSTEM_TEMPORARY_DIRECTORY
412
val tempFile = tempDir / "okio-temp-${System.currentTimeMillis()}.txt"
413
414
FileSystem.SYSTEM.write(tempFile) {
415
writeUtf8("Temporary file content")
416
}
417
418
// Cleanup temporary file
419
FileSystem.SYSTEM.delete(tempFile)
420
```
421
422
### Atomic Operations
423
424
```kotlin
425
val fs = FileSystem.SYSTEM
426
val sourceFile = "/tmp/source.txt".toPath()
427
val targetFile = "/tmp/target.txt".toPath()
428
429
// Create source file
430
fs.write(sourceFile) {
431
writeUtf8("Important data that must be moved atomically")
432
}
433
434
// Atomic move ensures no partial state
435
fs.atomicMove(sourceFile, targetFile)
436
437
// Source file no longer exists, target file has the content
438
println("Source exists: ${fs.exists(sourceFile)}") // false
439
println("Target exists: ${fs.exists(targetFile)}") // true
440
```
441
442
### Recursive Operations
443
444
```kotlin
445
val fs = FileSystem.SYSTEM
446
val baseDir = "/tmp/recursive-test".toPath()
447
448
// Create directory structure
449
fs.createDirectories(baseDir / "level1" / "level2" / "level3")
450
451
// Create files at various levels
452
fs.write(baseDir / "root-file.txt") { writeUtf8("Root level") }
453
fs.write(baseDir / "level1" / "level1-file.txt") { writeUtf8("Level 1") }
454
fs.write(baseDir / "level1" / "level2" / "level2-file.txt") { writeUtf8("Level 2") }
455
456
// List all files recursively
457
println("All files in directory tree:")
458
fs.listRecursively(baseDir).forEach { path ->
459
val metadata = fs.metadataOrNull(path)
460
val type = when {
461
metadata?.isDirectory == true -> "[DIR]"
462
metadata?.isRegularFile == true -> "[FILE]"
463
else -> "[OTHER]"
464
}
465
println("$type $path")
466
}
467
468
// Delete entire directory tree
469
fs.deleteRecursively(baseDir)
470
```
471
472
## Error Handling
473
474
File system operations can throw various exceptions:
475
476
```kotlin { .api }
477
expect open class IOException : Exception
478
expect class FileNotFoundException : IOException
479
expect class FileAlreadyExistsException : IOException
480
expect class DirectoryNotEmptyException : IOException
481
expect class AccessDeniedException : IOException
482
```
483
484
### Common Error Scenarios
485
486
```kotlin
487
val fs = FileSystem.SYSTEM
488
val nonExistentFile = "/path/that/does/not/exist.txt".toPath()
489
490
try {
491
// This will throw FileNotFoundException
492
fs.source(nonExistentFile)
493
} catch (e: FileNotFoundException) {
494
println("File not found: ${e.message}")
495
}
496
497
try {
498
// This will throw DirectoryNotEmptyException if directory has contents
499
fs.delete("/tmp".toPath())
500
} catch (e: DirectoryNotEmptyException) {
501
println("Directory not empty: ${e.message}")
502
}
503
504
// Safer operations using null-returning variants
505
val metadata = fs.metadataOrNull(nonExistentFile)
506
if (metadata != null) {
507
println("File exists with size: ${metadata.size}")
508
} else {
509
println("File does not exist")
510
}
511
512
val files = fs.listOrNull(nonExistentFile)
513
if (files != null) {
514
println("Directory contains ${files.size} items")
515
} else {
516
println("Directory does not exist or is not accessible")
517
}
518
```
519
520
## Platform-Specific Considerations
521
522
### POSIX File Systems (Unix/Linux/macOS/iOS)
523
524
- Support for symbolic links
525
- POSIX permissions in metadata extras
526
- Case-sensitive file names (usually)
527
- `/` as path separator
528
529
### File System Features by Platform
530
531
```kotlin
532
// Check platform capabilities
533
val fs = FileSystem.SYSTEM
534
val testDir = "/tmp/platform-test".toPath()
535
536
fs.createDirectory(testDir)
537
538
try {
539
// Symbolic links (POSIX systems)
540
val linkTarget = testDir / "target.txt"
541
val linkPath = testDir / "symlink.txt"
542
543
fs.write(linkTarget) { writeUtf8("Target content") }
544
fs.createSymlink(linkPath, linkTarget)
545
546
val linkMetadata = fs.metadata(linkPath)
547
if (linkMetadata.symlinkTarget != null) {
548
println("Symbolic link supported, points to: ${linkMetadata.symlinkTarget}")
549
}
550
} catch (e: Exception) {
551
println("Symbolic links not supported: ${e.message}")
552
} finally {
553
fs.deleteRecursively(testDir)
554
}
555
```