<?php
require_once('includes/PEAR.php');
require_once('includes/Tar.php');
@ini_set('max_execution_time', 3600 );
//const PART_SIZE = 262144;
//define('PART_SIZE',524288);
define('PART_SIZE',1048576);
//define('PART_SIZE',2097152);
//define('PART_SIZE',4194304);
//define('PART_SIZE',8388608);
if(function_exists('set_magic_quotes_runtime')){
	set_magic_quotes_runtime(0);
}
class MTTar extends Archive_Tar
{
	//get limit settings
	//get curl_enabled
	//get content list
	//run file extract
	var $ch;
	var $mode;
	var $max_execution_time;
	var $cache_id;
	var $_offset;
	var $debug;


	function MTTar($p_tarname, $p_compress = null)
	{
		$this->cache_id = md5(time().'TAR');
		parent::Archive_Tar($p_tarname, $p_compress = null);
	}

	function extract($p_path = '',$mode = null,$debug = false)
	{
		ignore_user_abort(true);
		$this->_showProgress(0);
		if($mode === null){
			$this->_getMode();
		}else{
			$this->mode = (int)$mode;
		}
		$this->debug = $debug;
		switch ($this->mode){
			case 0:$res = parent::extract($p_path);break;
			case 1:$res = $this->_safeExtract($p_path);break;
			case 2:$res = true;break;
			default:print 'unknown extract method';$res = false;

		}
		return $res;
	}

	function _getMode()
	{
		$time = ini_get('max_execution_time');
		if($time<3600){
			if(!extension_loaded('curl')){
				$this->mode = 2;
			}else{
				$this->mode = 1;
			}
		}else{
			$this->mode = 0;
		}
		$this->max_execution_time = $time;
	}
	function _safeExtract($p_path){
		if(!extension_loaded('curl')){
			print 'curl required';
			return false;
		}

		ignore_user_abort(true);
		$this->waitHeartBeat();
		$this->heartBeat();
		$this->getState();
		$this->_showProgress();
		$extract_result = true;

		$res = $this->listContent();
		$list = array();
		$total_size = 0;
		foreach ($res as $file){
			if(strpos($file['filename'],'?')!==false)continue;
			$list[] = array('filename'=>$file['filename'],'size'=>$file['size']);
			$total_size += $file['size'];
		}

		$part_count = $total_size/PART_SIZE;
		$part_size = 0;

		$lists = array();
		$part_current_count = 0;
		$part_current_size = 0;
		foreach ($list as $listElement){
			if($part_current_size>PART_SIZE){
				$part_current_count++;
				$part_current_size = 0;
					
			}
			if(!is_array($lists[$part_current_count])){
				$lists[$part_current_count] = array();
			}
			$lists[$part_current_count][] = $listElement['filename'];
			$part_current_size += $listElement['size'];

		}


		$path = pathinfo($_SERVER['REQUEST_URI']);
		$path = $path['dirname'];
		$url = 'http://'.$_SERVER['SERVER_NAME'].str_replace('//','/',$path.'/MTTar.php?extract');
		$total_time = 0;
		$part_count = count ($lists);


		$extract_result = true;
		$temp = array_keys($lists);
		foreach ($lists as $list_id=>$list){
			$this->heartBeat();
			$processed_id = $this->getProcessedItem();
			if($processed_id&&$processed_id >= $list_id){
				$this->debugLog("\n\b[".TIME_START."]\n----------------\n{$list_id}\n----------------\nskiped:{$processed_id}\n");
				$this->_showProgress(round(($list_id+1)*100/$part_count),$total_time);
				continue;
			}
			//		if(true)continue;
			$process_time = microtime(true);
			$ch = curl_init();
			//curl_
			@curl_setopt( $ch, CURLOPT_URL, $url);
			@curl_setopt( $ch, CURLOPT_POST, true);
			$postdata = 'file_list='.base64_encode(serialize($list)).
			'&extract_file_name='.base64_encode($this->_tarname).
			'&cache_id='.($this->cache_id).
			'&target_path='.base64_encode($p_path);
			@curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata);
			@curl_setopt($ch, CURLOPT_VERBOSE, 1);
			@curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
			@curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
			@curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
			@curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
			@curl_setopt( $ch, CURLOPT_TIMEOUT, 2 );
			@curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 2 );

			$responce = curl_exec($ch);
			$this->debugLog("\n\n[".TIME_START."]\n----------------\n{$list_id}\n----------------\n".
			sprintf('%3.3fs ',(microtime(true)-TIME_START)).sprintf('(%3.3fs) ',(microtime(true)-$process_time)).sprintf('%1.3fKb',memory_get_usage()/1024)."\n".
			"URL: {$url}\n");
			
			$this->_showProgress(round(($list_id+0.5)*100/$part_count),$total_time);

