huge refactoring

This commit is contained in:
Julien Neuhart
2018-03-29 20:13:19 +02:00
parent 2f97a44bed
commit e847c86baa
24 changed files with 1039 additions and 783 deletions

View File

@@ -0,0 +1,118 @@
package converter
import (
"net/http"
"os"
gfile "github.com/gulien/gotenberg/app/handlers/converter/file"
"github.com/gulien/gotenberg/app/handlers/converter/process"
ghttp "github.com/gulien/gotenberg/app/handlers/http"
)
type Converter struct {
files []*gfile.File
resultFilesPaths []string
FinalFilePath string
}
func NewConverter(r *http.Request, contentType ghttp.ContentType) (*Converter, error) {
c := &Converter{}
switch contentType {
case ghttp.MultipartFormDataContentType:
err := r.ParseMultipartForm(32 << 20)
if err != nil {
return nil, err
}
formData := r.MultipartForm
files, ok := formData.File["files"]
if !ok {
// TODO
return nil, nil
}
for i := range files {
file, err := files[i].Open()
if err != nil {
return nil, err
}
defer file.Close()
f, err := gfile.NewFile(file)
if err != nil {
// todo err
return nil, err
}
c.files = append(c.files, f)
}
break
default:
f, err := gfile.NewFile(r.Body)
if err != nil {
// todo err
return nil, err
}
c.files = append(c.files, f)
}
return c, nil
}
func (c *Converter) Convert() error {
if len(c.files) == 0 {
return &noFileToConvertError{}
}
var filesPaths []string
for _, f := range c.files {
if f.Type != gfile.PDFType {
path, err := process.ExecConversion(f)
if err != nil {
return err
}
filesPaths = append(filesPaths, path)
} else {
filesPaths = append(filesPaths, f.Path)
}
}
path, err := process.ExecMerge(filesPaths)
if err != nil {
return err
}
c.resultFilesPaths = filesPaths
c.FinalFilePath = path
return nil
}
func (c *Converter) Clear() error {
for _, f := range c.files {
err := os.Remove(f.Path)
if err != nil {
return err
}
}
for _, path := range c.resultFilesPaths {
err := os.Remove(path)
if err != nil {
return err
}
}
if c.FinalFilePath != "" {
err := os.Remove(c.FinalFilePath)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,7 @@
package converter
type noFileToConvertError struct{}
func (e *noFileToConvertError) Error() string {
return "There is no file to convert"
}

View File

@@ -0,0 +1,120 @@
package file
import (
"fmt"
"io"
"os"
ghttp "github.com/gulien/gotenberg/app/handlers/http"
"github.com/satori/go.uuid"
)
type (
File struct {
Type FileType
Path string
}
FileType string
FileExt string
)
const (
PDFType FileType = "PDF"
HTMLType FileType = "HTML"
OfficeType FileType = "Office"
PDFExt FileExt = ".pdf"
HTMLExt FileExt = ".html"
OfficeExt FileExt = ""
)
func NewFile(r io.Reader) (*File, error) {
f := &File{
Path: MakeFilePath(),
}
file, err := os.Create(f.Path)
if err != nil {
return nil, err
}
defer file.Close()
_, err = io.Copy(file, r)
if err != nil {
return nil, err
}
// resets the read pointer.
file.Seek(0, 0)
t, err := findFileType(file)
if err != nil {
return nil, err
}
f.Type = t
f, err = reworkFilePath(f)
if err != nil {
return nil, err
}
return f, nil
}
func MakeFilePath() string {
return fmt.Sprintf("./%s", uuid.NewV4().String())
}
var filesTypes = map[ghttp.ContentType]FileType{
ghttp.PDFContentType: PDFType,
ghttp.HTMLContentType: HTMLType,
ghttp.OctetStreamContentType: OfficeType,
ghttp.ZipContentType: OfficeType,
}
func findFileType(f *os.File) (FileType, error) {
ct, err := ghttp.SniffContentType(f)
if err != nil {
return "", err
}
t, ok := filesTypes[ct]
if !ok {
// TODO error
return "", nil
}
return t, nil
}
var filesExtensions = map[FileType]FileExt{
PDFType: PDFExt,
HTMLType: HTMLExt,
OfficeType: OfficeExt,
}
func reworkFilePath(f *File) (*File, error) {
ext, ok := filesExtensions[f.Type]
if !ok {
// TODO error
return nil, nil
}
if ext != OfficeExt {
newPath := fmt.Sprintf("./%s%s", MakeFilePath(), ext)
err := os.Rename(f.Path, newPath)
if err != nil {
return nil, err
}
f.Path = newPath
}
return f, nil
}

View File

@@ -0,0 +1,13 @@
package process
type impossibleConversionError struct{}
func (e *impossibleConversionError) Error() string {
return "Impossible conversion"
}
type commandTimeoutError struct{}
func (e *commandTimeoutError) Error() string {
return "The command has reached timeout"
}

View File

@@ -0,0 +1,119 @@
package process
import (
"bytes"
"fmt"
"os/exec"
"text/template"
"time"
"github.com/gulien/gotenberg/app/config"
gfile "github.com/gulien/gotenberg/app/handlers/converter/file"
)
var commandsConfig *config.CommandsConfig
func Load(config *config.CommandsConfig) {
commandsConfig = config
}
func Reset() {
commandsConfig = nil
}
type conversionData struct {
FilePath string
ResultFilePath string
}
func ExecConversion(file *gfile.File) (string, error) {
cmdData := &conversionData{
FilePath: file.Path,
ResultFilePath: fmt.Sprintf("%s%s", gfile.MakeFilePath(), gfile.PDFExt),
}
var (
cmdTemplate *template.Template
cmdTimeout int
)
switch file.Type {
case gfile.HTMLType:
cmdTemplate = commandsConfig.HTML.Template
cmdTimeout = commandsConfig.HTML.Timeout
break
case gfile.OfficeType:
cmdTemplate = commandsConfig.Office.Template
cmdTimeout = commandsConfig.Office.Timeout
break
default:
return "", &impossibleConversionError{}
}
var data bytes.Buffer
if err := cmdTemplate.Execute(&data, cmdData); err != nil {
return "", err
}
err := execCommand(data.String(), cmdTimeout)
if err != nil {
return "", err
}
return cmdData.ResultFilePath, nil
}
type mergeData struct {
FilesPaths []string
ResultFilePath string
}
func ExecMerge(filesPaths []string) (string, error) {
cmdData := &mergeData{
FilesPaths: filesPaths,
ResultFilePath: fmt.Sprintf("%s%s", gfile.MakeFilePath(), gfile.PDFExt),
}
cmdTemplate := commandsConfig.Merge.Template
cmdTimeout := commandsConfig.Merge.Timeout
var data bytes.Buffer
if err := cmdTemplate.Execute(&data, cmdData); err != nil {
return "", err
}
err := execCommand(data.String(), cmdTimeout)
if err != nil {
return "", err
}
return cmdData.ResultFilePath, nil
}
func execCommand(command string, timeout int) error {
// Wait for the process to finish or kill it after a timeout.
cmd := exec.Command("/bin/sh", "-c", command)
if err := cmd.Start(); err != nil {
return err
}
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
select {
case <-time.After(time.Duration(timeout) * time.Second):
if err := cmd.Process.Kill(); err != nil {
return err
}
return &commandTimeoutError{}
case err := <-done:
if err != nil {
return err
}
return nil
}
}