Line data Source code
1 :
2 : /** \file fileUtils.hpp
3 : * \brief Declarations of utilities for working with files
4 : *
5 : * \author Jared R. Males (jaredmales@gmail.com)
6 : *
7 : * \ingroup fileutils
8 : *
9 : */
10 :
11 : //***********************************************************************//
12 : // Copyright 2015-2020 Jared R. Males (jaredmales@gmail.com)
13 : //
14 : // This file is part of mxlib.
15 : //
16 : // mxlib is free software: you can redistribute it and/or modify
17 : // it under the terms of the GNU General Public License as published by
18 : // the Free Software Foundation, either version 3 of the License, or
19 : // (at your option) any later version.
20 : //
21 : // mxlib is distributed in the hope that it will be useful,
22 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
23 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 : // GNU General Public License for more details.
25 : //
26 : // You should have received a copy of the GNU General Public License
27 : // along with mxlib. If not, see <http://www.gnu.org/licenses/>.
28 : //***********************************************************************//
29 :
30 : #ifndef ioutils_fileUtils_hpp
31 : #define ioutils_fileUtils_hpp
32 :
33 : #include <string>
34 : #include <vector>
35 : #include <filesystem>
36 : #include <algorithm>
37 :
38 : #include "../mxlib.hpp"
39 :
40 : namespace mx
41 : {
42 : namespace ioutils
43 : {
44 :
45 : #ifdef MXLIBTEST_NAMESPACE
46 : namespace MXLIBTEST_NAMESPACE
47 : {
48 : #endif
49 :
50 : /** \addtogroup fileutils
51 : * @{
52 : */
53 :
54 : /// Convert a string to a path, handling exceptions.
55 : /** Wrapper for `path = str` assignment that handles exceptions
56 : * and implements mxlib standard error handling.
57 : *
58 : * \returns error_t::noerror if no exceptions
59 : * \returns error_t::std_bad_alloc if std::bad_alloc is caught
60 : * \returns error_t::std_filesystem_error if std::filesystem::filesystem_error is caught
61 : * \returns error_t::std_exception if any other exceptions are caught
62 : *
63 : * \throws a nested mx::exception for any uncaught exceptions.
64 : */
65 : template <class verboseT>
66 : error_t string2path( std::filesystem::path & path, const std::string &str);
67 :
68 : /// Check if a path exists
69 : /**
70 : * \returns true if the path exists and no errors occur
71 : * \returns false otherwise
72 : */
73 : template <class verboseT = verbose::d>
74 : bool exists( const std::string &path, /**< [in] the path to check for existence */
75 : mx::error_t &errc /**< [out] error code. Typically convereted as errno from std::filesystem*/
76 : );
77 :
78 : /// Check if a path exists and is a directory
79 : /**
80 : * \returns true only if \p dir both exists and is a directory, and no errors occur
81 : * \returns false otherwise
82 : */
83 : template <class verboseT = verbose::d>
84 : bool dir_exists_is( const std::string &dir, /**< [in] the path to check */
85 : mx::error_t &errc /**< [out] error code. Typically convereted as errno from std::filesystem*/
86 : );
87 :
88 : /// Create a directory or directories
89 : /** This will create any directories in path that don't exist. It silently ignores already existing directories.
90 : *
91 : * \returns error_t::noerror on success, indicating the directories were created or already existed.
92 : * \returns other codes, error_t::exxxx (from errno) or error_t::filesystem, on errors.
93 : */
94 : error_t createDirectories( const std::string &path /**< [in] the path of the directory(ies)to create */ );
95 :
96 : /// Get the stem of the filename
97 : /**
98 : * \returns the stem for the filename, that is without the path or extension
99 : */
100 : std::string pathStem( const std::string &fname );
101 :
102 : /// Get the base filename
103 : /**
104 : * \returns the filename, including the extension but without the path
105 : */
106 : std::string pathFilename( const std::string &fname );
107 :
108 : /// Get the parent path from a filename
109 : /**
110 : * \returns the parent path of the file
111 : */
112 : std::string parentPath( const std::string &fname );
113 :
114 : /// Get a list of file names from the specified directory, specifying a prefix, a substring to match, and an extension
115 : /**
116 : * \returns mx::error_t::success on success
117 : * \returns mx::error_t::invalidarg if \p directory is not a directory
118 : * \returns mx::error_t::dirnotfound if \p directory does not exist
119 : * \returns mx::error_t::exception if an exception is thrown from the standard library
120 : *
121 : * \tparam verbose if true then error messages are printed as they occur
122 : *
123 : *
124 : */
125 : template <class verboseT = verbose::d>
126 : error_t getFileNames( std::vector<std::string> &fileNames, /** [out] The populated list of file names.*/
127 : const std::string &directory, /**< [in] The path to the directory to search.
128 : Can not be empty.*/
129 : const std::string &prefix, /**< [in] The file name prefix (the beginning
130 : characters of the file name) to search
131 : for. If "" then not used.*/
132 : const std::string &substr, /**< [in] A substring of the filename to search
133 : for. If "" then not used. Only matches
134 : after the first character.*/
135 : const std::string &extension /**< [in] The file name extension to search for.
136 : If "" then not used. This does not need
137 : to include the ".", as in".ext".*/
138 : );
139 :
140 : /// Prepend and/or append strings to a file name, leaving the directory and extension unaltered.
141 : /**
142 : * \returns the new file name
143 : */
144 : std::string fileNamePrependAppend( const std::string &fname, /**< [in] the original file name, possibly including a
145 : directory and extension*/
146 : const std::string &prepend, /**< [in] is the string to insert at the beginning of the
147 : file name after the path*/
148 : const std::string &append /**< [in] is the string to insert at the end of the file
149 : name, before the extension*/
150 : );
151 :
152 : /// Append a string to a file name, leaving the directory and extension unaltered.
153 : /**
154 : * \returns the new file name
155 : */
156 : std::string fileNameAppend( const std::string &fname, /**< [in] the original file name, possibly including
157 : a directory and extension*/
158 : const std::string &append /**< [in] is the string to insert at the end
159 : of the file name, before the extension*/
160 : );
161 :
162 : /// Prepend strings to a file name, leaving the directory and extension unaltered.
163 : /**
164 : * \returns the new file name
165 : */
166 : std::string fileNamePrepend( const std::string &fname, /**< [in] the original file name, possibly including
167 : a directory and extension*/
168 : const std::string &prepend /**< [in] is the string to insert at the beginning of
169 : the file name after the path*/
170 : );
171 :
172 : /// Get the next file in a numbered sequence
173 : /** Searches for files in the path designated by basename of the form basenameXXXXextension
174 : * where the number of digits in XXXX is set by the \a ndigit parameter.
175 : *
176 : * \warning this does not currently detect missing files in the sequence, e.g. if you have 0,1,3 in the directory this
177 : * will start with 2!
178 : *
179 : * \todo switch to using a regex or something so we can detect the missing file.
180 : *
181 : * \retval std::string containing the next filename.
182 : *
183 : */
184 : std::string getSequentialFilename( const std::string &basename, ///< [in] path and initial name of the file*/
185 : const std::string &extension = "", /**< [in] [optional] extension to append after the
186 : number. Default is empty.*/
187 : const int startat = 0, /**< [in] [optional] number to start the
188 : search from.
189 : Default is 0.*/
190 : int ndigit = 4 /**< [in] [optional] number of digits in string
191 : representation
192 : of the number.Default is 4. */
193 : );
194 :
195 : /// Get the size in bytes of a file
196 : /** Uses fstat.
197 : *
198 : * \returns the file size if fd is valid and no errors occur
199 : * \returns -1 on an error
200 : */
201 : off_t fileSize( int fd /**< [in] an open file descriptor */ );
202 :
203 : /// Get the size in bytes of a file pointed to by a FILE pointer
204 : /** Uses fileno to get the associated descriptor, then uses fstat.
205 : *
206 : * \returns the file size if fd is valid and no errors occur
207 : * \returns -1 on an error
208 : *
209 : * \overload
210 : */
211 : off_t fileSize( FILE *f /**< [in] an open file */ );
212 :
213 : ///@} -fileutils
214 :
215 : /* ===================================================================== */
216 : /* implementations */
217 :
218 : template <class verboseT>
219 5 : error_t string2path( std::filesystem::path & path, const std::string &str)
220 : {
221 : try
222 : {
223 5 : path = str;
224 :
225 5 : return error_t::noerror;
226 : }
227 0 : catch( const std::bad_alloc &e )
228 : {
229 : // clang-format off
230 : #if defined( MXLIB_CATCH_ALL_EXCEPTIONS )
231 : return internal::mxlib_error_report<verboseT>( error_t::std_bad_alloc, e.what() );
232 : #else
233 0 : std::throw_with_nested(mx::exception<verboseT>(error_t::std_bad_alloc));
234 : #endif
235 : // clang-format on
236 : }
237 0 : catch( const std::filesystem::filesystem_error &e )
238 : {
239 : // clang-format off
240 : #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
241 : return internal::mxlib_error_report<verboseT>( error_t::std_filesystem_error, e.what() );
242 : #else
243 0 : std::throw_with_nested(mx::exception<verboseT>(error_t::std_filesystem_error));
244 : #endif
245 : // clang-format on
246 : }
247 0 : catch( const std::exception &e )
248 : {
249 : // clang-format off
250 : #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
251 : return internal::mxlib_error_report<verboseT>( error_t::std_exception, e.what() );
252 : #else
253 0 : std::throw_with_nested(mx::exception<verboseT>());
254 : #endif
255 : // clang-format on
256 : }
257 0 : catch(...)
258 : {
259 : // clang-format off
260 : #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
261 : return internal::mxlib_error_report<verboseT>( error_t::exception, "unknown exception");
262 : #else
263 0 : std::throw_with_nested(mx::exception<verboseT>());
264 : #endif
265 : // clang-format on
266 : }
267 : }
268 :
269 : template <class verboseT>
270 : bool exists( const std::string &strpath, mx::error_t &errc )
271 : {
272 : std::error_code ec;
273 :
274 : std::filesystem::path path;
275 :
276 : try
277 : {
278 : errc = string2path<verboseT>(path, strpath);
279 :
280 : if(!!errc)
281 : {
282 : internal::mxlib_error_report<verboseT>( errc, "converting path" );
283 : return false;
284 : }
285 : }
286 : catch(const mx::exception<verboseT> & e)
287 : {
288 : std::throw_with_nested(mx::exception<verboseT>(e.code()));
289 : }
290 :
291 : bool ex = std::filesystem::exists( path, ec );
292 :
293 : if( ec.value() != 0 )
294 : {
295 : errc = mx::errno2error_t( ec.value() );
296 : if( errc == error_t::error )
297 : {
298 : errc = error_t::filesystem;
299 : }
300 :
301 : internal::mxlib_error_report<verboseT>( errc, ec.message() );
302 :
303 : return false;
304 : }
305 :
306 : errc = error_t::noerror;
307 : return ex;
308 : }
309 :
310 : template <class verboseT>
311 5 : bool dir_exists_is( const std::string &dir, mx::error_t &errc )
312 : {
313 5 : std::error_code ec;
314 :
315 5 : std::filesystem::path path;
316 :
317 : try
318 : {
319 5 : errc = string2path<verboseT>(path, dir);
320 :
321 5 : if(!!errc)
322 : {
323 0 : internal::mxlib_error_report<verboseT>( errc, "converting path" );
324 0 : return false;
325 : }
326 : }
327 0 : catch(const mx::exception<verboseT> & e)
328 : {
329 0 : std::throw_with_nested(mx::exception<verboseT>(e.code()));
330 : }
331 :
332 5 : bool exists = std::filesystem::exists( path, ec );
333 :
334 : // clang-format off
335 : #ifdef MXLIBTEST_DIREXISTSIS_ISEXISTSERR
336 : ec = std::error_code( EEXIST, std::system_category() ); // LCOV_EXCL_LINE
337 : #endif
338 : // clang-format on
339 :
340 5 : if( ec.value() != 0 )
341 : {
342 1 : errc = mx::errno2error_t( ec.value() );
343 1 : if( errc == error_t::error )
344 : {
345 0 : errc = error_t::filesystem;
346 : }
347 :
348 1 : internal::mxlib_error_report<verboseT>( errc, ec.message() );
349 :
350 1 : return false;
351 : }
352 :
353 4 : if( !exists )
354 : {
355 1 : return false;
356 : }
357 :
358 3 : bool isdir = std::filesystem::is_directory( path, ec );
359 :
360 : // clang-format off
361 : #ifdef MXLIBTEST_DIREXISTSIS_ISDIRERR
362 : ec = std::error_code( EACCES, std::system_category() ); // LCOV_EXCL_LINE
363 : #endif
364 : // clang-format on
365 :
366 3 : if( ec.value() != 0 )
367 : {
368 1 : errc = errno2error_t( ec.value() );
369 1 : if( errc == mx::error_t::error )
370 : {
371 0 : errc = mx::error_t::filesystem;
372 : }
373 :
374 1 : internal::mxlib_error_report<verboseT>( errc, ec.message() );
375 :
376 1 : return false;
377 : }
378 :
379 2 : errc = error_t::noerror;
380 2 : return isdir;
381 5 : }
382 :
383 : template <class verboseT>
384 19 : error_t getFileNames( std::vector<std::string> &fileNames,
385 : const std::string &directory,
386 : const std::string &prefix,
387 : const std::string &substr,
388 : const std::string &extension )
389 : {
390 : try // there are several things that can throw here
391 : {
392 19 : fileNames.clear();
393 :
394 19 : if( std::filesystem::exists( directory ) )
395 : {
396 15 : if( std::filesystem::is_directory( directory ) )
397 : {
398 11 : bool hasext = false;
399 11 : std::string _ext;
400 11 : if( extension.size() > 0 )
401 : {
402 5 : if( extension[0] != '.' )
403 : {
404 1 : _ext = '.';
405 : }
406 :
407 5 : _ext += extension;
408 :
409 5 : hasext = true;
410 : }
411 :
412 11 : bool hasprefix = ( prefix.size() > 0 );
413 :
414 11 : bool hassub = ( substr.size() > 0 );
415 :
416 11 : std::filesystem::directory_iterator it{ directory };
417 11 : auto it_end = std::filesystem::directory_iterator{};
418 206 : for( it; it != it_end; ++it )
419 : {
420 110 : if( hasext )
421 : {
422 50 : if( it->path().extension() != _ext )
423 : {
424 66 : continue;
425 : }
426 : }
427 :
428 85 : std::string p = it->path().filename().generic_string();
429 :
430 85 : if( hasprefix )
431 : {
432 40 : if( p.size() < prefix.size() )
433 : {
434 0 : continue;
435 : }
436 : else
437 : {
438 : // This won't throw because:
439 : // - prefix has size > 0
440 : // - p.size() >= prefix.size()
441 : // - therefore prefix.size() > 0
442 : // - so pos1 = 0 will not throw.
443 40 : if( p.compare( 0, prefix.size(), prefix ) != 0 )
444 : {
445 19 : continue;
446 : }
447 : }
448 : }
449 :
450 66 : if( hassub )
451 : {
452 33 : if( p.size() < 2 )
453 : {
454 0 : continue;
455 : }
456 :
457 33 : size_t sspos = p.find( substr, 1 ); // only match if not prefix
458 :
459 33 : if( sspos == std::string::npos )
460 : {
461 22 : continue;
462 : }
463 : }
464 :
465 : // If here then it passed all checks
466 : // this could throw
467 44 : fileNames.push_back( it->path().native() );
468 : }
469 :
470 11 : std::sort( fileNames.begin(), fileNames.end() );
471 11 : }
472 : else
473 : {
474 4 : return internal::mxlib_error_report<verboseT>( error_t::invalidarg, directory + " is not a directory" );
475 : }
476 : }
477 : else
478 : {
479 4 : return internal::mxlib_error_report<verboseT>( error_t::dirnotfound, directory + " was not found" );
480 : }
481 :
482 11 : return error_t::noerror;
483 : }
484 0 : catch( const std::bad_alloc &e )
485 : {
486 : // clang-format off
487 : #if defined( MXLIB_CATCH_ALL_EXCEPTIONS )
488 : return internal::mxlib_error_report<verboseT>( error_t::std_bad_alloc, e.what() );;
489 : #else
490 0 : std::throw_with_nested(mx::exception<verboseT>(error_t::std_bad_alloc));
491 : #endif
492 : // clang-format on
493 : }
494 0 : catch( const std::filesystem::filesystem_error &e )
495 : {
496 : // clang-format off
497 : #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
498 : return internal::mxlib_error_report<verboseT>( error_t::std_filesystem_error, e.what() );
499 : #else
500 0 : std::throw_with_nested(mx::exception(error_t::std_filesystem_error));
501 : #endif
502 : // clang-format on
503 : }
504 0 : catch( const std::exception &e )
505 : {
506 : // clang-format off
507 : #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
508 : return internal::mxlib_error_report<verboseT>( error_t::exception, e.what() );
509 : #else
510 0 : std::throw_with_nested(mx::exception<verboseT>(error_t::std_exception));
511 : #endif
512 : // clang-format on
513 : }
514 0 : catch( ... )
515 : {
516 : // clang-format off
517 : #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
518 : return internal::mxlib_error_report<verboseT>( error_t::exception );
519 : #else
520 0 : std::throw_with_nested(mx::exception<verboseT>());
521 : #endif
522 : // clang-format on
523 : }
524 : }
525 :
526 : #ifdef MXLIBTEST_NAMESPACE
527 : } // namespace MXLIBTEST_NAMESPACE
528 : #endif
529 :
530 : } // namespace ioutils
531 : } // namespace mx
532 :
533 : #endif // fileUtils_hpp
|