//=============================================================================
//
//		Copyright (C) 1995-1997 by Paul S. McCarthy and Eric Sunshine.
//				Written by Paul S. McCarthy and Eric Sunshine.
//							All Rights Reserved.
//
//		This notice may not be removed from this source code.
//
//		This object is included in the MiscKit by permission from the authors
//		and its use is governed by the MiscKit license, found in the file
//		"License.rtf" in the MiscKit distribution.	Please refer to that file
//		for a list of all applicable permissions and restrictions.
//
//=============================================================================
//-----------------------------------------------------------------------------
// DirArray.m
//
//		An extensible array of directory entries.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: DirArray.m,v 1.2 97/06/10 04:53:13 sunshine Exp $
// $Log:		DirArray.m,v $
// Revision 1.2	 97/06/10  04:53:13	 sunshine
// v15: Upgraded naming.  Fixed bug: Wasn't taking sticky-bit into account
// when computing canToggleLock.
// 
// Revision 1.1	 97/02/05  08:22:38	 sunshine
// v13: Synchronized with ScrollDir (v29).	Added unscaledImage.  Added
// isLocked & canToggleLock.  Added manipulation methods.  Supports file
// renaming.
//-----------------------------------------------------------------------------
#import "DirArray.h"
#import <appkit/Application.h>
#import <appkit/NXImage.h>
#import <appkit/workspaceRequest.h>
#import <assert.h>
#import <errno.h>
#import <libc.h>
#import <unistd.h>

#ifdef _POSIX_SOURCE
# define GRP_TYPE gid_t
# define MAX_GRPS NGROUPS_MAX
#else
# define GRP_TYPE int
# define MAX_GRPS NGROUPS
#endif

@implementation DirArray

//-----------------------------------------------------------------------------
// safeStrdup
//-----------------------------------------------------------------------------
- (char*)safeStrdup:(char const*)s
	{
	char* t = 0;
	if (s != 0)
		t = NXCopyStringBufferFromZone( s, [self zone] );
	return t;
	}


//-----------------------------------------------------------------------------
// safeFree
//-----------------------------------------------------------------------------
- (void)safeFree:(void*)p
	{
	if (p != 0)
		NXZoneFree( [self zone], p );
	}


//-----------------------------------------------------------------------------
// dirWritable:
//-----------------------------------------------------------------------------
- (BOOL)dirWritable:(char const*)path
	{
	BOOL rc = NO;
	struct stat st;
	if (stat( path, &st ) == 0)
		{
		unsigned int mode = st.st_mode;
		if ((mode & 0002) != 0 ||								// "other"
		   ((mode & 0200) != 0 && st.st_uid == geteuid()))		// "owner"
			rc = YES;
		else if ((mode & 0020) != 0)							// "group"
			{
			gid_t const gid = getegid();
			if (st.st_gid == gid)
				rc = YES;
			else
				{
				GRP_TYPE groups[ MAX_GRPS ];
				int n = getgroups( MAX_GRPS, groups );
				while (n-- > 0)
					if (gid == groups[n])
						{ rc = YES; break; }
				}
			}
		}
	return rc;
	}


//-----------------------------------------------------------------------------
// dirSticky:
//-----------------------------------------------------------------------------
- (BOOL)dirSticky:(char const*)path
	{
	struct stat st;
	if (stat( path, &st ) == 0)
		return ((st.st_mode & 01000) != 0);
	return NO;
	}


//-----------------------------------------------------------------------------
// dirEntryCons:name:path:isDotDot:
//-----------------------------------------------------------------------------
- (void)dirEntryCons:(DirEntry*)de name:(char const*)n path:(char const*)path
	isDotDot:(BOOL)dotdot
	{
	int rc;
	memset( de, 0, sizeof(*de) );

	de->dir = self;
	de->name = [self safeStrdup:n];

	if ((rc = lstat( path, &(de->status) )) == 0)
		{
		unsigned int mode = de->status.st_mode;
		if ((mode & S_IFMT) == S_IFLNK) // soft-link?
			{
			char symbuf[ FILENAME_MAX + 1 ];
			int cc = readlink( path, symbuf, FILENAME_MAX );
			if (cc >= 0)
				{
				struct stat st;
				symbuf[ cc ] = '\0';
				de->softLink = [self safeStrdup:symbuf];
				if (stat( path, &st ) == 0)
					mode = st.st_mode;	// mode of file linked to
				}
			}
		de->isDirectory = ((mode & S_IFMT) == S_IFDIR);
		}

	de->canToggleLock = (!dotdot && [self dirWritable] &&
						(!sticky || de->status.st_uid == effectiveUid));
	de->isLocked = !de->canToggleLock;
	}


