<?php
// Xinfeng Chen, 2002.08.09, 2002.10.29, 2002.12.31
define('F_USED','*');
define('NULLPTR',0);
define('PTR_SIZE',8);
define('PTRFIELD_SIZE',PTR_SIZE*2);
define('HEADER_SIZE',PTR_SIZE*8);
// PtrSeg: ptrNext(8),ptrPrev(8)
// Header: magic(4),recSize(8),recNum(8),ptrHead(8),ptrTail(8),ptrFree(8),resv(20)

class CBaseRecordFile {
 var $_fp=NULLPTR, $_magic, $_recSize, $_recNum, $_ptrHead, $_ptrTail, $_ptrFree;
 var $_curId=NULLPTR, $_ptrPrev=NULLPTR, $_ptrNext=NULLPTR;
 var $recordBuffer;

 function _explodeRecord($a_line){}
 function _composeRecord($a_line){}
 function _compareRecord($a_key){/*return true/false*/}

 function _readHeader(){
  fseek($this->_fp, 0); $buf=fread($this->_fp, HEADER_SIZE);
  list($this->_magic, $this->_recSize, $this->_recNum,
    $this->_ptrHead, $this->_ptrTail, $this->_ptrFree)=explode('|',$buf);
 }

 function _writeHeader(){
  $buf='ZC10|'.$this->_recSize.'|'.$this->_recNum
    .'|'.$this->_ptrHead.'|'.$this->_ptrTail.'|'.$this->_ptrFree.'|';
  fseek($this->_fp, 0); fwrite($this->_fp, str_pad($buf,HEADER_SIZE), HEADER_SIZE);
 }

 function _readPointer($a_recId, $a_offset, $a_ptrVal){
  fseek($this->_fp, $a_recId*$this->_recSize+HEADER_SIZE+$a_offset*PTR_SIZE);
  $a_ptrVal=fread($this->_fp, PTR_SIZE);
 }

 function _writePointer($a_recId, $a_offset, $a_ptrVal){
  fseek($this->_fp, $a_recId*$this->_recSize+HEADER_SIZE+$a_offset*PTR_SIZE);
  fwrite($this->_fp, str_pad($a_ptrVal,PTR_SIZE,' ',STR_PAD_LEFT), PTR_SIZE);
 }

 function _validateRecord($a_recId){
  if($a_recId <= NULLPTR){return false;}
  fseek($this->_fp, $a_recId*$this->_recSize + HEADER_SIZE + PTRFIELD_SIZE);
  return (fread($this->_fp, 1) == F_USED);
 }

 function _allocSegment(){
  if($this->_ptrFree > NULLPTR){
   $ptrNew=$this->_ptrFree;
   $this->_readPointer($ptrNew, 0, &$this->_ptrFree);
  }else{
   $ptrNew=$this->_recNum+1;
   $this->_writePointer($ptrNew, 0, NULLPTR);
  }
  $this->_recNum++;
  return $ptrNew;
 }

 function _freeSegment($a_idx){
  $this->_writeRecord($a_idx, $this->_ptrFree, NULLPTR, '', ' ');
  $this->_ptrFree=$a_idx;
  $this->_recNum--;
 }

 function _readRecordPointer($a_recId, $a_ptrPrev, $a_ptrNext){
  fseek($this->_fp, $a_recId * $this->_recSize + HEADER_SIZE);
  $buf=fread($this->_fp, PTRFIELD_SIZE);
  $a_ptrNext=substr($buf,0,PTR_SIZE);
  $a_ptrPrev=substr($buf,PTR_SIZE);
 }

 function _readRecord($a_recId, $a_ptrPrev, $a_ptrNext, $a_buf){
  fseek($this->_fp, $a_recId * $this->_recSize + HEADER_SIZE);
  $buf=fread($this->_fp, $this->_recSize);

  if($buf[PTR_SIZE*2]==F_USED){
   $a_ptrNext=substr($buf,0,PTR_SIZE);
   $a_ptrPrev=substr($buf,PTR_SIZE,PTR_SIZE);
   $a_buf=substr($buf,PTR_SIZE*2+1);
   return true;
  }else{
   return false;
  }
 }

 function _writeRecord($a_recId, $a_ptrPrev, $a_ptrNext, $a_buf, $a_flag=F_USED){
  $buf=str_pad($a_ptrNext,PTR_SIZE,' ',STR_PAD_LEFT)
    .str_pad($a_ptrPrev,PTR_SIZE,' ',STR_PAD_LEFT).$a_flag.$a_buf;
  $buf=str_pad($buf, $this->_recSize, ' ');

  fseek($this->_fp, $a_recId * $this->_recSize + HEADER_SIZE);
  fwrite($this->_fp, $buf, $this->_recSize);
 }

 function readMemo(){
  fseek($this->_fp, HEADER_SIZE);
  return fread($this->_fp, $this->_recSize);
 }

 function writeMemo($a_buf){
  fseek($this->_fp, HEADER_SIZE);
  return fwrite($this->_fp, 
    str_pad($a_buf,$this->_recSize), $this->_recSize);
 }

 function openFile($a_filename){
  if(!($this->_fp=@fopen($a_filename, "r+b"))){return false;}
  $this->_readHeader();
  $this->moveFirst();
  return ($this->_magic=='ZC10');
 }

