view src/main/kotlin/name/blackcap/passman/RenameSubcommand.kt @ 28:287eadf5ab30 default tip

Check for timeouts inside subcommands while in interactive mode as well.
author David Barts <n5jrn@me.com>
date Wed, 31 Jul 2024 11:21:18 -0700
parents ea65ab890f66
children
line wrap: on
line source

package name.blackcap.passman

import org.apache.commons.cli.*

class RenameSubcommand(): Subcommand() {
    private companion object {
        const val FORCE = "force"
        const val HELP = "help"
    }
    private lateinit var commandLine: CommandLine
    private lateinit var source: String
    private lateinit var destination: String
    private lateinit var db: Database

    override fun run(args: Array<String>) {
        parseArguments(args)
        if (commandLine.hasOption(HELP)) {
            return
        }
        db = Database.default
        renameIt()
    }

    private fun parseArguments(args: Array<String>) {
        val options = Options().apply {
            addOption("f", FORCE, false, "If destination exists exists, force overwrite.")
            addOption("h", HELP, false, "Print this help message.")
        }
        try {
            commandLine = DefaultParser().parse(options, args)
        } catch (e: ParseException) {
            throw SubcommandException(message = e.message ?: "syntax error", status = 2, cause = e)
        }
        if (commandLine.hasOption(HELP)) {
            HelpFormatter().printHelp("$SHORTNAME rename [options] source destination", options)
            return
        }
        if (commandLine.args.size < 2) {
            throw SubcommandException(message = "expecting source and destination", status = 2)
        }
        if (commandLine.args.size > 2) {
            throw SubcommandException(message = "unexpected trailing arguments", status = 2)
        }
        source = commandLine.args[0]
        destination = commandLine.args[1]
    }

    private fun renameIt(): Unit {
        val sid = db.makeKey(source)
        val did = db.makeKey(destination)

        if(!recordExists(sid)) {
            throw SubcommandException(message = "no record matches ${see(source)}")
        }
        if (recordExists(did)) {
            if (commandLine.hasOption(FORCE)) {
                deleteRecord(did)
            } else {
                throw SubcommandException(message = "record matching ${see(destination)} already exists")
            }
        }

        db.connection.prepareStatement("select username, password, notes, created, modified, accessed from passwords where id = ?").use { sourceStmt ->
            sourceStmt.setLong(1, sid)
            val result = sourceStmt.executeQuery()
            result.next()
            db.connection.prepareStatement("insert into passwords (id, name, username, password, notes, created, modified, accessed) values (?, ?, ?, ?, ?, ?, ?, ?)").use {
                it.setLong(1, did)
                it.setEncryptedString(2, destination, db.encryption)
                it.setBytes(3, result.getBytes(1))
                it.setBytes(4, result.getBytes(2))
                it.setBytesOrNull(5, result.getBytes(3))
                it.setLong(6, result.getLong(4))
                it.setLong(7, System.currentTimeMillis())
                it.setDateOrNull(8, result.getLong(6))
                it.executeUpdate()
            }
        }

        deleteRecord(sid)
    }

    private fun recordExists(id: Long): Boolean {
        db.connection.prepareStatement("select count(*) from passwords where id = ?").use {
            it.setLong(1, id)
            val result = it.executeQuery()
            result.next()
            return result.getInt(1) > 0
        }
    }

    private fun deleteRecord(id: Long): Unit {
        db.connection.prepareStatement("delete from passwords where id = ?").use {
            it.setLong(1, id);
            it.executeUpdate()
        }
    }
}