
/*
 *  FILES.C
 *
 *  File Manager!
 *
 *  (c)Copyright 1987 Matthew Dillon, All Rights Reserved.
 */

#include "files.h"

extern LOCK *Lock();
extern LOCK *ParentDir(), *CurrentDir();
extern void *malloc();
extern void addentry();

RECORD *Rbase;		/*  All entries 	     */
RECORD *DisplayTop;	/*  Entry at the display top */
RECORD *Highlighted;	/*  Highlighted entry	     */
short  MaxNameLen;
long   NumEntries;
long   NumSelected;
long   NumTop;
long   NewNumTop;
FIB    *Fib;
short  Modified;

short  Xs, Xe, Ys, Ye, Rows, Cols;

char Title[80];

redisplay(newtop)
{
    short i;
    short noscroll = 0;
    uword percent, fill;
    RECORD *rec;

    bigboxbounds(&Xs,&Xe,&Ys,&Ye);
    Rows = (Ye-Ys)/Rp->TxHeight;
    Cols = (Xe-Xs)/Rp->TxWidth;

    if (newtop) {
	long delta = NumTop - NewNumTop;
	if (delta < 0)
	    delta = -delta;
	if (delta >= Rows)
	    noscroll = 1;
    }
    while (DisplayTop && !(DisplayTop->flags & R_SELECTED))
	DisplayTop = DisplayTop->next;
    if (newtop) {
	while (NewNumTop != NumTop && DisplayTop) {
	    if (NewNumTop < NumTop) {
		do {
		    DisplayTop = DisplayTop->prev;
		} while (DisplayTop && !(DisplayTop->flags & R_SELECTED));
		--NumTop;
		if (DisplayTop && !noscroll) {
		    ScrollRaster(Rp, 0, -Rp->TxHeight, Xs, Ys, Xe-1, Ye-1);
		    displayat(DisplayTop, 0);
		}
	    } else {
		do {
		    DisplayTop = DisplayTop->next;
		} while (DisplayTop && !(DisplayTop->flags & R_SELECTED));
		++NumTop;
		if (DisplayTop && !noscroll) {
		    ScrollRaster(Rp, 0, Rp->TxHeight, Xs, Ys, Xe-1, Ye-1);
		    displayrow(Rows-1);
		}
	    }
	}
	if (DisplayTop && !noscroll)
	    return(0);
    }
    sprintf(Title, "%ld/%ld", NumSelected, NumEntries);
    title(Title);
    if (DisplayTop == NULL) {
	DisplayTop = Rbase;
	NumTop = 0;
    }
    if (!newtop) {
	percent = fill = 0xFFFF;
	if (NumSelected) {
	    if (NumTop+(Rows>>1) > NumSelected)
		percent = 0xFFFF;
	    else
		percent = (NumTop + (Rows>>1)) * 0xFFFF / NumSelected;
	    fill = Rows * 0xFFFF / NumSelected;
	    if (Rows > NumSelected)
		fill = 0xFFFF;
	}
	setslider(percent, fill);
    }
    SetAPen(Rp, 0);
    RectFill(Rp, Xs, Ys, Xe-1, Ye-1);
    SetAPen(Rp, 1);
    for (rec = DisplayTop, i = 0; rec && i < Rows; rec = rec->next) {
	if (!(rec->flags & R_SELECTED))
	    continue;
	displayat(rec, i);
	++i;
    }
}

redisplayone(rec)
RECORD *rec;
{
    RECORD *nrec;
    short i;

    for (i = 0, nrec = DisplayTop; nrec && i < Rows; nrec = nrec->next) {
	if (!(nrec->flags & R_SELECTED))
	    continue;
	if (nrec == rec)
	    break;
	++i;
    }
    if (i < Rows && nrec == rec) {
	SetAPen(Rp, 0);
	RectFill(Rp, Xs, Ys + (Rp->TxHeight*i), Xe-1, Ys + (Rp->TxHeight*i) + Rp->TxHeight - 1);
	SetAPen(Rp, 1);
	displayat(rec, i);
    }
}

displayrow(row)
{
    register RECORD *rec = DisplayTop;
    register short i = row;

    while (i && rec) {
	rec = rec->next;
	while (rec && !(rec->flags & R_SELECTED))
	    rec = rec->next;
	--i;
    }
    if (rec && row < Rows)
	displayat(rec, row);
}

