Initial commit
This commit is contained in:
parent
2a4dcd5ab5
commit
cd5b65a886
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
uploads/
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
uploads/
|
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM golang:latest as builder
|
||||||
|
WORKDIR /go/src/git.dm1sh.ru/dm1sh/any2pdf/
|
||||||
|
COPY *.go go.mod ./
|
||||||
|
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o app .
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
RUN apk --no-cache --purge add imagemagick libreoffice ttf-dejavu ttf-opensans msttcorefonts-installer ttf-freefont ttf-liberation ttf-droid ttf-inconsolata ttf-font-awesome ttf-mononoki ttf-hack && rm -rf /usr/share/icons && rm -rf /usr/lib/libreoffice/share/gallery && update-ms-fonts && fc-cache -fv
|
||||||
|
WORKDIR /root/
|
||||||
|
COPY --from=builder /go/src/git.dm1sh.ru/dm1sh/any2pdf/app ./
|
||||||
|
COPY index.html ./
|
||||||
|
ENV PORT=8080
|
||||||
|
CMD ["./app"]
|
61
convert.go
Normal file
61
convert.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConvertToPDF(folderPath string, filesPaths []string, contType ContentType) (resultingPath string, error error) {
|
||||||
|
switch contType {
|
||||||
|
case Images:
|
||||||
|
return ImagesToPDF(folderPath, filesPaths)
|
||||||
|
case Office:
|
||||||
|
return OfficeToPDF(folderPath, filesPaths)
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("Unhandled ContentType with %d index", contType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImagesToPDF(folderPath string, filesPaths []string) (resultingPath string, error error) {
|
||||||
|
resultingPath = createResultingPath(folderPath, "Images")
|
||||||
|
|
||||||
|
if err := runCommand("convert", append(filesPaths, resultingPath)...); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultingPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func OfficeToPDF(folderPath string, filesPaths []string) (resultingPath string, error error) {
|
||||||
|
resultingPath = createResultingPath(folderPath, "Office document")
|
||||||
|
|
||||||
|
err := runCommand(
|
||||||
|
"soffice", "--headless", "--nologo", "--nofirststartwizard",
|
||||||
|
"--norestore", "--convert-to", "pdf", "--outdir", folderPath, filesPaths[0],
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runCommand("mv", folderPath + "/" + "0.pdf", resultingPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultingPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createResultingPath(folderPath string, suffix string) string {
|
||||||
|
return folderPath + "/" + time.Now().Format(time.ANSIC) + " " + suffix + ".pdf"
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCommand(command string, arg ...string) error {
|
||||||
|
cmd := exec.Command(command, arg...)
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
14
index.html
Normal file
14
index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>any2pdf</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form action="/upload" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="files" multiple>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
159
main.go
Normal file
159
main.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPort(default_port string) string {
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
|
||||||
|
if port == "" {
|
||||||
|
return default_port
|
||||||
|
}
|
||||||
|
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("Content-Type", "text/html")
|
||||||
|
http.ServeFile(w, r, "./index.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
const UPLOADS_FOLDER_PREFIX = "./uploads/"
|
||||||
|
|
||||||
|
func saveFilesOnDisk(files []*multipart.FileHeader) (folderPath string, filesPaths []string, error error) {
|
||||||
|
// Create folder for uploaded files
|
||||||
|
|
||||||
|
folderPath = UPLOADS_FOLDER_PREFIX + strconv.FormatInt(time.Now().Unix(), 10)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(folderPath, os.ModePerm); err != nil {
|
||||||
|
return "", filesPaths, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filesPaths = make([]string, len(files))
|
||||||
|
|
||||||
|
for i, fileHeader := range files {
|
||||||
|
// Open file from request
|
||||||
|
file, err := fileHeader.Open()
|
||||||
|
if err != nil {
|
||||||
|
return "", filesPaths, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Generate and store file name
|
||||||
|
|
||||||
|
fileName := strconv.Itoa(i) + strings.ToLower(filepath.Ext(fileHeader.Filename))
|
||||||
|
|
||||||
|
filesPaths[i] = folderPath + "/" + fileName
|
||||||
|
|
||||||
|
// Create file on disk
|
||||||
|
dst, err := os.Create(filesPaths[i])
|
||||||
|
if err != nil {
|
||||||
|
return "", filesPaths, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer dst.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(dst, file)
|
||||||
|
if err != nil {
|
||||||
|
return "", filesPaths, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return folderPath, filesPaths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Images ContentType = iota
|
||||||
|
Office
|
||||||
|
)
|
||||||
|
|
||||||
|
func detectContentType(files []*multipart.FileHeader) (contentType ContentType, error error) {
|
||||||
|
for _, file := range files {
|
||||||
|
fileExt := strings.ToLower(filepath.Ext(file.Filename))
|
||||||
|
switch fileExt {
|
||||||
|
case ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp":
|
||||||
|
if len(files) == 1 {
|
||||||
|
return Office, nil
|
||||||
|
} else {
|
||||||
|
return contentType, fmt.Errorf("Expected one document, got %d", len(files))
|
||||||
|
}
|
||||||
|
case ".jpg", ".jpeg", ".png", ".pdf":
|
||||||
|
contentType = Images
|
||||||
|
default:
|
||||||
|
return contentType, fmt.Errorf("%s file extension is unsupported yet", fileExt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_UPLOAD_SIZE = (1 << 27)
|
||||||
|
|
||||||
|
func uploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Restrict to POST http method
|
||||||
|
if r.Method != "POST" {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restrict max size of a bunch of files
|
||||||
|
r.Body = http.MaxBytesReader(w, r.Body, MAX_UPLOAD_SIZE)
|
||||||
|
|
||||||
|
// Parse body
|
||||||
|
if err := r.ParseMultipartForm(MAX_UPLOAD_SIZE); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
files := r.MultipartForm.File["files"]
|
||||||
|
|
||||||
|
folderPath, filesPaths, err := saveFilesOnDisk(files)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.RemoveAll(folderPath)
|
||||||
|
|
||||||
|
contType, err := detectContentType(files)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resFile, err := ConvertToPDF(folderPath, filesPaths, contType)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\"" + filepath.Base(resFile) + "\"")
|
||||||
|
|
||||||
|
http.ServeFile(w, r, resFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", indexHandler)
|
||||||
|
http.HandleFunc("/upload", uploadHandler)
|
||||||
|
|
||||||
|
port := getPort("80")
|
||||||
|
|
||||||
|
fmt.Printf("Listening on %s\n", port)
|
||||||
|
|
||||||
|
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user