mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-16 04:12:16 +01:00
feat(ExifTool): add capability to overwrite metadata of the PDF generated (#776)
* add new metadata functions to pdf engine interface * add exiftool module with relevant test cases * use exiftool to overwrite metadata in libreoffice * use exiftool to overwrite metadata in chromium * fix linter issues * fix more linter issues * more test cases for better coverage * remove utils * minor changes * remove metadata from pdfoptions * correct indentation * read/write metadata one file at a time.
This commit is contained in:
committed by
Julien Neuhart
parent
31e7582216
commit
71911cb1a7
@@ -70,6 +70,52 @@ func (multi *multiPdfEngines) Convert(ctx context.Context, logger *zap.Logger, f
|
||||
return fmt.Errorf("convert PDF to '%+v' with multi PDF engines: %w", formats, err)
|
||||
}
|
||||
|
||||
func (multi *multiPdfEngines) ReadMetadata(ctx context.Context, logger *zap.Logger, inputPaths string, metadata map[string]interface{}) error {
|
||||
var err error
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
for _, engine := range multi.engines {
|
||||
go func(engine gotenberg.PdfEngine) {
|
||||
errChan <- engine.ReadMetadata(ctx, logger, inputPaths, metadata)
|
||||
}(engine)
|
||||
|
||||
select {
|
||||
case readMetadataErr := <-errChan:
|
||||
errored := multierr.AppendInto(&err, readMetadataErr)
|
||||
if !errored {
|
||||
return nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("read PDF metadata with multi PDF engines: %w", err)
|
||||
}
|
||||
|
||||
func (multi *multiPdfEngines) WriteMetadata(ctx context.Context, logger *zap.Logger, inputPaths string, newMetadata map[string]interface{}) error {
|
||||
var err error
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
for _, engine := range multi.engines {
|
||||
go func(engine gotenberg.PdfEngine) {
|
||||
errChan <- engine.WriteMetadata(ctx, logger, inputPaths, newMetadata)
|
||||
}(engine)
|
||||
|
||||
select {
|
||||
case writeMetadataErr := <-errChan:
|
||||
errored := multierr.AppendInto(&err, writeMetadataErr)
|
||||
if !errored {
|
||||
return nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("write PDF metadata with multi PDF engines: %w", err)
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
var (
|
||||
_ gotenberg.PdfEngine = (*multiPdfEngines)(nil)
|
||||
|
||||
@@ -177,3 +177,169 @@ func TestMultiPdfEngines_Convert(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiPdfEngines_ReadMetadata(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
engine *multiPdfEngines
|
||||
ctx context.Context
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "nominal behavior",
|
||||
engine: newMultiPdfEngines(
|
||||
&gotenberg.PdfEngineMock{
|
||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, metadata map[string]interface{}) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
ctx: context.Background(),
|
||||
},
|
||||
{
|
||||
scenario: "at least one engine does not return an error",
|
||||
engine: newMultiPdfEngines(
|
||||
&gotenberg.PdfEngineMock{
|
||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, metadata map[string]interface{}) error {
|
||||
return errors.New("foo")
|
||||
},
|
||||
},
|
||||
&gotenberg.PdfEngineMock{
|
||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, metadata map[string]interface{}) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
ctx: context.Background(),
|
||||
},
|
||||
{
|
||||
scenario: "all engines return an error",
|
||||
engine: newMultiPdfEngines(
|
||||
&gotenberg.PdfEngineMock{
|
||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, metadata map[string]interface{}) error {
|
||||
return errors.New("foo")
|
||||
},
|
||||
},
|
||||
&gotenberg.PdfEngineMock{
|
||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, metadata map[string]interface{}) error {
|
||||
return errors.New("foo")
|
||||
},
|
||||
},
|
||||
),
|
||||
ctx: context.Background(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "context expired",
|
||||
engine: newMultiPdfEngines(
|
||||
&gotenberg.PdfEngineMock{
|
||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, metadata map[string]interface{}) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
ctx: func() context.Context {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
return ctx
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
err := tc.engine.ReadMetadata(tc.ctx, zap.NewNop(), "", nil)
|
||||
|
||||
if !tc.expectError && err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectError && err == nil {
|
||||
t.Fatal("expected error but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiPdfEngines_WriteMetadata(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
engine *multiPdfEngines
|
||||
ctx context.Context
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "nominal behavior",
|
||||
engine: newMultiPdfEngines(
|
||||
&gotenberg.PdfEngineMock{
|
||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, newMetadata map[string]interface{}) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
ctx: context.Background(),
|
||||
},
|
||||
{
|
||||
scenario: "at least one engine does not return an error",
|
||||
engine: newMultiPdfEngines(
|
||||
&gotenberg.PdfEngineMock{
|
||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, newMetadata map[string]interface{}) error {
|
||||
return errors.New("foo")
|
||||
},
|
||||
},
|
||||
&gotenberg.PdfEngineMock{
|
||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, newMetadata map[string]interface{}) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
ctx: context.Background(),
|
||||
},
|
||||
{
|
||||
scenario: "all engines return an error",
|
||||
engine: newMultiPdfEngines(
|
||||
&gotenberg.PdfEngineMock{
|
||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, newMetadata map[string]interface{}) error {
|
||||
return errors.New("foo")
|
||||
},
|
||||
},
|
||||
&gotenberg.PdfEngineMock{
|
||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, newMetadata map[string]interface{}) error {
|
||||
return errors.New("foo")
|
||||
},
|
||||
},
|
||||
),
|
||||
ctx: context.Background(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "context expired",
|
||||
engine: newMultiPdfEngines(
|
||||
&gotenberg.PdfEngineMock{
|
||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string, newMetadata map[string]interface{}) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
ctx: func() context.Context {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
return ctx
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
err := tc.engine.WriteMetadata(tc.ctx, zap.NewNop(), "", nil)
|
||||
|
||||
if !tc.expectError && err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectError && err == nil {
|
||||
t.Fatal("expected error but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user