passage/minorGems/io/file/File.h
2025-10-03 02:19:59 -04:00

994 lines
22 KiB
C++

/*
* Modification History
*
* 2001-February-11 Jason Rohrer
* Created.
*
* 2001-February-25 Jason Rohrer
* Fixed file name bugs in length and existence functions.
*
* 2001-May-11 Jason Rohrer
* Added a missing include.
*
* 2001-November-3 Jason Rohrer
* Added a function for checking if a file is a directory.
* Added a function for getting the child files of a directory.
* Added a function for getting a pathless file name.
*
* 2001-November-13 Jason Rohrer
* Made name length parameter optional in constructor.
* Made return length parameter optional in name getting functions.
*
* 2001-November-17 Jason Rohrer
* Added a functions for removing a file and for copying a file.
*
* 2002-March-11 Jason Rohrer
* Added destruction comment to getFullFileName().
*
* 2002-March-13 Jason Rohrer
* Changed mName to be \0-terminated to fix interaction bugs with Path.
* Fixed a missing delete.
* Added a function for creating a directory.
*
* 2002-March-31 Jason Rohrer
* Fixed some bad syntax.
*
* 2002-April-6 Jason Rohrer
* Replaced use of strdup.
*
* 2002-April-8 Jason Rohrer
* Fixed fopen bug.
*
* 2002-April-11 Jason Rohrer
* Fixed a memory leak.
* Fixed a casting error.
*
* 2002-June-28 Jason Rohrer
* Added a function for copying a file class.
*
* 2002-August-3 Jason Rohrer
* Added a function for getting the parent file.
*
* 2002-August-5 Jason Rohrer
* Used an unused error variable.
*
* 2002-September-11 Jason Rohrer
* Added return value to remove.
*
* 2003-January-27 Jason Rohrer
* Added a function for reading file contents.
*
* 2003-February-3 Jason Rohrer
* Added a function for writing a string to a file.
*
* 2003-March-13 Jason Rohrer
* Added a function for getting a child file from a directory.
*
* 2003-June-2 Jason Rohrer
* Fixed parent directory behavior when current file is root directory.
* Fixed a bug in getting child files of root directory.
*
* 2003-November-6 Jason Rohrer
* Added function for getting last modification time.
*
* 2003-November-10 Jason Rohrer
* Changed to use platform-dependent makeDirectory function.
*
* 2004-January-4 Jason Rohrer
* Added recursive child file functions.
*
* 2005-August-29 Jason Rohrer
* Fixed an uninitialized variable warning.
*
* 2010-March-6 Jason Rohrer
* Added versions of writeToFile readFileContents for binary data.
*
* 2010-April-23 Jason Rohrer
* Fixed a string length bug when line ends are Windows.
*
* 2010-May-14 Jason Rohrer
* String parameters as const to fix warnings.
*/
#include "minorGems/common.h"
#ifndef FILE_CLASS_INCLUDED
#define FILE_CLASS_INCLUDED
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include "Path.h"
#include "minorGems/util/SimpleVector.h"
#include "minorGems/util/stringUtils.h"
/**
* File interface. Provides access to information about a
* file.
*
* @author Jason Rohrer
*/
class File {
public:
/**
* Constructs a file.
*
* @param inPath the path for this file.
* Is destroyed when this class is destroyed.
* Pass in NULL to specify
* no path (the current working directory).
* @param inName the name of the file to open.
* Must be destroyed by caller if not const.
* Copied internally.
* @param inNameLength length of the name in chars,
* or -1 to use the c-string length of inName
* (assuming that inName is \0-terminated).
* Defaults to -1.
*/
File( Path *inPath, const char *inName, int inNameLength = -1 );
~File();
/**
* Gets whether this file is a directory.
*
* @return true iff this file is a directory.
*/
char isDirectory();
/**
* Makes a directory in the location of this file.
*
* Can only succeed if exists() is false.
*
* @return true iff directory creation succeeded.
*/
char makeDirectory();
/**
* Gets the files contained in this file if it is a directory.
*
* @param outNumFiles pointer to where the number of
* files will be returned.
*
* @return an array of files, or NULL if this
* file is not a directory, is an empty directory, or doesn't exist.
* Must be destroyed by caller if non-NULL.
*/
File **getChildFiles( int *outNumFiles );
/**
* Gets the files contained in this file if it is a directory and
* recursively in subdirectories of this file.
*
* @param inDepthLimit the maximum subdirectory depth to recurse into.
* If inDepthLimit is 0, then only child files in this directory
* will be returned.
* @param outNumFiles pointer to where the number of
* files will be returned.
*
* @return an array of files, or NULL if this
* file is not a directory, is an empty directory (or a directory
* containing empty subdirectories), or doesn't exist.
* Must be destroyed by caller if non-NULL.
*/
File **getChildFilesRecursive( int inDepthLimit, int *outNumFiles );
/**
* Gets a child of this directory.
*
* @param inChildFileName the name of the child file.
* Must be destroyed by caller if non-const.
*
* @return the child file (even if it does not exist), or NULL if
* this file is not a directory.
* Must be destroyed by caller if non-NULL.
*/
File *getChildFile( const char *inChildFileName );
/**
* Gets the parent directory of this file.
*
* @return the parent directory of this file.
* Must be destroyed by caller.
*/
File *getParentDirectory();
/**
* Gets the length of this file.
*
* @return the length of this file in bytes. Returns
* 0 if the file does not exist.
*/
long getLength();
/**
* Gets whether a file exists.
*
* @return true if the file exists.
*/
char exists();
/**
* Gets the last modification time of this file.
*
* @return the modification time in seconds based on the
* system clock. Returns 0 if the file does not exist.
*/
unsigned long getModificationTime();
/**
* Removes this file from the disk, if it exists.
*
* @return true iff the remove succeeded, false if the removal
* fails or the file does not exist.
*/
char remove();
/**
* Copies this file object (does not copy the file described by
* this object).
*
* @return a deep copy of this file object.
*/
File *copy();
/**
* Copies the contents of this file into another file.
*
* @param inDestination the file to copy this file into.
* If it exists, it will be overwritten.
* If it does not exist, it will be created.
* Must be destroyed by caller.
* @param inBlockSize the block size to use when copying.
* Defaults to blocks of 5000 bytes.
*/
void copy( File *inDestination, long inBlockSize = 5000 );
/**
* Gets the full-path file name sufficient
* to access this file from the current working
* directory.
*
* @param outLength pointer to where the name length, in
* characters, will be returned. Set to NULL to ignore
* the output length. Defaults to NULL.
*
* @return the full path file name for this file,
* in platform-specific form. Must be destroyed by caller.
* The returned string is '\0' terminated, but this
* extra character is not included in the length.
* Must be destroyed by caller.
*/
char *getFullFileName( int *outLength = NULL );
/**
* Gets the pathless name of this file.
*
* @param outLength pointer to where the name length, in
* characters, will be returned. Set to NULL to ignore
* the output length. Defaults to NULL.
*
* @return the name of this file. Must be destroyed by caller.
*/
char *getFileName( int *outLength = NULL );
/**
* Reads the contents of this file.
*
* @return a \0-terminated string containing the file contents,
* or NULL if reading the file into memory failed.
* Must be destroyed by caller.
*/
char *readFileContents();
/**
* Reads the contents of this file.
*
* @param outLength pointer to where the return array length should
* be returned.
* @param inTextMode true to open the file as text, false as binary.
* Defaults to false.
*
* @return an array containing the binary file contents,
* or NULL if reading the file into memory failed.
* Must be destroyed by caller.
*/
unsigned char *readFileContents( int *outLength,
char inTextMode = false );
/**
* Writes a string to this file.
*
* @param inString the \0-terminated string to write.
* Must be destroyed by caller if non-const.
*
* @return true if the file was written to successfully, or
* false otherwise.
*/
char writeToFile( const char *inString );
/**
* Writes a binary data to this file.
*
* @param inData the data to write.
* Must be destroyed by caller if non-const.
* @param inLength length of inData.
*
* @return true if the file was written to successfully, or
* false otherwise.
*/
char writeToFile( unsigned char *inData, int inLength );
private:
Path *mPath;
char *mName;
int mNameLength;
/**
* Gets the files contained in this file if it is a directory and
* recursively in subdirectories of this file.
*
* @param inDepthLimit the maximum subdirectory depth to recurse into.
* If inDepthLimit is 0, then only child files in this directory
* will be returned.
* @param inResultVector vector to add the discovered files to.
* Must be destroyed by caller.
*/
void getChildFilesRecursive( int inDepthLimit,
SimpleVector<File *> *inResultVector );
};
inline File::File( Path *inPath, const char *inName, int inNameLength )
: mPath( inPath ), mNameLength( inNameLength ) {
if( inNameLength == -1 ) {
inNameLength = strlen( inName );
mNameLength = inNameLength;
}
// copy name internally
mName = stringDuplicate( inName );
}
inline File::~File() {
delete [] mName;
if( mPath != NULL ) {
delete mPath;
}
}
inline long File::getLength() {
struct stat fileInfo;
// get full file name
int length;
char *stringName = getFullFileName( &length );
int statError = stat( stringName, &fileInfo );
delete [] stringName;
if( statError == 0 ) {
return fileInfo.st_size;
}
else {
// file does not exist
return 0;
}
}
inline char File::isDirectory() {
struct stat fileInfo;
// get full file name
int length;
char *stringName = getFullFileName( &length );
int statError = stat( stringName, &fileInfo );
delete [] stringName;
if( statError == -1 ) {
return false;
}
else {
return S_ISDIR( fileInfo.st_mode );
}
}
inline File **File::getChildFiles( int *outNumFiles ) {
int length;
char *stringName = getFullFileName( &length );
DIR *directory = opendir( stringName );
if( directory != NULL ) {
SimpleVector< File* > *fileVector = new SimpleVector< File* >();
struct dirent *entry = readdir( directory );
if( entry == NULL ) {
delete fileVector;
closedir( directory );
delete [] stringName;
*outNumFiles = 0;
return NULL;
}
while( entry != NULL ) {
// skip parentdir and thisdir files, if they occur
if( strcmp( entry->d_name, "." ) &&
strcmp( entry->d_name, ".." ) ) {
Path *newPath;
if( mPath != NULL ) {
newPath = mPath->append( mName );
}
else {
if( Path::isRoot( mName ) ) {
// use name as a string path
newPath = new Path( mName );
}
else {
char **folderPathArray = new char*[1];
folderPathArray[0] = mName;
// a non-absolute path to this directory's contents
int numSteps = 1;
char absolute = false;
newPath =
new Path( folderPathArray, numSteps,
absolute );
delete [] folderPathArray;
}
}
// safe to pass d_name in directly because it is copied
// internally by the constructor
fileVector->push_back(
new File( newPath,
entry->d_name,
strlen( entry->d_name ) ) );
}
entry = readdir( directory );
}
// now we have a vector full of this directory's files
int vectorSize = fileVector->size();
*outNumFiles = vectorSize;
if( vectorSize == 0 ) {
delete fileVector;
closedir( directory );
delete [] stringName;
return NULL;
}
else {
File **returnFiles = new File *[vectorSize];
for( int i=0; i<vectorSize; i++ ) {
returnFiles[i] = *( fileVector->getElement( i ) );
}
delete fileVector;
closedir( directory );
delete [] stringName;
return returnFiles;
}
}
else {
delete [] stringName;
*outNumFiles = 0;
return NULL;
}
}
inline File **File::getChildFilesRecursive( int inDepthLimit,
int *outNumFiles ) {
// create a vector for results
SimpleVector<File *> *resultVector = new SimpleVector<File *>();
// call the recursive function
getChildFilesRecursive( inDepthLimit, resultVector );
// extract results from vector
File **resultArray = NULL;
int numResults = resultVector->size();
if( numResults > 0 ) {
resultArray = resultVector->getElementArray();
}
delete resultVector;
*outNumFiles = numResults;
return resultArray;
}
inline void File::getChildFilesRecursive(
int inDepthLimit,
SimpleVector<File *> *inResultVector ) {
// get our child files
int numChildren;
File **childFiles = getChildFiles( &numChildren );
if( childFiles != NULL ) {
// for each child, add it to vector and
// recurse into it if it is a directory
for( int i=0; i<numChildren; i++ ) {
File *child = childFiles[i];
// add it to results vector
inResultVector->push_back( child );
if( child->isDirectory() ) {
// skip recursion if we have hit our depth limit
if( inDepthLimit > 0 ) {
// recurse into this subdirectory
child->getChildFilesRecursive( inDepthLimit - 1,
inResultVector );
}
}
}
delete [] childFiles;
}
}
inline File *File::getChildFile( const char *inChildFileName ) {
// make sure we are a directory
if( !isDirectory() ) {
return NULL;
}
// get a path to this directory
Path *newPath;
if( mPath != NULL ) {
newPath = mPath->append( mName );
}
else {
char **folderPathArray = new char*[1];
folderPathArray[0] = mName;
// a non-absolute path to this directory's contents
int numSteps = 1;
char absolute = false;
newPath =
new Path( folderPathArray, numSteps,
absolute );
delete [] folderPathArray;
}
return new File( newPath, inChildFileName );
}
inline File *File::getParentDirectory() {
if( mPath != NULL ) {
char *parentName;
Path *parentPath;
if( strcmp( mName, ".." ) == 0 ) {
// already a parent dir reference
// append one more parent dir reference with parentName below
parentPath = mPath->append( ".." );
parentName = stringDuplicate( ".." );
}
else {
// not a parent dir reference, so we can truncate
parentPath = mPath->truncate();
parentName = mPath->getLastStep();
}
File *parentFile = new File( parentPath, parentName );
delete [] parentName;
return parentFile;
}
else {
if( Path::isRoot( mName ) ) {
// we are already at the root
return new File( NULL, mName );
}
else {
// append parent dir symbol to path
char **parentPathSteps = new char*[1];
parentPathSteps[0] = mName;
Path *parentPath = new Path( parentPathSteps, 1, false );
const char *parentName = "..";
File *parentFile = new File( parentPath, parentName );
delete [] parentPathSteps;
return parentFile;
}
}
}
inline char File::exists() {
struct stat fileInfo;
// get full file name
int length;
char *stringName = getFullFileName( &length );
int statError = stat( stringName, &fileInfo );
delete [] stringName;
if( statError == 0 ) {
return true;
}
else {
// file does not exist
return false;
}
}
inline unsigned long File::getModificationTime() {
struct stat fileInfo;
// get full file name
int length;
char *stringName = getFullFileName( &length );
int statError = stat( stringName, &fileInfo );
delete [] stringName;
if( statError == 0 ) {
return fileInfo.st_mtime;
}
else {
// file does not exist
return 0;
}
}
inline char File::remove() {
char returnVal = false;
if( exists() ) {
char *stringName = getFullFileName();
int error = ::remove( stringName );
if( error == 0 ) {
returnVal = true;
}
delete [] stringName;
}
return returnVal;
}
inline File *File::copy() {
Path *pathCopy = NULL;
if( mPath != NULL ) {
pathCopy = mPath->copy();
}
return new File( pathCopy, mName );
}
inline void File::copy( File *inDestination, long inBlockSize ) {
char *thisFileName = getFullFileName();
char *destinationFileName = inDestination->getFullFileName();
FILE *thisFile = fopen( thisFileName, "rb" );
FILE *destinationFile = fopen( destinationFileName, "wb" );
long length = getLength();
long bytesCopied = 0;
char *buffer = new char[ inBlockSize ];
while( bytesCopied < length ) {
long bytesToCopy = inBlockSize;
// end of file case
if( length - bytesCopied < bytesToCopy ) {
bytesToCopy = length - bytesCopied;
}
fread( buffer, 1, bytesToCopy, thisFile );
fwrite( buffer, 1, bytesToCopy, destinationFile );
bytesCopied += bytesToCopy;
}
fclose( thisFile );
fclose( destinationFile );
delete [] buffer;
delete [] thisFileName;
delete [] destinationFileName;
}
inline char *File::getFileName( int *outLength ) {
char *returnName = stringDuplicate( mName );
if( outLength != NULL ) {
*outLength = mNameLength;
}
return returnName;
}
inline char *File::getFullFileName( int *outLength ) {
int length = mNameLength;
int pathLength = 0;
char *path = NULL;
if( mPath != NULL ) {
path = mPath->getPathString( &pathLength );
length += pathLength;
}
// extra character for '\0' termination
char *returnString = new char[ length + 1 ];
if( path != NULL ) {
memcpy( returnString, path, pathLength );
memcpy( &( returnString[pathLength] ), mName, mNameLength );
delete [] path;
}
else {
// no path, so copy the name directly in
memcpy( returnString, mName, mNameLength );
}
// terminate the string
returnString[ length ] = '\0';
if( outLength != NULL ) {
*outLength = length;
}
return returnString;
}
#include "minorGems/io/file/FileInputStream.h"
#include "minorGems/io/file/FileOutputStream.h"
inline char *File::readFileContents() {
int length;
// text mode!
unsigned char *data = readFileContents( &length, true );
if( data == NULL ) {
return NULL;
}
char *dataString = new char[ length + 1 ];
memcpy( dataString, data, length );
dataString[ length ] = '\0';
delete [] data;
return dataString;
}
inline unsigned char *File::readFileContents( int *outLength,
char inTextMode ) {
if( exists() ) {
int length = getLength();
unsigned char *returnData = new unsigned char[ length ];
if( returnData != NULL ) {
FileInputStream *input = new FileInputStream( this, inTextMode );
int numRead = input->read( returnData, length );
delete input;
// in text mode, read length might not equal binary file length,
// due to line end conversion
if( numRead == length ||
( inTextMode && numRead >= 0 ) ) {
*outLength = numRead;
return returnData;
}
else {
delete [] returnData;
return NULL;
}
}
else {
// failed to allocate this much memory
return NULL;
}
}
else {
return NULL;
}
}
inline char File::writeToFile( const char *inString ) {
return writeToFile( (unsigned char *)inString, strlen( inString ) );
}
inline char File::writeToFile( unsigned char *inData, int inLength ) {
FileOutputStream *output = new FileOutputStream( this );
long numWritten = output->write( inData, inLength );
delete output;
if( inLength == numWritten ) {
return true;
}
else {
return false;
}
}
#include "Directory.h"
inline char File::makeDirectory() {
if( exists() ) {
return false;
}
else {
return Directory::makeDirectory( this );
}
}
#endif