LCOV - code coverage report
Current view: top level - snapdev - file_contents.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 71 73 97.3 %
Date: 2023-05-29 16:11:08 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2011-2023  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 3 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
      17             : // along with this program.  If not, see <https://www.gnu.org/licenses/>.
      18             : #pragma once
      19             : 
      20             : /** \file
      21             :  * \brief Handle a file as one memory buffer.
      22             :  *
      23             :  * This class allows you to load and save the content of an entire file all
      24             :  * at once.
      25             :  */
      26             : 
      27             : // self
      28             : //
      29             : #include    <snapdev/mkdir_p.h>
      30             : 
      31             : 
      32             : // C++
      33             : //
      34             : #include    <fstream>
      35             : #include    <iostream>
      36             : #include    <ios>
      37             : 
      38             : 
      39             : // C
      40             : //
      41             : #include    <unistd.h>
      42             : 
      43             : 
      44             : 
      45             : namespace snapdev
      46             : {
      47             : 
      48             : 
      49             : 
      50             : class file_contents
      51             : {
      52             : public:
      53             :     /** \brief Define the way to determine the file size.
      54             :      *
      55             :      * The size of some files can't be determined ahead of time
      56             :      * (sockets, FIFO, /proc files, etc.) and thus attempting to
      57             :      * get the size of such files is not going to work.
      58             :      *
      59             :      * Before calling read_all(), you can change the size mode
      60             :      * to one where the size will be determined once the file
      61             :      * is read.
      62             :      */
      63             :     enum size_mode_t
      64             :     {
      65             :         /** \brief Seek to the end to determine the size of the file.
      66             :          *
      67             :          * This mode is the default. It tells the read_all() command
      68             :          * to seek to the end of the file, get the position, which
      69             :          * represents the size, then seek back to the start of the
      70             :          * file. This works with all regular files.
      71             :          */
      72             :         SIZE_MODE_SEEK,
      73             : 
      74             :         /** \brief Read the file, avoid the seek().
      75             :          *
      76             :          * This mode has to be used with any file which size can't be
      77             :          * determine with a seek() function. It will read as much as it
      78             :          * can and then return that contents. There is no real way to
      79             :          * know whether the entire file was read in this mode.
      80             :          *
      81             :          * Although it is possible to use this mode to read a regular
      82             :          * file, it is not a good idea since (1) it will be slower and
      83             :          * (2) it may re-allocate the contents buffer multiple times
      84             :          * so the data fits.
      85             :          */
      86             :         SIZE_MODE_READ,
      87             :     };
      88             : 
      89             : 
      90             :     /** \brief Initialize a content file.
      91             :      *
      92             :      * The constructor initialize the file content object with a filename.
      93             :      * The filename is used by the read_all() and write_all() functions.
      94             :      *
      95             :      * If the file_content is setup to be a temporary file, then the
      96             :      * destructor also makes use of the filename to delete the file
      97             :      * at that time. By default a file is not marked as temporary.
      98             :      *
      99             :      * \exception std::invalid_argument
     100             :      * The \p filename parameter cannot be an empty string.
     101             :      *
     102             :      * \exception std::ios::failure
     103             :      * The function checks whether all the folders exist. If not then the
     104             :      * file can't be create or read so there is no valid file_content()
     105             :      * possible with that path. This exception only occurs if the
     106             :      * \p create_missing_directories is true and the creation of any
     107             :      * of the directories fails.
     108             :      *
     109             :      * \param[in] filename  The name of the file to read and write.
     110             :      * \param[in] create_missing_directories  Whether to create missing directories
     111             :      *            as found in the path (see mkdir_p()).
     112             :      * \param[in] temporary  Whether the file is temporary.
     113             :      *
     114             :      * \sa snap::mkdir_p()
     115             :      */
     116          15 :     file_contents(
     117             :               std::string const & filename
     118             :             , bool create_missing_directories = false
     119             :             , bool temporary = false)
     120          15 :         : f_filename(filename)
     121          15 :         , f_temporary(temporary)
     122             :     {
     123          15 :         if(f_filename.empty())
     124             :         {
     125           1 :             throw std::invalid_argument("snapdev::file_contents: the filename of a file_contents object cannot be the empty string.");
     126             :         }
     127             : 
     128          14 :         if(create_missing_directories)
     129             :         {
     130           3 :             int const r(mkdir_p(f_filename, true));
     131           3 :             if(r != 0)
     132             :             {
     133           1 :                 throw std::ios::failure("snapdev::file_contents: the full path to filename for a file_contents object could not be created");
     134             :             }
     135             :         }
     136          19 :     }
     137             : 
     138             : 
     139             :     /** \brief Clean up as required.
     140             :      *
     141             :      * If the file_content was marked as temporary, then the destructor
     142             :      * deletes the file on disk before returning.
     143             :      *
     144             :      * If the file does not exist and it was marked as temporary, the
     145             :      * deletion will fail silently. Otherwise you get a warning in the
     146             :      * logs with more details about the error (i.e. permission, bad
     147             :      * filename, etc.)
     148             :      */
     149          13 :     ~file_contents()
     150             :     {
     151          13 :         if(f_temporary)
     152             :         {
     153           2 :             if(unlink(f_filename.c_str()) != 0)
     154             :             {
     155           1 :                 if(errno != ENOENT)
     156             :                 {
     157             :                     // TODO: what should we do here?!
     158             :                 }
     159             :             }
     160             :         }
     161          13 :     }
     162             : 
     163             : 
     164             :     /** \brief Retrieve the filename.
     165             :      *
     166             :      * This function returns a refrence to the filename used by this
     167             :      * object. Note that the filename cannot be modified.
     168             :      *
     169             :      * \return A reference to this file content filename.
     170             :      */
     171             :     std::string const & filename() const
     172             :     {
     173             :         return f_filename;
     174             :     }
     175             : 
     176             : 
     177             :     /** \brief Check whether this file exists.
     178             :      *
     179             :      * This function checks whether the file exists on disk. If the
     180             :      * read_all() function returns false and this function returns true,
     181             :      * then you probably do not have permissions to read the file or it
     182             :      * is a directory.
     183             :      *
     184             :      * \return true if the file exists and can be read.
     185             :      *
     186             :      * \sa read_all()
     187             :      */
     188             :     bool exists() const
     189             :     {
     190             :         return access(f_filename.c_str(), R_OK) == 0;
     191             :     }
     192             : 
     193             : 
     194             :     /** \brief Change the size mode.
     195             :      *
     196             :      * Certain files do not have a size that can be read using the seek()
     197             :      * function and tell(). This function can be used to change the mode
     198             :      * to instead read the entire file little by little until the EOF is
     199             :      * reached.
     200             :      *
     201             :      * By default, we expect you to be using a regular file so the mode
     202             :      * is set to size_mode_t::SIZE_MODE_SEEK.
     203             :      *
     204             :      * \param[in] mode  The new size mode.
     205             :      */
     206             :     void size_mode(size_mode_t mode)
     207             :     {
     208             :         f_size_mode = mode;
     209             :     }
     210             : 
     211             : 
     212             :     /** \brief Retrieve the current size mode.
     213             :      *
     214             :      * This function returns the current size mode for this file. The
     215             :      * mode is used to know whether we can use the seek() function or
     216             :      * not. If not, we just read the file up to EOF and return from
     217             :      * the read_all() function.
     218             :      *
     219             :      * \return The current size mode of this file_contents object
     220             :      * This function returns the current size mode for this file. The
     221             :      * mode is used to know whether we can use the seek() function or
     222             :      * not. If not, we just read the file up to EOF and return from
     223             :      * the read_all() function.
     224             :      *
     225             :      * \return The current size mode of this file_contents object.
     226             :      */
     227           4 :     size_mode_t size_mode() const
     228             :     {
     229           4 :         return f_size_mode;
     230             :     }
     231             : 
     232             : 
     233             :     /** \brief Read the entire file in a buffer.
     234             :      *
     235             :      * This function reads the entire file in memory. It saves the data
     236             :      * in a buffer which you can get with the contents() function.
     237             :      * The file can be binary in which case remember that c_str()
     238             :      * and other similar function will not work right.
     239             :      *
     240             :      * The function may return false if the file could not be opened or
     241             :      * read in full.
     242             :      *
     243             :      * \return true if the file could be read, false otherwise.
     244             :      *
     245             :      * \sa write_all()
     246             :      * \sa contents() const
     247             :      * \sa last_error()
     248             :      */
     249           6 :     bool read_all()
     250             :     {
     251             :         // try to open the file
     252             :         //
     253           6 :         std::ifstream in;
     254           6 :         in.open(f_filename, std::ios::in | std::ios::binary);
     255           6 :         if(!in.is_open())
     256             :         {
     257           2 :             f_error = "could not open file \""
     258           4 :                 + f_filename
     259           4 :                 + "\" for reading.";
     260           2 :             return false;
     261             :         }
     262             : 
     263             :         // get size
     264             :         //
     265           4 :         std::ifstream::pos_type size(0);
     266           4 :         size_mode_t mode(size_mode());
     267           4 :         if(mode == size_mode_t::SIZE_MODE_SEEK)
     268             :         {
     269           4 :             in.seekg(0, std::ios::end);
     270           4 :             size = in.tellg();
     271           4 :             in.seekg(0, std::ios::beg);
     272           4 :             if(std::ifstream::pos_type(-1) == size)
     273             :             {
     274             :                 // try again in READ mode
     275             :                 //
     276           1 :                 mode = size_mode_t::SIZE_MODE_READ;
     277           1 :                 in.clear();
     278             :             }
     279             :         }
     280             : 
     281           4 :         if(mode == size_mode_t::SIZE_MODE_READ)
     282             :         {
     283             :             // on certain files, the "get size" fails (i.e. "/proc/...",
     284             :             // FIFO, socket, etc.)
     285             :             //
     286           1 :             f_contents.clear();
     287           1 :             char buf[1024 * 16];
     288             :             do
     289             :             {
     290           1 :                 in.read(buf, sizeof(buf));
     291           1 :                 ssize_t sz(in.gcount());
     292           1 :                 if(sz <= 0)
     293             :                 {
     294           0 :                     break;
     295             :                 }
     296           1 :                 f_contents.insert(f_contents.end(), buf, buf + sz);
     297             :             }
     298           1 :             while(in.good());
     299             :         }
     300             :         else
     301             :         {
     302             :             // resize the buffer accordingly
     303             :             //
     304             :             try
     305             :             {
     306           3 :                 f_contents.resize(size, '\0');
     307             :             }
     308             :             catch(std::bad_alloc const & e)                         // LCOV_EXCL_LINE
     309             :             {
     310             :                 f_error = "cannot allocate buffer of "              // LCOV_EXCL_LINE
     311             :                     + std::to_string(size)                          // LCOV_EXCL_LINE
     312             :                     + " bytes to read file.";                       // LCOV_EXCL_LINE
     313             :                 f_contents.clear();                                 // LCOV_EXCL_LINE
     314             :                 return false;                                       // LCOV_EXCL_LINE
     315           0 :             }
     316             : 
     317             :             // read the data
     318             :             //
     319           3 :             in.read(f_contents.data(), size);
     320             :         }
     321             : 
     322           4 :         if(in.bad()) // eof() always true, fail() may be true too (in case we can't gather the size() above)
     323             :         {
     324             :             f_error = "an I/O error occurred reading \""        // LCOV_EXCL_LINE
     325             :                 + f_filename                                    // LCOV_EXCL_LINE
     326             :                 + "\".";                                        // LCOV_EXCL_LINE
     327             :             return false;                                       // LCOV_EXCL_LINE
     328             :         }
     329             : 
     330           4 :         f_error.clear();
     331             : 
     332           4 :         return true;
     333           6 :     }
     334             : 
     335             : 
     336             :     /** \brief Write the contents to the file.
     337             :      *
     338             :      * This function writes the file contents data to the file. If a new
     339             :      * filename is specified, the contents is saved there instead. Note
     340             :      * that the filename does not get modified, but it allows for creating
     341             :      * a backup before making changes and save the new file:
     342             :      *
     343             :      * \code
     344             :      *      file_content fc("filename.here");
     345             :      *      fc.read_all();
     346             :      *      fc.write_all("filename.bak");  // create backup
     347             :      *      std::string content(fc.contents());
     348             :      *      ... // make changes on 'contents'
     349             :      *      fc.contents(contents);
     350             :      *      fc.write_all();
     351             :      * \endcode
     352             :      *
     353             :      * \warning
     354             :      * If you marked the file_contents object as managing a temporary file
     355             :      * and specify a filename here which is not exactly equal to the
     356             :      * filename passed to the constructor, then the file you are writing
     357             :      * now will not be deleted automatically.
     358             :      *
     359             :      * \param[in] filename  The name to use or an empty string (or nothing)
     360             :      *                      to use the filename defined in the constructor.
     361             :      *
     362             :      * \return true if the file was saved successfully.
     363             :      *
     364             :      * \sa contents()
     365             :      * \sa contents(std::string const & new_contents)
     366             :      * \sa read_all()
     367             :      * \sa last_error()
     368             :      */
     369           6 :     bool write_all(std::string const & filename = std::string())
     370             :     {
     371             :         // select filename
     372             :         //
     373           6 :         std::string const name(filename.empty() ? f_filename : filename);
     374             : 
     375             :         // try to open the file
     376             :         //
     377           6 :         std::ofstream out;
     378           6 :         out.open(name, std::ios::trunc | std::ios::out | std::ios::binary);
     379           6 :         if(!out.is_open())
     380             :         {
     381           1 :             f_error = "could not open file \""
     382           2 :                 + name
     383           2 :                 + "\" for writing.";
     384           1 :             return false;
     385             :         }
     386             : 
     387             :         // write the data
     388             :         //
     389           5 :         out.write(f_contents.data(), f_contents.length());
     390             : 
     391           5 :         if(out.fail())
     392             :         {
     393             :             f_error = "could not write "                    // LCOV_EXCL_LINE
     394             :                 + std::to_string(f_contents.length())       // LCOV_EXCL_LINE
     395             :                 + " bytes to \"" + name + "\".";            // LCOV_EXCL_LINE
     396             :             return false;                                   // LCOV_EXCL_LINE
     397             :         }
     398             : 
     399           5 :         f_error.clear();
     400             : 
     401           5 :         return true;
     402           6 :     }
     403             : 
     404             : 
     405             :     /** \brief Change the contents with \p new_contents.
     406             :      *
     407             :      * This function replaces the current file contents with
     408             :      * \p new_contents.
     409             :      *
     410             :      * If \p new_contents is empty, then the file will become empty on a
     411             :      * write_all(). If you instead wanted to delete the file on
     412             :      * destruction of the function, set the temporary flag of the
     413             :      * construction to true to do that.
     414             :      *
     415             :      * \param[in] new_contents  The new contents of the file.
     416             :      *
     417             :      * \sa contents()
     418             :      * \sa write_all()
     419             :      */
     420           6 :     void contents(std::string const & new_contents)
     421             :     {
     422           6 :         f_contents = new_contents;
     423           6 :     }
     424             : 
     425             : 
     426             :     /** \brief Get a constant reference to the contents.
     427             :      *
     428             :      * This function gives you read access to the existing contents of the
     429             :      * file.
     430             :      *
     431             :      * The string represents the contents of the file only if you called
     432             :      * the read_all() function first. It is not mandatory to do so in case
     433             :      * you are creating a new file.
     434             :      *
     435             :      * \return A constant reference to this file contents.
     436             :      *
     437             :      * \sa contents(std::string const & new_contents)
     438             :      * \sa read_all()
     439             :      */
     440           2 :     std::string const & contents() const
     441             :     {
     442           2 :         return f_contents;
     443             :     }
     444             : 
     445             : 
     446             :     /** \brief Get a reference to the contents.
     447             :      *
     448             :      * This function gives you direct read/write access to the existing
     449             :      * contents of the file.
     450             :      *
     451             :      * The string represents the contents of the file only if you called
     452             :      * the read_all() function first. It is not mandatory to do so in case
     453             :      * you are creating a new file.
     454             :      *
     455             :      * \return A constant reference to this file contents.
     456             :      *
     457             :      * \sa contents() const
     458             :      * \sa contents(std::string const & new_contents)
     459             :      * \sa read_all()
     460             :      */
     461           4 :     std::string & contents()
     462             :     {
     463           4 :         return f_contents;
     464             :     }
     465             : 
     466             : 
     467             :     /** \brief Retrieve the last error message.
     468             :      *
     469             :      * This function returns a copy of the last error message generated
     470             :      * by one of read_all() or write_all().
     471             :      *
     472             :      * \return The last error generated.
     473             :      */
     474           1 :     std::string const & last_error() const
     475             :     {
     476           1 :         return f_error;
     477             :     }
     478             : 
     479             : private:
     480             :     std::string         f_filename = std::string();
     481             :     std::string         f_contents = std::string();
     482             :     std::string         f_error = std::string();
     483             :     size_mode_t         f_size_mode = size_mode_t::SIZE_MODE_SEEK;
     484             :     bool                f_temporary = false;
     485             : };
     486             : 
     487             : 
     488             : 
     489             : } // namespace snapdev
     490             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.14