/* RECORDLOCK - implementation of record locking */


#include "tdhkit.h"
#include "shsql.h"

#define WANTS_LOCK 0
#define WANTS_TO_UPDATE 1

int GL_encode(), GL_decode();

static char Table[ MAXPATH ];
static long Physloc[ MAXRECORDLOCK ];
static int Nrecs = 0, Currec = 0;
static FILE *Tablefp;
static char *Identity = NULL;

static int generate_locktime();
static int getjuldate();
static int Dmdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };


/* ================================= */
/* SFU_INIT - inform us that we are starting a new set.  Also indicate the table. */
int
SHSQL_sfu_init( table )
char *table;
{
char filename[MAXPATH];

strcpy( Table, table );
Nrecs = 0;
Currec = 0;

/* IDENTITY must have a value - else error */
Identity = TDH_getvarp( "_IDENTITY" );
if( Identity == NULL )
	return( SHSQL_err( 211, "sql select for update: session has no identity defined", "" ));
if( Identity[0] == '\0' )
	return( SHSQL_err( 211, "sql select for update: session has no identity defined", "" ));

sprintf( filename, "%s/data/%s", SHSQL_projdir, Table );
Tablefp = fopen( filename, "r+" );
if( Tablefp == NULL ) 
	return( SHSQL_err( 212, "sql select for update: can't open table for update of _lock* fields", Table ));

return( 0 );
}

/* ================================== */
/* SFU_REC - inform us of the next record in current set that is to be locked. */
int
SHSQL_sfu_rec( physloc )
long physloc;
{
if( Nrecs >= 200 ) return( SHSQL_err( 214, "sql select for update: too many records selected", "" ));
Physloc[ Nrecs++ ] = physloc;
return( 0 );
}

/* ================================== */
/* SFU - (select for update) for the current record, check lock fields to see if 
	record can be locked; if so, lock the record. */
int
SHSQL_sfu( fields, sp )
char *fields[];
struct selectparms *sp;
{
int i, ix;
char buf[ MAXRECORDLEN+1 ];
char data[MAXITEMS][DATAMAXLEN+1];
int ilt, ilo, stat;
int reclen, newlen, nextlen, do_append;
int currec;


/* select results must include _locktime and _lockowner fields */
ilt = ilo = -1;
for( i = 0; i < sp->nitems; i++ ) {
	if( strcmp( sp->itemlist[i], "_locktime" )==0 ) ilt = i;
	else if( strcmp( sp->itemlist[i], "_lockowner" )==0 ) ilo = i;
	}
if( ilt < 0 || ilo < 0 ) 
	return( SHSQL_err( 210, "sql select for update: _locktime and _lockowner fields expected", "" ));

if( Currec >= Nrecs || Currec < 0 ) 
	return( SHSQL_err( 213, "sql select for update: internal error, mismatch request", "" ));

currec = Currec;
Currec++;

stat = SHSQL_check_rlock( fields[ ilt ], fields[ ilo ] );

if( stat == 0 ) {  /*  ok to lock record */

	fseek( Tablefp, Physloc[currec], SEEK_SET );
	fgets( buf, MAXRECORDLEN, Tablefp );
	reclen = strlen( buf );
	
	/* parse into fields - data array.. */
	for( i = 0, ix = 0; i < sp->nfdf; i++ ) SHSQL_getfld( data[i], buf, &ix );
	
	generate_locktime( data[ sp->fldpos[ ilt ]] );
	strcpy( data[ sp->fldpos[ ilo ]], Identity );	/* Identity guaranteed to be present (by check_rlock above) */


	/* ====== now update the data record.. ========= */

	/* build new record (keep track of total length) (data2) */
	for( i = 0, newlen = 0; i < sp->nfdf; i++ ) {
		nextlen =  newlen + strlen( data[i] );
        	if( nextlen >= (MAXRECORDLEN-2) ) return( 215 ); /* SHSQL_err - record will be too long */
        	strcpy( &buf[ newlen ], data[i] );
        	newlen = nextlen;
		strcpy( &buf[ newlen++ ], SHSQL_delims );
		}

	if (newlen >= reclen) do_append = 1;


	if( !do_append ) {
		/* pad remainder of record.. */
		for( i = newlen; i < reclen-1; i++ ) buf[i] = SHSQL_delim;
		buf[i] = '\0';

		/* write out the record content - but not the final newline */
		fseek( Tablefp, Physloc[currec], SEEK_SET ); 
		fputs( buf, Tablefp );
		fflush( Tablefp );
		}

	else	{
		/* append new record */
		SHSQL_translog = 0; /* suppress transaction log here.. */
		stat = SHSQL_newrec( Table, Tablefp, NULL, newlen, buf, 0 );
		SHSQL_translog = 1;
		if( stat != 0 ) return( SHSQL_err( stat, "sql record lock: error on creation of new record", Table ) );

		/* rub out old record */
		for( i = 0; i < reclen-1; i++ ) buf[i] = SHSQL_delim;
		buf[i] = '\0';
		fseek( Tablefp, Physloc[currec], SEEK_SET ); 
		fputs( buf, Tablefp );
		fflush( Tablefp );
		}
	return( 0 );
	} 

else return( SHSQL_RECORDLOCKED );
}

