<commit_before>/* 
 * File:   FileIO.cpp
 * Author: kjell/weberr13
 * 
 * https://github.com/weberr13/FileIO
 * Created on August 15, 2013, 2:28 PM
 */



#include "FileIO.h"
#include <mutex>
#include <sstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <memory>

namespace FileIO {
   std::mutex mPermissionsMutex;
   volatile int* gFlagInterrupt = nullptr;
   
   
   /**
    * Makes it possible to set an interrupt handler flag to break possibly
    * lengthy operations such as MoveFile or CleanDirectoryOfFileContents
    * @param interrupted interrupt handler flag
    */
   void SetInterruptFlag(volatile int* interrupted) {
      gFlagInterrupt = interrupted;
   }
   
   /**
    * @return true if interrupt flag is initialized AND set to signal an interrupt
    */
   bool Interrupted() {
      if (nullptr == gFlagInterrupt) {
         return false;
      }
      
      return (*gFlagInterrupt > 0);
   }
   
   
   /**
    * Reads the stats for the path to see if it is a mountpoint
    * @param pathToDirectory that may be a mountpoint
    * @param returns Result<true> if it is a mountpoint
    */
   Result<bool> IsMountPoint(const std::string& pathToDirectory) {
      // A mount point must be a block-device, folder and with a separate device-ID from its ".." parent
      
      struct stat info;
      if (0 != stat(pathToDirectory.c_str(), &info)) {
         return Result<bool>{false, {"Cannot stat read location: " + pathToDirectory}};
      }
      
      if (!S_ISDIR(info.st_mode)) {
         return Result<bool>{false, {"Directory + [" + pathToDirectory + "] does not exist"}};
      }       
      
      std::string parentPath = pathToDirectory;
      if('/' != parentPath.back()){
         parentPath.append("/..");
      } else {
         parentPath.append("..");   
      }
     
      struct stat parent; 
      if (0 != stat(parentPath.c_str(), &parent)) {
         return Result<bool>{false, {"Cannot stat read parent location: [" + parentPath +"]"}};
      }
      
      //  st_dev: device number. st_ino: inode number
      const bool isMountPoint = (info.st_dev != parent.st_dev) || (info.st_dev == parent.st_dev && info.st_ino == parent.st_ino);
      if (!isMountPoint) {
         return Result<bool>{false, {"[" + parentPath + "] is not a mountpoint"}};
      }
      
      return Result<bool>{true};
   }

  /**
    * Reads content of binary  file
    * @param pathToFile to read
    * @return Result<std::vector<uint8_t>> all the content of the file, and/or an error string 
    *         if something went wrong 
    */
   Result<std::vector<char>> ReadBinaryFileContent(const std::string& pathToFile) {
      std::ifstream in(pathToFile, std::ifstream::binary);
      if (!in) {
         std::string error{"Cannot read-open file: "};
         error.append(pathToFile);
         return Result<std::vector<char>>{{}, error};
      }

      std::vector<char> contents;
      in.seekg(0, std::ios::end);
      auto end = in.tellg();

      // Attempt to read it the fastest way possible.
      if (-1 != end) {  // tellg() --> pos_type{-1} if reading the end failed.
         contents.resize(end);
         in.seekg(0, std::ios::beg);
         in.read(&contents[0], contents.size());
         in.close();
         return Result<std::vector<char>>{contents};
      }
      // Could not calculate with ifstream::tellg(). Is it a RAM file? 
      // Fallback solution to slower iteratator approach
      contents.assign((std::istreambuf_iterator<char>(in)),
              (std::istreambuf_iterator<char>()));
      in.close();
      return Result<std::vector<char>>{contents};
   }


   /**
    * Reads content of Ascii file
    * @param pathToFile to read
    * @return Result<std::string> all the content of the file, and/or an error string 
    *         if something went wrong 
    */
   Result<std::string> ReadAsciiFileContent(const std::string& pathToFile) {
      auto result = ReadBinaryFileContent(pathToFile);
      if(result.HasFailed()) {
        return Result<std::string>{{}, result.error};
      }

      std::string contents(result.result.data(), result.result.size());
      return Result<std::string>{contents};
    }



/**
* Write the serialized date to the given filename
* @param filename
* @param serialized
* @return whether or not the write was successful
*/
   Result<bool> WriteAppendBinaryFileContent(const std::string & filename, const std::vector<char>& content) {
      std::fstream outputFile;
      outputFile.open(filename.c_str(), std::fstream::out | std::fstream::binary | std::fstream::app);
      const bool openStatus = outputFile.is_open();
      std::shared_ptr<void> fileCloser(nullptr, [&](void *) { // RAII file close
         if (openStatus) {
            outputFile.close();
         }
      }); //fileCloser RAII

      std::string error = {"Unable to write test data to file: "};
      error.append(filename);

      if (!openStatus) {
         return Result<bool> {false, error};
      }

      //  FYI: thread_local must be trivial to initialize:
      //   http://coliru.stacked-crooked.com/view?id=6717cbf5974c0e5c
      const static int kOneMbBuffer = 1024 * 1024;
      thread_local char buffer[kOneMbBuffer];
      outputFile.rdbuf()->pubsetbuf(buffer, kOneMbBuffer);
      outputFile.exceptions(std::ios::failbit | std::ios::badbit); // trigger exception if error happens
      try {
         outputFile.write(&content[0], content.size());
      } catch(const std::exception& e) {
        error.append(". Writing triggered exception: ").append(e.what());
        return Result<bool>{false, error};
      }

      return Result<bool>{true};
   }



