File_Fortune
[ class tree: File_Fortune ] [ index: File_Fortune ] [ all elements ]

Source for file Fortune.php

Documentation is available at Fortune.php

  1. <?php
  2. /*
  3.  * File_Fortune: Read and manipulate fortune databases.
  4.  *
  5.  * PHP version 5
  6.  *
  7.  * @category  File_Formats
  8.  * @package   File_Fortune
  9.  * @author    Matthew Weier O'Phinney <mweierophinney@gmail.com>, based on
  10.  *            Fortune.pm, by Greg Ward
  11.  * @copyright 2005 - 2007, Matthew Weier O'Phinney; Fortune.pm v0.2(c) 1999, Greg Ward
  12.  * @version   CVS: $Id:$
  13.  * @license   New BSD {@link http://www.opensource.org/licenses/bsd-license.php}
  14.  */
  15.  
  16. /** File_Fortune_Exception */
  17. require_once 'File/Fortune/Exception.php';
  18.  
  19. /**
  20.  * File_Fortune
  21.  * 
  22.  * File_Fortune: Interface to fortune cookie databases
  23.  *
  24.  * The <em>fortune</em> program is a small but important part of *nix
  25.  * culture, and this package aims to provide support for its "fortune cookie"
  26.  * databases to PHP programmers.
  27.  *
  28.  * @uses      Iterator
  29.  * @uses      Countable
  30.  * @uses      ArrayAccess
  31.  * @package   File_Fortune
  32.  * @version   @release-version@
  33.  * @tutorial  File_Fortune/File_Fortune.cls
  34.  */
  35. class File_Fortune implements IteratorCountableArrayAccess
  36. {
  37.     const VERSION = '0.9.1';
  38.  
  39.     /**
  40.      * Character that delimits fortunes (defaults to '%')
  41.      * @var string 
  42.      */
  43.     public $delim = '%';
  44.  
  45.     /**
  46.      * Length of longest string in the fortune file
  47.      * @var int 
  48.      */
  49.     public $maxLength;
  50.  
  51.     /**
  52.      * Length of shortest string in the fortune file
  53.      * @var int 
  54.      */
  55.     public $minLength;
  56.  
  57.     /**
  58.      * Version number for fortune file
  59.      * @var int 
  60.      */
  61.     public $version;
  62.  
  63.     /**
  64.      * Flag - has an operation changed the file?
  65.      * @var boolean 
  66.      */
  67.     protected $_changed = false;
  68.  
  69.     /**
  70.      * Current offset (used by iterator)
  71.      * @var int 
  72.      */
  73.     protected $_curOffset = 1;
  74.  
  75.     /**
  76.      * Directory containing fortune files
  77.      * @var string 
  78.      */
  79.     protected $_directory;
  80.  
  81.     /**
  82.      * Fortune file filehandle
  83.      * @var resource 
  84.      */
  85.     protected $_file;
  86.  
  87.     /**
  88.      * Name of fortune file to use
  89.      * @var string 
  90.      */
  91.     protected $_filename;
  92.  
  93.     /**
  94.      * Array of fortune files found in $_directory
  95.      * @var array 
  96.      */
  97.     protected $_files;
  98.  
  99.     /**
  100.      * Bit field for flags
  101.      * @var array 
  102.      */
  103.     protected $_flags;
  104.  
  105.     /**
  106.      * Array of fortunes; unset by default.
  107.      * @var array 
  108.      */
  109.     protected $_fortunes;
  110.  
  111.     /**
  112.      * Name of fortune header file (.dat file)
  113.      * @var string 
  114.      */
  115.     protected $_headerFile;
  116.  
  117.     /**
  118.      * Flag indicating whether or not a header file is present. Defaults to
  119.      * false.
  120.      * @var bool 
  121.      */
  122.     protected $_noHeader = false;
  123.  
  124.     /**
  125.      * Number of strings (fortunes) in the file
  126.      * @var int 
  127.      */
  128.     public $numstr;
  129.  
  130.     /**
  131.      * Array of file offsets (fortune indices)
  132.      * @var array 
  133.      */
  134.     protected $offsets;
  135.  
  136.     /**
  137.      * Constructor
  138.      *
  139.      * Optionally pass a filename or directory name to set the fortune file or
  140.      * directory, and, if passing a fortune file name, optionally pass the name
  141.      * of the header file.
  142.      * 
  143.      * @param  string $file 
  144.      * @param  string $headerFile 
  145.      * @return void 
  146.      */
  147.     public function __construct($file = null$headerFile = null)
  148.     {
  149.         if (null !== $file{
  150.             if (is_dir($file)) {
  151.                 $this->setDirectory($file);
  152.             else {
  153.                 $this->setFile($file$headerFile);
  154.             }
  155.         }
  156.     }
  157.  
  158.     /**
  159.      * Destructor
  160.      *
  161.      * Close open files
  162.      * 
  163.      * @return void 
  164.      */
  165.     public function __destruct()
  166.     {
  167.         $this->_close();
  168.     }
  169.  
  170.     /**
  171.      * Return current fortune in iterator
  172.      * 
  173.      * @return string 
  174.      */
  175.     public function current()
  176.     {
  177.         if (empty($this->_file)) {
  178.             $this->_open();
  179.         }
  180.  
  181.         if (is_array($this->_fortunes)) {
  182.             return current($this->_fortunes);
  183.         }
  184.  
  185.         return $this->offsetGet($this->_curOffset);
  186.     }
  187.  
  188.     /**
  189.      * Return current iterator key
  190.      * 
  191.      * @return int 
  192.      */
  193.     public function key()
  194.     {
  195.         if (empty($this->_file)) {
  196.             $this->_open();
  197.         }
  198.  
  199.         if (is_array($this->_fortunes)) {
  200.             return key($this->_fortunes);
  201.         }
  202.  
  203.         return $this->_curOffset;
  204.     }
  205.  
  206.     /**
  207.      * Retrieve next element in iterator
  208.      * 
  209.      * @return string|false
  210.      */
  211.     public function next()
  212.     {
  213.         if (empty($this->_file)) {
  214.             $this->_open();
  215.         }
  216.  
  217.         if (is_array($this->_fortunes)) {
  218.             return next($this->_fortunes);
  219.         }
  220.  
  221.         ++$this->_curOffset;
  222.         if (!$this->offsetExists($this->_curOffset)) {
  223.             return false;
  224.         }
  225.  
  226.         return $this->offsetGet($this->_curOffset);
  227.     }
  228.  
  229.     /**
  230.      * Rewind iterator to first element
  231.      * 
  232.      * @return bool 
  233.      */
  234.     public function rewind()
  235.     {
  236.         if (empty($this->_file)) {
  237.             $this->_open();
  238.         }
  239.  
  240.         if (is_array($this->_fortunes)) {
  241.             return reset($this->_fortunes);
  242.         }
  243.  
  244.         $this->_curOffset = 1;
  245.         return true;
  246.     }
  247.  
  248.     /**
  249.      * Current element is valid (iterator)
  250.      * 
  251.      * @return bool 
  252.      */
  253.     public function valid()
  254.     {
  255.         if (empty($this->_file)) {
  256.             $this->_open();
  257.         }
  258.  
  259.         if (is_array($this->_fortunes)) {
  260.             return key($this->_fortunes!== null;
  261.         }
  262.  
  263.         return array_key_exists($this->_curOffset$this->offsets);
  264.     }
  265.  
  266.     /**
  267.      * Count of fortunes in current active file
  268.      * 
  269.      * @return int 
  270.      */
  271.     public function count()
  272.     {
  273.         if (empty($this->_file)) {
  274.             $this->_open();
  275.         }
  276.  
  277.         return $this->numstr;
  278.     }
  279.  
  280.     /**
  281.      * Does fortune exist at the given offset?
  282.      * 
  283.      * @param  int $offset 
  284.      * @return boolean 
  285.      */
  286.      public function offsetExists($offset)
  287.     {
  288.         if (empty($this->_file)) {
  289.             $this->_open();
  290.         }
  291.  
  292.         if (is_array($this->_fortunes&& isset($this->_fortunes[$offset - 1])) {
  293.             return true;
  294.         elseif (!is_array($this->_fortunes
  295.             && isset($this->offsets[$offset])
  296.             && isset($this->offsets[$offset - 1])) 
  297.         {
  298.             return true;
  299.         }
  300.  
  301.         return false;
  302.     }
  303.  
  304.     /**
  305.      * Retrieve fortune by offset
  306.      * 
  307.      * @param  int $offset 
  308.      * @return string 
  309.      */
  310.      public function offsetGet($offset)
  311.     {
  312.         if ($this->offsetExists($offset)) {
  313.             if (is_array($this->_fortunes)) {
  314.                 return $this->_fortunes[$offset - 1];
  315.             }
  316.  
  317.             return $this->_readFromFile($offset);
  318.         }
  319.  
  320.         throw new File_Fortune_Exception('Requested fortune index does not exist');
  321.     }
  322.  
  323.     /**
  324.      * Set a fortune at a given offset
  325.      * 
  326.      * @param  int $offset 
  327.      * @param  string $value 
  328.      * @return void 
  329.      */
  330.      public function offsetSet($offset$value)
  331.     {
  332.         $this->_initChanges();
  333.         if (!$this->offsetExists($offset)) {
  334.             throw new File_Fortune_Exception('Cannot modify fortune; offset does not exist');
  335.         }
  336.         $this->_fortunes[$offset - 1$value;
  337.     }
  338.  
  339.     /**
  340.      * Delete a fortune at a given offset
  341.      * 
  342.      * @param  int $offset 
  343.      * @return void 
  344.      */
  345.      public function offsetUnset($offset)
  346.     {
  347.         if (!$this->offsetExists($offset)) {
  348.             throw new File_Fortune_Exception('Cannot delete fortune; offset does not exist');
  349.         }
  350.         $this->_initChanges();
  351.         unset($this->_fortunes[$offset - 1]);
  352.         $this->_fortunes = array_merge(array()$this->_fortunes);
  353.         $this->numstr = count($this->_fortunes);
  354.     }
  355.  
  356.     /**
  357.      * Set fortune file
  358.      * 
  359.      * @param  string $file 
  360.      * @param  string $headerFile 
  361.      * @return File_Fortune 
  362.      */
  363.     public function setFile($file$headerFile = null)
  364.     {
  365.         if ($file != $this->_filename{
  366.             $this->_close();
  367.         }
  368.         $this->_filename = (string) $file;
  369.  
  370.         $this->setHeaderFile($headerFile);
  371.         return $this;
  372.     }
  373.  
  374.     /**
  375.      * Retrieve current fortune file name
  376.      * 
  377.      * @return string 
  378.      */
  379.     public function getFile()
  380.     {
  381.         return $this->_filename;
  382.     }
  383.  
  384.     /**
  385.      * Set header file name
  386.      * 
  387.      * @param  string $headerFile 
  388.      * @return File_Fortune 
  389.      */
  390.     public function setHeaderFile($headerFile)
  391.     {
  392.         $this->_headerFile = (string) $headerFile;
  393.         return $this;
  394.     }
  395.  
  396.     /**
  397.      * Retrieve current header file name
  398.      * 
  399.      * @return string 
  400.      */
  401.     public function getHeaderFile()
  402.     {
  403.         return $this->_headerFile;
  404.     }
  405.  
  406.     /**
  407.      * Set directory from which to randomly select a fortune file
  408.      * 
  409.      * @param  string $directory 
  410.      * @return File_Fortune 
  411.      */
  412.     public function setDirectory($directory)
  413.     {
  414.         $this->_close();
  415.  
  416.         $this->_directory = $directory;
  417.         $this->_files     = null;
  418.         return $this;
  419.     }
  420.  
  421.     /**
  422.      * Retrieve current directory of fortune files
  423.      * 
  424.      * @return string 
  425.      */
  426.     public function getDirectory()
  427.     {
  428.         return $this->_directory;
  429.     }
  430.  
  431.     /**
  432.      * Add a new fortune
  433.      * 
  434.      * @param  string $fortune 
  435.      * @return File_Fortune 
  436.      */
  437.     public function add($fortune)
  438.     {
  439.         $this->_initChanges();
  440.         $this->_fortunes[$fortune;
  441.         $this->numstr = count($this->_fortunes);
  442.         return $this;
  443.     }
  444.  
  445.     /**
  446.      * Update an existing fortune
  447.      * 
  448.      * @param  int $index 
  449.      * @param  string $fortune 
  450.      * @return void 
  451.      */
  452.     public function update($index$fortune)
  453.     {
  454.         $this->offsetSet($index$fortune);
  455.         return $this;
  456.     }
  457.  
  458.     /**
  459.      * Delete an existing fortune
  460.      * 
  461.      * @param  int $index 
  462.      * @return void 
  463.      */
  464.     public function delete($index)
  465.     {
  466.         $this->offsetUnset($index);
  467.         return $this;
  468.     }
  469.  
  470.     /**
  471.      * Retrieve random fortune
  472.      * 
  473.      * @return string 
  474.      */
  475.     public function getRandom()
  476.     {
  477.         if (empty($this->_file|| !empty($this->_directory)) {
  478.             $this->_open();
  479.         }
  480.  
  481.         if (is_array($this->_fortunes)) {
  482.             $offset array_rand(array_keys($this->_fortunes));
  483.         else {
  484.             do {
  485.                 $offset array_rand($this->offsets);
  486.             while ($offset < 1);
  487.         }
  488.  
  489.         return $this->offsetGet($offset);
  490.     }
  491.  
  492.     /**
  493.      * Retrieve all fortunes from the current file
  494.      * 
  495.      * @return array 
  496.      */
  497.     public function getAll()
  498.     {
  499.         if (empty($this->_file)) {
  500.             $this->_open();
  501.         }
  502.  
  503.         if (!is_array($this->_fortunes)) {
  504.             $fortunes = array();
  505.             for ($i = 1; $i <= $this->numstr; ++$i{
  506.                 $fortunes[$this->_readFromFile($i);
  507.             }
  508.             $this->_fortunes = $fortunes;
  509.         }
  510.  
  511.         return $this->_fortunes;
  512.     }
  513.  
  514.     /**
  515.      * Save changes
  516.      * 
  517.      * @return void 
  518.      */
  519.     public function save()
  520.     {
  521.         $filename $this->getFile();
  522.         $this->_close();
  523.         $this->setFile($filename);
  524.     }
  525.  
  526.     /**
  527.      * Create a new fortune file from an array of fortunes
  528.      * 
  529.      * @param  array $fortunes 
  530.      * @param  string $file 
  531.      * @return void 
  532.      */
  533.     public function create(array $fortunes$file = null)
  534.     {
  535.         if ((null !== $file&& ($file != $this->getFile())) {
  536.             $this->setFile($file);
  537.         else {
  538.             $file $this->getFile();
  539.         }
  540.         if (file_exists($file)) {
  541.             throw new File_Fortune_Exception('Cannot create fortune file; already exists');
  542.         }
  543.  
  544.         $this->_fortunes = $fortunes;
  545.         $this->_save();
  546.         $this->_close();
  547.     }
  548.  
  549.     /**
  550.      * Open a fortune file
  551.      * 
  552.      * @return void 
  553.      */
  554.     protected function _open()
  555.     {
  556.         if (!empty($this->_file)) {
  557.             $this->_close();
  558.         }
  559.  
  560.         if ($this->_directory{
  561.             // get random file from directory
  562.             $this->_getRandomFile();
  563.         }
  564.  
  565.         if (empty($this->_filename)) {
  566.             throw new File_Fortune_Exception('No file or directory set');
  567.         }
  568.  
  569.         if (!is_readable($this->_filename)) {
  570.             throw new File_Fortune_Exception('Fortune file "' $this->_filename . '" not readable');
  571.         }
  572.  
  573.         if (empty($this->_headerFilename)) {
  574.             $this->setHeaderFile($this->_filename . '.dat');
  575.         }
  576.  
  577.         if (false === ($file fopen($this->_filename'r'))) {
  578.             throw new File_Fortune_Exception('Unable to read fortune file "' $this->_filename . '"');
  579.         }
  580.         $this->_file =$file;
  581.  
  582.         $this->_readHeader();
  583.     }
  584.  
  585.     /**
  586.      * Randomly select a fortune file from a registered directory
  587.      * 
  588.      * @return void 
  589.      */
  590.     protected function _getRandomFile()
  591.     {
  592.         if (empty($this->_files)) {
  593.             if (!is_dir($this->_directory|| !is_readable($this->_directory)) {
  594.                 throw new File_Fortune_Exception('Directory "' $this->_directory . '" is invalid or unreadable');
  595.             }
  596.  
  597.             $directory = new DirectoryIterator($this->_directory);
  598.             $files     = array();
  599.             foreach ($directory as $file{
  600.                 if ($file->isDot(|| $file->isDir()) {
  601.                     continue;
  602.                 }
  603.  
  604.                 $filename $file->getFilename();
  605.                 if ('.dat' == substr($filename-4)) {
  606.                     continue;
  607.                 }
  608.  
  609.                 $files[$file->getPathName();
  610.             }
  611.             $this->_files = $files;
  612.         }
  613.         $index array_rand($this->_files);
  614.  
  615.         $this->_fortunes = null;
  616.         $this->_filename = $this->_files[$index];
  617.     }
  618.  
  619.     /**
  620.      * Read a fortune database header file
  621.      * 
  622.      * Reads the header file associated with this fortune database.  The name of
  623.      * the header file is determined by the {@link __construct() constructor}:
  624.      * either it is based on the name of the data file, or supplied by the
  625.      * caller.
  626.      * 
  627.      * If the header file does not exist, this function calls
  628.      * {@link _computeHeader()} automatically, which has the same effect as
  629.      * reading the header from a file.
  630.      * 
  631.      * The header contains the following values, which are stored as attributes
  632.      * of the {@link File_Fortune} object:
  633.      * <ul>
  634.      *     <li>{@link $version}; version number</li>
  635.      *     <li>{@link $numstr}; number of strings (fortunes) in the file</li>
  636.      *     <li>{@link $maxLength}; length of longest string in the file</li>
  637.      *     <li>{@link $minLength}; length of shortest string in the file</li>
  638.      *     <li>{@link $flags}; bit field for flags (see strfile(1) man
  639.      *     page)</li>
  640.      *     <li>{@link $delim}; character that delimits fortunes</li>
  641.      * </ul>
  642.      *
  643.      * {@link $numstr} is available via the count() function (count($fortunes));
  644.      * if you're interested in the others, you'll have to go grubbing through
  645.      * the File_Fortune object, e.g.:
  646.      *
  647.      * <code>
  648.      * $fortune_file = new Fortune('fortunes');
  649.      * $delim        = $fortune_file->delim;
  650.      * $maxLength    = $fortune_file->maxLength;
  651.      * </code>
  652.      *
  653.      * @return void 
  654.      * @throws File_Fortune_Exception
  655.      */
  656.     protected function _readHeader()
  657.     {
  658.         $headerFile $this->getHeaderFile();
  659.         if (!file_exists($headerFile)) {
  660.             return $this->_computeHeader();
  661.         }
  662.       
  663.         if (false === ($file fopen($headerFile'r'))) {
  664.             throw new File_Fortune_Exception('Unable to read header file "' $headerFile '"');
  665.         }
  666.  
  667.         /* 
  668.          * from the strfile(1) man page:
  669.          *       unsigned long str_version;  // version number
  670.          *       unsigned long str_numstr;   // # of strings in the file
  671.          *       unsigned long str_longlen;  // length of longest string
  672.          *       unsigned long str_shortlen; // shortest string length
  673.          *       unsigned long str_flags;    // bit field for flags
  674.          *       char str_delim;             // delimiting character
  675.          * that 'char' is padded out to a full word, so the header is 24 bytes
  676.          */
  677.  
  678.         if (false === ($header fread($file24))) {
  679.             throw new File_Fortune_Exception('Malformed header file "' $headerFile '"');
  680.         }
  681.  
  682.         $values unpack('N5nums/adelim/xxx'$header);
  683.         if (empty($values|| (6 != count($values))) {
  684.             throw new File_Fortune_Exception('Failed to load full header');
  685.         }
  686.  
  687.         $this->version    = $values['nums1'];
  688.         $this->numstr     = $values['nums2'];
  689.         $this->maxLength  = $values['nums3'];
  690.         $this->minLength  = $values['nums4'];
  691.         $this->flags      = $values['nums5'];
  692.         $this->delim      = $values['delim'];
  693.  
  694.         $expectedOffsets $this->numstr + 1;
  695.  
  696.         $amountData = 4 * $expectedOffsets;
  697.  
  698.         if (false === ($data fread($file$amountData))) {
  699.             throw new File_Fortune_Exception('Failed to read offsets for all fortunes');
  700.         }
  701.         $offsets array_merge(unpack('N*'$data));
  702.         if (count($offsets!= $expectedOffsets{
  703.             throw new File_Fortune_Exception('Expected header offsets do not match actual offsets');
  704.         }
  705.         $this->offsets = $offsets;
  706.  
  707.         fclose($file);
  708.     }
  709.  
  710.     /**
  711.      * Compute fortune file header information
  712.      *
  713.      * Reads the contents of the fortune file and computes the header
  714.      * information that would normally be found in a header (.dat) file and read
  715.      * by {@link _readHeader()}.  This is useful if you maintain a file of
  716.      * fortunes by hand and do not have the corresponding data file.
  717.      * 
  718.      * An optional delimiter argument may be passed to this function; if
  719.      * present, that delimiter will be used to separate entries in the fortune
  720.      * file.  If not provided, a percent sign ("%") will be used (this is the
  721.      * standard fortune file delimiter).
  722.      *
  723.      * Additionally, the {@link $noHeader} property will be set to true.
  724.      *
  725.      * <b>NOTE:</b> It is most efficient to use fortune files that have header
  726.      * files. In order to get offsets and fortunes, this method actually must
  727.      * read the entire fortune file, and stores all fortunes in the
  728.      * {@link $_fortunes} array.
  729.      *
  730.      * @param  string $delim Defaults to '%'
  731.      * @return void 
  732.      * @throws File_Fortune_Exception
  733.      */
  734.     protected function _computeHeader($delim '%')
  735.     {
  736.         $filename $this->getFile();
  737.         if (false === ($contents file_get_contents($filename))) {
  738.             throw new File_Fortune_Exception(
  739.                 'Unable to read fortune file (' $filename ') to compute headers'
  740.             );
  741.         }
  742.  
  743.         // Get all fortunes
  744.         // Strip off final delimiter
  745.         $contents substr_replace($contents''strrpos($contents"$delim\n"));
  746.         $fortunes explode("$delim\n"$contents)// explode into array
  747.  
  748.         // Get offsets, min, max, etc.
  749.         $offsets   = array(0);
  750.         $numstr    count($fortunes);
  751.         $curOffset = 0;
  752.         $delimLen  strlen($delim);
  753.         $min       = null;
  754.         $max       = null;
  755.         for ($i = 0; $i $numstr$i++{
  756.             $len        strlen($fortunes[$i]);
  757.             $curOffset += $len $delimLen + 1;
  758.             $offsets[]  $curOffset;
  759.             if (empty($min|| ($min $len)) {
  760.                 $min $len;
  761.             elseif (empty($max|| ($max $len)) {
  762.                 $max $len;
  763.             }
  764.         }
  765.  
  766.         // Set object properties
  767.         $this->version    = 1;
  768.         $this->numstr     = $numstr;
  769.         $this->maxLength  = $max;
  770.         $this->minLength  = $min;
  771.         $this->flags      = 0;
  772.         $this->delim      = $delim;
  773.         $this->offsets    = $offsets;
  774.         $this->_fortunes  = $fortunes;
  775.         $this->noHeader   = true;
  776.     }
  777.  
  778.     /**
  779.      * Close fortune file
  780.      *
  781.      * Closes the fortune file if it's open, and unsets all header-related
  782.      * properties (except {@link $delim}); does nothing otherwise.
  783.      * 
  784.      * @return void 
  785.      */
  786.     protected function _close()
  787.     {
  788.         if (!empty($this->_file)) {
  789.             fclose($this->_file);
  790.             $this->_file = null;
  791.  
  792.             if ($this->_changed{
  793.                 $this->_save();
  794.             }
  795.  
  796.             unset(
  797.                 $this->flags,
  798.                 $this->maxLength,
  799.                 $this->minLength,
  800.                 $this->numstr,
  801.                 $this->offsets,
  802.                 $this->version
  803.             );
  804.  
  805.             $this->delim     = '%';
  806.             $this->_changed  = false;
  807.             $this->_fortunes = null;
  808.         }
  809.     }
  810.  
  811.     /**
  812.      * Read a fortune from the file
  813.      *
  814.      * Reads a fortune directly from the file. If an error occurs, an exception
  815.      * is thrown. Otherwise, on sucess, the fortune is returned.
  816.      *
  817.      * @param int 
  818.      * @return string 
  819.      * @throws File_Fortune_Exception
  820.      */
  821.     protected function _readFromFile($offset
  822.     {
  823.         $start  $this->offsets[$offset - 1];
  824.         $end    $this->offsets[$offset];
  825.         $length $end $start;
  826.  
  827.         // decrement length 2 bytes for most fortunes (to drop trailing "%\n"),
  828.         // and none for the last one (keep trailing newline)
  829.         $delimLength strlen($this->delim| 1;
  830.         $length -= ($offset == $this->numstr? 0 : ($delimLength + 2);
  831.  
  832.         if (-1 == fseek($this->_file$start)) {
  833.             throw new File_Fortune_Exception('Unable to seek to fortune offset');
  834.         }
  835.         $fortune fread($this->_file$length);
  836.  
  837.         return $fortune;
  838.     }
  839.  
  840.     /**
  841.      * Initialize changes
  842.      * 
  843.      * @return void 
  844.      */
  845.     protected function _initChanges()
  846.     {
  847.         $this->_changed = true;
  848.         if (!is_array($this->_fortunes)) {
  849.             $this->getAll();
  850.         }
  851.     }
  852.  
  853.     /**
  854.      * Save changes to disc
  855.      *
  856.      * Changes to fortunes happen in-memory; this method saves them to disc.
  857.      * 
  858.      * @return void 
  859.      */
  860.     protected function _save()
  861.     {
  862.         $this->_initChanges();
  863.  
  864.         if (!empty($this->_file)) {
  865.             fclose($this->_file);
  866.             $this->_file = null;
  867.         }
  868.  
  869.         if (false === ($fh fopen($this->_filename'w'))) {
  870.             throw new File_Fortune_Exception(
  871.                 'Unable to open "' $this->filename . '" to write'
  872.             );
  873.         }
  874.         if (!flock($fhLOCK_EX)) {
  875.             throw new File_Fortune_Exception(
  876.                 'Unable to obtain file lock for writing'
  877.             );
  878.         }
  879.  
  880.         // Write file
  881.         $offsets = array(0)// obtain offsets as we go
  882.         $min     = null;     // For determining shortest fortune
  883.         $max     = null;     // For determining longest fortune
  884.         $numstr  count($this->_fortunes);
  885.         for ($i = 0; $i $numstr; ++$i{
  886.             $fortune $this->_fortunes[$i];
  887.             fwrite($fh$fortune);
  888.             if ($i != ($numstr - 1)) {
  889.                 fwrite($fh"\n" $this->delim . "\n");
  890.             else {
  891.                 fwrite($fh"\n");
  892.             }
  893.             if (false === ($pos ftell($fh))) {
  894.                 throw new File_Fortune_Exception(
  895.                     'Unable to obtain file position while writing fortunes'
  896.                 );
  897.             }
  898.  
  899.             // Determine if this is the min or max length fortune
  900.             $len     strlen($fortune);
  901.             if (empty($min|| ($len $min)) 
  902.                 $min $len;
  903.             
  904.  
  905.             if (empty($max|| ($len $max)) 
  906.                 $max $len;
  907.             }
  908.  
  909.             // Add offset to list
  910.             $offsets[$pos;
  911.         }
  912.         flock($fhLOCK_UN);
  913.         fclose($fh);
  914.  
  915.         // Create header block
  916.         // Set version
  917.         if (empty($this->version)) {
  918.             $this->version = 1;
  919.         else {
  920.             ++$this->version;
  921.         }
  922.  
  923.         // Set other header attributes
  924.         $this->offsets   = $offsets;         // list of offsets
  925.         $this->numstr    = count($this->_fortunes)// number of fortunes
  926.         $this->maxLength = $max;             // longest fortune
  927.         $this->minLength = $min;             // shortest fortune
  928.         if (empty($this->flags)) {
  929.             $this->flags = 0;
  930.         }
  931.  
  932.         // Write header file
  933.         $this->_writeHeader();
  934.     }
  935.  
  936.     /**
  937.      * Write the header file for a fortune file
  938.      *
  939.      * Writes a fortune header file to {@link $headerFilename}, using
  940.      * {@link $version}{@link $numstr}{@link $maxLength},
  941.      * {@link $minLength}{@link $flags}{@link $delim}, and {@link $offsets}.
  942.      *
  943.      * If an error occurs, an exception is thrown.
  944.      *
  945.      * @return void 
  946.      * @throws File_Fortune_Exception
  947.      */
  948.     protected function _writeHeader(
  949.     {
  950.         $headerFile $this->getHeaderFile();
  951.         if (empty($headerFile)) {
  952.             $headerFile $this->getFile('.dat';
  953.             $this->setHeaderFile($headerFile);
  954.         }
  955.  
  956.         $header pack(
  957.             'NNNNNaxxx'
  958.             $this->version,
  959.             $this->numstr,
  960.             $this->maxLength,
  961.             $this->minLength,
  962.             $this->flags,
  963.             $this->delim
  964.         );
  965.  
  966.         // Pack offsets
  967.         $offsetBin '';
  968.         foreach ($this->offsets as $offset{
  969.             $offsetBin .= pack('N'$offset);
  970.         }
  971.  
  972.         // Write header file
  973.         if (false === ($fh fopen($headerFile'w'))) {
  974.             throw new File_Fortune_Exception(
  975.                 'Unable to open header file "' $headerFile '" for writing'
  976.             );
  977.         }
  978.         if (!flock($fhLOCK_EX)) {
  979.             throw new File_Fortune_Exception(
  980.                 'Unable to obtain lock on header file'
  981.             );
  982.         }
  983.         fwrite($fh$header $offsetBin);
  984.         flock($fhLOCK_UN);
  985.         fclose($fh);
  986.     }
  987. }
  988.  
  989. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

Documentation generated on Mon, 11 Mar 2019 15:06:40 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.