From 72450e72e400eb03325ca182f0e118fde8cd1705 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 26 Jul 2017 13:44:24 +0200 Subject: Add support for printing progress --- libbutl/diagnostics.cxx | 93 +++++++++++++++++++++++++++++++++++++++++++++---- libbutl/diagnostics.hxx | 57 +++++++++++++++++++++++++----- libbutl/fdstream.hxx | 2 ++ 3 files changed, 136 insertions(+), 16 deletions(-) (limited to 'libbutl') diff --git a/libbutl/diagnostics.cxx b/libbutl/diagnostics.cxx index e73412b..05661ec 100644 --- a/libbutl/diagnostics.cxx +++ b/libbutl/diagnostics.cxx @@ -4,9 +4,23 @@ #include +#ifndef _WIN32 +# include // write() +#else +# include + +# include //_write() +#endif + +#include // ios::failure #include +#include +#include +#include // size_t #include // cerr +#include // stderr_fd() + using namespace std; namespace butl @@ -15,13 +29,82 @@ namespace butl static mutex diag_mutex; - diag_lock::diag_lock () + string diag_progress; + static string diag_progress_blank; // Being printed blanks out the line. + static size_t diag_progress_size; // Size of the last printed progress. + + // Print the progress string to STDERR. Ignore underlying OS errors (this is + // a progress bar after all, and throwing from dtors wouldn't be nice). Must + // be called with the diag_mutex being aquired. + // + // Note that the output will not interleave with that of independent writers, + // given that the printed strings are not longer than PIPE_BUF for POSIX + // (which is at least 512 bytes on all platforms). + // + // @@ Is there any atomicity guarantees on Windows? + // + static inline void + progress_print (string& s) { + // If the new progress string is shorter than the printed one, then we will + // complement it with the required number of spaces (to overwrite the + // trailing junk) prior to printing, and restore it afterwards. + // + size_t n (s.size ()); + + if (n < diag_progress_size) + s.append (diag_progress_size - n, ' '); + + if (!s.empty ()) + { + s += '\r'; // Position the cursor at the beginning of the line. + + try + { +#ifndef _WIN32 + write (stderr_fd(), s.c_str (), s.size ()); +#else + _write (stderr_fd(), s.c_str (), static_cast (s.size ())); +#endif + } + catch (const ios::failure&) {} + + s.resize (n); // Restore the progress string. + diag_progress_size = n; // Save the size of the printed progress string. + } + } + + diag_stream_lock::diag_stream_lock () + { + diag_mutex.lock (); + + // If diagnostics shares the output stream with the progress bar, then + // blank out the line prior to printing the diagnostics, if required. + // + if (diag_stream == &cerr && diag_progress_size != 0) + progress_print (diag_progress_blank); + } + + diag_stream_lock::~diag_stream_lock () + { + // If diagnostics shares output stream with the progress bar, then reprint + // the current progress string, that was overwritten with the diagnostics. + // + if (diag_stream == &cerr && !diag_progress.empty ()) + progress_print (diag_progress); + + diag_mutex.unlock (); + } + + diag_progress_lock::diag_progress_lock () + { + assert (diag_stream == &cerr); diag_mutex.lock (); } - diag_lock::~diag_lock () + diag_progress_lock::~diag_progress_lock () { + progress_print (diag_progress); diag_mutex.unlock (); } @@ -33,11 +116,7 @@ namespace butl if (epilogue_ == nullptr) { os.put ('\n'); - - { - diag_lock l; - *diag_stream << os.str (); - } + diag_stream_lock () << os.str (); // We can endup flushing the result of several writes. The last one may // possibly be incomplete, but that's not a problem as it will also be diff --git a/libbutl/diagnostics.hxx b/libbutl/diagnostics.hxx index c195ba0..9544945 100644 --- a/libbutl/diagnostics.hxx +++ b/libbutl/diagnostics.hxx @@ -23,8 +23,8 @@ namespace butl // Diagnostics destination stream (std::cerr by default). Note that its // modification is not MT-safe. Also note that concurrent writing to the // stream from multiple threads can result in interleaved characters. To - // prevent this an object of diag_lock type (see below) must be created prior - // to write operation. + // prevent this an object of diag_stream_lock type (see below) must be + // created prior to write operation. // LIBBUTL_SYMEXPORT extern std::ostream* diag_stream; @@ -32,12 +32,51 @@ namespace butl // An object of the type must be created prior to writing to diag_stream (see // above). // - struct LIBBUTL_SYMEXPORT diag_lock + struct LIBBUTL_SYMEXPORT diag_stream_lock { - diag_lock (); - ~diag_lock (); + diag_stream_lock (); + ~diag_stream_lock (); + + // Support for one-liners, for example: + // + // diag_stream_lock () << "Hello, World!" << endl; + // + template + std::ostream& + operator<< (const T& x) const + { + return *diag_stream << x; + } + }; + + // Progress line facility. + // + // The idea is to keep a progress line at the bottom of the terminal with + // other output scrolling above it. The printing of the progress line is + // integrated into diag_stream_lock and diag_progress_lock. To print or + // update the progress acquire diag_progress_lock and update the + // diag_progress string. To remove the progress line, set this string to + // empty. For better readability start the progress line with a space + // (which is where the cursor will be parked). Should only be used if + // diag_stream points to std::cerr. + // + // Note that child processes writing to the same stream may not completely + // overwrite the progress line so in this case it makes sense to keep the + // line as short as possible. + // + // To restore the progress line being overwritten by an independent writer + // (such as a child process), create and destroy the diag_progress_lock. + // + LIBBUTL_SYMEXPORT extern std::string diag_progress; + + struct LIBBUTL_SYMEXPORT diag_progress_lock + { + diag_progress_lock (); + ~diag_progress_lock (); }; + // + // struct diag_record; template struct diag_prologue; template struct diag_mark; @@ -47,11 +86,11 @@ namespace butl struct LIBBUTL_SYMEXPORT diag_record { template - friend const diag_record& - operator<< (const diag_record& r, const T& x) + const diag_record& + operator<< (const T& x) const { - r.os << x; - return r; + os << x; + return *this; } diag_record () diff --git a/libbutl/fdstream.hxx b/libbutl/fdstream.hxx index 6673fed..fa62ff4 100644 --- a/libbutl/fdstream.hxx +++ b/libbutl/fdstream.hxx @@ -596,6 +596,8 @@ namespace butl fdmode (int, fdstream_mode); // Portable functions for obtaining file descriptors of standard streams. + // Throw ios::failure on the underlying OS error. + // // Note that you normally wouldn't want to close them using fddup() to // convert them to auto_fd, for example: // -- cgit v1.1