Merge release-5-0 into master
[alexxy/gromacs.git] / src / gromacs / utility / datafilefinder.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2014,2015, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements gmx::DataFileFinder.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_utility
41  */
42 #include "gmxpre.h"
43
44 #include "datafilefinder.h"
45
46 #include "config.h"
47
48 #include <cstdlib>
49
50 #include <string>
51 #include <vector>
52
53 #include "gromacs/utility/directoryenumerator.h"
54 #include "gromacs/utility/exceptions.h"
55 #include "gromacs/utility/file.h"
56 #include "gromacs/utility/path.h"
57 #include "gromacs/utility/programcontext.h"
58 #include "gromacs/utility/stringutil.h"
59
60 namespace gmx
61 {
62
63 /********************************************************************
64  * DataFileFinder::Impl
65  */
66
67 class DataFileFinder::Impl
68 {
69     public:
70         static std::string getDefaultPath();
71
72         Impl() : envName_(NULL), bEnvIsSet_(false) {}
73
74         const char               *envName_;
75         bool                      bEnvIsSet_;
76         std::vector<std::string>  searchPath_;
77 };
78
79 std::string DataFileFinder::Impl::getDefaultPath()
80 {
81     const InstallationPrefixInfo installPrefix
82         = getProgramContext().installationPrefix();
83     if (!isNullOrEmpty(installPrefix.path))
84     {
85         const char *const dataPath
86             = installPrefix.bSourceLayout ? "share" : DATA_INSTALL_DIR;
87         return Path::join(installPrefix.path, dataPath, "top");
88     }
89     return std::string();
90 }
91
92 /********************************************************************
93  * DataFileFinder
94  */
95
96 DataFileFinder::DataFileFinder()
97     : impl_(NULL)
98 {
99 }
100
101 DataFileFinder::~DataFileFinder()
102 {
103 }
104
105 void DataFileFinder::setSearchPathFromEnv(const char *envVarName)
106 {
107     if (!impl_.get())
108     {
109         impl_.reset(new Impl());
110     }
111     impl_->envName_ = envVarName;
112     const char *const lib = getenv(envVarName);
113     if (lib != NULL)
114     {
115         impl_->bEnvIsSet_ = true;
116         Path::splitPathEnvironment(lib, &impl_->searchPath_);
117     }
118 }
119
120 FILE *DataFileFinder::openFile(const DataFileOptions &options) const
121 {
122     // TODO: There is a small race here, since there is some time between
123     // the exists() calls and actually opening the file.  It would be better
124     // to leave the file open after a successful exists() if the desire is to
125     // actually open the file.
126     std::string filename = findFile(options);
127     if (filename.empty())
128     {
129         return NULL;
130     }
131 #if 0
132     if (debug)
133     {
134         fprintf(debug, "Opening library file %s\n", fn);
135     }
136 #endif
137     return File::openRawHandle(filename, "r");
138 }
139
140 std::string DataFileFinder::findFile(const DataFileOptions &options) const
141 {
142     if (options.bCurrentDir_ && Path::exists(options.filename_))
143     {
144         return options.filename_;
145     }
146     if (impl_.get())
147     {
148         std::vector<std::string>::const_iterator i;
149         for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
150         {
151             // TODO: Deal with an empty search path entry more reasonably.
152             std::string testPath = Path::join(*i, options.filename_);
153             // TODO: Consider skipping directories.
154             if (Path::exists(testPath))
155             {
156                 return testPath;
157             }
158         }
159     }
160     const std::string &defaultPath = Impl::getDefaultPath();
161     if (!defaultPath.empty())
162     {
163         std::string testPath = Path::join(defaultPath, options.filename_);
164         if (Path::exists(testPath))
165         {
166             return testPath;
167         }
168     }
169     if (options.bThrow_)
170     {
171         const char *const envName   = (impl_.get() ? impl_->envName_ : NULL);
172         const bool        bEnvIsSet = (impl_.get() ? impl_->bEnvIsSet_ : false);
173         std::string       message(
174                 formatString("Library file '%s' not found", options.filename_));
175         if (options.bCurrentDir_)
176         {
177             message.append(" in current dir nor");
178         }
179         if (bEnvIsSet)
180         {
181             message.append(formatString(" in your %s path nor", envName));
182         }
183         message.append(" in the default directories.\nThe following paths were searched:");
184         if (options.bCurrentDir_)
185         {
186             message.append("\n  ");
187             message.append(Path::getWorkingDirectory());
188             message.append(" (current dir)");
189         }
190         if (impl_.get())
191         {
192             std::vector<std::string>::const_iterator i;
193             for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
194             {
195                 message.append("\n  ");
196                 message.append(*i);
197             }
198         }
199         if (!defaultPath.empty())
200         {
201             message.append("\n  ");
202             message.append(defaultPath);
203             message.append(" (default)");
204         }
205         if (!bEnvIsSet && envName != NULL)
206         {
207             message.append(
208                     formatString("\nYou can set additional directories to search "
209                                  "with the %s path variable.", envName));
210         }
211         GMX_THROW(FileIOError(message));
212     }
213     return std::string();
214 }
215
216 std::vector<DataFileInfo>
217 DataFileFinder::enumerateFiles(const DataFileOptions &options) const
218 {
219     // TODO: Consider if not being able to list one of the directories should
220     // really be a fatal error. Or alternatively, check somewhere else that
221     // paths in GMXLIB are valid.
222     std::vector<DataFileInfo>                result;
223     std::vector<std::string>::const_iterator i;
224     if (options.bCurrentDir_)
225     {
226         std::vector<std::string> files
227             = DirectoryEnumerator::enumerateFilesWithExtension(
228                         ".", options.filename_, false);
229         for (i = files.begin(); i != files.end(); ++i)
230         {
231             result.push_back(DataFileInfo(".", *i, false));
232         }
233     }
234     if (impl_.get())
235     {
236         std::vector<std::string>::const_iterator j;
237         for (j = impl_->searchPath_.begin(); j != impl_->searchPath_.end(); ++j)
238         {
239             std::vector<std::string> files
240                 = DirectoryEnumerator::enumerateFilesWithExtension(
241                             j->c_str(), options.filename_, false);
242             for (i = files.begin(); i != files.end(); ++i)
243             {
244                 result.push_back(DataFileInfo(*j, *i, false));
245             }
246         }
247     }
248     const std::string &defaultPath = Impl::getDefaultPath();
249     if (!defaultPath.empty())
250     {
251         std::vector<std::string> files
252             = DirectoryEnumerator::enumerateFilesWithExtension(
253                         defaultPath.c_str(), options.filename_, false);
254         for (i = files.begin(); i != files.end(); ++i)
255         {
256             result.push_back(DataFileInfo(defaultPath, *i, true));
257         }
258     }
259     if (result.empty() && options.bThrow_)
260     {
261         // TODO: Print the search path as is done in findFile().
262         std::string message(
263                 formatString("Could not find any files ending on '%s' in the "
264                              "current directory or the GROMACS library search path",
265                              options.filename_));
266         GMX_THROW(FileIOError(message));
267     }
268     return result;
269 }
270
271 } // namespace gmx