/* listed.c - create a menu from the contents of a directory
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif				/* HAVE_CONFIG_H */
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include "viewmenu.h"
#include "ldb.h"

#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
#  include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
#  include <sys/dir.h>
# endif
# if HAVE_NDIR_H
#  include <ndir.h>
# endif
#endif


#define LISTED_MAX	64		    /* max entries in a directory */

typedef struct {			 /* structure containing directory entries */
	char *name;			 /* filename */
	char *exec;			 /* exec string */
	char type;			 /* entry type ('A', 'L', etc) */
	char readonly;			 /* 1 if entry must be readonly */
	char mine;			 /* 1 if entry belongs to user */
	char key;			 /* key assigned, or 0 if none */
	int blank;			 /* byte to zero, or 0 if none */
	time_t ctime;			 /* creation time of the file */
} listed_entry;

listed_entry listed_entries[LISTED_MAX];
int num_listed_entries = 0;


/* Returns 1 if the directory entry "d" is to be added to the list, 0
 * otherwise.
 */
int menuview_listed__select(const struct dirent *d)
{
	if (!d)
		return (0);
	if (d->d_name[0] == '.')
		return (0);
	return (1);
}


/* Free the array "namelist" of file names.
 */
void menuview_listed__freelist(struct dirent **namelist, int n)
{
	int i;

	if (!namelist)
		return;

	for (i = 0; i < n; i++)
		free(namelist[i]);
	free(namelist);
}


/* Scan the directory "dir" for possible menu entries, storing them in the
 * array "listed_entries"
 *
 * Returns the number of entries stored, or negative on error.
 */
