
/*
 *  AnyMail -x MailReadyFileName -c Command -e user/pattern -i user/pattern ...
 *
 *  (C) Copyright 1990 by Chris Hind Genly, chris@genly.uucp
 *
 *  1.0
 *  ---
 *  - Original version.  Written by Chris Hind Genly.  Major hacks by
 *    Matt Dillon.
 *
 *  1.1
 *  ---
 *  - Empty mail files are no longer listed.
 *  - Pattern matching is now case insensitive.
 *  - LockFile() is used on files in uumail:
 *  - Display is updated after each mail file is scanned, rather than after
 *    all of uumail: is scanned.
 */

#include <exec/types.h>
#include <exec/lists.h>
#include <exec/ports.h>
#ifdef LATTICE
#include <proto/exec.h>
#include <proto/dos.h>
#endif
#include <libraries/dos.h>
#include <intuition/intuition.h>
#include <graphics/gfxbase.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "protos.h"
#include "config.h"
#include "version.h"

#ifndef LATTICE
char *strlwr(char *);
#endif

typedef struct Node Node;
typedef struct List List;
typedef struct Window Window;
typedef struct RastPort RPort;
typedef struct MsgPort MsgPort;

/* 1.1 */
#define LOWERCHAR(c) ('A' <= (c) && (c) <= 'Z' ? (c)-'A'+'a' : (c))

/*
 *  ONode is used to track each file that has been scanned and contains
 *  a list of messages already scanned for the file.
 */

typedef struct ONode {
    Node    on_Node;	    /*	name of file / link node	    */
    List    on_List;	    /*	message list for file		    */
    long    on_Offset;	    /*	offset for last valid message	    */
    long    on_EndOffset;   /*	end of file (check if appended to)  */
    long    on_MsgNo;	    /*	last message #			    */
} ONode;

#define on_Succ on_Node.ln_Succ
#define on_Name on_Node.ln_Name

Window	*Win;	    /*	intuition window	    */
MsgPort *SigPort;   /*	signal port for sendmail    */
List	FilList;    /*	file-name list		    */
List	Excludes;
List	Includes;
int	Count;
BPTR	MailLock;
char	Buf[256];
char	Title[256];

struct IntuitionBase *IntuitionBase;
struct GfxBase	     *GfxBase;

IDENT(".03");

struct NewWindow Nw = {
    40, 10, 560, 180, -1, -1,
    CLOSEWINDOW|REFRESHWINDOW|MOUSEBUTTONS,
    WINDOWCLOSE|WINDOWDRAG|WINDOWSIZING|SIMPLE_REFRESH|WINDOWDEPTH,
    NULL, NULL,
    "AnyMail Version 1.0     You have mail",
    NULL, /* Screen */
    NULL, /* BitMap */
    30, 30, -1, -1, WBENCHSCREEN
};

int Init(void);
int TimeToRepeat(int *);
int OkToScan(struct FileInfoBlock *);
void MakeNode(struct List *, char *);
void Display(void);
void Terminate(void);
void SaveConfig(Window *);
void LoadConfig(struct NewWindow *);
long AnyMail(char *, ONode *);
char *FromName(char *);
char *ExtractPersonalName(char *);
ONode *GetONode(char *);
int amatch(char *, char *, int *);



/*
 *  A FileInfoBlock structure MUST be longword aligned.
 */

#ifdef LATTICE
extern int atexit(int(*)());
#define SEEK_SET 0
#endif

char *MailRdyName;
struct FileInfoBlock *Fib;

