Commit 83be2191b4f3eb8906160d61f61cae48532ee651

Authored by Jay Berkenbilt
1 parent 641e92c6

Use "save" logger when saving data to standard output

This includes the output PDF, streams from --show-object and
attachments from --save-attachment. This also enables --verbose and
--progress to work with saving to stdout.
ChangeLog
  1 +2022-06-18 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * When --progress or --verbose is combined with writing to
  4 + standard output, progress reporting and verbose messages go to
  5 + standard error. Previously it was disabled in this case.
  6 +
1 2022-06-05 Jay Berkenbilt <ejb@ql.org> 7 2022-06-05 Jay Berkenbilt <ejb@ql.org>
2 8
3 * QPDFJob: API breaking change: QPDFJob::doIfVerbose passes a 9 * QPDFJob: API breaking change: QPDFJob::doIfVerbose passes a
@@ -79,9 +79,6 @@ Find all places in the library that write to stdout/stderr/cout/cerr. @@ -79,9 +79,6 @@ Find all places in the library that write to stdout/stderr/cout/cerr.
79 Also find places that raise exceptions if unable to warn. These should 79 Also find places that raise exceptions if unable to warn. These should
80 use the global output writer. 80 use the global output writer.
81 81
82 -Figure out a good way to use the save pipeline for QPDFWriter as well  
83 -as saving attachments, showing stream data, etc.  
84 -  
85 82
86 QPDFPagesTree 83 QPDFPagesTree
87 ============= 84 =============
libqpdf/QPDFJob.cc
@@ -691,22 +691,21 @@ QPDFJob::checkConfiguration() @@ -691,22 +691,21 @@ QPDFJob::checkConfiguration()
691 " before the -- that follows --encrypt."); 691 " before the -- that follows --encrypt.");
692 } 692 }
693 693
  694 + bool save_to_stdout = false;
694 if (m->require_outfile && m->outfilename && 695 if (m->require_outfile && m->outfilename &&
695 (strcmp(m->outfilename.get(), "-") == 0)) { 696 (strcmp(m->outfilename.get(), "-") == 0)) {
696 if (m->split_pages) { 697 if (m->split_pages) {
697 usage("--split-pages may not be used when" 698 usage("--split-pages may not be used when"
698 " writing to standard output"); 699 " writing to standard output");
699 } 700 }
700 - if (this->m->verbose) {  
701 - usage("--verbose may not be used when"  
702 - " writing to standard output");  
703 - }  
704 - if (m->progress) {  
705 - usage("--progress may not be used when"  
706 - " writing to standard output");  
707 - } 701 + save_to_stdout = true;
  702 + }
  703 + if (!m->attachment_to_show.empty()) {
  704 + save_to_stdout = true;
  705 + }
  706 + if (save_to_stdout) {
  707 + this->m->log->saveToStandardOutput();
708 } 708 }
709 -  
710 if ((!m->split_pages) && 709 if ((!m->split_pages) &&
711 QUtil::same_file(m->infilename.get(), m->outfilename.get())) { 710 QUtil::same_file(m->infilename.get(), m->outfilename.get())) {
712 QTC::TC("qpdf", "QPDFJob same file error"); 711 QTC::TC("qpdf", "QPDFJob same file error");
@@ -918,10 +917,11 @@ QPDFJob::doShowObj(QPDF&amp; pdf) @@ -918,10 +917,11 @@ QPDFJob::doShowObj(QPDF&amp; pdf)
918 obj.warnIfPossible("unable to filter stream data"); 917 obj.warnIfPossible("unable to filter stream data");
919 error = true; 918 error = true;
920 } else { 919 } else {
921 - QUtil::binary_stdout();  
922 - Pl_StdioFile out("stdout", stdout); 920 + // If anything has been written to standard output,
  921 + // this will fail.
  922 + this->m->log->saveToStandardOutput();
923 obj.pipeStreamData( 923 obj.pipeStreamData(
924 - &out, 924 + this->m->log->getSave().get(),
925 (filter && m->normalize) ? qpdf_ef_normalize : 0, 925 (filter && m->normalize) ? qpdf_ef_normalize : 0,
926 filter ? qpdf_dl_all : qpdf_dl_none); 926 filter ? qpdf_dl_all : qpdf_dl_none);
927 } 927 }
@@ -1023,9 +1023,10 @@ QPDFJob::doShowAttachment(QPDF&amp; pdf) @@ -1023,9 +1023,10 @@ QPDFJob::doShowAttachment(QPDF&amp; pdf)
1023 "attachment " + m->attachment_to_show + " not found"); 1023 "attachment " + m->attachment_to_show + " not found");
1024 } 1024 }
1025 auto efs = fs->getEmbeddedFileStream(); 1025 auto efs = fs->getEmbeddedFileStream();
1026 - QUtil::binary_stdout();  
1027 - Pl_StdioFile out("stdout", stdout);  
1028 - efs.pipeStreamData(&out, 0, qpdf_dl_all); 1026 + // saveToStandardOutput has already been called, but it's harmless
  1027 + // to call it again, so do as defensive coding.
  1028 + this->m->log->saveToStandardOutput();
  1029 + efs.pipeStreamData(this->m->log->getSave().get(), 0, qpdf_dl_all);
