Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mechanism to resolve updates #46

Merged
merged 15 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,23 @@ kotlin {
val posixMain by creating {
dependsOn(commonMain.get())
}
val macosMain by creating {
dependsOn(posixMain)
}
val linuxMain by creating {
dependsOn(posixMain)
}
val macosArm64Main by getting {
dependsOn(posixMain)
dependsOn(macosMain)
}
val macosX64Main by getting {
dependsOn(posixMain)
dependsOn(macosMain)
}
val linuxArm64Main by getting {
dependsOn(posixMain)
dependsOn(linuxMain)
}
val linuxX64Main by getting {
dependsOn(posixMain)
dependsOn(linuxMain)
}
// HACK: Prevent "Variable is never used" warnings.
// Unfortunately, @Suppress("UNUSED_PARAMETER") does not do the trick.
Expand Down
3 changes: 2 additions & 1 deletion configs/fiji.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ modes = [

directives = [
'--print-ij-dir|print-app-dir,ABORT', # For backwards compatibility.
'LAUNCH:JVM|INIT_THREADS',
'LAUNCH:JVM|apply-update,INIT_THREADS',
'LAUNCH:PYTHON|apply-update,INIT_THREADS',
]

# /============================================================================\
Expand Down
13 changes: 13 additions & 0 deletions src/commonMain/kotlin/file.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +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
}

// -- Platform-agnostic File members --
Expand Down Expand Up @@ -44,6 +48,15 @@ val File.base: File
return if (dot < lastSlash(path)) this else File(path.substring(0, dot))
}

fun File.mkdir(): Boolean {
if (!exists) return mkdir(path)
if (!isDirectory) {
warn("Error: '$path' already exists but is not a directory.")
return false
}
return true
}

operator fun File.div(p: String): File = File("$path$SLASH$p")

// -- File-related utility functions --
Expand Down
30 changes: 29 additions & 1 deletion src/commonMain/kotlin/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ fun main(args: Array<String>) {

// Declare the global (runtime-agnostic) directives.
val globalDirectiveFunctions: DirectivesMap = mutableMapOf(
"help" to { _ -> help(exeFile, programName, supportedOptions) }
"help" to { _ -> help(exeFile, programName, supportedOptions) },
"apply-update" to { _ -> applyUpdate(appDir, appDir / "update") }
)

// Finally, execute all the directives! \^_^/
Expand Down Expand Up @@ -572,3 +573,30 @@ private fun help(exeFile: File?, programName: String, supportedOptions: JaunchOp
val optionsUnique = linkedSetOf(*supportedOptions.values.toTypedArray())
optionsUnique.forEach { printlnErr(it.help()) }
}

private fun applyUpdate(appDir: File, updateSubDir: File) {
if (!updateSubDir.exists) return

// Recursively copy over all files in the update subdir
for (file in updateSubDir.ls()) {
val dest = appDir / file.path.substring((appDir / "update").path.length)
if (file.isDirectory) {
debug("+ mkdir '$dest'")
dest.mkdir() || error("Couldn't create path $dest")
applyUpdate(appDir, file)
}
else {
if (file.length == 0L) {
debug("+ rm '$dest'")
dest.rm() || error("Couldn't remove $dest")
debug("+ rm '$file'")
file.rm() || error("Couldn't remove $file")
} else {
debug("+ mv '$file' '$dest'")
file.mv(dest) || error("Couldn't replace $dest")
}
}
}

updateSubDir.rmdir()
}
2 changes: 2 additions & 0 deletions src/commonMain/kotlin/platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ expect fun printlnErr(s: String = "")

expect fun stdinLines(): Array<String>

expect fun mkdir(path: String): Boolean

data class MemoryInfo(var total: Long? = null, var free: Long? = null)

expect fun memInfo(): MemoryInfo
Expand Down
13 changes: 13 additions & 0 deletions src/linuxMain/kotlin/platform.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.toKString
import platform.posix.*

@OptIn(ExperimentalForeignApi::class)
actual fun mkdir(path: String): Boolean {
val result = mkdir(path, S_IRWXU.toUInt())
if (result != 0) {
val errorCode = errno
platform.posix.warn("Error creating directory '$path': ${strerror(errorCode)?.toKString()}")
}
return result == 0
}
13 changes: 13 additions & 0 deletions src/macosMain/kotlin/platform.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.toKString
import platform.posix.*

@OptIn(ExperimentalForeignApi::class)
actual fun mkdir(path: String): Boolean {
val result = mkdir(path, S_IRWXU.toUShort())
if (result != 0) {
val errorCode = errno
platform.posix.warn("Error creating directory '$path': ${strerror(errorCode)?.toKString()}")
}
return result == 0
}
27 changes: 27 additions & 0 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 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, UnsafeNumber::class)
private fun isMode(modeBits: Int): Boolean {
val statMode = memScoped {
val statResult = alloc<stat>()
Expand Down Expand Up @@ -67,6 +74,26 @@ actual class File actual constructor(private val rawPath: String) {
return path
}

@OptIn(ExperimentalForeignApi::class)
actual fun mv(dest: File): Boolean {
memScoped {
return rename(path, dest.path) == 0
}
}

@OptIn(ExperimentalForeignApi::class)
actual fun rm(): Boolean {
memScoped {
return remove(path) == 0
}
}

@OptIn(ExperimentalForeignApi::class)
actual fun rmdir(): Boolean {
memScoped {
return rmdir(path) == 0
}
}
}

@OptIn(ExperimentalForeignApi::class)
Expand Down
106 changes: 78 additions & 28 deletions src/windowsMain/kotlin/file.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import kotlinx.cinterop.*
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 @@ -16,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 @@ -55,45 +61,90 @@ 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()}")
printlnErr("Error reading file: ${GetLastError()}")
}
} finally {
CloseHandle(fileHandle)
}
}

