25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Payments   Humor   Music

C++ File Array class manages list of disk files, GNU g++

FileArray is a C++ class for GNU GCC g++. It holds a list of disk file names as a vector of strings. It provides functions for retrieving names from the list in sequential or random order and optionally deleting the file from the list upon retrieval.

It is useful for holding a list of files that were command-line arguments, or that result from drag-and-drop operations, or anywhere else you need to manage a list of files. Many of my programs use it.

This was adapted from an earlier Windows version. Windows and Linux file systems are structured so differently that most of the old version's capabilities are irrelevant here, and this Linux version doesn't do much more than manage a list of strings. It no longer supports file "globbing", but it still has vestigal code to use as a guide in case I have to add globbing later.

This file requires my.h.

filearay.h

/*	filearay.h		1-25-2010	GNU GCC g++
	Copyright (C)1999-2000, 2002, 2007-08, 2010 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

An array for holding a list of files, with handling functions for the list.

Globbing isn't supported in g++ version because it can be done by user using "find". 
If I do have to implement globbing, see http://linux.die.net/man/3/glob 

------
NOTES:
--01-25-2010
  This was adapted for g++ use from a previous version for Microsoft Visual C++, 
  which in turn was adapted from a Borland C++ version, which is why it contains
  vestigal code and comments relating to those. 
  I do not plan any attempt to keep the versions coordinated with each other.
  Further work on this one, if any, would be in the direction of making it
  more in line with Linux conventions for how utility programs should work.
  Without the FilePathParser class, FileArray isn't much more than a 
  vector of strings with a few functions to access them in different ways.
--YOU CAN USE THE LIST OF FILES AS A:
  1. Queue from which filenames are deleted as they're retrieved
  2. Fixed list through which you cycle.
  You should NOT use it both ways in the same program.
--REMEMBER TO SET THE DATA MEMBERS AFTER CONSTRUCTION, IF NECESSARY.
  For an ostrstream (for use by a TEdit), setting the stream to ios::binary does
  not have the same effect as using TEditControlOutput=true.  Apparently,
  binary mode only applies to file output.  So TEditControlOutput is still necessary.
  TEditControlOutput could be eliminated if you could determine within operator << whether the
  ostream had been opened in binary mode, but you can't.  I only found 1 ostream-derived
  class (don't recall which) that even recorded its opening mode, and it is protected.
--the return of empty strings when the list is empty is only for protection.  you should always:
  if(FileNameList.GetItemsInContainer())
	filename = FileNameList.GetNext();

*/
#ifndef __FILEARAY_H
#define __FILEARAY_H

//#include <direct.h>	// doesn't exist in gcc
#include <dirent.h>
#include "my.h"

using namespace std;

#if 0
// 1-25-2010 This class kept only as a guide in case globbing proves necessary later.
//////////////////////////////////////////////////////////////////////////////
// 	A utility class that provides easy parsing of partial or full file names
// 	and disk/directory management.
class FilePathParser
{
public:
	FilePathParser(const char* path = 0);
	FilePathParser(const string& path);

	// functions
	bool Parse(const char* path);	// reuse the parser on a new filename
	bool ChDir();          			// change default directory to the Drive+Dir it now holds

								// useful combinations you can retrieve.  they will reflect
								// any changes you've made to the basic elements
	string FileExt();			// File + Ext
	string DriveDir(); 			// Drive + Dir
	string DriveDirFile(); 		// DriveDir() + File
	string DriveDirFileExt(); 	// DriveDirFile() + Ext
	string Entire();            // DriveDirFileExt()

	// variables
	string Drive;  	// basic elements.  The contents follow the rules of fnsplit().
	string Dir;		// you can change these directly, but be sure to do it right
	string File;
	string Ext;
};
//----------------------------------------------------------------------------
// 							end class FilePathParser
//////////////////////////////////////////////////////////////////////////////
#endif // 0


class FileArray 
{
public:
	FileArray(bool editcontroloutput = false);						// constructor
	~FileArray();                                                	// destructor

