//----------------------------------------------------------------------------------------------------
//
//	LibInspector
//
//	Inherits From:		WMInspector
//
//	Declared In:		LibInspector.h
//
//	Disclaimer
//
//		You may freely copy, distribute and reuse this software and its
//		associated documentation. I disclaim any warranty of any kind, 
//		expressed or implied, as to its fitness for any particular use.
//
//----------------------------------------------------------------------------------------------------
#import "LibInspector.h"


#define AR_TV					"ar tv"
#define AR_X						"ar x"
#define CD						"cd"
#define MKDIR					"mkdir"
#define EXTRACTION_FOLDER		"/tmp/LibExtractions"
#define OUT_OF_MEMORY		"Out of Memory!"
#define MKDIR_ERROR			"Unable to create temporary folder!"
#define PROCESS_ERROR			"Unable to open process stream!"
#define PARSE_ERROR			"Unable to recognize library information!"
#define MAX_AR_FILENAME_LEN 	16

static FILE*	responseStream;


@implementation LibInspector

static id 	_SELF = nil;

//----------------------------------------------------------------------------------------------------
//  Private Methods
//----------------------------------------------------------------------------------------------------
- _alert: (STR) errorMessage
{
	NXRunAlertPanel (NULL, "%s", NULL, NULL, NULL, errorMessage);
	return self;
}

	
- _getTableOfContents
{
	//  Get library's table of contents by sending AR_TV command.
	//  Response stream will be read to populate browser (in brower:fillMatrix:inColumn).
	
	STR		command;
	char 	currentPath [MAXPATHLEN+1];
	
	[self selectionPathsInto:currentPath separator:'\0'];
	
	if (! (command = malloc (strlen (AR_TV) + strlen (currentPath) + 2)))
		{
		[self _alert: OUT_OF_MEMORY];
		return nil;
		}
		
	sprintf (command, "%s %s", AR_TV, currentPath);

	if (! (responseStream = popen (command, "r")))
		{
		if (command) free (command);
		[self _alert: PROCESS_ERROR];
		return nil;
		}		

	if (command) free (command);
	return self;
}


- (BOOL)_createLibExtractionFolder
{
	//  Make a temporary directory for extractions  (EXTRACTION_FOLDER).  
	//  Return YES if exists/successful, NO if error.
	
	STR			command;
	struct stat	statBuffer;
	
	if (stat (EXTRACTION_FOLDER, &statBuffer) == 0) return YES;

	if (! (command = malloc (strlen (MKDIR) + strlen (EXTRACTION_FOLDER) + 2)))
		{
		[self _alert: OUT_OF_MEMORY];
		return NO;
		}
		
	sprintf (command, "%s %s", MKDIR, EXTRACTION_FOLDER);
	
	system (command);
	
	if (stat (EXTRACTION_FOLDER, &statBuffer) != 0)
		{
		if (command) free (command);
		[self _alert: MKDIR_ERROR];
		return NO;
		}

	if (command) free (command);
	return YES;
}
	
	
- _openLibExtractionsFileViewer
{
	//  Search the application's windowList for the LibExtractions FileViewer.  
	//  Open the window if it exists, open a new one if not.  This will prevent 
	//  multiple FileViewers from being displayed.
	
	int	iterator;
	id	windowList = [NXApp windowList];
	id	libExtractionsFileViewer = nil;
	
	for (iterator = 0; iterator < [windowList count]; iterator++)
		if (NXOrderStrings ([[windowList objectAt:iterator] title], "LibExtractions", 
			YES, strlen ("LibExtractions"), NULL) == 0)
			libExtractionsFileViewer = [windowList objectAt:iterator];
			
	if (libExtractionsFileViewer)
		if ([libExtractionsFileViewer style] == NX_MINIWINDOWSTYLE)
			[libExtractionsFileViewer deminiaturize:nil]; 
		else 
			[libExtractionsFileViewer makeKeyAndOrderFront:nil];
	else
		[[Application workspace] selectFile: EXTRACTION_FOLDER inFileViewerRootedAt: 
			EXTRACTION_FOLDER];

	return self;
}


- _extractSelectedFiles
{
	//  Extract selected files into a temporary folder and open that folder.
	
	id		selectionList = [browser getSelectedCells: nil];
	int		iterator;
	STR		command;
	STR		filename;
	char 	currentPath [MAXPATHLEN+1];
	
	if ([selectionList count])  if (! [self _createLibExtractionFolder]) return nil;

	[self selectionPathsInto: currentPath separator:'\0'];
	
	if (! (command = malloc (strlen (CD) + strlen (EXTRACTION_FOLDER) + 
		strlen (AR_X) + MAXPATHLEN + MAX_AR_FILENAME_LEN + 10)))
		{
		[self _alert: OUT_OF_MEMORY];
		return nil;
		}
		
	for (iterator = 0; iterator < [selectionList count]; iterator++)
		{
		filename = NXCopyStringBuffer ([[selectionList objectAt: iterator] stringValue]);
		if (! (filename = strtok (filename, " \t\n")))
			{
			if (command) free (command);
			return nil;
			}
		sprintf (command, "%s %s; %s %s %s", CD, EXTRACTION_FOLDER, AR_X, 
			currentPath, filename);
		system (command);
		if (filename) free (filename);
		}
		
	[self _openLibExtractionsFileViewer];

	if (command) free (command);
	return self;
}


