<?php
define('BACKUP_EXT', 'tar');
define('ACCESS_TYPE', 'backup');

/**
 * @package Campcaster
 * @subpackage StorageServer
 * @copyright 2010 Sourcefabric O.P.S.
 * @license http://www.gnu.org/licenses/gpl.txt
 */
class Backup
{
    /**
     * Name of logfile
     * @var string
     */
    private $logFile;

    /**
     * Session id
     * @var string
     */
    private $sessid;

    /**
     * struct - see search criteria
     * @var array
     */
    private $criteria;

    /**
     * @var string
     */
    private $token;

    /**
     * name of statusfile
     * @var string
     */
    private $statusFile;

    /**
     * Affected gunids
     * @var array
     */
    private $ids;

    /**
     * Array of affected filenames
     * @var array
     */
    private $filenames  = array();

    /**
     * Base tmp name
     * @var string
     */
    private $tmpName;

    /**
     * Name of temporary tarball file
     * @var string
     */
    private $tmpFile;

    /**
     * Name of temporary directory
     * @var string
     */
    private $tmpDir;

    /**
     * Name of temporary playlist directory
     * @var string
     */
    private $tmpDirPlaylist;

    /**
     * Name of temporary audioclip directory
     * @var string
     */
    private $tmpDirClip;

    /**
     * Name of temporary metafile directory
     * @var string
     */
    private $tmpDirMeta;

    /**
     * @var string
     */
    private $loglevel = 'warn';  # 'debug';

    /**
     * @var GreenBox
     */
    private $gb;

    /**
     * @param GreeenBox $gb
     */
    public function __construct(&$gb)
    {
        global $CC_CONFIG;
        $this->gb =& $gb;
        $this->token = null;
        $this->logFile = $CC_CONFIG['bufferDir'].'/'.ACCESS_TYPE.'.log';
        $this->addLogItem("-I- ".date("Ymd-H:i:s")." construct\n");
    }


    /**
     * Open a backup
     *      Create a backup file (tarball)
     *
     * @param string $sessid
     * @param array $criteria
     * 		struct - see search criteria
     * @return array
     * 		hasharray with field:
     *      token string: backup token
     */
    public function openBackup($sessid, $criteria='')
    {
        if ($this->loglevel=='debug') {
            $this->addLogItem("-I- ".date("Ymd-H:i:s")." openBackup - sessid:$sessid\n");
        }
        $this->sessid   = $sessid;
        $this->criteria = $criteria;

        // get ids (and real filenames) which files match with criteria
        $srch = $this->gb->localSearch($this->criteria,$this->sessid);
        if (PEAR::isError($srch)) {
        	return $srch;
        }
        $this->setIDs($srch);

        // get real filenames
        if (is_array($this->ids)) {
            $this->setFilenames();

            $this->setEnviroment(true);

            // write a status file
            file_put_contents($this->statusFile, 'working');

            // save the metafile to tmpdir
            $hostname = trim(`hostname`);
            $ctime      = time();
            $ctime_f    = date("Ymd-H:i:s");
            file_put_contents("{$this->tmpDirMeta}/storage.xml",
                "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n".
                "<storage\n".
                " type=\"".ACCESS_TYPE."\"\n".
                " version=\"1.0\"\n".
                " ctime=\"$ctime\"\n".
                " hostname=\"$hostname\"\n".
                "/><!-- $ctime_f -->\n"
            );

            // copy all file to tmpdir
            $this->copyAllFiles();

            // do everything
            $this->doIt();

            return array('token'=>$this->token);
        } else {
        	return false;
        }
    }


    /**
     * Check the status of backup.
     *
     * @param unknown $token
     * @return array
     *      status  : string - susccess | working | fault
     *      faultString: string - description of fault
     *      token   : stirng - backup token
     *      url     : string - access url
     *      tmpfile : string - access filename
     */
    public function checkBackup($token)
    {
        global $CC_CONFIG;
        if ($this->loglevel=='debug') {
            $this->addLogItem("-I- ".date("Ymd-H:i:s")." checkBackup - token:$token\n");
        }
        $this->token = $token;
        $this->setEnviroment();
        $status = file_get_contents($this->statusFile);
        if (strpos($status,'fault')!==false) {
            list($status,$faultString) = explode('|',$status);
        }
        switch ($status) {
            case 'success':
                $r['url'] = BasicStor::GetUrlPart()."access/$token.".BACKUP_EXT;
                $r['tmpfile'] = $CC_CONFIG['accessDir']."/$token.".BACKUP_EXT;
            case 'working':
            case 'fault':
                $r['status'] = $status;
                $r['faultString'] = $faultString;
                $r['token'] = $token;
                break;
        }
        return $r;
    }


