1// SuperTux
2// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "util/file_system.hpp"
18
19#include <boost/filesystem.hpp>
20#include <boost/version.hpp>
21#include <sstream>
22#include <stdexcept>
23#include <sys/stat.h>
24#include <sys/types.h>
25#include <vector>
26#if defined(_WIN32)
27 #include <windows.h>
28 #include <shellapi.h>
29#else
30 #include <cstdlib>
31#endif
32
33#include "util/log.hpp"
34
35namespace fs = boost::filesystem;
36
37namespace FileSystem {
38
39bool exists(const std::string& path)
40{
41 fs::path location(path);
42 boost::system::error_code ec;
43
44 // If we get an error (such as "Permission denied"), then ignore it
45 // and pretend that the path doesn't exist.
46 return fs::exists(location, ec);
47}
48
49bool is_directory(const std::string& path)
50{
51 fs::path location(path);
52 return fs::is_directory(location);
53}
54
55void mkdir(const std::string& directory)
56{
57 fs::path location(directory);
58 if (!fs::create_directory(location))
59 {
60 throw std::runtime_error("failed to create directory: " + directory);
61 }
62}
63
64std::string dirname(const std::string& filename)
65{
66 std::string::size_type p = filename.find_last_of('/');
67 if (p == std::string::npos)
68 p = filename.find_last_of('\\');
69 if (p == std::string::npos)
70 return "./";
71
72 return filename.substr(0, p+1);
73}
74
75std::string basename(const std::string& filename)
76{
77 std::string::size_type p = filename.find_last_of('/');
78 if (p == std::string::npos)
79 p = filename.find_last_of('\\');
80 if (p == std::string::npos)
81 return filename;
82
83 return filename.substr(p+1, filename.size()-p-1);
84}
85
86std::string relpath(const std::string& filename, const std::string& basedir)
87{
88#if BOOST_VERSION >= 106000
89 return fs::relative(filename, basedir).string();
90#else
91 fs::path from = basedir;
92 fs::path to = filename;
93
94 // Taken from https://stackoverflow.com/a/29221546
95
96 // Start at the root path and while they are the same then do nothing then when they first
97 // diverge take the entire from path, swap it with '..' segments, and then append the remainder of the to path.
98 fs::path::const_iterator fromIter = from.begin();
99 fs::path::const_iterator toIter = to.begin();
100
101 // Loop through both while they are the same to find nearest common directory
102 while (fromIter != from.end() && toIter != to.end() && (*toIter) == (*fromIter))
103 {
104 ++toIter;
105 ++fromIter;
106 }
107
108 // Replace from path segments with '..' (from => nearest common directory)
109 fs::path finalPath;
110 while (fromIter != from.end())
111 {
112 finalPath /= "..";
113 ++fromIter;
114 }
115
116 // Append the remainder of the to path (nearest common directory => to)
117 while (toIter != to.end())
118 {
119 finalPath /= *toIter;
120 ++toIter;
121 }
122
123 return finalPath.string();
124#endif
125}
126
127std::string strip_extension(const std::string& filename)
128{
129 std::string::size_type p = filename.find_last_of('.');
130 if (p == std::string::npos)
131 return filename;
132
133 return filename.substr(0, p);
134}
135
136std::string normalize(const std::string& filename)
137{
138 std::vector<std::string> path_stack;
139
140 const char* p = filename.c_str();
141
142 while (true) {
143 while (*p == '/' || *p == '\\') {
144 p++;
145 continue;
146 }
147
148 const char* pstart = p;
149 while (*p != '/' && *p != '\\' && *p != 0) {
150 ++p;
151 }
152
153 size_t len = p - pstart;
154 if (len == 0)
155 break;
156
157 std::string pathelem(pstart, p-pstart);
158 if (pathelem == ".")
159 continue;
160
161 if (pathelem == "..") {
162 if (path_stack.empty()) {
163
164 log_warning << "Invalid '..' in path '" << filename << "'" << std::endl;
165 // push it into the result path so that the user sees his error...
166 path_stack.push_back(pathelem);
167 } else {
168 path_stack.pop_back();
169 }
170 } else {
171 path_stack.push_back(pathelem);
172 }
173 }
174
175 // construct path
176 std::ostringstream result;
177 for (std::vector<std::string>::iterator i = path_stack.begin();
178 i != path_stack.end(); ++i) {
179 result << '/' << *i;
180 }
181 if (path_stack.empty())
182 result << '/';
183
184 return result.str();
185}
186
187std::string join(const std::string& lhs, const std::string& rhs)
188{
189 if (lhs.empty())
190 {
191 return rhs;
192 }
193 else if (rhs.empty())
194 {
195 return lhs + "/";
196 }
197 else if (lhs.back() == '/' && rhs.front() != '/')
198 {
199 return lhs + rhs;
200 }
201 else if (lhs.back() != '/' && rhs.front() == '/')
202 {
203 return lhs + rhs;
204 }
205 else if (lhs.back() == '/' && rhs.front() == '/')
206 {
207 return lhs + rhs.substr(1);
208 }
209 else
210 {
211 return lhs + "/" + rhs;
212 }
213}
214
215bool remove(const std::string& path)
216{
217 fs::path location(path);
218 return fs::remove(location);
219}
220
221void open_path(const std::string& path)
222{
223#if defined(_WIN32) || defined (_WIN64)
224 ShellExecute(NULL, "open", path.c_str(), NULL, NULL, SW_SHOWNORMAL);
225#else
226 #if defined(__APPLE__)
227 std::string cmd = "open \"" + path + "\"";
228 #else
229 std::string cmd = "xdg-open \"" + path + "\"";
230 #endif
231
232 int ret = system(cmd.c_str());
233 if (ret < 0)
234 {
235 log_fatal << "failed to spawn: " << cmd << std::endl;
236 }
237 else if (ret > 0)
238 {
239 log_fatal << "error " << ret << " while executing: " << cmd << std::endl;
240 }
241#endif
242}
243
244} // namespace FileSystem
245
246/* EOF */
247