Split lines with many copyright years
[alexxy/gromacs.git] / src / gromacs / utility / directoryenumerator.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2010,2011,2014,2015,2016 by the GROMACS development team.
5  * Copyright (c) 2017,2019,2020, by the GROMACS development team, led by
6  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7  * and including many others, as listed in the AUTHORS file in the
8  * top-level source directory and at http://www.gromacs.org.
9  *
10  * GROMACS is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * as published by the Free Software Foundation; either version 2.1
13  * of the License, or (at your option) any later version.
14  *
15  * GROMACS is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with GROMACS; if not, see
22  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
24  *
25  * If you want to redistribute modifications to GROMACS, please
26  * consider that scientific software is very special. Version
27  * control is crucial - bugs must be traceable. We will be happy to
28  * consider code for inclusion in the official distribution, but
29  * derived work must not be called official GROMACS. Details are found
30  * in the README & COPYING files - if they are missing, get the
31  * official version at http://www.gromacs.org.
32  *
33  * To help us fund GROMACS development, we humbly ask that you cite
34  * the research papers on the package. Check out http://www.gromacs.org.
35  */
36 /*! \internal \file
37  * \brief
38  * Implements gmx::DirectoryEnumerator.
39  *
40  * \author Erik Lindahl (original C implementation)
41  * \author Teemu Murtola <teemu.murtola@gmail.com> (C++ wrapper + errno handling)
42  * \ingroup module_utility
43  */
44 #include "gmxpre.h"
45
46 #include "directoryenumerator.h"
47
48 #include "config.h"
49
50 #include <cerrno>
51 #include <cstdio>
52
53 #include <algorithm>
54 #include <string>
55 #include <vector>
56
57 #if HAVE_DIRENT_H
58 #    include <dirent.h>
59 #endif
60 #if GMX_NATIVE_WINDOWS
61 #    include <io.h>
62 #endif
63
64 #include "gromacs/utility/exceptions.h"
65 #include "gromacs/utility/fatalerror.h"
66 #include "gromacs/utility/futil.h"
67 #include "gromacs/utility/gmxassert.h"
68 #include "gromacs/utility/smalloc.h"
69 #include "gromacs/utility/stringutil.h"
70
71 namespace gmx
72 {
73
74 /********************************************************************
75  * DirectoryEnumerator::Impl
76  */
77
78 // TODO: Consider whether checking the return value of closing would be useful,
79 // and what could we do if it fails?
80 #if GMX_NATIVE_WINDOWS
81 // TODO: Consider if Windows provides more error details through other APIs.
82 class DirectoryEnumerator::Impl
83 {
84 public:
85     static Impl* init(const char* dirname, bool bThrow)
86     {
87         std::string tmpname(dirname);
88         // Remove possible trailing directory separator.
89         // TODO: Use a method in gmx::Path instead.
90         if (tmpname.back() == '/' || tmpname.back() == '\\')
91         {
92             tmpname.pop_back();
93         }
94
95         // Add wildcard.
96         tmpname.append("/*");
97
98         errno = 0;
99         _finddata_t finddata;
100         intptr_t    handle = _findfirst(tmpname.c_str(), &finddata);
101         if (handle < 0L)
102         {
103             if (errno != ENOENT && bThrow)
104             {
105                 const int         code = errno;
106                 const std::string message =
107                         formatString("Failed to list files in directory '%s'", dirname);
108                 GMX_THROW_WITH_ERRNO(FileIOError(message), "_findfirst", code);
109             }
110             return NULL;
111         }
112         return new Impl(handle, finddata);
113     }
114     Impl(intptr_t handle, _finddata_t finddata) :
115         windows_handle(handle),
116         finddata(finddata),
117         bFirst_(true)
118     {
119     }
120     ~Impl() { _findclose(windows_handle); }
121
122     bool nextFile(std::string* filename)
123     {
124         if (bFirst_)
125         {
126             *filename = finddata.name;
127             bFirst_   = false;
128             return true;
129         }
130         else
131         {
132             errno = 0;
133             if (_findnext(windows_handle, &finddata) != 0)
134             {
135                 if (errno == 0 || errno == ENOENT)
136                 {
137                     filename->clear();
138                     return false;
139                 }
140                 else
141                 {
142                     GMX_THROW_WITH_ERRNO(FileIOError("Failed to list files in a directory"),
143                                          "_findnext", errno);
144                 }
145             }
146             *filename = finddata.name;
147             return true;
148         }
149     }
150
151 private:
152     intptr_t    windows_handle;
153     _finddata_t finddata;
154     bool        bFirst_;
155 };
156 #elif HAVE_DIRENT_H
157 class DirectoryEnumerator::Impl
158 {
159 public:
160     static Impl* init(const char* dirname, bool bThrow)
161     {
162         errno       = 0;
163         DIR* handle = opendir(dirname);
164         if (handle == nullptr)
165         {
166             if (bThrow)
167             {
168                 const int         code = errno;
169                 const std::string message =
170                         formatString("Failed to list files in directory '%s'", dirname);
171                 GMX_THROW_WITH_ERRNO(FileIOError(message), "opendir", code);
172             }
173             return nullptr;
174         }
175         return new Impl(handle);
176     }
177     explicit Impl(DIR* handle) : dirent_handle(handle) {}
178     ~Impl() { closedir(dirent_handle); }
179
180     bool nextFile(std::string* filename)
181     {
182         errno     = 0;
183         dirent* p = readdir(dirent_handle);
184         if (p == nullptr)
185         {
186             if (errno == 0)
187             {
188                 // All the files have been found.
189                 filename->clear();
190                 return false;
191             }
192             else
193             {
194                 GMX_THROW_WITH_ERRNO(FileIOError("Failed to list files in a directory"), "readdir", errno);
195             }
196         }
197         *filename = p->d_name;
198         return true;
199     }
200
201 private:
202     DIR* dirent_handle;
203 };
204 #else
205 class DirectoryEnumerator::Impl
206 {
207 public:
208     static Impl* init(const char* /*dirname*/, bool /*bThrow*/)
209     {
210         std::string message(
211                 "Source compiled without POSIX dirent or Windows support "
212                 "- cannot scan directories. In the very unlikely event "
213                 "this is not a compile-time mistake you could consider "
214                 "implementing support for your platform in "
215                 "directoryenumerator.cpp, but contact the developers "
216                 "to make sure it's really necessary!");
217         GMX_THROW(NotImplementedError(message));
218     }
219
220     bool nextFile(std::string* /*filename*/) { return false; }
221 };
222 #endif
223
224 /********************************************************************
225  * DirectoryEnumerator
226  */
227
228 // static
229 std::vector<std::string> DirectoryEnumerator::enumerateFilesWithExtension(const char* dirname,
230                                                                           const char* extension,
231                                                                           bool        bThrow)
232 {
233     std::vector<std::string> result;
234     DirectoryEnumerator      dir(dirname, bThrow);
235     std::string              nextName;
236     while (dir.nextFile(&nextName))
237     {
238         if (debug)
239         {
240             std::fprintf(debug, "dir '%s' file '%s'\n", dirname, nextName.c_str());
241         }
242         // TODO: What about case sensitivity?
243         if (endsWith(nextName, extension))
244         {
245             result.push_back(nextName);
246         }
247     }
248
249     std::sort(result.begin(), result.end());
250     return result;
251 }
252
253
254 DirectoryEnumerator::DirectoryEnumerator(const char* dirname, bool bThrow) : impl_(nullptr)
255 {
256     GMX_RELEASE_ASSERT(dirname != nullptr && dirname[0] != '\0',
257                        "Attempted to open empty/null directory path");
258     impl_.reset(Impl::init(dirname, bThrow));
259 }
260
261 DirectoryEnumerator::DirectoryEnumerator(const std::string& dirname, bool bThrow) : impl_(nullptr)
262 {
263     GMX_RELEASE_ASSERT(!dirname.empty(), "Attempted to open empty/null directory path");
264     impl_.reset(Impl::init(dirname.c_str(), bThrow));
265 }
266
267 DirectoryEnumerator::~DirectoryEnumerator() {}
268
269 bool DirectoryEnumerator::nextFile(std::string* filename)
270 {
271     if (impl_ == nullptr)
272     {
273         filename->clear();
274         return false;
275     }
276     return impl_->nextFile(filename);
277 }
278
279 } // namespace gmx