#=================================================================
# Notes:
#
# I would have used flock(), but there are portability issues
# when used across networks (see "perldoc -f flock").
#=================================================================

use strict;

my $LCK_FORMAT = "%s.LCK";
my $LCK_RETRIES = 10;
my $LCK_DELAY = .250; # in seconds (can be fractional).
my $LCK_STALE = 25; # seconds

#-----------------------------------------------------------------
# Read the lock data.
#-----------------------------------------------------------------
sub db_read_lock {
    my ($filename) = @_;

    my $lockfile = sprintf ($LCK_FORMAT, $filename);

    # Read the lock file
    open (INF, "< $lockfile");
    my $in_data = <INF>;
    close INF;

    my ($in_pid, $in_ts) = split ("\t", $in_data);

    return ($in_pid, $in_ts);
}

#-----------------------------------------------------------------
# Return 1 if the lock is stale, 0 otherwise.
#-----------------------------------------------------------------
sub db_is_stale {
    my ($filename) = @_;
    my $rc = 0;

    # Read the lock file
    my ($in_pid, $in_ts) = db_read_lock ($filename);

    my $now = time ();
    if (($in_ts > $now) || (($now - $in_ts) > $LCK_STALE)) {
	$rc = 1;
    }
    return $rc;
}

#-----------------------------------------------------------------
# Returns 1 if file is locked, 0 if not locked.
#-----------------------------------------------------------------
sub db_is_locked {
    my ($filename) = @_;
    my $rc = 0;
    
    # Read the lock file
    my ($in_pid, $in_ts) = db_read_lock ($filename);

    if ((defined $in_pid) && (defined $in_ts)) {
	$rc = 1;
    }
    return $rc;
}

#-----------------------------------------------------------------
# Wait up to n seconds for the file to become unlocked. Return 1 
# if unlocked, 0 otherwise.
#-----------------------------------------------------------------
sub db_wait_for_unlock {
    my ($filename, $seconds) = @_;
    my $i;
    my $rc = 0;

    for ($i=0; $i < ($seconds + $seconds); $i++) {
	if (db_is_locked ($filename) == 1) {
	    # wait half a sec...
	    select (undef, undef, undef, 0.5); 
	}
	else {
	    $rc = 1;
	    last;
	}
    }

    return $rc;
}

#-----------------------------------------------------------------
# Create a lockfile for $filename.
#-----------------------------------------------------------------
sub db_file_lock_simple {
    my ($filename) = @_;
    my $success = 0;
    my $lockfile = sprintf ($LCK_FORMAT, $filename);
    my $rc = 0;
    
#    if ((-e $filename) && (! -e $lockfile)) {
    if (! -e $lockfile) {
	# Write the lock file
	open (OUTF, "> $lockfile");
	my $now = time ();
	print OUTF "$$\t$now";
	if (close OUTF) {
	    $success = 1;
	}

	# Read the lock file
	#my ($in_pid, $in_ts) = db_read_lock ($filename);

	# Verify the lock file
	#if ($in_pid == $$) {
	#    $success = 1;
	#}
    }
    
    return $success;
}

#-----------------------------------------------------------------
# Attempt 
#-----------------------------------------------------------------
sub db_file_lock {
    my ($filename) = @_;
    my $success = 0;

    # If there's an existing lock file, and it's stale, then
    # remove it.
    if (db_is_locked ($filename)) {
	if (db_is_stale ($filename)) {
	    if (db_file_unlock ($filename, 1) == 0) {
		# Could not remove stale lock file.
		return $success;
	    }
	}
    }

    my $tries = 0;

    # Try to apply a new lock.
    while ((($success = db_file_lock_simple ($filename)) == 0) 
	   && ($tries++ < $LCK_RETRIES)) {
	select (undef, undef, undef, $LCK_DELAY);
    }
	
    return $success;
}

#-----------------------------------------------------------------
# Remove a lockfile for $filename.
#-----------------------------------------------------------------
sub db_file_unlock {
    my ($filename, $force) = @_;
    my $success = 1;
    my $lockfile = sprintf ($LCK_FORMAT, $filename);
    
    if (-e $lockfile) {
	# Read the lock file
	my ($in_pid, $in_ts) = db_read_lock ($filename);

	# Is this our lock file (or are we forcing a lock removal)?
	if (($in_pid == $$) || ($force == 1)) {
	    unlink $lockfile;
	}

	# Read the lock file again
	my ($chk_pid, $chk_ts) = db_read_lock ($filename);
	if ((defined $chk_pid) && (defined $chk_ts)) {
	    # If it hasn't changed, then we were unsuccessful
	    # at unlocking.
	    if (($chk_pid == $in_pid) && ($chk_ts == $in_ts)) {
		$success = 0;
	    }
	}
    }
    
    return $success;
}

1;
