Location: PHPKode > scripts > Lightweight Club Calendar > lc-calendar-0.9.4/drivers/event/file.class.php
<?php
/**
* File based EventDriver for LCC
*
* PHP Version 4, 5
*
* @author <hide@address.com>
* @copyright Copyright (c) 2006, Benedikt Hallinger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, 51 Franklin St, Fifth Floor,
* Boston, MA  02110-1301  USA
*/

/**
* A Driver that is capaple of storing and retrieving Eventdata from flat files
*
* It is meant for small installations, because several race conditions exists where data could
* possibly be overriden, if too much people are editing the same event at the same time.
* Consider putting the storage place into a linux tmpfs or use another driver.
* However, it implements some kind of index file to speed up the event creation tasks etc a bit.
*
* Possible configuration:
*  'datapath'    Directory where the events data is stored, full path with trailing slash.
*                Default is the subdirectory 'filestore' of LCC_Core's 'datadir'
*/
class LCC_EventDriver_File extends LCC_EventDriver
{
	/**
	* Default config of the driver
	*
	* This gets overwritten by the core calling setConfig()
	* The default value of 'datapath' is set
	*
	* @var array
	*/
	var $config = array(
			'datapath' => ''  //where to store the files; default set by init()
		);
	
	/**
	* Returns the IDs of events for a given date
	*
	* @param int $timestamp       UNIX timestamp for the day in question
	* @return false|array         Indexed array containing the ID's of events for $timestamp
	*/
	function getEventsForDate($timestamp)
	{
		$this->_checkConfig();
		$return = false;  // Default: assume error
		if (is_int($timestamp) && $timestamp >= 0) {
		
			$contents = $this->_getIndex();

			if (!$contents) {
				$return = array();  // Index file not laoded or empty, so we assume no events
			} else {
				$return = array();  // Index returned correct value; assume no events for this day
				foreach ($contents as $line_raw){
					$line = split('\|',$line_raw);
					$id = trim($line[0]);
					$start = trim($line[1]);
					$end = trim($line[2]);
					
					
					// Convert start and end date to timestamp if neccessary
					// This is for compatibility to older storing mechanisms
					if (is_numeric($start)) {
						$ts_start = $start;
					} else {
						$ts_start = strtotime($start);
					}
					if (is_numeric($end)) {
						$ts_end = $end;
						
					} else {
						$ts_end = strtotime($end);
					}


					if ($ts_start == -1 || $ts_end == -1) {
						$return = false;
						break;
					}

					if ($ts_start <= $timestamp && $ts_end >= $timestamp) {
						//Match found!
						$return[] = $id;
					}
					
				}
			}
		}
		return $return;
	}

	/**
	* Used by the core to fetch all Data from the Event $id from the backend.
	*
	* The return value must be a associative array containing $key => $value pairs.
	* If there is no such event in the backend, return false - this will be used by the core
	* to test if a event exists.
	*
	* @return array|false
	*/
	function getData($id)
	{
		$this->_checkConfig();
		$data = false; // By default, assume event doesn't exist.
		// Construct the filename of the events file
		if ($this->_searchIndexForID($id)) {
			$filename = $this->config['datapath'].'event_'.$id;
			if (!is_readable($filename)) {
				die('<b>LCC_EventDriver_File ERROR:</b> Event file "'.$filename.'" is not readable!');
			} else {
				$file_conent = file($filename);
				if (is_array($file_conent)) {
					// Okay, parse the content and return the data
					// Syntax is KEY=Value
					$data = array();
					foreach ($file_conent as $line) {
						if (preg_match("/^(.+)=(.+)$/", $line, $match)) {
							// There are storage problems with newlines in our flat files,
							// here we decode what _writeEventData() encodes
							$data[$match[1]] = str_replace('-§-', PHP_EOL, $match[2]);
						}
					}
				} else {
					die('<b>LCC_EventDriver_File ERROR:</b> Could not contents of event file "'.$filename.'"');
				}
			}
		}

		return $data;
	}

