commit ebc2733a196e822efe475c9a132f4f434aab222d
Author: David Sorber <david.sorber@gmail.com>
Date:   Tue Sep 11 20:07:36 2018 -0400

    Adding non-verbose mode with "running" status information and progress
    bar. Also added terminate function in preparation of adding proper
    SIGINT handling.

diff --git a/software/photo_compress_archiver/PhotoCompressArchiver.cc b/software/photo_compress_archiver/PhotoCompressArchiver.cc
index 1c0a892..ead8caf 100644
--- a/software/photo_compress_archiver/PhotoCompressArchiver.cc
+++ b/software/photo_compress_archiver/PhotoCompressArchiver.cc
@@ -1,9 +1,11 @@
 #include <chrono>
+#include <cmath>
 #include <cstdio>
 #include <fstream>
 #include <iomanip>
 #include <iostream>
 #include <limits.h>
+#include <thread>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -12,21 +14,26 @@
 #include <packjpg.h>
 
 #include "PhotoCompressArchiver.hh"
+#include "ProgressBar.h"
 
 #define ERROR     std::cerr << BOLD << RED << "ERROR: " << ENDC 
 
 #define WRKR_OUT_REG(msg) {                                                   \
-    std::lock_guard<std::mutex> lock(m_output_mutex);                         \
-    std::cout << "T[" << std::setw(2) << tid << "] " << msg << std::endl;     \
+    if (m_verbose) {                                                          \
+        std::lock_guard<std::mutex> lock(m_output_mutex);                     \
+        std::cout << "T[" << std::setw(2) << tid << "] " << msg << std::endl; \
+    }                                                                         \
 }
 
 #define WRKR_OUT_ERR(msg) {                                                   \
-    std::lock_guard<std::mutex> lock(m_output_mutex);                         \
-    std::cerr << "T[" << std::setw(2) << tid << "] " << BOLD << RED           \
-              << "ERROR: " << ENDC << msg << std::endl;                       \
+    if (m_verbose) {                                                          \
+        std::lock_guard<std::mutex> lock(m_output_mutex);                     \
+        std::cerr << "T[" << std::setw(2) << tid << "] " << BOLD << RED       \
+                  << "ERROR: " << ENDC << msg << std::endl;                   \
+    }                                                                         \
 }
 
-// Helper functions
+// Helper function
 std::string _format_num_bytes(uint64_t num_bytes)
 {
     const uint64_t tera = (1024 * 1024 * 1024 * 1024L);
@@ -79,15 +86,19 @@ PhotoCompressArchiver::PhotoCompressArchiver(
     uint32_t num_threads,
     bool decompress,
     bool delete_orig,
+    bool verbose, 
     boost::regex& filter_regex)
     : m_path(path),
       m_num_threads(num_threads),
       m_decompress(decompress),
       m_delete_orig(delete_orig),
+      m_verbose(verbose),
+      m_terminate_flag(false),
       m_files_processed(0),
       m_num_input_files(0),
       m_total_uncompressed_size(0),
       m_total_compressed_size(0),
+      m_running_compressed_size(0),
       p_finder_thread(nullptr),
       m_filter_empty(true),
       m_filter_regex(filter_regex)
@@ -124,7 +135,6 @@ int PhotoCompressArchiver::execute()
     
     // Join the file finder thread
     p_finder_thread->join();
-    std::cout << "File finder completed:" << std::endl;
     
     // Error out of no input files were found
     if (m_num_input_files == 0)
@@ -134,14 +144,17 @@ int PhotoCompressArchiver::execute()
         return -1;
     }
     
-    std::cout << "  Found " << m_num_input_files << " files" << std::endl;
-    
+    std::cout << "File finder completed: " << m_num_input_files << " files";
     if (! m_decompress)
     {
-        std::cout << "  Total uncompressed bytes: " 
+        std::cout << "; " 
                   << _format_num_bytes(m_total_uncompressed_size) << "\n" 
                   << std::endl;
     }
+    else
+    {
+        std::cout << std::endl;
+    }
     
     // Add terminator for each worker thread
     for (uint32_t idx = 0; idx < m_num_threads; ++idx)
@@ -153,6 +166,58 @@ int PhotoCompressArchiver::execute()
     delete p_finder_thread;
     p_finder_thread = nullptr;
     