return lines
}

override fun toString(): String {
return path
}

@OptIn(ExperimentalForeignApi::class)
actual fun mv(dest: File): Boolean {
memScoped {
val pathW = path.wcstr.ptr
val destW = dest.path.wcstr.ptr
val flags = MOVEFILE_REPLACE_EXISTING.toUInt()
return MoveFileEx!!(pathW, destW, flags) != 0
ctrueden marked this conversation as resolved.
Show resolved Hide resolved
}
}

@OptIn(ExperimentalForeignApi::class)
actual fun rm(): Boolean {
memScoped {
return DeleteFileW(path) != 0
}
}

@OptIn(ExperimentalForeignApi::class)
actual fun rmdir(): Boolean {
memScoped {
return RemoveDirectoryW(path) != 0
}
}

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

@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
}

@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 All @@ -109,8 +160,7 @@ private fun canonicalize(path: String): String {
val bufferLength = MAX_PATH
memScoped {
val buffer = allocArray<UShortVar>(bufferLength)
val fullPathLength = GetFullPathName?.let { it(p.wcstr.ptr, bufferLength.toUInt(), buffer, null) } ?:
throw RuntimeException("GetFullPathName function not available")
val fullPathLength = GetFullPathName!!(p.wcstr.ptr, bufferLength.toUInt(), buffer, null)
if (fullPathLength == 0u) throw RuntimeException("Failed to get full path: ${GetLastError()}")
return buffer.toKString()
}
Expand Down
19 changes: 18 additions & 1 deletion src/windowsMain/kotlin/platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,23 @@ actual fun stdinLines(): Array<String> {
return lines
}

@OptIn(ExperimentalForeignApi::class)
actual fun mkdir(path: String): Boolean {
memScoped {
val result = CreateDirectoryW(path, null)
if (result == 0) {
val errorCode = GetLastError()
if (errorCode == ERROR_ALREADY_EXISTS.toUInt()) {
return true
} else {
warn("Error creating directory '$path': $errorCode")
return false
}
}
return true
}
}

@OptIn(ExperimentalForeignApi::class)
actual fun memInfo(): MemoryInfo {
val memInfo = MemoryInfo()
Expand All @@ -75,7 +92,7 @@ actual fun memInfo(): MemoryInfo {
memInfo.total = memoryStatus.ullTotalPhys.toLong()
memInfo.free = memoryStatus.ullAvailPhys.toLong()
} else {
println("Error getting memory status: ${GetLastError()}")
printlnErr("Error getting memory status: ${GetLastError()}")
}
}
return memInfo
Expand Down