/*	XGDirectory.cpp
 *
 *		This encapsulates a directory specifier, including all of the
 *	operations that can be done on a directory.
 */

/**********************************************************************

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>
#include <XFile.h>
#include <XError.h>
#include <XStringUtil.h>
#include <string.h>

#if OPT_XWIN
#include <sys/stat.h>
#endif

/************************************************************************/
/*																		*/
/*	Globals																*/
/*																		*/
/************************************************************************/

static XGDirectory GAppDirectory;

/************************************************************************/
/*																		*/
/*	Construction/Destruction											*/
/*																		*/
/************************************************************************/

/*$	XGDirectory::XGDirectory
 *
 *		Create one of these. Until it's initialized, the directory is
 *	invalid
 */

XGDirectory::XGDirectory()
{
	fInit = false;
	
#if OPT_WINOS
	fList = INVALID_HANDLE_VALUE;
#endif

#if OPT_XWIN
	fList = NULL;
#endif
}


#if OPT_MACOS
XGDirectory::XGDirectory( const XGFileSpecifier &fs )
{
	FSSpec fsp = fs.GetFileName();
	CInfoPBRec rec;
	rec.dirInfo.ioCompletion = NULL;
	rec.dirInfo.ioNamePtr = fsp.name;
	rec.dirInfo.ioVRefNum = fsp.vRefNum;
	rec.dirInfo.ioDrDirID = fsp.parID;
	rec.dirInfo.ioFDirIndex = 0;
	OSErr err = PBGetCatInfoSync(&rec);
	
	fInit = true;
	fVolRefNum = rec.dirInfo.ioVRefNum;
	fDirID = rec.dirInfo.ioDrDirID;
}
#endif
#if OPT_WINOS
// we should just be able to do this.
XGDirectory::XGDirectory( const XGFileSpecifier &fs )
{
		fInit = true;
		
#if OPT_WINOS
		fList = INVALID_HANDLE_VALUE;
#endif

		//fs.GetDirectory(*this);
		fPath = fs.GetFileName();
}
#endif

/*$	XGDirectory::~XGDirectory
 *
 *		Delete me
 */

XGDirectory::~XGDirectory()
{
#if OPT_WINOS
	if (fList != INVALID_HANDLE_VALUE) FindClose(fList);
#endif

#if OPT_XWIN
	if (fList) closedir(fList);
#endif
}

/************************************************************************/
/*																		*/
/*	Internal initialization												*/
/*																		*/
/************************************************************************/

/*$	XGDirectory::InitDirectoryInfo
 *
 *		This is an internal routine which is called by YAAF to note
 *	the current working directory of the application.
 */

void XGDirectory::InitDirectoryInfo()
{
	GAppDirectory.SetDirectoryInfo();
}

/*$	XGDirectory::SetDirectoryInfo
 *
 *		This is an internal routine which is called by YAAF to note
 *	the current working directory of the application. This is used
 *	to find a common directory for plugins. On Unix, this looks for
 *	files in
 *
 *			/usr/local/config/<appname>
 */

void XGDirectory::SetDirectoryInfo()
{
#if OPT_MACOS
	extern long _GMacOSVersion;
	
	/*
	 *	As this is one of the first things that is called during
	 *	application startup, the current working directory is set
	 *	to my application's location.
	 */
	
	if (_GMacOSVersion < 0x0700) {
		short wdRefNum;

		/* System 6 and earlier: get my cur working directory */
		GetVol(NULL,&wdRefNum);
		GetWDInfo(wdRefNum,&fVolRefNum,&fDirID,NULL);
	} else {
		ProcessSerialNumber psn;
		ProcessInfoRec pinfo;
		FSSpec fspec;
		
		/* System 7 and later: use process manager */
		GetCurrentProcess(&psn);
		
		memset((void *)&pinfo,0,sizeof(pinfo));
		pinfo.processInfoLength = sizeof(pinfo);
		pinfo.processAppSpec = &fspec;
		GetProcessInformation(&psn,&pinfo);
		
		fVolRefNum = fspec.vRefNum;
		fDirID = fspec.parID;
	}
	
	fInit = true;
#endif

#if OPT_WINOS
	char buffer[280];
	char *x,*y;
	
	::GetModuleFileName(NULL,buffer,sizeof(buffer));
	y = buffer;
	for (x = buffer; *x; x++) {
		if ((*x == ':') || (*x == '\\')) y = x+1;
	}
	*y = 0;
	
	fPath = buffer;
	fInit = true;
#endif

#if OPT_XWIN
	char buffer[256];
	char *c,*d;
	extern char **_GArgV;
	
	d = _GArgV[0];
	for (c = d; *c; c++) {
		if (*c == '/') d = c+1;
	}
	strcpy(buffer,"/usr/local/config/");
	strcat(buffer,d);
	fPath = buffer;
	fInit = true;
#endif
}