	friend ostream& operator << (ostream& os, const FileArray&);	// write
	friend istream& operator >> (istream& is, FileArray&);			// read

	// handy for: if(FileArray) or while(FileArray)
	// but if your FileArray is newed, it could cause ambiguity 
	// between if((FileArray*)FileArray != 0) and if((int)FileArray != 0)
	operator int() const { return(FileNameList.size()); }

	// functions
	int AddFile(const string& s);	// add 1 file OR all matching files if s contains wildcards
	void Flush()
		{ FileNameList.clear(); current = -1; }
	uint GetItemsInContainer() const	// for backwards compatibility with old Borland code
		{ return(FileNameList.size()); }
	uint size() 
		{ return(FileNameList.size()); }
	// Other vector functions could be duplicated here to allow treating this class as 
	// though it were derived from vector<string>.
	
	// FOR SEQUENTIAL USE OF THE FILE NAMES:
	string GetNext(bool DeleteEntry = true);

	// FOR CYCLING THROUGH THE FILE NAMES:
	string Next();
	string Prev();
	string Current();
	string Random();				// selects one at random

	// variables
	vector<string> FileNameList;
	
	// if this is true, operator << uses \r\n instead of endl for line ends, 
	// so the lines display properly in a text edit control.
	bool TEditControlOutput;	

protected:
	int current;							// index into the array, for cycling
};
//----------------------------------------------------------------------------
// 						end class FileArray
//////////////////////////////////////////////////////////////////////////////
#endif			// __FILEARAY_H

filearay.cpp

