
/*
 *  LOAD_MAIL.C
 *
 *  (C) Copyright 1985-1990 by Matthew Dillon,  All Rights Reserved.
 *
 *  file-io routines to scan the mail file and load required information.
 *
 *
 *  Global Routines:	HOLD_LOAD()         hold on loading mail after change
 *			NOHOLD_LOAD()       hold off.. load if changes
 *			LOAD_CHANGES()      reload mail if changed
 *			LOAD_MAIL()         load/reload mail
 *			SAVE_FILE()         save mail items back to spool
 *			CHECK_NEW_MAIL()    check for new mail
 *			WRITE_FILE()        append mail items to a file
 *			GET_EXTRA_OVR()     ret index of Field (create if not)
 *			ADD_EXTRA()         add another field (reloads mail)
 *			DELETE_EXTRA()      delete a field
 *			GET_EXTRA()         ret index of Field, or error
 *			M_SELECT()          select on current message list
 *
 *
 *  Static Routines:	LOAD_HASH()         load hash table from fields list
 *			FREE_ENTRY()        unload EVERYTHING
 *			FREE_TABLE()        unload all Fields table
 *			LOAD_FILE()         raw file loading/counting
 *
 *
 */

#include <stdio.h>
#include <sys/file.h>
#include "dmail.h"

void do_flock();
void free_table();
void load_hash();

#define NOHOLD	0
#define HOLD	1

#define NO_BASE     0
#define NO_FIELDS   1
#define ENTRY_OK    2

struct FIND Find[MAXTYPE + 1] = {
	"From:"   , 5, 1, 0,
	"To:"     , 3, 1, 0,
	"Subject:", 8, 1, 0 };

static int  File_size;
static int  changed, load_hold;
static int  Hash[256];

static char *quo_quo = "";

void
hold_load()
{
    load_hold = 1;
}

void
nohold_load()
{
    void load_changes();
    load_hold = 0;
    load_changes();
}

void
load_changes()
{
    if (changed  &&  !load_hold)
	load_mail(Entries, 1);
}

initial_load_mail()
{
    if (load_mail (0, 0) < 0)
	return (-1);
    return ((Entries) ? 1 : -1);
}


static
load_mail(at, from0)
{
    FILE *fi;
    int i, count, file_size;

    if (No_load_mail)
	return (-1);
    push_break();
    load_hash();
    if (from0)
	free_table (0, HOLD);
    else
	free_table (at, NOHOLD);
    fi = fopen (mail_file, "r+");
    if (m_fi != NULL)
	fclose (m_fi);
    m_fi = fopen (mail_file, "r+");
    if (fi == NULL  ||  m_fi == NULL) {
	pop_break();
	return (-1);
    }
    do_flock (fileno(m_fi), LOCK_EX);
    if (at)
	fseek (fi, Entry[at].fpos, 0);
    else
	fseek (fi, 0, 0);
    count = Entries;
    while (search_from(fi))
	++count;
    if (Entries != count) {
	if (!lmessage_overide)
	    printf ("%d Other Items loaded\n", count - Entries);
	lmessage_overide = 0;
	Entry = (struct ENTRY *)realloc (Entry, sizeof(*Entry) * (count + 1));
	bzero (&Entry[Entries], sizeof(*Entry) * (count + 1 - Entries));
    }
    Entries = count;
    for (i = at; i < Entries; ++i)
	Entry[i].no  =	Entry[i].status = 0;
    Entry[i].fpos = File_size = file_size = ftell (fi);
    fclose (fi);
    load_file ((from0) ? 0 : at);
    if (file_size != File_size) {       /* Last entry incomplete?       */
	free_table (Entries - 1, NOHOLD);
    }
    changed = 0;
    flock (fileno(m_fi), LOCK_UN);
    pop_break();
    return (1);
}

void
do_flock(fd, stat)
{
    if (flock(fd, stat | LOCK_NB) < 0) {
	puts ("File in use, Waiting for lock");
	flock (fd, stat);
	puts ("Have lock");
    }
}

