Skip to content

Commit

Permalink
Merge pull request #46 from apposed/resolve-updates
Browse files Browse the repository at this point in the history
Add mechanism to resolve updates
  • Loading branch information
ctrueden authored May 7, 2024
2 parents b7d97f2 + 24c98a3 commit c7dd816
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 35 deletions.
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
}
}

@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

0 comments on commit c7dd816

Please sign in to comment.