/******************************************************************************
 JUNIXDirEntry.cc

	Class representing an item in a directory.

	This class was not designed to be a base class.

	BASE CLASS = none

	Copyright © 1996 by Glenn W. Bach. All rights reserved.

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

#include <JUNIXDirEntry.h>
#include <jDirUtil.h>
#include <jUNIXUtil.h>
#include <JString.h>
#include <JRegex.h>
#include <unistd.h>
#include <fstream.h>
#include <jMissingProto.h>
#include <jAssert.h>

/******************************************************************************
 Constructor

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

JUNIXDirEntry::JUNIXDirEntry
	(
	const JCharacter* fullName
	)
{
	JUNIXDirEntryX(fullName);
}

JUNIXDirEntry::JUNIXDirEntry
	(
	const JCharacter* pathName,
	const JCharacter* fileName
	)
{
	const JString fullName = JCombinePathAndName(pathName, fileName);
	JUNIXDirEntryX(fullName);
}

// private

void
JUNIXDirEntry::JUNIXDirEntryX
	(
	const JCharacter* origFullName
	)
{
	// parse path and name

	JString fullName = origFullName;
	if (fullName.GetFirstCharacter() != '/')
		{
		fullName.Prepend( JGetCurrentDirectory() );
		}
	JStripTrailingDirSeparator(&fullName);

	if (JIsRootDirectory(fullName))
		{
		itsPath = itsName = "/";
		}
	else
		{
		const JBoolean foundDirSep =
			JSplitPathAndName(fullName, &itsPath, &itsName);
		assert( foundDirSep );
		}

	// set rest of instance variables

	ForceUpdate();
}

/******************************************************************************
 Constructors for search targets (private)

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

JUNIXDirEntry::JUNIXDirEntry
	(
	const JCharacter*	fileName,
	int					x
	)
	:
	itsName(fileName)
{
}

/******************************************************************************
 Copy constructor

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

JUNIXDirEntry::JUNIXDirEntry
	(
	const JUNIXDirEntry& source
	)
	:
	itsPath(source.itsPath),
	itsName(source.itsName),
	itsLinkName(source.itsLinkName)
{
	itsType        = source.itsType;
	itsSize        = source.itsSize;
	itsMode        = source.itsMode;
	itsModTime     = source.itsModTime;
	itsStatusTime  = source.itsStatusTime;
	itsSModTime    = source.itsSModTime;
	itsSStatusTime = source.itsSStatusTime;
	itsUserID      = source.itsUserID;
	itsGroupID     = source.itsGroupID;

	itsIsReadableFlag   = source.itsIsReadableFlag;
	itsIsWritableFlag   = source.itsIsWritableFlag;
	itsIsExecutableFlag = source.itsIsExecutableFlag;
}

/*****************************************************************************
 Destructor

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

JUNIXDirEntry::~JUNIXDirEntry()
{
}

/******************************************************************************
 Assignment operator

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

const JUNIXDirEntry&
JUNIXDirEntry::operator=
	(
	const JUNIXDirEntry& source
	)
{
	if (this == &source)
		{
		return *this;
		}

	itsPath        = source.itsPath;
	itsName        = source.itsName;
	itsLinkName    = source.itsLinkName;

	itsType        = source.itsType;
	itsSize        = source.itsSize;
	itsMode        = source.itsMode;
	itsModTime     = source.itsModTime;
	itsStatusTime  = source.itsStatusTime;
	itsSModTime    = source.itsSModTime;
	itsSStatusTime = source.itsSStatusTime;
	itsUserID      = source.itsUserID;
	itsGroupID     = source.itsGroupID;

	itsIsReadableFlag   = source.itsIsReadableFlag;
	itsIsWritableFlag   = source.itsIsWritableFlag;
	itsIsExecutableFlag = source.itsIsExecutableFlag;

	return *this;
}

/******************************************************************************
 FollowLink

	Returns a new object that describes what we point to.  If we are not
	a link, we return a copy of ourselves.

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

JUNIXDirEntry
JUNIXDirEntry::FollowLink()
	const
{
	if (!itsLinkName.IsEmpty())
		{
		return JUNIXDirEntry(itsPath, itsLinkName);
		}
	else
		{
		return *this;
		}
}

/******************************************************************************
 GetUserName

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

JString
JUNIXDirEntry::GetUserName()
	const
{
	return JGetUserName(itsUserID);
}

/******************************************************************************
 GetGroupName

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

JString
JUNIXDirEntry::GetGroupName()
	const
{
	return JGetGroupName(itsGroupID);
}

/*****************************************************************************
 SetMode

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

JError
JUNIXDirEntry::SetMode
	(
	const mode_t mode
	)
{
	const JString fullName = GetFullName();
	return JSetPermissions(fullName, mode);
}

JError
JUNIXDirEntry::SetMode
	(
	const ModeBit	bit,
	const JBoolean	allow
	)
{
	const JString fullName = GetFullName();

	mode_t mode;
	JError err = JGetPermissions(fullName, &mode);
	if (!err.OK())
		{
		return err;
		}

	if (allow)
		{
		mode |= (1 << bit);
		}
	else
		{
		mode &= ~(1 << bit);
		}

	return JSetPermissions(fullName, mode);
}

/*****************************************************************************
 GetModeString

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

JString
JUNIXDirEntry::GetModeString()
	const
{
	return JGetPermissionsString(itsMode);
}

/******************************************************************************
 NeedsUpdate

	Returns kTrue if the entry needs to be updated.

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

JBoolean
JUNIXDirEntry::NeedsUpdate()
{
	const JString fullName = GetFullName();
	struct stat linfo, info;
	if (lstat(fullName, &linfo) == 0 && stat(fullName, &info) == 0)
		{
		return JI2B(itsModTime     != (time_t) linfo.st_mtime ||
					itsStatusTime  != (time_t) linfo.st_ctime ||
					itsSModTime    != (time_t)  info.st_mtime  ||
					itsSStatusTime != (time_t)  info.st_ctime);
		}
	else
		{
		return JNegate(itsType == kDoesNotExist);
		}
}

/******************************************************************************
 Update

	If necessary, updates the entry.  Returns kTrue if the entry needed to
	be updated.

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

JBoolean
JUNIXDirEntry::Update()
{
	if (NeedsUpdate())
		{
		ForceUpdate();
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 ForceUpdate

	Updates the entry, regardless of whether or not it needs it.

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

void
JUNIXDirEntry::ForceUpdate()
{
	const JSize kBufferSize = 1024;

	// clear everything

	itsType = kDoesNotExist;

	itsLinkName.Clear();

	itsSize        = 0;
	itsMode        = 0;
	itsModTime     = 0;
	itsStatusTime  = 0;
	itsSModTime    = 0;
	itsSStatusTime = 0;
	itsUserID      = 0;
	itsGroupID     = 0;

	itsIsReadableFlag = itsIsWritableFlag = itsIsExecutableFlag = kFalse;

	// get info from system

	const JString fullName = GetFullName();

	struct stat lstbuf;
	if (lstat(fullName, &lstbuf) != 0)
		{
		return;
		}

	struct stat stbuf;
	const int statErr = stat(fullName, &stbuf);

	// simple information

	itsSize        = lstbuf.st_size;	
	itsMode        = lstbuf.st_mode;
	itsModTime     = lstbuf.st_mtime;
	itsStatusTime  = lstbuf.st_ctime;
	itsSModTime    =  stbuf.st_mtime;
	itsSStatusTime =  stbuf.st_ctime;
	itsUserID      = lstbuf.st_uid;
	itsGroupID     = lstbuf.st_gid;

	// file type

	const mode_t ltype = lstbuf.st_mode;
	const mode_t ftype = stbuf.st_mode;

	if (S_ISLNK(ltype))
		{			
		if (statErr == -1)
			{
			itsType = kBrokenLink;
			}
		else if (S_ISREG(ftype))
			{
			itsType = kFileLink;
			}
		else if (S_ISDIR(ftype))
			{
			itsType = kDirLink;
			}
		else
			{
			itsType = kUnknownLink;
			}

		char buf[ kBufferSize ];
		const long linkNameSize = readlink(fullName, buf, kBufferSize-1);
		if (linkNameSize != -1)
			{
			buf [ linkNameSize ] = '\0';
			itsLinkName = buf;
			}
		}
	else if (S_ISREG(ftype))
		{
		itsType = kFile;
		}
	else if (S_ISDIR(ftype))
		{
		itsType = kDir;
		}
	else
		{
		itsType = kUnknown;
		}

	// permissions

	if (getuid() == 0)
		{
		itsIsReadableFlag   = kTrue;
		itsIsWritableFlag   = kTrue;
		itsIsExecutableFlag = JConvertToBoolean( (stbuf.st_mode & S_IXUSR) != 0 );
		}
	else
		{
		itsIsReadableFlag   = JConvertToBoolean( access(fullName, R_OK) == 0 );
		itsIsWritableFlag   = JConvertToBoolean( access(fullName, W_OK) == 0 );
		itsIsExecutableFlag = JConvertToBoolean( access(fullName, X_OK) == 0 );
		}
}

/******************************************************************************
 MatchesContentFilter

	This returns kTrue for any file that matches the regex.

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

JBoolean
JUNIXDirEntry::MatchesContentFilter
	(
	const JRegex&	regex,
	const JSize		kBlockSize
	)
	const
{
	if (IsFile())
		{
		const JString fullName = GetFullName();
		const int fd = open(fullName, O_RDONLY);
		if (fd == -1)
			{
			return kFalse;
			}

		JCharacter* data = new JCharacter [ kBlockSize ];
		const ssize_t count = read(fd, data, kBlockSize);
		close(fd);
		if (count < 0)
			{
			return kFalse;
			}

		const JBoolean match = regex.MatchWithin(data, JIndexRange(1, count));

		delete [] data;
		return match;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 Comparison (static)

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

JOrderedSetT::CompareResult
JUNIXDirEntry::CompareNames
	(
	JUNIXDirEntry * const & e1,
	JUNIXDirEntry * const & e2
	)
{
	const int r = JStringCompare(e1->itsName, e2->itsName, kFalse);

	if (r > 0)
		{
		return JOrderedSetT::kFirstGreaterSecond;
		}
	else if (r < 0)
		{
		return JOrderedSetT::kFirstLessSecond;
		}
	else
		{
		return JOrderedSetT::kFirstEqualSecond;
		}
}

JOrderedSetT::CompareResult
JUNIXDirEntry::CompareSizes
	(
	JUNIXDirEntry * const & e1,
	JUNIXDirEntry * const & e2
	)
{
	if (e1->itsSize > e2->itsSize)
		{
		return JOrderedSetT::kFirstGreaterSecond;
		}
	else if (e1->itsSize < e2->itsSize)
		{
		return JOrderedSetT::kFirstLessSecond;
		}
	else
		{
		return JOrderedSetT::kFirstEqualSecond;
		}
}

JOrderedSetT::CompareResult
JUNIXDirEntry::CompareModTimes
	(
	JUNIXDirEntry * const & e1,
	JUNIXDirEntry * const & e2
	)
{
	if (e1->itsModTime > e2->itsModTime)
		{
		return JOrderedSetT::kFirstGreaterSecond;
		}
	else if (e1->itsModTime < e2->itsModTime)
		{
		return JOrderedSetT::kFirstLessSecond;
		}
	else
		{
		return JOrderedSetT::kFirstEqualSecond;
		}
}