//-----------------------------------------------------------------------------
// freeImage
//-----------------------------------------------------------------------------
- (void)freeImage:(NXImage*)p
	{
	if (p != 0)
		[p free];
	}


//-----------------------------------------------------------------------------
// dirEntryDestroy:
//-----------------------------------------------------------------------------
- (void)dirEntryDestroy:(DirEntry*)de
	{
	[self freeImage:de->scaledImage];
	[self freeImage:de->unscaledImage];
	[self safeFree:de->name];
	[self safeFree:de->softLink];
	}


//-----------------------------------------------------------------------------
// entryAt:
//-----------------------------------------------------------------------------
- (DirEntry const*)entryAt:(int)n
	{
	assert( 0 <=n && n < numEntries );
	return (entries + n);
	}


//-----------------------------------------------------------------------------
// isDir:
//-----------------------------------------------------------------------------
- (BOOL)isDir:(DirEntry const*)de
	{
	return de->isDirectory;
	}


//-----------------------------------------------------------------------------
// isDirAt:
//-----------------------------------------------------------------------------
- (BOOL)isDirAt:(int)n
	{
	return [self isDir:[self entryAt:n]];
	}


//-----------------------------------------------------------------------------
// canToggleLock:
//-----------------------------------------------------------------------------
- (BOOL)canToggleLock:(DirEntry const*)de
	{
	return de->canToggleLock;
	}


//-----------------------------------------------------------------------------
// canToggleLockAt:
//-----------------------------------------------------------------------------
- (BOOL)canToggleLockAt:(int)n
	{
	return [self canToggleLock:[self entryAt:n]];
	}


//-----------------------------------------------------------------------------
// isLocked:
//-----------------------------------------------------------------------------
- (BOOL)isLocked:(DirEntry const*)de
	{
	return de->isLocked;
	}


//-----------------------------------------------------------------------------
// isLockedAt:
//-----------------------------------------------------------------------------
- (BOOL)isLockedAt:(int)n
	{
	return [self isLocked:[self entryAt:n]];
	}


//-----------------------------------------------------------------------------
// setLocked:at:
//-----------------------------------------------------------------------------
- (void)setLocked:(BOOL)flag at:(int)n
	{
	assert( entries[n].canToggleLock );
	entries[n].isLocked = flag;
	}


//-----------------------------------------------------------------------------
// setName:at:
//-----------------------------------------------------------------------------
- (void)setName:(char const*)s at:(int)n
	{
	[self safeFree:entries[n].name];
	entries[n].name = [self safeStrdup:s];
	}


//-----------------------------------------------------------------------------
// loadImage:
//-----------------------------------------------------------------------------
- (void)loadImage:(DirEntry*)de
	{
	char buff[ FILENAME_MAX + 1 ];
	char const* const dir_part = de->dir->name;
	int const dirlen = strlen( dir_part );
	memcpy( buff, dir_part, dirlen );
	strcpy( buff + dirlen, de->name );
	de->unscaledImage = [[Application workspace] getIconForFile:buff];
	de->scaledImage = [de->unscaledImage copy];
	[de->scaledImage setScalable:YES];
	}


//-----------------------------------------------------------------------------
// getImage:scaled:
//-----------------------------------------------------------------------------
- (NXImage*)getImage:(DirEntry const*)de scaled:(BOOL)wantsScaled
	{
	if (de->unscaledImage == 0)
		[self loadImage:(DirEntry*)de];
	return (wantsScaled ? de->scaledImage : de->unscaledImage);
	}


//-----------------------------------------------------------------------------
// getImageAt:scaled:
//-----------------------------------------------------------------------------
- (NXImage*)getImageAt:(int)n scaled:(BOOL)flag
	{
	return [self getImage:[self entryAt:n] scaled:flag];
	}