main(int argc, char **argv)
{
    int i;
    char *command = 0;
    int doCommand = FALSE;

    Fib = (struct FileInfoBlock *)malloc(sizeof(struct FileInfoBlock));
    NewList(&Excludes);
    NewList(&Includes);
    NewList(&FilList);

    atexit(Terminate);

    for (i = 1; i < argc; ++i) {
	if (stricmp(argv[i], "-x") == 0) {
	    MailRdyName = argv[++i];
	} else if (stricmp(argv[i], "-e") == 0) {
	    MakeNode(&Excludes, argv[++i]);
	} else if (stricmp(argv[i], "-i") == 0) {
	    MakeNode(&Includes, argv[++i]);
	} else if (stricmp(argv[i], "-c") == 0) {
	    command = argv[++i];
	} else {
	    fprintf(stderr, "AnyMail: Unknown keyword %s\n", argv[i]);
	}
    }
    if (MailRdyName)
	SigPort = CreatePort(MailRdyName, 1);

    if (Init()) {
	while (TimeToRepeat(&doCommand)) {
	    ONode *onf;
	    long offset;

	    MailLock = Lock(GetConfigDir(UUMAIL), ACCESS_READ);
	    if (!MailLock)      /*  unrecoverable error */
		break;
	    if (Examine(MailLock, Fib)) {
		while (ExNext(MailLock, Fib)) {
		    if (!OkToScan(Fib))
			continue;
		    onf = GetONode(Fib->fib_FileName);
		    if (onf->on_EndOffset < Fib->fib_Size) {
			onf->on_EndOffset = Fib->fib_Size;
			offset = AnyMail(Fib->fib_FileName, onf);
			if (onf->on_Offset != offset) {
			    Display();
			    onf->on_Offset = offset;
			}
		    }
		}
	    } else {
		break;
	    }
	    UnLock(MailLock);
	    MailLock = 0;
	}
    }

    if (doCommand && command)
	Execute(command, 0, 0);

    return(0);
}

int
OkToScan(struct FileInfoBlock *fib)
{
    Node *n;
    char *fileName;
    int cnt;

    if (fib->fib_DirEntryType >= 0)
	return FALSE;

    for (n = Includes.lh_Head; n->ln_Succ; n = n->ln_Succ) {
	fileName = fib->fib_FileName;
	strlwr(fileName);
	if (amatch(fileName, n->ln_Name, &cnt) && cnt == strlen(fileName))
	    return(TRUE);
    }

    for (n = Excludes.lh_Head; n->ln_Succ; n = n->ln_Succ) {
	fileName = fib->fib_FileName;
	strlwr(fileName);
	if (amatch(fileName, n->ln_Name, &cnt) && cnt == strlen(fileName))
	    return(FALSE);
    }

    return(TRUE);
}

int
Init(void)
{
    IntuitionBase = OpenLibrary("intuition.library", 0);
    if (IntuitionBase == 0) {
	fprintf(stderr, "AnyMail: Unable to open intuition.library\n");
	return(0);
    }

    GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 0);
    if (GfxBase == 0) {
	fprintf(stderr, ": Unable to open graphics.library\n");
	return(0);
    }

    return(1);
}

int
TimeToRepeat(int *DoCommand)
{
    struct IntuiMessage *im;
    int done   = FALSE;
    int repeat = FALSE;
    long windowMask;
    long portMask;
    long mask;

    if (Win == NULL) {
	Display();
	return(TRUE);
    }

    windowMask = 1 << Win->UserPort->mp_SigBit;

    if (SigPort)
	portMask = 1 << SigPort->mp_SigBit;
    else
	portMask = 0;

    while (!done) {
	mask = Wait(windowMask|portMask|SIGBREAKF_CTRL_C);

	if (mask & windowMask) {
	    while (im = (struct IntuiMessage *)GetMsg(Win->UserPort)) {
		switch(im->Class) {
		case MOUSEBUTTONS:
		    if (im->Code == SELECTDOWN) {
			*DoCommand = TRUE;
			done = TRUE;
		    }
		    break;
		case REFRESHWINDOW:
		    BeginRefresh(Win);
		    Display();
		    EndRefresh(Win, TRUE);
		    break;
		case CLOSEWINDOW:
		    done = TRUE;
		    break;
		}
		ReplyMsg((struct Message *)im);
	    }
	}
	if (mask & portMask) {
	    repeat = TRUE;
	    done = TRUE;
	}
	if (mask & SIGBREAKF_CTRL_C)
	    exit(1);
    }

    return repeat;
}

