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
|