/* TABMAINT - simple maintenance for shsql tables.  Does the following:
	* removes blank lines that normally accumulate as the result of updates and deletes
	* any index files associated with the table are rebuilt
	Each tabmaint invocation works with one table.  All work is done on a copy of the 
	data file.  In normal mode a write lock is held for the duration of this process, and a 
	read lock is held only during the time that the temp file is copied over.  In -n mode
	the write lock is acquired and released by the caller, but tabmaint still does a read-lock
	when temp file is copied over.

	Options: -n altname = operate on a temp file called 'altname' in the data directory.
			      This implies that a maintenance operation such as alter or
			      dataedit is in progress.
				Write-lock on original table assumed to already be in place.
				Packing is not done.  tabmaint handles read-lock.
				Write-lock should be released by invoker after tabmaint exits.

 */
#include "tdhkit.h"
#include "shsql.h"

#define USAGE_MSG 	"usage: tabmaint table [-n altname]"
#define LS_COM 		"/bin/ls"
#define BUILDIX_COM 	"buildix"
#define LEVEL3_THRESH	8000

extern int GL_make_unique_string();
extern int unlink();

int
main( argc, argv )
int argc;
char **argv;
{
int i, stat;
char tablename[MAXPATH];
char buf[1024], tok[DATAMAXLEN+1];
char infile[MAXPATH], outfile[MAXPATH], zerofile[MAXPATH];
char fakename[MAXPATH];
int level, nrec, nremoved;
char ixfile[MAXPATH];
FILE *fp, *ofp, *xfp;
int len;
char ixtypestr[255];
long eof;
char orderstr[40];
int numflag;
char uniq[80];
char altname[80];
int n_opt;

TDH_errprog( "tabmaint" );

if( argc < 2 || argc > 4 ) {
	fprintf( stderr, "%s\n", USAGE_MSG );
	exit( 1 );
	}

/* process config file.. */
stat = SHSQL_allconfig();
if( stat != 0 ) exit( 1 );


strcpy( altname, "" );
n_opt = 0;
for( i = 1; i < argc; i++ ) {
	if( strcmp( argv[i], "-n" )==0 && i < argc-1 ) {
		n_opt = 1;
		i++;
		strcpy( altname, argv[i] );
		}
	else strcpy( tablename, argv[i] );
	}


if( GL_member( tablename[0], "./$" )) {
	fprintf( stderr, "tabmaint error: may only be used with database table files (%s)\n", tablename );
	exit( 1 );
	}

/* lock data file against writing.. */
if( !n_opt ) {
	stat = SHSQL_lock( tablename );
	if( stat != 0 ) {
		fprintf( stderr, "tabmaint error: table %s is currently write-locked by someone else.. cannot proceed\n", tablename );
		exit( 1 );
		}
	SHSQL_err( 400, "Note: table maintance starting.", tablename );
	}

if( n_opt ) {
	/* infile and outfile are same, since we don't do the pack operation.. */
	sprintf( infile, "%s/data/%s", SHSQL_projdir, altname );
	sprintf( outfile, "%s/data/%s", SHSQL_projdir, altname );
	}
else	{
	GL_make_unique_string( uniq, 0 );
	sprintf( altname, "__tmpmaint_%s", uniq );
	sprintf( infile, "%s/data/%s", SHSQL_projdir, tablename );
	sprintf( outfile, "%s/data/%s", SHSQL_projdir, altname );
	ofp = fopen( outfile, "w" );
	if( ofp == NULL ) { fprintf( stderr, "tabmaint error: Cannot open output file (%s)\n", outfile ); exit(1); }
	}

fp = fopen( infile, "r" );
if( fp == NULL ) { fprintf( stderr, "tabmaint error: Cannot open data file (%s)\n", infile ); exit(1); }


/* count records, remove blank lines from data file (unless skipping the pack step), and place result in temp file.. */
nrec = 0; nremoved = 0;
while( fgets( buf, MAXRECORDLEN-1, fp ) != NULL ) {
	if( !n_opt ) {
		if( buf[0] == SHSQL_delim ) { nremoved++; continue; }			/* datadelim */
		fprintf( ofp, "%s", buf );
		}
	nrec++; 
	}
fclose( fp );
if( !n_opt ) fclose( ofp );

if( nremoved > 0 ) fprintf( stderr, "..table file has been cleaned (%d records removed).. now has %d rows\n", nremoved, nrec );


/* at this point, result is in 'outfile' */




/* refresh the table.0 file.. */
strcpy( fakename, tablename );
for( i = 0; fakename[i] != '\0'; i++ ) if( fakename[i] == '/' ) fakename[i] = '!';
/* sprintf( buf, "(cd %s/indexes; %s %s*.2 > %s.0 2>/dev/null)", SHSQL_projdir, LS_COM, fakename, fakename ); */
sprintf( buf, "(cd %s/indexes; %s %s.*.2 > %s.0 2>/dev/null)", SHSQL_projdir, LS_COM, fakename, fakename );
system( buf );

/* if the .0 file is empty, remove it.. */
sprintf( buf, "%s/indexes/%s.0", SHSQL_projdir, fakename );
fp = fopen( buf, "r" );
if( fscanf( fp, "%s", tok ) < 1 ) {
	fclose( fp );
	unlink( buf );
	}
else fclose( fp );


if( nrec > LEVEL3_THRESH ) level = 3;
else level = 2;


/* for every entry in the table.0 file, run buildix.. */
/* The .0 file is built, just above, based on presence of existing .2 files.. */
sprintf( zerofile, "%s/indexes/%s.0", SHSQL_projdir, fakename );
fp = fopen( zerofile, "r" );

/* if there are no index files, we are done.. move temp file back into place.. */
/* if( fp == NULL ) exit( 0 ); */
if (fp == NULL) {
  /* set readlock while moving temp files into place.. */
  fprintf(stderr, "moving temp table into place...\n");
  stat = SHSQL_readlock( tablename, 1 );
  if( stat != 0 ) { SHSQL_unlock(); exit( 1 ); }
  sprintf( buf, "(cd %s/data; mv %s %s)", SHSQL_projdir, altname, tablename );
  system( buf );
  SHSQL_readlock( tablename, 0 );
  exit(0);
}


if( SHSQL_bin[0] != '\0' ) sprintf( tok, "%s/buildix", SHSQL_bin );
else strcpy( tok, "buildix" ); /* path lookup */

/* mark end of file since it can grow as a result of combinedword index.. */
fseek( fp, 0L, SEEK_END ); eof = ftell( fp ); fseek( fp, 0L, SEEK_SET ); /* restore.. */

while( fgets( ixfile, 255, fp ) != NULL ) {
	if( ftell( fp ) > eof ) break;
	len = strlen( ixfile );
	ixfile[ len-1 ] = '\0'; /* strip off newline */

	sprintf( buf, "%s/indexes/%s", SHSQL_projdir, ixfile );
	xfp = fopen( buf, "r" );
	if( xfp == NULL ) { fprintf( stderr, "cannot open %s to get index type\n", buf ); continue; }
	fgets( buf, 512, xfp );
	sscanf( buf, "%*s %*s %s %*s %s", ixtypestr, orderstr );
	if( strnicmp( orderstr, "num", 3 )==0 ) numflag = 1;
	else numflag = 0;
	fclose( xfp );

	ixfile[ len-2 ] = '\0'; /* strip off .2 */
	
	for( i = 0; ixfile[i] != '\0'; i++ ) if( ixfile[i] == '.' ) ixfile[i] = BLANK;

	fprintf( stderr, "..building index on %s..  ", ixfile );  fflush( stderr );
	sprintf( buf, "%s %s %d %s %s -n %s", tok, ixfile, level, ixtypestr, (numflag)?"-t num":"", altname );
	/* fprintf( stderr, "tabmaint is invoking: %s\n", buf ); */
	system( buf );
	fprintf( stderr, "\n" );
	}

/* at this point we have new data file and new index files, all using altname */

/* set readlock while moving temp files into place.. */
stat = SHSQL_readlock( tablename, 1 );
if( stat != 0 ) { SHSQL_unlock(); exit( 1 ); }


sprintf( buf, "(cd %s/data; mv %s %s)", SHSQL_projdir, altname, tablename );
system( buf );

/* now go thru the list again, renaming files.. */
rewind( fp );
while( fgets( ixfile, 255, fp ) != NULL ) {
	if( ftell( fp ) > eof ) break;
	for( i = 0; ixfile[i] != '\0'; i++ ) if( ixfile[i] == '.' ) ixfile[i] = BLANK;
	sscanf( ixfile, "%*s %s", tok );
	for( i = 1; i <= 3; i++ ) {
		sprintf( infile, "%s/indexes/%s.%s.%d", SHSQL_projdir, altname, tok, i );
		sprintf( outfile, "%s/indexes/%s.%s.%d", SHSQL_projdir, fakename, tok, i );
		rename( infile, outfile );
		}
	}
fclose( fp );
	
/* release locks */
SHSQL_readlock( tablename, 0 );

if( !n_opt ) {
	SHSQL_unlock( );
	SHSQL_err( 401, "Note: table maintenance finished.", tablename );
	}
exit( 0 );
}

