square / okio

A modern I/O library for Android, Java, and Kotlin Multiplatform.
https://square.github.io/okio/
Apache License 2.0
8.72k stars 1.17k forks source link

Add extensions to improve out-of-box experiences #1458

Closed Omico closed 3 months ago

Omico commented 3 months ago

Hi, I am just wondering if we could add something like the below to improve out-of-box experiences.

For example (incomplete)

@Throws(IOException::class)
public inline fun Path.exists(): Boolean = FileSystem.SYSTEM.exists(this)

@Throws(IOException::class)
public inline fun Path.notExists(): Boolean = FileSystem.SYSTEM.exists(this).not()

public inline val Path.metadata: FileMetadata
    @Throws(IOException::class)
    get() = FileSystem.SYSTEM.metadata(this)

public inline val Path.isRegularFile: Boolean
    @Throws(IOException::class)
    get() = metadata.isRegularFile

public inline val Path.isDirectory: Boolean
    @Throws(IOException::class)
    get() = metadata.isDirectory

@Throws(IOException::class)
public inline fun Path.createDirectory(mustCreate: Boolean = false): Unit =
    FileSystem.SYSTEM.createDirectory(this, mustCreate)

@Throws(IOException::class)
public inline fun Path.createDirectories(mustCreate: Boolean = false): Unit =
    FileSystem.SYSTEM.createDirectories(this, mustCreate)

@Throws(IOException::class)
public inline fun Path.delete(mustExist: Boolean = false): Unit = FileSystem.SYSTEM.delete(this, mustExist)

@Throws(IOException::class)
public inline fun Path.list(): List<Path> = FileSystem.SYSTEM.list(this)

@Throws(IOException::class)
public fun Path.listOrNull(): List<Path>? = FileSystem.SYSTEM.listOrNull(this)

@Throws(IOException::class)
public fun Path.listRecursively(followSymlinks: Boolean = false): Sequence<Path> =
    FileSystem.SYSTEM.listRecursively(this, followSymlinks)

@Throws(IOException::class)
public inline fun <T> Path.read(readerAction: BufferedSource.() -> T): T = FileSystem.SYSTEM.read(this, readerAction)

@Throws(IOException::class)
public inline fun Path.readUtf8(): String = read(BufferedSource::readUtf8)

@Throws(IOException::class)
public inline fun <T> Path.write(mustCreate: Boolean = false, writerAction: BufferedSink.() -> T): T =
    FileSystem.SYSTEM.write(this, mustCreate, writerAction)

@Throws(IOException::class)
public inline fun Path.writeUtf8(content: String, mustCreate: Boolean = false): BufferedSink =
    write(mustCreate) { writeUtf8(content) }
JakeWharton commented 3 months ago

~The ones for file system~ (they're all for file system)

These fundamentally undermine a principle of the design which is that code is agnostic to the actual FileSystem with which it's interacting. More concretely, using these extensions (in a library, say) breaks my ability to use the contents of a zip without extracting. Or files in an Android app's asset directory. Or files that live on a remote machine like AWS S3 bucket.

With Kotlin's upcoming context parameter language feature, it's possible that the FileSystem context could become implicitly propagated rather than implicitly. Until then, accepting a FileSystem anywhere you accept a Path is the correct design pattern.

JakeWharton commented 3 months ago

You can use extensions like these in your project so long as you're not a library. If you're a library, and you accept a Path from a user, you really must also accept a FileSystem or you break a bunch of use cases.

If you are in an application that only uses the system FileSystem then these are relatively safe.

Omico commented 3 months ago

If you are in an application that only uses the system FileSystem then these are relatively safe.

Yes, that's why I call it "out-of-box experiences." Because I only want to call the system FileSystem, it would be best if Okio could provide some extensions for JVM (including Android) and native platforms.

What I am asking is, indeed, for the upper layer. We can achieve this by placing it in different source sets, instead let users write again and again.

JakeWharton commented 3 months ago

I think we're stuck waiting for context parameters where we don't need to choose between convenient-but-inflexible and verbose-but-flexible. We explicitly do not want to make it easier to implicitly introduce a static dependency on the system file system that makes code harder to test and less flexible in the library case.