static
load_file(at)
int at;
{
    FILE *fi;
    char *next, *ptr;
    int i, bit, maxbit, len, count, havefrom;

    maxbit = 0;
    for (i = 0; Find[i].search != NULL; ++i)
	maxbit = (maxbit << 1) | 1;
    fi = fopen (mail_file, "r");
    count = -1;
    havefrom = 0;
    while (havefrom  ||  search_from (fi)) {
	havefrom = 0;
	if (++count >= Entries)
	    break;
	len = strlen(Buf) - 1;
	Buf[len] = '\0';
	next = next_word(Buf);
	len -= next - Buf;
	Entry[count].fpos = ftell (fi);
	Entry[count].from = malloc (len + 1);
	bcopy (next, Entry[count].from, len + 1);

	/* SEARCH FIELD LIST */

	bit = 0;
	if (XDebug)
	    printf ("No %d  ---------------------\n", count + 1);
	while (fgets (Buf, MAXFIELDSIZE, fi) != NULL) {
	    if (Buf[0] == '\n')
		break;
	    if (isfrom(Buf)) {
		havefrom = 1;
		break;
	    }
	    len = strlen(Buf) - 1;
	    Buf[len] = '\0';
	    if (XDebug)
		printf ("CHECK: %s\n", Buf);
	    next = next_word(Buf);
	    len -= next - Buf;
	    if (Hash[*Buf] == 0)
		continue;
	    if (Hash[*Buf] > 0) {
		i = Hash[*Buf] & 0xff;
		if (strncmp (Find[i].search, Buf, Find[i].len) == 0)
		    goto found;
		continue;
	    }
	    for (i = -Hash[*Buf] & 0xff; Find[i].search; ++i) {
		if (*Find[i].search != *Buf)
		    break;
		if (strncmp (Find[i].search, Buf, Find[i].len) == 0)
		    goto found;
	    }
	    continue;
found:
	    if (XDebug)
		printf ("Found: %d %s\n", i, Buf);
	    if (Find[i].notnew == 0) {
		Find[i].notnew = 1;
		ptr = Buf;
		while (*ptr  &&  *ptr != ':')
		    ++ptr;
		++ptr;
		Find[i].search =
			realloc (Find[i].search, ptr - Buf + 1);
		strncpy (Find[i].search, Buf, ptr - Buf);
		*(Find[i].search + (ptr - Buf)) = '\0';
		Find[i].len = strlen(Find[i].search);
	    }
	    compile_field (Buf, fi);
	    Entry[count].fields[i] =
		    malloc (strlen(next) + 1);
	    strcpy (Entry[count].fields[i], next);
	    if ((bit |= (1 << i)) == maxbit)
		break;
	}
	if (bit != maxbit) {
	    for (i = 0; Find[i].search != NULL; ++i) {
		if (((1 << i) & bit) == 0) {
		    Entry[count].fields[i] = quo_quo;
		}
	    }
	}
    }
    File_size = ftell (fi);
    fclose (fi);
    return (1);
}


static void
load_hash()
{
    register int i, c;

    bzero (Hash, sizeof(Hash));
    for (i = 0; Find[i].search; ++i) {
	c = *Find[i].search;
	if (Hash[c] > 0)
	    Hash[c] = -Hash[c];
	if (Hash[c] == 0)
	    Hash[c] = i | 0x100;
    }
}


void
free_entry()
{
    free_table(0, NOHOLD);
    Entry = (struct ENTRY *)realloc (Entry, sizeof(*Entry));
    bzero (Entry[0].fields, sizeof(Entry[0].fields));
    File_size = Entries = 0;
    Entry->status = Entry->no = Entry->fpos = Current = 0;
    Listsize = 3;
    if (m_fi) {
	fclose (m_fi);
	m_fi = NULL;
    }
}


static void
free_table(at, hold)
{
    int i, j;

    for (i = at; i < Entries; ++i) {
	xfree (Entry[i].from);
	for (j = 0; Find[j].search != NULL; ++j) {
	    if (Entry[i].fields[j] != quo_quo)
		xfree (Entry[i].fields[j]);
	}
    }
    Entries = (hold == HOLD) ? Entries : at;
    File_size = (at) ? Entry[Entries].fpos : 0;
}

static
search_from(fi)
FILE *fi;
{
    while (fgets (Buf, MAXFIELDSIZE, fi) != NULL) {
	if (isfrom (Buf))
	    return (1);
    }
    return (0);
}


