/***************************************************************
*
*			Lib.c
*
*		Disk Library Documentation System
*			Main Program
*
*	By Wilson Snyder		(c) January 1988
*
****************************************************************
*
*	NOTICE:
*
*	This program was written as a service to the amiga
*	users.  This program may be used by all parties, including
*	sale (see below) as long as the author's name and address
*	remains intact in the program and documentation.
*	Distribution of this program by parties selling it
*	as part of a commercial for-profit package of over $10
*	must contact the address below before such a package is sold.
*
*	I make no warranties over this program, performance,
*	etc.  And any costs incurred by this program, or its
*	actions are placed on the user.
*
*	PLEASE FORWARD INPROVEMENTS MADE TO THIS PROGRAM TO:
*
*	Wilson Snyder, 15 Davis Parkway, South Burlington, VT 05403
*
****************************************************************
*
*	AddName and base for Process taken from the TSIZE
*	utility written by the excellent work of the
*	Software Distillery.
*
****************************************************************/

#include "libraries/dosextens.h"
#include "ctype.h"
#include "stdio.h"

#define VERSION	"AMIGA-LIB.  Version 1.0.2.  By Wilson Snyder"
#define DESC "$$DES$$"

#define PERSUB	8	/* Characters to indent per subdirectory */
#define INFOIND 8	/* Indent for description and filesize */
#define RM	75	/* Right margin */
#define LM	0	/* Left margin */
#define TM	1	/* Top margin */
#define HM	2	/* Header margin (lines after header) */
#define LINES	60	/* Lines per page */

BPTR	Lock();		/* Define functions... */
BOOL	Examine();
BOOL	ExNext();

char	path [256];		/* Current search path */
char	header [80];		/* Header for top line */
BOOL	prtsize = FALSE;	/* Print the path size information */
BOOL	prtheader = FALSE;	/* Print the header and page breaks */

struct Sortem {		/* Linked list for insertion sort of directory names */
	char	FileName[108];	/* Directory name */
	struct Sortem *Next;	/* Pointer to next */
	};

/***************************************************************
*			ADDNAME
*
*	Add filename to the end of the current path.
*
*	Routine taken from from TSIZE utility by the Software Distillery.
***************************************************************/

int	AddName(name, len)
	register char *name;
	int len;
	{
	register char *p;	/* current position in path */
	register int i = 0;	/* added length counter */
	char ch;		/* last character of current path */

	p = path + len;

	/* add a slash between path and name if legal */
	if (len != 0 && (ch = path[len-1]) != '/' && ch != ':') {
		*p++ = '/';
		i++;
		}

	/* copy name to end of path (including null terminator) */
	while (*p++ = *name++) i++;

	/* return new length of path */
	return (len + i);
	}


/***************************************************************
*			FILECMP
*	Compare two filenames for sort routine (case insensitive.)
***************************************************************/

int	filecmp(a,b)
	char *a,*b;
	{
	for (;toupper(*a)==toupper(*b);a++,b++)
		if (*a=='\0') return (0);
	return (toupper(*a)-toupper(*b));
	}


/***************************************************************
*			INDENT
*	Indent a number of spaces.  Outputted to stdout.
***************************************************************/

void	Indent(num)
	int num;
	{
	while (num--)	putchar (' ');
	}

/***************************************************************
*			HEADER
*	Print header every so many lines.
***************************************************************/

void	Header(inc)
	int inc;
	{
	static int linecount;		/* Number of lines down */
	static int page;		/* Current page number */
	int t;				/* Temp */

	if (inc)
		linecount += inc;
	else	{
		/* 0 increment resets information */
		linecount = 0;
		page = 0;
		}

	if (linecount>LINES && prtheader) {
		/* Time to print the header */
		putchar ('\f');
		page++;
		for (t=TM;t;t--)	putchar ('\n');
		printf (header,page);
		for (t=HM;t;t--)	putchar ('\n');
		linecount = TM + HM + 1;
		}
	}


/***************************************************************
*			ROOT
*	Print information for the page header
***************************************************************/