	/**
	* This method is used to write data into the backend. $data is a associative array similar to the return value of getData().
	*
	* If $id is empty (= false, null or '') or not present in the backend, a new event must be created.
	* Otherwise the present data must be updated.
	* Return the ID of the event written or false, depending if storage succeedet
	*
	* @return int|false
	*/
	function storeData($id, $data)
	{
		$this->_checkConfig();
		if (!is_array($data)) return false;
		
		// Check if the event already exists
		// yes: just update the event
		// no: determine new id and create event file
		if ($this->_searchIndexForID($id)) {
			$result= $this->_writeEventData($id, $data);
			if ($result !== false) {
				$this->_updateIndexFile($id, $data);
				return $id;
			} else {
				return false;
			}
		} else {
			// find the next free ID
			$id = 0;    //where to start from
			$contents = $this->_getIndex();
			if (empty($contents)) {
				// no index file exists so far, start with ID=1
				$id++;
			} else {
				foreach ($contents as $line_raw) {
					$line = split('\|',$line_raw);
					$index_id = $line[0];
					if ($max_id < $index_id) $max_id = $index_id;
				}
				$id = $max_id +1;
			}
			
			// write new data
			// and update index
			$result = $this->_writeEventData($id, $data);
			if ($result !== false) {
				$this->_updateIndexFile($id, $data);
				return $id;
			} else {
				return false;
			}
		}
	}

	/**
	* This is used to delete this specific event in the backend.
	*
	* @return true|false
	*/
	function deleteEvent($id)
	{
		 $filename = $this->config['datapath'].'event_'.$id;
		 
		 $this->_checkConfig();
		 $this->_updateIndexFile($id, 'delete'); // Delete from index
		 if (file_exists($filename)) {
		 	return unlink($filename);
		 } else {
			return true;
		 }
	}
	
	/**
	* This, optional, method is called at driver loading time if it exists
	*
	* In this case, we need this feature, because we want to use LCC's
	* default data directory to store our data into.
	* This cant be set in a custom constructor since we have no access to the
	* core object there.
	*/
	function init()
	{
		/*
		* set 'datapath' to datadir of LCC_Core
		*/
		$datadir = $this->lcc_core->getConfig('data_dir'); // fetch core config
		
		// Correct the path if its not a path - a trailing / is expected
		if (substr($datadir,-1,1) != '/') {
			$datadir .= '/';
		}
		
		// set our default value
		$this->setConfig(array('datapath' => $datadir.'filestore/'));
	}

	
	
	
	/*******************************************************************
	* Here come some driver internal methods that make our life easier *
	*******************************************************************/
	
	/**
	* Verify that the drivers config is loaded and sane
	*
	* @return true      Return true or dies
	* @access private
	*/
	function _checkConfig()
	{
		if(!is_array($this->config) ||
		   !isset($this->config['datapath'])) {
			die('<b>LCC_EventDriver_File ERROR:</b> Configuration of driver not found or not valid');
		}

		// Correct the path if its not a path - a trailing / is expected
		if (substr($this->config['datapath'],-1,1) != '/') {
			$this->config['datapath'] .= '/';
		}
		
		// check that filepath is accessible
		if (!is_writable($this->config['datapath'])) {
			die('<b>LCC_EventDriver_File ERROR:</b> Configured datapath "'.$this->config['datapath'].'" is not writable!');
		}
		
		return true;
	}

	/**
	* This actually fetches the data from the index file
	*
	* @todo maybe implement a cache
	* @return array      Content of the index file (raw data)
	* @access private
	*/
	function _getIndex()
	{
		$this->_checkConfig();
		$contents = array();// index file is not here, so we assume there are no events. just return an empty array
		$indexfile = $this->config['datapath'].'event_index';
		if (file_exists($indexfile)) {
			if (!is_readable($indexfile)) {
				die('<b>LCC_EventDriver_File ERROR:</b> Could not open indexfile: '.$indexfile);
			} else {
				$contents = file($indexfile);
				if (!is_array($contents)) {
					die('<b>LCC_EventDriver_File ERROR:</b> Could not get contents of indexfile (but is was here and readable?): '.$indexfile);
				}
			}
		}

		return $contents;
	}
	