/************************************************************************/
/*																		*/
/*	Set directory														*/
/*																		*/
/************************************************************************/

/*$	XGDirectory::SetAppLocation
 *
 *		Set this directory object to the application's directory. This
 *	is useful if you want to look for "plugins" and other extensions
 *	that are stored in the same directory as the application executable.
 */

void XGDirectory::SetAppLocation()
{
	*this = GAppDirectory;
	Assert(fInit);
}

/*$	XGDirectory::SetCurLocation
 *
 *		Set the current location; this is set to the current working
 *	directory.
 */

void XGDirectory::SetCurLocation()
{
#if OPT_MACOS
	short wdRefNum;
	
	GetVol(NULL,&wdRefNum);
	GetWDInfo(wdRefNum,&fVolRefNum,&fDirID,NULL);
	fInit = true;
#endif

#if OPT_WINOS
	char buffer[512];
	
	Assert(0 != ::GetCurrentDirectory(sizeof(buffer),buffer));
	fPath = buffer;
	fInit = true;
#endif

#if OPT_XWIN
	char buffer[512];
	
	Assert(0 != getcwd(buffer,sizeof(buffer)));
	fPath = buffer;
	fInit = true;
#endif
}


bool XGDirectory::operator == ( const XGDirectory &other ) const
{
	if (!fInit || !other.fInit) return false;
#if OPT_MACOS
	return fVolRefNum == other.fVolRefNum && fDirID == other.fDirID;
#endif

#if OPT_WINOS || OPT_XWIN
	char sep;
#if OPT_WINOS
	sep = '\\';
#endif
#if OPT_XWIN
	sep = '/';
#endif
	const char *a = fPath.Get();
	int alen = fPath.GetLength();
	const char *b = other.fPath.Get();
	int blen = other.fPath.GetLength();
	
	if (alen > blen) {
		if (alen != blen + 1 || a[alen-1] != sep) return false;
		return !memcmp( a, b, blen );
	} else if (alen < blen) {
		if (blen != alen + 1 || b[blen-1] != sep) return false;
		return !memcmp( a, b, alen );
	} else {
		return !memcmp( a, b, alen );
	}
#endif
}


/*$	XGDirectory::UpdateCWD
 *
 *		Update the current working directory to reflect my contents
 */

void XGDirectory::UpdateCWD()
{
	Assert(fInit);

#if OPT_MACOS
	short wd;
	OSErr err;
	
	err = OpenWD(fVolRefNum,fDirID,'ERIK',&wd);
	Assert(err == noErr);
	SetVol(0,wd);
#endif

#if OPT_WINOS
	Assert(::SetCurrentDirectory(fPath.Get()));
#endif

#if OPT_XWIN
	Assert(0 == ::chdir(fPath.Get()));
#endif
}

/************************************************************************/
/*																		*/
/*	Directory management												*/
/*																		*/
/************************************************************************/

/*$	XGDirectory::ChangeDirectory
 *
 *		This changes the directory in this either down one level to
 *	the named directory, or up one level if NULL is passed for name.
 *	This verifies that the directory exists first.
 *
 *		This returns false if the directory doesn't exist.
 */

bool XGDirectory::ChangeDirectory(char *name)
{
	Assert(fInit);

#if OPT_MACOS
	CInfoPBRec rec;
	unsigned char buf[256];
	OSErr err;

	if (name == NULL) {
		/*
		 *	Find my parent's dirID
		 */
		
		if (fDirID == 2) return false;		/* 2 is the root dirID */
		cvtc2p(buf,name);
		rec.dirInfo.ioCompletion = NULL;
		rec.dirInfo.ioNamePtr = NULL;
		rec.dirInfo.ioVRefNum = fVolRefNum;
		rec.dirInfo.ioFDirIndex = -1;
		rec.dirInfo.ioDrDirID = fDirID;
		err = PBGetCatInfo(&rec,false);
		if (err) return false;
		
		fDirID = rec.dirInfo.ioDrParID;		/* Parent directory ID */
	} else {
		/*
		 *	Find the dirID of the provided name
		 */
		
		cvtc2p(buf,name);
		rec.dirInfo.ioCompletion = NULL;
		rec.dirInfo.ioNamePtr = buf;
		rec.dirInfo.ioVRefNum = fVolRefNum;
		rec.dirInfo.ioFDirIndex = 0;
		rec.dirInfo.ioDrDirID = fDirID;
		err = PBGetCatInfo(&rec,false);
		if (err) return false;
		
		fDirID = rec.dirInfo.ioDrDirID;
	}
	return true;
#endif

#if OPT_WINOS
	char buffer[256];
	char bufnew[256];
	BOOL flag;
	
	/*
	 *	Go to my current directory
	 */
	Assert(::GetCurrentDirectory(sizeof(buffer),buffer));
	if (!::SetCurrentDirectory(fPath.Get())) return false;
		
	if (name == NULL) {
		flag = ::SetCurrentDirectory("..");
	} else {
		flag = ::SetCurrentDirectory(name);
	}
	::GetCurrentDirectory(sizeof(bufnew),bufnew);
	
	/*
	 *	Revert to old directory
	 */
	
	::SetCurrentDirectory(buffer);
	
	/*
	 *	Update
	 */
	
	if (flag) {
		fPath = bufnew;
		return true;
	} else {
		return false;
	}
#endif

#if OPT_XWIN
	char buffer[256];
	char bufnew[256];
	int flag;
	
	/*
	 *	Go to my current directory
	 */
	
	Assert(::getcwd(buffer,sizeof(buffer)));
	if (::chdir(fPath.Get())) return false;
	if (name == NULL) {
		flag = ::chdir("..");
	} else {
		flag = ::chdir(name);
	}
	::getcwd(bufnew,sizeof(bufnew));
	
	/*
	 *	Revert to old directory
	 */
	
	::chdir(buffer);
	
	/*
	 *	Update directory path information
	 */
	
	if (flag) return false;
	fPath = bufnew;
	return true;
#endif
}

