<?php
/**
 * @package AkeebaBackup
 * @copyright Copyright (c)2009-2014 Nicholas K. Dionysopoulos
 * @license GNU General Public License version 3, or later
 *
 * @since 3.2
 */

// Protect from unauthorized access
defined('_JEXEC') or die();

/**
 * A model to handle the administration of remotely stored files
 */
class AkeebaModelRemotefiles extends FOFModel
{
	/**
	 * Returns an icon definition list for the applicable actions on this backup record 
	 * @return array
	 */
	function getActions()
	{
		$id = $this->getState('id', -1);
		
		$actions = array();
		
		// Load the stats record
		$stat = AEPlatform::getInstance()->get_statistics($id);
		
		// Get the post-proc engine from the remote location
		$remote_filename = $stat['remote_filename'];
		if(empty($remote_filename)) return $actions;
		
		$rfparts = explode('://', $remote_filename, 2);
		$engine = AEFactory::getPostprocEngine($rfparts[0]);
		
		$filename = $rfparts[1];
		
		// Does the engine support local d/l and we need to d/l the file locally?
		if( $engine->can_download_to_file && !$stat['filesexist'] ) {
			// Add a "Fetch back to server" button
			$action = array(
				'label'				=> JText::_('REMOTEFILES_FETCH'),
				'link'				=> "index.php?option=com_akeeba&view=remotefiles&task=dltoserver&tmpl=component&id={$stat['id']}&part=-1",
				'type'				=> 'button',
				'icon'				=> 'icon-download icon-white',
				'class'				=> 'btn-large btn-primary',
			);
			$actions[] = $action;
		}
		
		// Does the engine support remote deletes?
		if($engine->can_delete) {
			// Add a Delete button
			$action = array(
				'label'				=> JText::_('REMOTEFILES_DELETE'),
				'link'				=> "index.php?option=com_akeeba&view=remotefiles&task=delete&tmpl=component&id={$stat['id']}&part=-1",
				'type'				=> 'button',
				'icon'				=> 'icon-trash icon-white',
				'class'				=> 'btn-danger',
			);
			$actions[] = $action;
		}

		// Does the engine support downloads to browser?
		if($engine->can_download_to_browser) {
			$parts = $stat['multipart'];
			if($parts == 0) $parts++;
			for($i = 0; $i < $parts; $i++)
			{
				$action = array(
					'label'				=> JText::sprintf('REMOTEFILES_PART', $i),
					'link'				=> "index.php?option=com_akeeba&view=remotefiles&task=dlfromremote&id={$stat['id']}&part=$i",
					'type'				=> 'link',
					'class'				=> '',
					'icon'				=> 'icon-download-alt',
				);
				$actions[] = $action;
			}
		}
		
		return $actions;
	}
	