BOOL	Root()
	{
	struct FileInfoBlock *info;	/* pointer to file info */
	BPTR lock;			/* pointer to file lock */
	int t,loc;			/* temp stuff */

	/* allocate an info block (must be longword alligned) */
	if (!(info = (struct FileInfoBlock *)
		AllocMem(sizeof(struct FileInfoBlock), 0))) {
			printf ("Out of memory.\n");
			return (TRUE);
			}

	/* try to obtain lock on current file */
	if (!(lock = Lock(path, ACCESS_READ))) {
		printf ("Invalid file or directory, '%s'.\n", path);
		FreeMem(info, sizeof(struct FileInfoBlock));
		return (TRUE);
		}

	/* get information on file or directory associated with lock */
	if (!Examine(lock, info)) {
		printf ("Error examining locked file or directory.\n");
		UnLock(lock);
		FreeMem(info, sizeof(struct FileInfoBlock));
		return (TRUE);
		}

	/* Make sure user specified a directory, not a file */
	if (info->fib_DirEntryType <= 0) {
		printf ("Must specify a directory, not a file.\n");
		UnLock(lock);
		FreeMem(info, sizeof(struct FileInfoBlock));
		return (TRUE);
		}

	/* Build Header line (will be used with printf) */
	loc = 0;
	for (t=LM;t;t--)	header[loc++] = ' ';
	strcpy (&header[loc], "Page %-4d");
	loc += 9;
	for (t=RM-loc-strlen(info->fib_FileName);t;t--)	header[loc++] = ' ';
	strcpy (&header[loc],info->fib_FileName);
	loc += strlen(info->fib_FileName);
	header[loc++] = '\n';
	header[loc] = '\0';

	/* Initalize header printer, then force header printing */
	Header (0);
	Header (LINES+2);

	UnLock(lock);
	FreeMem(info, sizeof(struct FileInfoBlock));
	return (FALSE);
	}


/***************************************************************
*			BOTTOM
*	Display size information at bott
om of directory.
***************************************************************/

void	Bottom (depth,size,found)
	int depth;
	long size;
	BOOL found;
	{
	if (prtsize) {
		Indent (depth*PERSUB+INFOIND+LM);
		printf ("Total size %ld bytes.\n", size);
		if (found) printf ("\n");
		Header (2);
		}
	}

/***************************************************************
*			DISPLAY
*	Display information from the DESC file in the directory.
***************************************************************/

BOOL	Display (depth,dirname)
	int depth;
	char *dirname;
	{
	FILE *desc;		/* DESCription file info */
	BOOL found;		/* Found a description file */
	int lineloc;		/* Location on the output line */
	int wordlen;		/* Length of word in the buffer */
	char wordstore[RM+10];	/* Storage for current word */
	BOOL Cont;		/* & encountered, ignore return */
	BOOL Long;		/* Long line/word just printed */
	char ch;		/* Character from file */
	int t,lm;		/* temp */

	/* Print directory name */
	Header (2);
	Indent (depth*PERSUB+LM);
	printf ("%s\n", dirname);

	/* Add DESC filename to path, attempt to open the file */
	t = strlen(path);
	AddName (DESC,t);
	found = ((desc=fopen(path,"r"))!=0);

	/* If found, justify the file on the way out */
	if (found) {
		lm = depth*PERSUB+INFOIND+LM;
		lineloc = lm;
		Indent (lm);
		wordlen = 0;
		Cont = FALSE;
		Long = FALSE;
		/* Process one character at a time */
		while ((ch=getc(desc))!=EOF) {
			/* Ignore return if proceded by a & */
			if ((ch=='\n') && Cont) {
				Cont = FALSE;
				continue;
				}
			if (Cont) {	/* & not proceded by a return */
				wordstore[wordlen++] = '&';
				Cont = FALSE;
				}
			/* Dump out the current word */
			if (ch==' ' || ch=='\n') {
				wordstore[wordlen] = '\0';
				printf ("%s ",wordstore);
				lineloc += wordlen + 1;
				wordlen = 0;
				if (Long || ch=='\n') {
					putchar ('\n');
					Header(1);
					Indent (lm);
					lineloc = lm;
					Long = FALSE;
					}
				continue;
				}
			/* Force return if line goes over right margin */
			if (((wordlen+lineloc)>RM) || (wordlen>(RM-lm))) {
				if (wordlen>(RM-lm)) {
					wordstore[wordlen] = '\0';
					printf ("%s",wordstore);
					wordlen = 0;
					Long = TRUE;
					}
				putchar ('\n');
				Header(1);
				Indent (lm);
				lineloc = lm;
				}
			/* Check for continuation character */
			if (ch=='&')	Cont = TRUE;
			else	wordstore[wordlen++] = ch;
			}
		fclose (desc);
		/* Dump out any remaining word in the buffer */
		if (wordlen) {
			wordstore[wordlen] = '\0';
			puts (wordstore);
			putchar ('\n');
			Header (1);
			lineloc=0;
			}
		if (lineloc>lm)	putchar('\n');
		putchar('\n');
		}

	/* Restore path, return status if file was found */
	path[t] = '\0';
	return (found);
	}


/*************************************************************
*			PROCESS
*
*	Find the size of a directory tree by adding the sizes
*	of all files in the directory and all of its subdirectories.
*
*	General directory searching concept taken from TSIZE
*	utility by the Software Distillery.
************************************************************/