- (STR)_format: (STR)responseLine
{
	//  Parse and format the response line.  Where a token gets memcpy'd within the
	//  formatString indicates its position within the displayed string.  Have to exclude
	//  some information. Also, throw out names beginning with '__.'.  These are archive 
	//  table names, header stuff, etc., (or possibly very weird file names).  Returns a 
	//  formatted string that the caller must free, or some error condition.
	//  (example response line)  "rw-r--r--  0/1   4572 Jul 29 23:53 1992 NXImage.o".

	STR		currentToken;
	char		formatString[51];

	memset (formatString, ' ', 50);
	formatString [50] = '\0';

	if (! strtok (responseLine, " \t")) return PARSE_ERROR;
	if (! strtok (NULL, "  \t")) return PARSE_ERROR;
	if (! (currentToken = strtok (NULL, " \t\n"))) return PARSE_ERROR;
	memcpy (formatString + 18, currentToken, strlen (currentToken));
	if (! (currentToken = strtok (NULL, " \t\n"))) return PARSE_ERROR;
	memcpy (formatString + 26, currentToken, strlen (currentToken));
	if (! (currentToken = strtok (NULL, " \t\n"))) return PARSE_ERROR;
	memcpy (formatString + 30, currentToken, strlen (currentToken));
	if (! (currentToken = strtok (NULL, " \t\n"))) return PARSE_ERROR;
	if (! (currentToken = strtok (NULL, " \t\n"))) return PARSE_ERROR;
	memcpy (formatString + 33, currentToken, strlen (currentToken));
	if (! (currentToken = strtok (NULL, " \t\n"))) return PARSE_ERROR;
	memcpy (formatString, currentToken, strlen (currentToken));

	if (NXOrderStrings (currentToken, "__.", NO, 3, NULL) == 0) return NULL;
	
	return NXCopyStringBuffer (formatString);
}


//----------------------------------------------------------------------------------------------------
//  WMInspector Methods
//----------------------------------------------------------------------------------------------------
+ new
{
	//  Required/called by WM.  Only allow one instance...
	
    	char 	path[MAXPATHLEN+1];
    	id 		bundle;

    	if (_SELF) return _SELF;

        _SELF = self = [super new];

    	if ( ! (bundle = [NXBundle bundleForClass:[LibInspector class]] ) ) return nil;          
	if ( ! [bundle getPath:path forResource:"LibInspector" ofType:"nib"] ) return nil;
    	[NXApp loadNibFile:path owner:self  withNames:NO fromZone:[self zone]];
 
 	return _SELF;
}


- revert: sender
{
	//  Called by WM when file selection is type we inspect.	
	[self _getTableOfContents];
	[[[self okButton] setTitle: "Extract"] setIconPosition: NX_TITLEONLY];
	[browser loadColumnZero];
	return [super revert: sender];
}


- ok:sender
{
	//  User pressed 'Extract' button...
	
	[self _extractSelectedFiles];
	return [super ok:sender];
}


//----------------------------------------------------------------------------------------------------
//  Action Methods
//----------------------------------------------------------------------------------------------------
- browserClicked: sender
{
	//  A file or files have been selected in the browser.  Enable 'Extract'
	//  button if not an empty selection.
		
	if ([browser selectedCell]) 
		[[self okButton] setEnabled: YES];
	else
		[[self okButton] setEnabled: NO];

	return self;
}


//----------------------------------------------------------------------------------------------------
//  Browser Delegate Method
//----------------------------------------------------------------------------------------------------
- (int) browser: aBrowser fillMatrix: aMatrix inColumn: (int)aColumn
{
	//  Populate Table Of Contents browser with formatted command response.
	//  Set the font for the browser text here (fixed font for even column alignment).
	//  Minimal error checking within read loop - NULL indicates an entry we want
	//  to skip (like an archive header), PARSE_ERROR indicates a response that
	//  could not be recognized, all else are considered successful.
	
	id		font = [Font newFont: "Courier" size: 10];
	int		count = 0;
	char		nextLine [81];
	STR		formattedLine;
	
	while (fgets (nextLine, 999, responseStream))
		{
		if ( (formattedLine = [self _format: nextLine]) == NULL) continue;
		if ( NXOrderStrings (formattedLine, PARSE_ERROR, YES, -1, NULL) == 0)
			{
			[self _alert: PARSE_ERROR];
			break;
			}
		[aMatrix renewRows: (count+1) cols: 1];
		[[aMatrix cellAt: count :0] setFont: font];
		[[aMatrix cellAt: count :0] setStringValue: formattedLine];
		[[[aMatrix cellAt: count :0] setLoaded: YES] setLeaf: YES];
		if (formattedLine) free (formattedLine);
		count++;
		}

	if (font) [font free];
	pclose (responseStream);
	return count;
}


@end