diff --git a/code_generation/main.go b/code_generation/main.go index e4f043e1..f4439df7 100644 --- a/code_generation/main.go +++ b/code_generation/main.go @@ -21,6 +21,13 @@ type GenerateDataMethod struct { Output string } +func (m *GenerateDataMethod) IsMultiThreaded() bool { + if m.Name == "FPDFBitmap_CreateEx" || m.Name == "FSDK_SetUnSpObjProcessHandler" || m.Name == "FSDK_SetTimeFunction" || m.Name == "FSDK_SetLocaltimeFunction" { + return true + } + return false +} + type GenerateData struct { Methods []GenerateDataMethod } diff --git a/code_generation/templates/multi_threaded.go.tmpl b/code_generation/templates/multi_threaded.go.tmpl index ab303963..6498f234 100644 --- a/code_generation/templates/multi_threaded.go.tmpl +++ b/code_generation/templates/multi_threaded.go.tmpl @@ -12,29 +12,52 @@ import ( ) {{ range $method := .Methods }} func (i *pdfiumInstance) {{ $method.Name }}(request *requests.{{ $method.Input }}) (*responses.{{ $method.Output }}, error) { + {{ if eq $method.IsMultiThreaded true -}} + return nil, errors.New("unsupported method on multi-threaded usage") + {{- else -}} if i.closed { return nil, errors.New("instance is closed") } {{ if eq $method.Name "FPDF_LoadCustomDocument" -}} // Since multi-threaded usage implements gRPC, it can't serialize the reader onto that. - // To make it support the full interface, we just completely read the file into memory. - fileData, err := ioutil.ReadAll(request.Reader) - if err != nil { - return nil, err - } - + // To make it support the full interface, we just rewrite it to OpenDocument, + // and OpenDocument just fully reads the io.ReadSeeker into an byte array. doc, err := i.OpenDocument(&requests.OpenDocument{ - File: &fileData, - Password: request.Password, + FileReader: request.Reader, + FileReaderSize: request.Size, + Password: request.Password, }) if err != nil { return nil, err } return &responses.FPDF_LoadCustomDocument{Document: doc.Document}, nil - {{- else -}} + {{- else if eq $method.Name "OpenDocument" -}} + // Since multi-threaded usage implements gRPC, it can't serialize the reader onto that. + // To make it support the full interface, we just fully reads the io.ReadSeeker into + // an byte array. + if request.FileReader != nil { + fileData, err := ioutil.ReadAll(request.FileReader) + if err != nil { + return nil, err + } + request.FileReader = nil + request.FileReaderSize = 0 + request.File = &fileData + } return i.worker.plugin.{{ $method.Name }}(request) + {{- else if eq $method.Name "FPDF_SaveWithVersion" -}} + return i.worker.plugin.{{ $method.Name }}(request) + {{- else if eq $method.Name "FPDF_SaveAsCopy" -}} + if request.FileWriter != nil { + return nil, errors.New("using a file-writer is not supported on multi-threaded usage") + } + + return i.worker.plugin.{{ $method.Name }}(request) + {{- else -}} + return i.worker.plugin.{{ $method.Name }}(request) + {{- end }} {{- end }} } {{end}} \ No newline at end of file diff --git a/multi_threaded/generated.go b/multi_threaded/generated.go index 5b4b8ad1..39e9e161 100644 --- a/multi_threaded/generated.go +++ b/multi_threaded/generated.go @@ -108,11 +108,7 @@ func (i *pdfiumInstance) FPDFBitmap_Create(request *requests.FPDFBitmap_Create) } func (i *pdfiumInstance) FPDFBitmap_CreateEx(request *requests.FPDFBitmap_CreateEx) (*responses.FPDFBitmap_CreateEx, error) { - if i.closed { - return nil, errors.New("instance is closed") - } - - return i.worker.plugin.FPDFBitmap_CreateEx(request) + return nil, errors.New("unsupported method on multi-threaded usage") } func (i *pdfiumInstance) FPDFBitmap_Destroy(request *requests.FPDFBitmap_Destroy) (*responses.FPDFBitmap_Destroy, error) { @@ -1073,15 +1069,12 @@ func (i *pdfiumInstance) FPDF_LoadCustomDocument(request *requests.FPDF_LoadCust } // Since multi-threaded usage implements gRPC, it can't serialize the reader onto that. - // To make it support the full interface, we just completely read the file into memory. - fileData, err := ioutil.ReadAll(request.Reader) - if err != nil { - return nil, err - } - + // To make it support the full interface, we just rewrite it to OpenDocument, + // and OpenDocument just fully reads the io.ReadSeeker into an byte array. doc, err := i.OpenDocument(&requests.OpenDocument{ - File: &fileData, - Password: request.Password, + FileReader: request.Reader, + FileReaderSize: request.Size, + Password: request.Password, }) if err != nil { return nil, err @@ -1167,6 +1160,10 @@ func (i *pdfiumInstance) FPDF_SaveAsCopy(request *requests.FPDF_SaveAsCopy) (*re return nil, errors.New("instance is closed") } + if request.FileWriter != nil { + return nil, errors.New("using a file-writer is not supported on multi-threaded usage") + } + return i.worker.plugin.FPDF_SaveAsCopy(request) } @@ -1243,27 +1240,15 @@ func (i *pdfiumInstance) FPDF_VIEWERREF_GetPrintScaling(request *requests.FPDF_V } func (i *pdfiumInstance) FSDK_SetLocaltimeFunction(request *requests.FSDK_SetLocaltimeFunction) (*responses.FSDK_SetLocaltimeFunction, error) { - if i.closed { - return nil, errors.New("instance is closed") - } - - return i.worker.plugin.FSDK_SetLocaltimeFunction(request) + return nil, errors.New("unsupported method on multi-threaded usage") } func (i *pdfiumInstance) FSDK_SetTimeFunction(request *requests.FSDK_SetTimeFunction) (*responses.FSDK_SetTimeFunction, error) { - if i.closed { - return nil, errors.New("instance is closed") - } - - return i.worker.plugin.FSDK_SetTimeFunction(request) + return nil, errors.New("unsupported method on multi-threaded usage") } func (i *pdfiumInstance) FSDK_SetUnSpObjProcessHandler(request *requests.FSDK_SetUnSpObjProcessHandler) (*responses.FSDK_SetUnSpObjProcessHandler, error) { - if i.closed { - return nil, errors.New("instance is closed") - } - - return i.worker.plugin.FSDK_SetUnSpObjProcessHandler(request) + return nil, errors.New("unsupported method on multi-threaded usage") } func (i *pdfiumInstance) GetActionInfo(request *requests.GetActionInfo) (*responses.GetActionInfo, error) { @@ -1351,6 +1336,18 @@ func (i *pdfiumInstance) OpenDocument(request *requests.OpenDocument) (*response return nil, errors.New("instance is closed") } + // Since multi-threaded usage implements gRPC, it can't serialize the reader onto that. + // To make it support the full interface, we just fully reads the io.ReadSeeker into + // an byte array. + if request.FileReader != nil { + fileData, err := ioutil.ReadAll(request.FileReader) + if err != nil { + return nil, err + } + request.FileReader = nil + request.FileReaderSize = 0 + request.File = &fileData + } return i.worker.plugin.OpenDocument(request) } diff --git a/multi_threaded/multi_threaded_test.go b/multi_threaded/multi_threaded_test.go index 03ffda3e..ec2e0e18 100644 --- a/multi_threaded/multi_threaded_test.go +++ b/multi_threaded/multi_threaded_test.go @@ -1,11 +1,10 @@ package multi_threaded_test import ( - "github.com/klippa-app/go-pdfium/multi_threaded" - "log" "os" "time" + "github.com/klippa-app/go-pdfium/multi_threaded" "github.com/klippa-app/go-pdfium/shared_tests" . "github.com/onsi/ginkgo/v2" @@ -41,12 +40,8 @@ var _ = AfterSuite(func() { err := shared_tests.PdfiumInstance.Close() Expect(err).To(BeNil()) - log.Println("Closed instance") - err = shared_tests.PdfiumPool.Close() Expect(err).To(BeNil()) - - log.Println("Closed pool") }) var _ = Describe("Multi Threaded", func() { diff --git a/pdfium.go b/pdfium.go index aa2b5cb1..da1705b6 100644 --- a/pdfium.go +++ b/pdfium.go @@ -48,7 +48,12 @@ type Pdfium interface { // Start instance functions. // OpenDocument returns a PDFium references for the given file data. - // This is a gateway to FPDF_LoadMemDocument, FPDF_LoadMemDocument64, FPDF_LoadDocument and FPDF_LoadCustomDocument. + // This is a gateway to FPDF_LoadMemDocument, FPDF_LoadMemDocument64, + // FPDF_LoadDocument and FPDF_LoadCustomDocument. Please note that + // FPDF_LoadCustomDocument will only work efficiently on single-threaded + // usage, on multi-threaded this will just fully read from the reader + // into a byte array before it's being sent over to PDFium. + // This method already checks FPDF_GetLastError internally for the result. OpenDocument(request *requests.OpenDocument) (*responses.OpenDocument, error) // Close closes the instance. @@ -127,20 +132,17 @@ type Pdfium interface { // FPDF_LoadDocument opens and load a PDF document from a file path. // Loaded document can be closed by FPDF_CloseDocument(). - // If this function fails, you can use FPDF_GetLastError() to retrieve - // the reason why it failed. + // This method already checks FPDF_GetLastError internally for the result. FPDF_LoadDocument(request *requests.FPDF_LoadDocument) (*responses.FPDF_LoadDocument, error) // FPDF_LoadMemDocument opens and load a PDF document from memory. // Loaded document can be closed by FPDF_CloseDocument(). - // If this function fails, you can use FPDF_GetLastError() to retrieve - // the reason why it failed. + // This method already checks FPDF_GetLastError internally for the result. FPDF_LoadMemDocument(request *requests.FPDF_LoadMemDocument) (*responses.FPDF_LoadMemDocument, error) // FPDF_LoadMemDocument64 opens and load a PDF document from memory. // Loaded document can be closed by FPDF_CloseDocument(). - // If this function fails, you can use FPDF_GetLastError() to retrieve - // the reason why it failed. + // This method already checks FPDF_GetLastError internally for the result. FPDF_LoadMemDocument64(request *requests.FPDF_LoadMemDocument64) (*responses.FPDF_LoadMemDocument64, error) // FPDF_LoadCustomDocument loads a PDF document from a custom access descriptor. @@ -150,8 +152,7 @@ type Pdfium interface { // over gRPC. The single-threaded usage will actually efficiently walk over // the PDF as it's being used by PDFium. // Loaded document can be closed by FPDF_CloseDocument(). - // If this function fails, you can use FPDF_GetLastError() to retrieve - // the reason why it failed. + // This method already checks FPDF_GetLastError internally for the result. FPDF_LoadCustomDocument(request *requests.FPDF_LoadCustomDocument) (*responses.FPDF_LoadCustomDocument, error) // FPDF_CloseDocument closes the references, releases the resources. @@ -159,6 +160,9 @@ type Pdfium interface { // FPDF_GetLastError returns the last error code of a PDFium function, which is just called. // Usually, this function is called after a PDFium function returns, in order to check the error code of the previous PDFium function. + // Please note that when using go-pdfium from the same instance (on single-threaded any instance) + // from different subroutines, FPDF_GetLastError might already be reset from + // executing another PDFium method. FPDF_GetLastError(request *requests.FPDF_GetLastError) (*responses.FPDF_GetLastError, error) // FPDF_SetSandBoxPolicy set the policy for the sandbox environment.