Refactor logic for finding share/top/ files
[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 <cstdlib>
47
48 #include <string>
49 #include <vector>
50
51 #include "gromacs/utility/exceptions.h"
52 #include "gromacs/utility/file.h"
53 #include "gromacs/utility/path.h"
54 #include "gromacs/utility/programcontext.h"
55 #include "gromacs/utility/stringutil.h"
56
57 namespace gmx
58 {
59
60 /********************************************************************
61  * DataFileFinder::Impl
62  */
63
64 class DataFileFinder::Impl
65 {
66     public:
67         Impl() : envName_(NULL), bEnvIsSet_(false) {}
68
69         const char               *envName_;
70         bool                      bEnvIsSet_;
71         std::vector<std::string>  searchPath_;
72 };
73
74 /********************************************************************
75  * DataFileFinder
76  */
77
78 DataFileFinder::DataFileFinder()
79     : impl_(NULL)
80 {
81 }
82
83 DataFileFinder::~DataFileFinder()
84 {
85 }
86
87 void DataFileFinder::setSearchPathFromEnv(const char *envVarName)
88 {
89     if (!impl_.get())
90     {
91         impl_.reset(new Impl());
92     }
93     impl_->envName_ = envVarName;
94     const char *const lib = getenv(envVarName);
95     if (lib != NULL)
96     {
97         impl_->bEnvIsSet_ = true;
98         Path::splitPathEnvironment(lib, &impl_->searchPath_);
99     }
100 }
101
102 FILE *DataFileFinder::openFile(const DataFileOptions &options) const
103 {
104     // TODO: There is a small race here, since there is some time between
105     // the exists() calls and actually opening the file.  It would be better
106     // to leave the file open after a successful exists() if the desire is to
107     // actually open the file.
108     std::string filename = findFile(options);
109     if (filename.empty())
110     {
111         return NULL;
112     }
113 #if 0
114     if (debug)
115     {
116         fprintf(debug, "Opening library file %s\n", fn);
117     }
118 #endif
119     return File::openRawHandle(filename, "r");
120 }
121
122 std::string DataFileFinder::findFile(const DataFileOptions &options) const
123 {
124     if (options.bCurrentDir_ && Path::exists(options.filename_))
125     {
126         return options.filename_;
127     }
128     if (impl_.get())
129     {
130         std::vector<std::string>::const_iterator i;
131         for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
132         {
133             // TODO: Deal with an empty search path entry more reasonably.
134             std::string testPath = Path::join(*i, options.filename_);
135             // TODO: Consider skipping directories.
136             if (Path::exists(testPath))
137             {
138                 return testPath;
139             }
140         }
141     }
142     const char *const defaultPath = getProgramContext().defaultLibraryDataPath();
143     if (defaultPath != NULL && defaultPath[0] != '\0')
144     {
145         std::string testPath = Path::join(defaultPath, options.filename_);
146         if (Path::exists(testPath))
147         {
148             return testPath;
149         }
150     }
151     if (options.bThrow_)
152     {
153         const char *const envName   = (impl_.get() ? impl_->envName_ : NULL);
154         const bool        bEnvIsSet = (impl_.get() ? impl_->bEnvIsSet_ : false);
155         std::string       message(
156                 formatString("Library file %s not found", options.filename_));
157         if (options.bCurrentDir_)
158         {
159             message.append(" in current dir nor");
160         }
161         if (bEnvIsSet)
162         {
163             message.append(formatString(" in your %s path nor", envName));
164         }
165         message.append(" in the default directories.\nThe following paths were searched:");
166         if (options.bCurrentDir_)
167         {
168             message.append("\n  ");
169             message.append(Path::getWorkingDirectory());
170             message.append(" (current dir)");
171         }
172         if (impl_.get())
173         {
174             std::vector<std::string>::const_iterator i;
175             for (i = impl_->searchPath_.begin(); i != impl_->searchPath_.end(); ++i)
176             {
177                 message.append("\n  ");
178                 message.append(*i);
179             }
180         }
181         if (defaultPath != NULL && defaultPath[0] != '\0')
182         {
183             message.append("\n  ");
184             message.append(defaultPath);
185             message.append(" (default)");
186         }
187         if (!bEnvIsSet && envName != NULL)
188         {
189             message.append(
190                     formatString("\nYou can set additional directories to search "
191                                  "with the %s path variable.", envName));
192         }
193         GMX_THROW(FileIOError(message));
194     }
195     return std::string();
196 }
197
198 } // namespace gmx