Mercurial > cgi-bin > hgweb.cgi > PassMan
view src/main/kotlin/name/blackcap/passman/ImportSubcommand.kt @ 27:3a3067ba673b
Add idle-time detection to interactive mode, clean up imports.
author | David Barts <n5jrn@me.com> |
---|---|
date | Sat, 27 Jul 2024 09:50:54 -0700 |
parents | ea65ab890f66 |
children |
line wrap: on
line source
package name.blackcap.passman import com.opencsv.CSVParserBuilder import com.opencsv.CSVReaderBuilder import com.opencsv.exceptions.CsvException import org.apache.commons.cli.ParseException import java.io.FileInputStream import java.io.IOException import java.io.InputStreamReader import java.text.SimpleDateFormat import java.util.* class ImportSubcommand(): Subcommand() { private companion object { const val NFIELDS = 7 } private lateinit var csvDateFormat: SimpleDateFormat private lateinit var db: Database private val options = ImportExportArguments() private lateinit var csvFile: String private var line = 0 override fun run(args: Array<String>) { parseArguments(args) db = Database.default try { doImport() } catch (e: IOException) { throw SubcommandException(message = e.message ?: "I/O error", cause = e) } catch (e: CsvException) { val baseMessage = e.message ?: "CSV error" throw SubcommandException(message = "line $line, $baseMessage", cause = e) } } private fun parseArguments(args: Array<String>) { val params = parseInto("import", args, options) when (params.size) { 0 -> throw SubcommandException(message = "expecting CSV file name", status = 2) 1 -> csvFile = params[0] else -> throw SubcommandException(message = "unexpected trailing arguments", status = 2) } csvDateFormat = SimpleDateFormat(options.format).apply { timeZone = TimeZone.getTimeZone(options.zone) isLenient = false } } private fun doImport() { val csvParser = CSVParserBuilder() .withEscapeChar(options.escape) .withQuoteChar(options.quote) .withSeparator(options.separator) .withIgnoreLeadingWhiteSpace(options.ignore) .build() val csvReader = CSVReaderBuilder(InputStreamReader(FileInputStream(csvFile), options.charset)) .withCSVParser(csvParser) .build() csvReader.use { if (options.skip) { line++ it.skip(1) } it.iterator().forEach { fields -> line++ val importedEntry = fromCsv(fields) val thisEntry = Entry.fromDatabase(db, importedEntry.name) try { if (okToChange(thisEntry, importedEntry)) { if (thisEntry == null) { importedEntry.insert(db) } else { importedEntry.update(db) } } } finally { thisEntry?.password?.clear() } } } } private fun fromCsv(fields: Array<String>): Entry { if (fields.size != NFIELDS) { throw SubcommandException(message = "line $line, expected $NFIELDS fields but got ${fields.size}") } return Entry( name = fields[0], username = fields[1], password = fields[2].toCharArray(), notes = if (saysNull(fields[3])) null else fields[3], created = parseCsvTime(fields[4]) ?: Date(), modified = parseCsvTime(fields[5]), accessed = parseCsvTime(fields[6]) ) } private fun parseCsvTime(unparsed: String): Date? { if (saysNull(unparsed)) { return null } try { return csvDateFormat.parse(unparsed) } catch (e: ParseException) { throw SubcommandException(message = "${see(unparsed)} - invalid date/time string", cause = e) } } private fun okToChange(thisEntry: Entry?, otherEntry: Entry): Boolean = thisEntry == null || options.force || askUserIfOkToOverwrite(thisEntry, otherEntry) private fun saysNull(string: String) = string.lowercase() == "null" }