/** @file

    $Id$

    $Date$
    $Author$

    @copy &copy; Marc W&auml;ckerlin
    @license LGPL, see file <a href="license.html">COPYING</a>

    $Log$
    Revision 1.5  2005/03/02 22:03:08  marc
    some fixes for solaris

    Revision 1.4  2005/02/28 07:17:24  marc
    Dir is now usable and compilable, also added fixes for Solaris

    Revision 1.3  2005/02/18 15:52:20  marc
    correection in documentation

    Revision 1.2  2005/01/28 07:49:32  marc
    Save configuration using file.hxx

    Revision 1.1  2005/01/07 00:31:38  marc
    initial version


    1         2         3         4         5         6         7         8
    5678901234567890123456789012345678901234567890123456789012345678901234567890
    */
#ifndef __MRW_FILE_HPP__
#define __MRW_FILE_HPP__

#include <mrw/exception.hxx>
#include <mrw/errno.hxx>
#include <string>
#include <fstream>
#include <sys/types.h>
#include <dirent.h>
#include <cstddef> // offsetof
#include <cstdlib> // abort
#include <unistd.h> // unlink

namespace mrw {

  /** @defgroup sysFile File and System Utilities

      @brief By now, some utilies for file handling, i.e. a copy command.

      Some file and system specific utilities are missing in C++ and
      often there is even no good alternative in C libraries.
      */
  //@{

  //============================================================================
  /** @brief File handling utilities.

      Utilities for file access in C++.
      */
  class File {

      //................................................................ methods
    public:
    
      /** @brief Copy a file.

          Copy a file to a new location.
        
          First the source file is fully read into memory, then written
          to the destination. If the source file cannot be fully read,
          the destination is left untouched.

          @throw mrw::invalid_argument if read or write fails
          @param from the source file name
          @param to the destination file name
          @note If the destination file already exists, it will be overwritten.
          */
      static void copy(const std::string& from,
                       const std::string& to) throw(std::exception) {
        std::ofstream os(to.c_str());
        os<<read(from);
        if (!os) throw mrw::invalid_argument("Cannot write file: '"+to+"'");
      }
    
      /** @brief Read a file fully into memory.

          Read a file fully into memory.
        
          @throw mrw::invalid_argument if read fails
          @param filename the file to read
          @return the file's contents
          */
      static std::string read(const std::string& filename)
              throw(std::exception) {
        std::string contents; // declare on top to allow compiler optimization
        std::ifstream is(filename.c_str());
        std::string::size_type sz(size(is, filename));
        contents.resize(sz); // make buffer long enough to store all
        is.read(contents.begin().operator->(), sz); // hack to get the buffer
        if (!is.good() && is.eof())
          throw mrw::invalid_argument("Cannot read file: '"+filename+"'");
        return contents;
      };
    
      /** @brief Save a string to a file.

          A string is stored in a file, the whole file is overwritten by
          the contents of the string. This is the counterpart of
          read(const std::string&).

          @throw mrw::invalid_argument if write fails
          @param filename the name of the output file
          @param contents the text to be written to the file
          */
      static void save(const std::string& filename, const std::string& contents)
              throw(std::exception) {
        std::ofstream file(filename.c_str());
        if (!(file<<contents))
          throw mrw::invalid_argument("Cannot write file '"+filename+"'");
      }
    
      /** @brief Get the size of a file

          Get the size of a file.
        
          @throw mrw::invalid_argument if read fails
          @param filename the file name
          @return the file's size
          */
      static std::string::size_type size(const std::string& filename)
              throw(std::exception) {
        std::ifstream is(filename.c_str());
        if (!is)
          throw mrw::invalid_argument("Cannot get size of file: '"
                                      +filename+"'");
        is.seekg(0, std::ios::end);
        if (!is)
          throw mrw::invalid_argument("Cannot get size of file: '"
                                      +filename+"'");
        return is.tellg();
      }
    
      /** @brief Get the size of a file

          Get the size of a file.
        
          @throw mrw::invalid_argument if read fails
          @param file the filestream
          @param filename the name of the file (required for the exception)
          @return the file's size
          */
      static std::string::size_type size(std::ifstream& file,
                                         const std::string& filename = "")
              throw(std::exception) {
        if (!file)
          throw mrw::invalid_argument("Cannot get size of file: '"
                                      +filename+"'");
        file.seekg(0, std::ios::end);
        std::string::size_type sz(file.tellg());
        file.seekg(0, std::ios::beg);
        if (!file)
          throw mrw::invalid_argument("Cannot get size of file: '"
                                      +filename+"'");
        return sz;
      }

      /** @brief Remove a file

          Remove a file

          @throw mrw::unix_error in case of failure,
                                 i.e. if the file does not exist
          @param file the file name
          */
      static void remove(const std::string& file) throw(std::exception) {
        if (unlink(file.c_str()))
          throw mrw::unix_error("Cannot remove file "+file);
      }
  };

  //============================================================================
  /** @brief Directory access

      Parse through directories:
      
      @code
      mrw::Dir dir(mrw::ifelse(getenv("HOME"), ""));
      while (dir) {
        std::cout<<"Found ["<<dir().typestr()<<"] "<<dir().name()<<std::endl;
      }
      @endcode
      */
  class Dir {

      //............................................................... typedefs
    public:
      
      //------------------------------------------------------------------ Entry
      /** @brief a directory entry

          A Directory Entry. */
      class Entry {

          //........................................................... typedefs
        public:

          /** @brief The type of the actual file.

              The type of the actual file.
              
              @note On some systems UNKNOWN is the only
                    value returned. This is i.e. true on
                    LINUX...
              */
          enum FileType {
#ifdef _DIRENT_HAVE_D_TYPE
            UNKNOWN = DT_UNKNOWN, ///< The type is unknown.
            REGULAR = DT_REG,     ///< A regular file.
            DIR     = DT_DIR,     ///< A directory.
            FIFO    = DT_FIFO,    ///< A named pipe, or FIFO.
            SOCKET  = DT_SOCK,    ///< A local-domain socket.
            CHAR    = DT_CHR,     ///< A character device.
            BLOCK   = DT_BLK      ///< A block device.
#else
            UNKNOWN, REGULAR, DIR, FIFO, SOCKET, CHAR, BLOCK // dummy
#endif
          };

          //............................................................ methods
        public:

          Entry(): _new(true) {}

          /// Get the name of the actual file.
          operator const std::string&() const throw(std::bad_exception) {
            if (_new) { // only convert to string once
              _new = false;
              _name = _dirent.d.d_name;
            }
            return _name;
          }

          /// Get the name of the actual file.
          const std::string& name() const throw(std::bad_exception) {
            if (_new) { // only convert to string once
              _new = false;
              _name = _dirent.d.d_name;
            }
            return _name;
          }

          /// Get the name of the actual file.
          const std::string& operator()() throw(std::bad_exception) {
            return *this;
          }

          /// Get the type of the actual file.
          FileType type() const throw() {
#ifdef _DIRENT_HAVE_D_TYPE
            return FileType(_dirent.d.d_type);
#else
            return UNKNOWN;
#endif
          }

          /// Get the type of the actual file as string.
          const std::string& typestr() const throw() {
            static const std::string UNKNOWN_("UNKNOWN");
            static const std::string REGULAR_("REGULAR");
            static const std::string DIR_("DIR");
            static const std::string FIFO_("FIFO");
            static const std::string SOCKET_("SOCKET");
            static const std::string CHAR_("CHAR");
            static const std::string BLOCK_("BLOCK");
#ifdef _DIRENT_HAVE_D_TYPE
            switch (_dirent.d.d_type) {
              case UNKNOWN: return UNKNOWN_;
              case REGULAR: return REGULAR_;
              case DIR: return DIR_;
              case FIFO: return FIFO_;
              case SOCKET: return SOCKET_;
              case CHAR: return CHAR_;
              case BLOCK: return BLOCK_;
            }
            abort(); // this line is never reached
#else
            return UNKNOWN_;
#endif
          }
          //.......................................................... variables
        private:

          friend class Dir;

#ifdef NAME_MAX
          union { // for portability reasons, see "info readdir_r"
              dirent d;
              char b[offsetof (struct dirent, d_name) + NAME_MAX + 1];
          } _dirent;
#else
          union { // for portability reasons, see "info readdir_r"
              dirent d;
              char b[offsetof (struct dirent, d_name) + 1000 + 1];
          } _dirent;
#endif

          mutable bool _new;
          mutable std::string _name;

          //............................................................ methods
        private:

          dirent& entry() throw() {
            _new = true;
            return _dirent.d;
          }
      };
      //------------------------------------------------------------------------

      //................................................................ methods
    public:

      /** @brief Open a directory for reading the file list.

          Open a directory for reading the file list.

          @throw mrw::invalid_argument if directory cannot be opened
          @param dir name of the directory to open
          @param ignoreDots ignore directories <code>.</code> and
                            <code>..</code> (self anf top)
          */
      Dir(const std::string& dir, bool ignoreDots=true) throw(std::exception):
          _ignoreDots(ignoreDots), _dir(opendir(dir.c_str())) {
        if (!_dir)
          throw mrw::invalid_argument("Cannot read directory: '"+dir+'\'');
      }

      /// Directory is closed automatically in the destructor.
      ~Dir() throw() {
        closedir(_dir);
      }

      /** @brief Advance to the next directory entry.

          Advance to the next directory entry.

          @code
          mrw::Dir dir("/home");
          while (dir) ...
          @endcode

          @return
            - @c true if an entry has been found
            - @c false if there are no more entries left

          @warning If you call this method again after @c false was
                   returned, behaviour is unspecified, but a crash
                   is probable.
          */
      operator bool() throw() {
        static const std::string D("."), DD("..");
        static dirent* fake;
#       if defined(WIN32) || defined(_WIN32) || defined(__MINGW32__)
        if (!(fake=readdir(_dir))) return false;
        _entry.entry()=*fake;
#       else
          if (readdir_r(_dir, &_entry.entry(), &fake) || !fake) return false;
#       endif
        if (_ignoreDots
            && (_entry.type()==Entry::UNKNOWN || _entry.type()==Entry::DIR)
            && (D==_entry() || DD==_entry()))
          return operator bool();
        return true;
      }

      /** @brief Get the actual directory entry.

          Returns the actual directory entry.

          @code
          mrw::Dir dir("/home");
          while (dir) {
            mrw::Dir::Entry entry(dir());
            ...
          }
          @endcode

          @return the actual directory entry

          @warning You must first call operator bool(), otherwise the
                   behaviour is undefined, but a crash is probable.
          */
      const Entry& operator()() {
        return _entry;
      }

      //.............................................................. variables
    private:

      bool _ignoreDots;
      DIR* _dir;
      Entry _entry;
      
  };
  
  //@}
}

#endif