int
AnyMail(char *user, ONode *onf)
{
    static char FromLine[256];
    static char SubjLine[256];
    char *file = malloc(strlen(user) + 32);
    FILE *fi;
    long msgno = 0;
    long offset = onf->on_Offset;

    if ('a' <= user[0] && user[0] <= 'z')
	user[0] = user[0] - 'a' + 'A';

    strcpy(file, MakeConfigPath(UUMAIL, user));

    LockFile(file); /* 1.1 */

    if (fi = fopen(file, "r")) {
	if (fseek(fi, offset, SEEK_SET) < 0) {
	    fclose(fi);
	    UnLockFile(file); /* 1.1 */
	    return offset;
	}
	while (fgets(Buf, sizeof(Buf), fi)) {
	    /*
	     *	Start of message
	     */

	    if (strncmp(Buf, "From ", 5) != 0)
		continue;

	    ++msgno;

	    /*
	     *	Scan headers for From: and Subject:
	     *	Headers end with a blank line.
	     */

	    FromLine[0] = 0;
	    SubjLine[0] = 0;

	    while (fgets(Buf, sizeof(Buf), fi) && Buf[0] != '\n') {
		if (strncmp(Buf, "From:", 5) == 0)
		    strcpy(FromLine, Buf + 5);
		if (strncmp(Buf, "Subject:", 8) == 0)
		    strcpy(SubjLine, Buf + 8);
	    }

	    /* Remove the trailing new line from the subject */

	    if (SubjLine[0])
		SubjLine[strlen(SubjLine)-1] = 0;

	    /*
	     *	(try) Make sure we didn't read the file just as sendmail
	     *	was appending to it.
	     */

	    if (!feof(fi)) {
		sprintf(Buf, "%-2d %-20.20s  %s",
		    ++onf->on_MsgNo,
		    FromName(FromLine),
		    SubjLine
		);
		MakeNode(&onf->on_List, Buf);
		++Count;
		offset = ftell(fi);
	    }
	}
	fclose(fi);
    }

    UnLockFile(file);     /* 1.1 */

    return offset;
}

void
Display(void)
{
    int xmin, xmax, ymin, ymax, x, y;
    int len, lenmax;
    ONode *onf;
    Node *node;
    RPort *rp;

    if (Win == NULL) {
	LoadConfig(&Nw);
	Win = OpenWindow(&Nw);
	if (Win == NULL) {
	    Nw.LeftEdge = 40;
	    Nw.TopEdge = 10;
	    Nw.Width = 560;
	    Nw.Height= 180;
	    Win = OpenWindow(&Nw);
	}
	if (Win == NULL)
	    return;

	ScreenToFront(Win->WScreen);
    }
    rp = Win->RPort;

    xmin = Win->BorderLeft;
    ymin = Win->BorderTop;
    xmax = Win->Width  - Win->BorderRight  - 1;
    ymax = Win->Height - Win->BorderBottom - 1;

    if (xmin < xmax && ymin < ymax) {
	SetAPen(rp, 0);
	RectFill(rp, xmin, ymin, xmax, ymax);
    }

    x = xmin + 2;
    y = ymin + 4;
    lenmax = (xmax-xmin+1)/GfxBase->DefaultFont->tf_XSize;

    SetAPen(rp, 1);
    SetBPen(rp, 0);

    for (onf = (ONode *)FilList.lh_Head; onf->on_Succ; onf = (ONode *)onf->on_Succ) {

	if (onf->on_List.lh_Head->ln_Succ == 0)     /* 1.1 */
	   continue;				    /* 1.1 */

	/*
	 *  display 'To filename', then messages for this file
	 */

	{
	    if (y + GfxBase->DefaultFont->tf_YSize > ymax)
		break;
	    sprintf(Buf, "To %s", onf->on_Name);
	    len = strlen(Buf);
	    if (len > lenmax)
		len = lenmax;
	    Move(rp, x, y + GfxBase->DefaultFont->tf_Baseline);
	    SetDrMd(rp, COMPLEMENT);
	    Text(rp, Buf, len);
	    SetDrMd(rp, JAM2);
	    y += GfxBase->DefaultFont->tf_YSize;
	}
	for (node = onf->on_List.lh_Head; node->ln_Succ; node = node->ln_Succ) {
	    if (y + GfxBase->DefaultFont->tf_YSize > ymax)
		break;
	    len = strlen(node->ln_Name);
	    if (len > lenmax)
		len = lenmax;
	    Move(rp, x, y + GfxBase->DefaultFont->tf_Baseline);
	    Text(rp, node->ln_Name, len);
	    y += GfxBase->DefaultFont->tf_YSize;
	}
    }
    sprintf(Title, "AnyMail Version 1.1    You have %d message%s waiting.",
	Count,
	(Count==1) ? "" : "s"
    );
    SetWindowTitles(Win, Title, 0);
}