	/**
	 * Downloads a remote file back to the site's server
	 * @return array
	 */
	function downloadToServer()
	{
		$id = $this->getState('id', -1);
		$part = $this->getState('part', -1);
		$frag = $this->getState('frag', -1);
		
		$ret = array(
			'error'		=> false,
			'finished'	=> false,
			'id'		=> $id,
			'part'		=> $part,
			'frag'		=> $frag,
		);
		
		// Gather the necessary information to perform the download 
		$stat = AEPlatform::getInstance()->get_statistics($id);
		$remoteFilename = $stat['remote_filename'];
		$rfparts = explode('://', $remoteFilename);
		$engine = AEFactory::getPostprocEngine($rfparts[0]);
		$remote_filename = $rfparts[1];
		
		// Load the correct backup profile
		AEPlatform::getInstance()->load_configuration($stat['profile_id']);
		$config = AEFactory::getConfiguration();
		
		// Get a reference to the session object
		$session = JFactory::getSession();
		
		// Start timing ourselves
		$timer = AEFactory::getTimer(); // The core timer object
		$start = $timer->getRunningTime(); // Mark the start of this download
		$break = false; // Don't break the step
		
		while($timer->getTimeLeft() && !$break && ($part < $stat['multipart']) )
		{
			// Get the remote and local filenames
			$basename = basename($remote_filename);
			$extension = strtolower(str_replace(".", "", strrchr($basename, ".")));
			
			if($part > 0) {
				$new_extension = substr($extension,0,1) . sprintf('%02u', $part); 
			} else {
				$new_extension = $extension;
			}
			
			$filename = $basename.'.'.$new_extension;
			$remote_filename = substr($remote_filename, 0, -strlen($extension)).$new_extension;
			
			// Figure out where on Earth to put that file
			$local_file = $config->get('akeeba.basic.output_directory').'/'.basename($remote_filename);
			
			// Do we have to initialize the process?
			if($part == -1) {
				// Total size to download
				$session->set('dl_totalsize', $stat['total_size'], 'akeeba');
				// Currently downloaded size
				$session->set('dl_donesize', 0, 'akeeba');
				// Init
				$part = 0;
			}
			
			// Do we have to initialize the file?
			if($frag == -1) {
				// Delete and touch the output file
				AEPlatform::getInstance()->unlink($local_file);
				$fp = @fopen($local_file, 'wb');
				if($fp !== false) @fclose($fp);
				// Init
				$frag = 0;
			}
			
			// Calculate from and length
			$length = 1048576;
			$from = $frag * $length + 1;
			
			// Try to download the first frag
			$temp_file = $local_file.'.tmp';
			$staggered = true;
			$required_time = 1.0;
			$result = $engine->downloadToFile($remote_filename, $temp_file, $from, $length);
			if($result == -1) {
				// The engine doesn't support staggered downloads
				$staggered = false;
				$result = $engine->downloadToFile($remote_filename, $temp_file);
			}
			
			if(!$result) {
				// Failed download
				if(
					(
					( ($part < $stat['multipart']) || ( ($stat['multipart'] == 0) && ($part == 0) ) ) &&
					( $frag == 0 )
					)
					||
					!$staggered
				){
					// Failure to download the part's beginning = failure to download. Period.
					$ret['error'] = JText::_('REMOTEFILES_ERR_CANTDOWNLOAD').$engine->getWarning();
					return $ret;
				} elseif( $part >= $stat['multipart']){
					// Just finished! Update the stats record.
					$stat['filesexist'] = 1;
					AEPlatform::getInstance()->set_or_update_statistics($id, $stat, $engine);
					$ret['finished'] = true;
					return $ret;
				} else {
					// Since this is a staggered download, consider this normal and go to the next part.
					$part++; $frag = -1;
				}
			}
			
			// Add the currently downloaded frag to the total size of downloaded files
			if($result) {
				$filesize = (int)@filesize($temp_file);
				$total = $session->get('dl_donesize', 0, 'akeeba');
				$total += $filesize;
				$session->set('dl_donesize', $total, 'akeeba');
			}
			
			// Successful download, or have to move to the next part.
			if($staggered) {
				if($result)
				{
					// Append the file
					$fp = @fopen($local_file,'ab');
					if($fp === false) {
						// Can't open the file for writing
						AEPlatform::getInstance()->unlink($temp_file);
						$ret['error'] = JText::sprintf('REMOTEFILES_ERR_CANTOPENFILE', $local_file);
						return $ret;
					}
					$tf = fopen($temp_file,'rb');
					while(!feof($tf)) {
						$data = fread($tf, 262144);
						fwrite($fp, $data);
					}
					fclose($tf);
					fclose($fp);
					AEPlatform::getInstance()->unlink($tf);
				}
				
				// Advance the frag pointer and mark the end
				$end = $timer->getRunningTime();
				$frag++;
			} else {
				if($result)
				{
					// Rename the temporary file
					if (@file_exists($local_file))
					{
						$result = AEPlatform::getInstance()->unlink($local_file);
					}

					if ($result)
					{
						$result = AEPlatform::getInstance()->move($temp_file, $local_file);
					}
					else
					{
						$result = AEPlatform::getInstance()->unlink($temp_file);
					}

					if(!$result)
					{
						// Renaming failed. Goodbye.
						AEPlatform::getInstance()->unlink($temp_file);
						$ret['error'] = JText::sprintf('REMOTEFILES_ERR_CANTOPENFILE', $local_file);
						return $ret;
					}
				}
				// In whole part downloads we break the step without second thought
				$break = true;
				$end = $timer->getRunningTime();
				$frag = -1;
				$part++;
			}
			
			// Do we predict that we have enough time?
			$required_time = max(1.1 * ($end - $start), $required_time);
			if( $timer->getTimeLeft() < $required_time ) $break = true; 
			$start = $end;
		}
		
		// Pass the id, part, frag in the request so that the view can grab it
		$ret['id']		= $id;
		$ret['part']	= $part;
		$ret['frag']	= $frag;
		$this->setState('id',	$id);
		$this->setState('part', $part);
		$this->setState('frag', $frag);
		
		if($part >= $stat['multipart']) {
			// Just finished!
			$stat['filesexist'] = 1;
			AEPlatform::getInstance()->set_or_update_statistics($id, $stat, $engine);
			$ret['finished'] = true;
			return $ret;
		}
		
		return $ret;
	}
	