+    // Display status
+    std::cout << BOLD << "\n>>> Status:" << ENDC << std::endl;
+    std::string line(100, '-');
+    std::cout << line << std::endl;
+    if (! m_verbose)
+    {
+        // If not in verbose mode display running status
+        ProgressBar pbar = ProgressBar("Progress: ", PROGRESS_BAR_WIDTH);
+        double percent_done = 0.0;
+        while ((percent_done < 100.0) && (! m_terminate_flag))
+        {
+            // Grab current percent done
+            percent_done = get_global_percent_done();
+            
+            // Calculate current elapsed time
+            auto current = std::chrono::system_clock::now();
+            auto diff = current - start;
+            double duration = std::chrono::duration<double>(diff).count();
+            
+            double ratio = ((double)m_total_compressed_size / 
+                            m_running_compressed_size) * 100;
+                            
+            uint32_t num_errors = m_errors.size();
+            
+            std::cout << "Elapsed time: " << std::fixed 
+                      << std::setprecision(3) << duration << " s     "
+                      << "Files processed: " 
+                      << m_files_processed << " / " 
+                      << m_num_input_files << "     " 
+                      << "Errors: " << num_errors << "          "
+                      << std::endl;
+                      
+            pbar.update(std::lround(percent_done));
+            
+            std::cout << "Compressed/uncompressed size: " 
+                      << _format_num_bytes(m_total_compressed_size) << " / "
+                      << _format_num_bytes(m_running_compressed_size) 
+                      << "     Ratio: " << std::fixed 
+                      << std::setprecision(2) << ratio << "%               "
+                      << std::endl;
+                      
+            std::cout << line << std::endl;
+
+            // Delay slightly to prevent flickering 
+            if (percent_done < 100.0)
+            {
+                std::this_thread::sleep_for(std::chrono::milliseconds(100));
+                std::cout << UP_ONE << UP_ONE << UP_ONE << UP_ONE;
+            }
+        }
+    }
+    
     // Now join the worker threads
     for (auto worker : m_worker_threads)
     {
@@ -211,6 +276,12 @@ void PhotoCompressArchiver::find_files(
     const bfs::path& dir_path, 
     const boost::regex& file_regex)
 {
+    // Check the terminate flag before proceeding
+    if (m_terminate_flag)
+    {
+        return;
+    }
+    
     bfs::directory_iterator end_iter;
     for (bfs::directory_iterator dir_iter(dir_path); dir_iter != end_iter; ++dir_iter)
     {
@@ -265,6 +336,11 @@ void PhotoCompressArchiver::worker_thread_body(uint32_t tid)
         
         // Blocking wait for next work unit
         work_unit = m_file_list.pop_front();
+        if (m_terminate_flag)
+        {
+            break;
+        }
+    
         if (work_unit == nullptr)
         {
             break;
@@ -348,6 +424,7 @@ void PhotoCompressArchiver::worker_thread_body(uint32_t tid)
         //~ WRKR_OUT_REG("Output size:    " << out_size)
         
         // Increment counts
+        m_running_compressed_size += work_unit->m_file_size;
         m_total_compressed_size += out_size;
         ++m_files_processed;
         
@@ -404,5 +481,12 @@ void PhotoCompressArchiver::worker_thread_body(uint32_t tid)
 
 double PhotoCompressArchiver::get_global_percent_done()
 {
-    return ((double)m_files_processed / m_num_input_files) * 100;
+    return ((double)(m_files_processed + m_errors.size()) 
+            / m_num_input_files) * 100;
+}
+
+void PhotoCompressArchiver::terminate()
+{
+    m_terminate_flag = true;
+    m_file_list.terminate();
 }
diff --git a/software/photo_compress_archiver/PhotoCompressArchiver.hh b/software/photo_compress_archiver/PhotoCompressArchiver.hh
index 85fc236..5cc7b8a 100644
--- a/software/photo_compress_archiver/PhotoCompressArchiver.hh
+++ b/software/photo_compress_archiver/PhotoCompressArchiver.hh
@@ -23,6 +23,8 @@ const std::string ENDC("\033[0m");
 const std::string RED("\033[31m");
 const std::string YELLOW("\033[33m");
 const std::string PURPLE("\033[35m");
+const std::string UP_ONE = "\033[1A";
+const std::string DOWN_ONE = "\033[1B";
 
 const std::string version("v0.3.5");
 
@@ -32,6 +34,8 @@ const boost::regex PJG_REGEX("^.+\\.pjg$", boost::regex::icase);
 const std::string JPG_EXTENSION(".jpg");
 const std::string PJG_EXTENSION(".pjg");
 
+const uint32_t PROGRESS_BAR_WIDTH = 80;
+
 class WorkUnit
 {
     public:
@@ -56,6 +60,7 @@ class PhotoCompressArchiver
                 uint32_t num_threads,
                 bool decompress,
                 bool delete_orig,
+                bool verbose, 
                 boost::regex& filter_regex);
         
         ~PhotoCompressArchiver();
@@ -82,6 +87,11 @@ class PhotoCompressArchiver
          * m_files_processed and m_num_input_files instance variables.
          */ 
         double get_global_percent_done();
+        
+        /**
+         * Asynchronously terminate processing.
+         */ 
+        void terminate();
     
     private:
     
@@ -89,13 +99,17 @@ class PhotoCompressArchiver
         uint32_t m_num_threads;             // number of processing threads to use
         bool m_decompress;                  // decompress files instead of compress them
         bool m_delete_orig;                 // delete original files after de/compression
+        bool m_verbose ;                    // verbose processing output
         
         std::mutex m_output_mutex;          // output mutex
         
+        bool m_terminate_flag;              // terminate flag
+        
         std::atomic<uint32_t> m_files_processed;    // number of input files processed
         uint32_t m_num_input_files;                 // total number of input files found
         uint64_t m_total_uncompressed_size;         // total uncompressed size of all images
         uint64_t m_total_compressed_size;           // total compressed size of all images
+        uint64_t m_running_compressed_size;         // running compressed size (for status)
         
         // input file list (either images or compressed images)
         ProtectedAndSynchronizedQueue<WorkUnit*> m_file_list;
diff --git a/software/photo_compress_archiver/ProgressBar.h b/software/photo_compress_archiver/ProgressBar.h
new file mode 100644
index 0000000..41fc5d5
--- /dev/null
+++ b/software/photo_compress_archiver/ProgressBar.h
@@ -0,0 +1,80 @@
+
+#ifndef __PROGRESS_BAR_H__
+#define __PROGRESS_BAR_H__
+
+#include <cmath>
+#include <cstdint>
+#include <iostream>
+#include <string>
+
+class ProgressBar
+{
+public:
+    
+    ProgressBar(
+        const std::string& label, 
+        uint32_t bar_length, 
+        uint8_t progress=0)
+        : label(label),
+          bar_length(bar_length),
+          progress(progress)
+    { }
+    
+    ~ProgressBar() { }
+
+    void update(uint8_t new_progress)
+    {
+        // Progress is defined between 0 and 100 inclusive
+        if (this->progress >= 100)
+        {
+            return;
+        }
+        
+        this->progress = new_progress;
+        
+        std::cout << this->label;
+        std::cout << " [";
+        uint32_t pos = std::lround((static_cast<double>(this->progress) / 100) 
+                                   * this->bar_length);
+        for (uint32_t idx = 0; idx < this->bar_length; ++idx) 
+        {
+            if (idx < pos)
+            {
+                std::cout << "=";
+            }
+            else if ((idx == pos) && (idx == this->bar_length)) 
+            {
+                std::cout << "=";
+            }
+            else if (idx == pos) 
+            {
+                std::cout << ">";
+            }
+            else 
+            {
+                std::cout << " ";
+            }
+        }
+        std::cout << "] " << int(this->progress) << " %\r";
+        
+        // NOTE: normally newline would only be output when progress reaches
+        //       100% but here we redrawing an entire section of lines so 
+        //       we want a newline for each update() call 
+        std::cout << std::endl;
+    }
+    
+    ProgressBar& operator++()
+    {
+        this->update(this->progress + 1);
+        return *this;
+    }
+
+private:
+
+    std::string label;
+    uint32_t bar_length;
+    uint8_t progress;
+
+};
+
+#endif // __PROGRESS_BAR_H__
diff --git a/software/photo_compress_archiver/main.cc b/software/photo_compress_archiver/main.cc
index 8e348a2..4dfebb6 100644
--- a/software/photo_compress_archiver/main.cc
+++ b/software/photo_compress_archiver/main.cc
@@ -36,6 +36,7 @@ void usage(char** argv)
     std::cout << "  -h [ --help ]         display this help message\n";
     std::cout << "  -d [ --decompress ]   find '*.pjg' files and decompress them\n";
     std::cout << "  -k [ --delete-orig ]  delete original files\n";
+    std::cout << "  -v [ --verbose ]      verbose output while processing\n";
     std::cout << "  -t [ --num-threads ]  number of processing threads to use (max 2x system cores)\n";
     std::cout << "  -f [ --filter ]       regex with which to filter out input files\n";    
     std::cout << std::endl;
@@ -61,6 +62,8 @@ int main(int argc, char** argv)
             
             ("delete-orig,k", "delete orignal files")
             
+            ("verbose,v", "verbose output while processing")
+            
             ("num-threads,t", 
              bpo::value<uint32_t>(&num_threads)->default_value(num_cores), 
              "number of processing threads to use")
@@ -156,7 +159,16 @@ int main(int argc, char** argv)
     }
     std::cout << "  delete original files  => " << (delete_orig ? "ON" : "OFF") 
               << std::endl;
-              
+    
+    // Verbose output option
+    bool verbose = false;
+    if (general_opts_vm.count("verbose"))
+    {
+        verbose = true;
+    }
+    std::cout << "  verbose                => " << (verbose ? "ON" : "OFF") 
+              << std::endl;
+    
     // Number of processing threads option
     std::cout << "  num processing threads => " << num_threads << std::endl;
     
@@ -169,7 +181,7 @@ int main(int argc, char** argv)
     
     // Create the PCA object and let 'er rip!
     auto pca = new PhotoCompressArchiver(search_path, num_threads, decompress, 
-                                         delete_orig, filter_regex);
+                                         delete_orig, verbose, filter_regex);
     pca->execute();
     delete pca;
     
