Add C API to QPDFLogger

This commit is contained in:
Jay Berkenbilt 2022-06-18 13:38:36 -04:00
parent daef4e8fb8
commit 8130d50e3b
23 changed files with 592 additions and 15 deletions

View File

@ -1,5 +1,11 @@
2022-06-18 Jay Berkenbilt <ejb@ql.org>
* Add examples that show how to capture QPDFJob's output by
configuring the default logger (qpdfjob-save-attachment.cc,
qpdfjob-c-save-attachment.c). Fixes #691.
* Add C API for QPDFLogger -- see qpdflogger-c.h
* Add additional qpdfjob C API functions take a handle.
* Add qpdf_exit_code_e to Constants.h so that exit codes from

View File

@ -130,9 +130,9 @@ class QPDFLogger
void setError(std::shared_ptr<Pipeline>);
// See notes above about the save pipeline
QPDF_DLL
void setSave(std::shared_ptr<Pipeline>);
void setSave(std::shared_ptr<Pipeline>, bool only_if_not_set);
QPDF_DLL
void saveToStandardOutput();
void saveToStandardOutput(bool only_if_not_set);
// Shortcut for logic to reset output to new output/error streams.
// out_stream is used for info, err_stream is used for error, and

100
include/qpdf/qpdflogger-c.h Normal file
View File

@ -0,0 +1,100 @@
/* Copyright (c) 2005-2022 Jay Berkenbilt
*
* This file is part of qpdf.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Versions of qpdf prior to version 7 were released under the terms
* of version 2.0 of the Artistic License. At your option, you may
* continue to consider qpdf to be licensed under those terms. Please
* see the manual for additional information.
*/
#ifndef QPDFLOGGER_H
#define QPDFLOGGER_H
/*
* This file provides a C API for QPDFLogger. See QPDFLogger.hh for
* information about the logger.
*/
#include <qpdf/DLL.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* To operate on a logger, you need a handle to it. call
* qpdflogger_default_logger to get a handle for the default
* logger. The qpdf and qpdfjob functions may offer ways to get
* other logger handles. When you're done with the logger handler,
* call qpdflogger_cleanup. This does not destroy the underlying
* log object. It just cleans up the handle to it.
*/
typedef struct _qpdflogger_handle* qpdflogger_handle;
QPDF_DLL
qpdflogger_handle qpdflogger_default_logger();
QPDF_DLL
void qpdflogger_cleanup(qpdflogger_handle* l);
enum qpdf_log_dest_e {
qpdf_log_dest_default = 0,
qpdf_log_dest_stdout = 1,
qpdf_log_dest_stderr = 2,
qpdf_log_dest_discard = 3,
qpdf_log_dest_custom = 4,
};
typedef void (*qpdf_log_fn_t)(char const* data, size_t len, void* udata);
QPDF_DLL
void qpdflogger_set_info(
qpdflogger_handle l,
enum qpdf_log_dest_e dest,
qpdf_log_fn_t fn,
void* udata);
QPDF_DLL
void qpdflogger_set_warn(
qpdflogger_handle l,
enum qpdf_log_dest_e dest,
qpdf_log_fn_t fn,
void* udata);
QPDF_DLL
void qpdflogger_set_error(
qpdflogger_handle l,
enum qpdf_log_dest_e dest,
qpdf_log_fn_t fn,
void* udata);
/* A non-zero value for only_if_not_set means that the save
* pipeline will only be changed if it is not already set.
*/
QPDF_DLL
void qpdflogger_set_save(
qpdflogger_handle l,
enum qpdf_log_dest_e dest,
qpdf_log_fn_t fn,
void* udata,
int only_if_not_set);
QPDF_DLL
void qpdflogger_save_to_standard_output(
qpdflogger_handle l, int only_if_not_set);
#ifdef __cplusplus
}
#endif
#endif // QPDFLOGGER_H

View File

@ -110,7 +110,8 @@ set(libqpdf_SOURCES
SF_FlateLzwDecode.cc
SparseOHArray.cc
qpdf-c.cc
qpdfjob-c.cc)
qpdfjob-c.cc
qpdflogger-c.cc)
include(FindPkgConfig)
include(CheckTypeSize)

View File

