Skip to content

Commit

Permalink
Align file sink and source creation behavior across targets (#252)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
fzhinkin authored Jan 24, 2024
1 parent fe9eefc commit 78d0fb1
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 18 deletions.
32 changes: 29 additions & 3 deletions core/common/test/files/SmokeFileTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ class SmokeFileTest {
@Test
fun readNotExistingFile() {
assertFailsWith<FileNotFoundException> {
SystemFileSystem.source(createTempPath()).buffered().use {
it.readByte()
}
SystemFileSystem.source(createTempPath())
}
}

Expand Down Expand Up @@ -372,6 +370,34 @@ class SmokeFileTest {
}
}

@Test
fun createAnEmptyFileUsingSink() {
val path = createTempPath()
assertFalse(SystemFileSystem.exists(path))

SystemFileSystem.sink(path).close()
assertTrue(SystemFileSystem.exists(path))
assertTrue(SystemFileSystem.metadataOrNull(path)!!.isRegularFile)
}

@Test
fun closeFileSinkTwice() {
val path = createTempPath()
val sink = SystemFileSystem.sink(path)
sink.close()
sink.close() // there should be no error
}

@Test
fun closeFileSourceTwice() {
val path = createTempPath()
SystemFileSystem.sink(path).close()
assertTrue(SystemFileSystem.exists(path))
val source = SystemFileSystem.source(path)
source.close()
source.close() // there should be no error
}

private fun constructAbsolutePath(vararg parts: String): String {
return SystemPathSeparator.toString() + parts.joinToString(SystemPathSeparator.toString())
}
Expand Down
53 changes: 38 additions & 15 deletions core/js/src/files/PathsJs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,31 @@ internal class FileSource(private val path: Path) : RawSource {
private var buffer: dynamic = null
private var closed = false
private var offset = 0
private val fd = open(path)

private fun open(path: Path): dynamic {
if (!(fs.existsSync(path.path) as Boolean)) {
throw FileNotFoundException("File does not exist: ${path.path}")
}
val fd = try {
fs.openSync(path.path, "r")
} catch (e: Throwable) {
throw IOException("Failed to open a file ${path.path}.", e)
}
if (fd < 0) throw IOException("Failed to open a file ${path.path}.")
return fd
}

@OptIn(ExperimentalUnsignedTypes::class)
override fun readAtMostTo(sink: Buffer, byteCount: Long): Long {
check(!closed) { "Source is closed." }
if (byteCount == 0L) {
return 0
}
if (buffer === null) {
try {
buffer = fs.readFileSync(path.toString(), null)
buffer = fs.readFileSync(fd, null)
} catch (t: Throwable) {
if (fs.existsSync(path.path) as Boolean) {
throw IOException("Failed to read data from $path", t)
}
throw FileNotFoundException("File does not exist: $path")
throw IOException("Failed to read data from ${path.path}", t)
}
}
val len: Int = buffer.length as Int
Expand All @@ -122,12 +132,27 @@ internal class FileSource(private val path: Path) : RawSource {
}

override fun close() {
closed = true
if (!closed) {
closed = true
fs.closeSync(fd)
}
}
}

internal class FileSink(private val path: Path, private var append: Boolean) : RawSink {
internal class FileSink(path: Path, append: Boolean) : RawSink {
private var closed = false
private val fd = open(path, append)

private fun open(path: Path, append: Boolean): dynamic {
val flags = if (append) "a" else "w"
val fd = try {
fs.openSync(path.path, flags)
} catch (e: Throwable) {
throw IOException("Failed to open a file ${path.path}.", e)
}
if (fd < 0) throw IOException("Failed to open a file ${path.path}.")
return fd
}

override fun write(source: Buffer, byteCount: Long) {
check(!closed) { "Sink is closed." }
Expand All @@ -142,12 +167,7 @@ internal class FileSink(private val path: Path, private var append: Boolean) : R
val buf = buffer.Buffer.allocUnsafe(segmentBytes)
buf.fill(head.data, head.pos, segmentBytes)
try {
if (append) {
fs.appendFileSync(path.toString(), buf)
} else {
fs.writeFileSync(path.toString(), buf)
append = true
}
fs.writeFileSync(fd, buf)
} catch (e: Throwable) {
throw IOException("Write failed", e)
}
Expand All @@ -160,6 +180,9 @@ internal class FileSink(private val path: Path, private var append: Boolean) : R
override fun flush() = Unit

override fun close() {
closed = true
if (!closed) {
closed = true
fs.closeSync(fd)
}
}
}

0 comments on commit 78d0fb1

Please sign in to comment.