//
// jarq.cpp: new 07-19-23.  Non-compressed archiving of multiple files, including name, timestamps, mode.
//
// Written by John A deLaubenfels, https://www.adaptune.com/steg.htm .
// Permission granted to use freely in any non-fraudulent way.
//
//
//
// Limitations:
//    . we hold everything in RAM.  Everything.
//
// qxdo:
//    . recursive stuff?  But we want someone outside this class to list individually each file to be sucked up.
//    . spit selected files rather than dump 'em all out.
//    . get list of files in the archive.
//    . are we even USING the file name for the wrapper anch?
//    . checksum/CRC stuff.
//    . verbose info.
//
//
//






#ifdef NEED_PCH
   #include "pch.h"
#else
   #include "stdafx.h"
#endif // !NEED_PCH

#include "jarq.h"



#ifdef _WIN32
#include <sys/utime.h>
#include <io.h>
#endif // _WIN32

// #include "rndWrap.h"  // qxtemp!




// qxJarqOne()

JarqOne::JarqOne() :
m_iFormat(0),
m_lenFile(0),
m_ctime(0),
m_mtime(0),
m_atime(0),
m_hash(0),
m_mode(0)
{
}



// qxJarqOne()

JarqOne::JarqOne(const JarqOne &rs) :
m_iFormat(rs.m_iFormat),
m_lenFile(rs.m_lenFile),
m_ctime(rs.m_ctime),
m_mtime(rs.m_mtime),
m_atime(rs.m_atime),
m_mode(rs.m_mode),
m_hash(rs.m_hash),
m_ssFileName(rs.m_ssFileName),
m_vcFileData(rs.m_vcFileData)
{
} // JarqOne::JarqOne(const JarqOne &rs)





// qxJarqOne::operator=()

const JarqOne &JarqOne::operator=(const JarqOne &rs) {
   if(&rs != this) {
      m_iFormat    = rs.m_iFormat;
      m_lenFile    = rs.m_lenFile;
      m_ctime      = rs.m_ctime;
      m_mtime      = rs.m_mtime;
      m_atime      = rs.m_atime;
      m_mode       = rs.m_mode;
      m_hash       = rs.m_hash;
      m_ssFileName = rs.m_ssFileName;
      m_vcFileData = rs.m_vcFileData;
   } // if(&rs != this) 

   return *this;
} // const JarqOne &JarqOne::operator=(const JarqOne &rs)





// qxclear()

void JarqOne::clear() {
   m_lenFile=m_ctime=m_mtime=m_atime=m_mode=m_iFormat=0;

   zeroMe(m_ssFileName);
   m_ssFileName.clear();

   zeroMe(m_vcFileData);
   m_vcFileData.clear();
}




// qxfillOne()

int JarqOne::fillOne(const std::vector<unsigned char> &vcData, int iFormat) {
   long long llSize = vcData.size();

   this->m_lenFile  = llSize;
   this->m_hash     = iHash(vcData);

   if(llSize == 0) {
      this->m_vcFileData.clear();
      this->m_iFormat    = 0;
   } else {

      switch(iFormat) {
         case 0:                   // don't compress
            this->m_iFormat    = iFormat;
            this->m_vcFileData = vcData;
            break;
         case 1:                   // compress using zlib:
            {

            std::vector<unsigned char> vcCompress;
            CompressData(vcData, vcCompress);
            if((long long)vcCompress.size() < llSize) {
               this->m_vcFileData = vcCompress;
               this->m_iFormat = 1;
            } else {
               // less space not compressing:
               this->m_vcFileData = vcData;
               this->m_iFormat = 0;
            } // !if(vcCompress.size() < llSize)

            }
            break;
         default:
            this->m_vcFileData = vcData;
            this->m_iFormat = 0;
            break;
      } // switch(iFormat)
   } // !if(llSize == 0)

   return 0;
} // int JarqOne::fillOne(const std::vector<unsigned char> &vcData, int iFormat)





// qxfillOne()

int JarqOne::fillOne(const std::string &ssData, int iFormat) {
   std::vector<unsigned char> vcData;

   vcData.resize(ssData.size());
   for(unsigned long long ill=0; ill<ssData.size(); ill++)
      vcData[ill] = (unsigned char)ssData[ill];

   int iRet = this->fillOne(vcData, iFormat);

   return iRet;
} // int JarqOne::fillOne(const std::string &ssData, int iFormat)




//
// new 06-10-25: split out from writeOne()
//
//
//
//
// qxgetData()
//

int JarqOne::getData(std::vector<unsigned char> &vcData, std::vector<std::string> *pvcErrsOut) const {
   const int cbLen = 8192;
   char cbuf[cbLen];
   // std::vector<unsigned char> vcFat;
   long long llLen = m_vcFileData.size();
   const char *cpFileOut = "I dunno";
   std::vector<std::string>   vcErrsLoc;
   std::vector<std::string>  &vcErrsUse = ((pvcErrsOut == NULL) ? (vcErrsLoc) : (*pvcErrsOut));

   vcData.clear();

   if(m_lenFile == 0) {
   } else {
      switch(m_iFormat) {
         case 0:
            vcData = m_vcFileData;
            break;
         case 1:
            vcData.resize(m_lenFile + 16);
            UncompressData(m_vcFileData, vcData);
            llLen = vcData.size();
            if(llLen != m_lenFile) {
               qprintf(fp_dbg, "JarqOne::getData(), file \"%s\": wanted %lld bytes; got %lld.\n", cpFileOut, m_lenFile, llLen);
               qprintf(fp_std, "JarqOne::getData(), file \"%s\": wanted %lld bytes; got %lld.\n", cpFileOut, m_lenFile, llLen);
#ifdef _WIN32
               sprintf_s(cbuf, cbLen, "JarqOne::getData(), file \"%s\": wanted %lld bytes; got %lld", cpFileOut, m_lenFile, llLen);
#else
               sprintf(cbuf, "JarqOne::getData(), file \"%s\": wanted %lld bytes; got %lld", cpFileOut, m_lenFile, llLen);
#endif // !_WIN32               
               vcErrsUse.push_back(cbuf);

               return NADAfunc("Bad byte count.");
            } // if(llLen != m_lenFile)
            break;
         default:
            qprintf(fp_dbg, "Invalid m_iFormat: %d.\n", m_iFormat);
            qprintf(fp_std, "Invalid m_iFormat: %d.\n", m_iFormat);
#ifdef _WIN32
            sprintf_s(cbuf, cbLen, "Invalid m_iFormat: %d.", m_iFormat);
#else
            sprintf(cbuf, "Invalid m_iFormat: %d.", m_iFormat);
#endif // !_WIN32               

            vcErrsUse.push_back(cbuf);

            return NADAfunc("getData(): invalid m_iFormat");
            break;
      } // switch(m_iFormat)

      unsigned int hashy = iHash(vcData);
      if(m_hash == 0) {
         // strongly implies archive was saved before hashes went with each file; don't check against hashy.
      } else {
         // ok to compare hash values.
         if(hashy != m_hash) {
            // F*** MEEEEEE!!
            qprintf(fp_dbg, "JarqOne::getData(), file \"%s\": hash mismatch: %08X  %08X\n", cpFileOut, hashy, m_hash);
            qprintf(fp_std, "JarqOne::getData(), file \"%s\": hash mismatch: %08X  %08X\n", cpFileOut, hashy, m_hash);
#ifdef _WIN32            
            sprintf_s(cbuf, cbLen, "JarqOne::getData(), file \"%s\": hash mismatch: %08X  %08X", cpFileOut, hashy, m_hash);
#else
            sprintf(cbuf, "JarqOne::getData(), file \"%s\": hash mismatch: %08X  %08X", cpFileOut, hashy, m_hash);
#endif // !_WIN32            
            vcErrsUse.push_back(cbuf);
            return NADAfunc("getData(): invalid hash.");
         } else {
            qprintf(fp_dbg, "JarqOne::getData(), file \"%s\": hash matches: %08X  %08X\n", cpFileOut, hashy, m_hash);  // qxtemp?
         } // !if(hashy != m_hash)
      } // !if(m_hash == 0)
   } // if(m_lenFile == 0) {

   return 0;
} // int JarqOne::getData(std::vector<unsigned char> &vcData)





//
// fleshes out this guy from source file.
//
//
//
// Input:
//    pcFileName: file to read and add to this.
//    iFormat: 0: don't compress file contents.  1: compress file data using zlib.
//    bNoSavePath (default false): if true, strip path info from saved file name.
//
// Output:
//    return value: index in m_vcOnes if all went well; -1 otherwise.
//
//
//
//
// 07-21-23: iFormat.
// 07-23-23: fix for empty file.
// 10-31-23: bNoSavePath (default false).
// 11-13-23: don't barf on folder.
//
// qxreadOne()