			$wait_count = 0;
			do{
				$wait_count++;
				$this->heartBeat();
				sleep(5);
				$res = $this->getState();
				$this->debugLog(sprintf('%3.3fs ',(microtime(true)-TIME_START)).sprintf('(%3.3fs) ',(microtime(true)-$process_time)).sprintf('%1.3fKb',memory_get_usage()/1024).($res?var_export($res,true):false)."\n");
			}while (!$res&&($wait_count<18));

			if($res&&isset($res['res'])&&$res['res']){
				$this->setProcessedItem($list_id);
			}

			$process_time = (microtime(true)-$process_time);
			$total_time += $process_time;
			$process_time = sprintf('%3.3fs',$process_time);
			//print "<br>processed #{$list_id} file list for <b>{$process_time}</b><br>";
			$progress = round(($list_id+1)*100/$part_count);
			$this->_showProgress($progress,$total_time);


			if($res){
					
					
				//	print "<i style=\"color:green\">{$responce}</i><br>";
				//	var_dump($res);
				//	print "<hr>";
				//	print "<pre>".var_export($res['debug_info'],true)."</pre><hr>";
				if(!$res['res']){
					print base64_decode($res['output']);
					$extract_result = false;
					break;
				}
			}else{
				$extract_result = false;
				if(curl_error($ch)){
					print curl_errno($ch).': '.curl_error($ch);
					global $locString;
					print $locString['please_try_again'];
				}else{
					//print "<br>Invalid server responce from <b>{$url}</b>:<br><textarea cols=\"45\" rows=\"6\">";
					//print strip_tags($responce);
					//print "</textarea>";
					print $locString['please_try_again'];
				}
				break;
			}

			curl_close($ch);
		}
		
		if($extract_result)$this->unsetProcessedItem();


		//print "<br><h2>Total time {$total_time}</h2>";
		/*
		 foreach ($lists as $list){
		 print "<hr>";
		 $total_size = 0;
		 $count = 0;
		 foreach ($list as $file){
			$count++;
			$total_size += $file['size'];
			}
			$total_size = round($total_size/1024,2);
			print "Total {$count} files {$total_size}<br>";
			}
			print $part_current_count;

			var_dump($_SERVER['REQUEST_URI']);
			var_dump($_SERVER['SERVER_NAME']);
			*/
		//print "FINISH".date("H:i:s");

		return $extract_result;
	}
	function _getOffset()
	{
		$offset = 0;
		$cache_file = '.tar_'.$this->cache_id;
		$fp = @fopen($cache_file,'r');
		if($fp){
			$offset = fread($fp,filesize($cache_file));
			fclose($fp);
			$offset = unserialize($offset);
			if($offset){
				$offset = $offset[0];
				$offset = $offset>0?$offset-512:$offset;
				$offset = $offset<0?0:$offset;
			}else{
				$offset = 0;
			}
		}
		$this->_offset = $offset;
		return $offset;

	}
	function _setOffset()
	{
		$cache_file = '.tar_'.$this->cache_id;
		$fp = @fopen($cache_file,'w');
		if($fp){
			fwrite($fp,serialize(array($this->_offset,date("H:i:s"))));
			fclose($fp);
		}
	}
	function _readBlock($p_len=null)
	{
		static $isStart;
		static $isFirst;
		if($isStart === null){$isStart = true;}
		if($isFirst === null){$isFirst = true;}
		if($this->_offset === null){$this->_offset = 0;}
		if($isStart&&!$isFirst){
			$offset = $this->_getOffset();
			if($offset>0){
				if ($this->_compress_type == 'gz')
				$v_block = @gzseek($this->_file, $offset);
				else if ($this->_compress_type == 'none')
				$v_block = @fseek($this->_file, $offset);
			}
			$isStart = false;
		}
		$isFirst = false;
		$v_block = parent::_readBlock($p_len);
		if ($v_block){$this->_offset = $this->_offset+512;}
		return $v_block;
	}

	function _showProgress($progress = 0,$time_passed = 0,$time_estimated = 0){
		global $locString;
		$fp = @fopen('progress.html','w');
		if($fp){
			$out = '<html><head><META HTTP-EQUIV="Refresh" CONTENT="10"><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head><body>';
			if($progress>=0){
				$out .= '<div style="font-size:14px;text-align:center; background:url(i2/greybg.gif) repeat-x;  line-height:0px;height:14px;width:400px;"><div style="background:url(i2/greenbg.gif) repeat-x; height:14px;width:'.intval($progress).'%"></div>'.$progress.'%</div>';
			}else{
				$out .= $locString['extracting_files'];
			}
			$out .= "</body></html>";
			fwrite($fp,$out);
			fclose($fp);
		}
	}

	function saveState($result,$tar_out,$debug_info = '')
	{
		$fp=@fopen('.extract.state','w');
		if($fp){
			fwrite($fp,serialize(array('res'=>$result,'output'=>base64_encode($tar_out),'debug_info'=>$debug_info)));
			fclose($fp);
		}

	}

	function getState()
	{
		$fp=@fopen('.extract.state','r');
		if($fp){
			$res = fread($fp,filesize('.extract.state'));
			$res = unserialize($res);
			if($res){
				$res['output'] = base64_decode($res['output']);
			}
			fclose($fp);
			clearstatcache();
			unlink('.extract.state');
		}else{
			$res = false;
		}
		return $res;
	}

	function heartBeat()
	{
		if($fp = fopen('.heartbeat','w')){
			fwrite($fp,serialize(time()));
			fclose($fp);
		}
	}
	function checkHeartBeat($wait_time){
		$res = false;
		if(file_exists('.heartbeat')&&($fp = fopen('.heartbeat','r'))){
			if($time = unserialize(fread($fp,filesize('.heartbeat')))){
				$res = ((time()-$time)<$wait_time);
			}
			fclose($fp);
		}
		return $res;
	}
	function waitHeartBeat($wait_time = 90, $time = 5)
	{
		while($this->checkHeartBeat($wait_time)){
			$this->debugLog("[".TIME_START."]\n-------\n".' waitHeartBeat');
			sleep($time);
		}
	}

	function removeHeartBeat(){
		if(file_exists('.heartbeat')){
			unlink('.heartbeat');
		}
	}

	function debugLog($data = '')
	{
		if($this->debug){
			if($fp = fopen('.debug.log','a')){
				fwrite($fp,$data."\n");
				fclose($fp);
			}
		}
	}
	
	function getProcessedItem()
	{
		$id = 0;
		if(file_exists('.resume_extract')&&($fp = fopen('.resume_extract','r'))){
			if(($data = unserialize(fread($fp,filesize('.resume_extract'))))&&((time()-$data['time'])<1200)){
				$id = isset($data['id'])?$data['id']:0;
			}else
			fclose($fp);
		}
		return $id;
	}
	function setProcessedItem($id)
	{
		if($fp = fopen('.resume_extract','w')){
			fwrite($fp,serialize(array('time'=>time(),'id'=>$id,'cache_id'=>$this->cache_id)));
			fclose($fp);
		}
	}
	function unsetProcessedItem()
	{
		if(file_exists('.resume_extract')){
			unlink('.resume_extract');
		}
	}


}




