Skip to content

Commit 73feef4

Browse files
committed
Supported append-to-file mode for sinks, changes sink/source signatures
1 parent 15e94ed commit 73feef4

File tree

12 files changed

+119
-75
lines changed

12 files changed

+119
-75
lines changed

core/api/kotlinx-io-core.api

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,9 @@ public abstract interface class kotlinx/io/files/FileSystem {
213213
public static synthetic fun delete$default (Lkotlinx/io/files/FileSystem;Lkotlinx/io/files/Path;ZILjava/lang/Object;)V
214214
public abstract fun exists (Lkotlinx/io/files/Path;)Z
215215
public abstract fun metadataOrNull (Lkotlinx/io/files/Path;)Lkotlinx/io/files/FileMetadata;
216-
public abstract fun read (Lkotlinx/io/files/Path;)Lkotlinx/io/Source;
217-
public abstract fun write (Lkotlinx/io/files/Path;)Lkotlinx/io/Sink;
216+
public abstract fun sink (Lkotlinx/io/files/Path;Z)Lkotlinx/io/RawSink;
217+
public static synthetic fun sink$default (Lkotlinx/io/files/FileSystem;Lkotlinx/io/files/Path;ZILjava/lang/Object;)Lkotlinx/io/RawSink;
218+
public abstract fun source (Lkotlinx/io/files/Path;)Lkotlinx/io/RawSource;
218219
}
219220

