1 /*
2 * Modification History
3 *
4 * 2001-February-11 Jason Rohrer
5 * Created.
6 *
7 * 2001-February-25 Jason Rohrer
8 * Fixed file name bugs in length and existence functions.
9 *
10 * 2001-May-11 Jason Rohrer
11 * Added a missing include.
12 *
13 * 2001-November-3 Jason Rohrer
14 * Added a function for checking if a file is a directory.
15 * Added a function for getting the child files of a directory.
16 * Added a function for getting a pathless file name.
17 *
18 * 2001-November-13 Jason Rohrer
19 * Made name length parameter optional in constructor.
20 * Made return length parameter optional in name getting functions.
21 *
22 * 2001-November-17 Jason Rohrer
23 * Added a functions for removing a file and for copying a file.
24 *
25 * 2002-March-11 Jason Rohrer
26 * Added destruction comment to getFullFileName().
27 *
28 * 2002-March-13 Jason Rohrer
29 * Changed mName to be \0-terminated to fix interaction bugs with Path.
30 * Fixed a missing delete.
31 * Added a function for creating a directory.
32 *
33 * 2002-March-31 Jason Rohrer
34 * Fixed some bad syntax.
35 *
36 * 2002-April-6 Jason Rohrer
37 * Replaced use of strdup.
38 *
39 * 2002-April-8 Jason Rohrer
40 * Fixed fopen bug.
41 *
42 * 2002-April-11 Jason Rohrer
43 * Fixed a memory leak.
44 * Fixed a casting error.
45 *
46 * 2002-June-28 Jason Rohrer
47 * Added a function for copying a file class.
48 *
49 * 2002-August-3 Jason Rohrer
50 * Added a function for getting the parent file.
51 *
52 * 2002-August-5 Jason Rohrer
53 * Used an unused error variable.
54 *
55 * 2002-September-11 Jason Rohrer
56 * Added return value to remove.
57 *
58 * 2003-January-27 Jason Rohrer
59 * Added a function for reading file contents.
60 *
61 * 2003-February-3 Jason Rohrer
62 * Added a function for writing a string to a file.
63 *
64 * 2003-March-13 Jason Rohrer
65 * Added a function for getting a child file from a directory.
66 *
67 * 2003-June-2 Jason Rohrer
68 * Fixed parent directory behavior when current file is root directory.
69 * Fixed a bug in getting child files of root directory.
70 *
71 * 2003-November-6 Jason Rohrer
72 * Added function for getting last modification time.
73 *
74 * 2003-November-10 Jason Rohrer
75 * Changed to use platform-dependent makeDirectory function.
76 *
77 * 2004-January-4 Jason Rohrer
78 * Added recursive child file functions.
79 *
80 * 2005-August-29 Jason Rohrer
81 * Fixed an uninitialized variable warning.
82 *
83 * 2010-March-6 Jason Rohrer
84 * Added versions of writeToFile readFileContents for binary data.
85 *
86 * 2010-April-23 Jason Rohrer
87 * Fixed a string length bug when line ends are Windows.
88 *
89 * 2010-May-14 Jason Rohrer
90 * String parameters as const to fix warnings.
91 */
92
93
94
95 #include "minorGems/common.h"
96
97
98
99 #ifndef FILE_CLASS_INCLUDED
100 #define FILE_CLASS_INCLUDED
101
102 #include <sys/stat.h>
103 #include <stdio.h>
104 #include <string.h>
105
106 #include <dirent.h>
107
108 #include "Path.h"
109
110 #include "minorGems/util/SimpleVector.h"
111 #include "minorGems/util/stringUtils.h"
112
113
114
115 /**
116 * File interface. Provides access to information about a
117 * file.
118 *
119 * @author Jason Rohrer
120 */
121 class File {
122
123 public:
124
125 /**
126 * Constructs a file.
127 *
128 * @param inPath the path for this file.
129 * Is destroyed when this class is destroyed.
130 * Pass in NULL to specify
131 * no path (the current working directory).
132 * @param inName the name of the file to open.
133 * Must be destroyed by caller if not const.
134 * Copied internally.
135 * @param inNameLength length of the name in chars,
136 * or -1 to use the c-string length of inName
137 * (assuming that inName is \0-terminated).
138 * Defaults to -1.
139 */
140 File( Path *inPath, const char *inName, int inNameLength = -1 );
141
142
143 ~File();
144
145
146
147 /**
148 * Gets whether this file is a directory.
149 *
150 * @return true iff this file is a directory.
151 */
152 char isDirectory();
153
154
155
156 /**
157 * Makes a directory in the location of this file.
158 *
159 * Can only succeed if exists() is false.
160 *
161 * @return true iff directory creation succeeded.
162 */
163 char makeDirectory();
164
165
166
167 /**
168 * Gets the files contained in this file if it is a directory.
169 *
170 * @param outNumFiles pointer to where the number of
171 * files will be returned.
172 *
173 * @return an array of files, or NULL if this
174 * file is not a directory, is an empty directory, or doesn't exist.
175 * Must be destroyed by caller if non-NULL.
176 */
177 File **getChildFiles( int *outNumFiles );
178
179
180
181 /**
182 * Gets the files contained in this file if it is a directory and
183 * recursively in subdirectories of this file.
184 *
185 * @param inDepthLimit the maximum subdirectory depth to recurse into.
186 * If inDepthLimit is 0, then only child files in this directory
187 * will be returned.
188 * @param outNumFiles pointer to where the number of
189 * files will be returned.
190 *
191 * @return an array of files, or NULL if this
192 * file is not a directory, is an empty directory (or a directory
193 * containing empty subdirectories), or doesn't exist.
194 * Must be destroyed by caller if non-NULL.
195 */
196 File **getChildFilesRecursive( int inDepthLimit, int *outNumFiles );
197
198
199
200 /**
201 * Gets a child of this directory.
202 *
203 * @param inChildFileName the name of the child file.
204 * Must be destroyed by caller if non-const.
205 *
206 * @return the child file (even if it does not exist), or NULL if
207 * this file is not a directory.
208 * Must be destroyed by caller if non-NULL.
209 */
210 File *getChildFile( const char *inChildFileName );
211
212
213
214 /**
215 * Gets the parent directory of this file.
216 *
217 * @return the parent directory of this file.
218 * Must be destroyed by caller.
219 */
220 File *getParentDirectory();
221
222
223
224 /**
225 * Gets the length of this file.
226 *
227 * @return the length of this file in bytes. Returns
228 * 0 if the file does not exist.
229 */
230 long getLength();
231
232
233 /**
234 * Gets whether a file exists.
235 *
236 * @return true if the file exists.
237 */
238 char exists();
239
240
241
242 /**
243 * Gets the last modification time of this file.
244 *
245 * @return the modification time in seconds based on the
246 * system clock. Returns 0 if the file does not exist.
247 */
248 unsigned long getModificationTime();
249
250
251
252 /**
253 * Removes this file from the disk, if it exists.
254 *
255 * @return true iff the remove succeeded, false if the removal
256 * fails or the file does not exist.
257 */
258 char remove();
259
260
261
262 /**
263 * Copies this file object (does not copy the file described by
264 * this object).
265 *
266 * @return a deep copy of this file object.
267 */
268 File *copy();
269
270
271
272 /**
273 * Copies the contents of this file into another file.
274 *
275 * @param inDestination the file to copy this file into.
276 * If it exists, it will be overwritten.
277 * If it does not exist, it will be created.
278 * Must be destroyed by caller.
279 * @param inBlockSize the block size to use when copying.
280 * Defaults to blocks of 5000 bytes.
281 */
282 void copy( File *inDestination, long inBlockSize = 5000 );
283
284
285
286 /**
287 * Gets the full-path file name sufficient
288 * to access this file from the current working
289 * directory.
290 *
291 * @param outLength pointer to where the name length, in
292 * characters, will be returned. Set to NULL to ignore
293 * the output length. Defaults to NULL.
294 *
295 * @return the full path file name for this file,
296 * in platform-specific form. Must be destroyed by caller.
297 * The returned string is '\0' terminated, but this
298 * extra character is not included in the length.
299 * Must be destroyed by caller.
300 */
301 char *getFullFileName( int *outLength = NULL );
302
303
304
305 /**
306 * Gets the pathless name of this file.
307 *
308 * @param outLength pointer to where the name length, in
309 * characters, will be returned. Set to NULL to ignore
310 * the output length. Defaults to NULL.
311 *
312 * @return the name of this file. Must be destroyed by caller.
313 */
314 char *getFileName( int *outLength = NULL );
315
316
317
318 /**
319 * Reads the contents of this file.
320 *
321 * @return a \0-terminated string containing the file contents,
322 * or NULL if reading the file into memory failed.
323 * Must be destroyed by caller.
324 */
325 char *readFileContents();
326
327
328
329 /**
330 * Reads the contents of this file.
331 *
332 * @param outLength pointer to where the return array length should
333 * be returned.
334 * @param inTextMode true to open the file as text, false as binary.
335 * Defaults to false.
336 *
337 * @return an array containing the binary file contents,
338 * or NULL if reading the file into memory failed.
339 * Must be destroyed by caller.
340 */
341 unsigned char *readFileContents( int *outLength,
342 char inTextMode = false );
343
344
345
346 /**
347 * Writes a string to this file.
348 *
349 * @param inString the \0-terminated string to write.
350 * Must be destroyed by caller if non-const.
351 *
352 * @return true if the file was written to successfully, or
353 * false otherwise.
354 */
355 char writeToFile( const char *inString );
356
357
358 /**
359 * Writes a binary data to this file.
360 *
361 * @param inData the data to write.
362 * Must be destroyed by caller if non-const.
363 * @param inLength length of inData.
364 *
365 * @return true if the file was written to successfully, or
366 * false otherwise.
367 */
368 char writeToFile( unsigned char *inData, int inLength );
369
370
371
372 private:
373 Path *mPath;
374 char *mName;
375 int mNameLength;
376
377
378
379 /**
380 * Gets the files contained in this file if it is a directory and
381 * recursively in subdirectories of this file.
382 *
383 * @param inDepthLimit the maximum subdirectory depth to recurse into.
384 * If inDepthLimit is 0, then only child files in this directory
385 * will be returned.
386 * @param inResultVector vector to add the discovered files to.
387 * Must be destroyed by caller.
388 */
389 void getChildFilesRecursive( int inDepthLimit,
390 SimpleVector<File *> *inResultVector );
391
392
393
394 };
395
396
397
File(Path * inPath,const char * inName,int inNameLength)398 inline File::File( Path *inPath, const char *inName, int inNameLength )
399 : mPath( inPath ), mNameLength( inNameLength ) {
400
401 if( inNameLength == -1 ) {
402 inNameLength = strlen( inName );
403 mNameLength = inNameLength;
404 }
405
406 // copy name internally
407 mName = stringDuplicate( inName );
408
409 }
410
411
412
~File()413 inline File::~File() {
414 delete [] mName;
415
416 if( mPath != NULL ) {
417 delete mPath;
418 }
419 }
420
421
422
getLength()423 inline long File::getLength() {
424 struct stat fileInfo;
425
426 // get full file name
427 int length;
428 char *stringName = getFullFileName( &length );
429
430 int statError = stat( stringName, &fileInfo );
431
432 delete [] stringName;
433
434 if( statError == 0 ) {
435 return fileInfo.st_size;
436 }
437 else {
438 // file does not exist
439 return 0;
440 }
441 }
442
443
444
isDirectory()445 inline char File::isDirectory() {
446 struct stat fileInfo;
447
448 // get full file name
449 int length;
450 char *stringName = getFullFileName( &length );
451
452 int statError = stat( stringName, &fileInfo );
453
454 delete [] stringName;
455
456 if( statError == -1 ) {
457 return false;
458 }
459 else {
460 return S_ISDIR( fileInfo.st_mode );
461 }
462 }
463
464
465
getChildFiles(int * outNumFiles)466 inline File **File::getChildFiles( int *outNumFiles ) {
467
468 int length;
469 char *stringName = getFullFileName( &length );
470
471 DIR *directory = opendir( stringName );
472
473 if( directory != NULL ) {
474
475 SimpleVector< File* > *fileVector = new SimpleVector< File* >();
476
477 struct dirent *entry = readdir( directory );
478
479 if( entry == NULL ) {
480 delete fileVector;
481
482 closedir( directory );
483
484 delete [] stringName;
485
486 *outNumFiles = 0;
487 return NULL;
488 }
489
490
491 while( entry != NULL ) {
492 // skip parentdir and thisdir files, if they occur
493 if( strcmp( entry->d_name, "." ) &&
494 strcmp( entry->d_name, ".." ) ) {
495
496 Path *newPath;
497
498 if( mPath != NULL ) {
499 newPath = mPath->append( mName );
500 }
501 else {
502
503 if( Path::isRoot( mName ) ) {
504 // use name as a string path
505 newPath = new Path( mName );
506 }
507 else {
508 char **folderPathArray = new char*[1];
509 folderPathArray[0] = mName;
510
511 // a non-absolute path to this directory's contents
512 int numSteps = 1;
513 char absolute = false;
514 newPath =
515 new Path( folderPathArray, numSteps,
516 absolute );
517
518 delete [] folderPathArray;
519 }
520 }
521
522 // safe to pass d_name in directly because it is copied
523 // internally by the constructor
524
525 fileVector->push_back(
526 new File( newPath,
527 entry->d_name,
528 strlen( entry->d_name ) ) );
529 }
530
531 entry = readdir( directory );
532 }
533
534 // now we have a vector full of this directory's files
535 int vectorSize = fileVector->size();
536
537 *outNumFiles = vectorSize;
538
539 if( vectorSize == 0 ) {
540 delete fileVector;
541
542 closedir( directory );
543
544 delete [] stringName;
545
546 return NULL;
547 }
548 else {
549 File **returnFiles = new File *[vectorSize];
550 for( int i=0; i<vectorSize; i++ ) {
551 returnFiles[i] = *( fileVector->getElement( i ) );
552 }
553
554 delete fileVector;
555
556 closedir( directory );
557
558 delete [] stringName;
559
560 return returnFiles;
561 }
562 }
563 else {
564 delete [] stringName;
565
566 *outNumFiles = 0;
567 return NULL;
568 }
569
570
571
572 }
573
574
575
getChildFilesRecursive(int inDepthLimit,int * outNumFiles)576 inline File **File::getChildFilesRecursive( int inDepthLimit,
577 int *outNumFiles ) {
578
579 // create a vector for results
580 SimpleVector<File *> *resultVector = new SimpleVector<File *>();
581
582 // call the recursive function
583 getChildFilesRecursive( inDepthLimit, resultVector );
584
585
586 // extract results from vector
587 File **resultArray = NULL;
588
589 int numResults = resultVector->size();
590
591 if( numResults > 0 ) {
592 resultArray = resultVector->getElementArray();
593 }
594
595 delete resultVector;
596
597
598
599 *outNumFiles = numResults;
600 return resultArray;
601 }
602
603
604
getChildFilesRecursive(int inDepthLimit,SimpleVector<File * > * inResultVector)605 inline void File::getChildFilesRecursive(
606 int inDepthLimit,
607 SimpleVector<File *> *inResultVector ) {
608
609 // get our child files
610 int numChildren;
611 File **childFiles = getChildFiles( &numChildren );
612
613 if( childFiles != NULL ) {
614
615 // for each child, add it to vector and
616 // recurse into it if it is a directory
617
618 for( int i=0; i<numChildren; i++ ) {
619
620 File *child = childFiles[i];
621
622 // add it to results vector
623 inResultVector->push_back( child );
624
625 if( child->isDirectory() ) {
626 // skip recursion if we have hit our depth limit
627 if( inDepthLimit > 0 ) {
628 // recurse into this subdirectory
629 child->getChildFilesRecursive( inDepthLimit - 1,
630 inResultVector );
631 }
632 }
633 }
634
635 delete [] childFiles;
636 }
637 }
638
639
640
getChildFile(const char * inChildFileName)641 inline File *File::getChildFile( const char *inChildFileName ) {
642 // make sure we are a directory
643 if( !isDirectory() ) {
644 return NULL;
645 }
646
647 // get a path to this directory
648 Path *newPath;
649
650 if( mPath != NULL ) {
651 newPath = mPath->append( mName );
652 }
653 else {
654
655 char **folderPathArray = new char*[1];
656 folderPathArray[0] = mName;
657
658 // a non-absolute path to this directory's contents
659 int numSteps = 1;
660 char absolute = false;
661 newPath =
662 new Path( folderPathArray, numSteps,
663 absolute );
664
665 delete [] folderPathArray;
666 }
667
668 return new File( newPath, inChildFileName );
669 }
670
671
672
getParentDirectory()673 inline File *File::getParentDirectory() {
674
675 if( mPath != NULL ) {
676
677 char *parentName;
678
679 Path *parentPath;
680
681 if( strcmp( mName, ".." ) == 0 ) {
682 // already a parent dir reference
683 // append one more parent dir reference with parentName below
684 parentPath = mPath->append( ".." );
685
686 parentName = stringDuplicate( ".." );
687 }
688 else {
689 // not a parent dir reference, so we can truncate
690 parentPath = mPath->truncate();
691
692 parentName = mPath->getLastStep();
693 }
694
695 File *parentFile = new File( parentPath, parentName );
696
697 delete [] parentName;
698
699 return parentFile;
700 }
701 else {
702 if( Path::isRoot( mName ) ) {
703 // we are already at the root
704 return new File( NULL, mName );
705 }
706 else {
707 // append parent dir symbol to path
708 char **parentPathSteps = new char*[1];
709 parentPathSteps[0] = mName;
710
711 Path *parentPath = new Path( parentPathSteps, 1, false );
712
713 const char *parentName = "..";
714
715 File *parentFile = new File( parentPath, parentName );
716
717 delete [] parentPathSteps;
718
719 return parentFile;
720 }
721 }
722 }
723
724
725
exists()726 inline char File::exists() {
727 struct stat fileInfo;
728
729 // get full file name
730 int length;
731 char *stringName = getFullFileName( &length );
732
733 int statError = stat( stringName, &fileInfo );
734
735 delete [] stringName;
736
737 if( statError == 0 ) {
738 return true;
739 }
740 else {
741 // file does not exist
742 return false;
743 }
744 }
745
746
747
getModificationTime()748 inline unsigned long File::getModificationTime() {
749 struct stat fileInfo;
750
751 // get full file name
752 int length;
753 char *stringName = getFullFileName( &length );
754
755 int statError = stat( stringName, &fileInfo );
756
757 delete [] stringName;
758
759 if( statError == 0 ) {
760 return fileInfo.st_mtime;
761 }
762 else {
763 // file does not exist
764 return 0;
765 }
766 }
767
768
769
remove()770 inline char File::remove() {
771 char returnVal = false;
772
773 if( exists() ) {
774 char *stringName = getFullFileName();
775
776 int error = ::remove( stringName );
777
778 if( error == 0 ) {
779 returnVal = true;
780 }
781
782 delete [] stringName;
783 }
784
785 return returnVal;
786 }
787
788
789
copy()790 inline File *File::copy() {
791 Path *pathCopy = NULL;
792
793 if( mPath != NULL ) {
794 pathCopy = mPath->copy();
795 }
796
797 return new File( pathCopy, mName );
798 }
799
800
801
copy(File * inDestination,long inBlockSize)802 inline void File::copy( File *inDestination, long inBlockSize ) {
803 char *thisFileName = getFullFileName();
804 char *destinationFileName = inDestination->getFullFileName();
805
806 FILE *thisFile = fopen( thisFileName, "rb" );
807 FILE *destinationFile = fopen( destinationFileName, "wb" );
808
809 long length = getLength();
810
811 long bytesCopied = 0;
812
813 char *buffer = new char[ inBlockSize ];
814
815 while( bytesCopied < length ) {
816
817 long bytesToCopy = inBlockSize;
818
819 // end of file case
820 if( length - bytesCopied < bytesToCopy ) {
821 bytesToCopy = length - bytesCopied;
822 }
823
824 fread( buffer, 1, bytesToCopy, thisFile );
825 fwrite( buffer, 1, bytesToCopy, destinationFile );
826
827 bytesCopied += bytesToCopy;
828 }
829
830 fclose( thisFile );
831 fclose( destinationFile );
832
833 delete [] buffer;
834 delete [] thisFileName;
835 delete [] destinationFileName;
836 }
837
838
839
getFileName(int * outLength)840 inline char *File::getFileName( int *outLength ) {
841 char *returnName = stringDuplicate( mName );
842
843 if( outLength != NULL ) {
844 *outLength = mNameLength;
845 }
846
847 return returnName;
848 }
849
850
851
getFullFileName(int * outLength)852 inline char *File::getFullFileName( int *outLength ) {
853 int length = mNameLength;
854
855 int pathLength = 0;
856 char *path = NULL;
857 if( mPath != NULL ) {
858 path = mPath->getPathString( &pathLength );
859
860 length += pathLength;
861 }
862
863 // extra character for '\0' termination
864 char *returnString = new char[ length + 1 ];
865
866 if( path != NULL ) {
867 memcpy( returnString, path, pathLength );
868 memcpy( &( returnString[pathLength] ), mName, mNameLength );
869
870 delete [] path;
871 }
872 else {
873 // no path, so copy the name directly in
874 memcpy( returnString, mName, mNameLength );
875 }
876
877 // terminate the string
878 returnString[ length ] = '\0';
879
880
881 if( outLength != NULL ) {
882 *outLength = length;
883 }
884
885 return returnString;
886 }
887
888
889
890 #include "minorGems/io/file/FileInputStream.h"
891 #include "minorGems/io/file/FileOutputStream.h"
892
893
894
readFileContents()895 inline char *File::readFileContents() {
896
897 int length;
898 // text mode!
899 unsigned char *data = readFileContents( &length, true );
900
901 if( data == NULL ) {
902 return NULL;
903 }
904
905 char *dataString = new char[ length + 1 ];
906
907 memcpy( dataString, data, length );
908 dataString[ length ] = '\0';
909
910 delete [] data;
911 return dataString;
912 }
913
914
915
readFileContents(int * outLength,char inTextMode)916 inline unsigned char *File::readFileContents( int *outLength,
917 char inTextMode ) {
918
919 if( exists() ) {
920 int length = getLength();
921
922 unsigned char *returnData = new unsigned char[ length ];
923
924 if( returnData != NULL ) {
925 FileInputStream *input = new FileInputStream( this, inTextMode );
926 int numRead = input->read( returnData, length );
927
928 delete input;
929
930 // in text mode, read length might not equal binary file length,
931 // due to line end conversion
932 if( numRead == length ||
933 ( inTextMode && numRead >= 0 ) ) {
934 *outLength = numRead;
935 return returnData;
936 }
937 else {
938 delete [] returnData;
939 return NULL;
940 }
941 }
942 else {
943 // failed to allocate this much memory
944 return NULL;
945 }
946 }
947 else {
948 return NULL;
949 }
950
951 }
952
953
954
writeToFile(const char * inString)955 inline char File::writeToFile( const char *inString ) {
956 return writeToFile( (unsigned char *)inString, strlen( inString ) );
957 }
958
959
960
writeToFile(unsigned char * inData,int inLength)961 inline char File::writeToFile( unsigned char *inData, int inLength ) {
962 FileOutputStream *output = new FileOutputStream( this );
963
964 long numWritten = output->write( inData, inLength );
965
966 delete output;
967
968 if( inLength == numWritten ) {
969 return true;
970 }
971 else {
972 return false;
973 }
974
975 }
976
977
978
979 #include "Directory.h"
980
981
982
makeDirectory()983 inline char File::makeDirectory() {
984 if( exists() ) {
985 return false;
986 }
987 else {
988 return Directory::makeDirectory( this );
989 }
990 }
991
992
993
994 #endif
995