1 | #include "FilePath.h"
|
---|
2 | #include "File.h"
|
---|
3 |
|
---|
4 | #include <string.h>
|
---|
5 | #include <ctype.h>
|
---|
6 | #include <stdarg.h>
|
---|
7 | #include <stdio.h>
|
---|
8 | //#include <dir.h>
|
---|
9 | #include <direct.h>
|
---|
10 |
|
---|
11 | static char divider = '\\';
|
---|
12 | static char dividerStr[] = "\\";
|
---|
13 |
|
---|
14 | bool __pathIsAValidRoot(string path)
|
---|
15 | {
|
---|
16 | DWORD driveMask;
|
---|
17 | int driveNum;
|
---|
18 |
|
---|
19 | if (path.length() > 3)
|
---|
20 | {
|
---|
21 | return false;
|
---|
22 | }
|
---|
23 |
|
---|
24 | if (path[1] != ':' || !isalpha(path[0]))
|
---|
25 | {
|
---|
26 | return false;
|
---|
27 | }
|
---|
28 |
|
---|
29 | if (isupper(path[0]))
|
---|
30 | {
|
---|
31 | driveNum = path[0] - 'A';
|
---|
32 | }
|
---|
33 |
|
---|
34 | else
|
---|
35 | {
|
---|
36 | driveNum = path[0] - 'a';
|
---|
37 | }
|
---|
38 |
|
---|
39 | if (driveNum > 25)
|
---|
40 | {
|
---|
41 | return false;
|
---|
42 | }
|
---|
43 |
|
---|
44 | driveMask = GetLogicalDrives();
|
---|
45 |
|
---|
46 | if ((driveMask & (1 << driveNum)) != (1 << driveNum))
|
---|
47 | {
|
---|
48 | return false;
|
---|
49 | }
|
---|
50 |
|
---|
51 | return true;
|
---|
52 | }
|
---|
53 |
|
---|
54 | FilePath::FilePath() {}
|
---|
55 |
|
---|
56 | FilePath::FilePath(int npaths, ...)
|
---|
57 | {
|
---|
58 | va_list list;
|
---|
59 | string* pathArray;
|
---|
60 |
|
---|
61 | pathArray = new string[npaths];
|
---|
62 |
|
---|
63 | // initialise use of variable parameters
|
---|
64 | va_start(list, npaths);
|
---|
65 |
|
---|
66 | if (pathArray)
|
---|
67 | {
|
---|
68 | for (int i = 0; i < npaths; i ++)
|
---|
69 | {
|
---|
70 | // get next parameter (guaranteed to be a char * by caller!!!)
|
---|
71 | pathArray[i] = va_arg(list, char *);
|
---|
72 |
|
---|
73 | // discount leading slashes
|
---|
74 | if (pathArray[i][0] == divider)
|
---|
75 | {
|
---|
76 | pathArray[i] = pathArray[i].substr(1);
|
---|
77 | }
|
---|
78 |
|
---|
79 | // get rid of terminating slashes if present
|
---|
80 | if (pathArray[i][pathArray[i].length() - 1] == divider)
|
---|
81 | {
|
---|
82 | pathArray[i] = pathArray[i].substr(0, pathArray[i].length() - 1);
|
---|
83 | }
|
---|
84 | }
|
---|
85 | }
|
---|
86 |
|
---|
87 | // end of variable arguments
|
---|
88 | va_end(list);
|
---|
89 |
|
---|
90 | // construct path in the usual manner
|
---|
91 | if (npaths > 0)
|
---|
92 | {
|
---|
93 | this->path = pathArray[0];
|
---|
94 | }
|
---|
95 |
|
---|
96 | for (int i = 1; i < npaths; i ++)
|
---|
97 | {
|
---|
98 | // don't add slashes if suppressed
|
---|
99 | if (pathArray[i][0] != '!')
|
---|
100 | {
|
---|
101 | this->path.append("\\");
|
---|
102 | this->path.append(pathArray[i]);
|
---|
103 | }
|
---|
104 |
|
---|
105 | // if slashes suppressed, skip the magic character
|
---|
106 | else
|
---|
107 | {
|
---|
108 | this->path.append(pathArray[i].substr(1));
|
---|
109 | }
|
---|
110 | }
|
---|
111 |
|
---|
112 | // destroy temporary array
|
---|
113 | if (pathArray)
|
---|
114 | {
|
---|
115 | delete[] pathArray;
|
---|
116 | }
|
---|
117 |
|
---|
118 | // set if this is a valid root (unlikely in this circumstance)
|
---|
119 | this->is_aRoot = __pathIsAValidRoot(this->path);
|
---|
120 | }
|
---|
121 |
|
---|
122 | void FilePath::_init(string filePath, string subPath)
|
---|
123 | {
|
---|
124 | // copy the directory path
|
---|
125 | this->path = filePath;
|
---|
126 |
|
---|
127 | // if it didn't have a concluding divider, then add one; note that if
|
---|
128 | // filePath is empty, we're not actually going to add any divider at
|
---|
129 | // all
|
---|
130 |
|
---|
131 | if (filePath.length() > 0 &&
|
---|
132 | this->path[filePath.length()-1] != divider)
|
---|
133 | {
|
---|
134 | this->path.append(dividerStr);
|
---|
135 | }
|
---|
136 |
|
---|
137 | // copy in the subdirectory path, eliminating any leading divider
|
---|
138 | if (subPath[0] != divider)
|
---|
139 | {
|
---|
140 | this->path.append(subPath);
|
---|
141 | }
|
---|
142 | else
|
---|
143 | {
|
---|
144 | this->path.append(subPath.substr(1));
|
---|
145 | }
|
---|
146 |
|
---|
147 | // set if this is a valid root (unlikely when joining two paths)
|
---|
148 | this->is_aRoot = __pathIsAValidRoot(this->path);
|
---|
149 | }
|
---|
150 |
|
---|
151 | /**
|
---|
152 | * Construct a Filepath from a directory and a sub-directory path
|
---|
153 | */
|
---|
154 |
|
---|
155 | FilePath::FilePath(string filePath, string subPath)
|
---|
156 | {
|
---|
157 | this->_init(filePath, subPath);
|
---|
158 | }
|
---|
159 |
|
---|
160 | FilePath::FilePath(FilePath &path, string subPath)
|
---|
161 | {
|
---|
162 | this->_init(path.pathString(), subPath);
|
---|
163 | }
|
---|
164 |
|
---|
165 | FilePath::FilePath(string filePath)
|
---|
166 | {
|
---|
167 | if (filePath.length() < 3)
|
---|
168 | {
|
---|
169 | this->path = filePath;
|
---|
170 | this->path.append("\\");
|
---|
171 | }
|
---|
172 | else
|
---|
173 | {
|
---|
174 | this->path = filePath;
|
---|
175 |
|
---|
176 | // trim trailing directory separators
|
---|
177 | if (this->path[this->path.length() - 1] == '\\')
|
---|
178 | {
|
---|
179 | this->path = this->path.substr(0, this->path.length() - 1);
|
---|
180 | }
|
---|
181 | }
|
---|
182 |
|
---|
183 | // check for a valid root
|
---|
184 | this->is_aRoot = __pathIsAValidRoot(this->path);
|
---|
185 | }
|
---|
186 |
|
---|
187 | bool FilePath::isEmpty()
|
---|
188 | {
|
---|
189 | return this->path.length() == 0;
|
---|
190 | }
|
---|
191 |
|
---|
192 | bool FilePath::isRoot()
|
---|
193 | {
|
---|
194 | return this->is_aRoot;
|
---|
195 | }
|
---|
196 |
|
---|
197 | int FilePath::compare(const FilePath &other) const
|
---|
198 | { string::const_iterator thisc = this->path.begin();
|
---|
199 | string::const_iterator otherc = other.path.begin();
|
---|
200 |
|
---|
201 | while (thisc != this->path.end() && otherc != other.path.end())
|
---|
202 | { if (toupper(*thisc) != toupper(*otherc))
|
---|
203 | { return (toupper(*thisc) - toupper(*otherc));
|
---|
204 | }
|
---|
205 | thisc ++;
|
---|
206 | otherc ++;
|
---|
207 | }
|
---|
208 |
|
---|
209 | return this->path.size() - other.path.size();
|
---|
210 | }
|
---|
211 |
|
---|
212 | int FilePath::compare(const char *other) const
|
---|
213 | {
|
---|
214 | return strcmpi(this->path.c_str(), other);
|
---|
215 | }
|
---|
216 |
|
---|
217 | /**
|
---|
218 | * Create a filepath of the drive letter/colon only
|
---|
219 | */
|
---|
220 | FilePath *FilePath::rootDrive()
|
---|
221 | {
|
---|
222 | FilePath *reply = this->root();
|
---|
223 |
|
---|
224 | if (reply->path.length() >= 2 &&
|
---|
225 | reply->path[1] == ':' &&
|
---|
226 | isalpha(reply->path[0])) // if this is a drive letter destination
|
---|
227 | {
|
---|
228 | reply->path = reply->path.substr(0, 2);
|
---|
229 | }
|
---|
230 | else
|
---|
231 | {
|
---|
232 | reply->path = "";
|
---|
233 | }
|
---|
234 | return reply;
|
---|
235 | }
|
---|
236 |
|
---|
237 | /**
|
---|
238 | * Create a filepath of the root of the given location
|
---|
239 | */
|
---|
240 | FilePath *FilePath::root()
|
---|
241 | {
|
---|
242 | // TODO: cope with network destinations
|
---|
243 | FilePath *reply = new FilePath(this->path);
|
---|
244 |
|
---|
245 | unsigned int truncate = reply->path.find(divider);
|
---|
246 | if (truncate != -1) // if it's got a divider then skip past it;
|
---|
247 | // we will assume that it's a drive letter first
|
---|
248 | {
|
---|
249 | truncate ++;
|
---|
250 | reply->path = reply->path.substr(0, truncate);
|
---|
251 | }
|
---|
252 |
|
---|
253 | // if we've got a drive, then we'll use it plus the divider
|
---|
254 | else if (reply->path[1] == ':' && reply->path.length() == 2 &&
|
---|
255 | isalpha(reply->path[0]))
|
---|
256 | {
|
---|
257 | reply->path = reply->path + dividerStr;
|
---|
258 | }
|
---|
259 | else
|
---|
260 | {
|
---|
261 | reply->path = "";
|
---|
262 | }
|
---|
263 | return reply;
|
---|
264 | }
|
---|
265 |
|
---|
266 | /**
|
---|
267 | * Just a quick check on the existence of a file.
|
---|
268 | */
|
---|
269 | bool FilePath::exists()
|
---|
270 | { OFSTRUCT of;
|
---|
271 | string testpath;
|
---|
272 | bool reply;
|
---|
273 | DWORD attrs;
|
---|
274 |
|
---|
275 | if (this->path.length() < 5)
|
---|
276 | { return true;
|
---|
277 | }
|
---|
278 | if (this->path[this->path.length()-1] == '\\')
|
---|
279 | { testpath = this->path.substr(0, this->path.length()-1);
|
---|
280 | }
|
---|
281 | else
|
---|
282 | { testpath = this->path;
|
---|
283 | }
|
---|
284 | attrs = GetFileAttributes((LPCSTR) testpath.c_str());
|
---|
285 | if ((attrs == 0xffffffff) || ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0))
|
---|
286 | { reply = !(OpenFile((LPCSTR) testpath.c_str(), (LPOFSTRUCT) &of, OF_EXIST) == HFILE_ERROR);
|
---|
287 | }
|
---|
288 | else
|
---|
289 | { reply = true;
|
---|
290 | }
|
---|
291 | return reply;
|
---|
292 | }
|
---|
293 |
|
---|
294 | /**
|
---|
295 | * Get the immediate parent of the given filepath
|
---|
296 | */
|
---|
297 | FilePath *FilePath::parent()
|
---|
298 | {
|
---|
299 | // TODO: cope with network roots
|
---|
300 | FilePath *reply = new FilePath(this->path);
|
---|
301 |
|
---|
302 | // get the last divider
|
---|
303 | unsigned int truncate = reply->path.find_last_of(divider);
|
---|
304 |
|
---|
305 | // if it exists, then we can do a normal get parent
|
---|
306 | if (truncate != -1)
|
---|
307 | {
|
---|
308 | // if it's the leftmost one as well then we'll skip it to
|
---|
309 | // give a parent of <drive>:\ as this patches some problems
|
---|
310 | // with windows calls! Note that a parent call to the returned
|
---|
311 | // object in this case will return itself
|
---|
312 | if (truncate == reply->path.find(divider))
|
---|
313 | {
|
---|
314 | truncate ++;
|
---|
315 | }
|
---|
316 | reply->path = reply->path.substr(0, truncate);
|
---|
317 | }
|
---|
318 |
|
---|
319 | // if we've got a drive letter only, then append the divider to
|
---|
320 | // patch problematic windows calls; again subsequent parent
|
---|
321 | // calls to our returned object would in effect return itself
|
---|
322 | else if (reply->path[1] == ':' && reply->path.length() == 2 &&
|
---|
323 | isalpha(reply->path[0]))
|
---|
324 | {
|
---|
325 | reply->path = reply->path + dividerStr;
|
---|
326 | }
|
---|
327 |
|
---|
328 | // a non-rooted object; return blank
|
---|
329 | else
|
---|
330 | {
|
---|
331 | reply->path = "";
|
---|
332 | }
|
---|
333 |
|
---|
334 | reply->is_aRoot = __pathIsAValidRoot(reply->path);
|
---|
335 | return reply;
|
---|
336 | }
|
---|
337 |
|
---|
338 | /**
|
---|
339 | * Check that the given path is a child/descendant of this path
|
---|
340 | */
|
---|
341 | bool FilePath::isAncestorOf(string pathString)
|
---|
342 | {
|
---|
343 | if (this->path == "")
|
---|
344 | return false;
|
---|
345 |
|
---|
346 | if (pathString.length() < this->path.length())
|
---|
347 | { return false;
|
---|
348 | }
|
---|
349 | if (pathString.substr(0, this->path.length()) != this->path)
|
---|
350 | { return false;
|
---|
351 | }
|
---|
352 | if (pathString.length() == this->path.length())
|
---|
353 | { return false;
|
---|
354 | }
|
---|
355 | if (pathString[this->path.length()-1] != '\\' &&
|
---|
356 | pathString[this->path.length()] != '\\')
|
---|
357 | { return false;
|
---|
358 | }
|
---|
359 | // can't be a proper ancestor of itself
|
---|
360 | return true;
|
---|
361 | }
|
---|
362 |
|
---|
363 | FilePath *FilePath::append(FilePath &subPath)
|
---|
364 | {
|
---|
365 | return new FilePath(this->path, subPath.path);
|
---|
366 | }
|
---|
367 |
|
---|
368 | FilePath *FilePath::append(char *subPath)
|
---|
369 | {
|
---|
370 | return new FilePath(this->path, subPath);
|
---|
371 | }
|
---|
372 |
|
---|
373 | const char *FilePath::cString()
|
---|
374 | {
|
---|
375 | return this->path.c_str();
|
---|
376 | }
|
---|
377 |
|
---|
378 | string FilePath::pathString()
|
---|
379 | {
|
---|
380 | return this->path;
|
---|
381 | }
|
---|
382 |
|
---|
383 | bool FilePath::ensureWriteablePath()
|
---|
384 | {
|
---|
385 | //TODO: don't assume that all roots are writeable!
|
---|
386 | if (this->isRoot())
|
---|
387 | {
|
---|
388 | return true;
|
---|
389 | }
|
---|
390 |
|
---|
391 | // does this path exist?
|
---|
392 | File thisFile(this->path);
|
---|
393 | bool reply = false;
|
---|
394 |
|
---|
395 | // if not, ensure writeable parent
|
---|
396 | if (thisFile.exists() == false)
|
---|
397 | {
|
---|
398 | FilePath *parent;
|
---|
399 |
|
---|
400 | // create parent reference
|
---|
401 | parent = this->parent();
|
---|
402 |
|
---|
403 | if (*parent == thisFile)
|
---|
404 | {
|
---|
405 | delete parent;
|
---|
406 | return true;
|
---|
407 | }
|
---|
408 |
|
---|
409 | // if parent is writeable then try to make the directory
|
---|
410 | if (parent->ensureWriteablePath())
|
---|
411 | {
|
---|
412 | // make this directory; if it succeeded we reply positively
|
---|
413 | if (_mkdir(this->path.c_str()) == 0)
|
---|
414 | {
|
---|
415 | reply = true;
|
---|
416 | }
|
---|
417 | }
|
---|
418 |
|
---|
419 | // destroy parent reference
|
---|
420 | delete parent;
|
---|
421 | }
|
---|
422 |
|
---|
423 | else if (thisFile.isDirectory() == true)
|
---|
424 | {
|
---|
425 | // is this writeable by user?; if so we're okay
|
---|
426 | if (thisFile.isWriteable() || true)
|
---|
427 | {
|
---|
428 | reply = true;
|
---|
429 | }
|
---|
430 | }
|
---|
431 | return reply;
|
---|
432 | }
|
---|