int menuview_listed_scandir(char *dir)
{
	static char *mapping[] = {
		".acl", "C",		 /* ACL file */
		"~", 0,			 /* backup file - ignore */
		".an", "A",		 /* animation file */
		".ans", "V",		 /* VT/ANSI raw file */
		".vt", "V",		 /* VT/ANSI raw file */
		".bin", "B",		 /* binary file */
		".cm", "C",		 /* comment file */
		".cm.gz", "c",		 /* compressed comment file, readonly */
		".cm.bz2", "c",		 /* compressed comment file, readonly */
		".ro", "c",		 /* comment file, readonly */
		".ro.gz", "c",		 /* compressed comment file, readonly */
		".ro.bz2", "c",		 /* compressed comment file, readonly */
		".mn", "M",		 /* menu file */
		".raw", "U",		 /* unformatted file */
		".txt", "F",		 /* formatted file */
		".txt.gz", "F",		 /* compressed formatted file */
		".txt.bz2", "F",	 /* compressed formatted file */
		".sp.sh", "Ssh %s",	 /* shell script - spool */
		".sp.pl", "Sperl %s",	 /* perl script - spool */
		".sh", "Xsh %s",	 /* shell script - execute */
		".pl", "Xperl %s",	 /* perl script - execute */
		".perl", "Xperl %s",
		".html", "Xlynx -restrictions=all %s",	/* HTML file - view with Lynx */
		".htm", "Xlynx -restrictions=all %s",
		".sc", "Xsc %s",	 /* spreadsheet - view with `sc' */
		".log", "F",
		".log.gz", "F",
		".log.bz2", "F",
		".c", "F",
		".C", "F",
		".cc", "F",
		".cpp", "F",
		".mk", "F",
		".cf", "F",
		".conf", "F",
		".cfg", "F",
		".css", "F",
		".awk", "F",
		".sed", "F",
		".in", "F",
		".ti", "F",
		0, 0, 0, 0,
	};
	static char *allowed = "ABCDEFGHIJKLMNOP@RSTUVWXYZ"
	    "0123456789" "`!\"$%^&*()_{}[]:;'#~<>,|\\";
	struct dirent **namelist = 0;
	struct stat sb;
	char buf[1024];
	char used[128];
	char tmp[2];
	int order[LISTED_MAX + 2];
	uid_t myuid;
	int i, j, k, n, x, swapped, t;

	num_listed_entries = 0;
	strcpy(used, ".Q");

#ifdef HAVE_SCANDIR
	n = scandir(dir, &namelist, menuview_listed__select, alphasort);
	if (n < 0)
		return (-1);		    /* error reading directory */

	myuid = getuid();

	for (x = 0; x < n; x++) {
		if (num_listed_entries >= LISTED_MAX) {	/* directory too full */
			menuview_listed__freelist(namelist, n);
			return (-1);
		}

		listed_entries[num_listed_entries].type = 'F';
		listed_entries[num_listed_entries].readonly = 0;
		listed_entries[num_listed_entries].mine = 0;
		listed_entries[num_listed_entries].key = 0;
		listed_entries[num_listed_entries].blank = 0;
		listed_entries[num_listed_entries].name = 0;
		listed_entries[num_listed_entries].exec = 0;

		j = 1;
		for (i = 0; mapping[i]; i += 2) {	/* look up type mapping */
			k = NAMLEN(namelist[x]) - strlen(mapping[i]);
			if (k < 0)
				continue;
			if (!strcmp(namelist[x]->d_name + k, mapping[i])) {
				listed_entries[num_listed_entries].blank =
				    k;
				if (mapping[i + 1])
					j = mapping[i + 1][0];
				else
					j = 0;
				listed_entries[num_listed_entries].type =
				    toupper(j);
				if (j != 'C') {	/* make readonly */
					listed_entries[num_listed_entries].
					    readonly = 1;
				}
				if ((j != 0) && (strlen(mapping[i + 1]) > 1)) {	/* exec'able */
					sprintf(buf, mapping[i + 1] + 1,
						namelist[x]->d_name);
					free(listed_entries
					     [num_listed_entries].exec);
					listed_entries[num_listed_entries].
					    exec = strdup(buf);
				}
				break;
			}
		}

		if (!j)
			continue;

		if (listed_entries[num_listed_entries].type == 'F') {
			if ((listed_entries[num_listed_entries].blank == 0)
			    && (strchr(namelist[x]->d_name, '.'))) {
				listed_entries[num_listed_entries].type =
				    'B';
			}
		}

		strncpy(buf, menu_path, 1000);
		strcat(buf, "/");
		strncat(buf, namelist[x]->d_name, 1000 - strlen(buf));

		if (stat(buf, &sb))
			continue;

		listed_entries[num_listed_entries].ctime = sb.st_ctime;

		if (S_ISDIR(sb.st_mode))
			listed_entries[num_listed_entries].type = 'L';

		if (sb.st_uid == myuid) {
			listed_entries[num_listed_entries].mine = 1;
		} else {
			if (!(sb.st_mode & S_IRGRP))
				continue;   /* not readable */
			if (S_ISDIR(sb.st_mode)) {
				if (!(sb.st_mode & S_IXGRP))
					continue;	/* directory not enterable */
			}
			if (!(sb.st_mode & S_IWGRP)) {	/* not writeable - readonly */
				listed_entries[num_listed_entries].
				    readonly = 1;
			}
		}

		if (!strcmp(namelist[x]->d_name, "Mtitle")) {
			listed_entries[num_listed_entries].key = 1;
		}


		listed_entries[num_listed_entries].name =
		    strdup(namelist[x]->d_name);
		if (!listed_entries[num_listed_entries].name)
			continue;

		num_listed_entries++;

	}

	menuview_listed__freelist(namelist, n);

	for (i = 0; i < num_listed_entries; i++)
		order[i] = i;
	swapped = 1;
	while (swapped) {		    /* bubble sort by creation time */
		swapped = 0;
		for (i = 1; i < num_listed_entries; i++) {
			if (listed_entries[order[i - 1]].ctime >
			    listed_entries[order[i]].ctime) {
				swapped = 1;
				t = order[i - 1];
				order[i - 1] = order[i];
				order[i] = t;
			}
		}
	}

	for (n = 0; n < num_listed_entries; n++) {	/* assign keys */
		i = 0;
		while (listed_entries[order[n]].key == 0) {	/* pick key */
			if (i < strlen(listed_entries[order[n]].name)) {
				k = toupper(listed_entries[order[n]].
					    name[i]);
				i++;
				if (k == 'Q')
					k = '@';
				if (k == '-')
					k = '^';
				if (!strchr(allowed, k))
					k = 0;
				if ((k) && (!strchr(used, k))) {
					tmp[0] = k;
					tmp[1] = 0;
					strcat(used, tmp);
					listed_entries[order[n]].key = k;
					continue;
				}
			} else {
				k = 0;
				for (i = 0;
				     (i < strlen(allowed)) && (k == 0);
				     i++) {
					k = allowed[i];
					if (strchr(used, k))
						k = 0;
				}
				if (k == 0) {	/* no more keys left */
					k = 'A';	/* just give it "A" */
				}
				tmp[0] = k;
				tmp[1] = 0;
				strcat(used, tmp);
				listed_entries[order[n]].key = k;
			}
		}
	}

	return (num_listed_entries);

#else				/* !HAVE_SCANDIR */
	return -1;			    /* TODO: support for non-scandir()-capable systems */
#endif				/* HAVE_SCANDIR */
}


