/***************************************************************************/
/***************************************************************************/
/*                                                                         */
/*   (c) 1995-1999.  The Regents of the University of California.  All     */
/*   rights reserved.                                                      */
/*                                                                         */
/*   This work was produced at the University of California, Lawrence      */
/*   Livermore National Laboratory (UC LLNL) under contract no.            */
/*   W-7405-ENG-48 (Contract 48) between the U.S. Department of Energy     */
/*   (DOE) and The Regents of the University of California (University)    */
/*   for the operation of UC LLNL.  Copyright is reserved to the           */
/*   University for purposes of controlled dissemination,                  */
/*   commercialization through formal licensing, or other disposition      */
/*   under terms of Contract 48; DOE policies, regulations and orders;     */
/*   and U.S. statutes.  The rights of the Federal Government are          */
/*   reserved under Contract 48 subject to the restrictions agreed upon    */
/*   by the DOE and University.                                            */
/*                                                                         */
/*                                                                         */
/*                              DISCLAIMER                                 */
/*                                                                         */
/*   This software was prepared as an account of work sponsored by an      */
/*   agency of the United States Government.  Neither the United States    */
/*   Government nor the University of California nor any of their          */
/*   employees, makes any warranty, express or implied, or assumes any     */
/*   liability or responsibility for the accuracy, completeness, or        */
/*   usefulness of any information, apparatus, product, or process         */
/*   disclosed, or represents that its specific commercial products,       */
/*   process, or service by trade name, trademark, manufacturer, or        */
/*   otherwise, does not necessarily constitute or imply its               */
/*   endorsement, recommendation, or favoring by the United States         */
/*   Government or the University of California. The views and opinions    */
/*   of the authors expressed herein do not necessarily state or reflect   */
/*   those of the United States Government or the University of            */
/*   California, and shall not be used for advertising or product          */
/*   endorsement purposes.                                                 */
/*                                                                         */
/*   Permission to use, copy, modify and distribute this software and its  */
/*   documentation for any non-commercial purpose, without fee, is         */
/*   hereby granted, provided that the above copyright notice and this     */
/*   permission notice appear in all copies of the software and            */
/*   supporting documentation, and that all UC LLNL identification in      */
/*   the user interface remain unchanged.  The title to copyright LLNL     */
/*   XDIR shall at all times remain with The Regents of the University     */
/*   of California and users agree to preserve same. Users seeking the     */
/*   right to make derivative works with LLNL XDIR for commercial          */
/*   purposes may obtain a license from the Lawrence Livermore National    */
/*   Laboratory's Technology Transfer Office, P.O. Box 808, L-795,         */
/*   Livermore, CA 94550.                                                  */
/*                                                                         */
/***************************************************************************/
/***************************************************************************/

#include <Xm/Xm.h>
#include "xdir.h"
#include "list.h"

#define BYTES_PER_KB  1024

#define DIRLIST   0
#define TRUEPATH  1

struct hostuser_st {
	struct hostuser_st *next;
	char *hostname;
	char *username;
};

struct dirlist_part {
	int layout;
	int dotfiles;
	int count;
	char *list;
};

struct mapping_part {
	char *true_path;
};

struct cache_link {
	struct cache_link *prev;
	struct cache_link *next;
	struct hostuser_st *hostuser;
	char *path;
	long len;
	int type;
	union {
		struct dirlist_part d;
		struct mapping_part m;
	} part; 
};

static struct hostuser_st *head_hostuser = NULL;
static struct cache_link *head = NULL;
static struct cache_link *tail = NULL;
static long cache_len = 0;

extern int max_dir_cache_len;
extern struct st_host_info hinfo[];
extern int diagnostics;

void free_cache_entry();
void report_cache_addition();
void report_cache_deletion();
void report_cache_truepath_hit();
void report_cache_dirlist_hit();
void check_max_cache_len();


/*
 * insert_at_cache_head - Insert the cache entry pointed by "ptr" at the
 *                        head of the cache.
 */
void
insert_at_cache_head(ptr)
struct cache_link *ptr;
{
	ptr->next = head;
	ptr->prev = NULL;
	if (head)
		head->prev = ptr;
	head = ptr;
	if (!tail)
		tail = ptr;
}


