From 3dbab589e32d3ed5bd98e0634255ba7dfab4c892 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Fri, 9 Sep 2022 08:03:21 -0400 Subject: [PATCH] Add C API functions for using custom loggers Expose functions to the C API to create new loggers and to setLogger and getLogger for QPDF and QPDFJob. --- ChangeLog | 5 +++++ TODO | 7 +------ include/qpdf/qpdf-c.h | 15 +++++++++++++++ include/qpdf/qpdfjob-c.h | 13 +++++++++++++ include/qpdf/qpdflogger-c.h | 24 +++++++++++++++++++---- libqpdf/qpdf-c.cc | 13 +++++++++++++ libqpdf/qpdf/qpdflogger-c_impl.hh | 11 +++++++++++ libqpdf/qpdfjob-c.cc | 13 +++++++++++++ libqpdf/qpdflogger-c.cc | 22 +++++++++++++-------- qpdf/qpdf-ctest.c | 23 ++++++++++++++++++++++ qpdf/qpdfjob-ctest.c | 32 +++++++++++++++++++++++++++++-- qpdf/qtest/qpdf/c-check-warn.out | 6 +++--- qpdf/qtest/qpdf/qpdfjob-ctest.out | 3 ++- 13 files changed, 163 insertions(+), 24 deletions(-) create mode 100644 libqpdf/qpdf/qpdflogger-c_impl.hh diff --git a/ChangeLog b/ChangeLog index b7e793a8..db589d34 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2022-09-09 Jay Berkenbilt + + * Expose ability to create custom loggers and to get and set the + logger for QPDF and QPDFJob through the C API. + 2022-09-08 Jay Berkenbilt * Added new functions to the C API to support qpdf JSON: diff --git a/TODO b/TODO index 5f3cd99a..49e70d27 100644 --- a/TODO +++ b/TODO @@ -8,12 +8,7 @@ Always Next ==== -Pending changes: - -* Consider also exposing a way to set a new logger and to get the - logger from QPDF and QPDFJob in the C API. - -Soon: Break ground on "Document-level work" +* Break ground on "Document-level work" Possible future JSON enhancements ================================= diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h index 434c4ede..8d1b0e2f 100644 --- a/include/qpdf/qpdf-c.h +++ b/include/qpdf/qpdf-c.h @@ -140,6 +140,7 @@ #include #include #include +#include #include #ifdef __cplusplus @@ -249,6 +250,20 @@ extern "C" { QPDF_DLL void qpdf_set_suppress_warnings(qpdf_data qpdf, QPDF_BOOL value); + /* LOG FUNCTIONS */ + + /* Set or get the current logger. You need to call + * qpdflogger_cleanup on the logger handles when you are done with + * the handles. The underlying logger is cleaned up automatically + * and persists if needed after the logger handle is destroyed. + * See comments in qpdflogger-c.h for details. + */ + + QPDF_DLL + void qpdf_set_logger(qpdf_data qpdf, qpdflogger_handle logger); + QPDF_DLL + qpdflogger_handle qpdf_get_logger(qpdf_data qpdf); + /* CHECK FUNCTIONS */ /* Attempt to read the entire PDF file to see if there are any diff --git a/include/qpdf/qpdfjob-c.h b/include/qpdf/qpdfjob-c.h index 961fece4..a1540d84 100644 --- a/include/qpdf/qpdfjob-c.h +++ b/include/qpdf/qpdfjob-c.h @@ -32,6 +32,7 @@ */ #include +#include #include #ifndef QPDF_NO_WCHAR_T # include @@ -92,6 +93,18 @@ extern "C" { QPDF_DLL void qpdfjob_cleanup(qpdfjob_handle* j); + /* Set or get the current logger. You need to call + * qpdflogger_cleanup on the logger handles when you are done with + * the handles. The underlying logger is cleaned up automatically + * and persists if needed after the logger handle is destroyed. + * See comments in qpdflogger-c.h for details. + */ + + QPDF_DLL + void qpdfjob_set_logger(qpdfjob_handle j, qpdflogger_handle logger); + QPDF_DLL + qpdflogger_handle qpdfjob_get_logger(qpdfjob_handle j); + /* This function wraps QPDFJob::initializeFromArgv. The return * value is the same as qpdfjob_run. If this returns an error, it * is invalid to call any other functions this job handle. diff --git a/include/qpdf/qpdflogger-c.h b/include/qpdf/qpdflogger-c.h index 6bf456e7..235efac1 100644 --- a/include/qpdf/qpdflogger-c.h +++ b/include/qpdf/qpdflogger-c.h @@ -38,16 +38,28 @@ extern "C" { /* 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. + * logger. There are functions in qpdf-c.h and qpdfjob-c.h that + * also take or return logger handles. When you're done with the + * logger handler, call qpdflogger_cleanup. This cleans up the + * handle but leaves the underlying log object intact. (It uses a + * shared pointer and will be cleaned up automatically when it is + * no longer in use.) That means you can create a logger with + * qpdflogger_create(), pass the logger handle to a function in + * qpdf-c.h or qpdfjob-c.h, and then clean it up, subject to + * constraints imposed by the other function. */ typedef struct _qpdflogger_handle* qpdflogger_handle; QPDF_DLL qpdflogger_handle qpdflogger_default_logger(); + /* Calling cleanup on the handle returned by qpdflogger_create + * destroys the handle but not the underlying logger. See comments + * above. + */ + QPDF_DLL + qpdflogger_handle qpdflogger_create(); + QPDF_DLL void qpdflogger_cleanup(qpdflogger_handle* l); @@ -95,6 +107,10 @@ extern "C" { void qpdflogger_save_to_standard_output( qpdflogger_handle l, int only_if_not_set); + /* For testing */ + QPDF_DLL + int qpdflogger_equal(qpdflogger_handle l1, qpdflogger_handle l2); + #ifdef __cplusplus } #endif diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc index a8ae8102..b183c8fb 100644 --- a/libqpdf/qpdf-c.cc +++ b/libqpdf/qpdf-c.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -279,6 +280,18 @@ qpdf_check_pdf(qpdf_data qpdf) return status; } +void +qpdf_set_logger(qpdf_data qpdf, qpdflogger_handle logger) +{ + qpdf->qpdf->setLogger(logger->l); +} + +qpdflogger_handle +qpdf_get_logger(qpdf_data qpdf) +{ + return new _qpdflogger_handle(qpdf->qpdf->getLogger()); +} + void qpdf_set_suppress_warnings(qpdf_data qpdf, QPDF_BOOL value) { diff --git a/libqpdf/qpdf/qpdflogger-c_impl.hh b/libqpdf/qpdf/qpdflogger-c_impl.hh new file mode 100644 index 00000000..d9787a2d --- /dev/null +++ b/libqpdf/qpdf/qpdflogger-c_impl.hh @@ -0,0 +1,11 @@ +#include + +#include + +struct _qpdflogger_handle +{ + _qpdflogger_handle(std::shared_ptr l); + ~_qpdflogger_handle() = default; + + std::shared_ptr l; +}; diff --git a/libqpdf/qpdfjob-c.cc b/libqpdf/qpdfjob-c.cc index 9367547c..889afec6 100644 --- a/libqpdf/qpdfjob-c.cc +++ b/libqpdf/qpdfjob-c.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,18 @@ wrap_qpdfjob(qpdfjob_handle j, std::function fn) return QPDFJob::EXIT_ERROR; } +void +qpdfjob_set_logger(qpdfjob_handle j, qpdflogger_handle logger) +{ + j->j.setLogger(logger->l); +} + +qpdflogger_handle +qpdfjob_get_logger(qpdfjob_handle j) +{ + return new _qpdflogger_handle(j->j.getLogger()); +} + int qpdfjob_initialize_from_argv(qpdfjob_handle j, char const* const argv[]) { diff --git a/libqpdf/qpdflogger-c.cc b/libqpdf/qpdflogger-c.cc index 290b2667..2a6d1052 100644 --- a/libqpdf/qpdflogger-c.cc +++ b/libqpdf/qpdflogger-c.cc @@ -1,5 +1,7 @@ #include +#include + #include #include #include @@ -7,14 +9,6 @@ #include #include -struct _qpdflogger_handle -{ - _qpdflogger_handle(std::shared_ptr l); - ~_qpdflogger_handle() = default; - - std::shared_ptr l; -}; - _qpdflogger_handle::_qpdflogger_handle(std::shared_ptr l) : l(l) { @@ -26,6 +20,12 @@ qpdflogger_default_logger() return new _qpdflogger_handle(QPDFLogger::defaultLogger()); } +qpdflogger_handle +qpdflogger_create() +{ + return new _qpdflogger_handle(QPDFLogger::create()); +} + void qpdflogger_cleanup(qpdflogger_handle* l) { @@ -125,3 +125,9 @@ 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); } + +int +qpdflogger_equal(qpdflogger_handle l1, qpdflogger_handle l2) +{ + return l1->l.get() == l2->l.get(); +} diff --git a/qpdf/qpdf-ctest.c b/qpdf/qpdf-ctest.c index b562ed8b..60986611 100644 --- a/qpdf/qpdf-ctest.c +++ b/qpdf/qpdf-ctest.c @@ -140,6 +140,15 @@ write_to_file(char const* data, size_t size, void* udata) return fwrite(data, 1, size, f) != size; } +static int +custom_log(char const* data, size_t size, void* udata) +{ + fprintf(stderr, "|custom|"); + fwrite(data, 1, size, stderr); + fflush(stderr); + return 0; +} + static void test01( char const* infile, @@ -583,6 +592,20 @@ test23( char const* outfile, char const* xarg) { + /* Test check and also exercise custom logger */ + qpdflogger_handle l1 = qpdf_get_logger(qpdf); + qpdflogger_handle l2 = qpdflogger_default_logger(); + assert(qpdflogger_equal(l1, l2)); + qpdflogger_cleanup(&l1); + qpdflogger_cleanup(&l2); + qpdflogger_handle l = qpdflogger_create(); + qpdflogger_set_warn(l, qpdf_log_dest_custom, custom_log, NULL); + qpdf_set_logger(qpdf, l); + qpdflogger_handle l3 = qpdf_get_logger(qpdf); + assert(qpdflogger_equal(l, l3)); + qpdflogger_cleanup(&l); + qpdflogger_cleanup(&l3); + QPDF_ERROR_CODE status = 0; qpdf_read(qpdf, infile, password); status = qpdf_check_pdf(qpdf); diff --git a/qpdf/qpdfjob-ctest.c b/qpdf/qpdfjob-ctest.c index f3272aef..708d5fa6 100644 --- a/qpdf/qpdfjob-ctest.c +++ b/qpdf/qpdfjob-ctest.c @@ -26,6 +26,15 @@ custom_progress(int progress, void* data) printf("%s: write progress: %d%%\n", (char const*)data, progress); } +static int +custom_log(char const* data, size_t size, void* udata) +{ + fprintf(stderr, "|custom|"); + fwrite(data, 1, size, stderr); + fflush(stderr); + return 0; +} + static void run_tests() { @@ -55,6 +64,7 @@ run_tests() \"objectStreams\": \"generate\"\n\ }") == 0); printf("json test passed\n"); + fflush(stdout); assert(qpdfjob_run_from_json("{\n\ \"inputFile\": \"xref-with-short-size.pdf\",\n\ @@ -64,10 +74,28 @@ run_tests() \"objectStreams\": \"generate\"\n\ }") == 3); printf("json warn test passed\n"); + fflush(stdout); - assert(qpdfjob_run_from_json("{\n\ + /* Also exercise custom logger */ + j = qpdfjob_init(); + qpdflogger_handle l1 = qpdfjob_get_logger(j); + qpdflogger_handle l2 = qpdflogger_default_logger(); + assert(qpdflogger_equal(l1, l2)); + qpdflogger_cleanup(&l1); + qpdflogger_cleanup(&l2); + qpdflogger_handle l = qpdflogger_create(); + qpdflogger_set_error(l, qpdf_log_dest_custom, custom_log, NULL); + qpdfjob_set_logger(j, l); + qpdflogger_handle l3 = qpdfjob_get_logger(j); + assert(qpdflogger_equal(l, l3)); + qpdflogger_cleanup(&l); + qpdflogger_cleanup(&l3); + + qpdfjob_initialize_from_json(j, "{\n\ \"inputFile\": \"nothing-there.pdf\"\n\ -}") == 2); +}"); + assert(qpdfjob_run(j) == 2); + qpdfjob_cleanup(&j); printf("json error test passed\n"); } diff --git a/qpdf/qtest/qpdf/c-check-warn.out b/qpdf/qtest/qpdf/c-check-warn.out index 65602762..c733f15d 100644 --- a/qpdf/qtest/qpdf/c-check-warn.out +++ b/qpdf/qtest/qpdf/c-check-warn.out @@ -1,6 +1,6 @@ -WARNING: c-check-warn-in.pdf: file is damaged -WARNING: c-check-warn-in.pdf (offset 1556): xref not found -WARNING: c-check-warn-in.pdf: Attempting to reconstruct cross-reference table +|custom|WARNING: |custom|c-check-warn-in.pdf: file is damaged|custom| +|custom|WARNING: |custom|c-check-warn-in.pdf (offset 1556): xref not found|custom| +|custom|WARNING: |custom|c-check-warn-in.pdf: Attempting to reconstruct cross-reference table|custom| status: 1 warning: c-check-warn-in.pdf: file is damaged code: 5 diff --git a/qpdf/qtest/qpdf/qpdfjob-ctest.out b/qpdf/qtest/qpdf/qpdfjob-ctest.out index 22d431de..1778e70a 100644 --- a/qpdf/qtest/qpdf/qpdfjob-ctest.out +++ b/qpdf/qtest/qpdf/qpdfjob-ctest.out @@ -6,5 +6,6 @@ json test passed WARNING: xref-with-short-size.pdf (xref stream, offset 16227): Cross-reference stream data has the wrong size; expected = 52; actual = 56 qpdfjob json: operation succeeded with warnings; resulting file may have some problems json warn test passed -qpdfjob json: an output file name is required; use - for standard output +|custom|qpdfjob json|custom|: |custom|an output file name is required; use - for standard output|custom| +|custom|qpdfjob json|custom|: |custom|an output file name is required; use - for standard output|custom| json error test passed