//////////////////////
//////////////////////


if(isset($_GET['extract'])){
	header('Pragma: public');
	header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");                  // Date in the past
	header('Last-Modified: '.gmdate('D, d M Y H:i:s') . ' GMT');
	header('Cache-Control: no-store, no-cache, must-revalidate');     // HTTP/1.1
	header('Cache-Control: pre-check=0, post-check=0, max-age=0');    // HTTP/1.1
	header ("Pragma: no-cache");
	header("Connection: close");
	ignore_user_abort(true);
	ob_start();
	print 'Processing extract list';
	$size = ob_get_length();
	header("Content-Length: $size");
	ob_end_flush(); // Strange behaviour, will not work
	flush();
	//exit;
	sleep(3);
	$process_time = microtime(true);

	$tar_path = base64_decode($_POST['extract_file_name']);
	$targetPath = base64_decode($_POST['target_path']).'/';
	$cacheId = isset($_POST['cache_id'])?$_POST['cache_id']:null;
	$file_list = unserialize(base64_decode($_POST['file_list']));

	$tar_out = '';
	$result = true;

	$_curr_dir = str_replace('\\','/',dirname(__FILE__));
	$_tar_dir = str_replace('\\','/',dirname($tar_path));
	if(strpos($_tar_dir,$_curr_dir)!==0){
		$result = false;
		$tar_out .= 'Not safe source path';
		$tar_path = '';
	}
	//var_dump(array($tar_path,$targetPath,$file_list));
	if($targetPath&&$file_list&&file_exists($tar_path)){
		$fp = @fopen('extract.log','a');
		if($fp){
			fwrite($fp,implode("\n",$file_list));
			fclose($fp);
		}
		$tar = new MTTar($tar_path);
		if($cacheId)$tar->cache_id = $cacheId;
		ob_start();
		if($tar){

			$tar->setErrorHandling( PEAR_ERROR_PRINT );
			$result = $tar->extractList($file_list,$targetPath);
			//$tar->_setOffset();
		}else{
			$res = false;
		}
		$tar_out = ob_get_clean();
	}else{
		$result = false;
		$tar_out .= $targetPath?'':'Invalid target path. ';
		$tar_out .= $file_list?'':'Empty file list to extract. ';
	}

	$process_time = sprintf("%2.2fms",(microtime(true)-$process_time)*1000);
	MTTar::saveState($result,$tar_out,array($process_time));
}
?>