void
Terminate(void)
{
    if (Win) {
	SaveConfig(Win);
	CloseWindow(Win);
	Win = NULL;
    }

    if (GfxBase) {
	CloseLibrary(GfxBase);
	GfxBase = NULL;
    }

    if (IntuitionBase) {
	CloseLibrary(IntuitionBase);
	IntuitionBase = NULL;
    }

    if (MailLock) {
	UnLock(MailLock);
	MailLock = 0;
    }

    /*
     *	delete sigport before removing file
     */

    if (SigPort) {
	DeletePort(SigPort);
	SigPort = NULL;
    }

    if (MailRdyName)
	remove(MailRdyName);
}

/*
 * The real name is between parens.  If it can't be found, use the
 * whole from line
 */

char *
FromName(fromLine)
char *fromLine;
{
    char *start;
    char *end;

    start = strchr(fromLine, '(');
    if (start) {
	start++;
	end = strchr(start, ')');
	if (end) {
	    *end = 0;
	    return(start);
	}
    }
    end = strchr(fromLine, '\n');
    if (end)
	*end = 0;
    return(fromLine);
}

void
MakeNode(list, name)
List *list;
char *name;
{
    Node *node;

    if (node = malloc(sizeof(Node) + strlen(name) + 1)) {
	node->ln_Name = (char *)(node + 1);
	strcpy(node->ln_Name, name);
    }
    AddTail(list, node);
}

ONode *
GetONode(name)
char *name;
{
    ONode *onf;

    for (onf = (ONode *) FilList.lh_Head; onf->on_Succ; onf = (ONode *)onf->on_Succ) {
	if (stricmp(name, onf->on_Name) == 0)
	    break;
    }
    if (onf->on_Succ == NULL) {
	onf = malloc(sizeof(ONode) + strlen(name) + 1);
	setmem(onf, sizeof(ONode), 0);
	onf->on_Name = (char *)(onf + 1);
	strcpy(onf->on_Name, name);
	NewList(&onf->on_List);
	AddTail(&FilList, onf);
    }
    return(onf);
}

int CfgLE;
int CfgTE;
int CfgW;
int CfgH;

void
LoadConfig(nw)
struct NewWindow *nw;
{
    FILE *fi;
    if (fi = fopen("S:AnyMail.config", "r")) {
	fscanf(fi, "%d %d %d %d", &CfgLE, &CfgTE, &CfgW, &CfgH);
	nw->LeftEdge = CfgLE;
	nw->TopEdge  = CfgTE;
	nw->Width    = CfgW;
	nw->Height   = CfgH;
	fclose(fi);
    }
}

void
SaveConfig(win)
Window *win;
{
    FILE *fi;

    if (CfgLE != win->LeftEdge || CfgTE != win->TopEdge || CfgW != win->Width || CfgH != win->Height) {
	if (fi = fopen("S:AnyMail.config", "w")) {
	    fprintf(fi, "%d %d %d %d\n", win->LeftEdge, win->TopEdge, win->Width, win->Height);
	    fclose(fi);
	}
    }
}


/*
 * Match the pattern, p, against the start of the string, s.
 * Return TRUE if there is a match, FALSE if not.  If there is a match, set
 * cnt to the number of characters matched.
 *
 *	?  Matches any character
 *	#p Maches zero or more ps, where p is a pattern not starting with #.
 */
int
amatch(
    char *s,	   /* The string to match	   */
    char *p,	   /* The pattern to match against */
    int  *cnt
)
{
    char *bas = s;
    char buff[2];
    int cnt1, cnt2;

    while(*p) {
	switch(*p) {
	default:
	    /* 1.1 uses LOWERCHAR() */
	    if (LOWERCHAR(*s) == LOWERCHAR(*p)) ++s, ++p;
	    else return FALSE;
	    break;
	case '?':
	    if (*s) ++s, ++p;
	    else return FALSE;
	    break;
	case '#':
	    if (p[1] == 0) return FALSE; /* Syntax error */

	    buff[0] = p[1]; buff[1] = '\0';
	    if (amatch(s, buff, &cnt1) && amatch(s+cnt1, p, &cnt2)) {
		*cnt = s-bas + cnt1 + cnt2;
		return TRUE;
	    } else
		p += 2;
	    break;
	}
    }

    *cnt = s-bas;

    return TRUE;
}

#ifndef LATTICE

char *
strlwr(s)
char *s;
{
    char *b;

    for (b = s; *b; ++b) {
	if (*b >= 'A' && *b <= 'Z')
	    *b |= 0x20;
    }
    return(s);
}

#endif

