<?php

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * LogRotation.php
 *
 * @category     Core
 * @package      Core_logging
 * @copyright    Manuela v.d.Decken <manuela@isteam.de>
 * @author       Manuela v.d.Decken <manuela@isteam.de>
 * @license      http://www.gnu.org/licenses/gpl.html   GPL License
 * @version      0.0.1
 * @revision     $Revision: $
 * @lastmodified $Date: $
 * @since        File available since 29.06.2015
 * @description  handles logfiles and compress it if they are too big(like server logs)
 */
class LogRotation
{
    protected $iMaxLogfileSize = 20;   // size as MB
    protected $iMaxLogfiles    = 10;   //
    protected $iScanFrequency  = 3600; // scan each hour
    protected $sLogPath        = '';   // AppPath + 'var/log'
    protected $aLogFiles       = null; // like 'error.log'
    protected $aLogFilesGz     = null; // like 'error.log.1.gz'

/**
 * constructor
 * @param WbAdaptor $oReg
 * @throws AppException
 */
    public function __construct(WbAdaptor $oReg)
    {
        $this->sLogPath = $oReg->VarPath.'log/';
        if (!file_exists($this->sLogPath)) {
            $iOldUmask = umask(0);
            if (!mkdir($this->sLogPath, $oReg->FileMode, true)) {
                throw new AppException('unable to create LogPath ['.$this->sLogPath.']');
            }
            umask($iOldUmask);
        }
    }
/**
 * proceed compression if needed
 */
    public function execute()
    {
        if ($this->checkSemaphore($this->sLogPath.'LOCK')) {
            // scan for uncompressed log files
            $this->aLogFiles = $this->scanForFiles($this->sLogPath);
            foreach($this->aLogFiles as $sFile) {
            // iterate matching log files
                if ($this->isLogFileFull($this->sLogPath.$sFile)) {
                // start processing if uncompressed file exceeds limit
                    $aGzFiles = $this->scanForFiles($this->sLogPath, $sFile.'.*.gz');
                    // rename existing gz files
                    $this->renameExistingGzFiles($aGzFiles);
                    // compress logfile into gz archive
                    $this->compressFile($this->sLogPath.$sFile, $this->sLogPath.$sFile.'.0.gz');
                    // remove logfile (next entry will create new one)
                    unlink($this->sLogPath.$sFile);
                }
            }
        }
    }
/**
 *
 * @param string $sSemaphoreFile file which holds the timestamp from last scan
 * @return bool
 * @throws AppException
 */
    protected function checkSemaphore($sSemaphoreFile)
    {
        // test if readable file exists
        if (!is_readable($sSemaphoreFile)) {
            // try to create with current timestamp
            if (!touch($sSemaphoreFile)) {
                throw new AppException('unable to create semaphore ['.$sSemaphoreFile.']');
            }
        } else {
            // return true if it's time for next scan
            return ((filemtime($sSemaphoreFile) + $this->iScanFrequency) > time());
        }
    }
/**
 * compress source file into target file
 * @param string $sSourceFile
 * @param string $sTargetFile
 */
    protected function compressFile($sSourceFile, $sTargetFile)
    {
        // try to write/create target file
        if (touch($sTargetFile)) {
            $oSource = gzopen($sSourceFile, 'rb');
            $oTarget = gzopen($sTargetFile, 'wb9');
            if ($oSource && $oTarget) {
                while (!gzeof($oSource)) {
                    // read 64k buffer
                    $sBuff = gzread($oSource, 64*1024);
                    $iBuffLen = strlen($sBuff);
                    // write the buffer
                    gzwrite($oTarget, $sBuff, $iBuffLen);
                }
                // close files and clear buffer
                gzclose($oSource);
                gzclose($oTarget);
                unset($sBuff);
            }
        }
    }
/**
 * rename files to index+1 and delete files with index >= iMaxLogfiles
 * @param array $aGzFiles
 * @param string $sBasefile
 */
    protected function renameExistingGzFiles(array $aGzFiles, $sBasefile)
    {
        natsort($aGzFiles);
        $aGzFiles = array_merge(array_reverse($aGzFiles));
        $sPattern = '/^'.preg_quote($sBasefile, '/').'\.([0-9]+)\.gz$/s';
        foreach ($aGzFiles as $iKey=>$sFile) {
            if ($iKey < $this->iMaxLogfiles) {
                if (preg_match($sPattern, $sFile, $aMatch)) {
                    $sNewFile = $sBasefile.'.'.(string)($aMatch[1]++).'.gz';
                    rename($this->sLogPath.$sFile, $this->sLogPath.$sNewFile);
                }
            } else {
                unlink($this->sLogPath.$sFile);
            }
        }
    }
/**
 * check size of file
 * @param string $sFile
 * @return bool true if filesize greater than iMaxLogfileSize
 */
    protected function isLogFileFull($sFile)
    {
        $iSize = (int)floor(filesize($sFile) / pow(2, 20));
        return ($iSize >= $this->iMaxLogfileSize);
    }
/**
 * scan for log files
 * @param string $sLogPath
 * @param string $sPattern
 * @return array  list of files
 */
    protected function scanForFiles($sLogPath, $sPattern = '*.log')
    {
        $sOldDir = getcwd();
        chdir($sLogPath);
        $aRetval = glob($sPattern, GLOB_NOSORT);
        chdir($sOldDir);
        return $aRetval;
    }

} // end of class LogRotation