1029 } 1030 }
1030 1031
1031 void 1032 void
@@ -3138,14 +3139,17 @@ QPDFJob::setWriterOptions(QPDF&amp; pdf, QPDFWriter&amp; w) @@ -3138,14 +3139,17 @@ QPDFJob::setWriterOptions(QPDF&amp; pdf, QPDFWriter&amp; w)
3138 parse_version(m->force_version, version, extension_level); 3139 parse_version(m->force_version, version, extension_level);
3139 w.forcePDFVersion(version, extension_level); 3140 w.forcePDFVersion(version, extension_level);
3140 } 3141 }
3141 - if (m->progress && m->outfilename) { 3142 + if (m->progress) {
  3143 + char const* outfilename = this->m->outfilename
  3144 + ? this->m->outfilename.get()
  3145 + : "standard output";
3142 w.registerProgressReporter( 3146 w.registerProgressReporter(
3143 std::shared_ptr<QPDFWriter::ProgressReporter>( 3147 std::shared_ptr<QPDFWriter::ProgressReporter>(
3144 // line-break 3148 // line-break
3145 new ProgressReporter( 3149 new ProgressReporter(
3146 *this->m->log->getInfo(), 3150 *this->m->log->getInfo(),
3147 this->m->message_prefix, 3151 this->m->message_prefix,
3148 - m->outfilename.get()))); 3152 + outfilename)));
3149 } 3153 }
3150 } 3154 }
3151 3155
@@ -3273,7 +3277,15 @@ QPDFJob::writeOutfile(QPDF&amp; pdf) @@ -3273,7 +3277,15 @@ QPDFJob::writeOutfile(QPDF&amp; pdf)
3273 } else { 3277 } else {
3274 // QPDFWriter must have block scope so the output file will be 3278 // QPDFWriter must have block scope so the output file will be
3275 // closed after write() finishes. 3279 // closed after write() finishes.
3276 - QPDFWriter w(pdf, m->outfilename.get()); 3280 + QPDFWriter w(pdf);
  3281 + if (this->m->outfilename) {
  3282 + w.setOutputFilename(m->outfilename.get());
  3283 + } else {
  3284 + // saveToStandardOutput has already been called, but
  3285 + // calling it again is defensive and harmless.
  3286 + this->m->log->saveToStandardOutput();
  3287 + w.setOutputPipeline(this->m->log->getSave().get());
  3288 + }
3277 setWriterOptions(pdf, w); 3289 setWriterOptions(pdf, w);
3278 w.write(); 3290 w.write();
3279 } 3291 }
libqpdf/QPDFLogger.cc
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 2
3 #include <qpdf/Pl_Discard.hh> 3 #include <qpdf/Pl_Discard.hh>
4 #include <qpdf/Pl_OStream.hh> 4 #include <qpdf/Pl_OStream.hh>
  5 +#include <qpdf/QUtil.hh>
