annotate src/name/blackcap/clipman/RtfToHtml.kt @ 18:96cc73ae2904

Make it more fail-safe.
author David Barts <n5jrn@me.com>
date Tue, 21 Jan 2020 16:39:02 -0800
parents be282c48010a
children 8aa2dfac27eb
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
1 /*
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
2 * Because Java (and by implication Kotlin) sucks at processing RTF data,
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
3 * we deal with such data by invoking an external program to convert it
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
4 * to HTML.
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
5 */
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
6 package name.blackcap.clipman
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
7
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
8 import java.io.BufferedInputStream
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
9 import java.io.BufferedOutputStream
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
10 import java.io.ByteArrayOutputStream
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
11 import java.io.IOException
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
12 import java.io.InputStream
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
13 import java.io.OutputStream
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
14 import java.io.UnsupportedEncodingException
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
15
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
16 private val RTF_CHARSET_NAME = "UTF-8"
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
17 private val LANG = "en_US." + RTF_CHARSET_NAME
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
18 private val UNRTF = System.getenv("UNRTF")
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
19
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
20 private class Consumer(val source: InputStream): Thread() {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
21 val target = ByteArrayOutputStream()
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
22
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
23 override fun run() {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
24 source.use { it.copyTo(target) }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
25 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
26
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
27 val output: String
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
28 @Synchronized get() {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
29 if (isAlive()) {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
30 throw IllegalThreadStateException("consumer not finished!")
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
31 } else {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
32 return target.toString(RTF_CHARSET_NAME)
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
33 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
34 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
35 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
36
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
37 /**
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
38 * Convert an InputStream of RTF bytes to a String containing an HTML
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
39 * document.
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
40 * @param rtfStream stream containing the RTF document
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
41 * @return a Pair. On success, the first element contains the HTML and
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
42 * the second is null. On failure, the first is null and the
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
43 * second contains an error message.
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
44 */
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
45 public fun rtfToHtml(rtfStream: InputStream): Pair<String?, String?> {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
46 if (OS.type == OS.MAC && UNRTF == null) {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
47 return _rtfToHtml(rtfStream, ProcessBuilder("textutil", "-format",
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
48 "rtf", "-convert", "html", "-stdin", "-stdout"))
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
49 } else {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
50 return _rtfToHtml(rtfStream, ProcessBuilder(
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
51 if (UNRTF == null) { "unrtf" } else { UNRTF },
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
52 "--html", "--nopict"))
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
53 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
54 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
55
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
56 private fun _rtfToHtml(rtfStream: InputStream, pb: ProcessBuilder): Pair<String?, String?> {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
57 var job: Process? = null
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
58 try {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
59 /* set the Posix locale to force UTF-8 I/O */
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
60 pb.environment().run {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
61 put("LANG", LANG)
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
62 put("LC_ALL", LANG)
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
63 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
64
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
65 /* start the process */
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
66 job = pb.start()
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
67
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
68 /* start consuming its output and error streams */
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
69 val outputConsumer = Consumer(job.inputStream).apply { start() }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
70 val errorConsumer = Consumer(job.errorStream).apply { start() }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
71
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
72 /* feed it input */
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
73 job.outputStream.use { rtfStream.copyTo(it) }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
74
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
75 /* wait for it to exit */
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
76 val exitStatus = job.waitFor()
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
77
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
78 /* after it exits, wait for our data consumers to exit */
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
79 outputConsumer.join();
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
80 errorConsumer.join();
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
81
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
82 /* if it barfed, return an error, else return the HTML */
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
83 if (exitStatus != 0) {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
84 val errors = errorConsumer.output
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
85 if (errors.isEmpty()) {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
86 return Pair(null, "converter exited with status " + exitStatus)
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
87 } else {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
88 return Pair(null, errors)
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
89 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
90 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
91 return Pair(outputConsumer.output, null)
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
92 } catch (e: IOException) {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
93 return barfed(e)
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
94 } catch (e: InterruptedException) {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
95 if (job != null && job.isAlive()) {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
96 job.destroy()
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
97 job.waitFor()
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
98 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
99 return barfed(e)
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
100 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
101 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
102
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
103 private fun barfed(e: Exception): Pair<String?, String?> {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
104 val sb = StringBuilder(e::class.simpleName)
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
105 val message = e.message ?: ""
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
106 if (!message.isEmpty()) {
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
107 sb.append(": ")
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
108 sb.append(e.message)
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
109 }
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
110 return Pair(null, sb.toString())
be282c48010a Incomplete; checking it in as a backup.
David Barts <n5jrn@me.com>
parents:
diff changeset
111 }