save_file(reload, mark, notmark)
{
    FILE *fiscr;
    int fdscr;
    int new_size, i, count;
    char scratch[64];

    for (i = 0; i < Entries; ++i) {
	if ((Entry[i].status & mark) != mark  ||
		(~Entry[i].status & notmark) != notmark)
	    break;
    }
    if (i == Entries) {
	m_select (Nulav, M_RESET);
	puts ("No Changes Made");
	return (Entries);
    }
    if (m_fi == NULL)
	return (-1);
    count = 0;
    sprintf (scratch, "tmp:dmail%d", getpid());
    do_flock (fileno(m_fi), LOCK_EX);
    fdscr = open (scratch, O_RDWR | O_CREAT | O_TRUNC, MAILMODE);
#ifdef AMIGA	    /*	fix bug in Lattice C fdopen */
    fiscr = fopen("nil:", "w");
    fclose(fiscr);
#endif
    fiscr = fdopen (fdscr, "a+");
    for (i = 0; i < Entries; ++i) {
	if ((Entry[i].status & mark) == mark  &&
		(~Entry[i].status & notmark) == notmark) {
	    ++count;
	    fputs ("From ", fiscr);
	    fputs (Entry[i].from, fiscr);
	    putc ('\n', fiscr);
	    fseek (m_fi, Entry[i].fpos, 0);
	    while (fgets (Buf, MAXFIELDSIZE, m_fi) != NULL) {
		if (isfrom(Buf))
		    break;
		fputs (Buf, fiscr);
	    }
	}
    }

    /* If NEW MAIL has come in, append that to the scratch file also */

    new_size = (fseek (m_fi, 0, 2) >= 0) ? ftell(m_fi) : File_size;
    if (File_size != new_size) {
	fseek (m_fi, File_size, 0);
	while (fgets (Buf, MAXFIELDSIZE, m_fi) != NULL)
	    fputs (Buf, fiscr);
    }

    /* Write scratch file back to mail file, or try to */

    fflush (fiscr);
    fflush (m_fi);

    lseek (fdscr, 0 ,0);
#ifdef UNIX
    lseek (fileno(m_fi), 0, 0);
    while ((i = read (fdscr, Buf, MAXFIELDSIZE)) > 0)
	write (fileno(m_fi), Buf, i);
    ftruncate (fileno(m_fi), lseek (fileno(m_fi), 0, 1));
#else
    fclose(m_fi);
    if (m_fi = fopen (mail_file, "w")) {
	while ((i = read (fdscr, Buf, MAXFIELDSIZE)) > 0)
	    write (fileno(m_fi), Buf, i);
	fclose(m_fi);
	m_fi = fopen (mail_file, "r+");
    }
    if (m_fi == NULL) {
	printf("Unable to re-open %s !\n", mail_file);
	return(-1);
    }
#endif
    if (lseek (fileno(m_fi), 0, 2) == 0  &&  !reload) {
	if (Did_cd == 0) {
	    fclose(m_fi);
	    m_fi = NULL;
	    if (unlink (mail_file) == 0)
		printf ("%s  Removed\n", mail_file);
	    else
		printf ("0 messages left in %s\n", mail_file);
	}
    }
    fclose (fiscr);
    if (m_fi)
	fclose (m_fi);          /* Effectively unlocks the descriptor */
    m_fi = NULL;
    unlink (scratch);
    if (reload) {
	free_entry();
	load_mail(0, 0);
    }
    m_select (Nulav, M_RESET);
    return (count);
}

void
check_new_mail()
{
    push_break();
    if (m_fi == NULL) {
	m_fi = fopen (mail_file, "r+");
	if (m_fi == NULL) {
	    pop_break();
	    return;
	}
    }
    if (fseek (m_fi, 0, 2) < 0 || ftell(m_fi) != File_size)
	load_mail(Entries, 1);
    pop_break();
}


write_file(file, modes, mark, notmark)
char *file;
{
    int i, fd = 1, notopen = 1;
    FILE *fi = NULL;

    for (i = 0; i < Entries; ++i) {
	if ((Entry[i].status & mark) == mark  &&
		(~Entry[i].status & notmark) == notmark) {
	    if (notopen) {
		notopen = 0;
		fd = open (file, O_APPEND | O_WRONLY | modes, MAILMODE);
		if (fd < 0)
		    return (-1);
		do_flock (fd, LOCK_EX);
#ifdef AMIGA	    /*	fix bug in Lattice C fdopen */
		fi = fopen("nil:", "w");
		fclose(fi);
#endif
		fi = fdopen (fd, "a");

#ifdef NOTDEF
		if (fi) {
		    printf("ptr     %08lx\n", fi->_ptr);
		    printf("rcnt    %08lx\n", fi->_rcnt);
		    printf("wcnt    %08lx\n", fi->_wcnt);
		    printf("base    %08lx\n", fi->_base);
		    printf("size    %08lx\n", fi->_size);
		    printf("flag    %08lx\n", fi->_flag);
		    printf("file    %08lx\n", fi->_file);
		    return(-1);
		}
#endif
	    }
	    fputs ("From ", fi);
	    fputs (Entry[i].from, fi);
	    putc ('\n', fi);
	    if (m_fi) {
		fseek (m_fi, Entry[i].fpos, 0);
		while (fgets (Buf, MAXFIELDSIZE, m_fi) != NULL) {
		    if (isfrom(Buf))
			break;
		    fputs (Buf, fi);
		}
	    }
	}
    }
    if (!notopen)
	fclose (fi);
    return (1);
}

