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 | |
35 | namespace fs = boost::filesystem; |
36 | |
37 | namespace FileSystem { |
38 | |
39 | bool 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 | |
49 | bool is_directory(const std::string& path) |
50 | { |
51 | fs::path location(path); |
52 | return fs::is_directory(location); |
53 | } |
54 | |
55 | void 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 | |
64 | std::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 | |
75 | std::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 | |
86 | std::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 | |
127 | std::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 | |
136 | std::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 | |
187 | std::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 | |
215 | bool remove(const std::string& path) |
216 | { |
217 | fs::path location(path); |
218 | return fs::remove(location); |
219 | } |
220 | |
221 | void 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 | |