Add serial transfer
authorLanius Trolling <lanius@laniustrolling.dev>
Tue, 9 Apr 2024 16:19:38 +0000 (12:19 -0400)
committerLanius Trolling <lanius@laniustrolling.dev>
Tue, 9 Apr 2024 16:19:38 +0000 (12:19 -0400)
build.gradle.kts
src/jvmMain/kotlin/info/mechyrdia/data/MigrateFilesSerial.kt [new file with mode: 0644]

index c9f6d7f6490c0ad9d76b31fb8ee5a1a544064e85..c9753d9edb8b0600b7e7ab41b85e663bf39e2268 100644 (file)
@@ -287,6 +287,18 @@ tasks.register("migrateToGridFs", JavaExec::class) {
        setArgs(listOf("config", "gridfs"))
 }
 
+tasks.register("migrateToGridFsSerial", JavaExec::class) {
+       group = "administration"
+       
+       val runShadow: JavaExec by tasks
+       val main by sourceSets
+       
+       javaLauncher.convention(runShadow.javaLauncher)
+       classpath = main.runtimeClasspath
+       mainClass.set("info.mechyrdia.data.MigrateFilesSerial")
+       setArgs(listOf("config", "gridfs"))
+}
+
 tasks.withType<JavaExec> {
        javaLauncher.set(javaToolchains.launcherFor {
                languageVersion.set(JavaLanguageVersion.of(17))
diff --git a/src/jvmMain/kotlin/info/mechyrdia/data/MigrateFilesSerial.kt b/src/jvmMain/kotlin/info/mechyrdia/data/MigrateFilesSerial.kt
new file mode 100644 (file)
index 0000000..784b6bc
--- /dev/null
@@ -0,0 +1,103 @@
+@file:JvmName("MigrateFilesSerial")
+
+package info.mechyrdia.data
+
+import info.mechyrdia.Configuration
+import info.mechyrdia.FileStorageConfig
+import kotlinx.coroutines.runBlocking
+import kotlin.system.exitProcess
+
+private fun printUsage(): Nothing {
+       println("Usage: <FROM> <TO>")
+       println("Both arguments are of either following format:")
+       println("    gridfs - use GridFS (database connection indicated by config.json)")
+       println("    config - storage indicated in config file")
+       println("    file:<relative-path> - use flat-file storage")
+       exitProcess(-1)
+}
+
+private fun String.parseStorage(): FileStorageConfig {
+       val configuration = Configuration.Current
+       
+       return if (this == "config")
+               configuration.storage
+       else if (this == "gridfs")
+               FileStorageConfig.GridFs
+       else if (startsWith("file:"))
+               FileStorageConfig.Flat(removePrefix("file:"))
+       else {
+               println("Invalid format for argument value $this")
+               printUsage()
+       }
+}
+
+private suspend fun migrateFile(path: StoragePath, from: FileStorage, into: FileStorage): List<String> {
+       println("[Message] Starting transfer of /$path")
+       
+       val bytes = from.readFile(path) ?: return listOf("[Source Error] File does not exist at /$path")
+       if (!into.writeFile(path, bytes))
+               return listOf("[Target Error] File at /$path cannot be written to")
+       
+       println("[Message] Done transferring /$path")
+       return emptyList()
+}
+
+private suspend fun migrateDir(path: StoragePath, from: FileStorage, into: FileStorage): List<String> {
+       if (!into.createDir(path))
+               return listOf("[Target Error] Directory at /$path cannot be created")
+       
+       val inDir = from.listDir(path) ?: return listOf("[Source Error] Directory at /$path does not exist")
+       
+       return inDir.flatMap { entry ->
+               val entryPath = path / entry.name
+               when (entry.type) {
+                       StoredFileType.FILE -> migrateFile(entryPath, from, into)
+                       StoredFileType.DIRECTORY -> migrateDir(entryPath, from, into)
+               }
+       }
+}
+
+private suspend fun migrateRoot(from: FileStorage, into: FileStorage): List<String> {
+       val inRoot = from.listDir(StoragePath.Root)
+               ?: return listOf("[Source Error] Root directory does not exist")
+       
+       return inRoot.flatMap { entry ->
+               val entryPath = StoragePath.Root / entry.name
+               when (entry.type) {
+                       StoredFileType.FILE -> migrateFile(entryPath, from, into)
+                       StoredFileType.DIRECTORY -> migrateDir(entryPath, from, into)
+               }
+       }
+}
+
+fun main(args: Array<String>) {
+       if (args.size != 2) {
+               println("Invalid number of arguments ${args.size}, expected 2")
+               printUsage()
+       }
+       
+       val (from, into) = args.map { it.parseStorage() }
+       if (from == into) {
+               println("Cannot migrate storage to itself")
+               printUsage()
+       }
+       
+       val errors = runBlocking {
+               System.setProperty("logback.statusListenerClass", "ch.qos.logback.core.status.NopStatusListener")
+               
+               ConnectionHolder.initialize(Configuration.Current.dbConn, Configuration.Current.dbName)
+               
+               val fromStorage = FileStorage(from)
+               val intoStorage = FileStorage(into)
+               
+               migrateRoot(fromStorage, intoStorage)
+       }
+       
+       if (errors.isEmpty())
+               println("Successful migration! No errors encountered!")
+       else {
+               println("Migration encountered ${errors.size} ${errors.size.pluralize("error")}")
+               for (error in errors)
+                       println(error)
+       }
+}