220221
public final class kotlinx/io/files/FileSystem$Companion {

core/apple/test/NSInputStreamSourceTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class NSInputStreamSourceTest {
2929
fun nsInputStreamSourceFromFile() {
3030
val file = tempFileName()
3131
try {
32-
Path(file).sink().use {
32+
Path(file).sink().buffered().use {
3333
it.writeString("example")
3434
}
3535

core/common/src/files/FileSystem.kt

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
package kotlinx.io.files
77

88
import kotlinx.io.IOException
9-
import kotlinx.io.Sink
10-
import kotlinx.io.Source
9+
import kotlinx.io.RawSink
10+
import kotlinx.io.RawSource
1111

1212
/**
1313
* An interface providing basic operations on a filesystem.
@@ -75,7 +75,7 @@ public sealed interface FileSystem {
7575
public fun atomicMove(source: Path, destination: Path)
7676

7777
/**
78-
* Returns [Source] to read from a file the [path] points to.
78+
* Returns [RawSource] to read from a file the [path] points to.
7979
*
8080
* How a source will read the data is implementation-specific and failures caused
8181
* by the missing file or, for example, lack of permissions may not be reported immediately,
@@ -88,21 +88,26 @@ public sealed interface FileSystem {
8888
* @throws kotlinx.io.files.FileNotFoundException when the file does not exist.
8989
* @throws kotlinx.io.IOException when it's not possible to open the file for reading.
9090
*/
91-
public fun read(path: Path): Source
91+
public fun source(path: Path): RawSource
9292

9393
/**
94-
* Returns [Sink] to write into a file the [path] points to.
94+
* Returns [RawSink] to write into a file the [path] points to.
95+
* Depending on [append] value, the file will be overwritten or data will be appened to it.
9596
* File will be created if it does not exist yet.
9697
*
9798
* How a sink will write the data is implementation-specific and failures caused,
9899
* for example, by the lack of permissions may not be reported immediately,
99100
* but postponed until the sink will try to store data.
100101
*
101-
* If [path] points to a directory, this method will fail with [IOException].
102+
* If [path] points to a directory, this method will fail with [IOException]
103+
*
104+
* @param path the path to a file to write data to.
105+
* @param append the flag indicating whether the data should be appended to an existing file or it
106+
* should be overwritten, `false` by default, meaning the file will be overwritten.
102107
*
103108
* @throws kotlinx.io.IOException when it's not possible to open the file for writing.
104109
*/
105-
public fun write(path: Path): Sink
110+
public fun sink(path: Path, append: Boolean = false): RawSink
106111

107112
/**
108113
* Return [FileMetadata] associated with a file or directory the [path] points to.
@@ -128,13 +133,7 @@ public sealed interface FileSystem {
128133
}
129134
}
130135

131-
internal abstract class SystemFileSystemImpl : FileSystem {
132-
@Suppress("DEPRECATION")
133-
override fun read(path: Path): Source = path.source()
134-
135-
@Suppress("DEPRECATION")
136-
override fun write(path: Path): Sink = path.sink()
137-
}
136+
internal abstract class SystemFileSystemImpl : FileSystem
138137

139138
internal expect val SystemFileSystem: FileSystem
140139

core/common/src/files/Paths.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package kotlinx.io.files
77

8+
import kotlinx.io.RawSink
9+
import kotlinx.io.RawSource
810
import kotlinx.io.Sink
911
import kotlinx.io.Source
1012

@@ -93,26 +95,26 @@ public fun Path(base: Path, vararg parts: String): Path {
9395
}
9496

9597
/**
96-
* Returns [Source] for the given file or throws if path is not a file or does not exist
98+
* Returns [RawSource] for the given file or throws if path is not a file or does not exist
9799
*/
98100
@Deprecated(
99-
message = "Use FileSystem.read instead",
101+
message = "Use FileSystem.source instead",
100102
replaceWith = ReplaceWith(
101-
expression = "FileSystem.System.read(this)",
103+
expression = "FileSystem.System.source(this).buffered()",
102104
imports = arrayOf("kotlinx.io.files.FileSystem")
103105
),
104106
level = DeprecationLevel.WARNING
105107
)
106108
public expect fun Path.source(): Source
107109

108110
/**
109-
* Returns [Sink] for the given path, creates file if it doesn't exist, throws if it's a directory,
111+
* Returns [RawSink] for the given path, creates file if it doesn't exist, throws if it's a directory,
110112
* overwrites contents.
111113
*/
112114
@Deprecated(
113-
message = "Use FileSystem.write instead",
115+
message = "Use FileSystem.sink instead",
114116
replaceWith = ReplaceWith(
115-
expression = "FileSystem.System.write(this)",
117+
expression = "FileSystem.System.sink(this).buffered()",
116118
imports = arrayOf("kotlinx.io.files.FileSystem")
117119
),
118120
level = DeprecationLevel.WARNING

core/common/test/files/SmokeFileTest.kt

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ class SmokeFileTest {
3737
fun readWriteFile() {
3838
val path = createTempPath()
3939

40-
FileSystem.System.write(path).use {
40+
FileSystem.System.sink(path).buffered().use {
4141
it.writeString("example")
4242
}
4343

44-
FileSystem.System.read(path).use {
44+
FileSystem.System.source(path).buffered().use {
4545
assertEquals("example", it.readLine())
4646
}
4747
}
@@ -50,7 +50,7 @@ class SmokeFileTest {
5050
@Test
5151
fun readNotExistingFile() {
5252
assertFailsWith<FileNotFoundException> {
53-
FileSystem.System.read(createTempPath()).use {
53+
FileSystem.System.source(createTempPath()).buffered().use {
5454
it.readByte()
5555
}
5656
}
@@ -63,11 +63,11 @@ class SmokeFileTest {
6363

6464
val data = ByteArray((Segment.SIZE * 2.5).toInt()) { it.toByte() }
6565

66-
FileSystem.System.write(path).use {
66+
FileSystem.System.sink(path).buffered().use {
6767
it.write(data)
6868
}
6969

70-
FileSystem.System.read(path).use {
70+
FileSystem.System.source(path).buffered().use {
7171
assertArrayEquals(data, it.readByteArray())
7272
}
7373
}
@@ -77,7 +77,7 @@ class SmokeFileTest {
7777
fun basicFileSystemOps() {
7878
val path = createTempPath()
7979
assertFalse(FileSystem.System.exists(path))
80-
FileSystem.System.write(path).use {
80+
FileSystem.System.sink(path).buffered().use {
8181
it.writeString("hi")
8282
}
8383
assertTrue(FileSystem.System.exists(path))
@@ -95,14 +95,14 @@ class SmokeFileTest {
9595
fun atomicMove() {
9696
val src = createTempPath()
9797
val dst = createTempPath()
98-
FileSystem.System.write(src).use {
98+
FileSystem.System.sink(src).buffered().use {
9999
it.writeString("hello")
100100
}
101101
FileSystem.System.atomicMove(src, dst)
102102
assertFalse(FileSystem.System.exists(src))
103103
assertTrue(FileSystem.System.exists(dst))
104104

105-
FileSystem.System.read(dst).use {
105+
FileSystem.System.source(dst).buffered().use {
106106
assertEquals("hello", it.readString())
107107
}
108108
}
@@ -179,8 +179,10 @@ class SmokeFileTest {
179179
Path("${Path.separator}a", "b", "..${Path.separator}c").toString()
180180
)
181181

182-
assertEquals(constructRelativePath("a", "b", "c"),
183-
Path("", "a", "b", "c").toString())
182+
assertEquals(
183+
constructRelativePath("a", "b", "c"),
184+
Path("", "a", "b", "c").toString()
185+
)
184186
}
185187

186188
@Test
@@ -221,7 +223,7 @@ class SmokeFileTest {
221223

222224
val filePath = Path(path, "test.txt")
223225
assertNull(FileSystem.System.metadataOrNull(filePath))
224-
FileSystem.System.write(filePath).use {
226+
FileSystem.System.sink(filePath).buffered().use {
225227
it.writeString("blablabla")
226228
}
227229

@@ -240,7 +242,7 @@ class SmokeFileTest {
240242
fun fileSize() {
241243
val path = createTempPath()
242244
val expectedSize = 123
243-
FileSystem.System.write(path).buffered().use {
245+
FileSystem.System.sink(path).buffered().use {
244246
it.write(ByteArray(expectedSize))
245247
}
246248
val metadata = FileSystem.System.metadataOrNull(path)
@@ -280,7 +282,7 @@ class SmokeFileTest {
280282
val dir = createTempPath()
281283
FileSystem.System.createDirectories(dir)
282284

283-
assertFailsWith<IOException> { FileSystem.System.read(dir).readByte() }
285+
assertFailsWith<IOException> { FileSystem.System.source(dir).buffered().readByte() }
284286
}
285287

286288
@OptIn(ExperimentalStdlibApi::class)
@@ -290,12 +292,34 @@ class SmokeFileTest {
290292
FileSystem.System.createDirectories(dir)
291293

292294
assertFailsWith<IOException> {
293-
FileSystem.System.write(dir).use {
295+
FileSystem.System.sink(dir).buffered().use {
294296
it.writeByte(0)
295297
}
296298
}
297299
}
298300

301+
@OptIn(ExperimentalStdlibApi::class)
302+
@Test
303+
fun appendToFile() {
304+
val path = createTempPath()
305+
306+
FileSystem.System.sink(path).buffered().use {
307+
it.writeString("first")
308+
}
309+
FileSystem.System.sink(path).buffered().use {
310+
it.writeString("second")
311+
}
312+
assertEquals("second",
313+
FileSystem.System.source(path).buffered().use { it.readString() })
314+
315+
FileSystem.System.sink(path, append = true).buffered().use {
316+
it.writeString(" third")
317+
}
318+
assertEquals("second third",
319+
FileSystem.System.source(path).buffered().use { it.readString() })
320+
321+
}
322+
299323
private fun constructAbsolutePath(vararg parts: String): String {
300324
return Path.separator.toString() + parts.joinToString(Path.separator.toString())
301325
}

core/js/src/files/FileSystemJs.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package kotlinx.io.files
77

88
import kotlinx.io.IOException
9+
import kotlinx.io.RawSink
10+
import kotlinx.io.RawSource
911

1012
internal val fs: dynamic
1113
get(): dynamic {
@@ -98,6 +100,17 @@ internal actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl
98100
return null
99101
}
100102
}
103+
104+
override fun source(path: Path): RawSource {
105+
check(fs !== null) { "Module 'fs' was not found" }
106+
return FileSource(path)
107+
}
108+
109+
override fun sink(path: Path, append: Boolean): RawSink {
110+
check(fs !== null) { "Module 'fs' was not found" }
111+
check(buffer !== null) { "Module 'buffer' was not found" }
112+
return FileSink(path, append)
113+
}
101114
}
102115

103116
internal actual val SystemTemporaryDirectoryImpl: Path

core/js/src/files/PathsJs.kt

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ package kotlinx.io.files
77

88
import kotlinx.io.*
99

10-
private val buffer: dynamic
10+
internal val buffer: dynamic
1111
get(): dynamic {
1212
return try {
1313
js("require('buffer')")
@@ -90,18 +90,11 @@ public actual fun Path(path: String): Path {
9090
return Path(path, null)
9191
}
9292

93-
public actual fun Path.source(): Source {
94-
check(fs !== null) { "Module 'fs' was not found" }
95-
return FileSource(this).buffered()
96-
}
93+
public actual fun Path.source(): Source = FileSystem.System.source(this).buffered()
9794

98-
public actual fun Path.sink(): Sink {
99-
check(fs !== null) { "Module 'fs' was not found" }
100-
check(buffer !== null) { "Module 'buffer' was not found" }
101-
return FileSink(this).buffered()
102-
}
95+
public actual fun Path.sink(): Sink = FileSystem.System.sink(this).buffered()
10396

104-
private class FileSource(private val path: Path) : RawSource {
97+
internal class FileSource(private val path: Path) : RawSource {
10598
private var buffer: dynamic = null
10699
private var closed = false
107100
private var offset = 0
@@ -139,9 +132,8 @@ private class FileSource(private val path: Path) : RawSource {
139132
}
140133
}
141134

142-
private class FileSink(private val path: Path) : RawSink {
135+
internal class FileSink(private val path: Path, private var append: Boolean) : RawSink {
143136
private var closed = false
144-
private var append = false
145137

146138
override fun write(source: Buffer, byteCount: Long) {
147139
check(!closed) { "Sink is closed." }

core/jvm/src/files/FileSystemJvm.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
package kotlinx.io.files
77

8-
import kotlinx.io.IOException
8+
import kotlinx.io.*
9+
import java.io.FileInputStream
10+
import java.io.FileOutputStream
911
import java.nio.file.Files
1012
import java.nio.file.StandardCopyOption
1113

@@ -75,6 +77,10 @@ internal actual val SystemFileSystem: FileSystem = object : SystemFileSystemImpl
7577
return FileMetadata(path.file.isFile, path.file.isDirectory,
7678
if (path.file.isFile) path.file.length() else -1L)
7779
}
80+
81+
override fun source(path: Path): RawSource = FileInputStream(path.file).asSource()
82+
83+
override fun sink(path: Path, append: Boolean): RawSink = FileOutputStream(path.file, append).asSink()
7884
}
7985

8086
internal actual val SystemTemporaryDirectoryImpl: Path

core/jvm/src/files/PathsJvm.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
package kotlinx.io.files
77

8-
import kotlinx.io.*
8+
import kotlinx.io.Sink
9+
import kotlinx.io.Source
10+
import kotlinx.io.buffered
911
import java.io.File
10-
import java.io.FileInputStream
11-
import java.io.FileOutputStream
1212

1313
public actual class Path internal constructor(internal val file: File) {
1414
public actual val parent: Path?
@@ -42,6 +42,6 @@ public actual class Path internal constructor(internal val file: File) {
4242

4343
public actual fun Path(path: String): Path = Path(File(path))
4444

45-
public actual fun Path.source(): Source = FileInputStream(file).asSource().buffered()
45+
public actual fun Path.source(): Source = FileSystem.System.source(this).buffered()
4646

47-
public actual fun Path.sink(): Sink = FileOutputStream(file).asSink().buffered()
47+
public actual fun Path.sink(): Sink = FileSystem.System.sink(this).buffered()

0 commit comments

Comments
 (0)