	/**
	* Update the index file entry
	*
	* The index file gets used to determine the id's and start/end times of events.
	* This driver only honors the start and end days of an event, not its start and end times.
	*
	* @param int          $id      ID of the event to be updated
	* @param array|string $data    Events data (needed to get the dates), or string 'delete' to delete the event from index
	* @access private
	*/
	function _updateIndexFile($id, $data)
	{
		$this->_checkConfig();
		
		if ($data === 'delete') {
			$delete_event = true;
		} else {
			if (!is_numeric($data['start_date'])
		    || !is_numeric($data['end_date'])) {
			die('<b>LCC_EventDriver_File ERROR:</b> Unable to update indexfile, $data invalid!');
				$delete_event = false;
			}
		}
		
		$indexfile = $this->config['datapath'].'event_index';
		if (!is_writable($indexfile) && file_exists($indexfile)) {
			die('<b>LCC_EventDriver_File ERROR:</b> Could not open indexfile in write mode: '.$indexfile);
		} else {
			if ($this->_searchIndexForID($id)) {
				// Entry exists, update
	
				// Maybe we have a nice possibility where we can just float over the contents
				// and only replace the line neccessary
				$contents = $this->_getIndex();
	
				
				$i_handle = fopen($indexfile, 'w');
				flock($i_handle, LOCK_EX);
				foreach ($contents as $line) {
					if (preg_match('/^'.$id.'\|/', $line)) {
						// this is the id we want to update
						// if we want to delete this event from index, just dont write it
						if (!$delete_event) {
							fwrite($i_handle, $id.'|'.$data['start_date'].'|'.$data['end_date'].PHP_EOL);
						}
					} else {
						fwrite($i_handle, $line);
					}
				}
				flock($i_handle, LOCK_UN);
				fclose($i_handle);
			} else {
				// Entry doesn't exist, add
				$i_handle = fopen($indexfile, 'a');
				if ($i_handle == false) {
					die('<b>LCC_EventDriver_File ERROR:</b> Unable to update indexfile '.$indexfile);
				}
				flock($i_handle, LOCK_EX);
				fwrite($i_handle, $id.'|'.$data['start_date'].'|'.$data['end_date'].PHP_EOL);
				flock($i_handle, LOCK_UN);
				fclose($i_handle);
			}
		}
	}
	
	/**
	* Is there a event with id $id in the index file?
	*
	* @param int      $id      ID of the event to be found
	* @return boolean
	*/
	function _searchIndexForID($id)
	{
		$contents = $this->_getIndex();
		$return = array();
		if (!$contents) {
			return false;  // Index file is empty, so we assume no events
		} else {
			foreach ($contents as $line_raw){
				$line = split('\|',$line_raw);
				$index_id = $line[0];
				if ($id == $index_id) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	* This writes data to a event file
	*
	* If the file exists, it will be updatet, if not it will be created.
	*
	* @param $id     ID of the event to be written
	* @param $data   associative data array (field => value)
	* @return boolean
	* @access private
	*/
	function _writeEventData($id, $data)
	{
		$this->_checkConfig();
		
		// Construct the filename of the events file
		$filename = $this->config['datapath'].'event_'.$id;
		
		if (file_exists($filename) && !is_writable($filename)) {
			die('<b>LCC_EventDriver_File ERROR:</b> Event file "'.$filename.'" is not writable!');
		}
		
		// Write to event file
		$handle = fopen($filename, 'w');
		if ($handle == false) {
			die('<b>LCC_EventDriver_File ERROR:</b> Unable to create new event file '.$filename);
		}
		
		flock($handle, LOCK_EX);
		// there is a problem with newlines in our flat file, we must encode them
		foreach ($data as $field => $value) {
			fwrite($handle, $field.'='.str_replace(PHP_EOL, '-§-', $value).PHP_EOL);
		}
		flock($handle, LOCK_UN);
		fclose($handle);
		
		return true;
	}
}
?>
Return current item: Lightweight Club Calendar