 function closeFile(){
  fclose($this->_fp);
 }

 function getRecordCount() {return (integer)$this->_recNum;}

 function getAbsolutePosition() {return (integer)$this->_curId;}

 function setAbsolutePosition($a_idx) {
  if($a_idx <= NULLPTR){
   $this->_curId=$this->_ptrPrev=$this->_ptrNext=NULLPTR;
   return false;
  }
  if($this->_readRecord($a_idx, &$this->_ptrPrev, &$this->_ptrNext, &$this->recordBuffer)){
   $this->_curId=$a_idx;
   $this->_explodeRecord(&$this->recordBuffer);
   return true;
  }else{
   return false;
  }
 }

 function moveTo($a_offset, $a_forward=true) {
  $tmpIdx=($a_forward) ? $this->_ptrHead : $this->_ptrTail;
  $count=0;
  while(($count<$a_offset)&&($tmpIdx>NULLPTR)){
   $this->_readRecordPointer($tmpIdx,&$tmpPrev,&$tmpNext);
   $tmpIdx=($a_forward) ? $tmpNext : $tmpPrev;
   $count++;
  }
  return $this->setAbsolutePosition($tmpIdx);
 }

 function moveFirst(){return $this->setAbsolutePosition($this->_ptrHead);}
 function moveLast(){return $this->setAbsolutePosition($this->_ptrTail);}
 function moveNext(){return $this->setAbsolutePosition($this->_ptrNext);}
 function movePrev(){return $this->setAbsolutePosition($this->_ptrPrev);}

 function find($a_key) {
  $flag=$this->moveFirst();
  while ($flag){
   if($this->_compareRecord(&$a_key)){return true;}
   $flag=$this->moveNext();
  }
  return false;
 }

 function update() {
  flock($this->_fp, 2);
  if(!$this->_validateRecord($this->_curId)){
   flock($this->_fp, 3); return false;
  }

  $this->_composeRecord(&$this->recordBuffer);
  $this->_writeRecord(&$this->_curId, &$this->_ptrPrev, &$this->_ptrNext, &$this->recordBuffer);
  fflush($this->_fp);

  flock($this->_fp, 3);
  return true;
 }

 function insertNew() {
  if($this->_curId <= NULLPTR){return $this->appendNew();}

  flock($this->_fp, 2);
  $this->_readHeader();
  if(!$this->_validateRecord($this->_curId)){
   flock($this->_fp, 3); return false;
  }

  $ptrNew=$this->_allocSegment();
  if($this->_ptrPrev <= NULLPTR){
   $this->_ptrHead=$ptrNew;
  }else{
   $this->_writePointer($this->_ptrPrev, 0, $ptrNew);
  }
  $this->_writePointer($this->_curId, 1, $ptrNew);
  $this->_ptrNext=$this->_curId;
  $this->_curId=$ptrNew;
  $this->_writeRecord($ptrNew, $this->_ptrPrev, $this->_ptrNext, '');
  $this->_writeHeader();

  fflush($this->_fp);
  flock($this->_fp, 3);
  return $this->_curId;
 }

 function appendNew() {
  flock($this->_fp, 2);
  $this->_readHeader();

  $ptrNew=$this->_allocSegment();
  if($this->_ptrTail > NULLPTR){
   $this->_ptrPrev=$this->_ptrTail;
   $this->_writePointer($this->_ptrTail, 0, $ptrNew);
   $this->_ptrTail=$ptrNew;
  }else{
   $this->_ptrPrev= NULLPTR;
   $this->_ptrTail=$this->_ptrHead=$ptrNew;
  }
  $this->_ptrNext= NULLPTR;
  $this->_curId=$ptrNew;
  $this->_writeRecord($ptrNew, $this->_ptrPrev, $this->_ptrNext, "");
  $this->_writeHeader();

  fflush($this->_fp);
  flock($this->_fp, 3);
  return $this->_curId;
 }

 function delete() {
  flock($this->_fp, 2);
  $this->_readHeader();
  if(!$this->_validateRecord($this->_curId)){
   flock($this->_fp, 3); return false;
  }

  if($this->_ptrPrev > NULLPTR){
   $this->_writePointer($this->_ptrPrev, 0, $this->_ptrNext);
  }else{
   $this->_ptrHead=$this->_ptrNext;
  }
  if($this->_ptrNext > NULLPTR){
   $this->_writePointer($this->_ptrNext, 1, $this->_ptrPrev);
  }else{
   $this->_ptrTail=$this->_ptrPrev;
  }
  $this->_freeSegment($this->_curId);
  $this->_writeHeader();

  fflush($this->_fp);
  flock($this->_fp, 3);
  return true;
 }

 function createFile($a_fileName, $a_recSize){
  $this->_fp=fopen($a_fileName,"wb");
  $this->_recSize=($a_recSize>PTRFIELD_SIZE) ? $a_recSize : PTRFIELD_SIZE+2;
  $this->_recNum=0;
  $this->_ptrHead=$this->_ptrTail=$this->_ptrFree=NULLPTR;
  $this->_writeHeader();
  fwrite($this->_fp, str_repeat(' ',$this->_recSize));
  fclose($this->_fp);
  return true;
 }

 function destroyFile($a_fileName){
  if(!is_file($a_fileName)){return false;}
  return unlink($a_fileName);
 }
}

?>