Mercurial > cgi-bin > hgweb.cgi > ClipMan
view src/name/blackcap/clipman/RtfToHtml.kt @ 9:8fcff14defa2
Stomp out race conditions and set width adaptively.
author | David Barts <n5jrn@me.com> |
---|---|
date | Sun, 19 Jan 2020 15:43:01 -0800 |
parents | be282c48010a |
children | 8aa2dfac27eb |
line wrap: on
line source
/* * Because Java (and by implication Kotlin) sucks at processing RTF data, * we deal with such data by invoking an external program to convert it * to HTML. */ package name.blackcap.clipman import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.ByteArrayOutputStream import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.io.UnsupportedEncodingException private val RTF_CHARSET_NAME = "UTF-8" private val LANG = "en_US." + RTF_CHARSET_NAME private val UNRTF = System.getenv("UNRTF") private class Consumer(val source: InputStream): Thread() { val target = ByteArrayOutputStream() override fun run() { source.use { it.copyTo(target) } } val output: String @Synchronized get() { if (isAlive()) { throw IllegalThreadStateException("consumer not finished!") } else { return target.toString(RTF_CHARSET_NAME) } } } /** * Convert an InputStream of RTF bytes to a String containing an HTML * document. * @param rtfStream stream containing the RTF document * @return a Pair. On success, the first element contains the HTML and * the second is null. On failure, the first is null and the * second contains an error message. */ public fun rtfToHtml(rtfStream: InputStream): Pair<String?, String?> { if (OS.type == OS.MAC && UNRTF == null) { return _rtfToHtml(rtfStream, ProcessBuilder("textutil", "-format", "rtf", "-convert", "html", "-stdin", "-stdout")) } else { return _rtfToHtml(rtfStream, ProcessBuilder( if (UNRTF == null) { "unrtf" } else { UNRTF }, "--html", "--nopict")) } } private fun _rtfToHtml(rtfStream: InputStream, pb: ProcessBuilder): Pair<String?, String?> { var job: Process? = null try { /* set the Posix locale to force UTF-8 I/O */ pb.environment().run { put("LANG", LANG) put("LC_ALL", LANG) } /* start the process */ job = pb.start() /* start consuming its output and error streams */ val outputConsumer = Consumer(job.inputStream).apply { start() } val errorConsumer = Consumer(job.errorStream).apply { start() } /* feed it input */ job.outputStream.use { rtfStream.copyTo(it) } /* wait for it to exit */ val exitStatus = job.waitFor() /* after it exits, wait for our data consumers to exit */ outputConsumer.join(); errorConsumer.join(); /* if it barfed, return an error, else return the HTML */ if (exitStatus != 0) { val errors = errorConsumer.output if (errors.isEmpty()) { return Pair(null, "converter exited with status " + exitStatus) } else { return Pair(null, errors) } } return Pair(outputConsumer.output, null) } catch (e: IOException) { return barfed(e) } catch (e: InterruptedException) { if (job != null && job.isAlive()) { job.destroy() job.waitFor() } return barfed(e) } } private fun barfed(e: Exception): Pair<String?, String?> { val sb = StringBuilder(e::class.simpleName) val message = e.message ?: "" if (!message.isEmpty()) { sb.append(": ") sb.append(e.message) } return Pair(null, sb.toString()) }