/* Free the list of directory entries.
 */
void menuview_listed__free_entries(void)
{
	int i;
	for (i = 0; i < num_listed_entries; i++) {
		free(listed_entries[i].name);
		free(listed_entries[i].exec);
	}
	num_listed_entries = 0;
}


/* Add a blank line to "menu".
 */
void menuview_listed_addblank(menu_t * menu)
{
	menuentry_t *e;

	if (menu_addentry(menu))
		return;

	e = &(menu->menu[menu->num_entries - 1]);

	e->key = 0;
	e->type = MENU_ENTRY_TEXT;
	e->title = 0;
}


/* Munge the title of directory entry "n" so that it can be displayed.
 */
void menuview_listed_mungetitle(int n)
{
	char buf[1024];
	char *a;
	int i;

	listed_entries[n].name[0] = toupper(listed_entries[n].name[0]);

	i = listed_entries[n].blank;
	if (i > 0)
		listed_entries[n].name[i] = 0;

	for (i = 0; i < strlen(listed_entries[n].name); i++) {
		if (listed_entries[n].name[i] == '_')
			listed_entries[n].name[i] = ' ';
	}

	buf[0] = 0;
	if ((listed_entries[n].type == 'M')
	    || (listed_entries[n].type == 'L')) {
		strcpy(buf, "\035CG");
	}

	a = strchr(listed_entries[n].name, listed_entries[n].key);
	if (!a)
		a = strchr(listed_entries[n].name,
			   tolower(listed_entries[n].key));

	if (a) {
		i = *a;
		*a = 0;
		strcat(buf, listed_entries[n].name);
		strcat(buf, "\035B");
		*a = i;
		i = a[1];
		a[1] = 0;
		strcat(buf, a);
		strcat(buf, "\035b");
		a[1] = i;
		a++;
		strcat(buf, a);
	} else {
		strcat(buf, listed_entries[n].name);
	}

	if ((listed_entries[n].type == 'M')
	    || (listed_entries[n].type == 'L')) {
		strcat(buf, "\035CA");
	}

	if (strcmp(listed_entries[n].name, buf)) {
		free(listed_entries[n].name);
		listed_entries[n].name = strdup(buf);
	}
}


/* Read the contents of the directory "data->file" and generate a menu from
 * it, inheriting the status flags "data->status" for each entry.
 *
 * Returns 0 on error, or the menu on success.
 */
