view src/main/kotlin/name/blackcap/passman/RenameSubcommand.kt @ 14:4dae7a15ee48

Fix bugs found in additional round of testing.
author David Barts <n5jrn@me.com>
date Tue, 31 Jan 2023 19:07:46 -0800
parents c69665ff37d0
children 0fc90892a3ae
line wrap: on
line source

package name.blackcap.passman

import org.apache.commons.cli.*
import java.sql.PreparedStatement
import java.sql.Types
import kotlin.system.exitProcess

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)
        db = Database.open()
        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) {
            die(e.message ?: "syntax error", 2)
        }
        if (commandLine.hasOption(HELP)) {
            HelpFormatter().printHelp("$SHORTNAME rename [options] source destination", options)
            exitProcess(0)
        }
        if (commandLine.args.size < 2) {
            die("expecting source and destination", 2)
        }
        if (commandLine.args.size > 2) {
            die("unexpected trailing arguments", 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)) {
            die("no record matches ${see(source)}")
        }
        if (recordExists(did)) {
            if (commandLine.hasOption(FORCE)) {
                deleteRecord(did)
            } else {
                die("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()
        }
    }

    private fun PreparedStatement.setDateOrNull(parameterIndex: Int, value: Long?) {
        if (value == null || value == 0L) {
            setNull(parameterIndex, Types.INTEGER)
        } else {
            setLong(parameterIndex, value)
        }
    }
}