/*
 * Basic scheme: Each entry has a fields list.	Each entry in the fields list
 * is guarenteed to be a valid malloc'd pointer (except some may be set to
 * quo_quo).
 *
 * The find[] struct array holds the field name and length, the index
 * corresponding to the index into the field[] in an Entry.
 *
 * The header and width arrays hold the list format.
 */

get_extra_ovr(str)
char *str;
{
    register int i;

    i = get_extra (str);
    if (i < 0) {
	i = add_extra (str);
	load_changes();
    }
    return (i);
}


/*
 * If there's room to add it, append to end.
 * Else Find oldest field which doesn't exist in the setlist and replace it
 *  with the new one.
 */

add_extra(str)
char *str;
{
    register int i, j, j_age, k;

    for (i = EXSTART; i < MAXTYPE; ++i) {
	if (Find[i].search == NULL)
	    break;
	++Find[i].age;
    }
    if (i == MAXTYPE) {                 /* No room to add onto end */
	j = j_age = -1;
	for (i = EXSTART; i < MAXTYPE; ++i) {
	    for (k = 0; k < Listsize; ++k) {
		if (i == header[k])
		    break;
	    }
	    if (k == Listsize  &&  Find[i].age > j_age) {
		j = i;
		j_age = Find[i].age;
	    }
	}
	i = j;
    }
    if (i < 0)
	return (-1);
    push_break();
    if (Find[i].search != NULL)
	xfree (Find[i].search);
    Find[i].len = strlen(str);
    Find[i].search = malloc (Find[i].len + 1);
    Find[i].notnew = Find[i].age = 0;
    strcpy (Find[i].search, str);
    changed = 1;
    for (j = 0; j < Entries; ++j) {
	if (Entry[j].fields[i] && Entry[j].fields[i] != quo_quo)
	    xfree (Entry[j].fields[i]);
	Entry[j].fields[i] = quo_quo;
    }
    pop_break();
    return (i);
}


get_extra(str)
char *str;
{
    int i;

    for (i = 0; Find[i].search; ++i) {
	if (strncmp (str, Find[i].search, strlen(str)) == 0) {
	    Find[i].age = 0;
	    return (i);
	}
    }
    return (-1);
}


m_select(sav, mode)
register char *sav[];
{
    char *ptr, *dest;
    char l_map[256];
    int idx[MAXLIST], ix = 0;
    int ok, not, len, scr;
    register int i, j, avi;

    for (i = 0;i < 256; ++i)
	l_map[i] = i;
    for (i = 'A'; i <= 'Z'; ++i)
	l_map[i] += 'a' - 'A';
    hold_load();
    i = 0;
    idx[ix++] = get_extra_ovr (sav[i++]);
    for (; sav[i]; ++i) {
	if (strcmp (sav[i], ",") == 0  &&  sav[i + 1])
	    idx[ix++] = get_extra_ovr (sav[++i]);
    }
    idx[ix] = -1;
    nohold_load();
    j = 1;
    push_break();
    for (i = 0; i < Entries; ++i) {
	if (mode == M_CONT  &&  Entry[i].no == 0)
	    continue;
	ix = ok = 0;
	avi = 1;
	while ((ptr = sav[avi]) != NULL) {
	    if (ptr[0] == ','  &&  ptr[1] == '\0' && sav[avi+1]) {
		++ix;
		avi += 2;
		continue;
	    }
	    if (not = (*ptr == '!'))
		++ptr;
	    len = strlen (ptr);
	    dest = Entry[i].fields[idx[ix]];
	    if (*ptr == '\0') {
		ok = 1;
		goto gotit;
	    }
	    while (*dest) {
		scr = 0;
		while (l_map[dest[scr]] == l_map[ptr[scr]] && ptr[scr])
		    ++scr;
		if (ptr[scr] == '\0') {
		    ok = 1;
		    goto gotit;
		}
		++dest;
	    }
	    ++avi;
	}
gotit:
	Entry[i].no = (ok ^ not) ? j++ : 0;
    }
    pop_break();
    if (Current < 0)
	Current = 0;
    if (Entries) {
	if (Entry[Current].no == 0) {
	    Current = indexof (1);
	    if (Current < 0) {
		 Current = 0;
		 return (-1);
	    }
	}
    } else {
	Current = -1;
    }
    return (1);
}


