annotate src/main/kotlin/name/blackcap/passman/ImportSubcommand.kt @ 17:4427199eb218

Make escape character RFC4180 compliant by default.
author David Barts <n5jrn@me.com>
date Sun, 05 Feb 2023 11:14:25 -0800
parents 7a74ae668665
children ea65ab890f66
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
1 package name.blackcap.passman
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
2
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
3 import com.opencsv.CSVParserBuilder
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
4 import com.opencsv.CSVReaderBuilder
13
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
5 import com.opencsv.exceptions.CsvException
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
6 import org.apache.commons.cli.ParseException
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
7 import java.io.FileInputStream
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
8 import java.io.IOException
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
9 import java.io.InputStreamReader
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
10 import java.text.SimpleDateFormat
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
11 import java.util.*
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
13 class ImportSubcommand(): Subcommand() {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
14 private companion object {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
15 const val NFIELDS = 7
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
16 }
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
17 private lateinit var csvDateFormat: SimpleDateFormat
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
18 private lateinit var db: Database
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
19 private val options = ImportExportArguments()
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
20 private lateinit var csvFile: String
13
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
21 private var line = 0
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
22
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
23 override fun run(args: Array<String>) {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
24 parseArguments(args)
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
25 db = Database.open()
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
26 try {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
27 doImport()
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
28 } catch (e: IOException) {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
29 die(e.message ?: "I/O error")
13
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
30 } catch (e: CsvException) {
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
31 val message = e.message ?: "CSV error"
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
32 die("line $line, $message")
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
33 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
34 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
35
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
36 private fun parseArguments(args: Array<String>) {
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
37 val params = parseInto("import", args, options)
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
38 when (params.size) {
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
39 0 -> die("expecting CSV file name", 2)
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
40 1 -> csvFile = params[0]
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
41 else -> die("unexpected trailing arguments", 2)
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
42 }
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
43 csvDateFormat = SimpleDateFormat(options.format).apply {
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
44 timeZone = TimeZone.getTimeZone(options.zone)
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
45 isLenient = false
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
46 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
47 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
48
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
49 private fun doImport() {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
50 val csvParser = CSVParserBuilder()
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
51 .withEscapeChar(options.escape)
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
52 .withQuoteChar(options.quote)
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
53 .withSeparator(options.separator)
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
54 .withIgnoreLeadingWhiteSpace(options.ignore)
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
55 .build()
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
56
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
57 val csvReader = CSVReaderBuilder(InputStreamReader(FileInputStream(csvFile), options.charset))
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
58 .withCSVParser(csvParser)
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
59 .build()
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
60
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
61 csvReader.use {
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
62 if (options.skip) {
13
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
63 line++
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
64 it.skip(1)
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
65 }
13
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
66
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
67 it.iterator().forEach { fields ->
13
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
68 line++
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
69 val importedEntry = fromCsv(fields)
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
70 val thisEntry = Entry.fromDatabase(db, importedEntry.name)
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
71 try {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
72 if (okToChange(thisEntry, importedEntry)) {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
73 if (thisEntry == null) {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
74 importedEntry.insert(db)
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
75 } else {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
76 importedEntry.update(db)
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
77 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
78 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
79 } finally {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
80 thisEntry?.password?.clear()
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
81 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
82 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
83 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
84 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
85
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
86 private fun fromCsv(fields: Array<String>): Entry {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
87 if (fields.size != NFIELDS) {
13
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
88 die("line $line, expected $NFIELDS fields but got ${fields.size}")
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
89 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
90 return Entry(
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
91 name = fields[0],
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
92 username = fields[1],
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
93 password = fields[2].toCharArray(),
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
94 notes = if (saysNull(fields[3])) null else fields[3],
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
95 created = parseCsvTime(fields[4]) ?: Date(),
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
96 modified = parseCsvTime(fields[5]),
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
97 accessed = parseCsvTime(fields[6])
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
98 )
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
99 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
100
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
101 private fun parseCsvTime(unparsed: String): Date? {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
102 if (saysNull(unparsed)) {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
103 return null
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
104 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
105 try {
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
106 return csvDateFormat.parse(unparsed)
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
107 } catch (e: ParseException) {
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
108 die("${see(unparsed)} - invalid date/time string")
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
109 throw e /* kotlin is too stupid to realize this never happens */
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
110 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
111 }
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
112
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
113 private fun okToChange(thisEntry: Entry?, otherEntry: Entry): Boolean =
16
7a74ae668665 Add export subcommand.
David Barts <n5jrn@me.com>
parents: 14
diff changeset
114 thisEntry == null || options.force || askUserIfOkToOverwrite(thisEntry, otherEntry)
12
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
115
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
116 private fun saysNull(string: String) = string.lowercase() == "null"
a38a2a1036c3 Add import subcommand.
David Barts <n5jrn@me.com>
parents:
diff changeset
117
13
302d224bbd57 Improve help messages and csv error reportage.
David Barts <n5jrn@me.com>
parents: 12
diff changeset
118 }