organizrr/main.go
RaviAnand Mohabir 3e5484e9cd
Some checks reported errors
continuous-integration/drone/push Build encountered an error
feat: directly interact with js File APIs and use go-js-promise helper to avoid JSON memory overhead
2025-04-25 16:01:49 +02:00

312 lines
5.7 KiB
Go

package main
import (
"archive/zip"
"bytes"
"encoding/base64"
"fmt"
"io"
"path/filepath"
"slices"
"strings"
"time"
"syscall/js"
promise "github.com/nlepage/go-js-promise"
pdfcpu "github.com/pdfcpu/pdfcpu/pkg/api"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
)
type Customer struct{ js.Value }
func (customer Customer) GetFirstName() string {
return customer.Get("firstName").String()
}
func (customer Customer) GetLastName() string {
return customer.Get("lastName").String()
}
type Document struct {
js.Value
bytes []byte
}
func (document Document) GetID() string {
return document.Get("id").String()
}
func (document Document) GetName() string {
return document.Get("file").Get("name").String()
}
func (document *Document) GetBytes() ([]byte, error) {
if len(document.bytes) > 0 {
return document.bytes, nil
}
bytea, err := promise.Await(document.Get("file").Call("arrayBuffer"))
if err != nil {
return nil, err
}
uint8Array := js.Global().Get("Uint8Array").New(bytea)
document.bytes = make([]byte, uint8Array.Length())
js.CopyBytesToGo(document.bytes, uint8Array)
return document.bytes, nil
}
type CustomerFile struct{ js.Value }
func (file CustomerFile) GetID() string {
return file.Get("id").String()
}
func (file CustomerFile) GetDocuments() []CustomerDocument {
ds := file.Get("documents")
cds := make([]CustomerDocument, ds.Length())
for i := range ds.Length() {
cds[i] = CustomerDocument{ds.Index(i)}
}
return cds
}
func (file CustomerFile) GetSuffix() string {
return file.Get("suffix").String()
}
type CustomerDocument struct{ js.Value }
func (document CustomerDocument) GetID() string {
return document.Get("id").String()
}
func (document CustomerDocument) GetSelectedPages() (sps []string) {
sp := document.Get("selectedPages")
for i := range sp.Length() {
sps = append(sps, sp.Index(i).String())
}
return
}
type CreateArchiveInput struct{ js.Value }
func (input CreateArchiveInput) GetCustomer() Customer {
return Customer{input.Get("customer")}
}
func (input CreateArchiveInput) GetDocuments() []Document {
ds := input.Get("documents")
documents := make([]Document, ds.Length())
for i := range ds.Length() {
documents[i] = Document{ds.Index(i), nil}
}
return documents
}
func (input CreateArchiveInput) GetFiles() []CustomerFile {
fs := input.Get("files")
cfs := make([]CustomerFile, fs.Length())
for i := range fs.Length() {
cfs[i] = CustomerFile{fs.Index(i)}
}
return cfs
}
func createArchive(this js.Value, args []js.Value) any {
input := CreateArchiveInput{args[0]}
p, res, rej := promise.New()
go func() {
buf := new(bytes.Buffer)
w := zip.NewWriter(buf)
now := time.Now()
filePrefix := fmt.Sprintf("%s_%s_%s", now.Format("2006-01-02_15-04-05"), input.GetCustomer().GetLastName(), input.GetCustomer().GetFirstName())
var fileNames []string
for _, file := range input.GetFiles() {
if len(file.GetDocuments()) == 0 {
rej("At least one document must be provided")
return
}
var document *Document
for _, doc := range input.GetDocuments() {
if doc.GetID() == file.GetDocuments()[0].GetID() {
document = &doc
break
}
}
if document == nil {
rej("Couldn't find doc by ID: " + file.GetDocuments()[0].GetID())
return
}
ext := strings.ToLower(filepath.Ext(document.GetName()))
fileName := fmt.Sprintf("%s_%s%s", filePrefix, file.GetSuffix(), ext)
i := 1
for slices.Index(fileNames, fileName) != -1 {
fileName = fmt.Sprintf("%s_%s-%d%s", filePrefix, file.GetSuffix(), i, ext)
i++
}
fileNames = append(fileNames, fileName)
f, err := w.Create(fileName)
if err != nil {
rej("Couldn't create file: " + err.Error())
return
}
b, err := document.GetBytes()
if err != nil {
rej("Couldn't get bytes:" + err.Error())
return
}
if ext != ".pdf" {
_, err = f.Write(b)
if err != nil {
rej("Couldn't write file:" + err.Error())
return
}
continue
}
if len(file.GetDocuments()) == 1 {
if len(file.GetDocuments()[0].GetSelectedPages()) > 0 {
rs := bytes.NewReader(b)
err = pdfcpu.Trim(rs, f, file.GetDocuments()[0].GetSelectedPages(), nil)
if err != nil {
rej("Couldn't trim PDF: " + err.Error())
return
}
} else {
_, err = f.Write(b)
if err != nil {
rej("219 - Couldn't write file:" + err.Error())
return
}
}
continue
}
var rsc []io.ReadSeeker
for i := range file.GetDocuments() {
var document *Document
for _, doc := range input.GetDocuments() {
if doc.GetID() == file.GetDocuments()[i].GetID() {
document = &doc
break
}
}
if document == nil {
rej("Couldn't find doc by ID: " + file.GetDocuments()[i].GetID())
return
}
if i != 0 {
b, err = document.GetBytes()
if err != nil {
rej("251 - Couldn't get bytes:" + err.Error())
return
}
}
var (
rs = bytes.NewReader(b)
)
if len(file.GetDocuments()[i].GetSelectedPages()) > 0 {
var (
buf []byte
res = bytes.NewBuffer(buf)
)
err = pdfcpu.Trim(rs, res, file.GetDocuments()[i].GetSelectedPages(), nil)
if err != nil {
rej("270 - Couldn't trim PDF: " + err.Error())
return
}
rsc = append(rsc, bytes.NewReader(res.Bytes()))
} else {
rsc = append(rsc, rs)
}
}
pdfcpu.MergeRaw(rsc, f, false, nil)
}
if err := w.Close(); err != nil {
rej("Couldn't close ZIP:" + err.Error())
return
}
res(base64.StdEncoding.EncodeToString(buf.Bytes()))
}()
return p
}
func init() {
model.ConfigPath = "disable"
}
func main() {
c := make(chan struct{}, 0)
js.Global().Set("createArchive", js.FuncOf(createArchive))
<-c
}