menu_t *menuview_loadlisted(menudata_t data)
{
	struct passwd *p = 0;
	menuentry_t *e;
	char buf[1024];
	struct stat sb;
	menu_t *menu;
	int dentries;
	char first;
	char *a;
	int i;

	ldb_init();

	menu = calloc(1, sizeof(menu_t));
	if (!menu)
		return (0);

	menu_path = strdup(ldb_filename(data->file));
	a = menu_path + strlen(menu_path);
	if (a > menu_path)
		a--;
	if (a[0] == '/')
		*a = 0;			    /* strip trailing / */

	if (!stat(menu_path, &sb))
		p = getpwuid(sb.st_uid);
	menu->owner = strdup((p != 0) ? p->pw_name : "unknown");

	if (data->status & MENU_STATUS_EDIT)
		menu->allow_edit = 1;
	if ((p) && (sb.st_uid == getuid()))
		menu->allow_edit = 1;
	if (sb.st_mode & S_IWGRP)
		menu->allow_edit = 1;

	if (data->status & MENU_STATUS_READONLY) {
		data->status ^= (data->status & MENU_STATUS_ADD);
		data->status ^= (data->status & MENU_STATUS_EDIT);
		data->status ^= (data->status & MENU_STATUS_DELETE);
	} else {
		data->status |= MENU_STATUS_ADD;
	}

	dentries = menuview_listed_scandir(menu_path);
	if (dentries < 0) {
		menuview_listed__free_entries();
		free(menu_path);
		menu_free(menu);
		return (0);
	}

	if (data->status & MENU_STATUS_EDIT) {	/* make dir editable? */
		menu->allow_edit = 1;
	}

	menuview_listed_addblank(menu);
	first = 0;

	for (i = 0; i < dentries; i++) {    /* put files into menu */

		if ((listed_entries[i].type == MENU_ENTRY_LISTED)	/* submenu */
		    ||(listed_entries[i].type == MENU_ENTRY_MENU))
			continue;

		if (!strcmp(listed_entries[i].name, "Mtitle")) {	/* title */
			strncpy(buf, menu_path, 1000);
			strcat(buf, "/");
			strncat(buf, listed_entries[i].name,
				1000 - strlen(buf));
			free(menu->title);
			menu->title = strdup(buf);
			menu->title_type = MENU_TITLE_CENTRE;
			continue;
		}

		if (menu_addentry(menu)) {  /* extend array */
			menuview_listed__free_entries();
			free(menu_path);
			menu_free(menu);
			return (0);
		}

		e = &(menu->menu[menu->num_entries - 1]);

		e->key = listed_entries[i].key;
		e->type = listed_entries[i].type;

		if (strchr("ABCFLMRUV", listed_entries[i].type)) {
			strncpy(buf, menu_path, 1000);
			strcat(buf, "/");
			strncat(buf, listed_entries[i].name,
				1000 - strlen(buf));
			e->filename = strdup(ldb_filename(buf));
		} else {
			if (listed_entries[i].exec) {
				e->filename =
				    strdup(listed_entries[i].exec);
			} else {
				e->filename =
				    strdup(listed_entries[i].name);
			}
		}

		e->status = data->status;

		if (listed_entries[i].mine) {
			e->status ^= (e->status & MENU_STATUS_READONLY);
			e->status |= MENU_STATUS_ADD;
			e->status |= MENU_STATUS_EDIT;
			e->status |= MENU_STATUS_DELETE;
		} else if (listed_entries[i].readonly) {
			e->status ^= (e->status & MENU_STATUS_ADD);
			e->status ^= (e->status & MENU_STATUS_EDIT);
			e->status ^= (e->status & MENU_STATUS_DELETE);
			e->status |= MENU_STATUS_READONLY;
		}

		if (!(data->status & MENU_STATUS_BARINFO))
			menuview_listed_mungetitle(i);
		e->title = strdup(listed_entries[i].name);
		first = 1;
	}

	for (i = 0; i < dentries; i++) {    /* put submenus into menu */

		if ((listed_entries[i].type != MENU_ENTRY_LISTED)	/* !submenu */
		    &&(listed_entries[i].type != MENU_ENTRY_MENU))
			continue;

		if (first) {		    /* first entry - add blank line */
			menuview_listed_addblank(menu);
			first = 0;
		}

		if (menu_addentry(menu)) {  /* extend array */
			menuview_listed__free_entries();
			free(menu_path);
			menu_free(menu);
			return (0);
		}

		e = &(menu->menu[menu->num_entries - 1]);

		e->key = listed_entries[i].key;
		e->type = listed_entries[i].type;
		e->status = data->status;

		strncpy(buf, menu_path, 1000);
		strcat(buf, "/");
		strncat(buf, listed_entries[i].name, 1000 - strlen(buf));

		e->filename = strdup(ldb_filename(buf));

		if (!(data->status & MENU_STATUS_BARINFO))
			menuview_listed_mungetitle(i);
		e->title = strdup(listed_entries[i].name);
	}

	menu->filename = strdup(menu_path);

	free(menu_path);
	return (menu);
}

/* EOF */