	public function deleteRemoteFiles()
	{
		$id = $this->getState('id', -1);
		$part = $this->getState('part', -1);
		
		$ret = array(
			'error'		=> false,
			'finished'	=> false,
			'id'		=> $id,
			'part'		=> $part
		);
		
		// Gather the necessary information to perform the delete 
		$stat = AEPlatform::getInstance()->get_statistics($id);
		$remoteFilename = $stat['remote_filename'];
		$rfparts = explode('://', $remoteFilename);
		$engine = AEFactory::getPostprocEngine($rfparts[0]);
		$remote_filename = $rfparts[1];
		
		// Load the correct backup profile
		AEPlatform::getInstance()->load_configuration($stat['profile_id']);
		$config = AEFactory::getConfiguration();
		
		// Start timing ourselves
		$timer = AEFactory::getTimer(); // The core timer object
		$start = $timer->getRunningTime(); // Mark the start of this download
		$break = false; // Don't break the step
		
		while($timer->getTimeLeft() && !$break && ($part < $stat['multipart']) )
		{
			// Get the remote filename
			$basename = basename($remote_filename);
			$extension = strtolower(str_replace(".", "", strrchr($basename, ".")));
			
			if($part > 0) {
				$new_extension = substr($extension,0,1) . sprintf('%02u', $part); 
			} else {
				$new_extension = $extension;
			}
			
			$filename = $basename.'.'.$new_extension;
			$remote_filename = substr($remote_filename, 0, -strlen($extension)).$new_extension;
			
			// Do we have to initialize the process?
			if($part == -1) {
				// Init
				$part = 0;
			}
			
			// Try to delete the part
			$required_time = 1.0;
			$result = $engine->delete($remote_filename);
			
			if(!$result) {
				$ret['error'] = JText::_('REMOTEFILES_ERR_CANTDELETE').$engine->getWarning();
				return $ret;
				return;
			} else {
				// Successful delete
				$end = $timer->getRunningTime();
				$part++;
			}
			
			// Do we predict that we have enough time?
			$required_time = max(1.1 * ($end - $start), $required_time);
			if( $timer->getTimeLeft() < $required_time ) $break = true; 
			$start = $end;
		}
		
		if($part >= $stat['multipart']) {
			// Just finished!
			$stat['remote_filename'] = '';
			AEPlatform::getInstance()->set_or_update_statistics($id, $stat, $engine);
			$ret['finished'] = true;
			return $ret;
		} else {
			// More work to do...
			$ret['id'] = $id;
			$ret['part'] = $part;
			return $ret;
		}
	}
}