/* ============================== */
/* SFUCLOSE - close the data file after doing record locks */
int
SHSQL_sfu_close()
{
if( Tablefp != NULL ) fclose( Tablefp );
return( 0 );
}

/* ============================== */
/* GENERATE_LOCKTIME - generate timestamp code for now */

static int
generate_locktime( ts )
char *ts;
{

int m, d, y, h, min, s;
GL_sysdate( &m, &d, &y );
/* after the year 2050: y -= 50 */
GL_systime( &h, &min, &s );
sprintf( ts, "%c%c%c%c%c", GL_encode( y ), GL_encode( m ), GL_encode( d ), GL_encode( h ), GL_encode( min ) );
return( 0 );
}

/* ----------------------------------------- */
/* CHECK_RLOCK - given timestamp and owner fields from a record, determine whether the
   record is locked by another identity (return 1) or not (return 0).
*/
int
SHSQL_check_rlock( ts, owner, usagemode )
char *ts, *owner;
int usagemode;  /* 0 = wants to get lock    1 = wants to update */
{
long julian, em_now, em_ts; /* minutes since 1/1/2000 */
int m, d, y, h, min, s;

if( stricmp( ts, TDH_dbnull ) ==0 ) return( 0 ); /* no lock set */

if( Identity == NULL ) {
	Identity = TDH_getvarp( "_IDENTITY" );
	if( Identity == NULL ) Identity = "";  
	}

GL_sysdate( &m, &d, &y );
GL_systime( &h, &min, &s );
getjuldate( y, m, d, &julian );
em_now = min + (h * 60) + ( julian * 24 * 60 );

y = GL_decode( ts[0] ); m = GL_decode( ts[1] ); d = GL_decode( ts[2] ); h = GL_decode( ts[3] ); min = GL_decode( ts[4] );
/* after the year 2050: y += 50 */
getjuldate( y, m, d, &julian );
em_ts = min + (h * 60) + (julian * 24 * 60 );

if( em_now - em_ts > SHSQL_rlocktimeout ) {     /* Lock was set but has expired.  
						  If current process wants the lock, it is granted.  
						  If current process wants to update the record, we examine 
							the 'owner' field, which indicates who last touched 
							the record.  If owner matches current identity, access 
							is granted; if not this means that someone else has 
						    	touched the record in the meantime, and access is denied.
						 */

	if( usagemode == WANTS_TO_UPDATE && strcmp( Identity, owner )==0 ) return( 1 );	
	else return( 0 );
	}
else if( strcmp( Identity, owner )== 0 ) return( 0 );   /* lock set but owned by current identity */
else return( 1 ); 					/* record is locked by a different identity, and lock has 
							   not expired */
}

/* -------------------------------------------- */
int
SHSQL_check_rlock_rec( buf, ilt, ilo )
char *buf;
int ilt, ilo;
{
int i, ix;
char ts[80], owner[80], tok[DATAMAXLEN];
for( i = 0, ix = 0; ; i++ ) {
	SHSQL_getfld( tok, buf, &ix );
	if( tok[0] == '\0' ) break;
	else if( i == ilt ) strcpy( ts, tok );
	else if( i == ilo ) strcpy( owner, tok );
	}
return( SHSQL_check_rlock( ts, owner ) );
}

/* -------------------------------------------- */
/* GETJULDATE */

static int 
getjuldate( y, m, d, jul )
int y;		/* year */
int m;		/* month */
int d;		/* day */
long *jul;	/* julian date result */
{
int i, leap;
int pivotyear;
int refyear;

pivotyear = 0; /* in the future this can be adjusted for century handling */
refyear = 2000;

/* make sure month is in range.. */
if( m < 1 || m > 12 ) return( -1 ); 

/* decrement month so it serves as index into array... */
m--; 				    
			
/* if a two-digit year - adjust using pivot */
if( y >= pivotyear ) y += 2000;
else if( y < pivotyear ) y += 2100;

/* see if it is a leap year (every forth year except non-milenneal 00 years) */
if( y % 4 == 0 && (y % 100 != 0 || y % 400 == 0 )) leap = 1;
else leap = 0;

/* make sure day is in range.. */
if( leap && m == 1 ) {
	if( d < 1 || d > Dmdays[m]+leap ) return( -1 );
	}
else if( d < 1 || d > Dmdays[ m ] ) return( -1 ); /* bad day */


/* calculate julian date (reference date 1/1/2000 = 0 */
*jul = ( y - refyear ) * 365 + ( ( y - refyear ) / 4 );
for( i = 0; i < m; i++ ) *jul += Dmdays[i];
if( m > 1 ) *jul += leap; /* only after feb */
*jul += (d -1); 
if( y < refyear ) (*jul)--; /* correct for zero crossing */

return( 0 );
}
/* ----------------------------------- */

#ifdef TESTING
main()
{
char buf[80];

generate_locktime( buf );
printf( "%s\n", buf );
}
#endif