   /** 
    * A generic write function that supports a variety of modes
    * @param pathToFile
    * @param content
    * @param mode, std::ios::app or std::ios::trunc
    * @return 
    */
   Result<bool> WriteFileContentInternal(const std::string& pathToFile, const std::string& content, std::ios_base::openmode mode) {
      std::ofstream out(pathToFile, std::ios::out | mode);
      if (!out) {
         std::string error{"Cannot write-open file: "};
         error.append(pathToFile);
         return Result<bool>{false, error};
      }

      out << content;
      out.close();
      return Result<bool>{true};
   }

   /**
    * Write ascii content to file
    * @param pathToFile to write
    * @param content to write to file
    * @return Result<bool> result if operation whent OK, if it did not the Result<bool>::error string 
    *         contains the error message
    */
   Result<bool> WriteAsciiFileContent(const std::string& pathToFile, const std::string& content) {

      return WriteFileContentInternal(pathToFile, content, std::ios::trunc);
   }

   /**
    * Write ascii content to the end of a file
    * @param pathToFile to write
    * @param content to write to file
    * @return Result<bool> result if operation whent OK, if it did not the Result<bool>::error string 
    *         contains the error message
    */
   Result<bool> AppendWriteAsciiFileContent(const std::string& pathToFile, const std::string& content) {
      return WriteFileContentInternal(pathToFile, content, std::ios::app);
   }

   /**
    * Use stat to determine the presence of a file
    * @param pathToFile<commit_msg>Added TWO RAII file close<commit_after>/* 
 * File:   FileIO.cpp
 * Author: kjell/weberr13
 * 
 * https://github.com/weberr13/FileIO
 * Created on August 15, 2013, 2:28 PM
 */



#include "FileIO.h"
#include <mutex>
#include <sstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <memory>

namespace FileIO {
   std::mutex mPermissionsMutex;
   volatile int* gFlagInterrupt = nullptr;
   
   
   /**
    * Makes it possible to set an interrupt handler flag to break possibly
    * lengthy operations such as MoveFile or CleanDirectoryOfFileContents
    * @param interrupted interrupt handler flag
    */
   void SetInterruptFlag(volatile int* interrupted) {
      gFlagInterrupt = interrupted;
   }
   
   /**
    * @return true if interrupt flag is initialized AND set to signal an interrupt
    */
   bool Interrupted() {
      if (nullptr == gFlagInterrupt) {
         return false;
      }
      
      return (*gFlagInterrupt > 0);
   }
   
   
   /**
    * Reads the stats for the path to see if it is a mountpoint
    * @param pathToDirectory that may be a mountpoint
    * @param returns Result<true> if it is a mountpoint
    */
   Result<bool> IsMountPoint(const std::string& pathToDirectory) {
      // A mount point must be a block-device, folder and with a separate device-ID from its ".." parent
      
      struct stat info;
      if (0 != stat(pathToDirectory.c_str(), &info)) {
         return Result<bool>{false, {"Cannot stat read location: " + pathToDirectory}};
      }
      
      if (!S_ISDIR(info.st_mode)) {
         return Result<bool>{false, {"Directory + [" + pathToDirectory + "] does not exist"}};
      }       
      
      std::string parentPath = pathToDirectory;
      if('/' != parentPath.back()){
         parentPath.append("/..");
      } else {
         parentPath.append("..");   
      }
     
      struct stat parent; 
      if (0 != stat(parentPath.c_str(), &parent)) {
         return Result<bool>{false, {"Cannot stat read parent location: [" + parentPath +"]"}};
      }
      
      //  st_dev: device number. st_ino: inode number
      const bool isMountPoint = (info.st_dev != parent.st_dev) || (info.st_dev == parent.st_dev && info.st_ino == parent.st_ino);
      if (!isMountPoint) {
         return Result<bool>{false, {"[" + parentPath + "] is not a mountpoint"}};
      }
      
      return Result<bool>{true};
   }