/*$	XGDirectory::CreateDirectory
 *
 *		This creates a directory at the location specified. This
 *	returns false if it couldn't create the directory, including
 *	if the directory already exists. (That means you should test
 *	to see if it exists first by checking ChangeDirectory() first.)
 */

bool XGDirectory::CreateDirectory(char *name)
{
	Assert(fInit);

#if OPT_MACOS
	unsigned char buf[256];
	HParamBlockRec hparam;
	OSErr err;

	cvtc2p(buf,name);
	hparam.fileParam.ioCompletion = NULL;
	hparam.fileParam.ioVRefNum = fVolRefNum;
	hparam.fileParam.ioNamePtr = buf;
	hparam.fileParam.ioDirID = fDirID;
	
	err = PBDirCreate(&hparam,NULL);
	if (err) return false;
	fDirID = hparam.fileParam.ioDirID;
	return true;
#endif

#if OPT_WINOS
	char buffer[256];
	char bufnew[256];
	BOOL flag;
	
	/*
	 *	Go to my current directory
	 */
	
	Assert(::GetCurrentDirectory(sizeof(buffer),buffer));
	if (!::SetCurrentDirectory(fPath.Get())) return false;

	flag = ::CreateDirectory(name, NULL);
	::GetCurrentDirectory(sizeof(bufnew),bufnew);
	
	/*
	 *	Revert to old directory
	 */
	
	::SetCurrentDirectory(buffer);
	
	/*
	 *	Update directory path information
	 */
	
	if (!flag) return false;
	fPath = bufnew;
	return true;
#endif

#if OPT_XWIN
	char buffer[256];
	char bufnew[256];
	int flag;
	
	/*
	 *	Go to my current directory
	 */
	
	Assert(::getcwd(buffer,sizeof(buffer)));
	if (::chdir(fPath.Get())) return false;
	
	flag = ::mkdir(name,0666);
	::getcwd(bufnew,sizeof(bufnew));
	
	/*
	 *	Revert to old directory
	 */
	
	::chdir(buffer);
	
	/*
	 *	Update directory path information
	 */
	
	if (flag) return false;
	fPath = bufnew;
	return true;
#endif
}


void XGDirectory::GetDirectoryName( char *out )
{
	if (!fInit) {
		strcpy( out, "uninitialized XGDirectory" );
	}
	
#if OPT_MACOS	
	CInfoPBRec rec;
	Str255 name;
	rec.dirInfo.ioCompletion = NULL;
	rec.dirInfo.ioNamePtr = name;
	rec.dirInfo.ioVRefNum = fVolRefNum;
	rec.dirInfo.ioFDirIndex = -1;
	rec.dirInfo.ioDrDirID = fDirID;
	OSErr err = PBGetCatInfo(&rec,false);
	if (err) {
		strcpy( out, "directory name error" );
	} else {
		cvtp2c( out, name );
	}
#endif

#if OPT_WINOS || OPT_XWIN
	char sep;
#if OPT_WINOS
	sep = '\\';
#endif
#if OPT_XWIN
	sep = '/';
#endif
	const char *path = fPath.Get();
	const char *scan = path + strlen( path ) - 1;
	for (;;) {
		while (scan > path && *scan != sep) {
			--scan;
		}
		if (scan <= path) {
			strcpy( out, path );
			break;
		} else {
			if (!scan[1]) {
				--scan;
				continue;
			} else {
				strcpy( out, scan+1 );
				break;
			}
		}
	}
	
	char *term = strchr( out, sep );
	if (term) {
		*term = 0;
	}
#endif
}