5 #include <iostream> 6 #include <iostream>
6 #include <stdexcept> 7 #include <stdexcept>
7 8
@@ -182,6 +183,9 @@ QPDFLogger::setError(std::shared_ptr&lt;Pipeline&gt; p) @@ -182,6 +183,9 @@ QPDFLogger::setError(std::shared_ptr&lt;Pipeline&gt; p)
182 void 183 void
183 QPDFLogger::setSave(std::shared_ptr<Pipeline> p) 184 QPDFLogger::setSave(std::shared_ptr<Pipeline> p)
184 { 185 {
  186 + if (this->m->p_save == p) {
  187 + return;
  188 + }
185 if (p == this->m->p_stdout) { 189 if (p == this->m->p_stdout) {
186 auto pt = dynamic_cast<Pl_Track*>(p.get()); 190 auto pt = dynamic_cast<Pl_Track*>(p.get());
187 if (pt->getUsed()) { 191 if (pt->getUsed()) {
@@ -192,6 +196,7 @@ QPDFLogger::setSave(std::shared_ptr&lt;Pipeline&gt; p) @@ -192,6 +196,7 @@ QPDFLogger::setSave(std::shared_ptr&lt;Pipeline&gt; p)
192 if (this->m->p_info == this->m->p_stdout) { 196 if (this->m->p_info == this->m->p_stdout) {
193 this->m->p_info = this->m->p_stderr; 197 this->m->p_info = this->m->p_stderr;
194 } 198 }
  199 + QUtil::binary_stdout();
195 } 200 }
196 this->m->p_save = p; 201 this->m->p_save = p;
197 } 202 }
manual/release-notes.rst
@@ -133,6 +133,10 @@ For a detailed list of changes, please see the file @@ -133,6 +133,10 @@ For a detailed list of changes, please see the file
133 user password is not recoverable from the owner password when 133 user password is not recoverable from the owner password when
134 256-bit keys are in use. 134 256-bit keys are in use.
135 135
  136 + - ``--verbose`` and ``--progress`` may be now used when writing
  137 + the output PDF to standard output. In that case, the verbose and
  138 + progress messages are written to standard error.
  139 +
136 - Library Enhancements 140 - Library Enhancements
137 141
138 - New methods ``insertItemAndGet``, ``appendItemAndGet``, 142 - New methods ``insertItemAndGet``, ``appendItemAndGet``,
qpdf/qtest/progress-reporting.test
@@ -14,13 +14,23 @@ cleanup(); @@ -14,13 +14,23 @@ cleanup();
14 14
15 my $td = new TestDriver('progress-reporting'); 15 my $td = new TestDriver('progress-reporting');
16 16
17 -my $n_tests = 1; 17 +my $n_tests = 3;
18 18
19 $td->runtest("progress report on small file", 19 $td->runtest("progress report on small file",
20 - {$td->COMMAND => "qpdf --progress minimal.pdf a.pdf", 20 + {$td->COMMAND =>
  21 + "qpdf --progress --deterministic-id minimal.pdf a.pdf",
21 $td->FILTER => "perl filter-progress.pl"}, 22 $td->FILTER => "perl filter-progress.pl"},
22 {$td->FILE => "small-progress.out", $td->EXIT_STATUS => 0}, 23 {$td->FILE => "small-progress.out", $td->EXIT_STATUS => 0},
23 $td->NORMALIZE_NEWLINES); 24 $td->NORMALIZE_NEWLINES);
  25 +$td->runtest("progress report to stdout",
  26 + {$td->COMMAND =>
  27 + "qpdf --progress --deterministic-id minimal.pdf - > b.pdf",
  28 + $td->FILTER => "perl filter-progress.pl"},
  29 + {$td->FILE => "small-stdout-progress.out", $td->EXIT_STATUS => 0},
  30 + $td->NORMALIZE_NEWLINES);
  31 +$td->runtest("compare",
  32 + {$td->FILE => "a.pdf"},
  33 + {$td->FILE => "b.pdf"});
24 34
25 cleanup(); 35 cleanup();
26 $td->report($n_tests); 36 $td->report($n_tests);
qpdf/qtest/qpdf/small-stdout-progress.out 0 → 100644
  1 +qpdf: standard output: write progress: 0%
  2 +....other write progress....
  3 +qpdf: standard output: write progress: 100%