mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-16 12:22:17 +01:00
feat(otel): add more tracing when communicating with internal tools / external APIs
This commit is contained in:
@@ -21,7 +21,10 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/mholt/archives"
|
"github.com/mholt/archives"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"golang.org/x/text/unicode/norm"
|
"golang.org/x/text/unicode/norm"
|
||||||
|
|
||||||
@@ -232,10 +235,18 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys
|
|||||||
return fmt.Errorf("filter URL: %w", err)
|
return fmt.Errorf("filter URL: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.DebugContext(ctx, fmt.Sprintf("download file from '%s'", dl.Url))
|
dlCtx, dlSpan := gotenberg.Tracer().Start(ctx, "GET Download From",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress(dl.Url)),
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.DebugContext(dlCtx, fmt.Sprintf("download file from '%s'", dl.Url))
|
||||||
|
|
||||||
req, err := retryablehttp.NewRequest(http.MethodGet, dl.Url, nil)
|
req, err := retryablehttp.NewRequest(http.MethodGet, dl.Url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
dlSpan.RecordError(err)
|
||||||
|
dlSpan.SetStatus(codes.Error, err.Error())
|
||||||
|
dlSpan.End()
|
||||||
return fmt.Errorf("create request to '%s': %w", dl.Url, err)
|
return fmt.Errorf("create request to '%s': %w", dl.Url, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +256,7 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inject OTEL trace context into outbound request.
|
// Inject OTEL trace context into outbound request.
|
||||||
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
|
otel.GetTextMapPropagator().Inject(dlCtx, propagation.HeaderCarrier(req.Header))
|
||||||
|
|
||||||
// Propagate correlation ID header.
|
// Propagate correlation ID header.
|
||||||
if correlationIdHeader, ok := echoCtx.Get("correlationIdHeader").(string); ok {
|
if correlationIdHeader, ok := echoCtx.Get("correlationIdHeader").(string); ok {
|
||||||
@@ -268,6 +279,9 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys
|
|||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
dlSpan.RecordError(err)
|
||||||
|
dlSpan.SetStatus(codes.Error, err.Error())
|
||||||
|
dlSpan.End()
|
||||||
return WrapError(
|
return WrapError(
|
||||||
fmt.Errorf("download file from to '%s': %w", dl.Url, err),
|
fmt.Errorf("download file from to '%s': %w", dl.Url, err),
|
||||||
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("Unable to download file from '%s': %s", dl.Url, err)),
|
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("Unable to download file from '%s': %s", dl.Url, err)),
|
||||||
@@ -281,16 +295,24 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
dlErr := fmt.Errorf("download file from to '%s': got status: '%s'", dl.Url, resp.Status)
|
||||||
|
dlSpan.RecordError(dlErr)
|
||||||
|
dlSpan.SetStatus(codes.Error, dlErr.Error())
|
||||||
|
dlSpan.End()
|
||||||
return WrapError(
|
return WrapError(
|
||||||
fmt.Errorf("download file from to '%s': got status: '%s'", dl.Url, resp.Status),
|
dlErr,
|
||||||
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("Unable to download file from '%s': got status: '%s'", dl.Url, resp.Status)),
|
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("Unable to download file from '%s': got status: '%s'", dl.Url, resp.Status)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentDisposition := resp.Header.Get("Content-Disposition")
|
contentDisposition := resp.Header.Get("Content-Disposition")
|
||||||
if contentDisposition == "" {
|
if contentDisposition == "" {
|
||||||
|
dlErr := fmt.Errorf("no 'Content-Disposition' header from '%s'", dl.Url)
|
||||||
|
dlSpan.RecordError(dlErr)
|
||||||
|
dlSpan.SetStatus(codes.Error, dlErr.Error())
|
||||||
|
dlSpan.End()
|
||||||
return WrapError(
|
return WrapError(
|
||||||
fmt.Errorf("no 'Content-Disposition' header from '%s'", dl.Url),
|
dlErr,
|
||||||
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("No 'Content-Disposition' header from '%s'", dl.Url)),
|
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("No 'Content-Disposition' header from '%s'", dl.Url)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -300,16 +322,24 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys
|
|||||||
// See: https://github.com/golang/go/issues/69551.
|
// See: https://github.com/golang/go/issues/69551.
|
||||||
_, params, err := mime.ParseMediaType(contentDisposition)
|
_, params, err := mime.ParseMediaType(contentDisposition)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
dlErr := fmt.Errorf("parse 'Content-Disposition' header '%s' from '%s': %w", contentDisposition, dl.Url, err)
|
||||||
|
dlSpan.RecordError(dlErr)
|
||||||
|
dlSpan.SetStatus(codes.Error, dlErr.Error())
|
||||||
|
dlSpan.End()
|
||||||
return WrapError(
|
return WrapError(
|
||||||
fmt.Errorf("parse 'Content-Disposition' header '%s' from '%s': %w", contentDisposition, dl.Url, err),
|
dlErr,
|
||||||
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("Invalid 'Content-Disposition' header '%s' from '%s': %s", contentDisposition, dl.Url, err)),
|
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("Invalid 'Content-Disposition' header '%s' from '%s': %s", contentDisposition, dl.Url, err)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
filename, ok := params["filename"]
|
filename, ok := params["filename"]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
dlErr := fmt.Errorf("get filename from 'Content-Disposition' header '%s' from '%s'", contentDisposition, dl.Url)
|
||||||
|
dlSpan.RecordError(dlErr)
|
||||||
|
dlSpan.SetStatus(codes.Error, dlErr.Error())
|
||||||
|
dlSpan.End()
|
||||||
return WrapError(
|
return WrapError(
|
||||||
fmt.Errorf("get filename from 'Content-Disposition' header '%s' from '%s'", contentDisposition, dl.Url),
|
dlErr,
|
||||||
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("Invalid 'Content-Disposition' header '%s' from '%s': no filename", contentDisposition, dl.Url)),
|
NewSentinelHttpError(http.StatusBadRequest, fmt.Sprintf("Invalid 'Content-Disposition' header '%s' from '%s': no filename", contentDisposition, dl.Url)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -322,7 +352,11 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys
|
|||||||
|
|
||||||
out, err := os.Create(path)
|
out, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create local file: %w", err)
|
dlErr := fmt.Errorf("create local file: %w", err)
|
||||||
|
dlSpan.RecordError(dlErr)
|
||||||
|
dlSpan.SetStatus(codes.Error, dlErr.Error())
|
||||||
|
dlSpan.End()
|
||||||
|
return dlErr
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
err := out.Close()
|
err := out.Close()
|
||||||
@@ -336,9 +370,16 @@ func newContext(echoCtx echo.Context, logger *slog.Logger, fs *gotenberg.FileSys
|
|||||||
|
|
||||||
_, err = io.Copy(out, reader)
|
_, err = io.Copy(out, reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("copy downloaded file from '%s' to local file: %w", dl.Url, err)
|
dlErr := fmt.Errorf("copy downloaded file from '%s' to local file: %w", dl.Url, err)
|
||||||
|
dlSpan.RecordError(dlErr)
|
||||||
|
dlSpan.SetStatus(codes.Error, dlErr.Error())
|
||||||
|
dlSpan.End()
|
||||||
|
return dlErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dlSpan.SetStatus(codes.Ok, "")
|
||||||
|
dlSpan.End()
|
||||||
|
|
||||||
ctx.files[filename] = path
|
ctx.files[filename] = path
|
||||||
|
|
||||||
// Route the downloaded file to the appropriate field bucket.
|
// Route the downloaded file to the appropriate field bucket.
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ import (
|
|||||||
"github.com/dlclark/regexp2"
|
"github.com/dlclark/regexp2"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
||||||
@@ -784,6 +787,12 @@ func (mod *Chromium) Routes() ([]api.Route, error) {
|
|||||||
|
|
||||||
// Pdf converts a URL to PDF.
|
// Pdf converts a URL to PDF.
|
||||||
func (mod *Chromium) Pdf(ctx context.Context, logger *slog.Logger, url, outputPath string, options PdfOptions) error {
|
func (mod *Chromium) Pdf(ctx context.Context, logger *slog.Logger, url, outputPath string, options PdfOptions) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "chromium.Pdf",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("chromium")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
var conversionStart time.Time
|
var conversionStart time.Time
|
||||||
|
|
||||||
@@ -844,12 +853,23 @@ func (mod *Chromium) Pdf(ctx context.Context, logger *slog.Logger, url, outputPa
|
|||||||
if fileInfo, statErr := os.Stat(outputPath); statErr == nil {
|
if fileInfo, statErr := os.Stat(outputPath); statErr == nil {
|
||||||
mod.pdfOutputSizeCounter.Record(ctx, fileInfo.Size())
|
mod.pdfOutputSizeCounter.Record(ctx, fileInfo.Size())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mod *Chromium) Screenshot(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error {
|
func (mod *Chromium) Screenshot(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "chromium.Screenshot",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("chromium")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
var conversionStart time.Time
|
var conversionStart time.Time
|
||||||
|
|
||||||
@@ -912,8 +932,13 @@ func (mod *Chromium) Screenshot(ctx context.Context, logger *slog.Logger, url, o
|
|||||||
if fileInfo, statErr := os.Stat(outputPath); statErr == nil {
|
if fileInfo, statErr := os.Stat(outputPath); statErr == nil {
|
||||||
mod.imageOutputSizeCounter.Record(ctx, fileInfo.Size())
|
mod.imageOutputSizeCounter.Record(ctx, fileInfo.Size())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/barasher/go-exiftool"
|
"github.com/barasher/go-exiftool"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
@@ -75,29 +78,74 @@ func (engine *ExifTool) Debug() map[string]any {
|
|||||||
|
|
||||||
// Merge is not available in this implementation.
|
// Merge is not available in this implementation.
|
||||||
func (engine *ExifTool) Merge(ctx context.Context, logger *slog.Logger, inputPaths []string, outputPath string) error {
|
func (engine *ExifTool) Merge(ctx context.Context, logger *slog.Logger, inputPaths []string, outputPath string) error {
|
||||||
return fmt.Errorf("merge PDFs with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.Merge",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("merge PDFs with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split is not available in this implementation.
|
// Split is not available in this implementation.
|
||||||
func (engine *ExifTool) Split(ctx context.Context, logger *slog.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
func (engine *ExifTool) Split(ctx context.Context, logger *slog.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
return nil, fmt.Errorf("split PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.Split",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("split PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten is not available in this implementation.
|
// Flatten is not available in this implementation.
|
||||||
func (engine *ExifTool) Flatten(ctx context.Context, logger *slog.Logger, inputPath string) error {
|
func (engine *ExifTool) Flatten(ctx context.Context, logger *slog.Logger, inputPath string) error {
|
||||||
return fmt.Errorf("flatten PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.Flatten",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("flatten PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert is not available in this implementation.
|
// Convert is not available in this implementation.
|
||||||
func (engine *ExifTool) Convert(ctx context.Context, logger *slog.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
func (engine *ExifTool) Convert(ctx context.Context, logger *slog.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return fmt.Errorf("convert PDF to '%+v' with ExifTool: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.Convert",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("convert PDF to '%+v' with ExifTool: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadMetadata extracts the metadata of a given PDF file.
|
// ReadMetadata extracts the metadata of a given PDF file.
|
||||||
func (engine *ExifTool) ReadMetadata(ctx context.Context, logger *slog.Logger, inputPath string) (map[string]any, error) {
|
func (engine *ExifTool) ReadMetadata(ctx context.Context, logger *slog.Logger, inputPath string) (map[string]any, error) {
|
||||||
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.ReadMetadata",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
exifTool, err := exiftool.NewExiftool(exiftool.SetExiftoolBinaryPath(engine.binPath))
|
exifTool, err := exiftool.NewExiftool(exiftool.SetExiftoolBinaryPath(engine.binPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("new ExifTool: %w", err)
|
err = fmt.Errorf("new ExifTool: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(exifTool *exiftool.Exiftool) {
|
defer func(exifTool *exiftool.Exiftool) {
|
||||||
@@ -109,17 +157,30 @@ func (engine *ExifTool) ReadMetadata(ctx context.Context, logger *slog.Logger, i
|
|||||||
|
|
||||||
fileMetadata := exifTool.ExtractMetadata(inputPath)
|
fileMetadata := exifTool.ExtractMetadata(inputPath)
|
||||||
if fileMetadata[0].Err != nil {
|
if fileMetadata[0].Err != nil {
|
||||||
return nil, fmt.Errorf("read metadata with ExitfTool: %w", fileMetadata[0].Err)
|
err = fmt.Errorf("read metadata with ExitfTool: %w", fileMetadata[0].Err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return fileMetadata[0].Fields, nil
|
return fileMetadata[0].Fields, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMetadata writes the metadata into a given PDF file.
|
// WriteMetadata writes the metadata into a given PDF file.
|
||||||
func (engine *ExifTool) WriteMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]any, inputPath string) error {
|
func (engine *ExifTool) WriteMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]any, inputPath string) error {
|
||||||
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.WriteMetadata",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
exifTool, err := exiftool.NewExiftool(exiftool.SetExiftoolBinaryPath(engine.binPath))
|
exifTool, err := exiftool.NewExiftool(exiftool.SetExiftoolBinaryPath(engine.binPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("new ExifTool: %w", err)
|
err = fmt.Errorf("new ExifTool: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(exifTool *exiftool.Exiftool) {
|
defer func(exifTool *exiftool.Exiftool) {
|
||||||
@@ -131,7 +192,10 @@ func (engine *ExifTool) WriteMetadata(ctx context.Context, logger *slog.Logger,
|
|||||||
|
|
||||||
fileMetadata := exifTool.ExtractMetadata(inputPath)
|
fileMetadata := exifTool.ExtractMetadata(inputPath)
|
||||||
if fileMetadata[0].Err != nil {
|
if fileMetadata[0].Err != nil {
|
||||||
return fmt.Errorf("read metadata with ExitfTool: %w", fileMetadata[0].Err)
|
err = fmt.Errorf("read metadata with ExitfTool: %w", fileMetadata[0].Err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define a list of derived, system, or computed tags that ExifTool
|
// Define a list of derived, system, or computed tags that ExifTool
|
||||||
@@ -175,7 +239,10 @@ func (engine *ExifTool) WriteMetadata(ctx context.Context, logger *slog.Logger,
|
|||||||
strs[i] = str
|
strs[i] = str
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Errorf("write PDF metadata with ExifTool: %s %+v %s %w", key, val, reflect.TypeFor[[]any](), gotenberg.ErrPdfEngineMetadataValueNotSupported)
|
err = fmt.Errorf("write PDF metadata with ExifTool: %s %+v %s %w", key, val, reflect.TypeFor[[]any](), gotenberg.ErrPdfEngineMetadataValueNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
fileMetadata[0].SetStrings(key, strs)
|
fileMetadata[0].SetStrings(key, strs)
|
||||||
case bool:
|
case bool:
|
||||||
@@ -191,82 +258,174 @@ func (engine *ExifTool) WriteMetadata(ctx context.Context, logger *slog.Logger,
|
|||||||
// TODO: support more complex cases, e.g., arrays and nested objects
|
// TODO: support more complex cases, e.g., arrays and nested objects
|
||||||
// (limitations in underlying library).
|
// (limitations in underlying library).
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("write PDF metadata with ExifTool: %s %+v %s %w", key, val, reflect.TypeOf(val), gotenberg.ErrPdfEngineMetadataValueNotSupported)
|
err = fmt.Errorf("write PDF metadata with ExifTool: %s %+v %s %w", key, val, reflect.TypeOf(val), gotenberg.ErrPdfEngineMetadataValueNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exifTool.WriteMetadata(fileMetadata)
|
exifTool.WriteMetadata(fileMetadata)
|
||||||
if fileMetadata[0].Err != nil {
|
if fileMetadata[0].Err != nil {
|
||||||
return fmt.Errorf("write PDF metadata with ExifTool: %w", fileMetadata[0].Err)
|
err = fmt.Errorf("write PDF metadata with ExifTool: %w", fileMetadata[0].Err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PageCount returns the number of pages in a PDF file using ExifTool.
|
// PageCount returns the number of pages in a PDF file using ExifTool.
|
||||||
func (engine *ExifTool) PageCount(ctx context.Context, logger *slog.Logger, inputPath string) (int, error) {
|
func (engine *ExifTool) PageCount(ctx context.Context, logger *slog.Logger, inputPath string) (int, error) {
|
||||||
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.PageCount",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
metadata, err := engine.ReadMetadata(ctx, logger, inputPath)
|
metadata, err := engine.ReadMetadata(ctx, logger, inputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("read metadata with ExifTool: %w", err)
|
err = fmt.Errorf("read metadata with ExifTool: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pageCountValue, ok := metadata["PageCount"]
|
pageCountValue, ok := metadata["PageCount"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, errors.New("PageCount not found in metadata")
|
err = errors.New("PageCount not found in metadata")
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch val := pageCountValue.(type) {
|
switch val := pageCountValue.(type) {
|
||||||
case int:
|
case int:
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return val, nil
|
return val, nil
|
||||||
case int64:
|
case int64:
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return int(val), nil
|
return int(val), nil
|
||||||
case float64:
|
case float64:
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return int(val), nil
|
return int(val), nil
|
||||||
case string:
|
case string:
|
||||||
var res int
|
var res int
|
||||||
_, err := fmt.Sscanf(val, "%d", &res)
|
_, err := fmt.Sscanf(val, "%d", &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parse PageCount string '%s': %w", val, err)
|
err = fmt.Errorf("parse PageCount string '%s': %w", val, err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return res, nil
|
return res, nil
|
||||||
default:
|
default:
|
||||||
return 0, fmt.Errorf("unexpected PageCount type '%T'", pageCountValue)
|
err = fmt.Errorf("unexpected PageCount type '%T'", pageCountValue)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBookmarks is not available in this implementation.
|
// WriteBookmarks is not available in this implementation.
|
||||||
func (engine *ExifTool) WriteBookmarks(ctx context.Context, logger *slog.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error {
|
func (engine *ExifTool) WriteBookmarks(ctx context.Context, logger *slog.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error {
|
||||||
return fmt.Errorf("write PDF bookmarks with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.WriteBookmarks",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("write PDF bookmarks with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadBookmarks is not available in this implementation.
|
// ReadBookmarks is not available in this implementation.
|
||||||
func (engine *ExifTool) ReadBookmarks(ctx context.Context, logger *slog.Logger, inputPath string) ([]gotenberg.Bookmark, error) {
|
func (engine *ExifTool) ReadBookmarks(ctx context.Context, logger *slog.Logger, inputPath string) ([]gotenberg.Bookmark, error) {
|
||||||
return nil, fmt.Errorf("read PDF bookmarks with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.ReadBookmarks",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("read PDF bookmarks with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt is not available in this implementation.
|
// Encrypt is not available in this implementation.
|
||||||
func (engine *ExifTool) Encrypt(ctx context.Context, logger *slog.Logger, inputPath, userPassword, ownerPassword string) error {
|
func (engine *ExifTool) Encrypt(ctx context.Context, logger *slog.Logger, inputPath, userPassword, ownerPassword string) error {
|
||||||
return fmt.Errorf("encrypt PDF using ExifTool: %w", gotenberg.ErrPdfEncryptionNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.Encrypt",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("encrypt PDF using ExifTool: %w", gotenberg.ErrPdfEncryptionNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbedFiles is not available in this implementation.
|
// EmbedFiles is not available in this implementation.
|
||||||
func (engine *ExifTool) EmbedFiles(ctx context.Context, logger *slog.Logger, filePaths []string, inputPath string) error {
|
func (engine *ExifTool) EmbedFiles(ctx context.Context, logger *slog.Logger, filePaths []string, inputPath string) error {
|
||||||
return fmt.Errorf("embed files with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.EmbedFiles",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("embed files with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watermark is not available in this implementation.
|
// Watermark is not available in this implementation.
|
||||||
func (engine *ExifTool) Watermark(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
func (engine *ExifTool) Watermark(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||||
return fmt.Errorf("watermark PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.Watermark",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("watermark PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stamp is not available in this implementation.
|
// Stamp is not available in this implementation.
|
||||||
func (engine *ExifTool) Stamp(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
func (engine *ExifTool) Stamp(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||||
return fmt.Errorf("stamp PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.Stamp",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("stamp PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate is not available in this implementation.
|
// Rotate is not available in this implementation.
|
||||||
func (engine *ExifTool) Rotate(ctx context.Context, logger *slog.Logger, inputPath string, angle int, pages string) error {
|
func (engine *ExifTool) Rotate(ctx context.Context, logger *slog.Logger, inputPath string, angle int, pages string) error {
|
||||||
return fmt.Errorf("rotate PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "exiftool.Rotate",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("exiftool")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("rotate PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface guards.
|
// Interface guards.
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import (
|
|||||||
"github.com/alexliesenfeld/health"
|
"github.com/alexliesenfeld/health"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
@@ -528,6 +531,12 @@ func (a *Api) LibreOffice() (Uno, error) {
|
|||||||
|
|
||||||
// Pdf converts a document to PDF.
|
// Pdf converts a document to PDF.
|
||||||
func (a *Api) Pdf(ctx context.Context, logger *slog.Logger, inputPath, outputPath string, options Options) error {
|
func (a *Api) Pdf(ctx context.Context, logger *slog.Logger, inputPath, outputPath string, options Options) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "libreoffice.Pdf",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("libreoffice")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
var conversionStart time.Time
|
var conversionStart time.Time
|
||||||
|
|
||||||
@@ -579,6 +588,7 @@ func (a *Api) Pdf(ctx context.Context, logger *slog.Logger, inputPath, outputPat
|
|||||||
a.pdfOutputSizeCounter.Record(ctx, stat.Size(), attrs)
|
a.pdfOutputSizeCounter.Record(ctx, stat.Size(), attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,6 +598,8 @@ func (a *Api) Pdf(ctx context.Context, logger *slog.Logger, inputPath, outputPat
|
|||||||
return a.Pdf(ctx, logger, inputPath, outputPath, options)
|
return a.Pdf(ctx, logger, inputPath, outputPath, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
return fmt.Errorf("supervisor run task: %w", err)
|
return fmt.Errorf("supervisor run task: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -96,25 +100,44 @@ func (engine *PdfCpu) Debug() map[string]any {
|
|||||||
|
|
||||||
// Merge combines multiple PDFs into a single PDF.
|
// Merge combines multiple PDFs into a single PDF.
|
||||||
func (engine *PdfCpu) Merge(ctx context.Context, logger *slog.Logger, inputPaths []string, outputPath string) error {
|
func (engine *PdfCpu) Merge(ctx context.Context, logger *slog.Logger, inputPaths []string, outputPath string) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Merge",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
args := make([]string, 0, 2+len(inputPaths))
|
args := make([]string, 0, 2+len(inputPaths))
|
||||||
args = append(args, "merge", outputPath)
|
args = append(args, "merge", outputPath)
|
||||||
args = append(args, inputPaths...)
|
args = append(args, inputPaths...)
|
||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("merge PDFs with pdfcpu: %w", err)
|
err = fmt.Errorf("merge PDFs with pdfcpu: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split splits a given PDF file.
|
// Split splits a given PDF file.
|
||||||
func (engine *PdfCpu) Split(ctx context.Context, logger *slog.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
func (engine *PdfCpu) Split(ctx context.Context, logger *slog.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Split",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
var args []string
|
var args []string
|
||||||
|
|
||||||
switch mode.Mode {
|
switch mode.Mode {
|
||||||
@@ -128,17 +151,26 @@ func (engine *PdfCpu) Split(ctx context.Context, logger *slog.Logger, mode goten
|
|||||||
}
|
}
|
||||||
args = append(args, "extract", "-mode", "page", "-pages", mode.Span, inputPath, outputDirPath)
|
args = append(args, "extract", "-mode", "page", "-pages", mode.Span, inputPath, outputDirPath)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("split PDFs using mode '%s' with pdfcpu: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
err := fmt.Errorf("split PDFs using mode '%s' with pdfcpu: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("split PDFs with pdfcpu: %w", err)
|
err = fmt.Errorf("split PDFs with pdfcpu: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputPaths []string
|
var outputPaths []string
|
||||||
@@ -155,46 +187,104 @@ func (engine *PdfCpu) Split(ctx context.Context, logger *slog.Logger, mode goten
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("walk directory to find resulting PDFs from split with pdfcpu: %w", err)
|
err = fmt.Errorf("walk directory to find resulting PDFs from split with pdfcpu: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(digitSuffixSort(outputPaths))
|
sort.Sort(digitSuffixSort(outputPaths))
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return outputPaths, nil
|
return outputPaths, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten is not available in this implementation.
|
// Flatten is not available in this implementation.
|
||||||
func (engine *PdfCpu) Flatten(ctx context.Context, logger *slog.Logger, inputPath string) error {
|
func (engine *PdfCpu) Flatten(ctx context.Context, logger *slog.Logger, inputPath string) error {
|
||||||
return fmt.Errorf("flatten PDF with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Flatten",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("flatten PDF with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert is not available in this implementation.
|
// Convert is not available in this implementation.
|
||||||
func (engine *PdfCpu) Convert(ctx context.Context, logger *slog.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
func (engine *PdfCpu) Convert(ctx context.Context, logger *slog.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return fmt.Errorf("convert PDF to '%+v' with pdfcpu: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Convert",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("convert PDF to '%+v' with pdfcpu: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadMetadata is not available in this implementation.
|
// ReadMetadata is not available in this implementation.
|
||||||
func (engine *PdfCpu) ReadMetadata(ctx context.Context, logger *slog.Logger, inputPath string) (map[string]any, error) {
|
func (engine *PdfCpu) ReadMetadata(ctx context.Context, logger *slog.Logger, inputPath string) (map[string]any, error) {
|
||||||
return nil, fmt.Errorf("read PDF metadata with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdfcpu.ReadMetadata",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("read PDF metadata with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMetadata is not available in this implementation.
|
// WriteMetadata is not available in this implementation.
|
||||||
func (engine *PdfCpu) WriteMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]any, inputPath string) error {
|
func (engine *PdfCpu) WriteMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]any, inputPath string) error {
|
||||||
return fmt.Errorf("write PDF metadata with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdfcpu.WriteMetadata",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("write PDF metadata with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PageCount is not available in this implementation.
|
// PageCount is not available in this implementation.
|
||||||
func (engine *PdfCpu) PageCount(ctx context.Context, logger *slog.Logger, inputPath string) (int, error) {
|
func (engine *PdfCpu) PageCount(ctx context.Context, logger *slog.Logger, inputPath string) (int, error) {
|
||||||
return 0, fmt.Errorf("page count with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdfcpu.PageCount",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("page count with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadBookmarks reads the document outline (bookmarks) of a PDF file using pdfcpu.
|
// ReadBookmarks reads the document outline (bookmarks) of a PDF file using pdfcpu.
|
||||||
func (engine *PdfCpu) ReadBookmarks(ctx context.Context, logger *slog.Logger, inputPath string) ([]gotenberg.Bookmark, error) {
|
func (engine *PdfCpu) ReadBookmarks(ctx context.Context, logger *slog.Logger, inputPath string) ([]gotenberg.Bookmark, error) {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.ReadBookmarks",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
tmpPath := fmt.Sprintf("%s.read.json", inputPath)
|
tmpPath := fmt.Sprintf("%s.read.json", inputPath)
|
||||||
args := []string{"bookmarks", "export", inputPath, tmpPath}
|
args := []string{"bookmarks", "export", inputPath, tmpPath}
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -213,41 +303,55 @@ func (engine *PdfCpu) ReadBookmarks(ctx context.Context, logger *slog.Logger, in
|
|||||||
// If the file wasn't created, or it was created but is 0 bytes,
|
// If the file wasn't created, or it was created but is 0 bytes,
|
||||||
// it means pdfcpu had no bookmarks to write.
|
// it means pdfcpu had no bookmarks to write.
|
||||||
if os.IsNotExist(statErr) || (statErr == nil && info.Size() == 0) {
|
if os.IsNotExist(statErr) || (statErr == nil && info.Size() == 0) {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return make([]gotenberg.Bookmark, 0), nil
|
return make([]gotenberg.Bookmark, 0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: Check the error string just in case pdfcpu failed without
|
// Fallback: Check the error string just in case pdfcpu failed without
|
||||||
// touching the file.
|
// touching the file.
|
||||||
if strings.Contains(strings.ToLower(cmdErr.Error()), "no bookmarks") {
|
if strings.Contains(strings.ToLower(cmdErr.Error()), "no bookmarks") {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return make([]gotenberg.Bookmark, 0), nil
|
return make([]gotenberg.Bookmark, 0), nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("read bookmarks with pdfcpu: %w", cmdErr)
|
err = fmt.Errorf("read bookmarks with pdfcpu: %w", cmdErr)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If cmd succeeded, but output a 0-byte file anyway.
|
// If cmd succeeded, but output a 0-byte file anyway.
|
||||||
if info != nil && info.Size() == 0 {
|
if info != nil && info.Size() == 0 {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return make([]gotenberg.Bookmark, 0), nil
|
return make([]gotenberg.Bookmark, 0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the file content.
|
// Read the file content.
|
||||||
jsonBytes, err := os.ReadFile(tmpPath)
|
jsonBytes, err := os.ReadFile(tmpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read temporary bookmarks JSON file: %w", err)
|
err = fmt.Errorf("read temporary bookmarks JSON file: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the content is just empty whitespace.
|
// Check if the content is just empty whitespace.
|
||||||
if len(bytes.TrimSpace(jsonBytes)) == 0 {
|
if len(bytes.TrimSpace(jsonBytes)) == 0 {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return make([]gotenberg.Bookmark, 0), nil
|
return make([]gotenberg.Bookmark, 0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var data pdfcpuBookmarks
|
var data pdfcpuBookmarks
|
||||||
err = json.Unmarshal(jsonBytes, &data)
|
err = json.Unmarshal(jsonBytes, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unmarshal bookmarks: %w", err)
|
err = fmt.Errorf("unmarshal bookmarks: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety check: Does the parsed JSON actually contain bookmarks?
|
// Safety check: Does the parsed JSON actually contain bookmarks?
|
||||||
if len(data.Bookmarks) == 0 {
|
if len(data.Bookmarks) == 0 {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return make([]gotenberg.Bookmark, 0), nil
|
return make([]gotenberg.Bookmark, 0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,12 +368,20 @@ func (engine *PdfCpu) ReadBookmarks(ctx context.Context, logger *slog.Logger, in
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return mapBookmarks(data.Bookmarks), nil
|
return mapBookmarks(data.Bookmarks), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBookmarks adds a document outline (bookmarks) to a PDF file using pdfcpu.
|
// WriteBookmarks adds a document outline (bookmarks) to a PDF file using pdfcpu.
|
||||||
func (engine *PdfCpu) WriteBookmarks(ctx context.Context, logger *slog.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error {
|
func (engine *PdfCpu) WriteBookmarks(ctx context.Context, logger *slog.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.WriteBookmarks",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
if len(bookmarks) == 0 {
|
if len(bookmarks) == 0 {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,13 +404,19 @@ func (engine *PdfCpu) WriteBookmarks(ctx context.Context, logger *slog.Logger, i
|
|||||||
|
|
||||||
jsonBytes, err := json.Marshal(data)
|
jsonBytes, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshal bookmarks: %w", err)
|
err = fmt.Errorf("marshal bookmarks: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpPath := fmt.Sprintf("%s.json", inputPath)
|
tmpPath := fmt.Sprintf("%s.json", inputPath)
|
||||||
err = os.WriteFile(tmpPath, jsonBytes, 0o600)
|
err = os.WriteFile(tmpPath, jsonBytes, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("write temporary bookmarks JSON file: %w", err)
|
err = fmt.Errorf("write temporary bookmarks JSON file: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -311,21 +429,35 @@ func (engine *PdfCpu) WriteBookmarks(ctx context.Context, logger *slog.Logger, i
|
|||||||
args := []string{"bookmarks", "import", "-replace", inputPath, tmpPath, inputPath}
|
args := []string{"bookmarks", "import", "-replace", inputPath, tmpPath, inputPath}
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("write bookmarks with pdfcpu: %w", err)
|
err = fmt.Errorf("write bookmarks with pdfcpu: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbedFiles embeds files into a PDF. All files are embedded as file attachments
|
// EmbedFiles embeds files into a PDF. All files are embedded as file attachments
|
||||||
// without modifying the main PDF content.
|
// without modifying the main PDF content.
|
||||||
func (engine *PdfCpu) EmbedFiles(ctx context.Context, logger *slog.Logger, filePaths []string, inputPath string) error {
|
func (engine *PdfCpu) EmbedFiles(ctx context.Context, logger *slog.Logger, filePaths []string, inputPath string) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.EmbedFiles",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
if len(filePaths) == 0 {
|
if len(filePaths) == 0 {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,21 +469,37 @@ func (engine *PdfCpu) EmbedFiles(ctx context.Context, logger *slog.Logger, fileP
|
|||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command for attaching files: %w", err)
|
err = fmt.Errorf("create command for attaching files: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("attach files with pdfcpu: %w", err)
|
err = fmt.Errorf("attach files with pdfcpu: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt adds password protection to a PDF file using pdfcpu.
|
// Encrypt adds password protection to a PDF file using pdfcpu.
|
||||||
func (engine *PdfCpu) Encrypt(ctx context.Context, logger *slog.Logger, inputPath, userPassword, ownerPassword string) error {
|
func (engine *PdfCpu) Encrypt(ctx context.Context, logger *slog.Logger, inputPath, userPassword, ownerPassword string) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Encrypt",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
if userPassword == "" {
|
if userPassword == "" {
|
||||||
return errors.New("user password cannot be empty")
|
err := errors.New("user password cannot be empty")
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ownerPassword == "" {
|
if ownerPassword == "" {
|
||||||
@@ -368,29 +516,70 @@ func (engine *PdfCpu) Encrypt(ctx context.Context, logger *slog.Logger, inputPat
|
|||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("encrypt PDF with pdfcpu: %w", err)
|
err = fmt.Errorf("encrypt PDF with pdfcpu: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watermark applies a watermark (behind page content) to a PDF file using pdfcpu.
|
// Watermark applies a watermark (behind page content) to a PDF file using pdfcpu.
|
||||||
func (engine *PdfCpu) Watermark(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
func (engine *PdfCpu) Watermark(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||||
return engine.applyStampOrWatermark(ctx, logger, "watermark", inputPath, stamp)
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Watermark",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := engine.applyStampOrWatermark(ctx, logger, "watermark", inputPath, stamp)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stamp applies a stamp (on top of page content) to a PDF file using pdfcpu.
|
// Stamp applies a stamp (on top of page content) to a PDF file using pdfcpu.
|
||||||
func (engine *PdfCpu) Stamp(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
func (engine *PdfCpu) Stamp(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||||
return engine.applyStampOrWatermark(ctx, logger, "stamp", inputPath, stamp)
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Stamp",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := engine.applyStampOrWatermark(ctx, logger, "stamp", inputPath, stamp)
|
||||||
|
if err != nil {
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate rotates pages of a PDF file by the given angle using pdfcpu.
|
// Rotate rotates pages of a PDF file by the given angle using pdfcpu.
|
||||||
func (engine *PdfCpu) Rotate(ctx context.Context, logger *slog.Logger, inputPath string, angle int, pages string) error {
|
func (engine *PdfCpu) Rotate(ctx context.Context, logger *slog.Logger, inputPath string, angle int, pages string) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdfcpu.Rotate",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdfcpu")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
args := []string{"rotate"}
|
args := []string{"rotate"}
|
||||||
if pages != "" {
|
if pages != "" {
|
||||||
args = append(args, "-pages", pages)
|
args = append(args, "-pages", pages)
|
||||||
@@ -399,14 +588,21 @@ func (engine *PdfCpu) Rotate(ctx context.Context, logger *slog.Logger, inputPath
|
|||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rotate PDF with pdfcpu: %w", err)
|
err = fmt.Errorf("rotate PDF with pdfcpu: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -79,94 +83,201 @@ func (engine *PdfTk) Debug() map[string]any {
|
|||||||
|
|
||||||
// Split splits a given PDF file.
|
// Split splits a given PDF file.
|
||||||
func (engine *PdfTk) Split(ctx context.Context, logger *slog.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
func (engine *PdfTk) Split(ctx context.Context, logger *slog.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdftk.Split",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
var args []string
|
var args []string
|
||||||
outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath))
|
outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath))
|
||||||
|
|
||||||
switch mode.Mode {
|
switch mode.Mode {
|
||||||
case gotenberg.SplitModePages:
|
case gotenberg.SplitModePages:
|
||||||
if !mode.Unify {
|
if !mode.Unify {
|
||||||
return nil, fmt.Errorf("split PDFs using mode '%s' without unify with PDFtk: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
err := fmt.Errorf("split PDFs using mode '%s' without unify with PDFtk: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
args = append(args, inputPath, "cat", mode.Span, "output", outputPath)
|
args = append(args, inputPath, "cat", mode.Span, "output", outputPath)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("split PDFs using mode '%s' with PDFtk: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
err := fmt.Errorf("split PDFs using mode '%s' with PDFtk: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("split PDFs with PDFtk: %w", err)
|
err = fmt.Errorf("split PDFs with PDFtk: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return []string{outputPath}, nil
|
return []string{outputPath}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge combines multiple PDFs into a single PDF.
|
// Merge combines multiple PDFs into a single PDF.
|
||||||
func (engine *PdfTk) Merge(ctx context.Context, logger *slog.Logger, inputPaths []string, outputPath string) error {
|
func (engine *PdfTk) Merge(ctx context.Context, logger *slog.Logger, inputPaths []string, outputPath string) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdftk.Merge",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
args := make([]string, 0, 3+len(inputPaths))
|
args := make([]string, 0, 3+len(inputPaths))
|
||||||
args = append(args, inputPaths...)
|
args = append(args, inputPaths...)
|
||||||
args = append(args, "cat", "output", outputPath)
|
args = append(args, "cat", "output", outputPath)
|
||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("merge PDFs with PDFtk: %w", err)
|
err = fmt.Errorf("merge PDFs with PDFtk: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten is not available in this implementation.
|
// Flatten is not available in this implementation.
|
||||||
func (engine *PdfTk) Flatten(ctx context.Context, logger *slog.Logger, inputPath string) error {
|
func (engine *PdfTk) Flatten(ctx context.Context, logger *slog.Logger, inputPath string) error {
|
||||||
return fmt.Errorf("flatten PDF with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdftk.Flatten",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("flatten PDF with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert is not available in this implementation.
|
// Convert is not available in this implementation.
|
||||||
func (engine *PdfTk) Convert(ctx context.Context, logger *slog.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
func (engine *PdfTk) Convert(ctx context.Context, logger *slog.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return fmt.Errorf("convert PDF to '%+v' with PDFtk: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdftk.Convert",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("convert PDF to '%+v' with PDFtk: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadMetadata is not available in this implementation.
|
// ReadMetadata is not available in this implementation.
|
||||||
func (engine *PdfTk) ReadMetadata(ctx context.Context, logger *slog.Logger, inputPath string) (map[string]any, error) {
|
func (engine *PdfTk) ReadMetadata(ctx context.Context, logger *slog.Logger, inputPath string) (map[string]any, error) {
|
||||||
return nil, fmt.Errorf("read PDF metadata with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdftk.ReadMetadata",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("read PDF metadata with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMetadata is not available in this implementation.
|
// WriteMetadata is not available in this implementation.
|
||||||
func (engine *PdfTk) WriteMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]any, inputPath string) error {
|
func (engine *PdfTk) WriteMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]any, inputPath string) error {
|
||||||
return fmt.Errorf("write PDF metadata with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdftk.WriteMetadata",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("write PDF metadata with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PageCount is not available in this implementation.
|
// PageCount is not available in this implementation.
|
||||||
func (engine *PdfTk) PageCount(ctx context.Context, logger *slog.Logger, inputPath string) (int, error) {
|
func (engine *PdfTk) PageCount(ctx context.Context, logger *slog.Logger, inputPath string) (int, error) {
|
||||||
return 0, fmt.Errorf("page count with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdftk.PageCount",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("page count with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBookmarks is not available in this implementation.
|
// WriteBookmarks is not available in this implementation.
|
||||||
func (engine *PdfTk) WriteBookmarks(ctx context.Context, logger *slog.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error {
|
func (engine *PdfTk) WriteBookmarks(ctx context.Context, logger *slog.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error {
|
||||||
return fmt.Errorf("write PDF bookmarks with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdftk.WriteBookmarks",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("write PDF bookmarks with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadBookmarks is not available in this implementation.
|
// ReadBookmarks is not available in this implementation.
|
||||||
func (engine *PdfTk) ReadBookmarks(ctx context.Context, logger *slog.Logger, inputPath string) ([]gotenberg.Bookmark, error) {
|
func (engine *PdfTk) ReadBookmarks(ctx context.Context, logger *slog.Logger, inputPath string) ([]gotenberg.Bookmark, error) {
|
||||||
return nil, fmt.Errorf("read PDF bookmarks with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdftk.ReadBookmarks",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("read PDF bookmarks with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt adds password protection to a PDF file using PDFtk.
|
// Encrypt adds password protection to a PDF file using PDFtk.
|
||||||
func (engine *PdfTk) Encrypt(ctx context.Context, logger *slog.Logger, inputPath, userPassword, ownerPassword string) error {
|
func (engine *PdfTk) Encrypt(ctx context.Context, logger *slog.Logger, inputPath, userPassword, ownerPassword string) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdftk.Encrypt",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
if userPassword == "" {
|
if userPassword == "" {
|
||||||
return errors.New("user password cannot be empty")
|
err := errors.New("user password cannot be empty")
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ownerPassword == userPassword || ownerPassword == "" {
|
if ownerPassword == userPassword || ownerPassword == "" {
|
||||||
return gotenberg.NewPdfEngineInvalidArgs("pdftk", "both 'userPassword' and 'ownerPassword' must be provided and different. Consider switching to another PDF engine if this behavior does not work with your workflow")
|
err := gotenberg.NewPdfEngineInvalidArgs("pdftk", "both 'userPassword' and 'ownerPassword' must be provided and different. Consider switching to another PDF engine if this behavior does not work with your workflow")
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a temp output file in the same directory.
|
// Create a temp output file in the same directory.
|
||||||
@@ -181,32 +292,62 @@ func (engine *PdfTk) Encrypt(ctx context.Context, logger *slog.Logger, inputPath
|
|||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("encrypt PDF with PDFtk: %w", err)
|
err = fmt.Errorf("encrypt PDF with PDFtk: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Rename(tmpPath, inputPath)
|
err = os.Rename(tmpPath, inputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rename temporary output file with input file: %w", err)
|
err = fmt.Errorf("rename temporary output file with input file: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbedFiles is not available in this implementation.
|
// EmbedFiles is not available in this implementation.
|
||||||
func (engine *PdfTk) EmbedFiles(ctx context.Context, logger *slog.Logger, filePaths []string, inputPath string) error {
|
func (engine *PdfTk) EmbedFiles(ctx context.Context, logger *slog.Logger, filePaths []string, inputPath string) error {
|
||||||
return fmt.Errorf("embed files with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "pdftk.EmbedFiles",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("embed files with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watermark applies a watermark (behind page content) to a PDF file using PDFtk.
|
// Watermark applies a watermark (behind page content) to a PDF file using PDFtk.
|
||||||
// Only PDF source is supported.
|
// Only PDF source is supported.
|
||||||
|
//
|
||||||
|
//nolint:dupl
|
||||||
func (engine *PdfTk) Watermark(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
func (engine *PdfTk) Watermark(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdftk.Watermark",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
if stamp.Source != gotenberg.StampSourcePDF {
|
if stamp.Source != gotenberg.StampSourcePDF {
|
||||||
return fmt.Errorf("watermark PDF with PDFtk: %w", gotenberg.ErrPdfStampSourceNotSupported)
|
err := fmt.Errorf("watermark PDF with PDFtk: %w", gotenberg.ErrPdfStampSourceNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpPath := inputPath + ".tmp"
|
tmpPath := inputPath + ".tmp"
|
||||||
@@ -215,27 +356,48 @@ func (engine *PdfTk) Watermark(ctx context.Context, logger *slog.Logger, inputPa
|
|||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("watermark PDF with PDFtk: %w", err)
|
err = fmt.Errorf("watermark PDF with PDFtk: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Rename(tmpPath, inputPath)
|
err = os.Rename(tmpPath, inputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rename temporary output file with input file: %w", err)
|
err = fmt.Errorf("rename temporary output file with input file: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stamp applies a stamp (on top of page content) to a PDF file using PDFtk.
|
// Stamp applies a stamp (on top of page content) to a PDF file using PDFtk.
|
||||||
// Only PDF source is supported.
|
// Only PDF source is supported.
|
||||||
|
//
|
||||||
|
//nolint:dupl
|
||||||
func (engine *PdfTk) Stamp(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
func (engine *PdfTk) Stamp(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdftk.Stamp",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
if stamp.Source != gotenberg.StampSourcePDF {
|
if stamp.Source != gotenberg.StampSourcePDF {
|
||||||
return fmt.Errorf("stamp PDF with PDFtk: %w", gotenberg.ErrPdfStampSourceNotSupported)
|
err := fmt.Errorf("stamp PDF with PDFtk: %w", gotenberg.ErrPdfStampSourceNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpPath := inputPath + ".tmp"
|
tmpPath := inputPath + ".tmp"
|
||||||
@@ -244,19 +406,29 @@ func (engine *PdfTk) Stamp(ctx context.Context, logger *slog.Logger, inputPath s
|
|||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stamp PDF with PDFtk: %w", err)
|
err = fmt.Errorf("stamp PDF with PDFtk: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Rename(tmpPath, inputPath)
|
err = os.Rename(tmpPath, inputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rename temporary output file with input file: %w", err)
|
err = fmt.Errorf("rename temporary output file with input file: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,8 +436,17 @@ func (engine *PdfTk) Stamp(ctx context.Context, logger *slog.Logger, inputPath s
|
|||||||
// Page-specific rotation is not supported; if pages is non-empty,
|
// Page-specific rotation is not supported; if pages is non-empty,
|
||||||
// ErrPdfEngineMethodNotSupported is returned.
|
// ErrPdfEngineMethodNotSupported is returned.
|
||||||
func (engine *PdfTk) Rotate(ctx context.Context, logger *slog.Logger, inputPath string, angle int, pages string) error {
|
func (engine *PdfTk) Rotate(ctx context.Context, logger *slog.Logger, inputPath string, angle int, pages string) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "pdftk.Rotate",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("pdftk")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
if pages != "" {
|
if pages != "" {
|
||||||
return fmt.Errorf("rotate PDF with PDFtk (page-specific rotation): %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
err := fmt.Errorf("rotate PDF with PDFtk (page-specific rotation): %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var direction string
|
var direction string
|
||||||
@@ -277,7 +458,10 @@ func (engine *PdfTk) Rotate(ctx context.Context, logger *slog.Logger, inputPath
|
|||||||
case 270:
|
case 270:
|
||||||
direction = "west"
|
direction = "west"
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("rotate PDF with PDFtk: %w", gotenberg.ErrPdfRotateAngleNotSupported)
|
err := fmt.Errorf("rotate PDF with PDFtk: %w", gotenberg.ErrPdfRotateAngleNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpPath := inputPath + ".tmp"
|
tmpPath := inputPath + ".tmp"
|
||||||
@@ -285,19 +469,29 @@ func (engine *PdfTk) Rotate(ctx context.Context, logger *slog.Logger, inputPath
|
|||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rotate PDF with PDFtk: %w", err)
|
err = fmt.Errorf("rotate PDF with PDFtk: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Rename(tmpPath, inputPath)
|
err = os.Rename(tmpPath, inputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rename temporary output file with input file: %w", err)
|
err = fmt.Errorf("rename temporary output file with input file: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -82,37 +86,62 @@ func (engine *QPdf) Debug() map[string]any {
|
|||||||
|
|
||||||
// Split splits a given PDF file.
|
// Split splits a given PDF file.
|
||||||
func (engine *QPdf) Split(ctx context.Context, logger *slog.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
func (engine *QPdf) Split(ctx context.Context, logger *slog.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "qpdf.Split",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
var args []string
|
var args []string
|
||||||
outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath))
|
outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath))
|
||||||
|
|
||||||
switch mode.Mode {
|
switch mode.Mode {
|
||||||
case gotenberg.SplitModePages:
|
case gotenberg.SplitModePages:
|
||||||
if !mode.Unify {
|
if !mode.Unify {
|
||||||
return nil, fmt.Errorf("split PDFs using mode '%s' without unify with QPDF: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
err := fmt.Errorf("split PDFs using mode '%s' without unify with QPDF: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
args = append(args, inputPath)
|
args = append(args, inputPath)
|
||||||
args = append(args, engine.globalArgs...)
|
args = append(args, engine.globalArgs...)
|
||||||
args = append(args, "--pages", ".", mode.Span)
|
args = append(args, "--pages", ".", mode.Span)
|
||||||
args = append(args, "--", outputPath)
|
args = append(args, "--", outputPath)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("split PDFs using mode '%s' with QPDF: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
err := fmt.Errorf("split PDFs using mode '%s' with QPDF: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("split PDFs with QPDF: %w", err)
|
err = fmt.Errorf("split PDFs with QPDF: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return []string{outputPath}, nil
|
return []string{outputPath}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge combines multiple PDFs into a single PDF.
|
// Merge combines multiple PDFs into a single PDF.
|
||||||
func (engine *QPdf) Merge(ctx context.Context, logger *slog.Logger, inputPaths []string, outputPath string) error {
|
func (engine *QPdf) Merge(ctx context.Context, logger *slog.Logger, inputPaths []string, outputPath string) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "qpdf.Merge",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
args := make([]string, 0, 4+len(engine.globalArgs)+len(inputPaths))
|
args := make([]string, 0, 4+len(engine.globalArgs)+len(inputPaths))
|
||||||
args = append(args, "--empty")
|
args = append(args, "--empty")
|
||||||
args = append(args, engine.globalArgs...)
|
args = append(args, engine.globalArgs...)
|
||||||
@@ -122,20 +151,33 @@ func (engine *QPdf) Merge(ctx context.Context, logger *slog.Logger, inputPaths [
|
|||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("merge PDFs with QPDF: %w", err)
|
err = fmt.Errorf("merge PDFs with QPDF: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten merges annotation appearances with page content, deleting the
|
// Flatten merges annotation appearances with page content, deleting the
|
||||||
// original annotations.
|
// original annotations.
|
||||||
func (engine *QPdf) Flatten(ctx context.Context, logger *slog.Logger, inputPath string) error {
|
func (engine *QPdf) Flatten(ctx context.Context, logger *slog.Logger, inputPath string) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "qpdf.Flatten",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
args := make([]string, 0, 4+len(engine.globalArgs))
|
args := make([]string, 0, 4+len(engine.globalArgs))
|
||||||
args = append(args, inputPath)
|
args = append(args, inputPath)
|
||||||
args = append(args, "--generate-appearances")
|
args = append(args, "--generate-appearances")
|
||||||
@@ -145,51 +187,121 @@ func (engine *QPdf) Flatten(ctx context.Context, logger *slog.Logger, inputPath
|
|||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("flatten PDFs with QPDF: %w", err)
|
err = fmt.Errorf("flatten PDFs with QPDF: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert is not available in this implementation.
|
// Convert is not available in this implementation.
|
||||||
func (engine *QPdf) Convert(ctx context.Context, logger *slog.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
func (engine *QPdf) Convert(ctx context.Context, logger *slog.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return fmt.Errorf("convert PDF to '%+v' with QPDF: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "qpdf.Convert",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("convert PDF to '%+v' with QPDF: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadMetadata is not available in this implementation.
|
// ReadMetadata is not available in this implementation.
|
||||||
func (engine *QPdf) ReadMetadata(ctx context.Context, logger *slog.Logger, inputPath string) (map[string]any, error) {
|
func (engine *QPdf) ReadMetadata(ctx context.Context, logger *slog.Logger, inputPath string) (map[string]any, error) {
|
||||||
return nil, fmt.Errorf("read PDF metadata with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "qpdf.ReadMetadata",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("read PDF metadata with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMetadata is not available in this implementation.
|
// WriteMetadata is not available in this implementation.
|
||||||
func (engine *QPdf) WriteMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]any, inputPath string) error {
|
func (engine *QPdf) WriteMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]any, inputPath string) error {
|
||||||
return fmt.Errorf("write PDF metadata with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "qpdf.WriteMetadata",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("write PDF metadata with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PageCount is not available in this implementation.
|
// PageCount is not available in this implementation.
|
||||||
func (engine *QPdf) PageCount(ctx context.Context, logger *slog.Logger, inputPath string) (int, error) {
|
func (engine *QPdf) PageCount(ctx context.Context, logger *slog.Logger, inputPath string) (int, error) {
|
||||||
return 0, fmt.Errorf("page count with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "qpdf.PageCount",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("page count with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBookmarks is not available in this implementation.
|
// WriteBookmarks is not available in this implementation.
|
||||||
func (engine *QPdf) WriteBookmarks(ctx context.Context, logger *slog.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error {
|
func (engine *QPdf) WriteBookmarks(ctx context.Context, logger *slog.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error {
|
||||||
return fmt.Errorf("write PDF bookmarks with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "qpdf.WriteBookmarks",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("write PDF bookmarks with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadBookmarks is not available in this implementation.
|
// ReadBookmarks is not available in this implementation.
|
||||||
func (engine *QPdf) ReadBookmarks(ctx context.Context, logger *slog.Logger, inputPath string) ([]gotenberg.Bookmark, error) {
|
func (engine *QPdf) ReadBookmarks(ctx context.Context, logger *slog.Logger, inputPath string) ([]gotenberg.Bookmark, error) {
|
||||||
return nil, fmt.Errorf("read PDF bookmarks with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "qpdf.ReadBookmarks",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("read PDF bookmarks with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt adds password protection to a PDF file using QPDF.
|
// Encrypt adds password protection to a PDF file using QPDF.
|
||||||
func (engine *QPdf) Encrypt(ctx context.Context, logger *slog.Logger, inputPath, userPassword, ownerPassword string) error {
|
func (engine *QPdf) Encrypt(ctx context.Context, logger *slog.Logger, inputPath, userPassword, ownerPassword string) error {
|
||||||
|
ctx, span := gotenberg.Tracer().Start(ctx, "qpdf.Encrypt",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
if userPassword == "" {
|
if userPassword == "" {
|
||||||
return errors.New("user password cannot be empty")
|
err := errors.New("user password cannot be empty")
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ownerPassword == "" {
|
if ownerPassword == "" {
|
||||||
@@ -204,35 +316,78 @@ func (engine *QPdf) Encrypt(ctx context.Context, logger *slog.Logger, inputPath,
|
|||||||
|
|
||||||
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create command: %w", err)
|
err = fmt.Errorf("create command: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("encrypt PDF with QPDF: %w", err)
|
err = fmt.Errorf("encrypt PDF with QPDF: %w", err)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.SetStatus(codes.Ok, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbedFiles is not available in this implementation.
|
// EmbedFiles is not available in this implementation.
|
||||||
func (engine *QPdf) EmbedFiles(ctx context.Context, logger *slog.Logger, filePaths []string, inputPath string) error {
|
func (engine *QPdf) EmbedFiles(ctx context.Context, logger *slog.Logger, filePaths []string, inputPath string) error {
|
||||||
return fmt.Errorf("embed files with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "qpdf.EmbedFiles",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("embed files with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watermark is not available in this implementation.
|
// Watermark is not available in this implementation.
|
||||||
func (engine *QPdf) Watermark(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
func (engine *QPdf) Watermark(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||||
return fmt.Errorf("watermark PDF with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "qpdf.Watermark",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("watermark PDF with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stamp is not available in this implementation.
|
// Stamp is not available in this implementation.
|
||||||
func (engine *QPdf) Stamp(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
func (engine *QPdf) Stamp(ctx context.Context, logger *slog.Logger, inputPath string, stamp gotenberg.Stamp) error {
|
||||||
return fmt.Errorf("stamp PDF with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "qpdf.Stamp",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("stamp PDF with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate is not available in this implementation.
|
// Rotate is not available in this implementation.
|
||||||
func (engine *QPdf) Rotate(ctx context.Context, logger *slog.Logger, inputPath string, angle int, pages string) error {
|
func (engine *QPdf) Rotate(ctx context.Context, logger *slog.Logger, inputPath string, angle int, pages string) error {
|
||||||
return fmt.Errorf("rotate PDF with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
_, span := gotenberg.Tracer().Start(ctx, "qpdf.Rotate",
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress("qpdf")),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := fmt.Errorf("rotate PDF with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
span.RecordError(err)
|
||||||
|
span.SetStatus(codes.Error, err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
@@ -50,7 +51,10 @@ func (c client) send(ctx context.Context, body io.Reader, headers map[string]str
|
|||||||
}
|
}
|
||||||
|
|
||||||
tracer := gotenberg.Tracer()
|
tracer := gotenberg.Tracer()
|
||||||
ctx, span := tracer.Start(ctx, spanName, trace.WithSpanKind(trace.SpanKindClient))
|
ctx, span := tracer.Start(ctx, spanName,
|
||||||
|
trace.WithSpanKind(trace.SpanKindClient),
|
||||||
|
trace.WithAttributes(semconv.ServerAddress(url)),
|
||||||
|
)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
req, err := retryablehttp.NewRequest(method, url, body)
|
req, err := retryablehttp.NewRequest(method, url, body)
|
||||||
|
|||||||
Reference in New Issue
Block a user