LCOV - code coverage report
Current view: top level - snapdev - file_contents.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 54 54 100.0 %
Date: 2021-08-22 18:14:51 Functions: 8 8 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2011-2021  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/snapdev
       4             : // contact@m2osw.com
       5             : //
       6             : // This program is free software; you can redistribute it and/or modify
       7             : // it under the terms of the GNU General Public License as published by
       8             : // the Free Software Foundation; either version 2 of the License, or
       9             : // (at your option) any later version.
      10             : //
      11             : // This program is distributed in the hope that it will be useful,
      12             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             : // GNU General Public License for more details.
      15             : //
      16             : // You should have received a copy of the GNU General Public License along
      17             : // with this program; if not, write to the Free Software Foundation, Inc.,
      18             : // 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      19             : #pragma once
      20             : 
      21             : // self
      22             : //
      23             : #include    <snapdev/mkdir_p.h>
      24             : 
      25             : 
      26             : // C++ lib
      27             : //
      28             : #include    <fstream>
      29             : #include    <ios>
      30             : 
      31             : 
      32             : // C lib
      33             : //
      34             : #include    <unistd.h>
      35             : 
      36             : 
      37             : 
      38             : namespace snap
      39             : {
      40             : 
      41             : 
      42             : 
      43             : class file_contents
      44             : {
      45             : public:
      46             :     /** \brief Initialize a content file.
      47             :      *
      48             :      * The constructor initialize the file content object with a filename.
      49             :      * The filename is used by the read_all() and write_all() functions.
      50             :      *
      51             :      * If the file_content is setup to be a temporary file, then the
      52             :      * destructor also makes use of the filename to delete the file
      53             :      * at that time. By default a file is not marked as temporary.
      54             :      *
      55             :      * \exception std::invalid_argument
      56             :      * The \p filename parameter cannot be an empty string.
      57             :      *
      58             :      * \exception std::ios::failure
      59             :      * The function checks whether all the folders exist. If not then the
      60             :      * file can't be create or read so there is no valid file_content()
      61             :      * possible with that path. This exception only occurs if the
      62             :      * \p create_missing_directories is true and the creation of any
      63             :      * of the directories fails.
      64             :      *
      65             :      * \param[in] filename  The name of the file to read and write.
      66             :      * \param[in] create_missing_directories  Whether to create missing directories
      67             :      *            as found in the path (see mkdir_p()).
      68             :      * \param[in] temporary  Whether the file is temporary.
      69             :      *
      70             :      * \sa snap::mkdir_p()
      71             :      */
      72          14 :     file_contents(
      73             :               std::string const & filename
      74             :             , bool create_missing_directories = false
      75             :             , bool temporary = false)
      76          14 :         : f_filename(filename)
      77          16 :         , f_temporary(temporary)
      78             :     {
      79          14 :         if(f_filename.empty())
      80             :         {
      81           1 :             throw std::invalid_argument("snapdev::file_contents: the filename of a file_contents object cannot be the empty string.");
      82             :         }
      83             : 
      84          13 :         if(create_missing_directories)
      85             :         {
      86           3 :             int const r(mkdir_p(f_filename, true));
      87           3 :             if(r != 0)
      88             :             {
      89           1 :                 throw std::ios::failure("snapdev::file_contents: the full path to filename for a file_contents object could not be created");
      90             :             }
      91             :         }
      92          12 :     }
      93             : 
      94             : 
      95             :     /** \brief Clean up as required.
      96             :      *
      97             :      * If the file_content was marked as temporary, then the destructor
      98             :      * deletes the file on disk before returning.
      99             :      *
     100             :      * If the file does not exist and it was marked as temporary, the
     101             :      * deletion will fail silently. Otherwise you get a warning in the
     102             :      * logs with more details about the error (i.e. permission, bad
     103             :      * filename, etc.)
     104             :      */
     105          12 :     ~file_contents()
     106          12 :     {
     107          12 :         if(f_temporary)
     108             :         {
     109           2 :             if(unlink(f_filename.c_str()) != 0)
     110             :             {
     111           1 :                 if(errno != ENOENT)
     112             :                 {
     113             :                     // TODO: what should we do here?!
     114             :                 }
     115             :             }
     116             :         }
     117          12 :     }
     118             : 
     119             : 
     120             :     /** \brief Retrieve the filename.
     121             :      *
     122             :      * This function returns a refrence to the filename used by this
     123             :      * object. Note that the filename cannot be modified.
     124             :      *
     125             :      * \return A reference to this file content filename.
     126             :      */
     127             :     std::string const & filename() const
     128             :     {
     129             :         return f_filename;
     130             :     }
     131             : 
     132             : 
     133             :     /** \brief Check whether this file exists.
     134             :      *
     135             :      * This function checks whether the file exists on disk. If the
     136             :      * read_all() function returns false and this function returns true,
     137             :      * then you probably do not have permissions to read the file or it
     138             :      * is a directory.
     139             :      *
     140             :      * \return true if the file exists and can be read.
     141             :      *
     142             :      * \sa read_all()
     143             :      */
     144             :     bool exists() const
     145             :     {
     146             :         return access(f_filename.c_str(), R_OK) == 0;
     147             :     }
     148             : 
     149             : 
     150             :     /** \brief Read the entire file in a buffer.
     151             :      *
     152             :      * This function reads the entire file in memory. It saves the data
     153             :      * in a buffer which you can get with the contents() function.
     154             :      * The file can be binary in which case remember that c_str()
     155             :      * and other similar function will not work right.
     156             :      *
     157             :      * The function may return false if the file could not be opened or
     158             :      * read in full.
     159             :      *
     160             :      * \return true if the file could be read, false otherwise.
     161             :      *
     162             :      * \sa write_all()
     163             :      * \sa contents() const
     164             :      * \sa last_error()
     165             :      */
     166           5 :     bool read_all()
     167             :     {
     168             :         // try to open the file
     169             :         //
     170          10 :         std::ifstream in;
     171           5 :         in.open(f_filename, std::ios::in | std::ios::binary);
     172           5 :         if(!in.is_open())
     173             :         {
     174           4 :             f_error = "could not open file \""
     175           4 :                 + f_filename
     176           6 :                 + "\" for reading.";
     177           2 :             return false;
     178             :         }
     179             : 
     180             :         // get size
     181             :         //
     182           3 :         in.seekg(0, std::ios::end);
     183           3 :         std::ifstream::pos_type const size(in.tellg());
     184           3 :         in.seekg(0, std::ios::beg);
     185           3 :         if(std::ifstream::pos_type(-1) == size)
     186             :         {
     187             :             f_error = "could not get size of file \""           // LCOV_EXCL_LINE
     188             :                 + f_filename                                    // LCOV_EXCL_LINE
     189             :                 + "\".";                                        // LCOV_EXCL_LINE
     190             :             return false;                                       // LCOV_EXCL_LINE
     191             :         }
     192             : 
     193             :         // resize the buffer accordingly
     194             :         //
     195             :         try
     196             :         {
     197           3 :             f_contents.resize(size, '\0');
     198             :         }
     199             :         catch(std::bad_alloc const & e)                         // LCOV_EXCL_LINE
     200             :         {
     201             :             f_error = "cannot allocate buffer of "              // LCOV_EXCL_LINE
     202             :                 + std::to_string(size)                          // LCOV_EXCL_LINE
     203             :                 + " bytes to read file.";                       // LCOV_EXCL_LINE
     204             :             f_contents.clear();                                 // LCOV_EXCL_LINE
     205             :             return false;                                       // LCOV_EXCL_LINE
     206             :         }
     207             : 
     208             :         // read the data
     209             :         //
     210           3 :         in.read(f_contents.data(), size);
     211             : 
     212             :         if(in.fail()) // note: eof() will be true here          // LCOV_EXCL_LINE
     213             :         {
     214             :             f_error = "an I/O error occurred reading \""        // LCOV_EXCL_LINE
     215             :                 + f_filename                                    // LCOV_EXCL_LINE
     216             :                 + "\".";                                        // LCOV_EXCL_LINE
     217             :             return false;                                       // LCOV_EXCL_LINE
     218             :         }
     219             : 
     220           3 :         f_error.clear();
     221             : 
     222           3 :         return true;
     223             :     }
     224             : 
     225             : 
     226             :     /** \brief Write the contents to the file.
     227             :      *
     228             :      * This function writes the file contents data to the file. If a new
     229             :      * filename is specified, the contents is saved there instead. Note
     230             :      * that the filename does not get modified, but it allows for creating
     231             :      * a backup before making changes and save the new file:
     232             :      *
     233             :      * \code
     234             :      *      file_content fc("filename.here");
     235             :      *      fc.read_all();
     236             :      *      fc.write_all("filename.bak");  // create backup
     237             :      *      std::string content(fc.contents());
     238             :      *      ... // make changes on 'contents'
     239             :      *      fc.contents(contents);
     240             :      *      fc.write_all();
     241             :      * \endcode
     242             :      *
     243             :      * \warning
     244             :      * If you marked the file_contents object as managing a temporary file
     245             :      * and specify a filename here which is not exactly equal to the
     246             :      * filename passed to the constructor, then the file you are writing
     247             :      * now will not be deleted automatically.
     248             :      *
     249             :      * \param[in] filename  The name to use or an empty string (or nothing)
     250             :      *                      to use the filename defined in the constructor.
     251             :      *
     252             :      * \return true if the file was saved successfully.
     253             :      *
     254             :      * \sa contents()
     255             :      * \sa contents(std::string const & new_contents)
     256             :      * \sa read_all()
     257             :      * \sa last_error()
     258             :      */
     259           6 :     bool write_all(std::string const & filename = std::string())
     260             :     {
     261             :         // select filename
     262             :         //
     263          12 :         std::string const name(filename.empty() ? f_filename : filename);
     264             : 
     265             :         // try to open the file
     266             :         //
     267          12 :         std::ofstream out;
     268           6 :         out.open(name, std::ios::trunc | std::ios::out | std::ios::binary);
     269           6 :         if(!out.is_open())
     270             :         {
     271           2 :             f_error = "could not open file \""
     272           2 :                 + name
     273           3 :                 + "\" for writing.";
     274           1 :             return false;
     275             :         }
     276             : 
     277             :         // write the data
     278             :         //
     279           5 :         out.write(f_contents.data(), f_contents.length());
     280             : 
     281           5 :         if(out.fail())
     282             :         {
     283             :             f_error = "could not write "                    // LCOV_EXCL_LINE
     284             :                 + std::to_string(f_contents.length())       // LCOV_EXCL_LINE
     285             :                 + " bytes to \"" + name + "\".";            // LCOV_EXCL_LINE
     286             :             return false;                                   // LCOV_EXCL_LINE
     287             :         }
     288             : 
     289           5 :         f_error.clear();
     290             : 
     291           5 :         return true;
     292             :     }
     293             : 
     294             : 
     295             :     /** \brief Change the contents with \p new_contents.
     296             :      *
     297             :      * This function replaces the current file contents with
     298             :      * \p new_contents.
     299             :      *
     300             :      * If \p new_contents is empty, then the file will become empty on a
     301             :      * write_all(). If you instead wanted to delete the file on
     302             :      * destruction of the function, set the temporary flag of the
     303             :      * construction to true to do that.
     304             :      *
     305             :      * \param[in] new_contents  The new contents of the file.
     306             :      *
     307             :      * \sa contents()
     308             :      * \sa write_all()
     309             :      */
     310           6 :     void contents(std::string const & new_contents)
     311             :     {
     312           6 :         f_contents = new_contents;
     313           6 :     }
     314             : 
     315             : 
     316             :     /** \brief Get a constant reference to the contents.
     317             :      *
     318             :      * This function gives you read access to the existing contents of the
     319             :      * file.
     320             :      *
     321             :      * The string represents the contents of the file only if you called
     322             :      * the read_all() function first. It is not mandatory to do so in case
     323             :      * you are creating a new file.
     324             :      *
     325             :      * \return A constant reference to this file contents.
     326             :      *
     327             :      * \sa contents(std::string const & new_contents)
     328             :      * \sa read_all()
     329             :      */
     330           1 :     std::string const & contents() const
     331             :     {
     332           1 :         return f_contents;
     333             :     }
     334             : 
     335             : 
     336             :     /** \brief Get a reference to the contents.
     337             :      *
     338             :      * This function gives you direct read/write access to the existing
     339             :      * contents of the file.
     340             :      *
     341             :      * The string represents the contents of the file only if you called
     342             :      * the read_all() function first. It is not mandatory to do so in case
     343             :      * you are creating a new file.
     344             :      *
     345             :      * \return A constant reference to this file contents.
     346             :      *
     347             :      * \sa contents() const
     348             :      * \sa contents(std::string const & new_contents)
     349             :      * \sa read_all()
     350             :      */
     351           4 :     std::string & contents()
     352             :     {
     353           4 :         return f_contents;
     354             :     }
     355             : 
     356             : 
     357             :     /** \brief Retrieve the last error message.
     358             :      *
     359             :      * This function returns a copy of the last error message generated
     360             :      * by one of read_all() or write_all().
     361             :      *
     362             :      * \return The last error generated.
     363             :      */
     364           1 :     std::string const & last_error() const
     365             :     {
     366           1 :         return f_error;
     367             :     }
     368             : 
     369             : private:
     370             :     std::string         f_filename = std::string();
     371             :     std::string         f_contents = std::string();
     372             :     std::string         f_error = std::string();
     373             :     bool                f_temporary = false;
     374             : };
     375             : 
     376             : 
     377             : 
     378             : } // namespace snap
     379             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13