  /**
    * Reads content of binary  file
    * @param pathToFile to read
    * @return Result<std::vector<uint8_t>> all the content of the file, and/or an error string 
    *         if something went wrong 
    */
   Result<std::vector<char>> ReadBinaryFileContent(const std::string& pathToFile) {
      std::ifstream in(pathToFile, std::ifstream::binary);
      if (!in) {
         std::string error{"Cannot read-open file: "};
         error.append(pathToFile);
         return Result<std::vector<char>>{{}, error};
      }

      std::shared_ptr<void> fileCloser(nullptr, [&](void *) { // RAII file close
         if (in) {
            in.close();
         }
      }); //fileCloser RAII


      std::vector<char> contents;
      in.seekg(0, std::ios::end);
      auto end = in.tellg();

      // Attempt to read it the fastest way possible.
      if (-1 != end) {  // tellg() --> pos_type{-1} if reading the end failed.
         contents.resize(end);
         in.seekg(0, std::ios::beg);
         in.read(&contents[0], contents.size());
         return Result<std::vector<char>>{contents};
      }
      // Could not calculate with ifstream::tellg(). Is it a RAM file? 
      // Fallback solution to slower iteratator approach
      contents.assign((std::istreambuf_iterator<char>(in)),
              (std::istreambuf_iterator<char>()));
      return Result<std::vector<char>>{contents};
   }


   /**
    * Reads content of Ascii file
    * @param pathToFile to read
    * @return Result<std::string> all the content of the file, and/or an error string 
    *         if something went wrong 
    */
   Result<std::string> ReadAsciiFileContent(const std::string& pathToFile) {
      auto result = ReadBinaryFileContent(pathToFile);
      if(result.HasFailed()) {
        return Result<std::string>{{}, result.error};
      }

      std::string contents(result.result.data(), result.result.size());
      return Result<std::string>{contents};
    }



/**
* Write the serialized date to the given filename
* @param filename
* @param serialized
* @return whether or not the write was successful
*/
   Result<bool> WriteAppendBinaryFileContent(const std::string & filename, const std::vector<char>& content) {
      std::fstream outputFile;
      outputFile.open(filename.c_str(), std::fstream::out | std::fstream::binary | std::fstream::app);
      const bool openStatus = outputFile.is_open();
      std::shared_ptr<void> fileCloser(nullptr, [&](void *) { // RAII file close
         if (openStatus) {
            outputFile.close();
         }
      }); //fileCloser RAII

      std::string error = {"Unable to write test data to file: "};
      error.append(filename);

      if (!openStatus) {
         return Result<bool> {false, error};
      }

      //  FYI: thread_local must be trivial to initialize:
      //   http://coliru.stacked-crooked.com/view?id=6717cbf5974c0e5c
      const static int kOneMbBuffer = 1024 * 1024;
      thread_local char buffer[kOneMbBuffer];
      outputFile.rdbuf()->pubsetbuf(buffer, kOneMbBuffer);
      outputFile.exceptions(std::ios::failbit | std::ios::badbit); // trigger exception if error happens
      try {
         outputFile.write(&content[0], content.size());
      } catch(const std::exception& e) {
        error.append(". Writing triggered exception: ").append(e.what());
        return Result<bool>{false, error};
      }

      return Result<bool>{true};
   }



   /** 
    * A generic write function that supports a variety of modes
    * @param pathToFile
    * @param content
    * @param mode, std::ios::app or std::ios::trunc
    * @return 
    */
   Result<bool> WriteFileContentInternal(const std::string& pathToFile, const std::string& content, std::ios_base::openmode mode) {
      std::ofstream out(pathToFile, std::ios::out | mode);
      if (!out) {
         std::string error{"Cannot write-open file: "};
         error.append(pathToFile);
         return Result<bool>{false, error};
      }

      std::shared_ptr<void> fileCloser(nullptr, [&](void *) { // RAII file close
         if (out) {
            out.close();
         }
      }); //fileCloser RAII

      out << content;
      return Result<bool>{true};
   }

   /**
    * Write ascii content to file
    * @param pathToFile to write
    * @param content to write to file
    * @return Result<bool> result if operation whent OK, if it did not the Result<bool>::error string 
    *         contains the error message
    */
   Result<bool> WriteAsciiFileContent(const std::string& pathToFile, const std::string& content) {

      return WriteFileContentInternal(pathToFile, content, std::ios::trunc);
   }

   /**
    * Write ascii content to the end of a file
    * @param pathToFile to write
    * @param content to write to file
    * @return Result<bool> result if operation whent OK, if it did not the Result<bool>::error string 
    *         contains the error message
    */
   Result<bool> AppendWriteAsciiFileContent(const std::string& pathToFile, const std::string& content) {
      return WriteFileContentInternal(pathToFile, content, std::ios::app);
   }

   /**
    * Use stat to determine the presence of a file
    * @param pathToFile
{'fraction_non_alphanumeric': 0.12956700484052783, 'fraction_numerical': 0.005437305218486838, 'mean_word_length': 2.7211941771527264, 'pattern_counts': {'":': 0, '<': 109, '<?xml version=': 0, '>': 111, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7341017', 'n_tokens_mistral': 4603, 'n_tokens_neox': 4447, 'n_words': 1250}