132
|
1 /*******************************************************************************
|
|
2
|
|
3 copyright: Copyright (c) 2004 Kris Bell. All rights reserved
|
|
4
|
|
5 license: BSD style: $(LICENSE)
|
|
6
|
|
7 version: Oct 2004: Initial version
|
|
8 version: Nov 2006: Australian version
|
|
9 version: Feb 2007: Mutating version
|
|
10 version: Mar 2007: Folded FileProxy in
|
|
11 version: Nov 2007: VFS dictates '/' always be used
|
|
12
|
|
13 author: Kris
|
|
14
|
|
15 *******************************************************************************/
|
|
16
|
|
17 module tango.io.FilePath;
|
|
18
|
|
19 private import tango.sys.Common;
|
|
20
|
|
21 private import tango.io.FileConst;
|
|
22
|
|
23 private import tango.core.Exception;
|
|
24
|
|
25 private import tango.time.Time;
|
|
26
|
|
27 /*******************************************************************************
|
|
28
|
|
29 *******************************************************************************/
|
|
30
|
|
31 version (Win32)
|
|
32 {
|
|
33 version (Win32SansUnicode)
|
|
34 {
|
|
35 alias char T;
|
|
36 private extern (C) int strlen (char *s);
|
|
37 private alias WIN32_FIND_DATA FIND_DATA;
|
|
38 }
|
|
39 else
|
|
40 {
|
|
41 alias wchar T;
|
|
42 private extern (C) int wcslen (wchar *s);
|
|
43 private alias WIN32_FIND_DATAW FIND_DATA;
|
|
44 }
|
|
45 }
|
|
46
|
|
47 version (Posix)
|
|
48 {
|
|
49 private import tango.stdc.stdio;
|
|
50 private import tango.stdc.string;
|
|
51 private import tango.stdc.posix.utime;
|
|
52 private import tango.stdc.posix.dirent;
|
|
53 }
|
|
54
|
|
55 /*******************************************************************************
|
|
56
|
|
57 *******************************************************************************/
|
|
58
|
|
59 private extern (C) void memmove (void* dst, void* src, uint bytes);
|
|
60
|
|
61 /*******************************************************************************
|
|
62
|
|
63 Models a file path. These are expected to be used as the constructor
|
|
64 argument to various file classes. The intention is that they easily
|
|
65 convert to other representations such as absolute, canonical, or Url.
|
|
66
|
|
67 File paths containing non-ansi characters should be UTF-8 encoded.
|
|
68 Supporting Unicode in this manner was deemed to be more suitable
|
|
69 than providing a wchar version of FilePath, and is both consistent
|
|
70 & compatible with the approach taken with the Uri class.
|
|
71
|
|
72 FilePath is designed to be transformed, thus each mutating method
|
|
73 modifies the internal content. There is a read-only base-class
|
|
74 called PathView, which can be used to provide a view into the
|
|
75 content as desired.
|
|
76
|
|
77 Note that patterns of adjacent '.' separators are treated specially
|
|
78 in that they will be assigned to the name instead of the suffix. In
|
|
79 addition, a '.' at the start of a name signifies it does not belong
|
|
80 to the suffix i.e. ".file" is a name rather than a suffix.
|
|
81
|
|
82 Note also that normalization of path-separators occurs by default.
|
|
83 This means that the use of '\' characters with be converted into
|
|
84 '/' instead while parsing. To mutate the path into an O/S native
|
|
85 version, use the native() method. To obtain a copy instead, use the
|
|
86 path.dup.native sequence
|
|
87
|
|
88 Compile with -version=Win32SansUnicode to enable Win95 & Win32s file
|
|
89 support.
|
|
90
|
|
91 *******************************************************************************/
|
|
92
|
|
93 class FilePath : PathView
|
|
94 {
|
|
95 private char[] fp; // filepath with trailing 0
|
|
96
|
|
97 private bool dir_; // this represents a dir?
|
|
98
|
|
99 private int end_, // before the trailing 0
|
|
100 name_, // file/dir name
|
|
101 folder_, // path before name
|
|
102 suffix_; // after rightmost '.'
|
|
103
|
|
104 public alias set opAssign; // path = x;
|
|
105 public alias append opCatAssign; // path ~= x;
|
|
106
|
|
107 /***********************************************************************
|
|
108
|
|
109 Filter used for screening paths via toList()
|
|
110
|
|
111 ***********************************************************************/
|
|
112
|
|
113 public alias bool delegate (FilePath, bool) Filter;
|
|
114
|
|
115 /***********************************************************************
|
|
116
|
|
117 Passed around during file-scanning
|
|
118
|
|
119 ***********************************************************************/
|
|
120
|
|
121 struct FileInfo
|
|
122 {
|
|
123 char[] path,
|
|
124 name;
|
|
125 ulong bytes;
|
|
126 bool folder;
|
|
127 }
|
|
128
|
|
129 /***********************************************************************
|
|
130
|
|
131 Call-site shortcut to create a FilePath instance. This
|
|
132 enables the same syntax as struct usage, so may expose
|
|
133 a migration path
|
|
134
|
|
135 ***********************************************************************/
|
|
136
|
|
137 static FilePath opCall (char[] filepath = null)
|
|
138 {
|
|
139 return new FilePath (filepath);
|
|
140 }
|
|
141
|
|
142 /***********************************************************************
|
|
143
|
|
144 Create a FilePath from a copy of the provided string.
|
|
145
|
|
146 FilePath assumes both path & name are present, and therefore
|
|
147 may split what is otherwise a logically valid path. That is,
|
|
148 the 'name' of a file is typically the path segment following
|
|
149 a rightmost path-separator. The intent is to treat files and
|
|
150 directories in the same manner; as a name with an optional
|
|
151 ancestral structure. It is possible to bias the interpretation
|
|
152 by adding a trailing path-separator to the argument. Doing so
|
|
153 will result in an empty name attribute.
|
|
154
|
|
155 With regard to the filepath copy, we found the common case to
|
|
156 be an explicit .dup, whereas aliasing appeared to be rare by
|
|
157 comparison. We also noted a large proportion interacting with
|
|
158 C-oriented OS calls, implying the postfix of a null terminator.
|
|
159 Thus, FilePath combines both as a single operation.
|
|
160
|
|
161 ***********************************************************************/
|
|
162
|
|
163 this (char[] filepath = null)
|
|
164 {
|
|
165 set (filepath);
|
|
166 }
|
|
167
|
|
168 // now converts to '/' always. Use toNative to get Win32 paths
|
|
169 deprecated this (char[] filepath, bool native)
|
|
170 {
|
|
171 set (filepath);
|
|
172 }
|
|
173
|
|
174 /***********************************************************************
|
|
175
|
|
176 Return the complete text of this filepath
|
|
177
|
|
178 ***********************************************************************/
|
|
179
|
|
180 final char[] toString ()
|
|
181 {
|
|
182 return fp [0 .. end_];
|
|
183 }
|
|
184
|
|
185 /***********************************************************************
|
|
186
|
|
187 Duplicate this path
|
|
188
|
|
189 ***********************************************************************/
|
|
190
|
|
191 final FilePath dup ()
|
|
192 {
|
|
193 return FilePath (toString);
|
|
194 }
|
|
195
|
|
196 /***********************************************************************
|
|
197
|
|
198 Return the complete text of this filepath as a null
|
|
199 terminated string for use with a C api. Use toString
|
|
200 instead for any D api.
|
|
201
|
|
202 Note that the nul is always embedded within the string
|
|
203 maintained by FilePath, so there's no heap overhead when
|
|
204 making a C call
|
|
205
|
|
206 ***********************************************************************/
|
|
207
|
|
208 final char[] cString ()
|
|
209 {
|
|
210 return fp [0 .. end_+1];
|
|
211 }
|
|
212
|
|
213 /***********************************************************************
|
|
214
|
|
215 Return the root of this path. Roots are constructs such as
|
|
216 "c:"
|
|
217
|
|
218 ***********************************************************************/
|
|
219
|
|
220 final char[] root ()
|
|
221 {
|
|
222 return fp [0 .. folder_];
|
|
223 }
|
|
224
|
|
225 /***********************************************************************
|
|
226
|
|
227 Return the file path. Paths may start and end with a "/".
|
|
228 The root path is "/" and an unspecified path is returned as
|
|
229 an empty string. Directory paths may be split such that the
|
|
230 directory name is placed into the 'name' member; directory
|
|
231 paths are treated no differently than file paths
|
|
232
|
|
233 ***********************************************************************/
|
|
234
|
|
235 final char[] folder ()
|
|
236 {
|
|
237 return fp [folder_ .. name_];
|
|
238 }
|
|
239
|
|
240 /***********************************************************************
|
|
241
|
|
242 Returns a path representing the parent of this one. This
|
|
243 will typically return the current path component, though
|
|
244 with a special case where the name component is empty. In
|
|
245 such cases, the path is scanned for a prior segment:
|
|
246 ---
|
|
247 normal: /x/y/z => /x/y
|
|
248 special: /x/y/ => /x
|
|
249 ---
|
|
250
|
|
251 Note that this returns a path suitable for splitting into
|
|
252 path and name components (there's no trailing separator).
|
|
253
|
|
254 See pop() also, which is generally more useful when working
|
|
255 with FilePath instances
|
|
256
|
|
257 ***********************************************************************/
|
|
258
|
|
259 final char[] parent ()
|
|
260 {
|
|
261 auto p = path;
|
|
262 if (name.length is 0)
|
|
263 for (int i=p.length-1; --i > 0;)
|
|
264 if (p[i] is FileConst.PathSeparatorChar)
|
|
265 {
|
|
266 p = p[0 .. i];
|
|
267 break;
|
|
268 }
|
|
269 return stripped (p);
|
|
270 }
|
|
271
|
|
272 /***********************************************************************
|
|
273
|
|
274 Return the name of this file, or directory.
|
|
275
|
|
276 ***********************************************************************/
|
|
277
|
|
278 final char[] name ()
|
|
279 {
|
|
280 return fp [name_ .. suffix_];
|
|
281 }
|
|
282
|
|
283 /***********************************************************************
|
|
284
|
|
285 Ext is the tail of the filename, rightward of the rightmost
|
|
286 '.' separator e.g. path "foo.bar" has ext "bar". Note that
|
|
287 patterns of adjacent separators are treated specially; for
|
|
288 example, ".." will wind up with no ext at all
|
|
289
|
|
290 ***********************************************************************/
|
|
291
|
|
292 final char[] ext ()
|
|
293 {
|
|
294 auto x = suffix;
|
|
295 if (x.length)
|
|
296 x = x [1..$];
|
|
297 return x;
|
|
298 }
|
|
299
|
|
300 /***********************************************************************
|
|
301
|
|
302 Suffix is like ext, but includes the separator e.g. path
|
|
303 "foo.bar" has suffix ".bar"
|
|
304
|
|
305 ***********************************************************************/
|
|
306
|
|
307 final char[] suffix ()
|
|
308 {
|
|
309 return fp [suffix_ .. end_];
|
|
310 }
|
|
311
|
|
312 /***********************************************************************
|
|
313
|
|
314 return the root + folder combination
|
|
315
|
|
316 ***********************************************************************/
|
|
317
|
|
318 final char[] path ()
|
|
319 {
|
|
320 return fp [0 .. name_];
|
|
321 }
|
|
322
|
|
323 /***********************************************************************
|
|
324
|
|
325 return the name + suffix combination
|
|
326
|
|
327 ***********************************************************************/
|
|
328
|
|
329 final char[] file ()
|
|
330 {
|
|
331 return fp [name_ .. end_];
|
|
332 }
|
|
333
|
|
334 /***********************************************************************
|
|
335
|
|
336 Returns true if all fields are equal.
|
|
337
|
|
338 ***********************************************************************/
|
|
339
|
|
340 final override int opEquals (Object o)
|
|
341 {
|
|
342 return (this is o) || (o && toString == o.toString);
|
|
343 }
|
|
344
|
|
345 /***********************************************************************
|
|
346
|
|
347 Does this FilePath equate to the given text?
|
|
348
|
|
349 ***********************************************************************/
|
|
350
|
|
351 final override int opEquals (char[] s)
|
|
352 {
|
|
353 return toString() == s;
|
|
354 }
|
|
355
|
|
356 /***********************************************************************
|
|
357
|
|
358 Returns true if this FilePath is *not* relative to the
|
|
359 current working directory
|
|
360
|
|
361 ***********************************************************************/
|
|
362
|
|
363 final bool isAbsolute ()
|
|
364 {
|
|
365 return (folder_ > 0) ||
|
|
366 (folder_ < end_ && fp[folder_] is FileConst.PathSeparatorChar);
|
|
367 }
|
|
368
|
|
369 /***********************************************************************
|
|
370
|
|
371 Returns true if this FilePath is empty
|
|
372
|
|
373 ***********************************************************************/
|
|
374
|
|
375 final bool isEmpty ()
|
|
376 {
|
|
377 return end_ is 0;
|
|
378 }
|
|
379
|
|
380 /***********************************************************************
|
|
381
|
|
382 Returns true if this FilePath has a parent. Note that a
|
|
383 parent is defined by the presence of a path-separator in
|
|
384 the path. This means 'foo' within "\foo" is considered a
|
|
385 child of the root
|
|
386
|
|
387 ***********************************************************************/
|
|
388
|
|
389 final bool isChild ()
|
|
390 {
|
|
391 return folder.length > 0;
|
|
392 }
|
|
393
|
|
394 /***********************************************************************
|
|
395
|
|
396 Replace all 'from' instances with 'to'
|
|
397
|
|
398 ***********************************************************************/
|
|
399
|
|
400 final FilePath replace (char from, char to)
|
|
401 {
|
|
402 foreach (inout char c; path)
|
|
403 if (c is from)
|
|
404 c = to;
|
|
405 return this;
|
|
406 }
|
|
407
|
|
408 /***********************************************************************
|
|
409
|
|
410 Convert path separators to a standard format, using '/' as
|
|
411 the path separator. This is compatible with URI and all of
|
|
412 the contemporary O/S which Tango supports. Known exceptions
|
|
413 include the Windows command-line processor, which considers
|
|
414 '/' characters to be switches instead. Use the native()
|
|
415 method to support that.
|
|
416
|
|
417 Note: mutates the current path.
|
|
418
|
|
419 ***********************************************************************/
|
|
420
|
|
421 final FilePath standard ()
|
|
422 {
|
|
423 return replace ('\\', '/');
|
|
424 }
|
|
425
|
|
426 /***********************************************************************
|
|
427
|
|
428 Convert to native O/S path separators where that is required,
|
|
429 such as when dealing with the Windows command-line.
|
|
430
|
|
431 Note: mutates the current path. Use this pattern to obtain a
|
|
432 copy instead: path.dup.native
|
|
433
|
|
434 ***********************************************************************/
|
|
435
|
|
436 final FilePath native ()
|
|
437 {
|
|
438 version (Win32)
|
|
439 return replace ('/', '\\');
|
|
440 else
|
|
441 return this;
|
|
442 }
|
|
443
|
|
444 /***********************************************************************
|
|
445
|
|
446 Concatenate text to this path; no separators are added.
|
|
447 See join() also
|
|
448
|
|
449 ***********************************************************************/
|
|
450
|
|
451 final FilePath cat (char[][] others...)
|
|
452 {
|
|
453 foreach (other; others)
|
|
454 {
|
|
455 auto len = end_ + other.length;
|
|
456 expand (len);
|
|
457 fp [end_ .. len] = other;
|
|
458 fp [len] = 0;
|
|
459 end_ = len;
|
|
460 }
|
|
461 return parse;
|
|
462 }
|
|
463
|
|
464 /***********************************************************************
|
|
465
|
|
466 Append a folder to this path. A leading separator is added
|
|
467 as required
|
|
468
|
|
469 ***********************************************************************/
|
|
470
|
|
471 final FilePath append (char[] path)
|
|
472 {
|
|
473 if (file.length)
|
|
474 path = prefixed (path);
|
|
475 return cat (path);
|
|
476 }
|
|
477
|
|
478 /***********************************************************************
|
|
479
|
|
480 Prepend a folder to this path. A trailing separator is added
|
|
481 if needed
|
|
482
|
|
483 ***********************************************************************/
|
|
484
|
|
485 final FilePath prepend (char[] path)
|
|
486 {
|
|
487 adjust (0, folder_, folder_, padded (path));
|
|
488 return parse;
|
|
489 }
|
|
490
|
|
491 /***********************************************************************
|
|
492
|
|
493 Reset the content of this path to that of another and
|
|
494 reparse
|
|
495
|
|
496 ***********************************************************************/
|
|
497
|
|
498 FilePath set (FilePath path)
|
|
499 {
|
|
500 return set (path.toString);
|
|
501 }
|
|
502
|
|
503 /***********************************************************************
|
|
504
|
|
505 Reset the content of this path, and reparse.
|
|
506
|
|
507 ***********************************************************************/
|
|
508
|
|
509 final FilePath set (char[] path)
|
|
510 {
|
|
511 end_ = path.length;
|
|
512
|
|
513 expand (end_);
|
|
514 if (end_)
|
|
515 fp[0 .. end_] = path;
|
|
516
|
|
517 fp[end_] = '\0';
|
|
518 return parse;
|
|
519 }
|
|
520
|
|
521 /***********************************************************************
|
|
522
|
|
523 Sidestep the normal lookup for paths that are known to
|
|
524 be folders. Where folder is true, file-system lookups
|
|
525 will be skipped.
|
|
526
|
|
527 ***********************************************************************/
|
|
528
|
|
529 final FilePath isFolder (bool folder)
|
|
530 {
|
|
531 dir_ = folder;
|
|
532 return this;
|
|
533 }
|
|
534
|
|
535 /***********************************************************************
|
|
536
|
|
537 Replace the root portion of this path
|
|
538
|
|
539 ***********************************************************************/
|
|
540
|
|
541 final FilePath root (char[] other)
|
|
542 {
|
|
543 auto x = adjust (0, folder_, folder_, padded (other, ':'));
|
|
544 folder_ += x;
|
|
545 suffix_ += x;
|
|
546 name_ += x;
|
|
547 return this;
|
|
548 }
|
|
549
|
|
550 /***********************************************************************
|
|
551
|
|
552 Replace the folder portion of this path. The folder will be
|
|
553 padded with a path-separator as required
|
|
554
|
|
555 ***********************************************************************/
|
|
556
|
|
557 final FilePath folder (char[] other)
|
|
558 {
|
|
559 auto x = adjust (folder_, name_, name_ - folder_, padded (other));
|
|
560 suffix_ += x;
|
|
561 name_ += x;
|
|
562 return this;
|
|
563 }
|
|
564
|
|
565 /***********************************************************************
|
|
566
|
|
567 Replace the name portion of this path
|
|
568
|
|
569 ***********************************************************************/
|
|
570
|
|
571 final FilePath name (char[] other)
|
|
572 {
|
|
573 auto x = adjust (name_, suffix_, suffix_ - name_, other);
|
|
574 suffix_ += x;
|
|
575 return this;
|
|
576 }
|
|
577
|
|
578 /***********************************************************************
|
|
579
|
|
580 Replace the suffix portion of this path. The suffix will be
|
|
581 prefixed with a file-separator as required
|
|
582
|
|
583 ***********************************************************************/
|
|
584
|
|
585 final FilePath suffix (char[] other)
|
|
586 {
|
|
587 adjust (suffix_, end_, end_ - suffix_, prefixed (other, '.'));
|
|
588 return this;
|
|
589 }
|
|
590
|
|
591 /***********************************************************************
|
|
592
|
|
593 Replace the root and folder portions of this path and
|
|
594 reparse. The replacement will be padded with a path
|
|
595 separator as required
|
|
596
|
|
597 ***********************************************************************/
|
|
598
|
|
599 final FilePath path (char[] other)
|
|
600 {
|
|
601 adjust (0, name_, name_, padded (other));
|
|
602 return parse;
|
|
603 }
|
|
604
|
|
605 /***********************************************************************
|
|
606
|
|
607 Replace the file and suffix portions of this path and
|
|
608 reparse. The replacement will be prefixed with a suffix
|
|
609 separator as required
|
|
610
|
|
611 ***********************************************************************/
|
|
612
|
|
613 final FilePath file (char[] other)
|
|
614 {
|
|
615 adjust (name_, end_, end_ - name_, other);
|
|
616 return parse;
|
|
617 }
|
|
618
|
|
619 /***********************************************************************
|
|
620
|
|
621 Pop to the parent of the current filepath (in place)
|
|
622
|
|
623 ***********************************************************************/
|
|
624
|
|
625 final FilePath pop ()
|
|
626 {
|
|
627 auto path = parent();
|
|
628 end_ = path.length;
|
|
629 fp[end_] = '\0';
|
|
630 return parse;
|
|
631 }
|
|
632
|
|
633 /***********************************************************************
|
|
634
|
|
635 Join a set of path specs together. A path separator is
|
|
636 potentially inserted between each of the segments.
|
|
637
|
|
638 ***********************************************************************/
|
|
639
|
|
640 static char[] join (char[][] paths...)
|
|
641 {
|
|
642 char[] result;
|
|
643
|
|
644 foreach (path; paths)
|
|
645 result ~= padded (path);
|
|
646
|
|
647 return result.length ? result [0 .. $-1] : null;
|
|
648 }
|
|
649
|
|
650 /***********************************************************************
|
|
651
|
|
652 Return an adjusted path such that non-empty instances do not
|
|
653 have a trailing separator
|
|
654
|
|
655 ***********************************************************************/
|
|
656
|
|
657 static char[] stripped (char[] path, char c = FileConst.PathSeparatorChar)
|
|
658 {
|
|
659 if (path.length && path[$-1] is c)
|
|
660 path = path [0 .. $-1];
|
|
661 return path;
|
|
662 }
|
|
663
|
|
664 /***********************************************************************
|
|
665
|
|
666 Return an adjusted path such that non-empty instances always
|
|
667 have a trailing separator
|
|
668
|
|
669 ***********************************************************************/
|
|
670
|
|
671 static char[] padded (char[] path, char c = FileConst.PathSeparatorChar)
|
|
672 {
|
|
673 if (path.length && path[$-1] != c)
|
|
674 path = path ~ c;
|
|
675 return path;
|
|
676 }
|
|
677
|
|
678 /***********************************************************************
|
|
679
|
|
680 Return an adjusted path such that non-empty instances always
|
|
681 have a prefixed separator
|
|
682
|
|
683 ***********************************************************************/
|
|
684
|
|
685 static char[] prefixed (char[] s, char c = FileConst.PathSeparatorChar)
|
|
686 {
|
|
687 if (s.length && s[0] != c)
|
|
688 s = c ~ s;
|
|
689 return s;
|
|
690 }
|
|
691
|
|
692 /***********************************************************************
|
|
693
|
|
694 Parse the path spec
|
|
695
|
|
696 ***********************************************************************/
|
|
697
|
|
698 private final FilePath parse ()
|
|
699 {
|
|
700 folder_ = 0;
|
|
701 name_ = suffix_ = -1;
|
|
702
|
|
703 for (int i=end_; --i >= 0;)
|
|
704 switch (fp[i])
|
|
705 {
|
|
706 case FileConst.FileSeparatorChar:
|
|
707 if (name_ < 0)
|
|
708 if (suffix_ < 0 && i && fp[i-1] != '.')
|
|
709 suffix_ = i;
|
|
710 break;
|
|
711
|
|
712 version (Win32)
|
|
713 {
|
|
714 case '\\':
|
|
715 fp[i] = '/';
|
|
716 }
|
|
717 case FileConst.PathSeparatorChar:
|
|
718 if (name_ < 0)
|
|
719 name_ = i + 1;
|
|
720 break;
|
|
721
|
|
722 version (Win32)
|
|
723 {
|
|
724 case FileConst.RootSeparatorChar:
|
|
725 folder_ = i + 1;
|
|
726 break;
|
|
727 }
|
|
728
|
|
729 default:
|
|
730 break;
|
|
731 }
|
|
732
|
|
733 if (name_ < 0)
|
|
734 name_ = folder_;
|
|
735
|
|
736 if (suffix_ < 0 || suffix_ is name_)
|
|
737 suffix_ = end_;
|
|
738
|
|
739 return this;
|
|
740 }
|
|
741
|
|
742 /***********************************************************************
|
|
743
|
|
744 Potentially make room for more content
|
|
745
|
|
746 ***********************************************************************/
|
|
747
|
|
748 private final void expand (uint size)
|
|
749 {
|
|
750 ++size;
|
|
751 if (fp.length < size)
|
|
752 fp.length = (size + 127) & ~127;
|
|
753 }
|
|
754
|
|
755 /***********************************************************************
|
|
756
|
|
757 Insert/delete internal content
|
|
758
|
|
759 ***********************************************************************/
|
|
760
|
|
761 private final int adjust (int head, int tail, int len, char[] sub)
|
|
762 {
|
|
763 len = sub.length - len;
|
|
764
|
|
765 // don't destroy self-references!
|
|
766 if (len && sub.ptr >= fp.ptr+head+len && sub.ptr < fp.ptr+fp.length)
|
|
767 {
|
|
768 char[512] tmp = void;
|
|
769 assert (sub.length < tmp.length);
|
|
770 sub = tmp[0..sub.length] = sub;
|
|
771 }
|
|
772
|
|
773 // make some room if necessary
|
|
774 expand (len + end_);
|
|
775
|
|
776 // slide tail around to insert or remove space
|
|
777 memmove (fp.ptr+tail+len, fp.ptr+tail, end_ +1 - tail);
|
|
778
|
|
779 // copy replacement
|
|
780 memmove (fp.ptr + head, sub.ptr, sub.length);
|
|
781
|
|
782 // adjust length
|
|
783 end_ += len;
|
|
784 return len;
|
|
785 }
|
|
786
|
|
787
|
|
788 /**********************************************************************/
|
|
789 /********************** file-system methods ***************************/
|
|
790 /**********************************************************************/
|
|
791
|
|
792
|
|
793 /***********************************************************************
|
|
794
|
|
795 Does this path currently exist?
|
|
796
|
|
797 ***********************************************************************/
|
|
798
|
|
799 final bool exists ()
|
|
800 {
|
|
801 try {
|
|
802 fileSize();
|
|
803 return true;
|
|
804 } catch (IOException){}
|
|
805 return false;
|
|
806 }
|
|
807
|
|
808 /***********************************************************************
|
|
809
|
|
810 Returns the time of the last modification. Accurate
|
|
811 to whatever the OS supports
|
|
812
|
|
813 ***********************************************************************/
|
|
814
|
|
815 final Time modified ()
|
|
816 {
|
|
817 return timeStamps.modified;
|
|
818 }
|
|
819
|
|
820 /***********************************************************************
|
|
821
|
|
822 Returns the time of the last access. Accurate to
|
|
823 whatever the OS supports
|
|
824
|
|
825 ***********************************************************************/
|
|
826
|
|
827 final Time accessed ()
|
|
828 {
|
|
829 return timeStamps.accessed;
|
|
830 }
|
|
831
|
|
832 /***********************************************************************
|
|
833
|
|
834 Returns the time of file creation. Accurate to
|
|
835 whatever the OS supports
|
|
836
|
|
837 ***********************************************************************/
|
|
838
|
|
839 final Time created ()
|
|
840 {
|
|
841 return timeStamps.created;
|
|
842 }
|
|
843
|
|
844 /***********************************************************************
|
|
845
|
|
846 Create an entire path consisting of this folder along with
|
|
847 all parent folders. The path must not contain '.' or '..'
|
|
848 segments. Related methods include PathUtil.normalize() and
|
|
849 FileSystem.toAbsolute()
|
|
850
|
|
851 Note that each segment is created as a folder, including the
|
|
852 trailing segment.
|
|
853
|
|
854 Returns: a chaining reference (this)
|
|
855
|
|
856 Throws: IOException upon systen errors
|
|
857
|
|
858 Throws: IllegalArgumentException if the path contains invalid
|
|
859 path segment names (such as '.' or '..') or a segment
|
|
860 exists but as a file instead of a folder
|
|
861
|
|
862 ***********************************************************************/
|
|
863
|
|
864 final FilePath create ()
|
|
865 {
|
|
866 auto segment = name;
|
|
867 if (segment.length > 0)
|
|
868 {
|
|
869 if (segment == FileConst.CurrentDirString ||
|
|
870 segment == FileConst.ParentDirString)
|
|
871 badArg ("FilePath.create :: invalid path: ");
|
|
872
|
|
873 if (this.exists)
|
|
874 if (this.isFolder)
|
|
875 return this;
|
|
876 else
|
|
877 badArg ("FilePath.create :: file/folder conflict: ");
|
|
878
|
|
879 FilePath(this.parent).create;
|
|
880 return createFolder;
|
|
881 }
|
|
882 return this;
|
|
883 }
|
|
884
|
|
885 /***********************************************************************
|
|
886
|
|
887 List the set of filenames within this folder, using
|
|
888 the provided filter to control the list:
|
|
889 ---
|
|
890 bool delegate (FilePath path, bool isFolder) Filter
|
|
891 ---
|
|
892
|
|
893 Returning true from the filter includes the given path,
|
|
894 whilst returning false excludes it. Parameter 'isFolder'
|
|
895 indicates whether the path is a file or folder.
|
|
896
|
|
897 Note that paths composed of '.' characters are ignored.
|
|
898
|
|
899 ***********************************************************************/
|
|
900
|
|
901 final FilePath[] toList (Filter filter = null)
|
|
902 {
|
|
903 FilePath[] paths;
|
|
904
|
|
905 foreach (info; this)
|
|
906 {
|
|
907 auto p = from (info);
|
|
908
|
|
909 // test this entry for inclusion
|
|
910 if (filter is null || filter (p, info.folder))
|
|
911 paths ~= p;
|
|
912 else
|
|
913 delete p;
|
|
914 }
|
|
915
|
|
916 return paths;
|
|
917 }
|
|
918
|
|
919 /***********************************************************************
|
|
920
|
|
921 Construct a FilePath from the given FileInfo
|
|
922
|
|
923 ***********************************************************************/
|
|
924
|
|
925 static FilePath from (ref FileInfo info)
|
|
926 {
|
|
927 char[512] tmp = void;
|
|
928
|
|
929 auto len = info.path.length + info.name.length;
|
|
930 assert (len < tmp.length);
|
|
931
|
|
932 // construct full pathname
|
|
933 tmp [0 .. info.path.length] = info.path;
|
|
934 tmp [info.path.length .. len] = info.name;
|
|
935
|
|
936 return FilePath(tmp[0 .. len]).isFolder(info.folder);
|
|
937 }
|
|
938
|
|
939 /***********************************************************************
|
|
940
|
|
941 change the name or location of a file/directory, and
|
|
942 adopt the provided Path
|
|
943
|
|
944 ***********************************************************************/
|
|
945
|
|
946 final FilePath rename (FilePath dst)
|
|
947 {
|
|
948 return rename (dst.toString);
|
|
949 }
|
|
950
|
|
951 /***********************************************************************
|
|
952
|
|
953 Throw an exception using the last known error
|
|
954
|
|
955 ***********************************************************************/
|
|
956
|
|
957 private void exception ()
|
|
958 {
|
|
959 throw new IOException (toString ~ ": " ~ SysError.lastMsg);
|
|
960 }
|
|
961
|
|
962 /***********************************************************************
|
|
963
|
|
964 Throw an exception using the last known error
|
|
965
|
|
966 ***********************************************************************/
|
|
967
|
|
968 private void badArg (char[] msg)
|
|
969 {
|
|
970 throw new IllegalArgumentException (msg ~ toString);
|
|
971 }
|
|
972
|
|
973 /***********************************************************************
|
|
974
|
|
975 ***********************************************************************/
|
|
976
|
|
977 version (Win32)
|
|
978 {
|
|
979 /***************************************************************
|
|
980
|
|
981 return a wchar[] instance of the path
|
|
982
|
|
983 ***************************************************************/
|
|
984
|
|
985 private wchar[] toString16 (wchar[] tmp, char[] path)
|
|
986 {
|
|
987 auto i = MultiByteToWideChar (CP_UTF8, 0,
|
|
988 path.ptr, path.length,
|
|
989 tmp.ptr, tmp.length);
|
|
990 return tmp [0..i];
|
|
991 }
|
|
992
|
|
993 /***************************************************************
|
|
994
|
|
995 return a char[] instance of the path
|
|
996
|
|
997 ***************************************************************/
|
|
998
|
|
999 private char[] toString (char[] tmp, wchar[] path)
|
|
1000 {
|
|
1001 auto i = WideCharToMultiByte (CP_UTF8, 0, path.ptr, path.length,
|
|
1002 tmp.ptr, tmp.length, null, null);
|
|
1003 return tmp [0..i];
|
|
1004 }
|
|
1005
|
|
1006 /***************************************************************
|
|
1007
|
|
1008 return a wchar[] instance of the path
|
|
1009
|
|
1010 ***************************************************************/
|
|
1011
|
|
1012 private wchar[] name16 (wchar[] tmp, bool withNull=true)
|
|
1013 {
|
|
1014 int offset = withNull ? 0 : 1;
|
|
1015 return toString16 (tmp, this.cString[0 .. $-offset]);
|
|
1016 }
|
|
1017
|
|
1018 /***************************************************************
|
|
1019
|
|
1020 Get info about this path
|
|
1021
|
|
1022 ***************************************************************/
|
|
1023
|
|
1024 private DWORD getInfo (inout WIN32_FILE_ATTRIBUTE_DATA info)
|
|
1025 {
|
|
1026 version (Win32SansUnicode)
|
|
1027 {
|
|
1028 if (! GetFileAttributesExA (this.cString.ptr, GetFileInfoLevelStandard, &info))
|
|
1029 exception;
|
|
1030 }
|
|
1031 else
|
|
1032 {
|
|
1033 wchar[MAX_PATH] tmp = void;
|
|
1034 if (! GetFileAttributesExW (name16(tmp).ptr, GetFileInfoLevelStandard, &info))
|
|
1035 exception;
|
|
1036 }
|
|
1037
|
|
1038 return info.dwFileAttributes;
|
|
1039 }
|
|
1040
|
|
1041 /***************************************************************
|
|
1042
|
|
1043 Get flags for this path
|
|
1044
|
|
1045 ***************************************************************/
|
|
1046
|
|
1047 private DWORD getFlags ()
|
|
1048 {
|
|
1049 WIN32_FILE_ATTRIBUTE_DATA info = void;
|
|
1050
|
|
1051 return getInfo (info);
|
|
1052 }
|
|
1053
|
|
1054 /***************************************************************
|
|
1055
|
|
1056 Return the file length (in bytes)
|
|
1057
|
|
1058 ***************************************************************/
|
|
1059
|
|
1060 final ulong fileSize ()
|
|
1061 {
|
|
1062 WIN32_FILE_ATTRIBUTE_DATA info = void;
|
|
1063
|
|
1064 getInfo (info);
|
|
1065 return (cast(ulong) info.nFileSizeHigh << 32) +
|
|
1066 info.nFileSizeLow;
|
|
1067 }
|
|
1068
|
|
1069 /***************************************************************
|
|
1070
|
|
1071 Is this file writable?
|
|
1072
|
|
1073 ***************************************************************/
|
|
1074
|
|
1075 final bool isWritable ()
|
|
1076 {
|
|
1077 return (getFlags & FILE_ATTRIBUTE_READONLY) == 0;
|
|
1078 }
|
|
1079
|
|
1080 /***************************************************************
|
|
1081
|
|
1082 Is this file actually a folder/directory?
|
|
1083
|
|
1084 ***************************************************************/
|
|
1085
|
|
1086 final bool isFolder ()
|
|
1087 {
|
|
1088 if (dir_)
|
|
1089 return true;
|
|
1090
|
|
1091 return (getFlags & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
1092 }
|
|
1093
|
|
1094 /***************************************************************
|
|
1095
|
|
1096 Return timestamp information
|
|
1097
|
|
1098 ***************************************************************/
|
|
1099
|
|
1100 final Stamps timeStamps ()
|
|
1101 {
|
|
1102 static Time convert (FILETIME time)
|
|
1103 {
|
|
1104 return Time (TimeSpan.Epoch1601 + *cast(long*) &time);
|
|
1105 }
|
|
1106
|
|
1107 WIN32_FILE_ATTRIBUTE_DATA info = void;
|
|
1108 Stamps time = void;
|
|
1109
|
|
1110 getInfo (info);
|
|
1111 time.modified = convert (info.ftLastWriteTime);
|
|
1112 time.accessed = convert (info.ftLastAccessTime);
|
|
1113 time.created = convert (info.ftCreationTime);
|
|
1114 return time;
|
|
1115 }
|
|
1116
|
|
1117 /***********************************************************************
|
|
1118
|
|
1119 Transfer the content of another file to this one. Returns a
|
|
1120 reference to this class on success, or throws an IOException
|
|
1121 upon failure.
|
|
1122
|
|
1123 ***********************************************************************/
|
|
1124
|
|
1125 final FilePath copy (char[] source)
|
|
1126 {
|
|
1127 auto src = new FilePath (source);
|
|
1128
|
|
1129 version (Win32SansUnicode)
|
|
1130 {
|
|
1131 if (! CopyFileA (src.cString.ptr, this.cString.ptr, false))
|
|
1132 exception;
|
|
1133 }
|
|
1134 else
|
|
1135 {
|
|
1136 wchar[MAX_PATH+1] tmp1 = void;
|
|
1137 wchar[MAX_PATH+1] tmp2 = void;
|
|
1138
|
|
1139 if (! CopyFileW (toString16(tmp1, src.cString).ptr, name16(tmp2).ptr, false))
|
|
1140 exception;
|
|
1141 }
|
|
1142
|
|
1143 return this;
|
|
1144 }
|
|
1145
|
|
1146 /***************************************************************
|
|
1147
|
|
1148 Remove the file/directory from the file-system
|
|
1149
|
|
1150 ***************************************************************/
|
|
1151
|
|
1152 final FilePath remove ()
|
|
1153 {
|
|
1154 if (isFolder)
|
|
1155 {
|
|
1156 version (Win32SansUnicode)
|
|
1157 {
|
|
1158 if (! RemoveDirectoryA (this.cString.ptr))
|
|
1159 exception;
|
|
1160 }
|
|
1161 else
|
|
1162 {
|
|
1163 wchar[MAX_PATH] tmp = void;
|
|
1164 if (! RemoveDirectoryW (name16(tmp).ptr))
|
|
1165 exception;
|
|
1166 }
|
|
1167 }
|
|
1168 else
|
|
1169 version (Win32SansUnicode)
|
|
1170 {
|
|
1171 if (! DeleteFileA (this.cString.ptr))
|
|
1172 exception;
|
|
1173 }
|
|
1174 else
|
|
1175 {
|
|
1176 wchar[MAX_PATH] tmp = void;
|
|
1177 if (! DeleteFileW (name16(tmp).ptr))
|
|
1178 exception;
|
|
1179 }
|
|
1180
|
|
1181 return this;
|
|
1182 }
|
|
1183
|
|
1184 /***************************************************************
|
|
1185
|
|
1186 change the name or location of a file/directory, and
|
|
1187 adopt the provided Path
|
|
1188
|
|
1189 ***************************************************************/
|
|
1190
|
|
1191 final FilePath rename (char[] dst)
|
|
1192 {
|
|
1193 const int Typical = MOVEFILE_REPLACE_EXISTING +
|
|
1194 MOVEFILE_COPY_ALLOWED +
|
|
1195 MOVEFILE_WRITE_THROUGH;
|
|
1196
|
|
1197 int result;
|
|
1198 char[] cstr = dst ~ '\0';
|
|
1199
|
|
1200 version (Win32SansUnicode)
|
|
1201 result = MoveFileExA (this.cString.ptr, cstr.ptr, Typical);
|
|
1202 else
|
|
1203 {
|
|
1204 wchar[MAX_PATH] tmp1 = void;
|
|
1205 wchar[MAX_PATH] tmp2 = void;
|
|
1206 result = MoveFileExW (name16(tmp1).ptr, toString16(tmp2, cstr).ptr, Typical);
|
|
1207 }
|
|
1208
|
|
1209 if (! result)
|
|
1210 exception;
|
|
1211
|
|
1212 this.set (dst);
|
|
1213 return this;
|
|
1214 }
|
|
1215
|
|
1216 /***************************************************************
|
|
1217
|
|
1218 Create a new file
|
|
1219
|
|
1220 ***************************************************************/
|
|
1221
|
|
1222 final FilePath createFile ()
|
|
1223 {
|
|
1224 HANDLE h;
|
|
1225
|
|
1226 version (Win32SansUnicode)
|
|
1227 h = CreateFileA (this.cString.ptr, GENERIC_WRITE,
|
|
1228 0, null, CREATE_ALWAYS,
|
|
1229 FILE_ATTRIBUTE_NORMAL, cast(HANDLE) 0);
|
|
1230 else
|
|
1231 {
|
|
1232 wchar[MAX_PATH] tmp = void;
|
|
1233 h = CreateFileW (name16(tmp).ptr, GENERIC_WRITE,
|
|
1234 0, null, CREATE_ALWAYS,
|
|
1235 FILE_ATTRIBUTE_NORMAL, cast(HANDLE) 0);
|
|
1236 }
|
|
1237
|
|
1238 if (h == INVALID_HANDLE_VALUE)
|
|
1239 exception;
|
|
1240
|
|
1241 if (! CloseHandle (h))
|
|
1242 exception;
|
|
1243
|
|
1244 return this;
|
|
1245 }
|
|
1246
|
|
1247 /***************************************************************
|
|
1248
|
|
1249 Create a new directory
|
|
1250
|
|
1251 ***************************************************************/
|
|
1252
|
|
1253 final FilePath createFolder ()
|
|
1254 {
|
|
1255 version (Win32SansUnicode)
|
|
1256 {
|
|
1257 if (! CreateDirectoryA (this.cString.ptr, null))
|
|
1258 exception;
|
|
1259 }
|
|
1260 else
|
|
1261 {
|
|
1262 wchar[MAX_PATH] tmp = void;
|
|
1263 if (! CreateDirectoryW (name16(tmp).ptr, null))
|
|
1264 exception;
|
|
1265 }
|
|
1266 return this;
|
|
1267 }
|
|
1268
|
|
1269 /***************************************************************
|
|
1270
|
|
1271 List the set of filenames within this folder.
|
|
1272
|
|
1273 Each path and filename is passed to the provided
|
|
1274 delegate, along with the path prefix and whether
|
|
1275 the entry is a folder or not.
|
|
1276
|
|
1277 Returns the number of files scanned.
|
|
1278
|
|
1279 ***************************************************************/
|
|
1280
|
|
1281 final int opApply (int delegate(ref FileInfo) dg)
|
|
1282 {
|
|
1283 HANDLE h;
|
|
1284 int ret;
|
|
1285 char[] prefix;
|
|
1286 char[MAX_PATH+1] tmp = void;
|
|
1287 FIND_DATA fileinfo = void;
|
|
1288
|
|
1289 int next()
|
|
1290 {
|
|
1291 version (Win32SansUnicode)
|
|
1292 return FindNextFileA (h, &fileinfo);
|
|
1293 else
|
|
1294 return FindNextFileW (h, &fileinfo);
|
|
1295 }
|
|
1296
|
|
1297 static T[] padded (T[] s, T[] ext)
|
|
1298 {
|
|
1299 if (s.length is 0 || s[$-1] != '\\')
|
|
1300 return s ~ "\\" ~ ext;
|
|
1301 return s ~ ext;
|
|
1302 }
|
|
1303
|
|
1304 version (Win32SansUnicode)
|
|
1305 h = FindFirstFileA (padded(this.toString, "*\0").ptr, &fileinfo);
|
|
1306 else
|
|
1307 {
|
|
1308 wchar[MAX_PATH] host = void;
|
|
1309 h = FindFirstFileW (padded(name16(host, false), "*\0").ptr, &fileinfo);
|
|
1310 }
|
|
1311
|
|
1312 if (h is INVALID_HANDLE_VALUE)
|
|
1313 exception;
|
|
1314
|
|
1315 scope (exit)
|
|
1316 FindClose (h);
|
|
1317
|
|
1318 prefix = FilePath.padded (this.toString);
|
|
1319 do {
|
|
1320 version (Win32SansUnicode)
|
|
1321 {
|
|
1322 auto len = strlen (fileinfo.cFileName.ptr);
|
|
1323 auto str = fileinfo.cFileName.ptr [0 .. len];
|
|
1324 }
|
|
1325 else
|
|
1326 {
|
|
1327 auto len = wcslen (fileinfo.cFileName.ptr);
|
|
1328 auto str = toString (tmp, fileinfo.cFileName [0 .. len]);
|
|
1329 }
|
|
1330
|
|
1331 // skip hidden/system files
|
|
1332 if ((fileinfo.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)) is 0)
|
|
1333 {
|
|
1334 FileInfo info = void;
|
|
1335 info.name = str;
|
|
1336 info.path = prefix;
|
|
1337 info.bytes = (cast(ulong) fileinfo.nFileSizeHigh << 32) + fileinfo.nFileSizeLow;
|
|
1338 info.folder = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
1339
|
|
1340 // skip "..." names
|
|
1341 if (str.length > 3 || str != "..."[0 .. str.length])
|
|
1342 if ((ret = dg(info)) != 0)
|
|
1343 break;
|
|
1344 }
|
|
1345 } while (next);
|
|
1346
|
|
1347 return ret;
|
|
1348 }
|
|
1349 }
|
|
1350
|
|
1351
|
|
1352 /***********************************************************************
|
|
1353
|
|
1354 ***********************************************************************/
|
|
1355
|
|
1356 version (Posix)
|
|
1357 {
|
|
1358 /***************************************************************
|
|
1359
|
|
1360 Get info about this path
|
|
1361
|
|
1362 ***************************************************************/
|
|
1363
|
|
1364 private uint getInfo (inout stat_t stats)
|
|
1365 {
|
|
1366 if (posix.stat (this.cString.ptr, &stats))
|
|
1367 exception;
|
|
1368
|
|
1369 return stats.st_mode;
|
|
1370 }
|
|
1371
|
|
1372 /***************************************************************
|
|
1373
|
|
1374 Return the file length (in bytes)
|
|
1375
|
|
1376 ***************************************************************/
|
|
1377
|
|
1378 final ulong fileSize ()
|
|
1379 {
|
|
1380 stat_t stats = void;
|
|
1381
|
|
1382 getInfo (stats);
|
|
1383 return cast(ulong) stats.st_size; // 32 bits only
|
|
1384 }
|
|
1385
|
|
1386 /***************************************************************
|
|
1387
|
|
1388 Is this file writable?
|
|
1389
|
|
1390 ***************************************************************/
|
|
1391
|
|
1392 final bool isWritable ()
|
|
1393 {
|
|
1394 stat_t stats = void;
|
|
1395
|
|
1396 return (getInfo(stats) & O_RDONLY) == 0;
|
|
1397 }
|
|
1398
|
|
1399 /***************************************************************
|
|
1400
|
|
1401 Is this file actually a folder/directory?
|
|
1402
|
|
1403 ***************************************************************/
|
|
1404
|
|
1405 final bool isFolder ()
|
|
1406 {
|
|
1407 stat_t stats = void;
|
|
1408
|
|
1409 return (getInfo(stats) & S_IFDIR) != 0;
|
|
1410 }
|
|
1411
|
|
1412 /***************************************************************
|
|
1413
|
|
1414 Return timestamp information
|
|
1415
|
|
1416 ***************************************************************/
|
|
1417
|
|
1418 final Stamps timeStamps ()
|
|
1419 {
|
|
1420 static Time convert (timeval* tv)
|
|
1421 {
|
|
1422 return Time.epoch1970 +
|
|
1423 TimeSpan.seconds(tv.tv_sec) +
|
|
1424 TimeSpan.micros(tv.tv_usec);
|
|
1425 }
|
|
1426
|
|
1427 stat_t stats = void;
|
|
1428 Stamps time = void;
|
|
1429
|
|
1430 getInfo (stats);
|
|
1431
|
|
1432 time.modified = convert (cast(timeval*) &stats.st_mtime);
|
|
1433 time.accessed = convert (cast(timeval*) &stats.st_atime);
|
|
1434 time.created = convert (cast(timeval*) &stats.st_ctime);
|
|
1435 return time;
|
|
1436 }
|
|
1437
|
|
1438 /***********************************************************************
|
|
1439
|
|
1440 Transfer the content of another file to this one. Returns a
|
|
1441 reference to this class on success, or throws an IOException
|
|
1442 upon failure.
|
|
1443
|
|
1444 ***********************************************************************/
|
|
1445
|
|
1446 final FilePath copy (char[] source)
|
|
1447 {
|
|
1448 auto from = new FilePath (source);
|
|
1449
|
|
1450 auto src = posix.open (from.cString.ptr, O_RDONLY, 0640);
|
|
1451 scope (exit)
|
|
1452 if (src != -1)
|
|
1453 posix.close (src);
|
|
1454
|
|
1455 auto dst = posix.open (this.cString.ptr, O_CREAT | O_RDWR, 0660);
|
|
1456 scope (exit)
|
|
1457 if (dst != -1)
|
|
1458 posix.close (dst);
|
|
1459
|
|
1460 if (src is -1 || dst is -1)
|
|
1461 exception;
|
|
1462
|
|
1463 // copy content
|
|
1464 ubyte[] buf = new ubyte [16 * 1024];
|
|
1465 int read = posix.read (src, buf.ptr, buf.length);
|
|
1466 while (read > 0)
|
|
1467 {
|
|
1468 auto p = buf.ptr;
|
|
1469 do {
|
|
1470 int written = posix.write (dst, p, read);
|
|
1471 p += written;
|
|
1472 read -= written;
|
|
1473 if (written is -1)
|
|
1474 exception;
|
|
1475 } while (read > 0);
|
|
1476 read = posix.read (src, buf.ptr, buf.length);
|
|
1477 }
|
|
1478 if (read is -1)
|
|
1479 exception;
|
|
1480
|
|
1481 // copy timestamps
|
|
1482 stat_t stats;
|
|
1483 if (posix.stat (from.cString.ptr, &stats))
|
|
1484 exception;
|
|
1485
|
|
1486 utimbuf utim;
|
|
1487 utim.actime = stats.st_atime;
|
|
1488 utim.modtime = stats.st_mtime;
|
|
1489 if (utime (this.cString.ptr, &utim) is -1)
|
|
1490 exception;
|
|
1491
|
|
1492 return this;
|
|
1493 }
|
|
1494
|
|
1495 /***************************************************************
|
|
1496
|
|
1497 Remove the file/directory from the file-system
|
|
1498
|
|
1499 ***************************************************************/
|
|
1500
|
|
1501 final FilePath remove ()
|
|
1502 {
|
|
1503 if (isFolder)
|
|
1504 {
|
|
1505 if (posix.rmdir (this.cString.ptr))
|
|
1506 exception;
|
|
1507 }
|
|
1508 else
|
|
1509 if (tango.stdc.stdio.remove (this.cString.ptr) == -1)
|
|
1510 exception;
|
|
1511
|
|
1512 return this;
|
|
1513 }
|
|
1514
|
|
1515 /***************************************************************
|
|
1516
|
|
1517 change the name or location of a file/directory, and
|
|
1518 adopt the provided FilePath
|
|
1519
|
|
1520 ***************************************************************/
|
|
1521
|
|
1522 final FilePath rename (char[] dst)
|
|
1523 {
|
|
1524 char[] cstr = dst ~ '\0';
|
|
1525 if (tango.stdc.stdio.rename (this.cString.ptr, cstr.ptr) == -1)
|
|
1526 exception;
|
|
1527
|
|
1528 this.set (dst);
|
|
1529 return this;
|
|
1530 }
|
|
1531
|
|
1532 /***************************************************************
|
|
1533
|
|
1534 Create a new file
|
|
1535
|
|
1536 ***************************************************************/
|
|
1537
|
|
1538 final FilePath createFile ()
|
|
1539 {
|
|
1540 int fd;
|
|
1541
|
|
1542 fd = posix.open (this.cString.ptr, O_CREAT | O_WRONLY | O_TRUNC, 0660);
|
|
1543 if (fd == -1)
|
|
1544 exception;
|
|
1545
|
|
1546 if (posix.close(fd) == -1)
|
|
1547 exception;
|
|
1548
|
|
1549 return this;
|
|
1550 }
|
|
1551
|
|
1552 /***************************************************************
|
|
1553
|
|
1554 Create a new directory
|
|
1555
|
|
1556 ***************************************************************/
|
|
1557
|
|
1558 final FilePath createFolder ()
|
|
1559 {
|
|
1560 if (posix.mkdir (this.cString.ptr, 0777))
|
|
1561 exception;
|
|
1562
|
|
1563 return this;
|
|
1564 }
|
|
1565
|
|
1566 /***************************************************************
|
|
1567
|
|
1568 List the set of filenames within this folder.
|
|
1569
|
|
1570 Each path and filename is passed to the provided
|
|
1571 delegate, along with the path prefix and whether
|
|
1572 the entry is a folder or not.
|
|
1573
|
|
1574 Returns the number of files scanned.
|
|
1575
|
|
1576 ***************************************************************/
|
|
1577
|
|
1578 final int opApply (int delegate(ref FileInfo) dg)
|
|
1579 {
|
|
1580 int ret;
|
|
1581 DIR* dir;
|
|
1582 dirent entry;
|
|
1583 dirent* pentry;
|
|
1584 stat_t sbuf;
|
|
1585 char[] prefix;
|
|
1586 char[] sfnbuf;
|
|
1587
|
|
1588 dir = tango.stdc.posix.dirent.opendir (this.cString.ptr);
|
|
1589 if (! dir)
|
|
1590 exception;
|
|
1591
|
|
1592 scope (exit)
|
|
1593 tango.stdc.posix.dirent.closedir (dir);
|
|
1594
|
|
1595 // ensure a trailing '/' is present
|
|
1596 prefix = FilePath.padded (this.toString);
|
|
1597
|
|
1598 // prepare our filename buffer
|
|
1599 sfnbuf = prefix.dup;
|
|
1600
|
|
1601 // pentry is null at end of listing, or on an error
|
|
1602 while (readdir_r (dir, &entry, &pentry), pentry != null)
|
|
1603 {
|
|
1604 auto len = tango.stdc.string.strlen (entry.d_name.ptr);
|
|
1605 auto str = entry.d_name.ptr [0 .. len];
|
|
1606 ++len; // include the null
|
|
1607
|
|
1608 // resize the buffer as necessary ...
|
|
1609 if (sfnbuf.length < prefix.length + len)
|
|
1610 sfnbuf.length = prefix.length + len;
|
|
1611
|
|
1612 sfnbuf [prefix.length .. prefix.length + len]
|
|
1613 = entry.d_name.ptr [0 .. len];
|
|
1614
|
|
1615 // skip "..." names
|
|
1616 if (str.length > 3 || str != "..."[0 .. str.length])
|
|
1617 {
|
|
1618 if (stat (sfnbuf.ptr, &sbuf))
|
|
1619 exception;
|
|
1620
|
|
1621 FileInfo info = void;
|
|
1622 info.name = str;
|
|
1623 info.path = prefix;
|
|
1624 info.folder = (sbuf.st_mode & S_IFDIR) != 0;
|
|
1625 info.bytes = (sbuf.st_mode & S_IFREG) != 0 ? sbuf.st_size : 0;
|
|
1626
|
|
1627 if ((ret = dg(info)) != 0)
|
|
1628 break;
|
|
1629 }
|
|
1630 }
|
|
1631 return ret;
|
|
1632 }
|
|
1633 }
|
|
1634 }
|
|
1635
|
|
1636
|
|
1637
|
|
1638 /*******************************************************************************
|
|
1639
|
|
1640 *******************************************************************************/
|
|
1641
|
|
1642 interface PathView
|
|
1643 {
|
|
1644 /***********************************************************************
|
|
1645
|
|
1646 TimeStamp information. Accurate to whatever the OS supports
|
|
1647
|
|
1648 ***********************************************************************/
|
|
1649
|
|
1650 struct Stamps
|
|
1651 {
|
|
1652 Time created, /// time created
|
|
1653 accessed, /// last time accessed
|
|
1654 modified; /// last time modified
|
|
1655 }
|
|
1656
|
|
1657 /***********************************************************************
|
|
1658
|
|
1659 Return the complete text of this filepath
|
|
1660
|
|
1661 ***********************************************************************/
|
|
1662
|
|
1663 abstract char[] toString ();
|
|
1664
|
|
1665 /***********************************************************************
|
|
1666
|
|
1667 Return the complete text of this filepath
|
|
1668
|
|
1669 ***********************************************************************/
|
|
1670
|
|
1671 abstract char[] cString ();
|
|
1672
|
|
1673 /***********************************************************************
|
|
1674
|
|
1675 Return the root of this path. Roots are constructs such as
|
|
1676 "c:"
|
|
1677
|
|
1678 ***********************************************************************/
|
|
1679
|
|
1680 abstract char[] root ();
|
|
1681
|
|
1682 /***********************************************************************
|
|
1683
|
|
1684 Return the file path. Paths may start and end with a "/".
|
|
1685 The root path is "/" and an unspecified path is returned as
|
|
1686 an empty string. Directory paths may be split such that the
|
|
1687 directory name is placed into the 'name' member; directory
|
|
1688 paths are treated no differently than file paths
|
|
1689
|
|
1690 ***********************************************************************/
|
|
1691
|
|
1692 abstract char[] folder ();
|
|
1693
|
|
1694 /***********************************************************************
|
|
1695
|
|
1696 Return the name of this file, or directory, excluding a
|
|
1697 suffix.
|
|
1698
|
|
1699 ***********************************************************************/
|
|
1700
|
|
1701 abstract char[] name ();
|
|
1702
|
|
1703 /***********************************************************************
|
|
1704
|
|
1705 Ext is the tail of the filename, rightward of the rightmost
|
|
1706 '.' separator e.g. path "foo.bar" has ext "bar". Note that
|
|
1707 patterns of adjacent separators are treated specially; for
|
|
1708 example, ".." will wind up with no ext at all
|
|
1709
|
|
1710 ***********************************************************************/
|
|
1711
|
|
1712 abstract char[] ext ();
|
|
1713
|
|
1714 /***********************************************************************
|
|
1715
|
|
1716 Suffix is like ext, but includes the separator e.g. path
|
|
1717 "foo.bar" has suffix ".bar"
|
|
1718
|
|
1719 ***********************************************************************/
|
|
1720
|
|
1721 abstract char[] suffix ();
|
|
1722
|
|
1723 /***********************************************************************
|
|
1724
|
|
1725 return the root + folder combination
|
|
1726
|
|
1727 ***********************************************************************/
|
|
1728
|
|
1729 abstract char[] path ();
|
|
1730
|
|
1731 /***********************************************************************
|
|
1732
|
|
1733 return the name + suffix combination
|
|
1734
|
|
1735 ***********************************************************************/
|
|
1736
|
|
1737 abstract char[] file ();
|
|
1738
|
|
1739 /***********************************************************************
|
|
1740
|
|
1741 Returns true if this FilePath is *not* relative to the
|
|
1742 current working directory.
|
|
1743
|
|
1744 ***********************************************************************/
|
|
1745
|
|
1746 abstract bool isAbsolute ();
|
|
1747
|
|
1748 /***********************************************************************
|
|
1749
|
|
1750 Returns true if this FilePath is empty
|
|
1751
|
|
1752 ***********************************************************************/
|
|
1753
|
|
1754 abstract bool isEmpty ();
|
|
1755
|
|
1756 /***********************************************************************
|
|
1757
|
|
1758 Returns true if this FilePath has a parent
|
|
1759
|
|
1760 ***********************************************************************/
|
|
1761
|
|
1762 abstract bool isChild ();
|
|
1763
|
|
1764 /***********************************************************************
|
|
1765
|
|
1766 Does this path currently exist?
|
|
1767
|
|
1768 ***********************************************************************/
|
|
1769
|
|
1770 abstract bool exists ();
|
|
1771
|
|
1772 /***********************************************************************
|
|
1773
|
|
1774 Returns the time of the last modification. Accurate
|
|
1775 to whatever the OS supports
|
|
1776
|
|
1777 ***********************************************************************/
|
|
1778
|
|
1779 abstract Time modified ();
|
|
1780
|
|
1781 /***********************************************************************
|
|
1782
|
|
1783 Returns the time of the last access. Accurate to
|
|
1784 whatever the OS supports
|
|
1785
|
|
1786 ***********************************************************************/
|
|
1787
|
|
1788 abstract Time accessed ();
|
|
1789
|
|
1790 /***********************************************************************
|
|
1791
|
|
1792 Returns the time of file creation. Accurate to
|
|
1793 whatever the OS supports
|
|
1794
|
|
1795 ***********************************************************************/
|
|
1796
|
|
1797 abstract Time created ();
|
|
1798
|
|
1799 /***********************************************************************
|
|
1800
|
|
1801 Return the file length (in bytes)
|
|
1802
|
|
1803 ***********************************************************************/
|
|
1804
|
|
1805 abstract ulong fileSize ();
|
|
1806
|
|
1807 /***********************************************************************
|
|
1808
|
|
1809 Is this file writable?
|
|
1810
|
|
1811 ***********************************************************************/
|
|
1812
|
|
1813 abstract bool isWritable ();
|
|
1814
|
|
1815 /***********************************************************************
|
|
1816
|
|
1817 Is this file actually a folder/directory?
|
|
1818
|
|
1819 ***********************************************************************/
|
|
1820
|
|
1821 abstract bool isFolder ();
|
|
1822
|
|
1823 /***********************************************************************
|
|
1824
|
|
1825 Return timestamp information
|
|
1826
|
|
1827 ***********************************************************************/
|
|
1828
|
|
1829 abstract Stamps timeStamps ();
|
|
1830 }
|
|
1831
|
|
1832
|
|
1833
|
|
1834
|
|
1835
|
|
1836 /*******************************************************************************
|
|
1837
|
|
1838 *******************************************************************************/
|
|
1839
|
|
1840 debug (UnitTest)
|
|
1841 {
|
|
1842 unittest
|
|
1843 {
|
|
1844 version(Win32)
|
|
1845 {
|
|
1846 auto fp = new FilePath(r"C:/home/foo/bar");
|
|
1847 fp ~= "john";
|
|
1848 assert (fp == r"C:/home/foo/bar/john");
|
|
1849 fp = r"C:/";
|
|
1850 fp ~= "john";
|
|
1851 assert (fp == r"C:/john");
|
|
1852 fp = "foo.bar";
|
|
1853 fp ~= "john";
|
|
1854 assert (fp == r"foo.bar/john");
|
|
1855 fp = "";
|
|
1856 fp ~= "john";
|
|
1857 assert (fp == r"john");
|
|
1858
|
|
1859 fp = r"C:/home/foo/bar/john/foo.d";
|
|
1860 assert (fp.pop == r"C:/home/foo/bar/john");
|
|
1861 assert (fp.pop == r"C:/home/foo/bar");
|
|
1862 assert (fp.pop == r"C:/home/foo");
|
|
1863 assert (fp.pop == r"C:/home");
|
|
1864 assert (fp.pop == r"C:");
|
|
1865 assert (fp.pop == r"C:");
|
|
1866
|
|
1867 // special case for popping empty names
|
|
1868 fp = r"C:/home/foo/bar/john/";
|
|
1869 assert (fp.pop == r"C:/home/foo/bar", fp.toString);
|
|
1870
|
|
1871 fp = new FilePath;
|
|
1872 fp = r"C:/home/foo/bar/john/";
|
|
1873 assert (fp.isAbsolute);
|
|
1874 assert (fp.name == "");
|
|
1875 assert (fp.folder == r"/home/foo/bar/john/");
|
|
1876 assert (fp == r"C:/home/foo/bar/john/");
|
|
1877 assert (fp.path == r"C:/home/foo/bar/john/");
|
|
1878 assert (fp.file == r"");
|
|
1879 assert (fp.suffix == r"");
|
|
1880 assert (fp.root == r"C:");
|
|
1881 assert (fp.ext == "");
|
|
1882 assert (fp.isChild);
|
|
1883
|
|
1884 fp = new FilePath(r"C:/home/foo/bar/john");
|
|
1885 assert (fp.isAbsolute);
|
|
1886 assert (fp.name == "john");
|
|
1887 assert (fp.folder == r"/home/foo/bar/");
|
|
1888 assert (fp == r"C:/home/foo/bar/john");
|
|
1889 assert (fp.path == r"C:/home/foo/bar/");
|
|
1890 assert (fp.file == r"john");
|
|
1891 assert (fp.suffix == r"");
|
|
1892 assert (fp.ext == "");
|
|
1893 assert (fp.isChild);
|
|
1894
|
|
1895 fp.pop;
|
|
1896 assert (fp.isAbsolute);
|
|
1897 assert (fp.name == "bar");
|
|
1898 assert (fp.folder == r"/home/foo/");
|
|
1899 assert (fp == r"C:/home/foo/bar");
|
|
1900 assert (fp.path == r"C:/home/foo/");
|
|
1901 assert (fp.file == r"bar");
|
|
1902 assert (fp.suffix == r"");
|
|
1903 assert (fp.ext == "");
|
|
1904 assert (fp.isChild);
|
|
1905
|
|
1906 fp.pop;
|
|
1907 assert (fp.isAbsolute);
|
|
1908 assert (fp.name == "foo");
|
|
1909 assert (fp.folder == r"/home/");
|
|
1910 assert (fp == r"C:/home/foo");
|
|
1911 assert (fp.path == r"C:/home/");
|
|
1912 assert (fp.file == r"foo");
|
|
1913 assert (fp.suffix == r"");
|
|
1914 assert (fp.ext == "");
|
|
1915 assert (fp.isChild);
|
|
1916
|
|
1917 fp.pop;
|
|
1918 assert (fp.isAbsolute);
|
|
1919 assert (fp.name == "home");
|
|
1920 assert (fp.folder == r"/");
|
|
1921 assert (fp == r"C:/home");
|
|
1922 assert (fp.path == r"C:/");
|
|
1923 assert (fp.file == r"home");
|
|
1924 assert (fp.suffix == r"");
|
|
1925 assert (fp.ext == "");
|
|
1926 assert (fp.isChild);
|
|
1927
|
|
1928 fp = new FilePath(r"foo/bar/john.doe");
|
|
1929 assert (!fp.isAbsolute);
|
|
1930 assert (fp.name == "john");
|
|
1931 assert (fp.folder == r"foo/bar/");
|
|
1932 assert (fp.suffix == r".doe");
|
|
1933 assert (fp.file == r"john.doe");
|
|
1934 assert (fp == r"foo/bar/john.doe");
|
|
1935 assert (fp.ext == "doe");
|
|
1936 assert (fp.isChild);
|
|
1937
|
|
1938 fp = new FilePath(r"c:doe");
|
|
1939 assert (fp.isAbsolute);
|
|
1940 assert (fp.suffix == r"");
|
|
1941 assert (fp == r"c:doe");
|
|
1942 assert (fp.folder == r"");
|
|
1943 assert (fp.name == "doe");
|
|
1944 assert (fp.file == r"doe");
|
|
1945 assert (fp.ext == "");
|
|
1946 assert (!fp.isChild);
|
|
1947
|
|
1948 fp = new FilePath(r"/doe");
|
|
1949 assert (fp.isAbsolute);
|
|
1950 assert (fp.suffix == r"");
|
|
1951 assert (fp == r"/doe");
|
|
1952 assert (fp.name == "doe");
|
|
1953 assert (fp.folder == r"/");
|
|
1954 assert (fp.file == r"doe");
|
|
1955 assert (fp.ext == "");
|
|
1956 assert (fp.isChild);
|
|
1957
|
|
1958 fp = new FilePath(r"john.doe.foo");
|
|
1959 assert (!fp.isAbsolute);
|
|
1960 assert (fp.name == "john.doe");
|
|
1961 assert (fp.folder == r"");
|
|
1962 assert (fp.suffix == r".foo");
|
|
1963 assert (fp == r"john.doe.foo");
|
|
1964 assert (fp.file == r"john.doe.foo");
|
|
1965 assert (fp.ext == "foo");
|
|
1966 assert (!fp.isChild);
|
|
1967
|
|
1968 fp = new FilePath(r".doe");
|
|
1969 assert (!fp.isAbsolute);
|
|
1970 assert (fp.suffix == r"");
|
|
1971 assert (fp == r".doe");
|
|
1972 assert (fp.name == ".doe");
|
|
1973 assert (fp.folder == r"");
|
|
1974 assert (fp.file == r".doe");
|
|
1975 assert (fp.ext == "");
|
|
1976 assert (!fp.isChild);
|
|
1977
|
|
1978 fp = new FilePath(r"doe");
|
|
1979 assert (!fp.isAbsolute);
|
|
1980 assert (fp.suffix == r"");
|
|
1981 assert (fp == r"doe");
|
|
1982 assert (fp.name == "doe");
|
|
1983 assert (fp.folder == r"");
|
|
1984 assert (fp.file == r"doe");
|
|
1985 assert (fp.ext == "");
|
|
1986 assert (!fp.isChild);
|
|
1987
|
|
1988 fp = new FilePath(r".");
|
|
1989 assert (!fp.isAbsolute);
|
|
1990 assert (fp.suffix == r"");
|
|
1991 assert (fp == r".");
|
|
1992 assert (fp.name == ".");
|
|
1993 assert (fp.folder == r"");
|
|
1994 assert (fp.file == r".");
|
|
1995 assert (fp.ext == "");
|
|
1996 assert (!fp.isChild);
|
|
1997
|
|
1998 fp = new FilePath(r"..");
|
|
1999 assert (!fp.isAbsolute);
|
|
2000 assert (fp.suffix == r"");
|
|
2001 assert (fp == r"..");
|
|
2002 assert (fp.name == "..");
|
|
2003 assert (fp.folder == r"");
|
|
2004 assert (fp.file == r"..");
|
|
2005 assert (fp.ext == "");
|
|
2006 assert (!fp.isChild);
|
|
2007
|
|
2008 fp = new FilePath(r"c:/a/b/c/d/e/foo.bar");
|
|
2009 assert (fp.isAbsolute);
|
|
2010 fp.folder (r"/a/b/c/");
|
|
2011 assert (fp.suffix == r".bar");
|
|
2012 assert (fp == r"c:/a/b/c/foo.bar");
|
|
2013 assert (fp.name == "foo");
|
|
2014 assert (fp.folder == r"/a/b/c/");
|
|
2015 assert (fp.file == r"foo.bar");
|
|
2016 assert (fp.ext == "bar");
|
|
2017 assert (fp.isChild);
|
|
2018
|
|
2019 fp = new FilePath(r"c:/a/b/c/d/e/foo.bar");
|
|
2020 assert (fp.isAbsolute);
|
|
2021 fp.folder (r"/a/b/c/d/e/f/g/");
|
|
2022 assert (fp.suffix == r".bar");
|
|
2023 assert (fp == r"c:/a/b/c/d/e/f/g/foo.bar");
|
|
2024 assert (fp.name == "foo");
|
|
2025 assert (fp.folder == r"/a/b/c/d/e/f/g/");
|
|
2026 assert (fp.file == r"foo.bar");
|
|
2027 assert (fp.ext == "bar");
|
|
2028 assert (fp.isChild);
|
|
2029
|
|
2030 fp = new FilePath(r"C:\foo\bar\test.bar");
|
|
2031 assert (fp.path == "C:/foo/bar/");
|
|
2032 fp = new FilePath(r"C:/foo/bar/test.bar");
|
|
2033 assert (fp.path == r"C:/foo/bar/");
|
|
2034
|
|
2035 fp = new FilePath("");
|
|
2036 assert (fp.isEmpty);
|
|
2037 assert (!fp.isChild);
|
|
2038 assert (!fp.isAbsolute);
|
|
2039 assert (fp.suffix == r"");
|
|
2040 assert (fp == r"");
|
|
2041 assert (fp.name == "");
|
|
2042 assert (fp.folder == r"");
|
|
2043 assert (fp.file == r"");
|
|
2044 assert (fp.ext == "");
|
|
2045 /+
|
|
2046 fp = new FilePath(r"C:/foo/bar/test.bar");
|
|
2047 fp = new FilePath(fp.asPath ("foo"));
|
|
2048 assert (fp.name == r"test");
|
|
2049 assert (fp.folder == r"foo/");
|
|
2050 assert (fp.path == r"C:foo/");
|
|
2051 assert (fp.ext == ".bar");
|
|
2052
|
|
2053 fp = new FilePath(fp.asPath (""));
|
|
2054 assert (fp.name == r"test");
|
|
2055 assert (fp.folder == r"");
|
|
2056 assert (fp.path == r"C:");
|
|
2057 assert (fp.ext == ".bar");
|
|
2058
|
|
2059 fp = new FilePath(r"c:/joe/bar");
|
|
2060 assert(fp.cat(r"foo/bar/") == r"c:/joe/bar/foo/bar/");
|
|
2061 assert(fp.cat(new FilePath(r"foo/bar")).toString == r"c:/joe/bar/foo/bar");
|
|
2062
|
|
2063 assert (FilePath.join (r"a/b/c/d", r"e/f/" r"g") == r"a/b/c/d/e/f/g");
|
|
2064
|
|
2065 fp = new FilePath(r"C:/foo/bar/test.bar");
|
|
2066 assert (fp.asExt(null) == r"C:/foo/bar/test");
|
|
2067 assert (fp.asExt("foo") == r"C:/foo/bar/test.foo");
|
|
2068 +/
|
|
2069 }
|
|
2070 }
|
|
2071 }
|
|
2072
|
|
2073
|
|
2074 debug (FilePath)
|
|
2075 {
|
|
2076 import tango.io.Console;
|
|
2077
|
|
2078 void main()
|
|
2079 {
|
|
2080 Cout (FilePath("c:/temp/").file("foo.bar")).newline;
|
|
2081 }
|
|
2082
|
|
2083 }
|