/*	filearay.cpp		1-25-2010		GNU GCC g++
	Copyright (C)1999-2000, 2002, 2007, 2008, 2010 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/
#include "filearay.h"

using namespace std;

#if 0
//////////////////////////////////////////////////////////////////////////////
// class FilePathParser code
//----------------------------------------------------------------------------
// constructor
FilePathParser::FilePathParser(const char* path)
{
string s;
if(path)
	s = path;
else					// if NOTHING provided, get and use currently logged disk & dir
{
	char temp[_MAX_PATH] = {0};
	if(_getcwd(temp, _MAX_PATH) == NULL)// if error,
		temp[0] = 0;					// make it a null string
	s = temp;
	if(s.length()) 		// must add terminal \ to identify it as a path
		s += "\\";
}
Parse(s.c_str());
}                       	//constructor
//----------------------------------------------------------------------------
// constructor
FilePathParser::FilePathParser(const string& path) { Parse(path.c_str()); }
//----------------------------------------------------------------------------
// the various non-inline functions.
string FilePathParser::FileExt()			{ return File + Ext; }
string FilePathParser::DriveDir() 			{ return Drive + Dir; }
string FilePathParser::DriveDirFile() 		{ return DriveDir() + File; }
string FilePathParser::DriveDirFileExt() 	{ return DriveDirFile() + Ext; }
string FilePathParser::Entire()            	{ return DriveDirFileExt(); }
//----------------------------------------------------------------------------
// call this to reuse the parser on a new filename. (it resets everything).
// if path is ONLY a path, it must end with a "\", or the last directory will be parsed
// as a file name.
// 5-17-07 This fn required SIGNIFICANT revision to convert from Borland to VCPP.
bool FilePathParser::Parse(const char* path)
{
Drive.clear(); Dir.clear(); File.clear(); Ext.clear();

if(!path || !strlen(path))
	return false;

char drive[_MAX_DRIVE] = {0}, dir[_MAX_DIR] = {0}, file[_MAX_FNAME] = {0}, ext[_MAX_EXT] = {0};

_splitpath(path, drive, dir, file, ext);

if(strlen(drive))					// drive was specified
	Drive = drive;					// Drive has the :
else								// drive NOT specified: use current default
{
	drive[0] = (uchar)(_getdrive() - 1 + 'A');	//A=1, B=2, etc.
	drive[1] = 0;
	Drive = string(drive) + ":";
}

if(strlen(dir))						// dir was successfully parsed
	Dir = dir;						// dir has leading and trailing backslash
else                                // else get default dir for the drive we've just determined
{
	int newdrive = toupper(Drive[0]) - 'A' + 1;		// (default=0), A=1, B=2, etc.
	// if specified drive was illegal, an empty Dir is your flag of the error.
	if(_getdcwd(newdrive, dir, _MAX_DIR) == NULL)	// NULL is the error return
		Dir = "\\";									// set it to root as default
	else
	{
		Dir = string(dir).substr(2);	// must strip the leading C: that _getdcwd inserted.
		if(Dir.length() && (Dir[Dir.length() - 1] != '\\'))	// if it's root, it ends with a backslash
			Dir += "\\";									// other than root, it doesn't, so add it.
	}
}

if(strlen(file))
	File = file;
else
	File = "";
		
if(strlen(ext))
	Ext = ext;
else
	Ext = "";
		
return true;
}                    			//Parse
//----------------------------------------------------------------------------
// change the current default dir to the one it currently holds, changing disk if necessary.
bool FilePathParser::ChDir()
{
if(Drive.length() && Dir.length())
{
	if(_chdrive(toupper(Drive[0]) - 'A' + 1) == 0)	// VCPP A=1
	{
		string s(Dir);
		int i = s.length() - 1;
		if(s[i] == '\\')			// remove trailing backslash, if any
			s.erase(i);
		if(chdir(s.c_str()) == 0)
			return true;
	}
}
return false;
}                      			//ChDir
//----------------------------------------------------------------------------
// 						end class FilePathParser
//////////////////////////////////////////////////////////////////////////////
#endif // 0

// class FileArray code
//----------------------------------------------------------------------------
// constructor
FileArray::FileArray(bool editcontroloutput) 
{
current = -1;								// -1 when array is empty so ++ makes it 0
TEditControlOutput = editcontroloutput;		// see operator <<
}                       	// constructor
//----------------------------------------------------------------------------
// destructor
FileArray::~FileArray()
{
FileNameList.clear();
}                       	// destructor
//----------------------------------------------------------------------------
// output the current list of files, one to a line
// for output to a string, remember to add the << ends after returning.
// A TEdit control is good for editing the list (see wanimate.cpp),
// and requires \r\n instead of endl.
ostream& operator << (ostream& os, const FileArray& a)
{
int N = a.GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
{
	os << a.FileNameList[i];           		// output the string
	if(a.TEditControlOutput)	// choose a terminator
		os << "\r\n";
	else
		os << endl;
}
return(os);
}
//----------------------------------------------------------------------------
// add to file list from a stream
istream& operator >> (istream& is, FileArray& a)
{
string s;
while(is >> s)
	a.AddFile(s);
return(is);
}
//----------------------------------------------------------------------------
// add 1 file name, OR
// if the given name contains wildcards, find FILES that match the pattern, and add them all.
// could test for legality, but complicated, and a bad name will simply fail on open.
// returns number of filenames added.
// 5-17-07 This fn was essentially REWRITTEN to convert from Borland to VCPP.
// 1-25-2010 In g++ all it does is add the string into the vector.
int FileArray::AddFile(const string& filename)
{
int count = 0;

// builds a fully qualified name from scratch using either provided values or defaults

#if 0
FilePathParser f(filename);
if(!f.Dir.length())       	// if an explicit or default Disk+Dir couldn't be determined,
	return(0);				// quit to avoid lots of problems
#endif // 0

// You don't HAVE to test for wildcards and avoid searching if there aren't any:
// An unambiguous filename would just be found once by findfirst.
// But the original purpose of this function was to create a list of STRINGS.
// findfirst is for dealing with EXISTING FILES.  If user specified a SINGLE file,
// and it doesn't exist, findfirst will fail, and the name won't go into the list.
// That's the effect you'd want, but there's no provision HERE for warning the user that it happened.
// It is better to unconditionally add the STRING now and let the open fail later,
// where user can be informed that the named file doesn't exist.
// But if there ARE wildcards in the user's string, you MUST expand them now to whatever files DO exist,
// which the user expects you to do.

//string s = f.Entire();
string s = filename;

#if 0
// 1-25-2010 Kept only as a reference in case globbing proves necessary later.
string path = f.DriveDir();
if((s.find("?") != string::npos) || (s.find("*") != string::npos))// if there are wildcards...
{
	String^ t = gcnew String(f.FileExt().c_str());
	String^ p = gcnew String(path.c_str());
	string u;

	// Being new to .NET, this was difficult to figure out, but it sure is nice.
	array<String^>^ fa = System::IO::Directory::GetFiles(p, t);
	System::Collections::IEnumerator^ e = fa->GetEnumerator();
	while(e->MoveNext() != false)
	{
		SystemStringToBasicString(e->Current->ToString(), u);
		FileNameList.push_back(u);
	}
	delete t;
	delete p;
	delete e;	// don't know if this is required
	delete fa;	// or this
	
	// This method would have been a more direct descendant of the Borland methods,
	// but it crashes with an access violation. _findnext() appears to have one or more bugs.
	// keep for reference because it SHOULD have worked.
	// 9/14/07: Maybe try newing fileblock instead of creating it on the stack.
	//struct _finddata_t fileblock;
	//intptr_t searchhandle = _findfirst(s.c_str(), &fileblock);
	//while(searchhandle != -1L)
	//{
	//	// retrieved name never has path, so must prepend to each file.
	//	// if the caller wants upper or lower case upon use, it can do it itself.
	//	FileNameList.push_back(path + fileblock.name);
	//	count++;
	//	searchhandle = _findnext(searchhandle, &fileblock);
	//}
	//_findclose(searchhandle);
	
}
else
#endif //0

{
	// in this case, you don't know if the file exists or not, but add it to the list anyway.
	FileNameList.push_back(s);   	// just 1 file
	count++;
}
current = -1;			// so that either Current() or Next() will restart at 0
return(count);
}                       	// AddFile
//----------------------------------------------------------------------------
// for SEQUENTIAL use of the filenames:
//----------------------------------------------------------------------------
// return name of the next file (at index 0), and optionally (usually) delete it.
//
// this could also be bool GetNext(string& destination, bool delete), that
// fills the string and returns true if there was anything to get; otherwise
// leaves the string unchanged and returns false.  Unsure which is more useful.
//
string FileArray::GetNext(bool DeleteEntry)
{
string s;
if(GetItemsInContainer())
{
	s = FileNameList[0];
	if(DeleteEntry)
		FileNameList.erase(FileNameList.begin());
}
current = -1;			// EITHER Current() or Next() will start at 0
return(s);
}                		// GetNext
//----------------------------------------------------------------------------
// for CYCLING use of the filenames:
//----------------------------------------------------------------------------
// advances to the next filename, then returns it.
string FileArray::Next()
{
string s;
int N = GetItemsInContainer();
if(N)
{
	if(++current >= N)
		current = 0;
	s = FileNameList[current];
}
return(s);
}                   	// Next
//----------------------------------------------------------------------------
// goes back to previous filename, then returns it.
// maybe useful for cycling backwards or back and forth
string FileArray::Prev()
{
string s;
int N = GetItemsInContainer();
if(N)
{
	if(--current < 0)
		current = N - 1;
	s = FileNameList[current];
}
return(s);
}                    	// Prev
//----------------------------------------------------------------------------
// returns name of the filename currently pointed at.
string FileArray::Current()
{
string s;
int N = GetItemsInContainer();
if(N)
{
	current = range(current, 0, N - 1);
	s = FileNameList[current];
}
return(s);
}                    	// Current
//----------------------------------------------------------------------------
// returns a filename randomly selected from the list. (used sometimes for .MAP color files)
string FileArray::Random()
{
string s;
int N = GetItemsInContainer();
if(N)
	s = FileNameList[random(N)];
return(s);
}                    	// Current
//----------------------------------------------------------------------------
// 						end class FileArray
//////////////////////////////////////////////////////////////////////////////

 

Valid HTML 4.01 Transitional Valid CSS
Yahoo! Search
Search the web Search this site
View content labeling at ICRA.