int JarqOne::readOne(const char *pcFileName, int iFormat, bool bNoSavePath) {

   this->clear();

   // strip off irritating ".\" or "./" at the beginning.
   if((pcFileName == NULL) || (strlen(pcFileName) < 1))
      return NADA;

   // SMARTA(pcNamUse, char, (strlen(pcFileName)+16), smPcf);

   const char *pcNamUse = pcFileName;
   if((pcFileName[0] == '.') && is_slash(pcFileName[1]))
      pcNamUse += 2;

#ifdef _WIN32
   struct _stat64  stbu64;    // for big file sizes (<2^31).
   if(_stat64(pcNamUse, &stbu64) == -1) {
#else
   struct stat64  stbu64;    // for big file sizes (<2^31).
   if(stat64(pcNamUse, &stbu64) == -1) {
#endif // !_WIN32
      qprintf(fp_dbg, "%s is NOT found.\n", pcNamUse);
      qprintf(fp_std, "%s is NOT found.\n", pcNamUse);
      return NADAfunc("readOne(): file not found.");
   }

   long long llSize = stbu64.st_size;

   bool bDir(false);
   if((stbu64.st_mode & S_IFMT) == S_IFDIR) {
      bDir = true;
      qprintf(fp_dbg, "%s is a directory.\n", pcNamUse);
      qprintf(fp_std, "%s is a directory.\n", pcNamUse);
      // return NADAfunc("readOne(): file is a directory");
   }

   this->m_ctime = stbu64.st_ctime;
   this->m_mtime = stbu64.st_mtime;
   this->m_atime = stbu64.st_atime;
   this->m_mode  = stbu64.st_mode;
   this->m_ssFileName = pcNamUse;

   if(bDir && bNoSavePath)
      return NADA;

   if(!bNoSavePath) {
      this->m_ssFileName = pcNamUse;
   } else {
      // bNoSavePath: strip off path name and save only the file name:
      SMARTA(pcDir, char, (strlen(pcNamUse)+16), smaDir);
      SMARTA(pcFil, char, (strlen(pcNamUse)+16), smaFil);

      cut_file_name(pcNamUse, pcDir, pcFil);

      this->m_ssFileName = pcFil;
   } // !if(!bNoSavePath) {

   // 06-02-26: make sure trailing slash accompanies directory:
   int lenn = (int)this->m_ssFileName.size();
   if(lenn > 0) {
      if(bDir) {
         if(!is_slash(this->m_ssFileName[lenn-1]))
            this->m_ssFileName += SLASH;
      } else {
         // make sure file name does NOT end in a slash!
         if(is_slash(this->m_ssFileName[lenn-1]))
            this->m_ssFileName.resize(lenn-1);
      } // !if(bDir)
   } // if(lenn > 0)

   if(bDir) {
      this->m_lenFile = 0;
      this->m_vcFileData.clear();
      qprintf(fp_dbg, "Added directory %s.\n", pcNamUse);
      qprintf(fp_std, "Added directory %s.\n", pcNamUse);
      return 0;                           // qxl: ensure trailing slash.
   } // if(bDir)

   std::vector<unsigned char> vcLocal;

   // this->m_vcFileData.resize(llSize);
   vcLocal.resize(llSize);

   if(llSize == 0) {
      // trouble if we call zlib on a zero-length guy.
      this->m_iFormat = 0;
   } else {
      FILE *fp(NULL);

#ifdef _WIN32
      if(fopen_s(&fp, pcNamUse, "rb") != 0) {
#else
      if((fp = fopen(pcNamUse, "rb")) == NULL) {
#endif // !_WIN32
         qprintf(fp_dbg, "Unable to open %s.\n", pcNamUse);
         qprintf(fp_std, "Unable to open %s.\n", pcNamUse);
         return NADAfunc("readOne(): file found but can't open for read.");
      }

      long long ll;
      // if((ll = fread((void *)(&this->m_vcFileData[0]), sizeof(unsigned char), llSize, fp)) != llSize) {
      if((ll = fread((void *)(&vcLocal[0]), sizeof(unsigned char), llSize, fp)) != llSize) {
         qprintf(fp_dbg, "Didn't read enough bytes of %s: %lld -> %lld.\n", pcNamUse, llSize, ll);
         qprintf(fp_std, "Didn't read enough bytes of %s: %lld -> %lld.\n", pcNamUse, llSize, ll);
         fclose(fp);    fp = NULL;
         this->clear();
         return NADAfunc("readOne(): unable to read all bytes.");
      } // if((i = fread((void *)(&char1), sizeof(char), totLen, fp)) != totLen)

      // all done reading, so:
      fclose(fp); fp=NULL;

   } // !if(llSize == 0)

   // int JarqOne::fillOne(const std::vector<unsigned char> &vcData, int iFormat) {
   int jj = this->fillOne(vcLocal, iFormat);

   qprintf(fp_dbg, "Added file \"%s\".\n", pcNamUse);
   qprintf(fp_std, "Added file \"%s\".\n", pcNamUse);

   return 0;
} // int JarqOne::readOne(const char *pcFileName)






//
// write this's contents to the file name stored.
//
//
//
//
// 07-21-23: iFormat.
// 07-23-23: fix for empty file.
// 07-25-23: change abs paths to relative.
// 11-08-23: pvcContentsOut (default NULL).
// 11-13-23: make naked folder if appropriate.
// 11-30-23: call new fixSlashes().
// 06-19-24: pvcErrsOut (default NULL).
// 06-10-25: call new getData().
//
// qxwriteOne()
//

int JarqOne::writeOne(std::vector<unsigned char> *pvcContentsOut, std::vector<std::string> *pvcErrsOut) const {
   long long ill=0;
   const int cbLen = 8192;
   char cbuf[cbLen];
   std::vector<unsigned char> vcFat;
   std::vector<std::string>   vcErrsLoc;
   std::vector<std::string>  &vcErrsUse = ((pvcErrsOut == NULL) ? (vcErrsLoc) : (*pvcErrsOut));
   std::string ssTmp;

   bool bWrote(false), bPassed(false);

   long long llLen = m_vcFileData.size();
   const char *cpFileOut = NULL;
   FILE *fpOut = NULL;
   fileCloser afc(&fpOut);

   std::string ssFilAbs = this->getFileNams();
   std::string ssFilSla, ssFilRel;

   fixSlashes(ssFilAbs, ssFilSla);
   unAbsify(ssFilSla, ssFilRel);
   int iLen = (int)ssFilRel.size();

   bool bDir = ((iLen > 0) && (is_slash(ssFilRel[iLen-1])));
   
   // qprintf(fp_dbg, "Abs \"%s\", sla \"%s\", rel \"%s\", bDir %d.\n", ssFilAbs.c_str(), ssFilSla.c_str(), ssFilRel.c_str(), bDir);  // qxtemp

   if(bDir) {
      const char *pcDir = ssFilRel.c_str();
      mkdirWrap(pcDir, NULL);
#ifdef _WIN32
      _chmod(pcDir, this->m_mode);
#else
      chmod(pcDir, this->m_mode);
#endif // !WIN32

      // qprintf(fp_std, "called mkdirWrap() for \"%s\".\n", pcDir);  // qxtemp!

      return 0;    // qxl: verify the dir is there.
   } // if(bDir)

   if(ssFilRel.size() == 0) {
      // new option 11-08-23: just pass contents back, if possible.
   } else {
      // normal processing:

      cpFileOut = ssFilRel.c_str();
      SMARTA(pcDir, char, (strlen(cpFileOut) + 32), smaDir);
      SMARTA(pcFil, char, (strlen(cpFileOut) + 32), smaFil);

      cut_file_name(cpFileOut, pcDir, pcFil);

      // qprintf(fp_dbg, "From \"%s\", we get dir \"%s\", fil \"%s\".\n", cpFileOut, pcDir, pcFil);

      mkdirWrap(pcDir, NULL);


//    qprintf(fp_dbg, "Entering JarqOne()::writeOne(), to file \"%s\" with %lld bytes.\n", cpFileOut, llLen);

      arcMeDotty(cpFileOut);

#ifdef _WIN32
      if(fopen_s(&fpOut, cpFileOut, "wb") != 0) {
         sprintf_s(cbuf, cbLen, "Unable to open output file %s.", cpFileOut);
#else
      if((fpOut = fopen(cpFileOut, "wb")) == NULL) {
         sprintf(cbuf, "Unable to open output file %s.", cpFileOut);
#endif // !_WIN32
         qprintf(fp_dbg, "Unable to open output file %s.\n", cpFileOut);
         qprintf(fp_std, "Unable to open output file %s.\n", cpFileOut);
         vcErrsUse.push_back(cbuf);
         // return NADAfunc("writeOne(): unable to open file for write");   // out: might pass info back.
         fpOut = NULL;  // in case necessary.
      }
   } // !if(ssFileRel.size == 0)

   if(m_lenFile == 0) {
   } else {
      int iii = this->getData(vcFat, pvcErrsOut);

      if(iii < 0)
         return NADAfunc("getData() call returned error.");

      llLen = vcFat.size();

#ifdef MOVED_TO_FUNQ
      switch(m_iFormat) {
         case 0:
            vcFat = m_vcFileData;
            break;
         case 1:
            vcFat.resize(m_lenFile + 16);
            UncompressData(m_vcFileData, vcFat);
            llLen = vcFat.size();
            if(llLen != m_lenFile) {
               qprintf(fp_dbg, "JarqOne::writeOne(), file \"%s\": wanted %lld bytes; got %lld.\n", cpFileOut, m_lenFile, llLen);
               qprintf(fp_std, "JarqOne::writeOne(), file \"%s\": wanted %lld bytes; got %lld.\n", cpFileOut, m_lenFile, llLen);
#ifdef _WIN32
               sprintf_s(cbuf, cbLen, "JarqOne::writeOne(), file \"%s\": wanted %lld bytes; got %lld", cpFileOut, m_lenFile, llLen);
#else
               sprintf(cbuf, "JarqOne::writeOne(), file \"%s\": wanted %lld bytes; got %lld", cpFileOut, m_lenFile, llLen);
#endif // !_WIN32               
               vcErrsUse.push_back(cbuf);

               return NADAfunc("wrong number of bytes.");
            } // if(llLen != m_lenFile)
            break;
         default:
            qprintf(fp_dbg, "Invalid m_iFormat: %d.\n", m_iFormat);
            qprintf(fp_std, "Invalid m_iFormat: %d.\n", m_iFormat);
#ifdef _WIN32
            sprintf_s(cbuf, cbLen, "Invalid m_iFormat: %d.", m_iFormat);
#else
            sprintf(cbuf, "Invalid m_iFormat: %d.", m_iFormat);
#endif // !_WIN32               

            vcErrsUse.push_back(cbuf);

            return NADAfunc("writeOne(): invalid m_iFormat");
            break;
      } // switch(m_iFormat)

      unsigned int hashy = iHash(vcFat);
      if(m_hash == 0) {
         // strongly implies archive was saved before hashes went with each file; don't check against hashy.
      } else {
         // ok to compare hash values.
         if(hashy != m_hash) {
            // F*** MEEEEEE!!
            qprintf(fp_dbg, "JarqOne::writeOne(), file \"%s\": hash mismatch: %08X  %08X\n", cpFileOut, hashy, m_hash);
            qprintf(fp_std, "JarqOne::writeOne(), file \"%s\": hash mismatch: %08X  %08X\n", cpFileOut, hashy, m_hash);
#ifdef _WIN32            
            sprintf_s(cbuf, cbLen, "JarqOne::writeOne(), file \"%s\": hash mismatch: %08X  %08X", cpFileOut, hashy, m_hash);
#else
            sprintf(cbuf, "JarqOne::writeOne(), file \"%s\": hash mismatch: %08X  %08X", cpFileOut, hashy, m_hash);
#endif // !_WIN32            
            vcErrsUse.push_back(cbuf);
         } else {
            qprintf(fp_dbg, "JarqOne::writeOne(), file \"%s\": hash matches: %08X  %08X\n", cpFileOut, hashy, m_hash);  // qxtemp?
         } // !if(hashy != m_hash)
      } // !if(m_hash == 0)
#endif // MOVED_TO_FUNQ



      if(fpOut != NULL) {
         if((ill = fwrite(&vcFat[0], sizeof(char), llLen, fpOut)) != llLen) {
#ifdef _WIN32         
            sprintf_s(cbuf, cbLen, "only wrote %lld of %lld bytes.", ill, llLen);
#else
            sprintf(cbuf, "only wrote %lld of %lld bytes.", ill, llLen);
#endif // !_WIN32            
            vcErrsUse.push_back(cbuf);

            qprintf(fp_dbg, "only wrote %lld of %lld bytes.\n", ill, llLen);
            qprintf(fp_std, "only wrote %lld of %lld bytes.\n", ill, llLen);
            fclose(fpOut); fpOut=NULL;
            // return NADAfunc("writeOne() unable to write all bytes to file.");      // out: might pass back.
         } // if((i = fwrite(&m_vcFileData[0], sizeof(char), llLen, fpOut)) != llLen)
      } // if(fpOut != NULL)
   } // !if(m_lenFile == 0)

   // success:
   if(fpOut != NULL) {
      fclose(fpOut); fpOut=NULL;

      bWrote = true;

#ifdef _WIN32
      struct _stat64  stbu64;
      if(_stat64(cpFileOut, &stbu64) == -1) {
         sprintf_s(cbuf, cbLen, "Unable to find file %s.\n", cpFileOut);
#else
      struct stat64  stbu64;
      if(stat64(cpFileOut, &stbu64) == -1) {
         sprintf(cbuf, "Unable to find file %s.\n", cpFileOut);      
#endif // !_WIN32
         qprintf(fp_dbg, "Unable to find file %s.\n", cpFileOut);
         qprintf(fp_std, "Unable to find file %s.\n", cpFileOut);         

         vcErrsUse.push_back(cbuf);

         bWrote = false;
         // return NADAfunc("writeOne(): unable to find the file we wrote.");
      } // if(_stat64(cpFileOut, &stbu64) == -1)

      long long totLen = (long long)stbu64.st_size;

      if(totLen == llLen) {
         qprintf(fp_dbg, "JarqOne::writeOne(): we got what we thought: %lld.\n", llLen);
         
      } else {
         qprintf(fp_dbg, "JarqOne::writeOne(): thought we'd get %lld, but got %lld (diff %lld).\n", llLen, totLen, (totLen - llLen));
#ifdef _WIN32         
         sprintf_s(cbuf, cbLen, "JarqOne::writeOne(): thought we'd get %lld, but got %lld (diff %lld).\n", llLen, totLen,
          (totLen - llLen));
#else
         sprintf(cbuf, "JarqOne::writeOne(): thought we'd get %lld, but got %lld (diff %lld).\n", llLen, totLen,
          (totLen - llLen));         
#endif // !_WIN32          
         vcErrsUse.push_back(cbuf);

         bWrote = false;
      } // !if(totLen == llLen)


      int i=0;

#ifdef _WIN32
      // set timestamps (that we know how to) of output file:
      struct _utimbuf utb;
      utb.actime  = this->m_atime;      // stbu64.st_atime;
      utb.modtime = this->m_mtime;      // stbu64.st_mtime;

      if((i = _utime(cpFileOut, &utb)) != 0) {
         fprintf(stderr, "Bad _utime() return value: %d.\n", i);
         return NADAfunc("writeOne(): bad _utime() return value.");
      }

      _chmod(cpFileOut, this->m_mode);
      
#else
      struct timeval tvp[2];

      tvp[0].tv_sec = this->m_atime;
      tvp[0].tv_usec = 0;

      tvp[1].tv_sec = this->m_mtime;
      tvp[1].tv_usec = 0;

      if((i = utimes(cpFileOut, tvp)) != 0) {
         qprintf(fp_dbg, "Bad utimes() return value: %d.\n", i);
         qprintf(fp_std, "Bad utimes() return value: %d.\n", i);
         return NADA;
      }

      chmod(cpFileOut, this->m_mode);

#endif // !WIN32

      qprintf(fp_dbg, "Extracted file \"%s\" with %lld bytes.\n", cpFileOut, llLen);
//    qprintf(fp_std, "Extracted file \"%s\" with %lld bytes.\n", cpFileOut, llLen);

   } // if(fpOut != NULL)

   if(pvcContentsOut != NULL) {
      *pvcContentsOut = vcFat;
      bPassed = true;
   } // if(pvcContentsOut != NULL)

   bool bHappy = bWrote || bPassed;

   if((iLen > 0) && !bWrote)
      bHappy = false;

   int iRet = ((bHappy) ? 0 : NADA);

   return iRet;
} // int JarqOne::writeOne()...




//
// serializes "this" to file and/or vector.
//
//
//
//
//
// 07-21-23: rev 1.
// 06-19-24: rev 2.
//
// qxarchiveMe()
// qxJarqOne::archiveMe()

int JarqOne::archiveMe(int irev, spitObj &mySpit) const {

   switch(irev) {
      case 2:
         if(archiveUint(mySpit,   m_hash)      < 0) return NADAfunc("m_hash");
      case 1:
         if(archiveInt(mySpit,    m_iFormat)   < 0) return NADAfunc("m_iFormat");
         if(archiveLlong(mySpit,  m_lenFile)   < 0) return NADAfunc("m_lenFile");
      case 0:
         {
         if(archiveLlong(mySpit,  m_ctime)     < 0) return NADAfunc("m_ctime");
         if(archiveLlong(mySpit,  m_mtime)     < 0) return NADAfunc("m_mtime");
         if(archiveLlong(mySpit,  m_atime)     < 0) return NADAfunc("m_atime");
         if(archiveInt(mySpit,    m_mode)      < 0) return NADAfunc("m_mode");

         int nameLen = (int)m_ssFileName.size();
         if(archiveInt(mySpit,    nameLen)     < 0) return NADAfunc("nameLen");
         for(int i=0; i<nameLen; i++) {
            if(archiveChar(mySpit,   m_ssFileName[i])    < 0)    return NADAfunc("some char in m_ssFilename");
         } // for(int i=0; i<nameLen; i++)

         long long lenFil = m_vcFileData.size();
         if(archiveLlong(mySpit,  lenFil)      < 0) return NADAfunc("lenFil");

         if(archiveBytes(mySpit, ADDR_ZEROTH(m_vcFileData), lenFil) < 0)   return NADAfunc("m_vcFileData");

         }
         break;
      default:
         qprintf(fp_dbg, "Invalid irev in JarqOne::archiveMe(): %d.\n", irev);
         qprintf(fp_std, "Invalid irev in JarqOne::archiveMe(): %d.\n", irev);
         return NADAfunc("JarqOne::archiveMe(): invalid irev.");
         break;
   } // switch(irev)


   return 0;
} // int JarqOne::archiveMe(int irev, spitObj &mySpit) const




//
// Fleshes out "this" from serialized file or memory.
//
//
//
//
// 07-21-23: rev 1.
// 09-26-23: more graceful return if data corrupted.
// 06-19-24: rev 2.
//
// qxunArcMe()
// qxJarqOneunArcMe()

int JarqOne::unArcMe(int irev, sucqObj &suq) {
   this->clear();

   const int       fairlyLarge  = 4096;
   const long long floggingHuge = 0x0000000200000000LL;  // 8,589,934,592 

   switch(irev) {
      case 2:
         if(unArcUint(suq,        m_hash)      < 0) return NADAfunc("m_hash");
      case 1:
         if(unArcInt(suq,         m_iFormat)   < 0) return NADAfunc("m_iFormat");
         if(unArcLlong(suq,       m_lenFile)   < 0) return NADAfunc("m_lenFile");
      case 0:
         {

         if(unArcLlong(suq,       m_ctime)     < 0) return NADAfunc("m_ctime");
         if(unArcLlong(suq,       m_mtime)     < 0) return NADAfunc("m_mtime");
         if(unArcLlong(suq,       m_atime)     < 0) return NADAfunc("m_atime");
         if(unArcInt(suq,         m_mode)      < 0) return NADAfunc("m_mode");

         int nameLen = 0;
         if(unArcInt(suq,         nameLen)     < 0) return NADAfunc("nameLen");

         // qprintf(fp_dbg, "nameLen %d.\n", nameLen);
         if((nameLen < 0) || (nameLen > fairlyLarge)) return NADAfunc("Flogged-up nameLen");


         char c = 0;
         for(int i=0; i<nameLen; i++) {
            if(unArcChar(suq,         c)      < 0) return NADAfunc("char in m_ssFileName");
            m_ssFileName += c;
         } // for(int i=0; i<nameLen; i++)

         long long lenFil = 0;
         if(unArcLlong(suq,       lenFil)      < 0) return NADAfunc("lenFil");

         // qprintf(fp_dbg, "lenFil %lld.\n", lenFil);

         if((lenFil < 0) || (lenFil > floggingHuge)) return NADAfunc("Flogged-up lenFil");



         m_vcFileData.resize(lenFil);

         if(unArcBytes(suq, ADDR_ZEROTH(m_vcFileData), lenFil) < 0) return NADAfunc("m_vcFileData");

//       qprintf(fp_dbg, "JarqOne::unArcMe() gets %lld bytes from file \"%s\".\n", m_vcFileData.size(), m_ssFileName.c_str());

         }
         break;
      default:
         qprintf(fp_dbg, "Invalid irev in JarqOne::unArqMe(): %d.\n", irev);
         qprintf(fp_std, "Invalid irev in JarqOne::unArqMe(): %d.\n", irev);

         return NADAfunc("JarqOne::unArqMe(): invalid irev.");
         break;
   } // switch(irev)

   if(irev == 0) {
      m_iFormat = 0;
      m_lenFile = m_vcFileData.size();
   } // if(irev == 0)

   return 0;
} // int JarqOne::unArcMe(int irev, sucqObj &suq)


////////////////////////////////////////////////////////////////////////////////////////////



// qxJarqAnch()

JarqAnch::JarqAnch(const JarqAnch &rs) :
m_ssArcFileName(rs.m_ssArcFileName),
m_vcOnes(rs.m_vcOnes)
{
} // JarqAnch::JarqAnch(const JarqAnch &rs)




// qxJarqAnch::operator=()

const JarqAnch &JarqAnch::operator=(const JarqAnch &rs) {
   if(this != &rs) {
      m_ssArcFileName = rs.m_ssArcFileName;
      m_vcOnes        = rs.m_vcOnes;
   } // if(this != &rs)

   return *this;
} // const JarqAnch &JarqAnch::operator=(const JarqAnch &rs)





// qxJarqAnch::operator+=()

const JarqAnch &JarqAnch::operator+=(const JarqAnch &rs) {
   int nCopy = (int)rs.size();   // in case this == &rs

   for(int i=0; i<nCopy; i++)
      m_vcOnes.push_back(rs.m_vcOnes[i]);

   return *this;
} // const JarqAnch &JarqAnch::operator+=(const JarqAnch &rs)





// qxnSubstantial()

int JarqAnch::nSubstantial() const {
   int nCount = 0;

   for(int i=0; i<(int)this->size(); i++) {
      const JarqOne *pOne = this->getIthOne(i);
      if(pOne == NULL)
         continue;          // should not happen

      std::string ssFilNam = pOne->getFileNams();

      if(ssFilNam.size() > 0)
         nCount++;
   } // for(int i=0; i<(int)this->size(); i++)

   return nCount;
} // int JarqAnch::nSubstantial() const




// qxgetIthOne()
//

const JarqOne *JarqAnch::getIthOne(int ith) const {
   if((ith < 0) || (ith >= this->size()))
      return NULL;

   return &m_vcOnes[ith];
} // const JarqOne *JarqAnch::getIthOne(int ith) const







//
// adds a new file's contents to this archive anchor.
//
//
//
// Input:
//    pcFileName: file to read and add to this.
//    iFormat: 0: don't compress file contents.  1: compress file data using zlib.
//    bNoSavePath (default false): if true, strip path info from saved file name.
//
// Output:
//    return value: index in m_vcOnes if all went well; -1 otherwise.
//
//
//
//
// 07-21-23: iFormat.
// 10-31-23: bNoSavePath (default false).
// 11-12-23: allow directory file to be added.
//
// qxaddOne()

int JarqAnch::addOne(const char *pcFileName, int iFormat, bool bNoSavePath) {
#ifdef _WIN32
   struct _stat64  stbu64;    // for big file sizes (<2^31).
   if(_stat64(pcFileName, &stbu64) == -1) {
#else
   struct stat64  stbu64;    // for big file sizes (<2^31).
   if(stat64(pcFileName, &stbu64) == -1) {
#endif // !_WIN32
      qprintf(fp_dbg, "%s is NOT found.\n", pcFileName);
      qprintf(fp_std, "%s is NOT found.\n", pcFileName);
      return NADAfunc("JarqAnch::addOne(): file not found.");
   }

   long long llSize = stbu64.st_size;

   bool bDir(false);
   if((stbu64.st_mode & S_IFMT) == S_IFDIR) {
      qprintf(fp_dbg, "%s iz a directory.\n", pcFileName);
      qprintf(fp_std, "%s iz a directory.\n", pcFileName);
      // return NADAfunc("JarqAnch::addOne(): file is a directory.");
      bDir = true;
   }

   // make room for new guy:
   int nPrev = (int)this->size();
   m_vcOnes.resize(nPrev+1);
   JarqOne &nuOne = m_vcOnes[nPrev];

   if(bDir) {
      // needed if a folder has no files under it but must be present in the extract.
      // qxhere
      int j = nuOne.readOne(pcFileName, iFormat, bNoSavePath);

      if(j < 0) {
         qprintf(fp_dbg, "Bad return from JarqOne::readOne() for directory.\n");
         qprintf(fp_std, "Bad return from JarqOne::readOne() for directory.\n");
         m_vcOnes.resize(nPrev);
         return NADAfunc("JarqAnch::addOne(): bad return from JarqOne:readOne() for directory.");
      } // if(j < 0)
   } else {
      // normal:
      int j = nuOne.readOne(pcFileName, iFormat, bNoSavePath);

      if(j < 0) {
         qprintf(fp_dbg, "Bad return from JarqOne::readOne().\n");
         qprintf(fp_std, "Bad return from JarqOne::readOne().\n");
         m_vcOnes.resize(nPrev);
         return NADAfunc("JarqAnch::addOne(): bad return from JarqOne:readOne().");
      } // if(j < 0)
   } // !if(bDir)

   return nPrev;
} // int JarqAnch::addOne(const char *pcFileName)




//
//
// Input:
//    vcFileList: list of files to append to this.
//    iFormat: 0: don't compress file contents.  1: compress file data using zlib.
//    bNoSavePath (default false): if true, strip path info from saved file name.
//
// Output:
//    return value: number of files added.
//
//
// 07-21-23: iFormat.
// 10-31-23: bNoSavePath (default false).
//
// qxaddFiles()
//

int JarqAnch::addFiles(const std::vector<std::string> &vcFileList, int iFormat, bool bNoSavePath) {
   int nOld = (int)this->size();

   for(int i=0; i<(int)vcFileList.size(); i++) {
      const std::string &ssFile = vcFileList[i];
      int ii = this->addOne(ssFile.c_str(), iFormat, bNoSavePath);
      if(ii < 0) {
         qprintf(fp_dbg, "Unable to add file \"%s\".\n", ssFile.c_str());
         qprintf(fp_std, "Unable to add file \"%s\".\n", ssFile.c_str());
      } else {
         qprintf(fp_dbg, "Successfully added file \"%s\" to slot %d.\n", ssFile.c_str(), ii);
//       qprintf(fp_std, "Added file \"%s\".\n", ssFile.c_str());
      } // !if(ii < 0)
   } // for(int i=0; i<(int)vcFileList.size(); i=++)


   int nNew   = (int)this->size();
   int nAdded = nNew - nOld;

   return nAdded;
} // int JarqAnch::addFiles(const std::vector<std::string> &vcFileList)




//
// writes out everyone back into individual files.
//
// Input:
//    pvcContentsOut (default NULL): if non-NULL and a file has blank name, report its contents.
//    pvcFileWrites  (default NULL): if non-NULL, debug writes to there.
//    pvcErrsOut     (default NULL): if non-NULL, text about any problem(s) encountered.
//
// Output:
//    return: number of files written
//    *pvcContentsOut: info from guy with blank name.
//    *pvcErrsOut:     info about any errors encountered.
//
//
// 11-08-23: pvcContentsOut (default NULL).
// 12-02-23: pvcFileWrites  (default NULL).
// 12-19-23: pvcVcFileWrites (default NULL).
// 06-19-24: pvcErrsOut      (default NULL).
//
// qxspitFiles()

int JarqAnch::spitFiles(std::vector<std::vector<unsigned char>> *pvcVcContentsOut, std::vector<FILE *> *pvcFileWrites, std::vector<std::string> *pvcErrsOut) const {
   int nWrit=0;

   if(pvcVcContentsOut != NULL)
      pvcVcContentsOut->clear();

   int nFiles = (int)m_vcOnes.size();

   for(int i=0; i<nFiles; i++) {
      const JarqOne &aOne = m_vcOnes[i];

      std::string ssFilNam = aOne.getFileNams();

      if(ssFilNam.size() == 0) {
         // special case:
         if(pvcVcContentsOut != NULL) {
            std::vector<unsigned char> vcContentLoc;
            aOne.writeOne(&vcContentLoc, pvcErrsOut);   // no file write, but contents filled.
            pvcVcContentsOut->push_back(vcContentLoc);
         } // if(pvcVcContentsOut != NULL)
      } else {
         // normal:
         int j = aOne.writeOne(NULL, pvcErrsOut);
         if(j == 0) {
            nWrit++;
            if(pvcFileWrites != NULL) {
               int k(0);
               std::vector<FILE *> &vcFp = *pvcFileWrites;
               for(k=0; k<(int)vcFp.size(); k++) {
                  FILE *fp = vcFp[k];
                  qprintf(fp, "Wrote file %s\n", aOne.getFileName());
               } // for(k=0; k<(int)vcFp.size(); k++)
            } // if(pvcFileWrites != NULL)
         } // if(j == 0)
      } // if(ssFilNam.size() == 0)

   } // for(int i=0; i<nFiles; i++)

   return nWrit;
} // int JarqAnch::spitFiles() const {





//
//
//
//
//
//
// qxreadJarq()
//

int JarqAnch::readJarq(const char *cpFileIn) {
   this->clear();

   if(cpFileIn == NULL)
      return NADAfunc("JarqAnch::readJarq(): NULL cpFileIn.");

#ifdef _WIN32
   struct _stat64  stbu64;    // for big file sizes (>2^31).
   if(_stat64(cpFileIn, &stbu64) == -1) {
#else
   struct stat64  stbu64;    // for big file sizes (>2^31).
   if(stat64(cpFileIn, &stbu64) == -1) {
#endif // !_WIN32
      qprintf(fp_dbg, "Unable to find file %s.\n", cpFileIn);
      qprintf(fp_std, "Unable to find file %s.\n", cpFileIn);
      return NADAfunc("JarqAnch::readJarq(): Unable to find cpFileIn");
   } // if(_stat64(cpFileIn, &stbu64) == -1)

   if((stbu64.st_mode & S_IFMT) == S_IFDIR) 
      return NADAfunc("JarqAnch::readJarq(): file is a directory");

   long long totLen = (long long)stbu64.st_size;

   FILE *fpIn(NULL);
#ifdef _WIN32
   if(fopen_s(&fpIn, cpFileIn, "rb") != 0) {
#else
   if((fpIn = fopen(cpFileIn, "rb")) == NULL) {
#endif // !_WIN32
      qprintf(fp_dbg, "Unable to open file %s.\n", cpFileIn);
      qprintf(fp_std, "Unable to open file %s.\n", cpFileIn);
      return NADAfunc("JarqAnch::readJarq(): unable to open file.");
   }

   int irevSaved;
   sucqObj suq(fpIn, NULL, NULL);

   int iRet = this->unArcJarq(irevSaved, suq);

   fclose(fpIn);    fpIn = NULL;

   return iRet;
} // int JarqAnch::readJarq(const char *cpFileIn)




//
//
//
// cpFileOut should have the extension .jrq, probably.  It'll be a binary file.  We archive any existing file.
//
//
//
// qxwriteJarq()
//

int JarqAnch::writeJarq(const char *cpFileOut) const {
   if(cpFileOut == NULL) {
      qprintf(fp_dbg, "NULL cpFileOut.\n");
      qprintf(fp_std, "NULL cpFileOut.\n");
      return NADAfunc("JarqAnch::writeJarq(): NULL cpFileOut.");
   } // if(cpFileOut == NULL)

   qprintf(fp_dbg, "Entering JarqAnch()::writeJarq(), to file \"%s\".\n", cpFileOut);
   qprintf(fp_std, "Entering JarqAnch()::writeJarq(), to file \"%s\".\n", cpFileOut);

   arcMeDotty(cpFileOut);

   FILE *fpOut(NULL);
#ifdef _WIN32
   if(fopen_s(&fpOut, cpFileOut, "wb") != 0) {
#else
   if((fpOut = fopen(cpFileOut, "wb")) == NULL) {
#endif // !_WIN32
      qprintf(fp_dbg, "Unable to open output file %s.\n", cpFileOut);
      qprintf(fp_std, "Unable to open output file %s.\n", cpFileOut);
      return NADAfunc("JarqAnch::writeJarq(): unable to open output file.");
   }

   spitObj mySpit(fpOut, NULL);

   int iRet = this->archiveJarq(NADA, mySpit);

// qprintf(fp_dbg, "JarqAnch::writeJarq() back from archiveJarq(), 2nd time...\n"); qflush(fp_dbg);

   if(iRet < 0) {
      qprintf(fp_dbg, "Unable to write .jrq file %s ...\n", cpFileOut);
      qprintf(fp_std, "Unable to write .jrq file %s ...\n", cpFileOut);
      fclose(fpOut); fpOut = NULL;
      return NADAfunc("JarqAnch::writeJarq(): bad return from archiveJarq().");
   } // if(iRet < 0)

   // success:
   fclose(fpOut); fpOut=NULL;

#ifdef _WIN32
   struct _stat64  stbu64;    // for big file sizes (>2^31).
   if(_stat64(cpFileOut, &stbu64) == -1) {
#else
   struct stat64  stbu64;    // for big file sizes (>2^31).
   if(stat64(cpFileOut, &stbu64) == -1) {
#endif // !_WIN32
      qprintf(fp_dbg, "Unable to find file %s.\n", cpFileOut);
      qprintf(fp_std, "Unable to find file %s.\n", cpFileOut);
      return NADAfunc("JarqAnch::writeJarq(): unable to find output file.");
   } // if(_stat64(cpFileOut, &stbu64) == -1)

   if((stbu64.st_mode & S_IFMT) == S_IFDIR) 
      return NADAfunc("JarqAnch::writeJarq(): output file is a directory.");

   long long totLen = (long long)stbu64.st_size;

#ifdef MAYBE_LATER
   if(totLen == nBytes) {
      qprintf(fp_dbg, "JarqAnch::writeJarq(): we got what we thought: %lld.\n", nBytes);
   } else {
      qprintf(fp_dbg, "JarqAnch::writeJarq(): thought we'd get %lld, but got %lld (diff %lld).\n", nBytes, totLen, (totLen - nBytes));
   } // !if(totLen == nBytes)
#endif // MAYBE_LATER

   return iRet;
} // int JarqAnch::writeJarq(const char *cpFileOut) const




//
//
// Serializes a JarqAnch object to a .jrq archive file or a memory stream.
//
// Input:
//    irevIn: usually pass NADA to get the latest.
//    mySpit: pointes either to a vector or to a file being written.
//    nEmpty (default 0): number of bytes to prepend to archive.
//    nSkip  (default 0): number of JarqOne's to skip.
//
//
//
//
// 07-21-23: irev 1.
// 07-23-23: nEmpty (default 0).
// 11-08-23: nSkip  (default 0).
// 06-19-24: irev 2.
//
// qxarchiveJarq()
//

int JarqAnch::archiveJarq(int irevIn, spitObj &mySpit, int nEmpty, int nSkip) const {
// int iLatest =  0;    // starting point
// int iLatest =  1;    // iFormat in JarqOne.
   int iLatest =  2;    // ihash in JarqOne.
   int irev = ((irevIn<0) ? (iLatest) : (irevIn));

   if((irev < 0) || (irev > iLatest)) {
      qprintf(fp_dbg, "JarqAnch::archiveJarq(): invalid irev: %d.\n", irevIn);
      qprintf(fp_std, "JarqAnch::archiveJarq(): invalid irev: %d.\n", irevIn);
      return NADAfunc("invalid irev");
   } // if((irev < 0) || (irev > iLatest))

   for(int i=0; i<nEmpty; i++)
      if(archiveChar(mySpit,  0) < 0) return NADAfunc("nEmpty");

   if(archiveInt(mySpit,  ARC_JARQ) < 0) return NADAfunc("ARC_JARQ");
   if(archiveInt(mySpit,  irev)     < 0) return NADAfunc("irev");

   int iRet = 0;

   switch(irev) {
      case 2:
      case 1:
      case 0:
         {
         int nameLen = (int)m_ssArcFileName.size();
         if(archiveInt(mySpit,   nameLen)    < 0)    return NADAfunc("nameLen");
         for(int i=0; i<nameLen; i++) {
            if(archiveChar(mySpit,   m_ssArcFileName[i])    < 0)    return NADAfunc("char in m_ssArcFileName");
         } // for(int i=0; i<nameLen; i++)

         int nOnes = MAX(0, ((int)m_vcOnes.size() - nSkip));
         if(archiveInt(mySpit,  nOnes)     < 0) return NADAfunc("nOnes");

         for(int i=nSkip; i<(int)m_vcOnes.size(); i++) {
            const JarqOne &jaThis = m_vcOnes[i];
            int j = jaThis.archiveMe(irev, mySpit);
            iRet += j;
         } // for(int i=nSkip; i<(int)m_vcOnes.size(); i++)

         }
         break;
      default:
         return NADAfunc("JarqAnch::archiveJarq(): invalid irev.");
         break;
   } // switch(irev)

   return iRet;
} // int JarqAnch::archiveJarq(int irev, spitObj &mySpit) const




//
// Fills a JarqAnch object from a .jrq archive file or a memory stream.
//
//
//
//
// 06-19-24: rev 2.
//
// qxunArcJarq()
// qxJarqAnch::unArcJarq()
//

int JarqAnch::unArcJarq(int &irevSaved, sucqObj &suq) {
   int iRet = 0;

   // we should be empty now; if not, do that:
   this->clear();

   // verify that this really is an Jarq archive:
   int iTest;
   if(unArcInt(suq, iTest)     != 0) return NADAfunc("iTest 1");
   if((iTest != ARC_JARQ) && (iTest != OLD_JARQ))  return NADAfnc2("JarqAnch::unArcJarq(): invalid header: %04X", iTest);

   // get the revision the archive was saved under:
   if(unArcInt(suq, irevSaved) != 0) return NADAfunc("irevSaved");

   switch(irevSaved) {
      case 2:
      case 1:
      case 0:
         {

         int namLen = 0;
         char c;
         if(unArcInt(suq, namLen)   != 0)              return NADAfunc("nameLen");
         for(int i=0; i<namLen; i++) {
            if(unArcChar(suq, c)                   < 0) return NADAfunc("char in m_ssArcFileName");
            m_ssArcFileName += c;
         } // for(int i=0; i<namLen; i++)

         int nOnes = 0;
         if(unArcInt(suq, nOnes)   != 0)              return NADAfunc("nOnes");

         if(nOnes < 0)                                return NADAfunc("nOnes < 0");

         m_vcOnes.resize(nOnes);

         for(int i=0; i<nOnes; i++) {
            // qprintf(fp_dbg, "unArcJarq() calling unArcMe().\n");
            JarqOne &jaThis = m_vcOnes[i];
            int j = jaThis.unArcMe(irevSaved, suq);

            // qprintf(fp_dbg, "unArcJarq() back from unArcMe().\n");
            iRet += j;
         } // for(int i=0; i<nOnes; i++)

         }
         break;
      default:
         iRet = NADA;
         break;
   } // switch(irevSaved)

   return iRet;
} // int JarqAnch::unArcJarq(int &irevSaved, sucqObj &suq)




//
// new 07-31-23
//
//
//
//
//
// qxarcAppend()

int JarqAnch::arcAppend(const char *pcExisting) {
   if(pcExisting == NULL) {
      qprintf(fp_dbg, "NULL pcExisting.\n");
      qprintf(fp_std, "NULL pcExisting.\n");
      return NADAfunc("JarqAnch::writeJarq(): NULL pcExisting.");
   } // if(pcExisting == NULL)

   qprintf(fp_dbg, "Entering JarqAnch()::arcAppend(), to file \"%s\".\n", pcExisting);
   qprintf(fp_std, "Entering JarqAnch()::arcAppend(), to file \"%s\".\n", pcExisting);

   // arcMeDotty(pcExisting);  // not here, idiot!

   int ix = 0;
   FILE *fpIO(NULL);
#ifdef _WIN32
   if((ix = fopen_s(&fpIO, pcExisting, "r+b")) != 0) {
      qprintf(fp_dbg, "Unable to open output file %s; got code %d, fpIO = %p.\n", pcExisting, ix, fpIO);
      qprintf(fp_std, "Unable to open output file %s; got code %d, fpIO = %p.\n", pcExisting, ix, fpIO);
      return NADAfunc("JarqAnch::arcAppend(): unable to open output file.");
   }
#else
   if((fpIO = fopen(pcExisting, "r+b")) == NULL) {
      qprintf(fp_dbg, "Unable to open output file %s; fpIO = %p.\n", pcExisting, fpIO);
      qprintf(fp_std, "Unable to open output file %s; fpIO = %p.\n", pcExisting, fpIO);
      return NADAfunc("JarqAnch::arcAppend(): unable to open output file.");
   }
#endif // !_WIN32

   spitObj mySpit(fpIO, NULL);

   int irevSaved;
   sucqObj suq(fpIO, NULL, NULL);

   int iTest;
   if(unArcInt(suq, iTest)     != 0) {fclose(fpIO); return NADAfunc("iTest 2");}
   if((iTest != ARC_JARQ) && (iTest != OLD_JARQ))  {fclose(fpIO); return NADAfnc2("JarqAnch::unArcJarq(): invalid header: %04X", iTest);}

   int locNguys = NADA;

   // get the revision the archive was saved under:
   if(unArcInt(suq, irevSaved) != 0) return NADAfunc("irevSaved");


   std::string ssArcFileLy;
   int nOnes = 0;

   switch(irevSaved) {
      case 2:
      case 1:
      case 0:
         {

         int namLen = 0;
         char c;
         if(unArcInt(suq, namLen)   != 0)              return NADAfunc("nameLen");
         for(int i=0; i<namLen; i++) {
            if(unArcChar(suq, c)                   < 0) return NADAfunc("char in ssArcFileLy");
            ssArcFileLy += c;
         } // for(int i=0; i<namLen; i++)

         locNguys = ftell(fpIO);

         if(unArcInt(suq, nOnes)   != 0)              return NADAfunc("nOnes");

         }
         break;
      default:
         qprintf(fp_dbg, "Invalid irev in JarqOne::unArqMe(): %d.\n", irevSaved);
         qprintf(fp_std, "Invalid irev in JarqOne::unArqMe(): %d.\n", irevSaved);
         return NADAfunc("JarqAnch::arcAppend(): invalid irev.");
         break;
   } // switch(irevSaved)

   // qprintf(fp_dbg, "locNguys = %d; nOnes = %d; irevSaved %d.\n", locNguys, nOnes, irevSaved);  // qxtemp

   int newNones = nOnes;


   fseek(fpIO, 0, SEEK_END);

   for(int i=0; i<(int)m_vcOnes.size(); i++) {
      const JarqOne &aJO = m_vcOnes[i];
      int j = aJO.archiveMe(irevSaved, mySpit);
      if(j >= 0)
         newNones++;
   } // for(int i=0; i<(int)m_vcOnes.size(); i++)

   if(newNones > nOnes) {
      fseek(fpIO, locNguys, SEEK_SET);
      if(archiveInt(mySpit,  newNones)     < 0) {fclose(fpIO); return NADAfunc("newNones");}
   } // if(newNguys > locNguys)

   fclose(fpIO); fpIO = NULL;

   qprintf(fp_dbg, "We made the file go from %d to %d inside it.\n", nOnes, newNones);
   qprintf(fp_std, "We made the file go from %d to %d inside it.\n", nOnes, newNones);

   return 0;
} // int JarqAnch::arcAppend(const char *pcExisting)




//
// 12-12-24: static JarqAnch::creApp(): create or append to archive file, using list of files to be archived.
//
// Our claim to fame is to minimize RAM use: just suck and spit one file at a time.
//
// Input:
//    ssArcFile:   name of archive file (probably [something].jrq).
//    vecSrcFiles: every file we want to include in the archive.  No wilds (resolve these upstream).
//    iFormat:     0: uncompressed.  1: compressed.
//    bAppend:     false: start new archive file; true: append to existing file.
//
//
// qxcreApp()
//

int JarqAnch::creApp(const std::string &ssArcFile, const std::vector<std::string> &vecSrcFiles, int iFormat, bool bAppend) {
   if(!bAppend) {
      // archive any existing file and start over.
      arcMeDotty(ssArcFile.c_str());

      JarqAnch jaEmpty;
      jaEmpty.writeJarq(ssArcFile.c_str());
   } // if(!bAppend)

   const char *pcExisting = ssArcFile.c_str();

   int ix = 0;
   FILE *fpIO(NULL);
#ifdef _WIN32
   if((ix = fopen_s(&fpIO, pcExisting, "r+b")) != 0) {
      qprintf(fp_dbg, "Unable to open output file %s; got code %d, fpIO = %p.\n", pcExisting, ix, fpIO);
      qprintf(fp_std, "Unable to open output file %s; got code %d, fpIO = %p.\n", pcExisting, ix, fpIO);
      return NADAfunc("JarqAnch::arcAppend(): unable to open output file.");
   }
#else
   if((fpIO = fopen(pcExisting, "r+b")) == NULL) {
      qprintf(fp_dbg, "Unable to open output file %s; fpIO = %p.\n", pcExisting, fpIO);
      qprintf(fp_std, "Unable to open output file %s; fpIO = %p.\n", pcExisting, fpIO);
      return NADAfunc("JarqAnch::arcAppend(): unable to open output file.");
   }
#endif // !_WIN32

   fileCloser afc(&fpIO);

   spitObj mySpit(fpIO, NULL);

   int irevSaved;
   sucqObj suq(fpIO, NULL, NULL);

   int iTest;
   if(unArcInt(suq, iTest)     != 0) {return NADAfunc("iTest 3");}
   if((iTest != ARC_JARQ) && (iTest != OLD_JARQ))  {return NADAfnc2("JarqAnch::unArcJarq(): invalid header: %04X", iTest);}

   int locNguys = NADA;

   // get the revision the archive was saved under:
   if(unArcInt(suq, irevSaved) != 0) return NADAfunc("irevSaved");


   std::string ssArcFileLy;
   int nOnes = 0;

   switch(irevSaved) {
      case 2:
      case 1:
      case 0:
         {

         int namLen = 0;
         char c;
         if(unArcInt(suq, namLen)   != 0)              return NADAfunc("nameLen");
         for(int i=0; i<namLen; i++) {
            if(unArcChar(suq, c)                   < 0) return NADAfunc("char in ssArcFileLy");
            ssArcFileLy += c;
         } // for(int i=0; i<namLen; i++)

         locNguys = ftell(fpIO);

         if(unArcInt(suq, nOnes)   != 0)              return NADAfunc("nOnes");

         }
         break;
      default:
         qprintf(fp_dbg, "Invalid irev in JarqOne::unArqMe(): %d.\n", irevSaved);
         qprintf(fp_std, "Invalid irev in JarqOne::unArqMe(): %d.\n", irevSaved);
         return NADAfunc("JarqAnch::arcAppend(): invalid irev.");
         break;
   } // switch(irevSaved)

   // qprintf(fp_dbg, "locNguys = %d; nOnes = %d; irevSaved %d.\n", locNguys, nOnes, irevSaved);  // qxtemp

   int newNones = nOnes;


   fseek(fpIO, 0, SEEK_END);

   for(int ifil=0; ifil<(int)vecSrcFiles.size(); ifil++) {
      const unsigned char *pcCandidate = (const unsigned char *)vecSrcFiles[ifil].c_str();

      // ajAnch.addOne((const char *)pcCandidate, iFormat);
      JarqOne aJO;
      int i = aJO.readOne((const char *)pcCandidate, iFormat);
      if(i < 0)
         continue;

      int j = aJO.archiveMe(irevSaved, mySpit);
      if(j >= 0)
         newNones++;

   } // for(int ifil=0; ifil<(int)vecSrcFiles.size(); ifil++)

   if(newNones > nOnes) {
      fseek(fpIO, locNguys, SEEK_SET);
      if(archiveInt(mySpit,  newNones)     < 0) {fclose(fpIO); return NADAfunc("newNones");}
   } // if(newNguys > locNguys)

   fclose(fpIO); fpIO = NULL;    // optional: we have a fileCloser in play.


   return 0;
} // int JarqAnch::creApp()...



//
//
// 12-12-24: static JarqAnch::extLst(): extract or list from archive file.
//
// Our claim to fame is to minimize RAM use: just suck and spit one file at a time.
//
// Input:
//    ssArcFile:   name of archive file (probably [something].jrq).
//    vcFiles:     optional list of file names (wilds ok) to include.
//    vcExclude:   optional list of file names (wilds ok) to exclude.
//    bList:       false: extract.  true: just list.
//    bVerbose:    if true and bList, add info for each file listed.
//    bRaw:        if true and bList, add raw permissions info.
//
//
//
//
//
// qxextLst()
//

int JarqAnch::extLst(const std::string &ssArcFile, const std::vector<std::string> &vcFiles, const std::vector<std::string> &vcExclude, bool bList, bool bVerbose, bool bRaw) {

   const char *cpFileIn = ssArcFile.c_str();

#ifdef _WIN32
   struct _stat64  stbu64;    // for big file sizes (>2^31).
   if(_stat64(cpFileIn, &stbu64) == -1) {
#else
   struct stat64  stbu64;    // for big file sizes (>2^31).
   if(stat64(cpFileIn, &stbu64) == -1) {
#endif // !_WIN32
      qprintf(fp_dbg, "Unable to find file %s.\n", cpFileIn);
      qprintf(fp_std, "Unable to find file %s.\n", cpFileIn);
      return NADAfunc("JarqAnch::extLst(): Unable to find cpFileIn");
   } // if(_stat64(cpFileIn, &stbu64) == -1)

   if((stbu64.st_mode & S_IFMT) == S_IFDIR) 
      return NADAfunc("JarqAnch::extLst(): file is a directory");

   long long totLen = (long long)stbu64.st_size;

   FILE *fpIn(NULL);
#ifdef _WIN32
   if(fopen_s(&fpIn, cpFileIn, "rb") != 0) {
#else
   if((fpIn = fopen(cpFileIn, "rb")) == NULL) {
#endif // !_WIN32
      qprintf(fp_dbg, "Unable to open file %s.\n", cpFileIn);
      qprintf(fp_std, "Unable to open file %s.\n", cpFileIn);
      return NADAfunc("JarqAnch::readJarq(): unable to open file.");
   }

   fileCloser afc(&fpIn);

   int irevSaved;
   sucqObj suq(fpIn, NULL, NULL);

   // int iRet = this->unArcJarq(irevSaved, suq);

   // fclose(fpIn);    fpIn = NULL;


   // verify that this really is an Jarq archive:
   int iTest;
   if(unArcInt(suq, iTest)     != 0) return NADAfunc("iTest 4");
   if((iTest != ARC_JARQ) && (iTest != OLD_JARQ))  return NADAfnc2("JarqAnch::extLst(): invalid header: %04X", iTest);

   // get the revision the archive was saved under:
   if(unArcInt(suq, irevSaved) != 0) return NADAfunc("irevSaved");

   int nOnes = 0;
   std::string ssArcFileName;
   int iRet  = 0;

   switch(irevSaved) {
      case 2:
      case 1:
      case 0:
         {

         int namLen = 0;
         char c;
         if(unArcInt(suq, namLen)   != 0)              return NADAfunc("namLen");
         for(int i=0; i<namLen; i++) {
            if(unArcChar(suq, c)                   < 0) return NADAfunc("char in ssArcFileName");
            ssArcFileName += c;
         } // for(int i=0; i<namLen; i++)

         // int nOnes = 0;
         if(unArcInt(suq, nOnes)   != 0)              return NADAfunc("nOnes");

         if(nOnes < 0)                                return NADAfunc("nOnes < 0");

         // m_vcOnes.resize(nOnes);

#ifdef MOVED_DOWN
         for(int i=0; i<nOnes; i++) {
            // qprintf(fp_dbg, "unArcJarq() calling unArcMe().\n");
            JarqOne &jaThis = m_vcOnes[i];
            int j = jaThis.unArcMe(irevSaved, suq);

            // qprintf(fp_dbg, "unArcJarq() back from unArcMe().\n");
            iRet += j;
         } // for(int i=0; i<nOnes; i++)
#endif // MOVED_DOWN

         }
         break;
      default:
         iRet = NADA;
         break;
   } // switch(irevSaved)

   for(int i=0; i<nOnes; i++) {
      // qprintf(fp_dbg, "unArcJarq() calling unArcMe().\n");
      JarqOne jaThis;
      int j = jaThis.unArcMe(irevSaved, suq);

      if(j < 0)
         continue;


      const char *pcFull = jaThis.getFileName();
      int iLen = (int)strlen(pcFull);
      bool bDir = ((iLen > 0) && (is_slash(pcFull[iLen-1])));



      SMARTA(pcDir, char, (iLen + 32), smaDir);
      SMARTA(pcFil, char, (iLen + 32), smaFil);

      if(bDir) {
         cut_file_name(pcFull, pcDir, pcFil);       // qxl: does this make sense?
      } else {
         cut_file_name(pcFull, pcDir, pcFil);
      } // !if(bDir)

      bool bSkip = false;

      // if this file is on the excluded list, that overrides everything:
      for(int iexcl=0; iexcl<(int)vcExclude.size(); iexcl++) {
         const unsigned char *pcExcl = (const unsigned char *)vcExclude[iexcl].c_str();
         if(match(pcExcl, (const unsigned char *)pcFil, getLwc())) {
            bSkip = true;
            break;
         } // if(match(pcExcl, pcCandidate, getLwc()))
      } // for(int iexcl=0; iexcl<(int)vcExclude.size(); iexcl++)

      if(bSkip)
         continue;


      if(vcFiles.size() == 0) {
         // everybody's in; bSkip is already false.
      } else {
         bSkip = true;
         for(int ispec=0; ispec<(int)vcFiles.size(); ispec++) {
            const unsigned char *pcSpec = (const unsigned char *)vcFiles[ispec].c_str();
            if(match(pcSpec, (const unsigned char *)pcFil, getLwc())) {
               bSkip = false;
               break;
            } // if(match(pcSpec, pcFil, getLwc()))
         } // for(int ispec=0; ispec<(int)vcFiles.size(); ispec++)
      } // !if(vcFiles.size() == 0)

      if(bSkip)
         continue;

      if(bList) {
         // list only.
         // const char *pcFull = pOne->getFileName();

         if(bVerbose) {
            char cdate[256];
            date_format(jaThis.getMtime(), cdate);

            char clenn[256] = "";                       // blank size for directories.
            if(bitTest(jaThis.getMode(), 14) == 0) {
               // not a directory; show size:
               comma_me(jaThis.getLen(), clenn, 256);
            } // if(bitTest(jaThis.getMode(), 14) == 0)

            std::string ssMode = formatMode(jaThis.getMode(), bRaw);

            fprintf(stdout, "%6s   %s %15s   %s\n", ssMode.c_str(), cdate, clenn, pcFull);
         } else {
            fprintf(stdout, "%s\n", pcFull);
         } // !if(bVerbose)

      } else {
         // extract.

         std::vector<std::string> vcErrsOut;

         jaThis.writeOne(NULL, &vcErrsOut);

         if(vcErrsOut.size() > 0) {
            fprintf(stdout, "\nErrors:\n\n");
            for(int i=0; i<(int)vcErrsOut.size(); i++) {
               const std::string &ssLine = vcErrsOut[i];
               fprintf(stdout, "%s\n", ssLine.c_str());
            } // for(int i=0; i<(int)vcErrsOut.size(); i++)
         } // if(vcErrsOut.size() > 0)


      } // !if(bList)

      iRet += j;
   } // for(int i=0; i<nOnes; i++)





   return iRet;
} // int JarqAnch::extLst()...


// Begin code from
// https://www.experts-exchange.com/articles/3189/In-Memory-Compression-and-Decompression-Using-ZLIB.html


// qxGetMaxCompressedLen()

int GetMaxCompressedLen( int nLenSrc ) {
    int n16kBlocks = (nLenSrc+16383) / 16384; // round up any fraction of a block
    return ( nLenSrc + 6 + (n16kBlocks*5) );
}




// qxCompressData()

int CompressData( const BYTE* abSrc, int nLenSrc, BYTE* abDst, int nLenDst) {
    z_stream zInfo ={0};
    zInfo.total_in=  zInfo.avail_in=  nLenSrc;
    zInfo.total_out= zInfo.avail_out= nLenDst;
    zInfo.next_in= (BYTE*)abSrc;
    zInfo.next_out= abDst;

    int nErr, nRet= -1;
    nErr= deflateInit( &zInfo, Z_DEFAULT_COMPRESSION ); // zlib function
    if ( nErr == Z_OK ) {
        nErr= deflate( &zInfo, Z_FINISH );              // zlib function
        if ( nErr == Z_STREAM_END ) {
            nRet= zInfo.total_out;
        }
    }
    deflateEnd( &zInfo );    // zlib function
    return( nRet );
}




// qxUncompressData()

int UncompressData( const BYTE* abSrc, int nLenSrc, BYTE* abDst, int nLenDst ) {
    z_stream zInfo ={0};
    zInfo.total_in=  zInfo.avail_in=  nLenSrc;
    zInfo.total_out= zInfo.avail_out= nLenDst;
    zInfo.next_in= (BYTE*)abSrc;
    zInfo.next_out= abDst;

    int nErr, nRet= -1;
    nErr= inflateInit( &zInfo );               // zlib function
    if ( nErr == Z_OK ) {
        nErr= inflate( &zInfo, Z_FINISH );     // zlib function
        if ( nErr == Z_STREAM_END ) {
            nRet= zInfo.total_out;
        }
    }
    inflateEnd( &zInfo );   // zlib function
    return( nRet ); // -1 or len of output
}


// End code from
// https://www.experts-exchange.com/articles/3189/In-Memory-Compression-and-Decompression-Using-ZLIB.html



// my wrappers:



//
//
// Input:
//    vcFatIn: data to be compressed.
//    vcThinOut: waiting to be filled.  We ensure it has enough room.
//
// Output:
//    vcThinOut: filled, we hope, shrunk to needed size.
//    return value: inner call's return.
//
//
//
//
// qxCompressData()

int CompressData(const std::vector<unsigned char> &vcFatIn, std::vector<unsigned char> &vcThinOut) {
   int lenOutSafe = GetMaxCompressedLen((int)vcFatIn.size());
   vcThinOut.resize(lenOutSafe);

   int iRet = CompressData(&vcFatIn[0], (int)vcFatIn.size(), &vcThinOut[0], (int)vcThinOut.size());

   if(iRet > 0)
      vcThinOut.resize(iRet);

   return iRet;
} // int CompressData(const std::vector<unsigned char> &vcFatIn, std::vector<unsigned char> &vcThinOut)




//
// Input:
//    vcThinIn: data to be compressed.
//    vcFatOut: waiting to be filled.  MUST HAVE ENOUGH ROOM; we do not do this.
//
// Output:
//    vcFatOut: filled, we hope, shrunk to needed size.
//    return value: inner call's return.
//
//
//
//
/// qxUncompressData()

int UncompressData(const std::vector<unsigned char> &vcThinIn, std::vector<unsigned char> &vcFatOut) {

   int iRet = UncompressData(&vcThinIn[0], (int)vcThinIn.size(), &vcFatOut[0], (int)vcFatOut.size());

   if(iRet > 0)
      vcFatOut.resize(iRet);

   return iRet;
} // int UncompressData(const std::vector<unsigned char> &vcThinIn, std::vector<unsigned char> &vcFatOut)