@ -710,7 +710,7 @@ QPDFJob::checkConfiguration()
save_to_stdout = true;
}
if (save_to_stdout) {
this->m->log->saveToStandardOutput();
this->m->log->saveToStandardOutput(true);
}
if ((!m->split_pages) &&
QUtil::same_file(m->infilename.get(), m->outfilename.get())) {
@ -925,7 +925,7 @@ QPDFJob::doShowObj(QPDF& pdf)
} else {
// If anything has been written to standard output,
// this will fail.
this->m->log->saveToStandardOutput();
this->m->log->saveToStandardOutput(true);
obj.pipeStreamData(
this->m->log->getSave().get(),
(filter && m->normalize) ? qpdf_ef_normalize : 0,
@ -1031,7 +1031,7 @@ QPDFJob::doShowAttachment(QPDF& pdf)
auto efs = fs->getEmbeddedFileStream();
// saveToStandardOutput has already been called, but it's harmless
// to call it again, so do as defensive coding.
this->m->log->saveToStandardOutput();
this->m->log->saveToStandardOutput(true);
efs.pipeStreamData(this->m->log->getSave().get(), 0, qpdf_dl_all);
}
@ -3289,7 +3289,7 @@ QPDFJob::writeOutfile(QPDF& pdf)
} else {
// saveToStandardOutput has already been called, but
// calling it again is defensive and harmless.
this->m->log->saveToStandardOutput();
this->m->log->saveToStandardOutput(true);
w.setOutputPipeline(this->m->log->getSave().get());
}
setWriterOptions(pdf, w);

View File

@ -181,8 +181,11 @@ QPDFLogger::setError(std::shared_ptr<Pipeline> p)
}
void
QPDFLogger::setSave(std::shared_ptr<Pipeline> p)
QPDFLogger::setSave(std::shared_ptr<Pipeline> p, bool only_if_not_set)
{
if (only_if_not_set && (this->m->p_save != nullptr)) {
return;
}
if (this->m->p_save == p) {
return;
}
@ -202,9 +205,9 @@ QPDFLogger::setSave(std::shared_ptr<Pipeline> p)
}
void
QPDFLogger::saveToStandardOutput()
QPDFLogger::saveToStandardOutput(bool only_if_not_set)
{
setSave(standardOutput());
setSave(standardOutput(), only_if_not_set);
}
void

163
libqpdf/qpdflogger-c.cc Normal file
View File

@ -0,0 +1,163 @@
#include <qpdf/qpdflogger-c.h>
#include <qpdf/Pipeline.hh>
#include <qpdf/QIntC.hh>
#include <qpdf/QPDFLogger.hh>
#include <functional>
#include <memory>
struct _qpdflogger_handle
{
_qpdflogger_handle(std::shared_ptr<QPDFLogger> l);
~_qpdflogger_handle() = default;
std::shared_ptr<QPDFLogger> l;
};
namespace
{
class FunctionPipeline: public Pipeline
{
public:
FunctionPipeline(char const* identifier, qpdf_log_fn_t fn, void* udata);
virtual ~FunctionPipeline() = default;
virtual void write(unsigned char const* buf, size_t len) override;
virtual void finish() override;
private:
qpdf_log_fn_t fn;
void* udata;
};
}; // namespace
FunctionPipeline::FunctionPipeline(
char const* identifier, qpdf_log_fn_t fn, void* udata) :
Pipeline(identifier, nullptr),
fn(fn),
udata(udata)
{
}
void
FunctionPipeline::write(unsigned char const* buf, size_t len)
{
fn(reinterpret_cast<char const*>(buf), QIntC::to_ulong(len), udata);
}
void
FunctionPipeline::finish()
{
// Nothing needed
}
_qpdflogger_handle::_qpdflogger_handle(std::shared_ptr<QPDFLogger> l) :
l(l)
{
}
qpdflogger_handle
qpdflogger_default_logger()
{
return new _qpdflogger_handle(QPDFLogger::defaultLogger());
}
void
qpdflogger_cleanup(qpdflogger_handle* l)
{
delete *l;
*l = nullptr;
}
static void
set_log_dest(
QPDFLogger* l,
std::function<void(std::shared_ptr<Pipeline>)> method,
qpdf_log_dest_e dest,
char const* identifier,
qpdf_log_fn_t fn,
void* udata)
{
switch (dest) {
case qpdf_log_dest_default:
method(nullptr);
break;
case qpdf_log_dest_stdout:
method(l->standardOutput());
break;
case qpdf_log_dest_stderr:
method(l->standardError());
break;
case qpdf_log_dest_discard:
method(l->discard());
break;
case qpdf_log_dest_custom:
method(std::make_shared<FunctionPipeline>(identifier, fn, udata));
break;
}
}
static void
set_log_dest(
QPDFLogger* l,
void (QPDFLogger::*method)(std::shared_ptr<Pipeline>),
qpdf_log_dest_e dest,
char const* identifier,
qpdf_log_fn_t fn,
void* udata)
{
set_log_dest(
l,
std::bind(std::mem_fn(method), l, std::placeholders::_1),
dest,
identifier,
fn,
udata);
}
void
qpdflogger_set_info(
qpdflogger_handle l, qpdf_log_dest_e dest, qpdf_log_fn_t fn, void* udata)
{
set_log_dest(
l->l.get(), &QPDFLogger::setInfo, dest, "info logger", fn, udata);
}
void
qpdflogger_set_warn(
qpdflogger_handle l, qpdf_log_dest_e dest, qpdf_log_fn_t fn, void* udata)
{
set_log_dest(
l->l.get(), &QPDFLogger::setWarn, dest, "warn logger", fn, udata);
}
void
qpdflogger_set_error(
qpdflogger_handle l, qpdf_log_dest_e dest, qpdf_log_fn_t fn, void* udata)
{
set_log_dest(
l->l.get(), &QPDFLogger::setError, dest, "error logger", fn, udata);
}
void
qpdflogger_set_save(
qpdflogger_handle l,
qpdf_log_dest_e dest,
qpdf_log_fn_t fn,
void* udata,
int only_if_not_set)
{
auto method = std::bind(
std::mem_fn(&QPDFLogger::setSave),
l->l.get(),
std::placeholders::_1,
only_if_not_set);
set_log_dest(l->l.get(), method, dest, "save logger", fn, udata);
}
void
qpdflogger_save_to_standard_output(qpdflogger_handle l, int only_if_not_set)
{
qpdflogger_set_save(
l, qpdf_log_dest_stdout, nullptr, nullptr, only_if_not_set);
}

