commit 328d0fa5f3489c7096d725991ab2c53f6814e3fc
Author: David Sorber <dsorber@prometheus.fios-router.home>
Date:   Sat Feb 11 17:58:26 2017 -0500

    Adding proper option parsing and some general improvements. I've added an option for decompression but I still need to implement it.

diff --git a/software/photo_compress_archiver/Makefile b/software/photo_compress_archiver/Makefile
index 17be8f8..1dfc80a 100644
--- a/software/photo_compress_archiver/Makefile
+++ b/software/photo_compress_archiver/Makefile
@@ -3,7 +3,7 @@ CXX=clang++
 CXXFLAGS=-std=c++11 -g
 OBJECTS = main.o PhotoCompressArchiver.o
 
-LDFLAGS=-L/opt/local/lib -lboost_system-mt -lboost_regex-mt -lboost_filesystem-mt
+LDFLAGS=-L/opt/local/lib -lboost_system-mt -lboost_filesystem-mt -lboost_program_options-mt -lboost_regex-mt
 
 all: main
 	
diff --git a/software/photo_compress_archiver/PhotoCompressArchiver.cc b/software/photo_compress_archiver/PhotoCompressArchiver.cc
index 4cf44aa..318f13d 100644
--- a/software/photo_compress_archiver/PhotoCompressArchiver.cc
+++ b/software/photo_compress_archiver/PhotoCompressArchiver.cc
@@ -1,4 +1,5 @@
 #include <chrono>
+#include <cstdio>
 #include <iomanip>
 #include <iostream>
 #include <sys/stat.h>
@@ -18,10 +19,14 @@ const char *PROG_OPT2 = "-o";
 const char *PROG_OPT3 = "-p";
 
 PhotoCompressArchiver::PhotoCompressArchiver(
-    const char* path, 
-    uint32_t num_cores)
+    const std::string& path, 
+    uint32_t num_cores,
+    bool decompress,
+    bool keep_orig)
     : m_path(path),
       m_num_cores(num_cores),
+      m_decompress(decompress),
+      m_keep_orig(keep_orig),
       m_files_processed(0),
       m_num_input_files(0),
       m_total_uncompressed_size(0),
@@ -37,19 +42,24 @@ int PhotoCompressArchiver::execute()
     // Start the file finder thread
     OUT(std::cout << "Starting file finder..." << std::flush;);
     p_finder_thread = new std::thread(&PhotoCompressArchiver::find_files, this, 
-                                    m_path, JPEG_REGEX);
+                                    m_path, 
+                                    (m_decompress ? PJG_REGEX : JPEG_REGEX));
     OUT(std::cout << "DONE" << std::endl;)
     p_finder_thread->join();
     OUT(std::cout << "File finder completed:" << std::endl;)
     
     OUT(std::cout << "  Found " << m_num_input_files << " images" << std::endl;)
-    OUT(std::cout << "  Total uncompressed bytes: " << m_total_uncompressed_size 
-                  << "\n" << std::endl;)
+    
+    if (! m_decompress)
+    {
+        OUT(std::cout << "  Total uncompressed bytes: " 
+                      << m_total_uncompressed_size << "\n" << std::endl;)
+    }
     
     // Start worker threads
-    OUT(std::cout << "Starting worker threads..." << std::endl;)
+    OUT(std::cout << "Starting worker threads..." << std::flush;)
     
-    // TODO: need to handle the cause were number of input files is less than
+    // TODO: need to handle the case were number of input files is less than
     //       the number of threads
     uint32_t num_threads = m_num_cores;
     uint32_t sublist_len = m_num_input_files / num_threads;
@@ -81,7 +91,7 @@ int PhotoCompressArchiver::execute()
                                               this, idx, sublist);
         m_worker_threads.push_back(worker);        
     }
-    OUT(std::cout << "All worker threads started" << std::endl;)
+    OUT(std::cout << "DONE\n" << std::endl;)
     
     // Clean up the finder thread
     delete p_finder_thread;
@@ -94,13 +104,18 @@ int PhotoCompressArchiver::execute()
         delete worker;
     }
     
-    OUT(std::cout << "\n  Total compressed bytes: " << m_total_compressed_size 
-                  << std::endl;)
+    if (! m_decompress)
+    {
+        OUT(std::cout << "\n  Total compressed bytes: " 
+                      << m_total_compressed_size 
+                      << std::endl;)
         
-    // Calculate and display ratio
-    double ratio = (double)m_total_compressed_size / m_total_uncompressed_size;
-    OUT(std::cout << "  Compression ratio: " << std::fixed 
-                  << std::setprecision(4) << (ratio * 100) << "%\n" << std::endl;)
+        // Calculate and display ratio
+        double ratio = (double)m_total_compressed_size / m_total_uncompressed_size;
+        OUT(std::cout << "  Compression ratio: " << std::fixed 
+                      << std::setprecision(4) << (ratio * 100) << "%\n" 
+                      << std::endl;)
+    }
     
     return 0;
 }
