github.com/elliott5/community@v0.14.1-0.20160709191136-823126fb026a/plugin-libreoffice/plugin.go (about) 1 // Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved. 2 // 3 // This software (Documize Community Edition) is licensed under 4 // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html 5 // 6 // You can operate outside the AGPL restrictions by purchasing 7 // Documize Enterprise Edition and obtaining a commercial license 8 // by contacting <sales@documize.com>. 9 // 10 // https://documize.com 11 12 // Package main provides a simple Documize plugin for document conversions using libreoffice. 13 package main 14 15 import ( 16 "bytes" 17 "encoding/base64" 18 "errors" 19 "flag" 20 "fmt" 21 "io/ioutil" 22 "net" 23 "net/rpc" 24 "net/rpc/jsonrpc" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "runtime" 29 "strings" 30 "sync" 31 "time" 32 "unicode" 33 "unicode/utf8" 34 35 "github.com/documize/community/wordsmith/api" 36 ) 37 38 var cmdmtx sync.Mutex // enforce only one conversion at a time 39 40 // LibreOffice provides a peg on which to hang the Convert method. 41 type LibreOffice struct{} 42 43 var dir *os.File 44 var outputDir string 45 46 func init() { 47 tempDir := os.TempDir() 48 if !strings.HasSuffix(tempDir, string(os.PathSeparator)) { 49 tempDir += string(os.PathSeparator) 50 } 51 outputDir = tempDir + "documize-plugin-libreoffice" 52 err := os.MkdirAll(outputDir, os.ModePerm) 53 if err != nil { 54 panic(err) 55 } 56 } 57 58 func createTempDir() *os.File { 59 fmt.Println("create temp dir") 60 err := os.Mkdir(outputDir, 0777) // make the dir if non-existent, TODO filemode 61 if err != nil { 62 //fmt.Println("unable to create temp dir") 63 panic(err) 64 } 65 dir, err = os.Open(outputDir) 66 if err != nil { 67 //fmt.Println("unable to open created temp dir") 68 panic(err) 69 } 70 return dir 71 } 72 73 func removePrevTempFiles(dir *os.File) error { 74 fin, err := dir.Readdirnames(-1) 75 if err != nil { 76 return err 77 } 78 for _, nam := range fin { // remove any previous temporary files 79 target := outputDir + string(os.PathSeparator) + nam 80 //fmt.Println("delete temp file: " + target) 81 err = os.Remove(target) 82 if err != nil { 83 return err 84 } 85 } 86 return nil 87 } 88 89 func runLibreOffice(inPath string) error { 90 var err error 91 var cmd = "soffice" 92 switch runtime.GOOS { 93 case "darwin": // may not be in the path 94 cmd = "/Applications/LibreOffice.app/Contents/MacOS/soffice" 95 case "windows": // TODO 96 } 97 cmd, err = exec.LookPath(cmd) 98 if err != nil { 99 return err 100 } 101 102 args := []string{"--headless", 103 "--convert-to", "html", // "html:XHTML Writer File:UTF8", 104 "--outdir", outputDir, inPath} 105 106 fmt.Println("libreoffice args:", args) 107 108 ecmd := exec.Command(cmd, args...) 109 var outBuf, errBuf bytes.Buffer 110 ecmd.Stdout, ecmd.Stderr = &outBuf, &errBuf 111 over := make(chan error, 1) 112 go func() { 113 if e := ecmd.Start(); e != nil { 114 over <- e 115 } else { 116 over <- ecmd.Wait() 117 } 118 }() 119 select { 120 case err = <-over: 121 if err != nil { 122 return errors.New(string(outBuf.Bytes()) + string(errBuf.Bytes()) + err.Error()) 123 } 124 case <-time.After(2 * time.Minute): 125 ke := "" 126 if runtime.GOOS != "windows" { // Process is not available on windows 127 err = ecmd.Process.Kill() 128 if err != nil { 129 ke = ", kill error: " + err.Error() 130 } 131 } 132 return errors.New("libreoffice.Convert() cancelled via timeout" + ke) 133 } 134 return nil 135 } 136 137 // Convert converts a file into the Countersoft Documize format. 138 func (file *LibreOffice) Convert(r api.DocumentConversionRequest, reply *api.DocumentConversionResponse) error { 139 var err error 140 141 cmdmtx.Lock() // enforce only one conversion at a time 142 defer cmdmtx.Unlock() 143 144 dir, err = os.Open(outputDir) 145 if err != nil { 146 dir = createTempDir() 147 } 148 defer func() { 149 if e := dir.Close(); e != nil { 150 fmt.Fprintln(os.Stderr, "Error closing temp dir: "+e.Error()) 151 } 152 }() 153 154 err = removePrevTempFiles(dir) 155 if err != nil { 156 return err 157 } 158 159 _, inFileX := filepath.Split(r.Filename) 160 inFile := fixName(inFileX) 161 if inFile == "" { 162 return errors.New("no filename") 163 } 164 165 xtn := filepath.Ext(inFile) 166 if xtn == "" { 167 return errors.New("invalid filename: " + inFile) 168 } 169 170 inPath := outputDir + string(os.PathSeparator) + inFile 171 fmt.Println("writing data to: " + inPath) 172 if err = ioutil.WriteFile(inPath, r.Filedata, 0777); err != nil { 173 return err 174 } 175 176 err = runLibreOffice(inPath) 177 if err != nil { 178 return err 179 } 180 181 outPath := strings.TrimSuffix(inPath, xtn) + ".html" 182 fmt.Println("output file: " + outPath) 183 reply.PagesHTML, err = ioutil.ReadFile(outPath) 184 if err != nil { 185 return err 186 } 187 fmt.Printf("%d bytes read from: %s\n", len(reply.PagesHTML), outPath) 188 189 incorporateImages(reply) 190 191 return ioutil.WriteFile(outputDir+string(os.PathSeparator)+"debug.html", reply.PagesHTML, 0666) 192 } 193 194 func incorporateImages(reply *api.DocumentConversionResponse) { 195 dir, err := os.Open(outputDir) 196 if err == nil { 197 names, err := dir.Readdirnames(-1) 198 if err == nil { 199 for _, nam := range names { 200 //fmt.Println("Found file " + nam) 201 switch pic := strings.ToLower(filepath.Ext(nam)[1:]); pic { 202 case "jpg", "gif", "png", "webp": // TODO others? 203 //fmt.Println("Found picture file " + nam) 204 enam := fixName(nam) 205 buf, err := ioutil.ReadFile(outputDir + string(os.PathSeparator) + nam) 206 if err == nil { 207 enc := base64.StdEncoding.EncodeToString(buf) 208 benam := []byte(`<img src="` + enam + `"`) 209 split := bytes.Split(reply.PagesHTML, benam) 210 rep := []byte(`<img src="data:image/` + pic + `;base64,` + enc + `"`) 211 /* 212 fmt.Println("DEBUG read picture file ", nam, 213 "size", len(buf), 214 "benam", string(benam), 215 "len(split)", len(split), 216 "replacement size", len(rep)) 217 */ 218 if len(rep)*len(split) > 1000000 { 219 fmt.Println("Too large a file increase to replace (>1Mb)") 220 } else { 221 reply.PagesHTML = bytes.Join(split, rep) 222 } 223 } 224 } 225 } 226 } 227 } 228 } 229 230 var port string 231 232 func init() { 233 flag.StringVar(&port, "port", "", "the port to listen on") 234 } 235 236 func main() { 237 var err error 238 239 fmt.Println("Documize plugin-libreoffice starting up") 240 241 fmt.Println("outputDir:" + outputDir) 242 243 // register all the services that we accept 244 fileConverter := new(LibreOffice) 245 err = rpc.Register(fileConverter) 246 if err != nil { 247 panic(err) 248 } 249 250 flag.Parse() 251 if port == "" { 252 fmt.Fprintln(os.Stderr, "no port specified, please use the '-port' flag") 253 return 254 } 255 256 // set up the server 257 listener, err := net.Listen("tcp", ":"+port) 258 if err != nil { 259 panic(err) 260 } 261 262 // start server 263 fmt.Println("Documize plugin-libreoffice server up on", listener.Addr()) 264 265 for { 266 conn, err := listener.Accept() 267 if err != nil { 268 panic(err) 269 } 270 271 go jsonrpc.ServeConn(conn) // using JSON encoding 272 } 273 } 274 275 func fixName(s string) string { 276 ret := "" 277 for _, r := range s { 278 if utf8.RuneLen(r) > 1 || 279 unicode.IsSpace(r) || 280 unicode.IsSymbol(r) || 281 r == '%' { 282 ret += fmt.Sprintf("%%%x", r) 283 } else { 284 ret += string(r) 285 } 286 } 287 return ret 288 }