displayat(rec, i)
RECORD *rec;
{
    short len;

    len = strlen(rec->name);
    if (len > Cols)
	len = Cols;
    if (rec == Highlighted) {
	SetAPen(Rp, 0);
	SetBPen(Rp, 1);
    }
    Move(Rp, Xs, Ys + (Rp->TxHeight*i) + Rp->TxBaseline);
    Text(Rp, rec->name, len);
    if (Cols > MaxNameLen && rec->comment) {
	len = strlen(rec->comment);
	if (len > Cols - MaxNameLen)
	    len = Cols - MaxNameLen;
	Move(Rp, Xs + MaxNameLen * Rp->TxWidth, Ys + (Rp->TxHeight * i) + Rp->TxBaseline);
	Text(Rp, rec->comment, len);
    }
    if (rec == Highlighted) {
	SetAPen(Rp, 1);
	SetBPen(Rp, 0);
    }
}

bigboxhit(y, up)
{
    RECORD *hl = Highlighted;
    short row;

    Highlighted = NULL;
    if (hl)
	redisplayone(hl);
    row = (y - Ys) / Rp->TxHeight;
    if (row >= Rows || Rows < 0)
	return(0);
    for (hl = DisplayTop; hl && !(hl->flags & R_SELECTED); hl = hl->next);
    while (hl && row) {
	if (hl->flags & R_SELECTED)
	    --row;
	hl = hl->next;
    }
    for (; hl && !(hl->flags & R_SELECTED); hl = hl->next);
    if (Highlighted = hl) {
	redisplayone(hl);
	setcomment((hl->comment) ? hl->comment : "");
	if (up)
	    activate_com();
    }
}

sliderhit()
{
    uword pos, fill;

    getsliderpos(&pos, &fill);
    NewNumTop = pos * (NumSelected - Rows + 1) / 0xFFFF;
    redisplay(1);
}

/*
 *  Add a disk volume to the list.  Determine
 *  the root and add entries beginning at the
 *  specification.
 */

void
add_volume(str)
char *str;
{
    LOCK *lock;
    char path[128];
    int len;

    Highlighted = NULL;
    Fib = malloc(sizeof(FIB));
    if (Fib == NULL)
	return;
    resetsort();                /* also cleans up the database  */
    path[0] = 0;
    if (lock = Lock(str, ACCESS_READ)) {
	if (Examine(lock, Fib)) {
	    buildpath(lock, path);
	    len = strlen(path);
	    if (path[len-1] == ':' || path[len-1] == '/') {
		path[len] = '*';
		path[len+1] = 0;
		select_pattern(path, 1);
		path[len] = 0;
	    }
	    Examine(lock, Fib);
	    if (Fib->fib_DirEntryType < 0) {
		addentry(path, Fib->fib_Comment, Fib->fib_Size);
	    } else {
		RECORD *base;

		/*  find start of killpatterns, if any	*/
		for (base = Rbase; base; base = base->next) {
		    if (strcmp(base->name, KILLNAME) < 0)
			continue;
		    break;
		}
		title("Wait.. Scanning");
		scandir(lock, path, base);
	    }
	    title("Wait.. Update");
	    rem_selected(NULL,   1);
	}
	UnLock(lock);
    }
    free(Fib);
    strcat(path, "*");
    title("Select path");
    select_pattern(path, 0);
}

buildpath(lock, path)
LOCK *lock;
char *path;
{
    LOCK *parent;
    short plen, nlen;

    plen = strlen(path);
    nlen = strlen(Fib->fib_FileName) + 1;
    if (nlen == 1) {    /*  RAM: */
	strcpy(Fib->fib_FileName, "ram");
	nlen = 4;
    }
    bmov(path, path + nlen, plen + 1);
    strcpy(path, Fib->fib_FileName);
    if (Fib->fib_DirEntryType >= 0)
	path[nlen-1] = '/';
    if (parent = ParentDir(lock)) {
	if (Examine(parent, Fib)) {
	    buildpath(parent, path);
	}
	UnLock(parent);
    } else {
	path[nlen-1] = ':';
    }
}

scandir(lock, path, base)
LOCK *lock;
char *path;
RECORD *base;
{
    short restorelen = strlen(path);
    short len = restorelen;
    LOCK *olddirlock;

    if (path[restorelen-1] != ':' && path[restorelen-1] != '/') {
	strcpy(path + restorelen, "/");
	++len;
    }
    olddirlock = CurrentDir(lock);
    if (notkilled(path, base))
	addentry(path, Fib->fib_Comment, Fib->fib_Size);
    while (ExNext(lock, Fib)) {
	strcpy(path+len, Fib->fib_FileName);
	if (Fib->fib_DirEntryType < 0) {
	    if (notkilled(path, base))
		addentry(path, Fib->fib_Comment, Fib->fib_Size);
	} else {
	    LOCK *lock = Lock(Fib->fib_FileName, ACCESS_READ);
	    if (lock) {
		FIB *oldfib = Fib;
		if (Fib = malloc(sizeof(FIB))) {
		    Examine(lock, Fib);
		    scandir(lock, path, base);
		    UnLock(lock);
		    free(Fib);
		}
		Fib = oldfib;
	    }
	}
    }
    CurrentDir(olddirlock);
    path[restorelen] = 0;
}