View File

@ -33,10 +33,18 @@ set(TEST_PROGRAMS
runlength
sha2
sparse_array)
set(TEST_C_PROGRAMS
logger_c)
foreach(PROG ${TEST_PROGRAMS})
add_executable(${PROG} ${PROG}.cc)
target_link_libraries(${PROG} libqpdf_object)
endforeach()
foreach(PROG ${TEST_C_PROGRAMS})
add_executable(${PROG} ${PROG}.c)
target_link_libraries(${PROG} libqpdf_object)
set_property(TARGET ${PROG} PROPERTY LINKER_LANGUAGE CXX)
endforeach()
# Since libtests link with the object library and don't use the DLL,
# we don't need to (and shouldn't) add the libqpdf target directory to

View File

@ -22,7 +22,7 @@ test1()
*(logger->getInfo()) << "getSave exception: " << e.what() << "\n";
}
try {
logger->saveToStandardOutput();
logger->saveToStandardOutput(true);
assert(false);
} catch (std::logic_error& e) {
*(logger->getInfo())
@ -40,12 +40,12 @@ test2()
// First call saveToStandardOutput. Then use info, which then to
// go stderr.
QPDFLogger l;
l.saveToStandardOutput();
l.saveToStandardOutput(true);
l.info(std::string("info to stderr\n"));
*(l.getSave()) << "save to stdout\n";
l.setInfo(nullptr);
l.info("info still to stderr\n");
l.setSave(nullptr);
l.setSave(nullptr, false);
l.setInfo(nullptr);
l.info("info back to stdout\n");
}

108
libtests/logger_c.c Normal file
View File

@ -0,0 +1,108 @@
#include <qpdf/assert_test.h>
#include <qpdf/qpdflogger-c.h>
#include <qpdf/Constants.h>
#include <qpdf/qpdfjob-c.h>
#include <stdio.h>
#include <stdlib.h>
static void
fn(char const* data, size_t len, void* udata)
{
FILE* f = (FILE*)udata;
fwrite(data, 1, len, f);
}
static void
do_run(char const* json, int exp_status)
{
int status = qpdfjob_run_from_json(json);
assert(status == exp_status);
}
static FILE*
do_fopen(char const* filename)
{
FILE* f = NULL;
#ifdef _MSC_VER
if (fopen_s(&f, filename, "wb") != 0) {
f = NULL;
}
#else
f = fopen(filename, "wb");
#endif
if (f == NULL) {
fprintf(stderr, "unable to open %s\n", filename);
exit(2);
}
return f;
}
int
main()
{
FILE* info = do_fopen("info");
FILE* warn = do_fopen("warn");
FILE* error = do_fopen("error");
FILE* save = do_fopen("save");
FILE* save2 = do_fopen("save2");
qpdflogger_handle l = qpdflogger_default_logger();
qpdflogger_set_info(l, qpdf_log_dest_custom, fn, (void*)info);
qpdflogger_set_warn(l, qpdf_log_dest_custom, fn, (void*)warn);
qpdflogger_set_error(l, qpdf_log_dest_custom, fn, (void*)error);
qpdflogger_set_save(l, qpdf_log_dest_custom, fn, (void*)save, 0);
do_run(
"{\"inputFile\": \"normal.pdf\", \"showNpages\": \"\"}",
qpdf_exit_success);
do_run(
"{\"inputFile\": \"warning.pdf\", \"showNpages\": \"\"}",
qpdf_exit_warning);
do_run(
"{\"inputFile\": \"missing.pdf\", \"showNpages\": \"\"}",
qpdf_exit_error);
do_run(
"{\"inputFile\": \"normal.pdf\","
" \"staticId\": \"\","
" \"outputFile\": \"-\"}",
qpdf_exit_success);
fclose(info);
fclose(warn);
fclose(error);
fclose(save);
qpdflogger_set_info(l, qpdf_log_dest_stderr, NULL, NULL);
qpdflogger_set_warn(l, qpdf_log_dest_stdout, NULL, NULL);
qpdflogger_set_error(l, qpdf_log_dest_default, NULL, NULL);
qpdflogger_set_save(l, qpdf_log_dest_custom, fn, (void*)save2, 0);
do_run(
"{\"inputFile\": \"2pages.pdf\", \"showNpages\": \"\"}",
qpdf_exit_success);
do_run(
"{\"inputFile\": \"warning.pdf\", \"showNpages\": \"\"}",
qpdf_exit_warning);
do_run(
"{\"inputFile\": \"missing.pdf\", \"showNpages\": \"\"}",
qpdf_exit_error);
do_run(
"{\"inputFile\": \"attach.pdf\","
" \"showAttachment\": \"a\"}",
qpdf_exit_success);
/* This won't change save since it's already set */
qpdflogger_save_to_standard_output(l, 1);
do_run(
"{\"inputFile\": \"attach.pdf\","
" \"showAttachment\": \"a\"}",
qpdf_exit_success);
qpdflogger_cleanup(&l);
return 0;
}