//-----------------------------------------------------------------------------
// init
//-----------------------------------------------------------------------------
- (id)init
	{
	[super init];
	totalBytes = 0;
	numEntries = 0;
	maxEntries = 0;
	entries = 0;
	writable = NO;
	sticky = NO;
	effectiveUid = geteuid();
	return self;
	}


//-----------------------------------------------------------------------------
// empty
//-----------------------------------------------------------------------------
- (void)empty
	{
	DirEntry* p;
	for (p = entries + numEntries; p != entries; )
		[self dirEntryDestroy:--p];
	numEntries = 0;
	totalBytes = 0;
	}


//-----------------------------------------------------------------------------
// free
//-----------------------------------------------------------------------------
- (id)free
	{
	[self empty];
	[self safeFree:name];
	[self safeFree:entries];
	return [super free];
	}


//-----------------------------------------------------------------------------
// count
//-----------------------------------------------------------------------------
- (int)count
	{
	return numEntries;
	}


//-----------------------------------------------------------------------------
// totalBytes
//-----------------------------------------------------------------------------
- (size_t)totalBytes
	{
	return totalBytes;
	}


//-----------------------------------------------------------------------------
// dirWritable
//-----------------------------------------------------------------------------
- (BOOL)dirWritable
	{
	return writable;
	}


//-----------------------------------------------------------------------------
// dirSticky
//-----------------------------------------------------------------------------
- (BOOL)dirSticky
	{
	return sticky;
	}


//-----------------------------------------------------------------------------
// remove:
//-----------------------------------------------------------------------------
- (void)remove:(int)n
	{
	if (0 <= n && n < numEntries)
		{
		DirEntry* p = entries + n;
		totalBytes -= p->status.st_size;
		numEntries--;
		[self dirEntryDestroy:p];
		if (n < numEntries)
			memmove( p, p + 1, (numEntries - n) * sizeof(*p) );
		}
	}


//-----------------------------------------------------------------------------
// expand
//-----------------------------------------------------------------------------
- (void)expand
	{
	DirEntry* p = entries;
	int N = maxEntries;
	if (N == 0)
		{
		N = 16;
		p = (DirEntry*) NXZoneMalloc( [self zone], N * sizeof(*p) );
		}
	else
		{
		N += N;
		p = (DirEntry*) NXZoneRealloc( [self zone], p, N * sizeof(*p) );
		}
	maxEntries = N;
	entries = p;
	}


//-----------------------------------------------------------------------------
// addName:path:isDotDot:
//-----------------------------------------------------------------------------
- (void)addName:(char const*)n path:(char const*)fullpath isDotDot:(BOOL)dotdot
	{
	DirEntry* p;
	if (numEntries >= maxEntries)
		[self expand];
	p = entries + numEntries++;
	[self dirEntryCons:p name:n path:fullpath isDotDot:dotdot];
	totalBytes += p->status.st_size;
	}


//-----------------------------------------------------------------------------
// loadPath:showHidden:
//-----------------------------------------------------------------------------
- (int)loadPath:(char const*)path showHidden:(BOOL)showHidden
	{
	int rc = 0;
	int dirlen;
	DIR* dirp;
	char namebuff[ FILENAME_MAX + 1 ];

	dirlen = strlen( path );
	strcpy( namebuff, path );
	if (dirlen == 0 || namebuff[ dirlen - 1 ] != '/')
		{
		namebuff[ dirlen++ ] = '/';
		namebuff[ dirlen ] = '\0';
		}

	[self empty];
	[self safeFree:name];
	name = [self safeStrdup:namebuff];
	writable = [self dirWritable:path];
	sticky = [self dirSticky:path];

	if ((dirp = opendir( path )) != 0)
		{
		struct direct const* dp;
		while ((dp = readdir(dirp)) != 0)
			{			// Do not include the "." (self) entry.
			int const len = dp->d_namlen;
			char const* const nam = dp->d_name;
			BOOL const dot_name = (nam[0] == '.');
			BOOL const dot_dot_name = (dot_name && len == 2 && nam[1] == '.');
			if ((len > 1 || !dot_name) &&					// exclude "."
				(showHidden || !dot_name || dot_dot_name))	// include ".."
				{
				memcpy( namebuff + dirlen, nam, len + 1 );
				[self addName:nam path:namebuff isDotDot:dot_dot_name];
				}
			}
		closedir( dirp );
		}
	else
		rc = errno;

	return rc;
	}

@end
