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))
--- /dev/null
+@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)
+ }
+}