View File

@ -24,10 +24,22 @@ $td->runtest("check stderr",
{$td->FILE => "exp-stderr"},
$td->NORMALIZE_NEWLINES);
$td->runtest("logger C API",
{$td->COMMAND => "logger_c >stdout 2>stderr"},
{$td->STRING => "", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
foreach my $f (qw(stdout stderr info warn error save save2))
{
$td->runtest("check $f (C)",
{$td->FILE => "$f"},
{$td->FILE => "c-exp-$f"},
$td->NORMALIZE_NEWLINES);
}
cleanup();
$td->report(3);
$td->report(11);
sub cleanup
{
unlink "stdout", "stderr";
unlink "stdout", "stderr", "info", "warn", "error", "save", "save2";
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
qpdfjob json: open missing.pdf: No such file or directory

View File

@ -0,0 +1,2 @@
1
1

Binary file not shown.

View File

@ -0,0 +1,2 @@
quack
quack

View File

@ -0,0 +1,3 @@
2
1
qpdfjob json: open missing.pdf: No such file or directory

View File

@ -0,0 +1,4 @@
WARNING: warning.pdf: file is damaged
WARNING: warning.pdf (offset 1556): xref not found
WARNING: warning.pdf: Attempting to reconstruct cross-reference table
qpdfjob json: operation succeeded with warnings

View File

@ -0,0 +1,4 @@
WARNING: warning.pdf: file is damaged
WARNING: warning.pdf (offset 1556): xref not found
WARNING: warning.pdf: Attempting to reconstruct cross-reference table
qpdfjob json: operation succeeded with warnings

View File

@ -0,0 +1,79 @@
%PDF-1.3
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [
3 0 R
]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/ProcSet 5 0 R
/Font <<
/F1 6 0 R
>>
>>
>>
endobj
4 0 obj
<<
/Length 44
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
5 0 obj
[
/PDF
/Text
]
endobj
6 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
>>
endobj
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000063 00000 n
0000000135 00000 n
0000000307 00000 n
0000000403 00000 n
0000000438 00000 n
trailer <<
/Size 7
/Root 1 0 R
>>
startxref
556
%%EOF

View File

@ -0,0 +1,79 @@
%PDF-1.3
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [
3 0 R
]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/ProcSet 5 0 R
/Font <<
/F1 6 0 R
>>
>>
>>
endobj
4 0 obj
<<
/Length 44
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
5 0 obj
[
/PDF
/Text
]
endobj
6 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
>>
endobj
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000063 00000 n
0000000135 00000 n
0000000307 00000 n
0000000403 00000 n
0000000438 00000 n
trailer <<
/Size 7
/Root 1 0 R
>>
startxref
1556
%%EOF

View File

@ -158,6 +158,8 @@ For a detailed list of changes, please see the file
output and errors that slipped through the cracks with
``setOutputStreams``.
- A C API is available in :file:`include/qpdf/qpdflogger-c.h`.
- New methods ``insertItemAndGet``, ``appendItemAndGet``,
``eraseItemAndGet``, ``replaceKeyAndGet``, and
``removeKeyAndGet`` return the newly added or removed object.