/*
 * make_room_in_cache - Free up the least recently used cache entries
 *                      to create enough space for a directory list
 *                      "len" bytes in length.  Returns True if successful,
 *                      else False.
 */
make_room_in_cache(len)
long len;
{
	long bytes_available = max_dir_cache_len*BYTES_PER_KB-cache_len;

	/* No hope if entry is bigger than entire cache */
	if (len > max_dir_cache_len*BYTES_PER_KB)
		return False;

	/* Delete least recently used entries until there is enough room */
	while (head && (bytes_available<len)) {
		bytes_available += tail->len;
		free_cache_entry(tail);
	}

	/* Sanity check */
	if (len > bytes_available)
		fatal_error("Bug in make_room_in_cache()");

	/* Looks good */
	return True;
}


/*
 * free_cache_entry - Free the cache entry pointed to by "ptr".  This
 *                    function is a no-op if "ptr" is NULL.
 */
void
free_cache_entry(ptr)
struct cache_link *ptr;
{
	/* No-op if pointer is NULL */
	if (!ptr)
		return;

	/* Debug edit */
	report_cache_deletion(ptr);

	/* Adjust cache len */
	cache_len -= ptr->len;

	/* Free the entry's fields */
	XtFree(ptr->path);
	switch (ptr->type) {
	case DIRLIST:
		XtFree(ptr->part.d.list);
		break;
	case TRUEPATH:
		XtFree(ptr->part.m.true_path);
	}

	/* Now remove the entry from the doubly-linked list */
	if (ptr->prev)
		ptr->prev->next = ptr->next;
	else
		head = ptr->next;
	if (ptr->next)
		ptr->next->prev = ptr->prev;
	else
		tail = ptr->prev;
	XtFree((char *)ptr);
}


/*
 * find_dirlist_in_cache - Return a pointer to the link in the cache
 *                         that contains the directory list that is
 *                         uniquely specified by the arguments.  If
 *                         there is no hit, return NULL.
 */
struct cache_link *
find_dirlist_in_cache(host, path, layout, dotfiles)
int host;
char *path;
int layout;
int dotfiles;
{
	struct hostuser_st *hostuser = head_hostuser;
	struct cache_link *ptr = head;

	/* Find hostname/username pair */
	while (hostuser) {
		if (!strcmp(hinfo[host].hostname, hostuser->hostname) &&
				(!strcmp(hinfo[host].username, hostuser->username)))
			break;
		hostuser = hostuser->next;
	}
	if (hostuser == NULL)
		return NULL;

	/* TREE layout is the same as ICONIC */
	if (layout == TREE)
		layout = ICONIC;

	/* Search the cache for a match */
	while (ptr) {
		if ((ptr->type == DIRLIST) && (hostuser == ptr->hostuser) &&
				!strcmp(path, ptr->path) && (layout == ptr->part.d.layout) &&
				(dotfiles == ptr->part.d.dotfiles))
			return ptr;
		ptr = ptr->next;
	}
	return NULL;
}


/*
 * find_path_in_cache - Return a pointer to the link in the cache
 *                      that contains the directory mapping that is
 *                      uniquely specified by the arguments.  If
 *                      there is no hit, return NULL.
 */
struct cache_link *
find_path_in_cache(host, path, type)
int host;
char *path;
int type;
{
	struct hostuser_st *hostuser = head_hostuser;
	struct cache_link *ptr = head;

	/* Find hostname/username pair */
	while (hostuser) {
		if (!strcmp(hinfo[host].hostname, hostuser->hostname) &&
				(!strcmp(hinfo[host].username, hostuser->username)))
			break;
		hostuser = hostuser->next;
	}
	if (hostuser == NULL)
		return NULL;

	/* Search the cache for an exact match */
	while (ptr) {
		if ((ptr->type == type) && (ptr->hostuser == hostuser) &&
				!strcmp(ptr->path, path))
			return ptr;
		ptr = ptr->next;
	}
	return NULL;
}


