1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2008, assimp team
6All rights reserved.
7
8Redistribution and use of this software in source and binary forms,
9with or without modification, are permitted provided that the
10following conditions are met:
11
12* Redistributions of source code must retain the above
13 copyright notice, this list of conditions and the
14 following disclaimer.
15
16* Redistributions in binary form must reproduce the above
17 copyright notice, this list of conditions and the
18 following disclaimer in the documentation and/or other
19 materials provided with the distribution.
20
21* Neither the name of the assimp team, nor the names of its
22 contributors may be used to endorse or promote products
23 derived from this software without specific prior
24 written permission of the assimp team.
25
26THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37
38----------------------------------------------------------------------
39*/
40
41/** @file FileSystemFilter.h
42 * Implements a filter system to filter calls to Exists() and Open()
43 * in order to improve the success rate of file opening ...
44 */
45#ifndef AI_FILESYSTEMFILTER_H_INC
46#define AI_FILESYSTEMFILTER_H_INC
47
48#include "../include/assimp/IOSystem.hpp"
49#include "../include/assimp/DefaultLogger.hpp"
50#include "fast_atof.h"
51#include "ParsingUtils.h"
52
53namespace Assimp {
54
55inline bool IsHex(char s) {
56 return (s>='0' && s<='9') || (s>='a' && s<='f') || (s>='A' && s<='F');
57}
58
59// ---------------------------------------------------------------------------
60/** File system filter
61 */
62class FileSystemFilter : public IOSystem
63{
64public:
65 /** Constructor. */
66 FileSystemFilter(const std::string& file, IOSystem* old)
67 : wrapped (old)
68 , src_file (file)
69 , sep(wrapped->getOsSeparator())
70 {
71 ai_assert(NULL != wrapped);
72
73 // Determine base directory
74 base = src_file;
75 std::string::size_type ss2;
76 if (std::string::npos != (ss2 = base.find_last_of("\\/"))) {
77 base.erase(ss2,base.length()-ss2);
78 }
79 else {
80 base = "";
81 // return;
82 }
83
84 // make sure the directory is terminated properly
85 char s;
86
87 if (base.length() == 0) {
88 base = ".";
89 base += getOsSeparator();
90 }
91 else if ((s = *(base.end()-1)) != '\\' && s != '/') {
92 base += getOsSeparator();
93 }
94
95 DefaultLogger::get()->info("Import root directory is \'" + base + "\'");
96 }
97
98 /** Destructor. */
99 ~FileSystemFilter()
100 {
101 // haha
102 }
103
104 // -------------------------------------------------------------------
105 /** Tests for the existence of a file at the given path. */
106 bool Exists( const char* pFile) const
107 {
108 std::string tmp = pFile;
109
110 // Currently this IOSystem is also used to open THE ONE FILE.
111 if (tmp != src_file) {
112 BuildPath(tmp);
113 Cleanup(tmp);
114 }
115
116 return wrapped->Exists(tmp);
117 }
118
119 // -------------------------------------------------------------------
120 /** Returns the directory separator. */
121 char getOsSeparator() const
122 {
123 return sep;
124 }
125
126 // -------------------------------------------------------------------
127 /** Open a new file with a given path. */
128 IOStream* Open( const char* pFile, const char* pMode = "rb")
129 {
130 ai_assert(pFile);
131 ai_assert(pMode);
132
133 // First try the unchanged path
134 IOStream* s = wrapped->Open(pFile,pMode);
135
136 if (!s) {
137 std::string tmp = pFile;
138
139 // Try to convert between absolute and relative paths
140 BuildPath(tmp);
141 s = wrapped->Open(tmp,pMode);
142
143 if (!s) {
144 // Finally, look for typical issues with paths
145 // and try to correct them. This is our last
146 // resort.
147 tmp = pFile;
148 Cleanup(tmp);
149 BuildPath(tmp);
150 s = wrapped->Open(tmp,pMode);
151 }
152 }
153
154 return s;
155 }
156
157 // -------------------------------------------------------------------
158 /** Closes the given file and releases all resources associated with it. */
159 void Close( IOStream* pFile)
160 {
161 return wrapped->Close(pFile);
162 }
163
164 // -------------------------------------------------------------------
165 /** Compare two paths */
166 bool ComparePaths (const char* one, const char* second) const
167 {
168 return wrapped->ComparePaths (one,second);
169 }
170
171private:
172
173 // -------------------------------------------------------------------
174 /** Build a valid path from a given relative or absolute path.
175 */
176 void BuildPath (std::string& in) const
177 {
178 // if we can already access the file, great.
179 if (in.length() < 3 || wrapped->Exists(in)) {
180 return;
181 }
182
183 // Determine whether this is a relative path (Windows-specific - most assets are packaged on Windows).
184 if (in[1] != ':') {
185
186 // append base path and try
187 const std::string tmp = base + in;
188 if (wrapped->Exists(tmp)) {
189 in = tmp;
190 return;
191 }
192 }
193
194 // Chop of the file name and look in the model directory, if
195 // this fails try all sub paths of the given path, i.e.
196 // if the given path is foo/bar/something.lwo, try
197 // <base>/something.lwo
198 // <base>/bar/something.lwo
199 // <base>/foo/bar/something.lwo
200 std::string::size_type pos = in.rfind('/');
201 if (std::string::npos == pos) {
202 pos = in.rfind('\\');
203 }
204
205 if (std::string::npos != pos) {
206 std::string tmp;
207 std::string::size_type last_dirsep = std::string::npos;
208
209 while(true) {
210 tmp = base;
211 tmp += sep;
212
213 std::string::size_type dirsep = in.rfind('/', last_dirsep);
214 if (std::string::npos == dirsep) {
215 dirsep = in.rfind('\\', last_dirsep);
216 }
217
218 if (std::string::npos == dirsep || dirsep == 0) {
219 // we did try this already.
220 break;
221 }
222
223 last_dirsep = dirsep-1;
224
225 tmp += in.substr(dirsep+1, in.length()-pos);
226 if (wrapped->Exists(tmp)) {
227 in = tmp;
228 return;
229 }
230 }
231 }
232
233 // hopefully the underlying file system has another few tricks to access this file ...
234 }
235
236 // -------------------------------------------------------------------
237 /** Cleanup the given path
238 */
239 void Cleanup (std::string& in) const
240 {
241 char last = 0;
242 if(in.empty()) {
243 return;
244 }
245
246 // Remove a very common issue when we're parsing file names: spaces at the
247 // beginning of the path.
248 std::string::iterator it = in.begin();
249 while (IsSpaceOrNewLine( *it ))++it;
250 if (it != in.begin()) {
251 in.erase(in.begin(),it+1);
252 }
253
254 const char sep = getOsSeparator();
255 for (it = in.begin(); it != in.end(); ++it) {
256 // Exclude :// and \\, which remain untouched.
257 // https://sourceforge.net/tracker/?func=detail&aid=3031725&group_id=226462&atid=1067632
258 if ( !strncmp(&*it, "://", 3 )) {
259 it += 3;
260 continue;
261 }
262 if (it == in.begin() && !strncmp(&*it, "\\\\", 2)) {
263 it += 2;
264 continue;
265 }
266
267 // Cleanup path delimiters
268 if (*it == '/' || (*it) == '\\') {
269 *it = sep;
270
271 // And we're removing double delimiters, frequent issue with
272 // incorrectly composited paths ...
273 if (last == *it) {
274 it = in.erase(it);
275 --it;
276 }
277 }
278 else if (*it == '%' && in.end() - it > 2) {
279
280 // Hex sequence in URIs
281 if( IsHex((&*it)[0]) && IsHex((&*it)[1]) ) {
282 *it = HexOctetToDecimal(&*it);
283 it = in.erase(it+1,it+2);
284 --it;
285 }
286 }
287
288 last = *it;
289 }
290 }
291
292private:
293 IOSystem* wrapped;
294 std::string src_file, base;
295 char sep;
296};
297
298} //!ns Assimp
299
300#endif //AI_DEFAULTIOSYSTEM_H_INC
301