Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2023, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // Standard C++ library
49 : #include <cerrno>
50 : #include <cstdio>
51 : #include <cstdlib>
52 : #include <fstream>
53 : #include <iostream>
54 : #include <sys/stat.h>
55 : #include <sys/types.h>
56 :
57 : #ifdef _WIN32
58 : #include <Shlwapi.h>
59 : #include <windows.h>
60 : #else
61 : #include <unistd.h>
62 : #endif
63 :
64 : #ifdef __APPLE__
65 : #include <mach-o/dyld.h>
66 : #endif
67 :
68 : // EnergyPlus Headers
69 : #include <EnergyPlus/DataStringGlobals.hh>
70 : #include <EnergyPlus/FileSystem.hh>
71 : #include <EnergyPlus/UtilityRoutines.hh>
72 :
73 : namespace EnergyPlus {
74 :
75 : namespace FileSystem {
76 :
77 : #ifdef _WIN32
78 : std::string const exeExtension(".exe");
79 : #else
80 771 : std::string const exeExtension;
81 : #endif
82 :
83 : static constexpr std::array<std::string_view, static_cast<std::size_t>(FileTypes::Num)> FileTypesExt{
84 : "epJSON", "json", "glhe", "cbor", "msgpack", "ubjson", "bson", "idf", "imf", "csv", "tsv", "txt", "eso", "mtr"};
85 : static constexpr std::array<std::string_view, static_cast<std::size_t>(FileTypes::Num)> FileTypesExtUC{
86 : "EPJSON", "JSON", "GLHE", "CBOR", "MSGPACK", "UBJSON", "BSON", "IDF", "IMF", "CSV", "TSV", "TXT", "ESO", "MTR"};
87 :
88 : static_assert(FileTypesExt.size() == static_cast<std::size_t>(FileTypes::Num), "Mismatched FileTypes enum and FileTypesExt array.");
89 : static_assert(FileTypesExtUC.size() == static_cast<std::size_t>(FileTypes::Num), "Mismatched FileTypes enum and FileTypesExtUC array.");
90 :
91 : static_assert(!FileTypesExt.back().empty(), "Likely missing an enum from FileTypes in FileTypesExt array.");
92 : static_assert(!FileTypesExtUC.back().empty(), "Likely missing an enum from FileTypes in FileTypesExtUC array.");
93 :
94 4777 : fs::path makeNativePath(fs::path const &path)
95 : {
96 : // path.make_preferred() on windows will change "/" to "\\", because '/' is a fallback separator
97 : // but on Unix it will *not* change "\\" to "/", because POSIX doesn't define it as a fallback separator. In fact, it's even allowed in a
98 : // filename. Do we really need that though?
99 : // path.make_preferred();
100 4777 : fs::path result = path;
101 9554 : std::string tempPathAsStr = result.make_preferred().string();
102 4777 : std::replace(tempPathAsStr.begin(), tempPathAsStr.end(), DataStringGlobals::altpathChar, DataStringGlobals::pathChar);
103 4777 : result = fs::path(tempPathAsStr);
104 9554 : return result;
105 : }
106 :
107 : // TODO: remove as providing little benefit over calling fs::path::filename directly?
108 771 : fs::path getFileName(fs::path const &filePath)
109 : {
110 771 : return filePath.filename();
111 : }
112 :
113 2482 : fs::path getParentDirectoryPath(fs::path const &path)
114 : {
115 : // Note: this is needed because "/a/b/c".parent_path() = "/a/b/c/"
116 4964 : std::string pathStr = path.string();
117 2482 : if (!pathStr.empty()) {
118 2461 : while ((pathStr.back() == DataStringGlobals::pathChar) || (pathStr.back() == DataStringGlobals::altpathChar)) {
119 0 : pathStr.erase(pathStr.size() - 1);
120 : }
121 : }
122 :
123 : // If empty, return "./" instead
124 2482 : fs::path parent_path = fs::path(pathStr).parent_path();
125 2482 : if (parent_path.empty()) {
126 21 : parent_path = "./";
127 : }
128 4964 : return parent_path;
129 : }
130 :
131 2104 : fs::path getAbsolutePath(fs::path const &path)
132 : {
133 : // /*
134 : // * Returns the absolute path for a given relative path.
135 : // *
136 : // * If the relative path points to a symlink, the symlink will
137 : // * be resolved, and this function will return the absolute path
138 : // * of the link.
139 : // */
140 :
141 : // Not available in experimental/filesystem
142 : // return fs::weakly_canonical(fs::absolute(p));
143 :
144 4208 : fs::path p = fs::absolute(path);
145 :
146 2104 : while (fs::is_symlink(p)) {
147 0 : auto linkpath = fs::read_symlink(p);
148 0 : if (linkpath.is_absolute()) {
149 0 : p = linkpath;
150 : } else {
151 : // Note: temp will end up absolute but not canonical yet
152 : // eg:
153 : // temp="/home/a_folder/a_symlink"
154 : // linkpath="../another_folder/a_file"
155 0 : p = p.parent_path() / linkpath;
156 : // eg: temp ="/home/a_folder/../another_folder/a_file"
157 : }
158 : }
159 :
160 2104 : fs::path result;
161 : // `p` now is absolute, but it isn't necessarilly canonical.
162 : // If you have <filesystem>, you can use `fs::weakly_canonical`. <experimental/filesystem> does **not** have `weakly_canonical` though
163 : // This block resolves a canonical path, even if it doesn't exist (yet?) on disk.
164 19194 : for (fs::path::iterator it = p.begin(); it != p.end(); ++it) {
165 17090 : if (*it == fs::path("..")) {
166 30 : if (fs::is_symlink(result) || (result.filename() == fs::path(".."))) {
167 0 : result /= *it;
168 : } else {
169 30 : result = result.parent_path();
170 : }
171 17060 : } else if (*it != fs::path(".")) {
172 17060 : result /= *it;
173 : }
174 : }
175 :
176 : // Workaround to maintain std::string & backward compat
177 : // TODO: is this wanted?
178 : // The problem is really only for unit tests, if you try to compare getAbsolutePath("sandbox/") == getAbsolutePath("sandbox") you get false
179 : // because one has the trailing sep, the other doesn't. Both paths have the same components though (if iterated on), and as long as you use
180 : // path operations (such as operator/) and not string concatenation, this works just fine.
181 : // std::string s = result.string();
182 : // if (fs::is_directory(s)) {
183 : // s += '/';
184 : //}
185 4208 : return result;
186 : }
187 :
188 1542 : fs::path getProgramPath()
189 : {
190 : // /*
191 : // * Returns the relative path to the executable file (including symlinks).
192 : // *
193 : // * To resolve symlinks, wrap this call in getAbsolutePath().
194 : // */
195 : char executableRelativePath[1024];
196 :
197 : #ifdef __APPLE__
198 : uint32_t pathSize = sizeof(executableRelativePath);
199 : _NSGetExecutablePath(executableRelativePath, &pathSize);
200 : #elif __linux__
201 1542 : ssize_t len = readlink("/proc/self/exe", executableRelativePath, sizeof(executableRelativePath) - 1);
202 1542 : if (len == -1) {
203 0 : std::cout << "ERROR: Unable to locate executable." << std::endl;
204 0 : std::exit(EXIT_FAILURE);
205 : } else {
206 1542 : executableRelativePath[len] = '\0';
207 : }
208 : #elif _WIN32
209 : GetModuleFileName(NULL, executableRelativePath, sizeof(executableRelativePath));
210 : #endif
211 :
212 1542 : return executableRelativePath;
213 : }
214 :
215 : // TODO: remove? seems like fs::path::extension would do fine. It's only used in CommandLineInterface to check the input file type, so we could
216 : // just compare to ".EPJSON" instead of "EPJSON"...
217 0 : fs::path getFileExtension(fs::path const &filePath)
218 : {
219 0 : std::string pext = fs::path(filePath).extension().string();
220 0 : if (!pext.empty()) {
221 : // remove '.'
222 0 : pext = std::string(++pext.begin(), pext.end());
223 : }
224 0 : return fs::path{pext};
225 : }
226 :
227 782 : FileTypes getFileType(fs::path const &filePath)
228 : {
229 : #ifdef _WIN32
230 : auto const filePathStr = fs::path(filePath).extension().string();
231 : auto extension = std::string_view(filePathStr.c_str());
232 : #else
233 782 : auto extension = std::string_view(fs::path(filePath).extension().c_str());
234 : #endif
235 :
236 782 : extension.remove_prefix(extension.find_last_of('.') + 1);
237 1564 : std::string stringExtension = std::string(extension);
238 1564 : return static_cast<FileTypes>(getEnumerationValue(FileTypesExtUC, UtilityRoutines::MakeUPPERCase(stringExtension)));
239 : }
240 :
241 : // TODO: remove for fs::path::replace_extension directly? Note that replace_extension mutates the object
242 0 : fs::path removeFileExtension(fs::path const &filePath)
243 : {
244 : // return fs::path(filePath).stem().string();
245 0 : return fs::path(filePath).replace_extension();
246 : }
247 :
248 0 : fs::path replaceFileExtension(fs::path const &filePath, fs::path const &ext)
249 : {
250 : // return fs::path(filePath).stem().string();
251 0 : return fs::path(filePath).replace_extension(ext);
252 : }
253 :
254 : // TODO: remove? `fs::create_directory` for a single or `fs::create_directories` for nested directory creation
255 771 : void makeDirectory(fs::path const &directoryPath)
256 : {
257 : // Create a directory if doesn't already exist
258 771 : if (pathExists(directoryPath)) { // path already exists
259 771 : if (!directoryExists(directoryPath)) {
260 0 : std::cout << "ERROR: " << getAbsolutePath(directoryPath).string() << " already exists and is not a directory." << std::endl;
261 0 : std::exit(EXIT_FAILURE);
262 : }
263 : } else { // directory does not already exist
264 : // Create_directories is recursive, create_directory isn't. I don't see why we wouldn't want recursive
265 0 : fs::create_directories(directoryPath);
266 : }
267 771 : }
268 :
269 : // TODO: remove?
270 771 : bool pathExists(fs::path const &path)
271 : {
272 771 : return fs::exists(path);
273 : }
274 :
275 : // TODO: I think we want to keep this one
276 771 : bool directoryExists(fs::path const &directoryPath)
277 : {
278 771 : return fs::exists(directoryPath) && fs::is_directory(directoryPath);
279 : }
280 :
281 13403 : bool fileExists(fs::path const &filePath)
282 : {
283 13403 : return fs::exists(filePath) && !fs::is_directory(filePath);
284 : }
285 :
286 829 : void moveFile(fs::path const &filePath, fs::path const &destination)
287 : {
288 : // TODO: should we throw? Or return false?
289 829 : if (!fileExists(filePath)) {
290 26 : return;
291 : }
292 :
293 : // rename would fail if copying across devices
294 : try {
295 803 : fs::rename(fs::path(filePath), destination);
296 0 : } catch (fs::filesystem_error &) {
297 0 : fs::copy(filePath, destination, fs::copy_options::update_existing);
298 0 : fs::remove(filePath);
299 : }
300 : }
301 :
302 1568 : int systemCall(std::string const &command)
303 : {
304 : #ifdef _WIN32
305 : // Wrap in double quotes and pass that to system
306 : // Note: on Windows, system(command) will already send the command through "cmd /C command"
307 : // cf C:\Program Files (x86)\Windows Kits\10\Source\10.0.17763.0\ucrt\exec
308 : // Ends up calling something that looks like the following:
309 : // cmd /C ""C:\path\to\ReadVarsESO.exe" "A folder with spaces\1ZoneUncontrolled.mvi" unlimited"
310 : return system(("\"" + command + "\"").c_str());
311 : #else
312 1568 : return system(command.c_str());
313 : #endif
314 : }
315 :
316 4394 : bool removeFile(fs::path const &filePath)
317 : {
318 4394 : if (!fileExists(filePath)) {
319 2 : return false;
320 : }
321 :
322 4392 : return fs::remove(filePath);
323 : }
324 :
325 59 : void linkFile(fs::path const &filePath, fs::path const &linkPath)
326 : {
327 59 : if (!fileExists(filePath)) {
328 0 : return;
329 : }
330 :
331 : #ifdef _WIN32
332 : fs::copy(filePath, linkPath, fs::copy_options::update_existing);
333 : #else
334 : // we could return bool?
335 59 : fs::create_symlink(filePath, linkPath);
336 : #endif
337 : }
338 :
339 780 : std::string readFile(fs::path const &filePath, std::ios_base::openmode mode)
340 : {
341 : #ifdef _WIN32
342 : auto filePathStr = filePath.string();
343 : auto path = filePathStr.c_str();
344 : #else
345 780 : auto path = filePath.c_str();
346 : #endif
347 :
348 780 : if (!fileExists(filePath)) {
349 0 : throw FatalError(fmt::format("File does not exists: {}", path));
350 : }
351 :
352 780 : std::string_view fopen_mode;
353 780 : if (mode == std::ios_base::in) {
354 0 : fopen_mode = "r";
355 780 : } else if (mode == std::ios_base::binary) {
356 0 : fopen_mode = "b";
357 780 : } else if (mode == (std::ios_base::in | std::ios_base::binary)) {
358 780 : fopen_mode = "rb";
359 : } else {
360 0 : throw FatalError("ERROR - readFile: Bad openmode argument. Must be std::ios_base::in or std::ios_base::binary");
361 : }
362 :
363 780 : auto close_file = [](FILE *f) { fclose(f); };
364 1560 : auto holder = std::unique_ptr<FILE, decltype(close_file)>(fopen(path, fopen_mode.data()), close_file);
365 780 : if (!holder) {
366 0 : throw FatalError(fmt::format("Could not open file: {}", path));
367 : }
368 :
369 780 : auto f = holder.get();
370 780 : const auto size = fs::file_size(filePath);
371 780 : std::string result;
372 780 : result.resize(size);
373 :
374 780 : auto bytes_read = fread(result.data(), 1, size, f);
375 780 : auto is_eof = feof(f);
376 780 : auto has_error = ferror(f);
377 780 : if (is_eof != 0) {
378 0 : return result;
379 : }
380 780 : if (has_error != 0 || bytes_read != size) {
381 0 : throw FatalError(fmt::format("Error reading file: {}", path));
382 : }
383 780 : return result;
384 : }
385 :
386 1 : nlohmann::json readJSON(fs::path const &filePath, std::ios_base::openmode mode)
387 : {
388 : #ifdef _WIN32
389 : auto filePathStr = filePath.string();
390 : auto path = filePathStr.c_str();
391 : #else
392 1 : auto path = filePath.c_str();
393 : #endif
394 :
395 1 : if (!fileExists(filePath)) {
396 0 : throw FatalError(fmt::format("File does not exists: {}", path));
397 : }
398 :
399 1 : std::string_view fopen_mode;
400 1 : if (mode == std::ios_base::in) {
401 0 : fopen_mode = "r";
402 1 : } else if (mode == std::ios_base::binary) {
403 0 : fopen_mode = "b";
404 1 : } else if (mode == (std::ios_base::in | std::ios_base::binary)) {
405 1 : fopen_mode = "rb";
406 : } else {
407 0 : throw FatalError("ERROR - readFile: Bad openmode argument. Must be std::ios_base::in or std::ios_base::binary");
408 : }
409 :
410 1 : auto close_file = [](FILE *f) { fclose(f); };
411 2 : auto holder = std::unique_ptr<FILE, decltype(close_file)>(fopen(path, fopen_mode.data()), close_file);
412 1 : if (!holder) {
413 0 : throw FatalError(fmt::format("Could not open file: {}", path));
414 : }
415 1 : auto f = holder.get();
416 :
417 1 : auto const ext = getFileType(filePath);
418 1 : switch (ext) {
419 1 : case FileTypes::EpJSON:
420 : case FileTypes::JSON:
421 : case FileTypes::GLHE:
422 1 : return nlohmann::json::parse(f, nullptr, true, true);
423 0 : case FileTypes::CBOR:
424 0 : return nlohmann::json::from_cbor(f);
425 0 : case FileTypes::MsgPack:
426 0 : return nlohmann::json::from_msgpack(f);
427 0 : case FileTypes::UBJSON:
428 0 : return nlohmann::json::from_ubjson(f);
429 0 : case FileTypes::BSON:
430 0 : return nlohmann::json::from_bson(f);
431 0 : default:
432 0 : throw FatalError("Invalid file extension. Must be epJSON, JSON, or other experimental extensions");
433 : }
434 : }
435 :
436 : } // namespace FileSystem
437 2313 : } // namespace EnergyPlus
|