/*
 * find_truepath_in_cache - Return a pointer to the link in the cache
 *                          that contains the specified path in the
 *                          "true_path" field.  If there is no hit,
 *                          return NULL.
 */
struct cache_link *
find_truepath_in_cache(host, path)
int host;
char *path;
{
	struct hostuser_st *hostuser = head_hostuser;
	struct cache_link *ptr = head;

	/* Find hostname/username pair */
	while (hostuser) {
		if (!strcmp(hinfo[host].hostname, hostuser->hostname) &&
				(!strcmp(hinfo[host].username, hostuser->username)))
			break;
		hostuser = hostuser->next;
	}
	if (hostuser == NULL)
		return NULL;

	/* Search the cache for an exact match */
	while (ptr) {
		if ((ptr->type == TRUEPATH) && (ptr->hostuser == hostuser) &&
				!strcmp(ptr->part.m.true_path, path))
			return ptr;
		ptr = ptr->next;
	}
	return NULL;
}


/*
 * add_dirlist_to_cache - Add to the cache the directory list "dlist",
 *                        which is uniquely specified by the other
 *                        arguments.
 */
void
add_dirlist_to_cache(host, path, layout, dotfiles, dlist)
int host;
char *path;
int layout;
int dotfiles;
struct sl_struct *dlist;
{
	struct hostuser_st *hostuser = head_hostuser;
	long len;
	long list_len;
	int i;
	int slen;
	struct cache_link *ptr;
	char *p;

	/* Make sure that cache len is consistent with current max len */
	check_max_cache_len();

	/* Find hostname/username pair */
	while (hostuser) {
		if (!strcmp(hinfo[host].hostname, hostuser->hostname) &&
				(!strcmp(hinfo[host].username, hostuser->username)))
			break;
		hostuser = hostuser->next;
	}
	if (hostuser == NULL) {
		hostuser = XtNew(struct hostuser_st);
		hostuser->hostname = XtNewString(hinfo[host].hostname);
		hostuser->username = XtNewString(hinfo[host].username);
		hostuser->next = head_hostuser;
		head_hostuser = hostuser;
	}

	/* If directory is already in the cache, delete it */
	free_cache_entry(find_dirlist_in_cache(host, path, layout, dotfiles));

	/* Determine the length of the directory list (plus overhead) */
	list_len = 0;
	for (i=0; i<dlist->nentries; i++)
		list_len += strlen(dlist->entries[i]);
	list_len += dlist->nentries;
	len = list_len+strlen(path)+sizeof(struct cache_link);

	/* Make room for cache entry */
	if (!make_room_in_cache(len))
		return;

	/* Create and initialize the cache entry */
	ptr = XtNew(struct cache_link);
	ptr->type = DIRLIST;
	ptr->hostuser = hostuser;
	ptr->path = XtNewString(path);
	if (layout == TREE)
		ptr->part.d.layout = ICONIC;
	else
		ptr->part.d.layout = layout;
	ptr->part.d.dotfiles = dotfiles;
	ptr->len = len;
	ptr->part.d.count = dlist->nentries;
	ptr->part.d.list = XtMalloc(len);

	/* Now copy the actual directory entries */
	p = ptr->part.d.list;
	for (i=0; i<dlist->nentries; i++) {
		slen = strlen(dlist->entries[i]);
		bcopy(dlist->entries[i], p, slen+1);
		p += slen+1;
	}

	/* Insert the entry at the head of the the cache */
	insert_at_cache_head(ptr);
	cache_len += ptr->len;
	report_cache_addition();
}


/*
 * add_truepath_to_cache - Add to the cache the directory mapping specified
 *                         by the arguments.
 */
