Skip to content

Commit

Permalink
Extend filesystem support (#213)
Browse files Browse the repository at this point in the history
* Added FileSystem interface providing basic operations on paths: reading/writing files, creating directories, moving and deleting files and directories, and gathering fs node metadata.
* Provided default FileSystem implementation - SystemFileSystem
* Extended Path API to allow getting parent paths and constructing children's paths using platform-dependent APIs under the hood.
* Added API to get path to the system temporary directory

Closes: #206 #211 #212 #183 #214

---------

Co-authored-by: Jeff Lockhart <[email protected]>
  • Loading branch information
fzhinkin and jeffdgr8 authored Sep 13, 2023
1 parent 7ab546c commit e421c04
Show file tree
Hide file tree
Showing 29 changed files with 1,461 additions and 165 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,35 @@ kotlin {

configureNativePlatforms()

val nativeTargets = nativeTargets()
val appleTargets = appleTargets()
val mingwTargets = mingwTargets()

/*
Native source sets hierarchy:
native
|-> apple
|-> nonApple
|-> mingw
|-> unix
|-> linux
|-> android
*/

sourceSets {
val nativeMain = createSourceSet("nativeMain", parent = commonMain.get(), children = nativeTargets)
val nativeTest = createSourceSet("nativeTest", parent = commonTest.get(), children = nativeTargets)
val nativeMain = createSourceSet("nativeMain", parent = commonMain.get())
val nativeTest = createSourceSet("nativeTest", parent = commonTest.get())
val nonAppleMain = createSourceSet("nonAppleMain", parent = nativeMain)
val nonAppleTest = createSourceSet("nonAppleTest", parent = nativeTest)
createSourceSet("appleMain", parent = nativeMain, children = appleTargets)
createSourceSet("appleTest", parent = nativeTest, children = appleTargets)
createSourceSet("mingwMain", parent = nonAppleMain, children = mingwTargets)
createSourceSet("mingwTest", parent = nonAppleTest, children = mingwTargets)
val unixMain = createSourceSet("unixMain", parent = nonAppleMain)
val unixTest = createSourceSet("unixTest", parent = nonAppleTest)
createSourceSet("linuxMain", parent = unixMain, children = linuxTargets())
createSourceSet("linuxTest", parent = unixTest, children = linuxTargets())
createSourceSet("androidMain", parent = unixMain, children = androidTargets())
createSourceSet("androidTest", parent = unixTest, children = androidTargets())
}
}

Expand Down Expand Up @@ -137,10 +159,6 @@ fun KotlinMultiplatformExtension.configureNativePlatforms() {
mingwX64()
}

fun nativeTargets(): List<String> {
return linuxTargets() + mingwTargets() + androidTargets()
}

fun appleTargets() = listOf(
"iosArm64",
"iosX64",
Expand Down
23 changes: 23 additions & 0 deletions core/android/src/files/FileSystemAndroid.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/

package kotlinx.io.files

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.toKString
import platform.posix.__posix_basename
import platform.posix.dirname

@OptIn(ExperimentalForeignApi::class)
internal actual fun dirnameImpl(path: String): String {
return dirname(path)?.toKString() ?: ""
}

@OptIn(ExperimentalForeignApi::class)
internal actual fun basenameImpl(path: String): String {
return __posix_basename(path)?.toKString() ?: ""
}

internal actual fun isAbsoluteImpl(path: String): Boolean = path.startsWith('/')
43 changes: 42 additions & 1 deletion core/api/kotlinx-io-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,53 @@ public final class kotlinx/io/Utf8Kt {
public static synthetic fun writeString$default (Lkotlinx/io/Sink;Ljava/lang/String;IIILjava/lang/Object;)V
}

public final class kotlinx/io/files/FileMetadata {
public fun <init> ()V
public fun <init> (ZZJ)V
public synthetic fun <init> (ZZJILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getSize ()J
public final fun isDirectory ()Z
public final fun isRegularFile ()Z
}

public abstract interface class kotlinx/io/files/FileSystem {
public abstract fun atomicMove (Lkotlinx/io/files/Path;Lkotlinx/io/files/Path;)V
public abstract fun createDirectories (Lkotlinx/io/files/Path;Z)V
public static synthetic fun createDirectories$default (Lkotlinx/io/files/FileSystem;Lkotlinx/io/files/Path;ZILjava/lang/Object;)V
public abstract fun delete (Lkotlinx/io/files/Path;Z)V
public static synthetic fun delete$default (Lkotlinx/io/files/FileSystem;Lkotlinx/io/files/Path;ZILjava/lang/Object;)V
public abstract fun exists (Lkotlinx/io/files/Path;)Z
public abstract fun metadataOrNull (Lkotlinx/io/files/Path;)Lkotlinx/io/files/FileMetadata;
public abstract fun sink (Lkotlinx/io/files/Path;Z)Lkotlinx/io/RawSink;
public static synthetic fun sink$default (Lkotlinx/io/files/FileSystem;Lkotlinx/io/files/Path;ZILjava/lang/Object;)Lkotlinx/io/RawSink;
public abstract fun source (Lkotlinx/io/files/Path;)Lkotlinx/io/RawSource;
}

public final class kotlinx/io/files/FileSystemJvmKt {
public static final field SystemFileSystem Lkotlinx/io/files/FileSystem;
public static final field SystemTemporaryDirectory Lkotlinx/io/files/Path;
}

public final class kotlinx/io/files/Path {
public fun equals (Ljava/lang/Object;)Z
public final fun getName ()Ljava/lang/String;
public final fun getParent ()Lkotlinx/io/files/Path;
public fun hashCode ()I
public final fun isAbsolute ()Z
public fun toString ()Ljava/lang/String;
}

public final class kotlinx/io/files/PathsKt {
public final class kotlinx/io/files/PathsJvmKt {
public static final field SystemPathSeparator C
public static final fun Path (Ljava/lang/String;)Lkotlinx/io/files/Path;
public static final fun sink (Lkotlinx/io/files/Path;)Lkotlinx/io/Sink;
public static final fun source (Lkotlinx/io/files/Path;)Lkotlinx/io/Source;
}

public final class kotlinx/io/files/PathsKt {
public static final fun Path (Ljava/lang/String;[Ljava/lang/String;)Lkotlinx/io/files/Path;
public static final fun Path (Lkotlinx/io/files/Path;[Ljava/lang/String;)Lkotlinx/io/files/Path;
public static final fun sinkDeprecated (Lkotlinx/io/files/Path;)Lkotlinx/io/Sink;
public static final fun sourceDeprecated (Lkotlinx/io/files/Path;)Lkotlinx/io/Source;
}

45 changes: 45 additions & 0 deletions core/apple/src/files/FileSystemApple.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
@file:OptIn(ExperimentalForeignApi::class)

package kotlinx.io.files

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.cstr
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import kotlinx.io.IOException
import platform.Foundation.NSTemporaryDirectory
import platform.posix.*


internal actual fun atomicMoveImpl(source: Path, destination: Path) {
if (rename(source.path, destination.path) != 0) {
throw IOException("Move failed: ${strerror(errno)?.toKString()}")
}
}

public actual val SystemTemporaryDirectory: Path
get() = Path(NSTemporaryDirectory())

internal actual fun dirnameImpl(path: String): String {
memScoped {
return dirname(path.cstr.ptr)?.toKString() ?: ""
}
}

internal actual fun basenameImpl(path: String): String {
memScoped {
return basename(path.cstr.ptr)?.toKString() ?: ""
}
}

internal actual fun isAbsoluteImpl(path: String): Boolean = path.startsWith('/')

internal actual fun mkdirImpl(path: String) {
if (mkdir(path, PermissionAllowAll) != 0) {
throw IOException("mkdir failed: ${strerror(errno)?.toKString()}")
}
}
12 changes: 4 additions & 8 deletions core/apple/test/NSInputStreamSourceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
package kotlinx.io

import kotlinx.io.files.Path
import kotlinx.io.files.sink
import kotlinx.io.files.SystemFileSystem
import platform.Foundation.NSInputStream
import platform.Foundation.NSTemporaryDirectory
import platform.Foundation.NSURL
import platform.Foundation.NSUUID
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
Expand All @@ -28,11 +26,9 @@ class NSInputStreamSourceTest {
@OptIn(ExperimentalStdlibApi::class)
@Test
fun nsInputStreamSourceFromFile() {
// can be replaced with createTempFile() when #183 is fixed
// https://github.com/Kotlin/kotlinx-io/issues/183
val file = "${NSTemporaryDirectory()}${NSUUID().UUIDString()}"
val file = tempFileName()
try {
Path(file).sink().use {
SystemFileSystem.sink(Path(file)).buffered().use {
it.writeString("example")
}

Expand All @@ -42,7 +38,7 @@ class NSInputStreamSourceTest {
assertEquals(7, source.readAtMostTo(buffer, 10))
assertEquals("example", buffer.readString())
} finally {
deleteFile(file)
SystemFileSystem.delete(Path(file))
}
}

Expand Down
4 changes: 4 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,7 @@ tasks.withType<DokkaTaskPartial>().configureEach {
)
}
}

animalsniffer {
annotation = "kotlinx.io.files.AnimalSnifferIgnore"
}
8 changes: 7 additions & 1 deletion core/common/src/-CommonPlatform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ package kotlinx.io

internal expect fun String.asUtf8ToByteArray(): ByteArray

/**
* Signals about a general issue occurred during I/O operation.
*/
public expect open class IOException(message: String?, cause: Throwable?) : Exception {
public constructor(message: String? = null)
}

/**
* Signals that the end of the file or stream was reached unexpectedly during an input operation.
*/
public expect open class EOFException(message: String? = null) : IOException


Expand All @@ -35,4 +41,4 @@ public expect open class EOFException(message: String? = null) : IOException
// This is a workaround that should be removed as soon as stdlib will support AutoCloseable
// actual typealias on JVM.
@OptIn(ExperimentalStdlibApi::class)
internal typealias AutoCloseableAlias = AutoCloseable
internal typealias AutoCloseableAlias = AutoCloseable
Loading

0 comments on commit e421c04

Please sign in to comment.