@@ -128,8 +143,9 @@ void PhotoCompressArchiver::find_files(
             int rc = stat(dir_iter->path().string().c_str(), &statbuf);
             if (rc)
             {
-                OUT(std::cerr << "ERROR: unable to stat file " 
-                              << dir_iter->path() << std::endl;)
+                OUT(std::cerr << BOLD << RED << "ERROR: " << ENDC 
+                              << "unable to stat file " << dir_iter->path() 
+                              << std::endl;)
             }     
             m_total_uncompressed_size += statbuf.st_size;
         }
@@ -143,11 +159,11 @@ void PhotoCompressArchiver::fork_worker(
     uint32_t sublist_file_count = file_sublist->size();
     uint32_t files_processed = 0;
     
+#if 0
+    // Print out the input files given to this worker
     OUT(std::cout << "T[" << tid << "] top of the morning to ya: " 
                   << sublist_file_count << std::endl;)
-    
-    // Print out the input files given to this worker
-#if 0
+
     for (auto& filepath : *file_sublist)
     {
         OUT(std::cout << "T[" << tid << "] file: " << filepath.string() 
@@ -252,7 +268,8 @@ void PhotoCompressArchiver::fork_worker(
         stat_rc = stat(output_filenames[output_idx - 1]->c_str(), &statbuf);
         if (stat_rc)
         {
-            OUT(std::cerr << "ERROR while stating: " 
+            OUT(std::cerr << BOLD << RED << "ERROR " << ENDC 
+                          << "while stating: " 
                           << *output_filenames[output_idx - 1] << std::endl;)
         }
         
@@ -261,6 +278,12 @@ void PhotoCompressArchiver::fork_worker(
         ++m_files_processed;
         ++files_processed;
         
+        // Unless keeping original files, remove the source file
+        if (! m_keep_orig)
+        {
+            std::remove(file_sublist->at(output_idx - 1).string().c_str());
+        }
+        
         // Calculate the local percent done
         complete_percent = (double)files_processed / sublist_file_count;
         complete_percent *= 100;
@@ -286,7 +309,7 @@ void PhotoCompressArchiver::fork_worker(
     stat_rc = stat(output_filenames[output_idx - 1]->c_str(), &statbuf);
     if (stat_rc)
     {
-        OUT(std::cerr << "ERROR while stating: " 
+        OUT(std::cerr << BOLD << RED << "ERROR " << ENDC << "while stating: " 
                       << *output_filenames[output_idx - 1] << std::endl;)
     }
     
@@ -295,6 +318,12 @@ void PhotoCompressArchiver::fork_worker(
     ++m_files_processed;
     ++files_processed;
     
+    // Unless keeping original files, remove the source file
+    if (! m_keep_orig)
+    {
+        std::remove(file_sublist->at(output_idx - 1).string().c_str());
+    }
+    
     // Calculate the local percent done
     complete_percent = (double)files_processed / sublist_file_count;
     complete_percent *= 100;
@@ -306,7 +335,8 @@ void PhotoCompressArchiver::fork_worker(
                   << *output_filenames[output_idx - 1] << " -- " 
                   << statbuf.st_size << std::endl;)
     
-    OUT(std::cout << "T[" << tid << "] complete ==> RC: " << status << std::endl;)
+    OUT(std::cout << "T[" << tid << "] "<< PURPLE << "complete ==> RC: " 
+                  << status << ENDC << std::endl;)
     
     // Free up the argv array and output filename list
     delete[] exec_argv;
diff --git a/software/photo_compress_archiver/PhotoCompressArchiver.hh b/software/photo_compress_archiver/PhotoCompressArchiver.hh
index ccf2949..076a3cb 100644
--- a/software/photo_compress_archiver/PhotoCompressArchiver.hh
+++ b/software/photo_compress_archiver/PhotoCompressArchiver.hh
@@ -15,13 +15,26 @@
 // Alias the boost fs namespace to keep the code readable
 namespace bfs = boost::filesystem;
 
+const std::string BOLD("\033[1m");
+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 version("v0.1.0");
+
 const boost::regex JPEG_REGEX("^.+\\.(jpg)|(jpeg)$", boost::regex::icase);
+const boost::regex PJG_REGEX("^.+\\.pjg$", boost::regex::icase);
 
 class PhotoCompressArchiver
 {
     public:
     
-        PhotoCompressArchiver(const char* path, uint32_t num_cores);
+        PhotoCompressArchiver(
+                const std::string& path, 
+                uint32_t num_cores,
+                bool decompress,
+                bool keep_orig);
         
         ~PhotoCompressArchiver();
         
@@ -52,6 +65,9 @@ class PhotoCompressArchiver
     
         const bfs::path m_path;             // top level path where to look for input files
         uint32_t m_num_cores;               // number of cores on the machine
+        bool m_decompress;                  // decompress files instead of compress them
+        bool m_keep_orig;                   // keep; don't delete original files
+        
         std::mutex m_output_mutex;          // output mutex
         
         std::atomic<uint32_t> m_files_processed;        // number of input files processed
diff --git a/software/photo_compress_archiver/PhotoCompressArchiver.o b/software/photo_compress_archiver/PhotoCompressArchiver.o
new file mode 100644
index 0000000..259b86f
Binary files /dev/null and b/software/photo_compress_archiver/PhotoCompressArchiver.o differ
diff --git a/software/photo_compress_archiver/main.cc b/software/photo_compress_archiver/main.cc
index d1b5849..d993407 100644
--- a/software/photo_compress_archiver/main.cc
+++ b/software/photo_compress_archiver/main.cc
@@ -2,38 +2,120 @@
 #include <string>
 
 #include <boost/filesystem.hpp>
+#include <boost/program_options.hpp>
 #include <boost/regex.hpp>
 
 #include "PhotoCompressArchiver.hh"
 
-// clang++ -std=c++11 -L. -l packjpg -I/opt/local/include -L/opt/local/lib 
-//  -lboost_system-mt -lboost_regex-mt -lboost_filesystem-mt -o pca main.cc
-int main(int argc, char** argv)
+// Alias the namespace for readability
+namespace bpo = boost::program_options;
+
+void header()
 {
-    std::cout << "Photo Compress Archiver -- v0.1.0" << std::endl;
+    std::cout << BOLD << "Photo Compress Archiver -- " << version << "\n" << ENDC;
+    std::cout << "    Copyright 2017 - David Sorber\n";
+    std::cout << "    >>> Utilizing packJPG by Matthias Stirner\n" << std::endl;    
+}
+
+void usage(char** argv)
+{
+    // Remove leading "./" if it exits
+    char* app_name = argv[0];
+    if (argv[0][0] == '.' && argv[0][1] == '/')
+    {
+        char* app_name = &argv[0][2];
+    }
     
-    uint32_t num_cores = std::thread::hardware_concurrency();
-    std::cout << "    [" << num_cores << " cores detected]\n" << std::endl;
+    header();
+    std::cout << "USAGE: " << app_name << " [-hkd] <file path>\n\n";
+    std::cout << "Positional arguments:\n";
+    std::cout << "  <file path>           path in which to recursively look "
+              << "for input files\n\n";
+    std::cout << "General options:\n";
+    std::cout << "  -h [ --help ]         display this help message\n";
+    std::cout << "  -d [ --decompress ]   find '*.pjg' files and decompress them\n";
+    std::cout << "  -k [ --keep-orig ]    keep original files; don't delete them\n";
+    std::cout << std::endl;
+}
+
+
+int main(int argc, char** argv)
+{
+    bpo::variables_map general_opts_vm;
     
-    // Make sure the correct number of arguments were specified
-    if (argc < 2)
+    try
     {
-        std::cerr << "ERROR: Not enough arguments" << std::endl;
+        // General options
+        bpo::options_description general_opts("General options");
+        general_opts.add_options()
+            ("help,h", "display this help message")
+            ("decompress,d", "find '*.pjg' files and decompress them")
+            ("keep-orig,k", "keep orignal files")
+            ("file_path", "path in which to recursively look for input files")
+        ;
+
+        // Positional arguments
+        bpo::positional_options_description pos_opts; 
+        pos_opts.add("file_path", 1);
+        
+        // Parse general/positional options
+        bpo::store(bpo::command_line_parser(argc, argv).options(general_opts)
+                   .positional(pos_opts).allow_unregistered().run(), 
+                   general_opts_vm);
+        
+        
+        // Display help if the option is listed or if no arguments are provided
+        if (general_opts_vm.count("help") || (argc == 1)) 
+        {
+            usage(argv);
+            return 0;
+        }
+    }    
+    catch (std::exception& e) 
+    {
+        std::cerr << BOLD << RED << "ERROR: " << ENDC << e.what() << "\n";
         return -1;
     }
     
-    // Make sure that path specified actually exists
-    if (! bfs::exists(argv[1]))
+    
+    // Make a copy of the file search path for passing into the object below
+    std::string search_path(general_opts_vm["file_path"].as<std::string>());
+    
+    // Make sure that path specified actually exists before proceeding
+    if (! bfs::exists(search_path))
     {
-        std::cerr << "ERROR: the path \"" << argv[1] << "\" does not exist" 
-                  << std::endl;
+        std::cerr << BOLD << RED << "ERROR: " << ENDC << "the path \"" 
+                  << search_path << "\" does not exist" << std::endl;
         return -2;
     }
     
-    auto pca = new PhotoCompressArchiver(argv[1], num_cores);
-    pca->execute();
+    // Display header and 
+    header();    
+    uint32_t num_cores = std::thread::hardware_concurrency();
+    std::cout << "    [" << num_cores << " cores detected]\n";
     
-    //~ delete pca;
+    // Decode/store decompress option
+    bool decompress = false;
+    if (general_opts_vm.count("decompress"))
+    {
+        decompress = true;
+        std::cout << "    decompress mode     => ON\n";
+    }
+
+    // Decode/store keep-orig option
+    bool keep_orig = false;
+    if (general_opts_vm.count("keep-orig"))
+    {
+        keep_orig = true;
+        std::cout << "    keep original files => ON\n";
+    }
+    
+    std::cout << std::endl;
+    
+    auto pca = new PhotoCompressArchiver(search_path, num_cores, decompress, 
+                                         keep_orig);
+    pca->execute();
+    delete pca;
     
     return 0;
 }