void
add_truepath_to_cache(host, path, true_path)
int host;
char *path;
char *true_path;
{
	struct hostuser_st *hostuser = head_hostuser;
	long len;
	struct cache_link *ptr;

	/* Make sure that cache len is consistent with current max len */
	check_max_cache_len();

	/* Find hostname/username pair */
	while (hostuser) {
		if (!strcmp(hinfo[host].hostname, hostuser->hostname) &&
				(!strcmp(hinfo[host].username, hostuser->username)))
			break;
		hostuser = hostuser->next;
	}
	if (hostuser == NULL) {
		hostuser = XtNew(struct hostuser_st);
		hostuser->hostname = XtNewString(hinfo[host].hostname);
		hostuser->username = XtNewString(hinfo[host].username);
		hostuser->next = head_hostuser;
		head_hostuser = hostuser;
	}

	/* If a mapping for "path" is already in the cache, delete it */
	ptr = find_path_in_cache(host, path, TRUEPATH);
	free_cache_entry(ptr);

	/* Determine the length of the mapping (plus overhead) */
	len = strlen(path)+strlen(true_path)+sizeof(struct cache_link);

	/* Make room for cache entry */
	if (!make_room_in_cache(len))
		return;

	/* Create and initialize the cache entry */
	ptr = XtNew(struct cache_link);
	ptr->type = TRUEPATH;
	ptr->hostuser = hostuser;
	ptr->path = XtNewString(path);
	ptr->part.m.true_path = XtNewString(true_path);
	ptr->len = len;

	/* Insert the entry at the head of the the cache */
	insert_at_cache_head(ptr);
	cache_len += ptr->len;
	report_cache_addition();
}


/*
 * fetch_dirlist_from_cache - If the directory list uniquely specified
 *                            by the input arguments exists in the cache,
 *                            then this function sets "dlist" to a pointer
 *                            to a structure that contains that directory
 *                            list.  Returns True if successful, else False.
 */
fetch_dirlist_from_cache(host, path, layout, dotfiles, dlist)
int host;
char *path;
int layout;
int dotfiles;
struct sl_struct **dlist;
{
	struct cache_link *ptr;
	int i;
	int slen;
	char *p;

	/* Make sure that cache len is consistent with current max len */
	check_max_cache_len();

	/* Look for directory in cache */
	if ((ptr = find_dirlist_in_cache(host, path, layout, dotfiles)) == NULL)
		return False;

	/* Move found directory to head of cache */
	if (ptr->prev)
		ptr->prev->next = ptr->next;
	else
		head = ptr->next;
	if (ptr->next)
		ptr->next->prev = ptr->prev;
	else
		tail = ptr->prev;
	insert_at_cache_head(ptr);

	/* Allocate list structure and fill it in */
	*dlist = XtNew(struct sl_struct);
	(*dlist)->nentries = ptr->part.d.count;
	(*dlist)->entries = (char **)XtMalloc(sizeof(char *)*ptr->part.d.count);
	p = ptr->part.d.list;
	for (i=0; i<ptr->part.d.count; i++) {
		slen = strlen(p)+1;
		(*dlist)->entries[i] = XtMalloc(slen);
		bcopy(p, (*dlist)->entries[i], slen);
		p += slen;
	}
	report_cache_dirlist_hit();
	return True;
}


/*
 * fetch_truepath_from_cache - If the true path information specified by the
 *                             input arguments exists in the cache, then
 *                             this function sets "true_path" to a pointer
 *                             to a string that contains the true directory
 *                             path for "path".  Returns True if successful,
 *                             else False.
 */
fetch_truepath_from_cache(host, path, true_path)
int host;
char *path;
char **true_path;
{
	struct cache_link *ptr;

	/* Make sure that cache len is consistent with current max len */
	check_max_cache_len();

	/* First look for mapping in cache */
	if ((ptr = find_path_in_cache(host, path, TRUEPATH)) == NULL)
		if ((ptr = find_truepath_in_cache(host, path)) == NULL)
			return False;

	/* Move found mapping to head of cache */
	if (ptr->prev)
		ptr->prev->next = ptr->next;
	else
		head = ptr->next;
	if (ptr->next)
		ptr->next->prev = ptr->prev;
	else
		tail = ptr->prev;
	insert_at_cache_head(ptr);

	/* Return true path */
	*true_path = XtNewString(ptr->part.m.true_path);
	report_cache_truepath_hit();
	return True;
}


/*
 * report_cache_use - Let's user know if cache information was used to
 *                    update "dirwin".  Set "used_cache" to True for
 *                    message to be displayed.  Set "used_cache" to
 *                    False for no message.
 */
