Skip to content

Commit e421c04

Browse files
fzhinkinjeffdgr8
andauthored
Extend filesystem support (#213)
* 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]>
1 parent 7ab546c commit e421c04

File tree

29 files changed

+1461
-165
lines changed

29 files changed

+1461
-165
lines changed

build-logic/src/main/kotlin/kotlinx/io/conventions/kotlinx-io-multiplatform.gradle.kts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,35 @@ kotlin {
6060

6161
configureNativePlatforms()
6262

63-
val nativeTargets = nativeTargets()
6463
val appleTargets = appleTargets()
64+
val mingwTargets = mingwTargets()
65+
66+
/*
67+
Native source sets hierarchy:
68+
native
69+
|-> apple
70+
|-> nonApple
71+
|-> mingw
72+
|-> unix
73+
|-> linux
74+
|-> android
75+
*/
76+
6577
sourceSets {
66-
val nativeMain = createSourceSet("nativeMain", parent = commonMain.get(), children = nativeTargets)
67-
val nativeTest = createSourceSet("nativeTest", parent = commonTest.get(), children = nativeTargets)
78+
val nativeMain = createSourceSet("nativeMain", parent = commonMain.get())
79+
val nativeTest = createSourceSet("nativeTest", parent = commonTest.get())
80+
val nonAppleMain = createSourceSet("nonAppleMain", parent = nativeMain)
81+
val nonAppleTest = createSourceSet("nonAppleTest", parent = nativeTest)
6882
createSourceSet("appleMain", parent = nativeMain, children = appleTargets)
6983
createSourceSet("appleTest", parent = nativeTest, children = appleTargets)
84+
createSourceSet("mingwMain", parent = nonAppleMain, children = mingwTargets)
85+
createSourceSet("mingwTest", parent = nonAppleTest, children = mingwTargets)
86+
val unixMain = createSourceSet("unixMain", parent = nonAppleMain)
87+
val unixTest = createSourceSet("unixTest", parent = nonAppleTest)
88+
createSourceSet("linuxMain", parent = unixMain, children = linuxTargets())
89+
createSourceSet("linuxTest", parent = unixTest, children = linuxTargets())
90+
createSourceSet("androidMain", parent = unixMain, children = androidTargets())
91+
createSourceSet("androidTest", parent = unixTest, children = androidTargets())
7092
}
7193
}
7294

@@ -137,10 +159,6 @@ fun KotlinMultiplatformExtension.configureNativePlatforms() {
137159
mingwX64()
138160
}
139161

