// Filename:   fileutil.C
// Contents:   a files area utility program
// Author: Greg Shaw
// Created:    8/24/93

/*
This file is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

In addition to the permissions in the GNU General Public License, the
Free Software Foundation gives you unlimited permission to link the
compiled version of this file with other programs, and to distribute
those programs without any restriction coming from the use of this
file.  (The General Public License restrictions do apply in other
respects; for example, they cover modification of the file, and
distribution when not linked into another program.)

This file is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#ifndef _FILEUTIL_C_
#define _FILEUTIL_C_

#include "bbshdr.h"
#include "fileutil.h"

#undef DEBUG

User user;                      // defined here for extern references elsewhere
moncon mon_obj;                 // monitor connection object (not used)
Chat chatobj;			// chat connections (not used)

// Method: constructor
// Purpose:    initialize the list
// Input:  none
// Output: none
// Author: Greg Shaw
// Created:    8/24/93

fileutil::fileutil()
{
	numrecs = 0;
};


// Method: delete_missing
// Purpose:    scan all files sections to delete all missing files
// Input:  sname - long name of file section
// Output: stdout - list of deleted files
// Author: Greg Shaw
// Created:    9/2/93

int fileutil::delete_missing(char *sname,char *secname)
{

	typedef struct f_list
	{
		char *name;
		struct f_list *next;
	} flist;

	FInfo  *rec;
	int    deleted;
	int    offset;
	FILE   *recfile;
	FILE   *outfile;
	char   fname[MAX_FILENAMELENGTH+1];
	char   *bbsdir;
	char   tmpstr[255];
	char   c;
	flist  *filelist;           // list of filenames already found
	flist  *tmp;
	char   line[150];

	filelist = NULL;
	deleted = 0;
	bbsdir = getenv("BBSDIR");
	if (bbsdir == NULL)
	{
		printf("Unable to get BBSDIR environment variable.");
		exit(0);
	}
	sprintf(tmpstr,"%s/filehdr/%s",bbsdir,secname);
	if (recfile = bopen(tmpstr,"r"), recfile == NULL)
	{
		sprintf(tmpstr,"fileutil: Unable to open %s",secname);
		ap_log(tmpstr);
		fprintf(stderr,"%s\n",tmpstr);
		return(-1);
	}
	sprintf(tmpstr,"%s/filehdr/%s.new",bbsdir,secname);
	if (outfile = bopen(tmpstr,"w"), outfile == NULL)
	{
		sprintf(tmpstr,"fileutil: Unable to open %s.new",secname);
		ap_log(tmpstr);
		fprintf(stderr,"%s\n",tmpstr);
		return(-1);
	}
	while (!feof(recfile))
	{
		offset = 0;
		while (c = fgetc(recfile), c != '\n' && c != '\r' && !feof(recfile))
			line[offset++] = c;
		line[offset] = 0;
		if (line[0] == '[' && line[1] == 'A')
		{
			sscanf(&line[2],"%*s %*d %s",fname);
			tmp = filelist;
			while (tmp != NULL && strcmp(tmp->name,fname) != 0)
				tmp = tmp->next;
			// duplicate?
			if (tmp != NULL || (rec = list_obj.find(fname), rec != NULL && (rec->avail != 'Y' || rec->size == 0)))
			{
				if (tmp != NULL)
				{
					sprintf(tmpstr,"duplicate found: %s",fname);
					ap_log(tmpstr);
				}
				// delete next 5 more lines
				deleted++;
				offset = 0;
				while (offset < 5 && !feof(recfile))
				{
					while (c = fgetc(recfile), c != '\n' && c != '\r' && !feof(recfile));
					offset++;
				}
			}
			else
			{
				// save in new record
				if (tmp = (flist *)malloc(sizeof(flist)), tmp == NULL)
				{
					sprintf(tmpstr,"fileutil: unable to malloc temp record.");
					ap_log(tmpstr);
					exit(0);
				}
				tmp->name = (char *) malloc(strlen(fname)+1);
				strcpy(tmp->name,fname);
				tmp->next = filelist;
				filelist = tmp;
				fprintf(outfile,"%s\n",line);
			}
		}
		else
			fprintf(outfile,"%s\n",line);
	}
	bclose(outfile);
	bclose(recfile);
	if (deleted)                // some deleted?
	{                           // now move things back to where they should be
		sprintf(tmpstr,"%s/filehdr/%s",bbsdir,secname);
		sprintf(line,"%s/filehdr/%s.old",bbsdir,secname);
		rename(tmpstr,line);
		sprintf(line,"%s/filehdr/%s.new",bbsdir,secname);
		sprintf(tmpstr,"%s/filehdr/%s",bbsdir,secname);
		rename(line,tmpstr);
	} else
	{
		sprintf(tmpstr,"%s/filehdr/%s.new",bbsdir,secname);
		remove(tmpstr);
	}
	printf("%-25.25s %6d\n",sname,deleted);
	// now delete the storage used for filenames
	while (filelist != NULL)
	{
		tmp = filelist;
		filelist = filelist->next;
		free(tmp->name);
		free(tmp);
	}
	return(0);
};


// Method: newfiles
// Purpose:    scan all files sections to build a 'new files' list.
// Input:  section - name of section to build the 'new' list for
//     fname - the file name (+path) for the output file
// Output: a file that contains the new files list
// Author: Greg Shaw
// Created:    8/24/93

int fileutil::newfiles(char *section, FILE *outfile)
{
	FInfo  *rec;
	char   datestr[12];
	struct tm *tmrec;
	time_t now;

	time(&now);
	list_obj.top();             // top of list
	while (rec = list_obj.next(), rec != NULL)
	{
		if (now - rec->date < (DAYS*24*60*60))
		{
			tmrec = localtime(&rec->date);
			strftime(datestr,11,"%x",tmrec);

			fprintf(outfile,"%-14.14s %-25.25s %s %-8.8s %2d\n",rec->name,
			section,datestr,rec->uploader,rec->numdls);
		}
	}
	return(0);
};


// Method: most
// Purpose:    scan the files section for the most popular files.  Compare
//     against existing list and add as appropriate
// Input:  section - name of section to build the 'most' list for
//     fname - the file name (+path) for the output file
// Output: a file that contains the most popular files
// Author: Greg Shaw
// Created:    8/24/93

int fileutil::most(char *section)
{
	FInfo  *rec;
	int    num;
	int    x;
	struct stat fistat;         // file status record
	char   tmpstr[255];
	char   bbsdir[MAX_FILENAMELENGTH+1];

	// not checking error
	strcpy(bbsdir,getenv("BBSDIR"));
	list_obj.top();             // top of list
	while (rec = list_obj.next(), rec != NULL)
	{
		sprintf(tmpstr,"%s/files/%s/%s",bbsdir,dn_path,rec->name);
		if (stat(tmpstr,&fistat) == 0 && S_ISREG (fistat.st_mode))
		{
			rec->size = fistat.st_size;
			rec->date = fistat.st_ctime;
			rec->avail = 'Y';
		}
		if (rec->numdls > 0)
		{
			num = -1;
			for (x=0; x<numrecs; x++)
			{
				if (rec->numdls > recs[x].numdls)
				{
					num = x;    // found where we need to put guy
					x = numrecs;
				}
			}
			if (num != -1 )     // found where we need to put him
			{
				// move everybody down
				for (x=numrecs-2; x >= num; --x)
				{
					memcpy(&recs[x+1], &recs[x], sizeof(FInfo));
					strcpy(sec[x+1], sec[x]);
				}
				memcpy(&recs[num], rec, sizeof(FInfo));
				strcpy(sec[num], section);

			}
			// hit end before found lesser one
			else if (x < MAX_RECS)
			{
				memcpy(&recs[numrecs],rec,sizeof(FInfo));
				strcpy(sec[numrecs], section);
				numrecs++;
			}
		}
	}
	return(0);
};


// Method: update
// Purpose:    update the section passed in
//         - check for new files added to directory
//         - delete files with zero length in description
// Input:  section - name of section to update
// Output: a file that contains the most popular files
// Author: Greg Shaw
// Created:    8/24/93

int fileutil::update(char *section)
{
	FILE   *outfile;            // output file
	DIR    *fdir;               // directory file descriptor
	FInfo  *rec;
	struct dirent *dentry;      // directory entry
	struct stat fistat;         // file status record
	time_t now;                 // date of file added (today)
	char   bbsdir[255];         // bbs directory
	char   tmpstr[255];         // tmpstr
	int    newfiles;            // new files in section
	int    missing;             // missing files in section
	int    deleted;             // missing files in section
	int    totfiles;            // total files in section
	unsigned long totsize;      // total size of section

	time(&now);
	newfiles = deleted = missing = totfiles = 0;
	totsize = 0;
	// not checking error
	strcpy(bbsdir,getenv("BBSDIR"));
	sprintf(tmpstr,"%s/filehdr/%s",bbsdir,section);
	if (outfile = bopen(tmpstr,"a"), outfile == NULL)
	{
		printf("fileutil: Unable to open files section header %s",section);
		return(0);
	}
	strcpy(tmpstr,bbsdir);
	sprintf(tmpstr,"%s/files/%s",bbsdir,dn_path);
	if (fdir = opendir(tmpstr), fdir == NULL)
	{
		printf("fileutil: Unable to open directory %s for reading.\n",tmpstr);
		bclose(outfile);
		exit(0);
	}
	// ok.  output file is open. directory is open.  doit.
	while (dentry = readdir(fdir), dentry != NULL)
	{
		sprintf(tmpstr,"%s/files/%s/%s",bbsdir,dn_path,dentry->d_name);
		if (stat(tmpstr,&fistat) == 0 && S_ISREG(fistat.st_mode))
		{                       // not found, add to area
			if (rec = list_obj.find(dentry->d_name), rec == NULL)
			{
				#ifdef DEBUG
				printf("file not found\n");
				#endif
				newfiles++;
				totsize += fistat.st_size/1024;
				fprintf(outfile,"[A sysop 0 %s ]\n",dentry->d_name);
				fprintf(outfile,"[B ]\n");
				fprintf(outfile,"[C ]\n");
				fprintf(outfile,"[D ]\n");
				fprintf(outfile,"[E ]\n");
				fprintf(outfile,"[F ]\n");
			}
			#ifdef DEBUG
			else
				printf("file found\n");
			#endif
		}
	}
	closedir(fdir);
	bclose(outfile);
	// Ok.  Got new files added to section.  Now delete no-good files
	list_obj.top();             // start at top
	while (rec = list_obj.next(), rec != NULL)
	{
		// file size 0?  Kill!
		if (rec != NULL && rec->size == 0)
		{
			deleted++;
			sprintf(tmpstr,"%s/files/%s/%s",bbsdir,dn_path,rec->name);
			remove(tmpstr);
		}
	}
	// now go through section file to delete record descriptions
	// now check for missing files
	list_obj.top();             // start at top
	while (rec = list_obj.next(), rec != NULL)
	{
		sprintf(tmpstr,"%s/files/%s/%s",bbsdir,dn_path,rec->name);
		if (stat(tmpstr,&fistat) == 0 && S_ISREG (fistat.st_mode))
		{
			rec->size = fistat.st_size;
			rec->date = fistat.st_ctime;
			rec->avail = 'Y';
			totsize += rec->size/1024;
			totfiles++;
		}
	}
	// now add statistics to file
	totfiles += newfiles;
	totfiles -= deleted;
	printf("%-25.25s %6ld    %5d    %3d    %3d    %3d\n",section,totsize,
	totfiles,missing,newfiles,deleted);
	sprintf(tmpstr,"%s/filehdr/%s",bbsdir,section);
	chmod(tmpstr,0775);
	return(0);
};


// Method: cycle_sections
// Purpose:    scan all files sections and do a command on each in turn
// Input:  fname - the output filename
// Output: a file that contains the
// Author: Greg Shaw
// Created:    8/24/93

int fileutil::cycle_sections(int type, char *fname)
{
	CardRec    user;            // dummy for files object 'open'
	FILE   *infile;             // for reading bbs files master header
	FILE   *outfile = NULL;     // output text file for some functions
	time_t now;                 // current time
	int    x;
	char   ftype;               // type of files section
	char   c;                   // char
	char   *u;                  // simple char pointer
	char   *bbsdir;             // home of BBS
	char   word[25];            // word
	char   datestr[12];         // date string storage
	char   tmpstr[255];         // temp str
	// section name
	char   name[MAX_FILENAMELENGTH];
	char   dn_path[255];        // download path
	char   long_desc[255];      // long description
	struct tm  *tmrec;

	bbsdir = getenv("BBSDIR");
	if (bbsdir == NULL)
	{
		printf("Unable to get BBSDIR environment variable.");
		exit(0);
	}
	list_obj.clear_list();      // nuke old values
	// tack on files header
	sprintf(tmpstr,"%s/filehdr/bbs_files_hdr",bbsdir);
	if (infile = bopen(tmpstr,"r"), infile == NULL)
	{
		sprintf(tmpstr,"fileutil: Unable to open main files section header (bbs_files_hdr)");
		ap_log(tmpstr);
		return(-1);
	}
	// ok.  got file.  let's loop through list for each section
	if ((type == 0 || type == 1) && (outfile = bopen(fname,"w"), outfile == NULL))
	{
		sprintf(tmpstr,"fileutil: Unable to open output file %s",fname);
		ap_log(tmpstr);
		return(-1);
	}
	switch(type)
	{
		case 0:                 // new files
			time(&now);
			tmrec = localtime(&now);
			strftime(datestr,11,"%x",tmrec);
			fprintf(outfile,"New files in the last %d days.  (%s)\n",DAYS,datestr);
			fprintf(outfile,"Name           Section                     Date   by    Downloads\n");
			break;
		case 1:                 // most popular files
			time(&now);
			tmrec = localtime(&now);
			strftime(datestr,11,"%x",tmrec);
			fprintf(outfile,"Most popular files on BBS by downloads: (%s)\n",datestr);
			fprintf(outfile,"Name            Section                  Dls  Date   Uploaded by\n");
			break;
		case 2:                 // update sections
			time(&now);
			tmrec = localtime(&now);
			strftime(datestr,11,"%x",tmrec);
			printf("SysOp Report for BBS: (%s)\n",datestr);
			printf("Section                     Size      Files  Missing New  Deleted\n");
			break;
		case 3:                 // delete missing files from bbs and headers
			time(&now);
			tmrec = localtime(&now);
			strftime(datestr,11,"%x",tmrec);
			printf("Deleted Files Report for BBS: (%s)\n",datestr);
			printf("Section                     Deleted\n");
			break;
	}
	while (!feof(infile))
	{
		// look for left bracket
		while (c = fgetc(infile), c != '[' && !feof(infile));
		// now get the rest of the line
		if (feof(infile))
			continue;
		if (x = fscanf(infile,"%s %c %*s %*s %*d %s %*s %*d%50s",name,&ftype,dn_path, long_desc), x != 4)
		{
			sprintf(tmpstr,"Error in bbs main files header (%s: Expected 4, got %d).",name,x);
			ap_log(tmpstr);
			return(-1);
		}
		if (ftype == 'C')
		{
			//         printf("CD-ROM filesection %s skipped.\n",name);
		}
		else if (ftype == 'R')
		{
			while (fscanf(infile,"%s",word) == 1 && strchr(word,']') == NULL)
			{
				strcat(long_desc," ");
				strcat(long_desc,word);
			}
			if (u = strchr(long_desc,']'), u != NULL)
				u[0] = 0;       // turn into null
			// open section
			if (open(name,&user) != 0)
			{
				sprintf(tmpstr,"fileutil: error opening %s",name);
				ap_log(tmpstr);
				bclose(infile);
				bclose(outfile);
				return(-1);
			}
			// now pass to appropriate function
			switch(type)
			{
				case 0:         // generate new files list
					newfiles(long_desc,outfile);
					break;
				case 1:         // generate most popular files list
					most(long_desc);
					break;
				case 2:         // update files section
					update(name);
					break;
				case 3:         // delete missing files
					delete_missing(long_desc,name);
					break;
				default:
					return(0);
			}
		}
		else                    // unknown filesection type
		{
			sprintf(tmpstr,"fileutil: unknown filesection type %c for %s",ftype,name);
			ap_log(tmpstr);
			return(1);
		}
	}
	if (type == 1)              // most popular files?
	{                           // then generate list from 20 files
		for (x=0; x<numrecs; x++)
		{
			tmrec = localtime(&recs[x].date);
			strftime(datestr,11,"%x",tmrec);
			fprintf(outfile,"%-14.14s   %-25.25s %d %s %-8.8s\n",
			recs[x].name,sec[x],recs[x].numdls,
			datestr, recs[x].uploader);
		}
	}
	bclose(infile);
	if (type == 0 || type == 1)
		bclose(outfile);
	return(0);
};


// Function:   usage
// Purpose:    give the user some clue as to how to execute program
// Input:  none
// Output: a (hopefully) useful display of what the program should do
// Author: Greg Shaw
// Created:    8/25/93

void usage(void)
{
	printf("Usage: fileutil -c section_name\n");
	printf("       fileutil -n output_filename\n");
	printf("       fileutil -m output_filename\n");
	printf("       fileutil -u\n");
	printf("       fileutil -d\n");
};


main(int argc,char *argv[])
{
	fileutil fileobj;           // files object for utilities
	char   c;                   // character sent back by getopt
	CardRec    user;            // dummy for files object 'open'



	// all options are mutually exclusive
	c = getopt(argc,argv,"cnmud");
	// so one only should be present
	if (c == -1)
		usage();
	else
	{
		switch(c)
		{
			case 'c':           // create section from directory
				// NOTE: this is the most dangerous one
				// should only be done ONCE
				if (argc != 3)
				{
					usage();
					return(0);
				}
				if (fileobj.open(argv[2],&user) != -1)
				{
					fileobj.create();
				}
				break;
			case 'd':           // delete missing files
				if (argc != 2)
				{
					usage();
					return(0);
				}
				// most downloaded
				fileobj.cycle_sections(3,argv[2]);
				break;
			case 'm':           // create list of most popular files
				if (argc != 3)
				{
					usage();
					return(0);
				}
				// most downloaded
				fileobj.cycle_sections(1,argv[2]);
				break;
			case 'n':           // create list of new files
				if (argc != 3)
				{
					usage();
					return(0);
				}
				// new files
				fileobj.cycle_sections(0,argv[2]);
				break;
			case 'u':           // update sections for new files without overwrite
				if (argc != 2)
				{
					usage();
					return(0);
				}
				// update
				fileobj.cycle_sections(2,NULL);
				break;
			default:
				usage();
		}
	}
	return(0);
};


#endif                          // _FILEUTIL_C_