notkilled(path,base)
char *path;
RECORD *base;
{
    while (base && strcmp(base->name, KILLNAME) == 0) {
	if (base->comment && newwildcmp(base->comment, path))
	    return(0);
	base = base->next;
    }
    return(1);
}

load_database(fi)
FILE *fi;
{
    char name[132];
    char comm[132];
    char size[32];

    fgets(name, 128, fi);   /*  # entries per item  */
    Highlighted = NULL;
    resetsort();
    title("Wait... Loading");
    while (fgets(name, 128, fi) && fgets(comm, 128, fi) && fgets(size, 32, fi)) {
	name[strlen(name)-1] = 0;   /*  remove newlines */
	comm[strlen(comm)-1] = 0;
	addentry(name, comm, atoi(size));
    }
    rem_selected(NULL, 1);
    selectall();
    redisplay(0);
}

save_database(fi)
FILE *fi;
{
    RECORD *rec;
    char buf[32];

    cleanup();
    title("Saving...");
    fputs("3\n", fi);
    for (rec = Rbase; rec; rec = rec->next) {
	fwrite(rec->name, strlen(rec->name), 1, fi);
	putc('\n', fi);
	if (rec->comment)
	    fwrite(rec->comment, strlen(rec->comment), 1, fi);
	putc('\n', fi);
	sprintf(buf, "%ld\n", rec->bytes);
	fputs(buf, fi);
	if (ferror(fi))
	    break;
    }
}

selectall()
{
    RECORD *rec;
    short len;

    Highlighted = NULL;
    DisplayTop = NULL;
    MaxNameLen = 0;
    for (rec = Rbase; rec; rec = rec->next) {
	if (rec->flags & R_KILLPAT)
	    continue;
	len = strlen(rec->name);
	if (MaxNameLen <= len)
	    MaxNameLen = len + 1;
	rec->flags |= R_SELECTED;
    }
    NumSelected = NumEntries;
}

select_pattern(str, noref)
char *str;
{
    register RECORD *rec;
    register short len;
    short which = 0;

    if (*str == '+')    /*  ADD selected patterns   */
	++str, which = 1;
    if (*str == '-')    /*  REMOVE selected patterns*/
	++str, which = 2;
    DisplayTop = NULL;
    Highlighted= NULL;

    switch(which) {
    case 0:
	NumSelected = 0;
	MaxNameLen = 0;
	for (rec = Rbase; rec; rec = rec->next) {
	    rec->flags &= ~R_SELECTED;
	    if (rec->flags & R_KILLPAT)
		continue;
	    if (newwildcmp(str, rec->name) || (rec->comment && newwildcmp(str, rec->comment))) {
		if (noref) {
		    rec->flags |= R_UPDATE;
		} else {
		    rec->flags |= R_SELECTED;
		    ++NumSelected;
		    if ((len = strlen(rec->name)) >= MaxNameLen)
			MaxNameLen = len + 1;
		}
	    }
	}
	break;
    case 1:
	for (rec = Rbase; rec; rec = rec->next) {
	    if ((rec->flags & R_KILLPAT) || (rec->flags & R_SELECTED))
		continue;
	    if (newwildcmp(str, rec->name) || (rec->comment && newwildcmp(str, rec->comment))) {
		rec->flags |= R_SELECTED;
		++NumSelected;
		if ((len = strlen(rec->name)) >= MaxNameLen)
		    MaxNameLen = len + 1;
	    }
	}
	break;
    case 2:
	for (rec = Rbase; rec; rec = rec->next) {
	    if (!(rec->flags & R_SELECTED))
		continue;
	    if (newwildcmp(str, rec->name) || (rec->comment && newwildcmp(str, rec->comment))) {
		rec->flags &= ~R_SELECTED;
		--NumSelected;
	    }
	}
	break;
    }
    if (!noref)
	redisplay(0);
}

/*
 *  If onerec != NULL, remove the one record,
 *  else remove all SELECTED records.
 */

