Skip to content

Commit

Permalink
Change File.stat() function to File.length
Browse files Browse the repository at this point in the history
And make the Windows File code more DRY between length and lines().
  • Loading branch information
ctrueden committed May 4, 2024
1 parent 8419cfd commit c32bcb6
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 70 deletions.
2 changes: 1 addition & 1 deletion src/commonMain/kotlin/file.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ expect class File(rawPath: String) {
val isFile: Boolean
val isDirectory: Boolean
val isRoot: Boolean
val length : Long
fun ls(): List<File>
fun lines(): List<String>
fun mv(dest:File): Boolean
fun rm() : Boolean
fun rmdir() : Boolean
fun stat() : Long
}

// -- Platform-agnostic File members --
Expand Down
2 changes: 1 addition & 1 deletion src/commonMain/kotlin/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ private fun applyUpdate(appDir: File, updateSubDir: File) {
if (file.isDirectory) applyUpdate(appDir, file)
else {
val dest = appDir / file.path.substring((appDir / "update").path.length)
if (file.stat() == 0L) {
if (file.length == 0L) {
val rmDest = dest.rm()
if (!rmDest) error("Couldn't remove $dest")
val rmFile = file.rm()
Expand Down
16 changes: 7 additions & 9 deletions src/posixMain/kotlin/file.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ actual class File actual constructor(private val rawPath: String) {
actual val isDirectory: Boolean get() = isMode(S_IFDIR)
actual val isRoot: Boolean = path == SLASH

@OptIn(ExperimentalForeignApi::class)
actual val length: Long get() = memScoped {
val statResult = alloc<stat>()
stat(path, statResult.ptr)
return statResult.st_size
}

@OptIn(ExperimentalForeignApi::class)
private fun isMode(modeBits: Int): Boolean {
val statMode = memScoped {
Expand Down Expand Up @@ -88,15 +95,6 @@ actual class File actual constructor(private val rawPath: String) {
return rmdir(path) == 0
}
}

@OptIn(ExperimentalForeignApi::class)
actual fun stat(): Long {
memScoped {
val statBuf = alloc<stat>()
return stat(path, statBuf.ptr).toLong()
}
}

}

@OptIn(ExperimentalForeignApi::class)
Expand Down
106 changes: 47 additions & 59 deletions src/windowsMain/kotlin/file.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import kotlinx.cinterop.*
import kotlinx.cinterop.internal.CCall
import platform.windows.*
import kotlin.math.min

@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
actual class File actual constructor(private val rawPath: String) {
Expand All @@ -17,15 +17,20 @@ actual class File actual constructor(private val rawPath: String) {

actual val isDirectory: Boolean get() = isMode(FILE_ATTRIBUTE_DIRECTORY)

private fun isMode(modeBits: Int): Boolean {
val attrs = GetFileAttributesA(path)
return attrs != INVALID_FILE_ATTRIBUTES && (attrs.toInt() and modeBits) != 0
}

actual val isRoot: Boolean =
// Is it a drive letter plus backslash (e.g. `C:\`)?
path.length == 3 && (path[0] in 'a'..'z' || path[0] in 'A'..'Z') && path[1] == ':' && path[2] == '\\'

@OptIn(ExperimentalForeignApi::class)
actual val length: Long get() {
val fileHandle = openFile(path) ?: return -1
try {
return fileSize(fileHandle) ?: -1
} finally {
CloseHandle(fileHandle)
}
}

@OptIn(ExperimentalForeignApi::class)
actual fun ls(): List<File> {
if (!isDirectory) throw IllegalArgumentException("Not a directory: $path")
Expand Down Expand Up @@ -56,29 +61,16 @@ actual class File actual constructor(private val rawPath: String) {
@OptIn(ExperimentalForeignApi::class)
actual fun lines(): List<String> {
val lines = mutableListOf<String>()

memScoped {
val fileHandle = CreateFileA(
path,
GENERIC_READ,
FILE_SHARE_READ.toUInt(),
null,
OPEN_EXISTING.toUInt(),
FILE_ATTRIBUTE_NORMAL.toUInt(),
null
)

if (fileHandle == INVALID_HANDLE_VALUE) {
println("Error opening file: ${GetLastError()}")
return emptyList()
}

val fileHandle = openFile(path) ?: return lines
try {
val fileSize = GetFileSize(fileHandle, null).toInt()
val buffer = allocArray<ByteVar>(fileSize)
val fileSize = fileSize(fileHandle) ?: return lines
val size = min(fileSize, Int.MAX_VALUE.toLong()).toInt()
if (size < fileSize) warn("Reading only $size bytes of large file $path")
val buffer = allocArray<ByteVar>(size)
val bytesRead = alloc<DWORDVar>()
// TODO: Is bytesRead < fileSize possible? If so, do we need to loop here?
if (ReadFile(fileHandle, buffer, fileSize.toUInt(), bytesRead.ptr, null) != 0) {
if (ReadFile(fileHandle, buffer, size.toUInt(), bytesRead.ptr, null) != 0) {
lines.addAll(buffer.toKString().split(Regex("(\\r\\n|\\n)")))
} else {
println("Error reading file: ${GetLastError()}")
Expand All @@ -87,7 +79,6 @@ actual class File actual constructor(private val rawPath: String) {
CloseHandle(fileHandle)
}
}

return lines
}

Expand Down Expand Up @@ -119,44 +110,41 @@ actual class File actual constructor(private val rawPath: String) {
}
}

@OptIn(ExperimentalForeignApi::class)
actual fun stat(): Long {
memScoped {
// Open the file to get a handle
val fileHandle = CreateFileW(
path,
GENERIC_READ, // Open for reading
0u, // No sharing
null, // Default security
OPEN_EXISTING.toUInt(),
0u, // Default attributes
null // No template
)

// Check if file handle is valid
if (fileHandle == INVALID_HANDLE_VALUE) {
println("Failed to open the file: ${GetLastError()}")
return -1
}

// Prepare to get file information
val fileInfo = alloc<BY_HANDLE_FILE_INFORMATION>()
private fun isMode(modeBits: Int): Boolean {
val attrs = GetFileAttributesA(path)
return attrs != INVALID_FILE_ATTRIBUTES && (attrs.toInt() and modeBits) != 0
}

// Get file information
val success = GetFileInformationByHandle(fileHandle, fileInfo.ptr) != 0
CloseHandle(fileHandle) // Close the file handle
@OptIn(ExperimentalForeignApi::class)
private fun openFile(path: String): HANDLE? {
val fileHandle = CreateFileW(
path,
GENERIC_READ,
FILE_SHARE_READ.toUInt(),
null,
OPEN_EXISTING.toUInt(),
FILE_ATTRIBUTE_NORMAL.toUInt(),
null
)

if (fileHandle == INVALID_HANDLE_VALUE) {
warn("Error opening file: ${GetLastError()}")
return null
}
return fileHandle
}

if (success) {
val highSize = fileInfo.nFileSizeHigh.toLong() shl 32
val lowSize = fileInfo.nFileSizeLow.toLong()
return highSize + lowSize
} else {
println("Failed to get file information: ${GetLastError()}")
return -1
}
@OptIn(ExperimentalForeignApi::class)
private fun fileSize(fileHandle: HANDLE): Long? = memScoped {
val fileSize = alloc<LARGE_INTEGER>()
if (GetFileSizeEx(fileHandle, fileSize.ptr) == 0) {
warn("Error getting file size: ${GetLastError()}")
return null
}
return fileSize.QuadPart
}
}

@OptIn(ExperimentalForeignApi::class)
private fun canonicalize(path: String): String {
if (path.isEmpty()) return canonicalize(".")
Expand Down

0 comments on commit c32bcb6

Please sign in to comment.