diff --git a/README.md b/README.md index ae8fbdb46..384aded1c 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,9 @@ which includes the `FileSystem` interface and its default implementation - `Syst There are several `kotlinx-io` modules: - [kotlinx-io-bytestring](./bytestring) - provides `ByteString`. -- [kotlinx-io-core](./core) - provides IO primitives (`Buffer`, `Source`, `Sink`), filesystems support, depends on `kotlinx-io-bytestring`. -- [kotlinx-io-okio](./integration/okio) - bridges `kotlinx-io` and `Okio` `ByteString`, `kotlinx.io.RawSource` and `okio.Source`, `kotlinx.io.RawSink` and `okio.Sink`. +- [kotlinx-io-core](./core) - provides IO primitives (`Buffer`, `Source`, `Sink`), depends on `kotlinx-io-bytestring`. +- [kotlinx-io-filesystem](./filesystem) - provides basic filesystem support, depends on `kotlinx-io-core`. +- [kotlinx-io-okio](./integration/okio) - bridges `kotlinx-io` and `Okio` `ByteString`, `kotlinx.io.RawSource` and `okio.Source`, `kotlinx.io.RawSink` and `okio.Sink`. ## Using in your projects @@ -82,6 +83,7 @@ Add the library to dependencies: On JVM, `kotlinx-io` supports Java Modules: - `kotlinx-io-bytestring` library provides `kotlinx.io.bytestring` module; - `kotlinx-io-core` library provides `kotlinx.io.core` module. +- `kotlinx-io-filesystem` library provides `kotlinx.io.filesystem` module. - `kotlinx-io-okio` library provides `kotlinx.io.okio` module. Read [this](https://kotlinlang.org/docs/gradle-configure-project.html#configure-with-java-modules-jpms-enabled) article diff --git a/build.gradle.kts b/build.gradle.kts index 2e45281b6..919925f9d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,6 +33,7 @@ apiValidation { dependencies { kover(project(":kotlinx-io-core")) kover(project(":kotlinx-io-bytestring")) + kover(project(":kotlinx-io-filesystem")) kover(project(":kotlinx-io-okio")) } diff --git a/core/Module.md b/core/Module.md index 0fef6a6ca..647a4b940 100644 --- a/core/Module.md +++ b/core/Module.md @@ -75,19 +75,10 @@ fun Message.Companion.fromBson(source: Source): Message { } ``` -# Package kotlinx.io - -Core IO primitives. - -# Package kotlinx.io.files - -Basic API for working with files. - #### Thread-safety guarantees Until stated otherwise, types and functions provided by the library are not thread safe. -#### Known issues +# Package kotlinx.io -- [#312](https://github.com/Kotlin/kotlinx-io/issues/312) For `wasmWasi` target, directory listing ([kotlinx.io.files.FileSystem.list]) does not work with NodeJS runtime on Windows, -as `fd_readdir` function is [not implemented there](https://github.com/nodejs/node/blob/6f4d6011ea1b448cf21f5d363c44e4a4c56ca34c/deps/uvwasi/src/uvwasi.c#L19). +Core IO primitives. diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index e28bc59cd..2f73a77e2 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -236,58 +236,6 @@ 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 ()V - public fun (ZZJ)V - public synthetic fun (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 list (Lkotlinx/io/files/Path;)Ljava/util/Collection; - public abstract fun metadataOrNull (Lkotlinx/io/files/Path;)Lkotlinx/io/files/FileMetadata; - public abstract fun resolve (Lkotlinx/io/files/Path;)Lkotlinx/io/files/Path; - 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/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; -} - public abstract interface class kotlinx/io/unsafe/BufferIterationContext : kotlinx/io/unsafe/SegmentReadContext { public abstract fun next (Lkotlinx/io/Segment;)Lkotlinx/io/Segment; } diff --git a/core/api/kotlinx-io-core.klib.api b/core/api/kotlinx-io-core.klib.api index 02d9050a7..8dddd115b 100644 --- a/core/api/kotlinx-io-core.klib.api +++ b/core/api/kotlinx-io-core.klib.api @@ -45,18 +45,6 @@ abstract interface kotlinx.io/RawSource : kotlin/AutoCloseable { // kotlinx.io/R abstract fun readAtMostTo(kotlinx.io/Buffer, kotlin/Long): kotlin/Long // kotlinx.io/RawSource.readAtMostTo|readAtMostTo(kotlinx.io.Buffer;kotlin.Long){}[0] } -sealed interface kotlinx.io.files/FileSystem { // kotlinx.io.files/FileSystem|null[0] - abstract fun atomicMove(kotlinx.io.files/Path, kotlinx.io.files/Path) // kotlinx.io.files/FileSystem.atomicMove|atomicMove(kotlinx.io.files.Path;kotlinx.io.files.Path){}[0] - abstract fun createDirectories(kotlinx.io.files/Path, kotlin/Boolean = ...) // kotlinx.io.files/FileSystem.createDirectories|createDirectories(kotlinx.io.files.Path;kotlin.Boolean){}[0] - abstract fun delete(kotlinx.io.files/Path, kotlin/Boolean = ...) // kotlinx.io.files/FileSystem.delete|delete(kotlinx.io.files.Path;kotlin.Boolean){}[0] - abstract fun exists(kotlinx.io.files/Path): kotlin/Boolean // kotlinx.io.files/FileSystem.exists|exists(kotlinx.io.files.Path){}[0] - abstract fun list(kotlinx.io.files/Path): kotlin.collections/Collection // kotlinx.io.files/FileSystem.list|list(kotlinx.io.files.Path){}[0] - abstract fun metadataOrNull(kotlinx.io.files/Path): kotlinx.io.files/FileMetadata? // kotlinx.io.files/FileSystem.metadataOrNull|metadataOrNull(kotlinx.io.files.Path){}[0] - abstract fun resolve(kotlinx.io.files/Path): kotlinx.io.files/Path // kotlinx.io.files/FileSystem.resolve|resolve(kotlinx.io.files.Path){}[0] - abstract fun sink(kotlinx.io.files/Path, kotlin/Boolean = ...): kotlinx.io/RawSink // kotlinx.io.files/FileSystem.sink|sink(kotlinx.io.files.Path;kotlin.Boolean){}[0] - abstract fun source(kotlinx.io.files/Path): kotlinx.io/RawSource // kotlinx.io.files/FileSystem.source|source(kotlinx.io.files.Path){}[0] -} - sealed interface kotlinx.io/Sink : kotlinx.io/RawSink { // kotlinx.io/Sink|null[0] abstract val buffer // kotlinx.io/Sink.buffer|{}buffer[0] abstract fun (): kotlinx.io/Buffer // kotlinx.io/Sink.buffer.|(){}[0] @@ -91,30 +79,6 @@ sealed interface kotlinx.io/Source : kotlinx.io/RawSource { // kotlinx.io/Source abstract fun transferTo(kotlinx.io/RawSink): kotlin/Long // kotlinx.io/Source.transferTo|transferTo(kotlinx.io.RawSink){}[0] } -final class kotlinx.io.files/FileMetadata { // kotlinx.io.files/FileMetadata|null[0] - constructor (kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Long = ...) // kotlinx.io.files/FileMetadata.|(kotlin.Boolean;kotlin.Boolean;kotlin.Long){}[0] - - final val isDirectory // kotlinx.io.files/FileMetadata.isDirectory|{}isDirectory[0] - final fun (): kotlin/Boolean // kotlinx.io.files/FileMetadata.isDirectory.|(){}[0] - final val isRegularFile // kotlinx.io.files/FileMetadata.isRegularFile|{}isRegularFile[0] - final fun (): kotlin/Boolean // kotlinx.io.files/FileMetadata.isRegularFile.|(){}[0] - final val size // kotlinx.io.files/FileMetadata.size|{}size[0] - final fun (): kotlin/Long // kotlinx.io.files/FileMetadata.size.|(){}[0] -} - -final class kotlinx.io.files/Path { // kotlinx.io.files/Path|null[0] - final val isAbsolute // kotlinx.io.files/Path.isAbsolute|{}isAbsolute[0] - final fun (): kotlin/Boolean // kotlinx.io.files/Path.isAbsolute.|(){}[0] - final val name // kotlinx.io.files/Path.name|{}name[0] - final fun (): kotlin/String // kotlinx.io.files/Path.name.|(){}[0] - final val parent // kotlinx.io.files/Path.parent|{}parent[0] - final fun (): kotlinx.io.files/Path? // kotlinx.io.files/Path.parent.|(){}[0] - - final fun equals(kotlin/Any?): kotlin/Boolean // kotlinx.io.files/Path.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // kotlinx.io.files/Path.hashCode|hashCode(){}[0] - final fun toString(): kotlin/String // kotlinx.io.files/Path.toString|toString(){}[0] -} - final class kotlinx.io/Buffer : kotlinx.io/Sink, kotlinx.io/Source { // kotlinx.io/Buffer|null[0] constructor () // kotlinx.io/Buffer.|(){}[0] @@ -190,10 +154,6 @@ final class kotlinx.io/Segment { // kotlinx.io/Segment|null[0] final fun writeBackData(kotlin/ByteArray, kotlin/Int) // kotlinx.io/Segment.writeBackData|writeBackData(kotlin.ByteArray;kotlin.Int){}[0] } -open class kotlinx.io.files/FileNotFoundException : kotlinx.io/IOException { // kotlinx.io.files/FileNotFoundException|null[0] - constructor (kotlin/String?) // kotlinx.io.files/FileNotFoundException.|(kotlin.String?){}[0] -} - open class kotlinx.io/EOFException : kotlinx.io/IOException { // kotlinx.io/EOFException|null[0] constructor () // kotlinx.io/EOFException.|(){}[0] constructor (kotlin/String?) // kotlinx.io/EOFException.|(kotlin.String?){}[0] @@ -221,12 +181,6 @@ final object kotlinx.io.unsafe/UnsafeBufferOperations { // kotlinx.io.unsafe/Uns final inline fun writeToTail(kotlinx.io/Buffer, kotlin/Int, kotlin/Function3): kotlin/Int // kotlinx.io.unsafe/UnsafeBufferOperations.writeToTail|writeToTail(kotlinx.io.Buffer;kotlin.Int;kotlin.Function3){}[0] } -final val kotlinx.io.files/SystemFileSystem // kotlinx.io.files/SystemFileSystem|{}SystemFileSystem[0] - final fun (): kotlinx.io.files/FileSystem // kotlinx.io.files/SystemFileSystem.|(){}[0] -final val kotlinx.io.files/SystemPathSeparator // kotlinx.io.files/SystemPathSeparator|{}SystemPathSeparator[0] - final fun (): kotlin/Char // kotlinx.io.files/SystemPathSeparator.|(){}[0] -final val kotlinx.io.files/SystemTemporaryDirectory // kotlinx.io.files/SystemTemporaryDirectory|{}SystemTemporaryDirectory[0] - final fun (): kotlinx.io.files/Path // kotlinx.io.files/SystemTemporaryDirectory.|(){}[0] final val kotlinx.io.unsafe/BufferIterationContextImpl // kotlinx.io.unsafe/BufferIterationContextImpl|{}BufferIterationContextImpl[0] final fun (): kotlinx.io.unsafe/BufferIterationContext // kotlinx.io.unsafe/BufferIterationContextImpl.|(){}[0] final val kotlinx.io.unsafe/SegmentReadContextImpl // kotlinx.io.unsafe/SegmentReadContextImpl|{}SegmentReadContextImpl[0] @@ -234,8 +188,6 @@ final val kotlinx.io.unsafe/SegmentReadContextImpl // kotlinx.io.unsafe/SegmentR final val kotlinx.io.unsafe/SegmentWriteContextImpl // kotlinx.io.unsafe/SegmentWriteContextImpl|{}SegmentWriteContextImpl[0] final fun (): kotlinx.io.unsafe/SegmentWriteContext // kotlinx.io.unsafe/SegmentWriteContextImpl.|(){}[0] -final fun (kotlinx.io.files/Path).kotlinx.io.files/sink(): kotlinx.io/Sink // kotlinx.io.files/sink|sink@kotlinx.io.files.Path(){}[0] -final fun (kotlinx.io.files/Path).kotlinx.io.files/source(): kotlinx.io/Source // kotlinx.io.files/source|source@kotlinx.io.files.Path(){}[0] final fun (kotlinx.io/Buffer).kotlinx.io/indexOf(kotlin/Byte, kotlin/Long = ..., kotlin/Long = ...): kotlin/Long // kotlinx.io/indexOf|indexOf@kotlinx.io.Buffer(kotlin.Byte;kotlin.Long;kotlin.Long){}[0] final fun (kotlinx.io/Buffer).kotlinx.io/indexOf(kotlinx.io.bytestring/ByteString, kotlin/Long = ...): kotlin/Long // kotlinx.io/indexOf|indexOf@kotlinx.io.Buffer(kotlinx.io.bytestring.ByteString;kotlin.Long){}[0] final fun (kotlinx.io/Buffer).kotlinx.io/readString(): kotlin/String // kotlinx.io/readString|readString@kotlinx.io.Buffer(){}[0] @@ -292,9 +244,6 @@ final fun (kotlinx.io/Source).kotlinx.io/readULongLe(): kotlin/ULong // kotlinx. final fun (kotlinx.io/Source).kotlinx.io/readUShort(): kotlin/UShort // kotlinx.io/readUShort|readUShort@kotlinx.io.Source(){}[0] final fun (kotlinx.io/Source).kotlinx.io/readUShortLe(): kotlin/UShort // kotlinx.io/readUShortLe|readUShortLe@kotlinx.io.Source(){}[0] final fun (kotlinx.io/Source).kotlinx.io/startsWith(kotlin/Byte): kotlin/Boolean // kotlinx.io/startsWith|startsWith@kotlinx.io.Source(kotlin.Byte){}[0] -final fun kotlinx.io.files/Path(kotlin/String): kotlinx.io.files/Path // kotlinx.io.files/Path|Path(kotlin.String){}[0] -final fun kotlinx.io.files/Path(kotlin/String, kotlin/Array...): kotlinx.io.files/Path // kotlinx.io.files/Path|Path(kotlin.String;kotlin.Array...){}[0] -final fun kotlinx.io.files/Path(kotlinx.io.files/Path, kotlin/Array...): kotlinx.io.files/Path // kotlinx.io.files/Path|Path(kotlinx.io.files.Path;kotlin.Array...){}[0] final fun kotlinx.io/discardingSink(): kotlinx.io/RawSink // kotlinx.io/discardingSink|discardingSink(){}[0] final inline fun (kotlinx.io.unsafe/SegmentReadContext).kotlinx.io.unsafe/withData(kotlinx.io/Segment, kotlin/Function3) // kotlinx.io.unsafe/withData|withData@kotlinx.io.unsafe.SegmentReadContext(kotlinx.io.Segment;kotlin.Function3){}[0] final inline fun (kotlinx.io/Sink).kotlinx.io/writeToInternalBuffer(kotlin/Function1) // kotlinx.io/writeToInternalBuffer|writeToInternalBuffer@kotlinx.io.Sink(kotlin.Function1){}[0] diff --git a/core/apple/test/NSInputStreamSourceTest.kt b/core/apple/test/NSInputStreamSourceTest.kt index 94bcf39bc..8247a818c 100644 --- a/core/apple/test/NSInputStreamSourceTest.kt +++ b/core/apple/test/NSInputStreamSourceTest.kt @@ -4,11 +4,7 @@ */ package kotlinx.io - -import kotlinx.io.files.Path -import kotlinx.io.files.SystemFileSystem import platform.Foundation.NSInputStream -import platform.Foundation.NSURL import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -23,24 +19,6 @@ class NSInputStreamSourceTest { assertEquals("a", buffer.readString()) } - @Test - fun nsInputStreamSourceFromFile() { - val file = tempFileName() - try { - SystemFileSystem.sink(Path(file)).buffered().use { - it.writeString("example") - } - - val input = NSInputStream(uRL = NSURL.fileURLWithPath(file)) - val source = input.asSource() - val buffer = Buffer() - assertEquals(7, source.readAtMostTo(buffer, 10)) - assertEquals("example", buffer.readString()) - } finally { - SystemFileSystem.delete(Path(file)) - } - } - @Test fun sourceFromInputStream() { val input = NSInputStream(data = ("a" + "b".repeat(Segment.SIZE * 2) + "c").encodeToByteArray().toNSData()) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 39ea02e77..a2f44aeb7 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -3,9 +3,6 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. */ -import org.gradle.internal.os.OperatingSystem -import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl - plugins { id("kotlinx-io-multiplatform") id("kotlinx-io-publish") @@ -31,18 +28,6 @@ kotlin { } } } - @OptIn(ExperimentalWasmDsl::class) - wasmWasi { - nodejs { - testTask { - // fd_readdir is unsupported on Windows: - // https://github.com/nodejs/node/blob/6f4d6011ea1b448cf21f5d363c44e4a4c56ca34c/deps/uvwasi/src/uvwasi.c#L19 - if (OperatingSystem.current().isWindows) { - filter.setExcludePatterns("*SmokeFileTest.listDirectory") - } - } - } - } sourceSets { commonMain.dependencies { @@ -54,32 +39,6 @@ kotlin { } } -tasks.named("wasmWasiNodeTest") { - // TODO: remove once https://youtrack.jetbrains.com/issue/KT-65179 solved - doFirst { - val layout = project.layout - val templateFile = layout.projectDirectory.file("wasmWasi/test/test-driver.mjs.template").asFile - - val driverFile = layout.buildDirectory.file( - "compileSync/wasmWasi/test/testDevelopmentExecutable/kotlin/kotlinx-io-kotlinx-io-core-wasm-wasi-test.mjs" - ) - - fun File.mkdirsAndEscape(): String { - mkdirs() - return absolutePath.replace("\\", "\\\\") - } - - val tmpDir = temporaryDir.resolve("kotlinx-io-core-wasi-test").mkdirsAndEscape() - val tmpDir2 = temporaryDir.resolve("kotlinx-io-core-wasi-test-2").mkdirsAndEscape() - - val newDriver = templateFile.readText() - .replace("", tmpDir, false) - .replace("", tmpDir2, false) - - driverFile.get().asFile.writeText(newDriver) - } -} - animalsniffer { annotation = "kotlinx.io.files.AnimalSnifferIgnore" } diff --git a/core/common/test/util.kt b/core/common/test/util.kt index 3d1570511..48b4b15f1 100644 --- a/core/common/test/util.kt +++ b/core/common/test/util.kt @@ -40,8 +40,6 @@ fun assertNoEmptySegments(buffer: Buffer) { assertTrue(segmentSizes(buffer).all { it != 0 }, "Expected all segments to be non-empty") } -expect fun tempFileName(): String - private fun fromHexChar(char: Char): Int { val code = char.code return when (code) { diff --git a/core/js/src/-PlatformJs.kt b/core/js/src/-PlatformJs.kt index 7e198e25c..274c50813 100644 --- a/core/js/src/-PlatformJs.kt +++ b/core/js/src/-PlatformJs.kt @@ -22,12 +22,3 @@ public actual open class EOFException : IOException { public constructor(message: String?, cause: Throwable?) : super(message, cause) } - -internal actual fun withCaughtException(block: () -> Unit): Throwable? { - try { - block() - return null - } catch (t: Throwable) { - return t - } -} diff --git a/core/jvm/module/module-info.java b/core/jvm/module/module-info.java index 3ff9848aa..ff6d38789 100644 --- a/core/jvm/module/module-info.java +++ b/core/jvm/module/module-info.java @@ -3,6 +3,5 @@ requires transitive kotlinx.io.bytestring; exports kotlinx.io; - exports kotlinx.io.files; exports kotlinx.io.unsafe; } diff --git a/core/jvm/test/utilJVM.kt b/core/jvm/test/utilJVM.kt index a17d5ea4b..c933b169e 100644 --- a/core/jvm/test/utilJVM.kt +++ b/core/jvm/test/utilJVM.kt @@ -4,23 +4,8 @@ */ package kotlinx.io -import kotlinx.io.files.SystemTemporaryDirectory -import java.io.File -import kotlin.random.Random import kotlin.test.assertEquals -@OptIn(ExperimentalStdlibApi::class) -actual fun tempFileName(): String { - val tmpDir = SystemTemporaryDirectory.file - while (true) { - val randomString = Random.nextBytes(32).toHexString() - val res = File(tmpDir, randomString) - if (!res.exists()) { - return res.absolutePath - } - } -} - fun assertByteArrayEquals(expectedUtf8: String, b: ByteArray) { assertEquals(expectedUtf8, b.toString(Charsets.UTF_8)) } diff --git a/core/native/test/util.kt b/core/native/test/util.kt index f8f50c5fd..574b3f9fd 100644 --- a/core/native/test/util.kt +++ b/core/native/test/util.kt @@ -7,22 +7,6 @@ package kotlinx.io -import kotlinx.io.files.SystemTemporaryDirectory -import platform.posix.F_OK -import platform.posix.access -import kotlin.random.Random -@OptIn(ExperimentalStdlibApi::class) -actual fun tempFileName(): String { - val tmpDir = SystemTemporaryDirectory.path - for (i in 0 until 10) { - val name = Random.nextBytes(32).toHexString() - val path = "$tmpDir/$name" - if (access(path, F_OK) != 0) { - return path - } - } - throw IOException("Failed to generate temp file name") -} internal actual fun String.asUtf8ToByteArray(): ByteArray = commonAsUtf8ToByteArray() diff --git a/filesystem/Module.md b/filesystem/Module.md new file mode 100644 index 000000000..ee46d7b95 --- /dev/null +++ b/filesystem/Module.md @@ -0,0 +1,20 @@ +# Module kotlinx-io-filesystem + +The module provides experimental files and filesystem support. The API is unstable and will change in the future. + +#### Thread-safety guarantees + +Until stated otherwise, types and functions provided by the library are not thread safe. + +#### Known issues + +[//]: <> (TODO: Link to SystemFileSystem doesn't work) +- For JS and Wasm, [kotlinx.io.files.SystemFileSystem] is supported only in the NodeJs environment. Attempts to use it + in the browser environment will result in a runtime error. +- [#312](https://github.com/Kotlin/kotlinx-io/issues/312) For `wasmWasi` target, directory listing ([kotlinx.io.files.FileSystem.list]) does not work with NodeJS runtime on Windows, + as `fd_readdir` function is [not implemented there](https://github.com/nodejs/node/blob/6f4d6011ea1b448cf21f5d363c44e4a4c56ca34c/deps/uvwasi/src/uvwasi.c#L19). + +# Package kotlinx.io.files + +Basic API for working with files. + diff --git a/core/androidNative/src/files/FileSystemAndroid.kt b/filesystem/androidNative/src/files/FileSystemAndroid.kt similarity index 100% rename from core/androidNative/src/files/FileSystemAndroid.kt rename to filesystem/androidNative/src/files/FileSystemAndroid.kt diff --git a/filesystem/api/kotlinx-io-filesystem.api b/filesystem/api/kotlinx-io-filesystem.api new file mode 100644 index 000000000..219ed7171 --- /dev/null +++ b/filesystem/api/kotlinx-io-filesystem.api @@ -0,0 +1,52 @@ +public final class kotlinx/io/files/FileMetadata { + public fun ()V + public fun (ZZJ)V + public synthetic fun (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 list (Lkotlinx/io/files/Path;)Ljava/util/Collection; + public abstract fun metadataOrNull (Lkotlinx/io/files/Path;)Lkotlinx/io/files/FileMetadata; + public abstract fun resolve (Lkotlinx/io/files/Path;)Lkotlinx/io/files/Path; + 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/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; +} + diff --git a/filesystem/api/kotlinx-io-filesystem.klib.api b/filesystem/api/kotlinx-io-filesystem.klib.api new file mode 100644 index 000000000..0e562490d --- /dev/null +++ b/filesystem/api/kotlinx-io-filesystem.klib.api @@ -0,0 +1,60 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm32Hfp, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, wasmWasi, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +sealed interface kotlinx.io.files/FileSystem { // kotlinx.io.files/FileSystem|null[0] + abstract fun atomicMove(kotlinx.io.files/Path, kotlinx.io.files/Path) // kotlinx.io.files/FileSystem.atomicMove|atomicMove(kotlinx.io.files.Path;kotlinx.io.files.Path){}[0] + abstract fun createDirectories(kotlinx.io.files/Path, kotlin/Boolean = ...) // kotlinx.io.files/FileSystem.createDirectories|createDirectories(kotlinx.io.files.Path;kotlin.Boolean){}[0] + abstract fun delete(kotlinx.io.files/Path, kotlin/Boolean = ...) // kotlinx.io.files/FileSystem.delete|delete(kotlinx.io.files.Path;kotlin.Boolean){}[0] + abstract fun exists(kotlinx.io.files/Path): kotlin/Boolean // kotlinx.io.files/FileSystem.exists|exists(kotlinx.io.files.Path){}[0] + abstract fun list(kotlinx.io.files/Path): kotlin.collections/Collection // kotlinx.io.files/FileSystem.list|list(kotlinx.io.files.Path){}[0] + abstract fun metadataOrNull(kotlinx.io.files/Path): kotlinx.io.files/FileMetadata? // kotlinx.io.files/FileSystem.metadataOrNull|metadataOrNull(kotlinx.io.files.Path){}[0] + abstract fun resolve(kotlinx.io.files/Path): kotlinx.io.files/Path // kotlinx.io.files/FileSystem.resolve|resolve(kotlinx.io.files.Path){}[0] + abstract fun sink(kotlinx.io.files/Path, kotlin/Boolean = ...): kotlinx.io/RawSink // kotlinx.io.files/FileSystem.sink|sink(kotlinx.io.files.Path;kotlin.Boolean){}[0] + abstract fun source(kotlinx.io.files/Path): kotlinx.io/RawSource // kotlinx.io.files/FileSystem.source|source(kotlinx.io.files.Path){}[0] +} + +final class kotlinx.io.files/FileMetadata { // kotlinx.io.files/FileMetadata|null[0] + constructor (kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Long = ...) // kotlinx.io.files/FileMetadata.|(kotlin.Boolean;kotlin.Boolean;kotlin.Long){}[0] + + final val isDirectory // kotlinx.io.files/FileMetadata.isDirectory|{}isDirectory[0] + final fun (): kotlin/Boolean // kotlinx.io.files/FileMetadata.isDirectory.|(){}[0] + final val isRegularFile // kotlinx.io.files/FileMetadata.isRegularFile|{}isRegularFile[0] + final fun (): kotlin/Boolean // kotlinx.io.files/FileMetadata.isRegularFile.|(){}[0] + final val size // kotlinx.io.files/FileMetadata.size|{}size[0] + final fun (): kotlin/Long // kotlinx.io.files/FileMetadata.size.|(){}[0] +} + +final class kotlinx.io.files/Path { // kotlinx.io.files/Path|null[0] + final val isAbsolute // kotlinx.io.files/Path.isAbsolute|{}isAbsolute[0] + final fun (): kotlin/Boolean // kotlinx.io.files/Path.isAbsolute.|(){}[0] + final val name // kotlinx.io.files/Path.name|{}name[0] + final fun (): kotlin/String // kotlinx.io.files/Path.name.|(){}[0] + final val parent // kotlinx.io.files/Path.parent|{}parent[0] + final fun (): kotlinx.io.files/Path? // kotlinx.io.files/Path.parent.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // kotlinx.io.files/Path.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // kotlinx.io.files/Path.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // kotlinx.io.files/Path.toString|toString(){}[0] +} + +open class kotlinx.io.files/FileNotFoundException : kotlinx.io/IOException { // kotlinx.io.files/FileNotFoundException|null[0] + constructor (kotlin/String?) // kotlinx.io.files/FileNotFoundException.|(kotlin.String?){}[0] +} + +final val kotlinx.io.files/SystemFileSystem // kotlinx.io.files/SystemFileSystem|{}SystemFileSystem[0] + final fun (): kotlinx.io.files/FileSystem // kotlinx.io.files/SystemFileSystem.|(){}[0] +final val kotlinx.io.files/SystemPathSeparator // kotlinx.io.files/SystemPathSeparator|{}SystemPathSeparator[0] + final fun (): kotlin/Char // kotlinx.io.files/SystemPathSeparator.|(){}[0] +final val kotlinx.io.files/SystemTemporaryDirectory // kotlinx.io.files/SystemTemporaryDirectory|{}SystemTemporaryDirectory[0] + final fun (): kotlinx.io.files/Path // kotlinx.io.files/SystemTemporaryDirectory.|(){}[0] + +final fun (kotlinx.io.files/Path).kotlinx.io.files/sink(): kotlinx.io/Sink // kotlinx.io.files/sink|sink@kotlinx.io.files.Path(){}[0] +final fun (kotlinx.io.files/Path).kotlinx.io.files/source(): kotlinx.io/Source // kotlinx.io.files/source|source@kotlinx.io.files.Path(){}[0] +final fun kotlinx.io.files/Path(kotlin/String): kotlinx.io.files/Path // kotlinx.io.files/Path|Path(kotlin.String){}[0] +final fun kotlinx.io.files/Path(kotlin/String, kotlin/Array...): kotlinx.io.files/Path // kotlinx.io.files/Path|Path(kotlin.String;kotlin.Array...){}[0] +final fun kotlinx.io.files/Path(kotlinx.io.files/Path, kotlin/Array...): kotlinx.io.files/Path // kotlinx.io.files/Path|Path(kotlinx.io.files.Path;kotlin.Array...){}[0] diff --git a/core/apple/src/files/FileSystemApple.kt b/filesystem/apple/src/files/FileSystemApple.kt similarity index 100% rename from core/apple/src/files/FileSystemApple.kt rename to filesystem/apple/src/files/FileSystemApple.kt diff --git a/filesystem/apple/test/NSStreamExtensionsTest.kt b/filesystem/apple/test/NSStreamExtensionsTest.kt new file mode 100644 index 000000000..34231ee00 --- /dev/null +++ b/filesystem/apple/test/NSStreamExtensionsTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2024 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package kotlinx.io.files + +import kotlinx.io.Buffer +import kotlinx.io.asSource +import kotlinx.io.buffered +import kotlinx.io.readString +import kotlinx.io.tempFileName +import kotlinx.io.writeString +import platform.Foundation.NSInputStream +import platform.Foundation.NSURL +import kotlin.test.Test +import kotlin.test.assertEquals + +class NSStreamExtensionsTest { + @Test + fun nsInputStreamSourceFromFile() { + val file = tempFileName() + try { + SystemFileSystem.sink(Path(file)).buffered().use { + it.writeString("example") + } + + val input = NSInputStream(uRL = NSURL.fileURLWithPath(file)) + val source = input.asSource() + val buffer = Buffer() + assertEquals(7, source.readAtMostTo(buffer, 10)) + assertEquals("example", buffer.readString()) + } finally { + SystemFileSystem.delete(Path(file)) + } + } +} diff --git a/filesystem/build.gradle.kts b/filesystem/build.gradle.kts new file mode 100644 index 000000000..f864ae3a1 --- /dev/null +++ b/filesystem/build.gradle.kts @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2024 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +import org.gradle.internal.os.OperatingSystem +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + +plugins { + id("kotlinx-io-multiplatform") + id("kotlinx-io-publish") + id("kotlinx-io-dokka") + id("kotlinx-io-android-compat") + alias(libs.plugins.kover) +} + +kotlin { + js { + nodejs { + testTask { + useMocha { + timeout = "300s" + } + } + } + browser { + testTask { + useMocha { + timeout = "300s" + } + } + } + } + @OptIn(ExperimentalWasmDsl::class) + wasmWasi { + nodejs { + testTask { + // fd_readdir is unsupported on Windows: + // https://github.com/nodejs/node/blob/6f4d6011ea1b448cf21f5d363c44e4a4c56ca34c/deps/uvwasi/src/uvwasi.c#L19 + if (OperatingSystem.current().isWindows) { + filter.setExcludePatterns("*SmokeFileTest.listDirectory") + } + } + } + } + + sourceSets { + commonMain.dependencies { + api(project(":kotlinx-io-core")) + } + appleTest.dependencies { + implementation(libs.kotlinx.coroutines.core) + } + } +} + +tasks.named("wasmWasiNodeTest") { + // TODO: remove once https://youtrack.jetbrains.com/issue/KT-65179 solved + doFirst { + val layout = project.layout + val templateFile = layout.projectDirectory.file("wasmWasi/test/test-driver.mjs.template").asFile + + val driverFile = layout.buildDirectory.file( + "compileSync/wasmWasi/test/testDevelopmentExecutable/kotlin/kotlinx-io-kotlinx-io-filesystem-wasm-wasi-test.mjs" + ) + + fun File.mkdirsAndEscape(): String { + mkdirs() + return absolutePath.replace("\\", "\\\\") + } + + val tmpDir = temporaryDir.resolve("kotlinx-io-core-wasi-test").mkdirsAndEscape() + val tmpDir2 = temporaryDir.resolve("kotlinx-io-core-wasi-test-2").mkdirsAndEscape() + + val newDriver = templateFile.readText() + .replace("", tmpDir, false) + .replace("", tmpDir2, false) + + driverFile.get().asFile.writeText(newDriver) + } +} + +animalsniffer { + annotation = "kotlinx.io.files.AnimalSnifferIgnore" +} diff --git a/core/common/src/files/FileSystem.kt b/filesystem/common/src/files/FileSystem.kt similarity index 100% rename from core/common/src/files/FileSystem.kt rename to filesystem/common/src/files/FileSystem.kt diff --git a/core/common/src/files/Paths.kt b/filesystem/common/src/files/Paths.kt similarity index 100% rename from core/common/src/files/Paths.kt rename to filesystem/common/src/files/Paths.kt diff --git a/core/common/test/files/SmokeFileTest.kt b/filesystem/common/test/files/SmokeFileTest.kt similarity index 97% rename from core/common/test/files/SmokeFileTest.kt rename to filesystem/common/test/files/SmokeFileTest.kt index a4242b398..6622d77ef 100644 --- a/core/common/test/files/SmokeFileTest.kt +++ b/filesystem/common/test/files/SmokeFileTest.kt @@ -6,6 +6,8 @@ package kotlinx.io.files import kotlinx.io.* +import kotlinx.io.files.SystemPathSeparator +import kotlinx.io.unsafe.UnsafeBufferOperations import kotlin.test.* class SmokeFileTest { @@ -71,18 +73,19 @@ class SmokeFileTest { } } + @OptIn(UnsafeIoApi::class) @Test fun readWriteMultipleSegments() { val path = createTempPath() - val data = ByteArray((Segment.SIZE * 2.5).toInt()) { it.toByte() } + val data = ByteArray((UnsafeBufferOperations.maxSafeWriteCapacity * 2.5).toInt()) { it.toByte() } SystemFileSystem.sink(path).buffered().use { it.write(data) } SystemFileSystem.source(path).buffered().use { - assertArrayEquals(data, it.readByteArray()) + assertContentEquals(data, it.readByteArray()) } } @@ -247,7 +250,7 @@ class SmokeFileTest { @Test fun isAbsolute() { // to make it work on both Windows and Unix, just repeat the separator twice - val rootPath = SystemPathSeparator.repeat(2) + val rootPath = "${SystemPathSeparator}${SystemPathSeparator}" assertTrue(Path(rootPath).isAbsolute) assertFalse(Path("").isAbsolute) assertFalse(Path("..").isAbsolute) diff --git a/core/common/test/files/SmokeFileTestWindows.kt b/filesystem/common/test/files/SmokeFileTestWindows.kt similarity index 100% rename from core/common/test/files/SmokeFileTestWindows.kt rename to filesystem/common/test/files/SmokeFileTestWindows.kt diff --git a/core/common/test/files/UtilsTest.kt b/filesystem/common/test/files/UtilsTest.kt similarity index 100% rename from core/common/test/files/UtilsTest.kt rename to filesystem/common/test/files/UtilsTest.kt diff --git a/filesystem/common/test/files/util.kt b/filesystem/common/test/files/util.kt new file mode 100644 index 000000000..71162c325 --- /dev/null +++ b/filesystem/common/test/files/util.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2024 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 + +expect fun tempFileName(): String diff --git a/filesystem/js/src/FSUtils.kt b/filesystem/js/src/FSUtils.kt new file mode 100644 index 000000000..03d4b96cf --- /dev/null +++ b/filesystem/js/src/FSUtils.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package kotlinx.io + +internal actual fun withCaughtException(block: () -> Unit): Throwable? { + try { + block() + return null + } catch (t: Throwable) { + return t + } +} diff --git a/core/js/src/node/nodeModulesJs.kt b/filesystem/js/src/node/nodeModulesJs.kt similarity index 100% rename from core/js/src/node/nodeModulesJs.kt rename to filesystem/js/src/node/nodeModulesJs.kt diff --git a/filesystem/jvm/module/module-info.java b/filesystem/jvm/module/module-info.java new file mode 100644 index 000000000..fb20a05f6 --- /dev/null +++ b/filesystem/jvm/module/module-info.java @@ -0,0 +1,6 @@ +module kotlinx.io.filesystem { + requires transitive kotlin.stdlib; + requires transitive kotlinx.io.core; + + exports kotlinx.io.files; +} diff --git a/core/jvm/src/files/FileSystemJvm.kt b/filesystem/jvm/src/files/FileSystemJvm.kt similarity index 100% rename from core/jvm/src/files/FileSystemJvm.kt rename to filesystem/jvm/src/files/FileSystemJvm.kt diff --git a/core/jvm/src/files/PathsJvm.kt b/filesystem/jvm/src/files/PathsJvm.kt similarity index 100% rename from core/jvm/src/files/PathsJvm.kt rename to filesystem/jvm/src/files/PathsJvm.kt diff --git a/core/jvm/test/files/SmokeFileTestWindowsJVM.kt b/filesystem/jvm/test/files/SmokeFileTestWindowsJVM.kt similarity index 100% rename from core/jvm/test/files/SmokeFileTestWindowsJVM.kt rename to filesystem/jvm/test/files/SmokeFileTestWindowsJVM.kt diff --git a/filesystem/jvm/test/util.kt b/filesystem/jvm/test/util.kt new file mode 100644 index 000000000..6fbed29a8 --- /dev/null +++ b/filesystem/jvm/test/util.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package kotlinx.io + +import kotlinx.io.files.SystemTemporaryDirectory +import java.io.File +import kotlin.random.Random + +@OptIn(ExperimentalStdlibApi::class) +actual fun tempFileName(): String { + val tmpDir = SystemTemporaryDirectory.file + while (true) { + val randomString = Random.nextBytes(32).toHexString() + val res = File(tmpDir, randomString) + if (!res.exists()) { + return res.absolutePath + } + } +} diff --git a/core/linux/src/files/FileSystemLinux.kt b/filesystem/linux/src/files/FileSystemLinux.kt similarity index 100% rename from core/linux/src/files/FileSystemLinux.kt rename to filesystem/linux/src/files/FileSystemLinux.kt diff --git a/core/mingw/src/files/FileSystemMingw.kt b/filesystem/mingw/src/files/FileSystemMingw.kt similarity index 100% rename from core/mingw/src/files/FileSystemMingw.kt rename to filesystem/mingw/src/files/FileSystemMingw.kt diff --git a/core/mingw/test/files/SmokeFileTestWindowsMinGW.kt b/filesystem/mingw/test/files/SmokeFileTestWindowsMinGW.kt similarity index 100% rename from core/mingw/test/files/SmokeFileTestWindowsMinGW.kt rename to filesystem/mingw/test/files/SmokeFileTestWindowsMinGW.kt diff --git a/core/native/src/files/FileSystemNative.kt b/filesystem/native/src/files/FileSystemNative.kt similarity index 100% rename from core/native/src/files/FileSystemNative.kt rename to filesystem/native/src/files/FileSystemNative.kt diff --git a/core/native/src/files/PathsNative.kt b/filesystem/native/src/files/PathsNative.kt similarity index 100% rename from core/native/src/files/PathsNative.kt rename to filesystem/native/src/files/PathsNative.kt diff --git a/filesystem/native/test/util.kt b/filesystem/native/test/util.kt new file mode 100644 index 000000000..305613bd0 --- /dev/null +++ b/filesystem/native/test/util.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2024 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package kotlinx.io + +import kotlinx.io.files.SystemTemporaryDirectory +import platform.posix.F_OK +import platform.posix.access +import kotlin.random.Random + +@OptIn(ExperimentalStdlibApi::class) +actual fun tempFileName(): String { + val tmpDir = SystemTemporaryDirectory.path + repeat(10) { + val name = Random.nextBytes(32).toHexString() + val path = "$tmpDir/$name" + if (access(path, F_OK) != 0) { + return path + } + } + throw IOException("Failed to generate temp file name") +} diff --git a/core/nativeNonAndroid/src/files/FileSystemNativeNonAndroid.kt b/filesystem/nativeNonAndroid/src/files/FileSystemNativeNonAndroid.kt similarity index 100% rename from core/nativeNonAndroid/src/files/FileSystemNativeNonAndroid.kt rename to filesystem/nativeNonAndroid/src/files/FileSystemNativeNonAndroid.kt diff --git a/core/nativeNonApple/src/files/FileSystemNativeNonApple.kt b/filesystem/nativeNonApple/src/files/FileSystemNativeNonApple.kt similarity index 100% rename from core/nativeNonApple/src/files/FileSystemNativeNonApple.kt rename to filesystem/nativeNonApple/src/files/FileSystemNativeNonApple.kt diff --git a/core/nodeFilesystemShared/src/files/FileSystemNodeJs.kt b/filesystem/nodeFilesystemShared/src/files/FileSystemNodeJs.kt similarity index 100% rename from core/nodeFilesystemShared/src/files/FileSystemNodeJs.kt rename to filesystem/nodeFilesystemShared/src/files/FileSystemNodeJs.kt diff --git a/core/nodeFilesystemShared/src/files/PathsNodeJs.kt b/filesystem/nodeFilesystemShared/src/files/PathsNodeJs.kt similarity index 98% rename from core/nodeFilesystemShared/src/files/PathsNodeJs.kt rename to filesystem/nodeFilesystemShared/src/files/PathsNodeJs.kt index a612c352a..7297c5348 100644 --- a/core/nodeFilesystemShared/src/files/PathsNodeJs.kt +++ b/filesystem/nodeFilesystemShared/src/files/PathsNodeJs.kt @@ -9,6 +9,7 @@ import kotlinx.io.* import kotlinx.io.node.buffer import kotlinx.io.node.fs import kotlinx.io.unsafe.UnsafeBufferOperations +import kotlin.math.min import kotlinx.io.node.path as nodeJsPath public actual class Path internal constructor( @@ -112,7 +113,7 @@ internal class FileSource(private val path: Path) : RawSource { if (offset >= len) { return -1L } - val bytesToRead = minOf(byteCount, (len - offset)) + val bytesToRead = min(byteCount, (len - offset).toLong()) for (i in 0 until bytesToRead) { sink.writeByte(buffer!!.readInt8(offset++)) } diff --git a/core/nodeFilesystemShared/src/node/buffer.kt b/filesystem/nodeFilesystemShared/src/node/buffer.kt similarity index 100% rename from core/nodeFilesystemShared/src/node/buffer.kt rename to filesystem/nodeFilesystemShared/src/node/buffer.kt diff --git a/core/nodeFilesystemShared/src/node/fs.kt b/filesystem/nodeFilesystemShared/src/node/fs.kt similarity index 100% rename from core/nodeFilesystemShared/src/node/fs.kt rename to filesystem/nodeFilesystemShared/src/node/fs.kt diff --git a/core/nodeFilesystemShared/src/node/os.kt b/filesystem/nodeFilesystemShared/src/node/os.kt similarity index 100% rename from core/nodeFilesystemShared/src/node/os.kt rename to filesystem/nodeFilesystemShared/src/node/os.kt diff --git a/core/nodeFilesystemShared/src/node/path.kt b/filesystem/nodeFilesystemShared/src/node/path.kt similarity index 100% rename from core/nodeFilesystemShared/src/node/path.kt rename to filesystem/nodeFilesystemShared/src/node/path.kt diff --git a/core/nodeFilesystemShared/src/try.kt b/filesystem/nodeFilesystemShared/src/try.kt similarity index 100% rename from core/nodeFilesystemShared/src/try.kt rename to filesystem/nodeFilesystemShared/src/try.kt diff --git a/core/nodeFilesystemShared/test/files/SmokeFileTestWindowsNodeJs.kt b/filesystem/nodeFilesystemShared/test/files/SmokeFileTestWindowsNodeJs.kt similarity index 100% rename from core/nodeFilesystemShared/test/files/SmokeFileTestWindowsNodeJs.kt rename to filesystem/nodeFilesystemShared/test/files/SmokeFileTestWindowsNodeJs.kt diff --git a/core/nodeFilesystemShared/test/files/utils.kt b/filesystem/nodeFilesystemShared/test/files/utils.kt similarity index 100% rename from core/nodeFilesystemShared/test/files/utils.kt rename to filesystem/nodeFilesystemShared/test/files/utils.kt diff --git a/core/unix/src/files/FileSystemUnix.kt b/filesystem/unix/src/files/FileSystemUnix.kt similarity index 100% rename from core/unix/src/files/FileSystemUnix.kt rename to filesystem/unix/src/files/FileSystemUnix.kt diff --git a/core/wasmJs/src/-PlatformWasmJs.kt b/filesystem/wasmJs/src/-PlatformWasmJs.kt similarity index 100% rename from core/wasmJs/src/-PlatformWasmJs.kt rename to filesystem/wasmJs/src/-PlatformWasmJs.kt diff --git a/core/wasmJs/src/node/nodeModulesWasmJs.kt b/filesystem/wasmJs/src/node/nodeModulesWasmJs.kt similarity index 99% rename from core/wasmJs/src/node/nodeModulesWasmJs.kt rename to filesystem/wasmJs/src/node/nodeModulesWasmJs.kt index 9bb913ce3..2e75f0c8c 100644 --- a/core/wasmJs/src/node/nodeModulesWasmJs.kt +++ b/filesystem/wasmJs/src/node/nodeModulesWasmJs.kt @@ -5,7 +5,6 @@ package kotlinx.io.node - internal fun requireExists(): Boolean = js("typeof require === 'function'") internal fun requireModule(mod: String): JsAny? = js("""{ diff --git a/core/wasmWasi/src/-WasmUtils.kt b/filesystem/wasmWasi/src/-WasmUtils.kt similarity index 90% rename from core/wasmWasi/src/-WasmUtils.kt rename to filesystem/wasmWasi/src/-WasmUtils.kt index 3a644b525..df80552e8 100644 --- a/core/wasmWasi/src/-WasmUtils.kt +++ b/filesystem/wasmWasi/src/-WasmUtils.kt @@ -54,6 +54,17 @@ internal fun Buffer.readToLinearMemory(pointer: Pointer, bytes: Int) { } } +internal fun checkBounds(size: Long, startIndex: Long, endIndex: Long) { + if (startIndex < 0 || endIndex > size) { + throw IndexOutOfBoundsException( + "startIndex ($startIndex) and endIndex ($endIndex) are not within the range [0..size($size))" + ) + } + if (startIndex > endIndex) { + throw IllegalArgumentException("startIndex ($startIndex) > endIndex ($endIndex)") + } +} + @OptIn(UnsafeIoApi::class, UnsafeWasmMemoryApi::class) internal fun Buffer.writeFromLinearMemory(pointer: Pointer, bytes: Int) { var remaining = bytes diff --git a/core/wasmWasi/src/files/FileSystemWasm.kt b/filesystem/wasmWasi/src/files/FileSystemWasm.kt similarity index 99% rename from core/wasmWasi/src/files/FileSystemWasm.kt rename to filesystem/wasmWasi/src/files/FileSystemWasm.kt index 19810c8b1..9694cd9c8 100644 --- a/core/wasmWasi/src/files/FileSystemWasm.kt +++ b/filesystem/wasmWasi/src/files/FileSystemWasm.kt @@ -462,7 +462,7 @@ private class FileSink(private val fd: Fd) : RawSink { val resultPtr = allocator.allocateInt() while (remaining > 0) { - val bytesToWrite = minOf(remaining, TEMP_CIOVEC_BUFFER_LEN).toInt() + val bytesToWrite = min(remaining, TEMP_CIOVEC_BUFFER_LEN.toLong()).toInt() source.readToLinearMemory(temporaryWriteBuffer, bytesToWrite) ciovec.length = bytesToWrite @@ -512,7 +512,7 @@ private class FileSource(private val fd: Fd) : RawSource { val resultPtr = allocator.allocateInt() while (remaining > 0) { - val bytesToRead = minOf(remaining, TEMP_CIOVEC_BUFFER_LEN).toInt() + val bytesToRead = min(remaining, TEMP_CIOVEC_BUFFER_LEN.toLong()).toInt() ciovec.length = bytesToRead val res = Errno(fd_read(fd, ciovec.address, 1, resultPtr.address.toInt())) diff --git a/core/wasmWasi/src/files/PathsWasm.kt b/filesystem/wasmWasi/src/files/PathsWasm.kt similarity index 100% rename from core/wasmWasi/src/files/PathsWasm.kt rename to filesystem/wasmWasi/src/files/PathsWasm.kt diff --git a/core/wasmWasi/src/wasi/functions.kt b/filesystem/wasmWasi/src/wasi/functions.kt similarity index 100% rename from core/wasmWasi/src/wasi/functions.kt rename to filesystem/wasmWasi/src/wasi/functions.kt diff --git a/core/wasmWasi/src/wasi/types.kt b/filesystem/wasmWasi/src/wasi/types.kt similarity index 100% rename from core/wasmWasi/src/wasi/types.kt rename to filesystem/wasmWasi/src/wasi/types.kt diff --git a/core/wasmWasi/test/WasiFsTest.kt b/filesystem/wasmWasi/test/WasiFsTest.kt similarity index 100% rename from core/wasmWasi/test/WasiFsTest.kt rename to filesystem/wasmWasi/test/WasiFsTest.kt diff --git a/core/wasmWasi/test/test-driver.mjs.template b/filesystem/wasmWasi/test/test-driver.mjs.template similarity index 95% rename from core/wasmWasi/test/test-driver.mjs.template rename to filesystem/wasmWasi/test/test-driver.mjs.template index 21003bed2..6fd47efce 100644 --- a/core/wasmWasi/test/test-driver.mjs.template +++ b/filesystem/wasmWasi/test/test-driver.mjs.template @@ -14,7 +14,7 @@ const path = require('path'); const url = require('url'); const filepath = url.fileURLToPath(import.meta.url); const dirpath = path.dirname(filepath); -const wasmBuffer = fs.readFileSync(path.resolve(dirpath, './kotlinx-io-kotlinx-io-core-wasm-wasi-test.wasm')); +const wasmBuffer = fs.readFileSync(path.resolve(dirpath, './kotlinx-io-kotlinx-io-filesystem-wasm-wasi-test.wasm')); const wasmModule = new WebAssembly.Module(wasmBuffer); const wasmInstance = new WebAssembly.Instance(wasmModule, wasi.getImportObject()); diff --git a/core/wasmWasi/test/utils.kt b/filesystem/wasmWasi/test/utils.kt similarity index 100% rename from core/wasmWasi/test/utils.kt rename to filesystem/wasmWasi/test/utils.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 795152545..214c9ae36 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,11 +22,13 @@ rootProject.name = "kotlinx-io" include(":kotlinx-io-core") include(":kotlinx-io-benchmarks") include(":kotlinx-io-bytestring") +include(":kotlinx-io-filesystem") include(":kotlinx-io-smoke-tests") include(":kotlinx-io-okio") project(":kotlinx-io-core").projectDir = file("./core") project(":kotlinx-io-benchmarks").projectDir = file("./benchmarks") project(":kotlinx-io-bytestring").projectDir = file("./bytestring") +project(":kotlinx-io-filesystem").projectDir = file("./filesystem") project(":kotlinx-io-smoke-tests").projectDir = file("./smoke-tests") project(":kotlinx-io-okio").projectDir = file("./integration/kotlinx-io-okio") diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index 51ffb4205..9fc969aad 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -49,6 +49,7 @@ tasks { if (useLocalBuild) { dependsOn(project(":kotlinx-io-core").tasks.named("publishToMavenLocal")) dependsOn(project(":kotlinx-io-bytestring").tasks.named("publishToMavenLocal")) + dependsOn(project(":kotlinx-io-filesystem").tasks.named("publishToMavenLocal")) dependsOn(project(":kotlinx-io-okio").tasks.named("publishToMavenLocal")) } @@ -64,6 +65,7 @@ tasks { if (useLocalBuild) { dependsOn(project(":kotlinx-io-core").tasks.named("publishToMavenLocal")) dependsOn(project(":kotlinx-io-bytestring").tasks.named("publishToMavenLocal")) + dependsOn(project(":kotlinx-io-filesystem").tasks.named("publishToMavenLocal")) dependsOn(project(":kotlinx-io-okio").tasks.named("publishToMavenLocal")) } diff --git a/smoke-tests/src/test/kotlin/GradleProjectsTest.kt b/smoke-tests/src/test/kotlin/GradleProjectsTest.kt index c68d0f990..6bd4b407f 100644 --- a/smoke-tests/src/test/kotlin/GradleProjectsTest.kt +++ b/smoke-tests/src/test/kotlin/GradleProjectsTest.kt @@ -28,6 +28,8 @@ public class GradleProjectsTest { private val stagingRepository: String = System.getProperty("stagingRepository")!! private val bytestringDependency: String = "org.jetbrains.kotlinx:kotlinx-io-bytestring:$kotlinxIoVersion" private val coreDependency: String = "org.jetbrains.kotlinx:kotlinx-io-core:$kotlinxIoVersion" + private val fsDependency: String = "org.jetbrains.kotlinx:kotlinx-io-filesystem:$kotlinxIoVersion" + private val okioAdapterDependency: String = "org.jetbrains.kotlinx:kotlinx-io-okio:$kotlinxIoVersion" private fun generateBuildScript(multiplatform: Boolean, dependencyName: String, isOkio: Boolean = false) { @@ -102,6 +104,17 @@ public class GradleProjectsTest { assertTestPassed(results) } + @Test + fun filesystemJvm() { + setupTest("filesystem-jvm", false, fsDependency) + val results = GradleRunner.create() + .withProjectDir(projectDir.root) + .withArguments(":test") + .run() + + assertTestPassed(results) + } + @Test fun bytestringMultiplatform() { setupTest("bytestring-multiplatform", true, bytestringDependency) @@ -124,6 +137,17 @@ public class GradleProjectsTest { assertTestPassed(results, ":allTests") } + @Test + fun filesystemMultiplatform() { + setupTest("filesystem-multiplatform", true, fsDependency) + val results = GradleRunner.create() + .withProjectDir(projectDir.root) + .withArguments(":allTests") + .run() + + assertTestPassed(results, ":allTests") + } + @Test fun okioJvm() { setupTest("okio", false, okioAdapterDependency) diff --git a/smoke-tests/src/test/resources/gradle-projects/bytestring-multiplatform/SmokeTest.kt b/smoke-tests/src/test/resources/gradle-projects/bytestring-multiplatform/SmokeTest.kt index 6379f1e2a..1292914c3 100644 --- a/smoke-tests/src/test/resources/gradle-projects/bytestring-multiplatform/SmokeTest.kt +++ b/smoke-tests/src/test/resources/gradle-projects/bytestring-multiplatform/SmokeTest.kt @@ -3,6 +3,8 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. */ +package org.example + import kotlinx.io.bytestring.ByteString import kotlin.test.Test import kotlin.test.assertEquals diff --git a/smoke-tests/src/test/resources/gradle-projects/core-multiplatform/SmokeTest.kt b/smoke-tests/src/test/resources/gradle-projects/core-multiplatform/SmokeTest.kt index c8546e00c..b9aab8b87 100644 --- a/smoke-tests/src/test/resources/gradle-projects/core-multiplatform/SmokeTest.kt +++ b/smoke-tests/src/test/resources/gradle-projects/core-multiplatform/SmokeTest.kt @@ -3,10 +3,10 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. */ +package org.example + import kotlinx.io.Buffer import kotlinx.io.bytestring.ByteString -import kotlinx.io.files.Path -import kotlinx.io.files.SystemFileSystem import kotlinx.io.readByteArray import kotlinx.io.readByteString import kotlinx.io.write @@ -30,13 +30,4 @@ class SmokeTest { assertEquals(ByteString(0x42), buffer.readByteString()) } - - @Test - fun testUseFiles() { - try { - SystemFileSystem.exists(Path(".")) - } catch (t: Throwable) { - // that's fine - } - } } diff --git a/smoke-tests/src/test/resources/gradle-projects/filesystem-jvm/SmokeTest.kt b/smoke-tests/src/test/resources/gradle-projects/filesystem-jvm/SmokeTest.kt new file mode 100644 index 000000000..f7ddba38f --- /dev/null +++ b/smoke-tests/src/test/resources/gradle-projects/filesystem-jvm/SmokeTest.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2024 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package org.example + +import kotlinx.io.Buffer +import kotlinx.io.bytestring.ByteString +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem +import kotlinx.io.readByteArray +import kotlinx.io.readByteString +import kotlinx.io.write +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class SmokeTest { + @Test + fun testCore() { + val buffer = Buffer() + buffer.writeLong(0) + assertContentEquals(ByteArray(8), buffer.readByteArray()) + } + + @Test + fun testByteString() { + val byteString = ByteString(0x42) + val buffer = Buffer() + buffer.write(byteString) + + assertEquals(ByteString(0x42), buffer.readByteString()) + } + + @Test + fun testUseFiles() { + try { + SystemFileSystem.exists(Path(".")) + } catch (t: Exception) { + // that's fine + } + } +} diff --git a/smoke-tests/src/test/resources/gradle-projects/filesystem-multiplatform/SmokeTest.kt b/smoke-tests/src/test/resources/gradle-projects/filesystem-multiplatform/SmokeTest.kt new file mode 100644 index 000000000..f7ddba38f --- /dev/null +++ b/smoke-tests/src/test/resources/gradle-projects/filesystem-multiplatform/SmokeTest.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2024 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package org.example + +import kotlinx.io.Buffer +import kotlinx.io.bytestring.ByteString +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem +import kotlinx.io.readByteArray +import kotlinx.io.readByteString +import kotlinx.io.write +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class SmokeTest { + @Test + fun testCore() { + val buffer = Buffer() + buffer.writeLong(0) + assertContentEquals(ByteArray(8), buffer.readByteArray()) + } + + @Test + fun testByteString() { + val byteString = ByteString(0x42) + val buffer = Buffer() + buffer.write(byteString) + + assertEquals(ByteString(0x42), buffer.readByteString()) + } + + @Test + fun testUseFiles() { + try { + SystemFileSystem.exists(Path(".")) + } catch (t: Exception) { + // that's fine + } + } +} diff --git a/smoke-tests/src/test/resources/maven-projects/filesystem-jvm/pom.xml b/smoke-tests/src/test/resources/maven-projects/filesystem-jvm/pom.xml new file mode 100644 index 000000000..3d5275ea4 --- /dev/null +++ b/smoke-tests/src/test/resources/maven-projects/filesystem-jvm/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.jetbrains.kotlinx + kotlinx-maven-projects + 1.0-SNAPSHOT + + + core-jvm + ${project.parent.version} + + core-jvm + + + UTF-8 + 1.8 + 1.8 + + + + + org.jetbrains.kotlinx + kotlinx-io-filesystem-jvm + ${kotlinx.io.version} + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + + compile + + compile + + + + ${project.basedir}/src/main/kotlin + + + + + + test-compile + + test-compile + + + + ${project.basedir}/src/test/kotlin + + + + + + + + diff --git a/smoke-tests/src/test/resources/maven-projects/filesystem-jvm/src/test/kotlin/SmokeTest.kt b/smoke-tests/src/test/resources/maven-projects/filesystem-jvm/src/test/kotlin/SmokeTest.kt new file mode 100644 index 000000000..db5ee6ae2 --- /dev/null +++ b/smoke-tests/src/test/resources/maven-projects/filesystem-jvm/src/test/kotlin/SmokeTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. + */ + +package org.example + +import kotlinx.io.Buffer +import kotlinx.io.bytestring.ByteString +import kotlinx.io.readByteArray +import kotlinx.io.readByteString +import kotlinx.io.write +import java.lang.RuntimeException +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class SmokeTest { + @Test + fun testCore() { + val buffer = Buffer() + buffer.writeLong(0) + assertContentEquals(ByteArray(8), buffer.readByteArray()) + } + + @Test + fun testByteString() { + val byteString = ByteString(0x42) + val buffer = Buffer() + buffer.write(byteString) + + assertEquals(ByteString(0x42), buffer.readByteString()) + } + + @Test + fun testUseFiles() { + try { + SystemFileSystem.exists(Path(".")) + } catch (t: Exception) { + // that's fine + } + } +}