140-
fun nativeTargets(): List<String> {
141-
return linuxTargets() + mingwTargets() + androidTargets()
142-
}
143-
144162
fun appleTargets() = listOf(
145163
"iosArm64",
146164
"iosX64",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.io.files
7+
8+
import kotlinx.cinterop.ExperimentalForeignApi
9+
import kotlinx.cinterop.toKString
10+
import platform.posix.__posix_basename
11+
import platform.posix.dirname
12+
13+
@OptIn(ExperimentalForeignApi::class)
14+
internal actual fun dirnameImpl(path: String): String {
15+
return dirname(path)?.toKString() ?: ""
16+
}
17+
18+
@OptIn(ExperimentalForeignApi::class)
19+
internal actual fun basenameImpl(path: String): String {
20+
return __posix_basename(path)?.toKString() ?: ""
21+
}
22+
23+
internal actual fun isAbsoluteImpl(path: String): Boolean = path.startsWith('/')

core/api/kotlinx-io-core.api

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,53 @@ public final class kotlinx/io/Utf8Kt {
195195
public static synthetic fun writeString$default (Lkotlinx/io/Sink;Ljava/lang/String;IIILjava/lang/Object;)V
196196
}
197197

198+
public final class kotlinx/io/files/FileMetadata {
199+
public fun <init> ()V
200+
public fun <init> (ZZJ)V
201+
public synthetic fun <init> (ZZJILkotlin/jvm/internal/DefaultConstructorMarker;)V
202+
public final fun getSize ()J
203+
public final fun isDirectory ()Z
204+
public final fun isRegularFile ()Z
205+
}
206+
207+
public abstract interface class kotlinx/io/files/FileSystem {
208+
public abstract fun atomicMove (Lkotlinx/io/files/Path;Lkotlinx/io/files/Path;)V
209+
public abstract fun createDirectories (Lkotlinx/io/files/Path;Z)V
210+
public static synthetic fun createDirectories$default (Lkotlinx/io/files/FileSystem;Lkotlinx/io/files/Path;ZILjava/lang/Object;)V
211+
public abstract fun delete (Lkotlinx/io/files/Path;Z)V
212+
public static synthetic fun delete$default (Lkotlinx/io/files/FileSystem;Lkotlinx/io/files/Path;ZILjava/lang/Object;)V
213+
public abstract fun exists (Lkotlinx/io/files/Path;)Z
214+
public abstract fun metadataOrNull (Lkotlinx/io/files/Path;)Lkotlinx/io/files/FileMetadata;
215+
public abstract fun sink (Lkotlinx/io/files/Path;Z)Lkotlinx/io/RawSink;
216+
public static synthetic fun sink$default (Lkotlinx/io/files/FileSystem;Lkotlinx/io/files/Path;ZILjava/lang/Object;)Lkotlinx/io/RawSink;
217+
public abstract fun source (Lkotlinx/io/files/Path;)Lkotlinx/io/RawSource;
218+
}
219+
220+
public final class kotlinx/io/files/FileSystemJvmKt {
221+
public static final field SystemFileSystem Lkotlinx/io/files/FileSystem;
222+
public static final field SystemTemporaryDirectory Lkotlinx/io/files/Path;
223+
}
224+
198225
public final class kotlinx/io/files/Path {
226+
public fun equals (Ljava/lang/Object;)Z
227+
public final fun getName ()Ljava/lang/String;
228+
public final fun getParent ()Lkotlinx/io/files/Path;
229+
public fun hashCode ()I
230+
public final fun isAbsolute ()Z
231+
public fun toString ()Ljava/lang/String;
199232
}
200233

201-
public final class kotlinx/io/files/PathsKt {
234+
public final class kotlinx/io/files/PathsJvmKt {
235+
public static final field SystemPathSeparator C
202236
public static final fun Path (Ljava/lang/String;)Lkotlinx/io/files/Path;
203237
public static final fun sink (Lkotlinx/io/files/Path;)Lkotlinx/io/Sink;
204238
public static final fun source (Lkotlinx/io/files/Path;)Lkotlinx/io/Source;
205239
}
206240

241+
public final class kotlinx/io/files/PathsKt {
242+
public static final fun Path (Ljava/lang/String;[Ljava/lang/String;)Lkotlinx/io/files/Path;
243+
public static final fun Path (Lkotlinx/io/files/Path;[Ljava/lang/String;)Lkotlinx/io/files/Path;
244+
public static final fun sinkDeprecated (Lkotlinx/io/files/Path;)Lkotlinx/io/Sink;
245+
public static final fun sourceDeprecated (Lkotlinx/io/files/Path;)Lkotlinx/io/Source;
246+
}
247+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
4+
*/
5+
@file:OptIn(ExperimentalForeignApi::class)
6+
7+
package kotlinx.io.files
8+
9+
import kotlinx.cinterop.ExperimentalForeignApi
10+
import kotlinx.cinterop.cstr
11+
import kotlinx.cinterop.memScoped
12+
import kotlinx.cinterop.toKString
13+
import kotlinx.io.IOException
14+
import platform.Foundation.NSTemporaryDirectory
15+
import platform.posix.*
16+
17+
18+
internal actual fun atomicMoveImpl(source: Path, destination: Path) {
19+
if (rename(source.path, destination.path) != 0) {
20+
throw IOException("Move failed: ${strerror(errno)?.toKString()}")
21+
}
22+
}
23+
24+
public actual val SystemTemporaryDirectory: Path
25+
get() = Path(NSTemporaryDirectory())
26+
27+
internal actual fun dirnameImpl(path: String): String {
28+
memScoped {
29+
return dirname(path.cstr.ptr)?.toKString() ?: ""
30+
}
31+
}
32+
33+
internal actual fun basenameImpl(path: String): String {
34+
memScoped {
35+
return basename(path.cstr.ptr)?.toKString() ?: ""
36+
}
37+
}
38+
39+
internal actual fun isAbsoluteImpl(path: String): Boolean = path.startsWith('/')
40+
41+
internal actual fun mkdirImpl(path: String) {
42+
if (mkdir(path, PermissionAllowAll) != 0) {
43+
throw IOException("mkdir failed: ${strerror(errno)?.toKString()}")
44+
}
45+
}

core/apple/test/NSInputStreamSourceTest.kt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
package kotlinx.io
77

88
import kotlinx.io.files.Path
9-
import kotlinx.io.files.sink
9+
import kotlinx.io.files.SystemFileSystem
1010
import platform.Foundation.NSInputStream
11-
import platform.Foundation.NSTemporaryDirectory
1211
import platform.Foundation.NSURL
13-
import platform.Foundation.NSUUID
1412
import kotlin.test.Test
1513
import kotlin.test.assertEquals
1614
import kotlin.test.assertFailsWith
@@ -28,11 +26,9 @@ class NSInputStreamSourceTest {
2826
@OptIn(ExperimentalStdlibApi::class)
2927
@Test
3028
fun nsInputStreamSourceFromFile() {
31-
// can be replaced with createTempFile() when #183 is fixed
32-
// https://github.com/Kotlin/kotlinx-io/issues/183
33-
val file = "${NSTemporaryDirectory()}${NSUUID().UUIDString()}"
29+
val file = tempFileName()
3430
try {
35-
Path(file).sink().use {
31+
SystemFileSystem.sink(Path(file)).buffered().use {
3632
it.writeString("example")
3733
}
3834

@@ -42,7 +38,7 @@ class NSInputStreamSourceTest {
4238
assertEquals(7, source.readAtMostTo(buffer, 10))
4339
assertEquals("example", buffer.readString())
4440
} finally {
45-
deleteFile(file)
41+
SystemFileSystem.delete(Path(file))
4642
}
4743
}
4844

core/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,7 @@ tasks.withType<DokkaTaskPartial>().configureEach {
7272
)
7373
}
7474
}
75+
76+
animalsniffer {
77+
annotation = "kotlinx.io.files.AnimalSnifferIgnore"
78+
}

core/common/src/-CommonPlatform.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,16 @@ package kotlinx.io
2323

2424
internal expect fun String.asUtf8ToByteArray(): ByteArray
2525

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

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

3238

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

0 commit comments

Comments
 (0)