Skip to content

Commit 78d0fb1

Browse files
authored
Align file sink and source creation behavior across targets (#252)
* JS: open a file on the FileSink construction That ensures that even if nothing was written, a new empty file will be created on close. * JS: check file existence when creating the FileSource Align the behavior across all targets Fixes #251
1 parent fe9eefc commit 78d0fb1

File tree

2 files changed

+67
-18
lines changed

2 files changed

+67
-18
lines changed

core/common/test/files/SmokeFileTest.kt

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ class SmokeFileTest {
5050
@Test
5151
fun readNotExistingFile() {
5252
assertFailsWith<FileNotFoundException> {
53-
SystemFileSystem.source(createTempPath()).buffered().use {
54-
it.readByte()
55-
}
53+
SystemFileSystem.source(createTempPath())
5654
}
5755
}
5856

@@ -372,6 +370,34 @@ class SmokeFileTest {
372370
}
373371
}
374372

373+
@Test
374+
fun createAnEmptyFileUsingSink() {
375+
val path = createTempPath()
376+
assertFalse(SystemFileSystem.exists(path))
377+
378+
SystemFileSystem.sink(path).close()
379+
assertTrue(SystemFileSystem.exists(path))
380+
assertTrue(SystemFileSystem.metadataOrNull(path)!!.isRegularFile)
381+
}
382+
383+
@Test
384+
fun closeFileSinkTwice() {
385+
val path = createTempPath()
386+
val sink = SystemFileSystem.sink(path)
387+
sink.close()
388+
sink.close() // there should be no error
389+
}
390+
391+
@Test
392+
fun closeFileSourceTwice() {
393+
val path = createTempPath()
394+
SystemFileSystem.sink(path).close()
395+
assertTrue(SystemFileSystem.exists(path))
396+
val source = SystemFileSystem.source(path)
397+
source.close()
398+
source.close() // there should be no error
399+
}
400+
375401
private fun constructAbsolutePath(vararg parts: String): String {
376402
return SystemPathSeparator.toString() + parts.joinToString(SystemPathSeparator.toString())
377403
}

core/js/src/files/PathsJs.kt

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,31 @@ internal class FileSource(private val path: Path) : RawSource {
9292
private var buffer: dynamic = null
9393
private var closed = false
9494
private var offset = 0
95+
private val fd = open(path)
96+
97+
private fun open(path: Path): dynamic {
98+
if (!(fs.existsSync(path.path) as Boolean)) {
99+
throw FileNotFoundException("File does not exist: ${path.path}")
100+
}
101+
val fd = try {
102+
fs.openSync(path.path, "r")
103+
} catch (e: Throwable) {
104+
throw IOException("Failed to open a file ${path.path}.", e)
105+
}
106+
if (fd < 0) throw IOException("Failed to open a file ${path.path}.")
107+
return fd
108+
}
95109

96-
@OptIn(ExperimentalUnsignedTypes::class)
97110
override fun readAtMostTo(sink: Buffer, byteCount: Long): Long {
98111
check(!closed) { "Source is closed." }
99112
if (byteCount == 0L) {
100113
return 0
101114
}
102115
if (buffer === null) {
103116
try {
104-
buffer = fs.readFileSync(path.toString(), null)
117+
buffer = fs.readFileSync(fd, null)
105118
} catch (t: Throwable) {
106-
if (fs.existsSync(path.path) as Boolean) {
107-
throw IOException("Failed to read data from $path", t)
108-
}
109-
throw FileNotFoundException("File does not exist: $path")
119+
throw IOException("Failed to read data from ${path.path}", t)
110120
}
111121
}
112122
val len: Int = buffer.length as Int
@@ -122,12 +132,27 @@ internal class FileSource(private val path: Path) : RawSource {
122132
}
123133

124134
override fun close() {
125-
closed = true
135+
if (!closed) {
136+
closed = true
137+
fs.closeSync(fd)
138+
}
126139
}
127140
}
128141

129-
internal class FileSink(private val path: Path, private var append: Boolean) : RawSink {
142+
internal class FileSink(path: Path, append: Boolean) : RawSink {
130143
private var closed = false
144+
private val fd = open(path, append)
145+
146+
private fun open(path: Path, append: Boolean): dynamic {
147+
val flags = if (append) "a" else "w"
148+
val fd = try {
149+
fs.openSync(path.path, flags)
150+
} catch (e: Throwable) {
151+
throw IOException("Failed to open a file ${path.path}.", e)
152+
}
153+
if (fd < 0) throw IOException("Failed to open a file ${path.path}.")
154+
return fd
155+
}
131156

132157
override fun write(source: Buffer, byteCount: Long) {
133158
check(!closed) { "Sink is closed." }
@@ -142,12 +167,7 @@ internal class FileSink(private val path: Path, private var append: Boolean) : R
142167
val buf = buffer.Buffer.allocUnsafe(segmentBytes)
143168
buf.fill(head.data, head.pos, segmentBytes)
144169
try {
145-
if (append) {
146-
fs.appendFileSync(path.toString(), buf)
147-
} else {
148-
fs.writeFileSync(path.toString(), buf)
149-
append = true
150-
}
170+
fs.writeFileSync(fd, buf)
151171
} catch (e: Throwable) {
152172
throw IOException("Write failed", e)
153173
}
@@ -160,6 +180,9 @@ internal class FileSink(private val path: Path, private var append: Boolean) : R
160180
override fun flush() = Unit
161181

162182
override fun close() {
163-
closed = true
183+
if (!closed) {
184+
closed = true
185+
fs.closeSync(fd)
186+
}
164187
}
165188
}

0 commit comments

Comments
 (0)