<?php

/**
 * Snapshot File Stream Wrapper
 *
 * This class creates a new stream type that can be used when reading/writing files.
 * The class creates snapshot versions of files upon changes, and can additionally
 * retreive the contents of said file when given an arbitrary date.
 *
 * When you call the stream wrapper using snapshot://path/to/file, it will behave as if you
 * are accessing the normal local filesystem. The only difference comes into effect when
 * you attempt to modify the file, either by writing to it with fwrite or fput, or 
 * opening the file with a mode that alters the file such as mode 'w'. An unmodified
 * copy of the file will be created with an dated extension. For example, file.txt would
 * become file.txt.20061231153000. example1.php shows this functionality.
 *
 * When reading, specify your normal file path (eg. snapshot://path/to/file). You can however
 * now additionally specify a date and time to view the file with. For example, accessing
 * snapshot://path/to/file+20061231153500 would specify that you want to view the file as it 
 * was at 15:35 on 31/Dec/2006. NOTE: You cannot write to a file using this mode. Any
 * to use a mode other than 'r' will result in an error. example2.php shows this
 *  functionality.
 *
 * @author Rick Hodger <rick@fuzzi.org.uk>
 * @version 1.0
 * @package filesnap
 * @example example1.php
 * @example example2.php
 */

class filesnap {

    var 
$pos 0;
    var 
$fp 0;
    var 
$madediff false;
    var 
$src '';

    function 
stream_open($path$mode$options, &$opened_path) {
        
$this->madediff=false;
        
$path substr($path,11);
        if (
substr_count($path,'+') > 0) {
            
// find a specific dated version
            
if ($mode==='r') {
                list(
$this->src,$get)=explode('+',$path);
                foreach (
glob($this->src.".*") as $x) {
                    
$a[]=substr($x,strlen($this->src)+1);
                }
                
$current=date('YmdHis',filemtime($this->src));
                
sort($a);
                if(
$get >= $current) {
                    
// do nothing, current src file is correct
                
} elseif ($get <= $a[0]) {
                    
$this->src $this->src.'.'.$a[0];
                } else {
                    
// somewhere in between
                    
$i=0;
                    foreach(
$a as $b) {
                        if (
$get $b) {
                            break;
                        }
                    }
                    
$this->src $this->src.'.'.$b;
                }
            } else {
                
// writing to a dated file is bad, return a failure on anything but a read attempt
                
return false;
            }
        } else {
            
$this->src=$path;
        }
        
$opened_path=$this->src;
        if (
$mode==='w' || $mode==='w+') {
            
$this->dodiff();
        }
        if (
$mode==='r' || !file_exists($this->src)) {
            
$this->madediff=true;
        }
        if (
$this->fp fopen($this->src,$mode)) {
            
$this->pos ftell($this->fp);
            return 
true;
        } else {
            return 
false;
        }
    }

    function 
stream_close() {
        
fclose($this->fp);
    }

    function 
stream_read($count) {
        return 
fread($this->fp,$count);
    }

    function 
dodiff() {    
        if (
$this->madediff==false) {
            
copy($this->src,$this->src.'.'.date('YmdHis'));
            
$this->madediff=true;
        }
    }

    function 
stream_write($data) {
        
$this->dodiff();
        if (
fwrite($this->fp,$data) != false) {;
            
$this->pos $this->pos strlen($data);
            return 
strlen($data);
        } else {
            return 
false;
        }
    }

    function 
stream_eof() {
        return 
feof($this->fp);
    }

    function 
stream_tell() {
        return 
$this->pos;
    }

    function 
stream_seek($offset$whence) {
        if (
fseek($this->fp,$offset,$whence)) {
            
$this->pos ftell($this->fp);
            return 
true;
        } else {
            return 
false;
        }
    }

    function 
stream_flush() {
        
fflush($this->fp);
    }

    function 
stream_stat() {
        
fstat($this->fp);
    }
}

stream_wrapper_register('snapshot','filesnap')
    or die(
'Failed to register snapshot protocol!');

?>