##
#
#    Copyright 2001, AllAfrica Global Media
#
#    This file is part of XML::Comma
#
#    XML::Comma 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 of the License, or
#    any later version.
#
#    This program 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.
#
#    For more information about XML::Comma, point a web browser at
#    http://xml-comma.org, or read the tutorial included
#    with the XML::Comma distribution at docs/guide.html
#
##

package XML::Comma::SQL::Lock;

@ISA = qw( XML::Comma::SQL::DBH_User );

use strict;
use XML::Comma;
use XML::Comma::Util qw( dbg );
use XML::Comma::SQL::DBH_User;

my $PROC_PTABLE_AVAILABLE;
eval {
  require Proc::ProcessTable;
  $PROC_PTABLE_AVAILABLE = 1;
};

my $LOCK_LOOP_WAIT_SECONDS = 3;

sub new {
  my $base_class = shift();
  my $self = {};
  XML::Comma::SQL::DBH_User::decorate_and_bless ( $self, $base_class );
  my $dbh = $self->get_dbh();
  # check for hold table -- setup if necessary
  eval { $self->sql_get_hold('_startup_test_hold_');
         $self->sql_release_hold('_startup_test_hold_'); };
  if ( $@ ) {
    # dbg 'hold error', $@;
    # release hold to "commit" the aborted transaction
    $self->sql_create_hold_table($dbh);
    $self->sql_release_hold('_startup_test_hold_');
  }
  # check for lock table -- setup if necessary
  eval { $self->sql_get_lock_record('++') };
  if ( $@ ) {
    $self->sql_get_hold('_startup_create_lock_');
    # check again
    eval { $self->sql_get_lock_record('++') };
    if ( $@ ) {
      $self->sql_create_lock_table();
    }
    $self->sql_release_hold('_startup_create_lock_');
  }
  return $self;
}

# $self, $key, $no_block
sub lock {
  my ( $self, $key, $no_block, $timeout ) = @_;
  # dbg 'locking', $key;
  my $dbh = $self->get_dbh();
  my $locked = $self->sql_doc_lock ( $key );
  if ( $locked || $no_block ) {
    return $locked;
  }
  my $waited = 0;
  my $lr = $self->sql_get_lock_record ( $key );
  # recurse if the doc was unlocked out from under this routine
  unless ( $lr ) {
    $self->lock ( $key, $no_block, $timeout );
  }
  # okay, now our real try-again-to-lock loop
  while ( ! defined $timeout or $waited < $timeout ) {
    # check to see if we're allowed to treat this lock as expired
    $self->maybe_unlock ( $lr->{pid}, $key );
    # try to lock again
    if ( $self->sql_doc_lock($key) ) { return 1; }
    # sleep and keep going round and round
    sleep $LOCK_LOOP_WAIT_SECONDS;
    $waited += $LOCK_LOOP_WAIT_SECONDS;
  }
  XML::Comma::Log->err ( 'LOCK_TIMEOUT', "timed out waiting for lock on $key" );
}

# $self, $key
sub unlock {
  # dbg 'unlocking', $_[1];
  $_[0]->sql_doc_unlock ( $_[1] );
}

sub maybe_unlock {
  my ( $self, $pid, $key ) = @_;
  return  unless  $PROC_PTABLE_AVAILABLE;
  my $lr = $self->sql_get_lock_record($key);
  return unless $lr;
  if ( $lr->{info} eq Sys::Hostname::hostname() ) {
    my $plist = Proc::ProcessTable->new();
    foreach my $p ( @{$plist->table()} ) {
      return  if  $p->pid() == $pid;
    }
    $self->unlock ( $key );
  }
}


####
##
## DEPRECATED -- the string-based hold methods don't really work as
## intended, given some futziness with the mysql implementation.
##
##

# generic, string-based "hold". this can be used to implement a
# temporary lock without using the special doc lock table.
sub wait_for_hold {
  $_[0]->sql_get_hold ( $_[1] );
}

sub release_hold {
  $_[0]->sql_release_hold ( $_[1] );
}

sub release_all_my_locks {
  $_[0]->sql_delete_locks_held_by_this_pid ();
}

##
##
####


# FIX: make destroy unlock all locks held by this pid?
sub DESTROY {
  # print 'D: ' . $_[0] . "\n";
  $_[0]->disconnect();
}