    /**
     * Close a backup
     *
     * @param unknown $token
     * @return boolean
     */
    public function closeBackup($token)
    {
        if ($this->loglevel=='debug') {
            $this->addLogItem("-I- ".date("Ymd-H:i:s")." closeBackup - token:$token\n");
        }
        # post procedures
        $this->token = $token;
        $this->setEnviroment();
        BasicStor::bsRelease($token, ACCESS_TYPE);
        Backup::rRmDir($this->tmpDir);
        unlink($this->statusFile);
        unlink($this->tmpFile);
        if (is_file($this->tmpName)) {
        	unlink($this->tmpName);
        }
        return !is_file($this->tmpFile);
    }


    /**
     *  list of unclosed backups
     *
     *  @param string $stat
     *      if this parameter is not set, then return with all unclosed backups
     *  @return array of hasharray with field:
     *      status : string - susccess | working | fault
     *      token  : stirng - backup token
     *      url    : string - access url
     */
    public function listBackups($stat='')
    {
        if ($this->loglevel=='debug') {
            $this->addLogItem("-I- ".date("Ymd-H:i:s")." listBackups - stat:$stat\n");
        }
        // open temporary dir
        $tokens = BasicStor::GetTokensByType(ACCESS_TYPE);
        // echo '<XMP>tokens:'; print_r($tokens); echo '</XMP>';
        foreach ($tokens as $token) {
            $st = $this->checkBackup($token);
            if ($stat=='' || $st['status']==$stat) {
                $r[] = $st;
            }
        }
        return $r;
    }


    /**
     * Set the ids from searchResult
     *
     * @param array $searchResult : array of gunids
     */
    private function setIDs($searchResult)
    {
        if ($this->loglevel=='debug') {
            $this->addLogItem("-I- ".date("Ymd-H:i:s")." setIDs\n");
        }
        if (is_array($searchResult['results'])) {
            $this->ids = $searchResult['results'];
        } else {
            $this->addLogItem("-E- ".date("Ymd-H:i:s")." setIDs - the parameter is not array!\n");
            return PEAR::raiseError('The IDs variable isn\'t array.');
        }
    }


    /**
     * Set the filenames from ids.
     *
     */
    private function setFilenames()
    {
        if ($this->loglevel=='debug') {
            $this->addLogItem("-I- ".date("Ymd-H:i:s")." setFilenames\n");
        }
        if (is_array($this->ids)) {
            foreach ($this->ids as $i => $item) {
                $gunid = $item['gunid'];
                // get a stored file object of this gunid
                $sf = StoredFile::RecallByGunid($gunid);
                if (is_null($sf) || PEAR::isError($sf)) {
                	return $sf;
                }
                $lid = BasicStor::IdFromGunid($gunid);
                if (($res = BasicStor::Authorize('read', $lid, $this->sessid)) !== TRUE) {
                    $this->addLogItem("-E- ".date("Ymd-H:i:s")." setFilenames - authorize gunid:$gunid\n");
                    return PEAR::raiseError('Backup::setFilenames : Authorize ... error.');
                }
                // if the file is a playlist then it has only a meta file
                if (strtolower($sf->md->format) != 'playlist') {
                    $this->filenames[] = array(
                        'filename'  => $sf->getRealFileName(),
                        'format'    => $sf->md->format
                    );
                }
                $this->filenames[] = array(
                    'filename'  => $sf->getRealMetadataFileName(),
                    'format'    => $sf->md->format
                );
                if ($this->loglevel=='debug') {
                    $this->addLogItem("-I- ".date("Ymd-H:i:s")." setFilenames - add file: {$sf->md->format}|".$sf->getRealMetadataFileName()."\n");
                }
            }
            return $this->filenames;
        } else {
            $this->addLogItem("-E- ".date("Ymd-H:i:s")." setFilenames - The IDs variable isn't array.\n");
            return PEAR::raiseError('Backup::setFilenames : The IDs variable isn\'t array.');
        }
    }


    /**
     * Create the tarball - call the shell script
     *
     */
    private function doIt()
    {
        if ($this->loglevel=='debug') {
            $this->addLogItem("-I- ".date("Ymd-H:i:s")." doIt\n");
        }
        $command = dirname(__FILe__)."/../bin/backup.sh"
            ." {$this->tmpDir}"
            ." {$this->tmpFile}"
            ." {$this->statusFile}"
            ." >> {$this->logFile} &";
        $res = system("$command");
        sleep(2);
        if ($this->loglevel=='debug') {
            $this->addLogItem("-I- ".date("Ymd-H:i:s")." doIt - command:$command\n");
        }
    }