long	Process(len,depth)
	int len;	/* Length of path name */
	int depth;	/* Number of directories down */
	{
	struct FileInfoBlock *info = NULL;	/* pointer to file info */
	struct Sortem *sort_top = NULL;		/* top of sort list */
	struct Sortem *sort_cur = NULL;		/* current sort list loc */
	struct Sortem *sort_tmp = NULL;		/* current sort temp loc */
	struct Sortem *sort_last= NULL;		/* last sort temp loc */
	long size = 0;				/* running total of file size */
	BPTR lock;				/* pointer to file lock */
	BOOL found;				/* found DESC file */

	/* Try to obtain lock on current directory */
	if (!(lock = Lock(path, ACCESS_READ))) {
		printf ("Invalid directory, '%s'.\n", path);
		goto EXIT;
		}

	/* Allocate an info block (must be longword alligned) */
	if (!(info = (struct FileInfoBlock *)
		AllocMem(sizeof(struct FileInfoBlock), 0))) {
			printf ("Out of memory.\n");
			goto EXIT;
			}

	/* Get information on file or directory associated with lock */
	if (!Examine(lock, info)) {
		printf ("Error examining locked directory.\n");
		goto EXIT;
		}

	/* This should not happen, but... */
	if (info->fib_DirEntryType <= 0)  {
		printf ("Locked a file, not directory.\n");
		goto EXIT;
		}

	/* Display top of directory header */
	found = Display (depth,info->fib_FileName);

	/* Read this directory */
	while (ExNext(lock, info)) {
		/* This is a directory */
		if (info->fib_DirEntryType > 0)	{
			/* sub-directory, allocate sortem node */
			if (!(sort_cur = (struct Sortem *)
					AllocMem(sizeof(struct Sortem), 0))) {
				printf ("Out of memory.\n");
				break;
				}
			strcpy(sort_cur->FileName,info->fib_FileName);
			sort_cur->Next = NULL;

			/* Alphabetical insertion sort */
			/* Find insertion location for new node */
			for (sort_tmp = sort_top; sort_tmp; 
				sort_tmp = sort_tmp->Next) {
				if (filecmp(sort_tmp->FileName,info->fib_FileName)>0)
					break;
				sort_last = sort_tmp;
				}

			/* Insert the node */
			if (sort_tmp==sort_top) {
				sort_cur->Next = sort_top;
				sort_top = sort_cur;
				}
			else	if (!sort_tmp) {
				sort_cur->Next = NULL;
				sort_last->Next = sort_cur;
				}
				else {
					sort_cur->Next = sort_last->Next;
					sort_last->Next = sort_cur;
					}
			sort_cur = NULL;
			}
		else 	/* found a file, just add size */
			size += info->fib_Size;
		}

	/* Transverse sorted list, process then deallocate the node */
	for (sort_tmp = sort_top; sort_tmp; sort_tmp = sort_last) {
		size += Process(AddName(sort_tmp->FileName, len),depth+1);
		sort_last = sort_tmp->Next;
		FreeMem(sort_tmp, sizeof(struct Sortem));
		}

	/* Repair path */
	path[len] = '\0';

	/* Print bottom of directory information */
	Bottom (depth, size, found);

	EXIT:
	if (info) FreeMem(info, sizeof(struct FileInfoBlock));
	if (lock) UnLock(lock);
	return(size);
	}

/***************************************************************
*			MAIN
***************************************************************/

main(argc, argv)
	int argc;
	char *argv[];
	{
	int len;	/* Length of file name given on command line */
	int t;		/* Temp variable */

	/* Help information */
	if (argv[1][0]=='?') {
		printf ("%s\nUseage: %s [Read-Path] [-size]\n",
			VERSION, argv[0]);
		exit (0);
		}

	/* parse command line (len used as temp param counter) */
	for (len=1;len<argc;len++)
		if (argv[len][0]=='-') {	/* switch */
			switch (toupper(argv[len][1])) {
				case 'S':	/* Size Flag */
					prtsize = TRUE;
					break;
				case 'H':	/* Header Flag */
					prtheader = TRUE;
					break;
				/* ADD OTHER SWITCHES HERE */
				default:
					printf ("Bad Switch.\n");
				}

			/* Drop this argument */
			argc--;
			for (t=len;t<argc;t++)	argv[t] = argv[t+1];
			}

	/* Process pathname */
	if (argc>1)
		len = AddName(argv[1], 0);
	else	len = AddName(":", 0);

	/* Check root info, exit if invalid */
	if (Root()) return(20);

	/* Process root directory */
	(void)Process(len,0);

	EXIT:
	return(0);
	}
