
/*
	Copyright 1993  Jeremy Slade.

	You are free to use all or any parts of the Locus project
	however you wish, just give credit where credit is due.
	The author (Jeremy Slade) shall not be held responsible
	for any damages that result out of use or misuse of any
	part of this project.

*/

/*
	Project: Locus

	File: DynamicItemSpec.m

	Description: See DynamicItemSpec.h

	Original Author: Jeremy Slade

	Revision History:
		Created
			V.101	JGS Tue Feb  2 19:11:44 GMT-0700 1993

*/


#import "DynamicItemSpec.h"

#import "DynamicItems.h"
#import "Globals.h"
#import "Group.h"
#import "ItemCell.h"

#import <regex.h>
#import <sys/dir.h>
#import <sys/types.h>


@implementation DynamicItemSpec


// -------------------------------------------------------------------------
//   Creating, initializing
// -------------------------------------------------------------------------


+ initialize
{
	[self setVersion:DynamicItemSpec_VERSION];
	return ( self );
}



- initExpr:(const char *)aString
{
	[super init];
	
	[self setExpression:aString];
	enabled = YES;
	
	return ( self );
}



- free
{
	if ( expression ) NX_FREE ( expression );
	if ( regexpr ) NX_FREE ( regexpr );
	return ( [super free] );
}



// -------------------------------------------------------------------------
//   Expression
// -------------------------------------------------------------------------


- setExpression:(const char *)aString
{
	if ( expression ) NX_FREE ( expression );
	if ( regexpr ) NX_FREE ( regexpr ); regexpr = NULL;
	
	expression = aString ? NXCopyStringBuffer ( aString ) : NULL;
	
	return ( self );
}



- (const char *)expression
{
	return ( expression );
}



- (const char *)regexpression
/*
	Translate the shell-style globbing expression into a regular expression so we can use it with recmp.
*/
{
	char buf[MAXPATHLEN+1] = "";
	char *in, *out;
	
	if ( regexpr )
		return ( regexpr );
		
	// Translate the expression.  Following is a brief summary of the
	// translation:
	// 	'.' is replaced with '\.'
	//	'?' is replaced with '.'
	//	'*' is replaced with '.*'
	//	'[a-z]' is left the same
	//	'[!a-z]' is replaced with '[^a-z]'
	
	// Only include part of expression after last '/'
	if ( in = rindex ( expression, '/' ) ) in++;
		else in = expression;
		
	out = buf;
	*out++ = '^';
	while ( *in ) {
		switch ( *in ) {
			case 0x2e:	// Period -- '.'
				*out++ = '\\'; *out++ = '.';
				in++;
				break;
			case 0x3f:	// Question Mark -- '?'
				*out++ = '.';
				in++;
				break;
			case 0x2a:	// Asterisk -- '*'
				*out++ = '.'; *out++ = '*';
				in++;
				break;
			case 0x5b:	// Left bracket -- '['
				*out++ = '['; in++; // Output left bracket, move to next char
				if ( *in == '!' ) *out++ = '^'; // Test for next char being '!'
					else *out++ = *in;
				in++;
				while ( *in != ']' && *in != '\0' ) {
					// Output until right bracket
					*out++ = *in++;
				}
				*out++ = ']';	// Output the right bracket
				in++;
				break;
			default:	*out++ = *in++;
		}
	}
	*out++ = '$';
	*out = '\0'; // Make sure output is terminated

	regexpr = NXCopyStringBuffer ( buf );
	return ( regexpr );
}



- setEnabled:(BOOL)flag
{
	enabled = flag;
	return ( self );
}



- (BOOL)enabled
{
	return ( enabled );
}



// -------------------------------------------------------------------------
//   Scanning for Matching Items
// -------------------------------------------------------------------------


const char *match;  // Used by matchItems(), set before each search

- addMatchingItemsToGroup:group
/*
	Scan the directories for all items matching our expression and add them to the group.
*/
{
	struct direct **namelist;
	int i, count;
	int matchItems ( struct direct *dp );
	char path[MAXPATHLEN+1], *s;
	id newItem;

	if ( !expression || !enabled )
		return ( self );

	match = [self regexpression]; // assign our expression to the global
	
	// If the expression specifies a full path, search that path
	if ( expression[0] == '/' ) {
		if ( s = rindex ( expression, '/' ) ) *s = '\0';
		strcpy ( path, expression );
		if ( s ) *s = '/';
	} else

	// expression has '/' in it, must be a relative path
	if ( index ( expression, '/' ) ) {
		if ( [group defaultPath] ) {
			if ( s = rindex ( expression, '/' ) ) *s = '\0';
			sprintf ( path, "%s/%s", [group defaultPath], expression );
			if ( s ) *s = '/';
		} else
			return ( self ); // No default path specified,
			                 // can't use relative expression
	} else

	// Just do search in the group's defaultPath
	if ( [group defaultPath] ) {
		strcpy ( path, [group defaultPath] );
	} else
		return ( self ); // Don't have a path to search

	// Perform the search using scandir()...
	count = scandir ( path, &namelist, matchItems, NULL );
	
	// Add list of matched files to the group...
	// set s to end of path string so we can just append the matching
	// items to create the full path.
	s = path + strlen(path) -1;
	if ( *s != '/' ) {
		strcat ( path, "/" );
		s++;
	}
	s++;
	for ( i=0; i<count; i++ ) {
		strcpy ( s, namelist[i]->d_name );
		newItem = [[ItemCell alloc] initPath:path];
		[[newItem setDynamic:YES] setGroup:group];
		[group addDynamicItem:newItem];
		free ( namelist[i] );
	}
	if ( count != -1 ) free ( namelist );
	
	return ( self );
}



int matchItems ( struct direct *dp )
/*
	This is the matching function used by scandir().  Match the specified entry against the current regular expression defined by match.
*/
{
	if ( !strcmp ( dp->d_name, "." ) || !strcmp ( dp->d_name, ".." ) )
		return ( 0 );
	else
		return ( (!recmp ( (char *)match, dp->d_name )) ? 1 : 0 );
}



// -------------------------------------------------------------------------
//    Archiving
// -------------------------------------------------------------------------


- awake
{
	[super awake];
	
	// Ignore archived regular expression -- generate it again
	if ( regexpr ) {
		NX_FREE ( regexpr );
		regexpr = NULL;
	}
	
	return ( self );
}



- read:(NXTypedStream *)stream
{
	int versionNumber;
	
	[super read:stream];
	
	versionNumber = NXTypedStreamClassVersion ( stream, [[self class] name] );
	
	if ( versionNumber <= DynamicItemSpec_VERSION ) { // thru current version
		NXReadTypes ( stream, "**s",
			&expression,
			&regexpr,
			&enabled );
	}
	
	else { // Unrecongized version!
		[self errMsg:"DynamicItemSpec: unrecognized version %i of archived object!\n", versionNumber];
	}
	
	return ( self );
}



- write:(NXTypedStream *)stream
{
	[super write:stream];
	
	NXWriteTypes ( stream, "**s",
		&expression,
		&regexpr,
		&enabled );
		
	return ( self );
}



@end