rem_selected(onerec, noref)
RECORD *onerec;
{
    register RECORD *rec;
    register long len, maxlen;

    Highlighted = NULL;
    cleanup();
    if (onerec) {
	if (onerec->flags & R_SELECTED) {
	    onerec->flags &= ~R_SELECTED;
	    --NumSelected;
	}
	onerec->flags |= R_KILLPAT;
	--NumEntries;
    } else {
	maxlen = 0;
	for (rec = Rbase; rec; rec = rec->next) {
	    if (noref) {
		if (rec->flags & R_UPDATE) {
		    rec->flags &= ~R_UPDATE;
		    rec->flags |= R_KILLPAT;
		    --NumEntries;
		}
	    } else {
		if (rec->flags & R_SELECTED) {
		    rec->flags &= ~R_SELECTED;
		    rec->flags |= R_KILLPAT;
		   --NumEntries;
		}
	    }
	    if (!(rec->flags & R_KILLPAT) && (len=strlen(rec->name)) > maxlen)
		maxlen = len;
	}
	if (!noref)
	    NumSelected = 0;
	MaxNameLen = maxlen+1;
    }
    if (noref)
	cleanup();
    else
	redisplay(0);
}

undo()
{
    RECORD *rec;

    Highlighted = NULL;
    for (rec = Rbase; rec; rec = rec->next) {
	if (rec->flags & R_KILLPAT) {
	    rec->flags &= ~R_KILLPAT;
	    rec->flags |= R_SELECTED;
	    ++NumSelected;
	    ++NumEntries;
	    if (strlen(rec->name) >= MaxNameLen)
		MaxNameLen = strlen(rec->name)+1;
	}
    }
    redisplay(0);
}

cleanup()
{
    RECORD *rec, *nrec;
    for (rec = Rbase; rec; rec = nrec) {
	nrec = rec->next;
	if (rec->flags & R_KILLPAT) {
	    if (rec == DisplayTop)
		DisplayTop = nrec;
	    rmrecord(rec);
	}
    }
}

rmrecord(rec)
RECORD *rec;
{
    if (rec->flags & R_SOFTERR) {
	puts("panic: soft error");
	exit(1);
    }
    if (rec->prev)
	rec->prev->next = rec->next;
    else
	Rbase = rec->next;
    if (rec->next)
	rec->next->prev = rec->prev;
    rec->flags |= R_SOFTERR;
    freestr(rec->name);
    freestr(rec->comment);
    freerecord(rec);
}

/*
 *  modify the comment field for the highlighted
 *  item.
 */

mod_comment(str)
char *str;
{
    if (Highlighted) {
	Modified = 1;
	freestr(Highlighted->comment);
	if (str[0]) {
	    Highlighted->comment = allocstr(str);
	    if (Highlighted->comment == NULL)
		title("OUT OF MEMORY!");
	} else {
	    Highlighted->comment = NULL;
	}
	redisplayone(Highlighted);
    }
}


static RECORD *Cache;

resetsort()
{
    cleanup();
    Cache = Rbase;
}

void
addentry(name, comm, size)
char *name;
char *comm;
long size;
{
    RECORD *rec;
    short n;

    Modified = 1;
    rec = allocrecord();
    if (rec == NULL) {
	title("OUT OF MEMORY!");
	return;
    }
    rec->name = allocstr(name);
    if (rec->name == NULL) {
	rmrecord(rec);
	title("OUT OF MEMORY!");
	return;
    }
    rec->comment = NULL;
    if (strlen(comm)) {
	rec->comment = allocstr(comm);
	if (rec->comment == NULL) {
	    freestr(rec->name);
	    rmrecord(rec);
	    title("OUT OF MEMORY!");
	    return;
	}
    }
    rec->bytes = size;

    if (Rbase == NULL) {
	Rbase = rec;
	rec->prev = NULL;
	rec->next = NULL;
    } else {
	short n = strcmp(name, Cache->name);
	if (n == 0 && strcmp(name, KILLNAME) == 0)
	    n = 1;
	if (n < 0) {                    /*  name < Cache, move backwards */
	    while ((Cache = Cache->prev) && (n=strcmp(name, Cache->name)) < 0);
	} else
	if (n > 0) {                    /*  name > Cache, move forwards  */
	    while (Cache->next && (n=strcmp(name, Cache->next->name)) > 0)
		Cache = Cache->next;
	    if (Cache->next && n == 0)
		Cache = Cache->next;
	}
	if (Cache) {
	    rec->next = Cache->next;	/*  insert after cache */
	    rec->prev = Cache;
	    Cache->next = rec;
	} else {			/*  or at beginning    */
	    rec->next = Rbase;
	    rec->prev = NULL;
	    Rbase = rec;
	}
	if (rec->next)
	    rec->next->prev = rec;
	if (n == 0) {                   /*  replace if exact   */
	    if (Cache->comment) {
		char *swap = Cache->comment;
		Cache->comment = rec->comment;
		rec->comment = swap;
		Cache->flags |= R_UPDATE;
	    }
	}
    }
    rec->flags = R_SELECTED;
    ++NumSelected;
    ++NumEntries;
    if (MaxNameLen <= strlen(rec->name))
	MaxNameLen = strlen(rec->name) + 1;
    Cache = rec;
}