    /**
     *  Copy the real files into the tmp dirs to tar they.
     *
     */
    private function copyAllFiles()
    {
        if ($this->loglevel=='debug') {
            $this->addLogItem("-I- ".date("Ymd-H:i:s")." copyAllFiles\n");
        }
        //echo '<XMP>this->filenames:'; print_r($this->filenames); echo '</XMP>';
        if (is_array($this->filenames)) {
            foreach ($this->filenames as $v) {
                # get the filename from full path
                $fn = substr($v['filename'],strrpos($v['filename'],'/'));
                switch (strtolower($v['format'])) {
                    case 'playlist':
                        # if playlist then copy to the playlist dir
                        copy($v['filename'],$this->tmpDirPlaylist.$fn);
                        break;
                    case 'audioclip':
                        # if audioclip then copy to the audioclip dir
                        copy($v['filename'],$this->tmpDirClip.$fn);
                        break;
                }
            }
        }
    }


    /**
     *  Figure out the enviroment to the backup
     *
     */
    private function setEnviroment($createDir=false)
    {
        global $CC_CONFIG;
        if ($this->loglevel=='debug') {
            $this->addLogItem("-I- ".date("Ymd-H:i:s")." setEnviroment - createDirs:$createDir\n");
        }
        // create temporary directories
        if (is_null($this->token) && $createDir) {
            $this->tmpName = tempnam($CC_CONFIG['bufferDir'], ACCESS_TYPE.'_');
            $this->tmpFile = $this->tmpName.'.'.BACKUP_EXT;
            $this->tmpDir = $this->tmpName.'.dir';
            $this->tmpDirPlaylist = $this->tmpDir. '/playlist';
            $this->tmpDirClip = $this->tmpDir. '/audioClip';
            $this->tmpDirMeta = $this->tmpDir. '/meta-inf';
            touch($this->tmpFile);
            mkdir($this->tmpDir);
            mkdir($this->tmpDirPlaylist);
            mkdir($this->tmpDirClip);
            mkdir($this->tmpDirMeta);
            $this->genToken();
        } else {
            $symlink = $CC_CONFIG['accessDir'].'/'.$this->token.'.'.BACKUP_EXT;
            if (is_link($symlink) && is_file(readlink($symlink))) {
                $this->tmpName          = str_replace('.tar','',readlink($symlink));
                $this->tmpFile          = $this->tmpName.'.'.BACKUP_EXT;
                $this->tmpDir           = $this->tmpName.'.dir';
                $this->tmpDirPlaylist   = $this->tmpDir. '/playlist';
                $this->tmpDirClip       = $this->tmpDir. '/audioClip';
                $this->tmpDirMeta       = $this->tmpDir. '/meta-inf';
            } else {
                $this->addLogItem("-E- ".date("Ymd-H:i:s")." setEnviroment - Corrupt symbolic link.\n");
                return false;
            }
        }
        $this->statusFile = $CC_CONFIG['accessDir'].'/'.$this->token.'.'.BACKUP_EXT.'.status';
        if ($this->loglevel=='debug') {
            $this->addLogItem("this->tmpName: $this->tmpName\n");
            $this->addLogItem("this->tmpFile: $this->tmpFile\n");
            $this->addLogItem("this->tmpDir: $this->tmpDir\n");
            $this->addLogItem("this->tmpDirPlaylist: $this->tmpDirPlaylist\n");
            $this->addLogItem("this->tmpDirClip: $this->tmpDirClip\n");
            $this->addLogItem("this->tmpDirMeta: $this->tmpDirMeta\n");
            $this->addLogItem("this->token: $this->token\n");
            $this->addLogItem("this->statusFile: $this->statusFile\n");
        }
    }


    /**
     * Generate a new token.
     * @return void
     */
    private function genToken()
    {
        $acc = BasicStor::bsAccess($this->tmpFile, BACKUP_EXT, null, ACCESS_TYPE);
        if (PEAR::isError($acc)) {
        	return $acc;
        }
        $this->token = $acc['token'];
    }


    /**
     * Add a line to the logfile.
     *
     * @param string $item
     * 		the new row of log file
     */
    private function addLogItem($item)
    {
        $f = fopen($this->logFile,'a');
        fwrite($f,$item);
        fclose($f);
    }


    /**
     * Delete a directory recursive
     *
     * @param string $dirname
     * 		path of dir.
     */
    private static function rRmDir($dirname)
    {
        if (is_dir($dirname)) {
            $dir_handle = opendir($dirname);
        }
        while ($file = readdir($dir_handle)) {
            if ( ($file != ".") && ($file != "..") ) {
                if (!is_dir($dirname."/".$file)) {
                    unlink ($dirname."/".$file);
                } else {
                    Backup::rRmDir($dirname."/".$file);
                }
            }
        }
        closedir($dir_handle);
        rmdir($dirname);
        return true;
    }

} // classs Backup
?>