void
report_cache_use(dirwin, used_cache)
struct dirwin_st *dirwin;
int used_cache;
{
	if (used_cache)
		XtManageChild(dirwin->w_cacheMsg);
	else
		XtUnmanageChild(dirwin->w_cacheMsg);
}


/*
 * report_cache_addition - Debug edit
 */
void
report_cache_addition()
{
	char msg[100];

	if (diagnostics == DEBUG) {
		sprintf(msg, "*** Entry added to cache.  Cache len = %ld\n", cache_len);
		write_log(msg);
	}
}


/*
 * report_cache_deletion - Debug edit
 */
void
report_cache_deletion(ptr)
struct cache_link *ptr;
{
	char msg[200];

	if (diagnostics == DEBUG) {
		if (ptr->type == DIRLIST) {
			sprintf(msg,
				"*** Removed directory list from cache.  Cache len = %ld\n",
				cache_len);
			write_log(msg);
			sprintf(msg, "          %s", ptr->path);
			write_log(msg);
			switch (ptr->part.d.layout) {
			case TABULAR:
				write_log(", Tabular");
				break;
			case ICONIC:
			case TREE:
				write_log(", Iconic/Tree");
				break;
			case FULL_INFO:
				write_log(", Full Info");
			}
			if (ptr->part.d.dotfiles)
				write_log(", Dotfiles");
			write_log("\n");
		} else {
			sprintf(msg,
				"*** Removed true path from cache.  Cache len = %ld\n",
				cache_len);
			write_log(msg);
			sprintf(msg, "          %s\n", ptr->path);
			write_log(msg);
		}
	}
}


/*
 * report_cache_truepath_hit - Debug edit
 */
void
report_cache_truepath_hit()
{

	if (diagnostics == DEBUG)
		write_log("*** True path cache hit\n");
}


/*
 * report_cache_dirlist_hit - Debug edit
 */
void
report_cache_dirlist_hit()
{
	if (diagnostics == DEBUG)
		write_log("*** Directory list cache hit\n");
}


/*
 * check_max_cache_len - If cache length exceeds the maximum allowable (this
 *                       could happen if the user changes this limit), then
 *                       free up as many of the least recently used cache
 *                       entries as necessary.
 */
void
check_max_cache_len()
{
	long bytes_available = max_dir_cache_len*BYTES_PER_KB-cache_len;

	/* Delete least recently used entries until cache len is small enough */
	while (head && (bytes_available<0)) {
		bytes_available += tail->len;
		free_cache_entry(tail);
	}

	/* Sanity check */
	if (bytes_available < 0)
		fatal_error("Bug in check_max_cache_len()");
}


/*
 * remove_dirlist_from_cache - Remove the cache entry that contains the
 *                             directory list that is  uniquely specified
 *                             by the arguments.
 */
void
remove_dirlist_from_cache(host, path, layout, dotfiles)
int host;
char *path;
int layout;
int dotfiles;
{
	free_cache_entry(find_dirlist_in_cache(host, path, layout, dotfiles));
}


/*
 * flush_cache_all - Removes all entries from the cache.
 */
void
flush_cache_all()
{
	while (head)
		free_cache_entry(head);
}


/*
 * flush_cache_directory - Remove all cache entries for the directory
 *                         specified by "host" and "path".
 */
void
flush_cache_directory(host, path)
int host;
char *path;
{
	struct hostuser_st *hostuser = head_hostuser;
	struct cache_link *ptr = head;
	struct cache_link *tptr;

	/* Find hostname/username pair */
	while (hostuser) {
		if (!strcmp(hinfo[host].hostname, hostuser->hostname) &&
				(!strcmp(hinfo[host].username, hostuser->username)))
			break;
		hostuser = hostuser->next;
	}
	if (hostuser == NULL)
		return;

	/* Now remove cache entries */
	while (ptr) {
		tptr = ptr;
		ptr = ptr->next;
		if ((hostuser == tptr->hostuser) && !strcmp(path, tptr->path))
			free_cache_entry(tptr);
	}
}



syntax highlighted by Code2HTML, v. 0.9.1