/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS-IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.etherpad.openofficeservice; import net.appjet.common.sars.{SarsServer,SarsMessageHandler}; import java.io.{DataInputStream,DataOutputStream}; import java.io.{File,FileOutputStream,ByteArrayInputStream,ByteArrayOutputStream}; /* Libraries needed for OO.org Conversion */ import com.sun.star.bridge.{XBridge,XBridgeFactory}; import com.sun.star.beans.{PropertyValue,XPropertySet}; import com.sun.star.connection.{NoConnectException,XConnection,XConnector}; import com.sun.star.container.XNamed; import com.sun.star.document.{XExporter,XFilter}; import com.sun.star.frame.{XComponentLoader,XStorable}; import com.sun.star.lang.{XComponent,XMultiComponentFactory}; import com.sun.star.uno.{UnoRuntime,XComponentContext}; class OOSException(m: String) extends RuntimeException(m); class UnsupportedFormatException(format: String) extends OOSException("Unsupported format: "+format); object TemporaryFailure extends OOSException("Temporary failure"); object OpenOfficeServerUtility { def checkServerAvailability(host: String, port: Int): Boolean = { // Assume the server is running; this is the responsibility of the user return true; } def runOpenOfficeServer(path: String, host: String, port: Int, timeout: Int, wait: Boolean) { // nothing } } class OpenOfficeFileConverter { var host: String = "localhost"; var port: Int = 8100; def setOpenOfficeServerDetails(host: String, port: Int) { this.host = host; this.port = port; } def convertFile(src: File, dst: File, converter: String, extension: String): Boolean = { try { val fromFile: String = "file:///" + src.getAbsolutePath(); val toFile: String = "file:///" + dst.getAbsolutePath(); val cnx: String = "socket,host="+this.host+",port="+this.port+""; val xRemoteContext: XComponentContext = com.sun.star.comp.helper.Bootstrap.createInitialComponentContext(null); val x: Object = xRemoteContext.getServiceManager().createInstanceWithContext("com.sun.star.connection.Connector", xRemoteContext); val xConnector: XConnector = UnoRuntime.queryInterface(classOf[XConnector], x).asInstanceOf[XConnector]; val connection: XConnection = xConnector.connect(cnx); if(connection == null) { throw new OOSException("Connection failure"); } val x2: Object = xRemoteContext.getServiceManager().createInstanceWithContext("com.sun.star.bridge.BridgeFactory", xRemoteContext); val xBridgeFactory: XBridgeFactory = UnoRuntime.queryInterface(classOf[XBridgeFactory], x2).asInstanceOf[XBridgeFactory]; val xBridge: XBridge = xBridgeFactory.createBridge("", "urp", connection, null); val x3: Object = xBridge.getInstance("StarOffice.ServiceManager"); if (x3 == null) { throw new OOSException("Failed to get bridge"); } val xMultiComponentFactory: XMultiComponentFactory = UnoRuntime.queryInterface(classOf[XMultiComponentFactory], x3).asInstanceOf[XMultiComponentFactory]; val xProperySet: XPropertySet = UnoRuntime.queryInterface(classOf[XPropertySet], xMultiComponentFactory).asInstanceOf[XPropertySet]; val oDefaultContext: Object = xProperySet.getPropertyValue("DefaultContext"); val xComponentContext: XComponentContext = UnoRuntime.queryInterface(classOf[XComponentContext], oDefaultContext).asInstanceOf[XComponentContext]; val desktopObj: Object = xMultiComponentFactory.createInstanceWithContext("com.sun.star.frame.Desktop", xComponentContext); val xcomponentloader: XComponentLoader = UnoRuntime.queryInterface(classOf[XComponentLoader], desktopObj).asInstanceOf[XComponentLoader]; if(xcomponentloader == null) { throw new OOSException("XComponent Loader could not be loaded"); } val loadProps: Array[PropertyValue] = new Array[PropertyValue](2); loadProps(0) = new PropertyValue(); loadProps(0).Name = "Hidden"; loadProps(0).Value = boolean2Boolean(false); loadProps(1) = new PropertyValue(); loadProps(1).Name = "UpdateDocMode"; loadProps(1).Value = "1"; val component: XComponent = xcomponentloader.loadComponentFromURL(fromFile,"_blank", 0, loadProps); if (component == null) { throw new OOSException("Failed to load document"); } val convProps: Array[PropertyValue] = new Array[PropertyValue](2); convProps(0) = new PropertyValue(); convProps(0).Name = "FilterName"; convProps(0).Value = converter; val xstorable: XStorable = UnoRuntime.queryInterface(classOf[XStorable],component).asInstanceOf[XStorable]; if (xstorable == null) { throw new OOSException("Storable could not be loaded"); } xstorable.storeToURL(toFile, convProps); component.dispose(); return true; } catch { case e => { e.printStackTrace(); throw new OOSException("Unknown exception occurred: "+e.getMessage()); } } } } object OpenOfficeService { val formats = Map( "pdf" -> "writer_pdf_Export", "doc" -> "MS Word 97", "html" -> "HTML (StarWriter)", "odt" -> "writer8", //"html" -> "XHTML Writer File", "txt" -> "Text" ); def createTempFile(bytes: Array[byte], suffix: String) = { var f = File.createTempFile("ooconvert-", if (suffix == null) { null } else if (suffix == "") { "" } else { "."+suffix }); if (bytes != null) { val fos = new FileOutputStream(f); fos.write(bytes); } f; } var soffice = "soffice"; def setExecutable(exec: String) { soffice = exec; } var openOfficeServerHost: String = "localhost"; var openOfficeServerPort: Int = 8100; def setOpenOfficeServer(host: String, port: Int) { openOfficeServerHost = host; openOfficeServerPort = port; } def convertFile(from: String, to: String, bytes: Array[byte]): Array[byte] = { if (from == to) { return bytes; } val tempFile = createTempFile(bytes, from); val outFile = createTempFile(null, to); /* Just hardcoding server and port here. If you intend to use an Openoffice.org instance on a network machine, do it at your risk. Just, remember to setOpenOfficeServer from etherpad/importexport/importexport.js, Also, remember that OO.org is reading and writing files over file:/// URI. So, make sure that you can access the files from network machine. Hint, NFS. Not Need for Speed game, you idiot, Network File System. */ if (! OpenOfficeServerUtility.checkServerAvailability(openOfficeServerHost, openOfficeServerPort)) { try { OpenOfficeServerUtility.runOpenOfficeServer(soffice, openOfficeServerHost, openOfficeServerPort, 20000, true); } catch { case e: java.io.IOException => { e.printStackTrace(); throw TemporaryFailure; } } } var converter = new OpenOfficeFileConverter(); converter.setOpenOfficeServerDetails(openOfficeServerHost, openOfficeServerPort); var status = false; try { status = converter.convertFile(tempFile, outFile, formats(to), to); } catch { case e => { e.printStackTrace(); throw new OOSException("Unknown exception occurred: "+e.getMessage()); } } if (status == false) { throw new UnsupportedFormatException(from); } net.appjet.common.util.BetterFile.getFileBytes(outFile); } def main(args: Array[String]) { if (args.length > 0) { soffice = args(0); if (soffice.length == 0) { exit(1); } } // Query format: // from: String, to: String, count: Int, bytes: Array[byte] // Response format: // status: Int, // status 0 (success) - : count: Int, bytes: Array[byte] // status 1 (temporary failure) - : // status 2 (permanent failure) - : type: Int // type - 0: unknown failure. // - 1: unsupported format val handler = new SarsMessageHandler { override def handle(b: Array[byte]): Option[Array[byte]] = { val is = new DataInputStream(new ByteArrayInputStream(b)); val from = is.readUTF; val to = is.readUTF; val len = is.readInt; val bytes = new Array[byte](len); is.readFully(bytes); var status = 0; var permfailuretype = 0; println("Converting "+from+" -> "+to+" ("+len+" bytes)"); val output = try { convertFile(from, to, bytes); } catch { case TemporaryFailure => { status = 1; null; } case e: UnsupportedFormatException => { status = 2; permfailuretype = 1; null; } case e => { status = 2; permfailuretype = 0; e.printStackTrace(); null; } } val retBytes = new ByteArrayOutputStream(); val ret = new DataOutputStream(retBytes); if (status != 0) { ret.writeInt(status); // error status match { case 2 => { ret.writeInt(permfailuretype); } case _ => { } } } else { ret.writeInt(0); // success ret.writeInt(output.length); ret.write(output, 0, output.length); } Some(retBytes.toByteArray()); } } val server = new SarsServer("ooffice-password", handler, None, 8101); server.start(); println("Server running..."); server.join(); println("Server quitting..."); } }