/************************************************************************/
/*																		*/
/*	Scan Directory														*/
/*																		*/
/************************************************************************/

/*$	XGDirectory::GetFirstFile
 *
 *		Get the first file in the list of files inside of this
 *	directory. This returns all of the files and directories found,
 *	setting the 'dirflag' boolean to true if this is a directory.
 *	(The name of the directory can be extracted from the file
 *	specifier). This ignores the '.' and '..' directories on Unix
 *	and Windows.
 *
 *		This returns 'true' if a file is found, and 'false' if there
 *	were no files in this directory.
 */

bool XGDirectory::GetFirstFile(XGFileSpecifier &out, bool *dirflag)
{
	Assert(fInit);

#if OPT_MACOS
	fIndex = 0;
	return GetNextFile(out,dirflag);
#endif

#if OPT_WINOS
	WIN32_FIND_DATA data;
	char buffer[256];

	Assert(0 != ::GetCurrentDirectory(sizeof(buffer),buffer));
	if (!::SetCurrentDirectory(fPath.Get())) return false;

	if (fList != INVALID_HANDLE_VALUE) FindClose(fList);

	fList = FindFirstFile("*.*",&data);
	::SetCurrentDirectory(buffer);
	
	if (!strcmp(data.cFileName,".") || !strcmp(data.cFileName,"..")) {
		return GetNextFile(out,dirflag);
	}
	
	if (fList != INVALID_HANDLE_VALUE) {
		if(data.cFileName == 0 || data.cFileName[0] == 0)
			return false;
		out.SetFile(*this,data.cFileName);
		if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			*dirflag = true;
		} else {
			*dirflag = false;
		}
		return true;
	} else {
		return false;
	}
#endif

#if OPT_XWIN
	if (fList) closedir(fList);
	
	fList = opendir(fPath.Get());
	if (fList == NULL) return false;
	
	return GetNextFile(out,dirflag);
#endif
}

/*$	XGDirectory::GetNextFile
 *
 *		Get the next file in the list of files. This should only be
 *	called after GetFirstFile() returns. This gets the next file
 *	in the list of files until there are no more files to retrieve.
 *
 *		This returns 'true' if a file was found, and 'false' if
 *	there are no more files in this directory.
 */

bool XGDirectory::GetNextFile(XGFileSpecifier &out, bool *dirflag)
{
	Assert(fInit);

#if OPT_MACOS
	CInfoPBRec rec;
	unsigned char buffer[64];
	OSErr err;
	FSSpec fspec;
	
	buffer[0] = 0;
	
	rec.hFileInfo.ioCompletion = NULL;
	rec.hFileInfo.ioNamePtr = buffer;
	rec.hFileInfo.ioVRefNum = fVolRefNum;
	rec.hFileInfo.ioFDirIndex = ++fIndex;
	rec.hFileInfo.ioDirID = fDirID;
	
	err = PBGetCatInfo(&rec,false);
	if (err) return false;
	if (rec.hFileInfo.ioFlAttrib & 0x10) *dirflag = true;
	else *dirflag = false;
	
	pstrcpy(fspec.name,buffer);
	fspec.vRefNum = fVolRefNum;
	fspec.parID = fDirID;
	out = fspec;
	return true;
#endif

#if OPT_WINOS
	WIN32_FIND_DATA data;
	char buffer[256];
	BOOL flag;

	if (fList == INVALID_HANDLE_VALUE) return false;

	Assert(0 != ::GetCurrentDirectory(sizeof(buffer),buffer));
	Assert(::SetCurrentDirectory(fPath.Get()));

	for (;;) {
		flag = FindNextFile(fList,&data);
		if (!flag) break;
		if (strcmp(data.cFileName,".") && strcmp(data.cFileName,"..")) break;
	}
	::SetCurrentDirectory(buffer);
	
	if (flag) {
		if(data.cFileName == 0 || data.cFileName[0] == 0)
			return false;
		out.SetFile(*this,data.cFileName);
		if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			*dirflag = true;
		} else {
			*dirflag = false;
		}
		return true;
	} else {
		::FindClose(fList);
		fList = INVALID_HANDLE_VALUE;
		return false;
	}
#endif


#if OPT_XWIN
	struct dirent *data;
	int flag;
	struct stat statbuf;
	
	if (fList == NULL) return false;
	
	for (;;) {
		data = readdir(fList);
		if (data == NULL) {
			closedir(fList);
			fList = NULL;
			return false;
		}
		if (strcmp(data->d_name,".") && strcmp(data->d_name,"..")) {
			break;
		}
	}
		
	out.SetFile(*this,data->d_name);
	flag = stat(out.fFileName.Get(),&statbuf);
	if (flag) {
		closedir(fList);
		fList = NULL;
		return false;
	}
	*dirflag = (S_ISDIR(statbuf.st_mode)) ? true : false;
	return true;
#endif
}
