<?php
defined("ABSPATH") or die("");

class DUP_PRO_FTP_UploadInfo
{
    public $next_offset;
    public $error_details = null;
    public $success = false;
    public $fatal_error = false;
}

/**
 * Description of cls-ftp-chunker
 *
 */
class DUP_PRO_FTP_Chunker
{
    public $server;
    public $port = 21;
    public $username;
    public $password;
    public $timeout_in_sec = 90;
    public $ssl = false;
    public $passive_mode = false;
    public $echo = false;   
    public $ftp_connection_id = false;
    public $ftp_login_result = false;

    public function __construct($server, $port = 21, $username = 'anonymous', $password = 'anonymous@gmail.com', $timeout_in_sec = 15, $ssl = false, $passive_mode = false)
    {
        $this->server = $server;
        $this->port = $port;
        $this->username = $username;
        $this->password = $password;
        $this->timeout_in_sec = $timeout_in_sec;
        $this->ssl = $ssl;
        $this->passive_mode = $passive_mode;
    }
    
    public function open()
    {
        $opened = false;
        
        $this->close();
        
        if($this->ssl)
        {
            if(function_exists('ftp_ssl_connect'))
            {
                DUP_PRO_LOG::trace("Attempting to open ssl connection");
                $this->ftp_connection_id = ftp_ssl_connect($this->server, $this->port, $this->timeout_in_sec);                
            }
            else
            {
                DUP_PRO_LOG::traceError("Attempted to open FTP SSL connection when OpenSSL hasn't been statically built into this PHP install.");
                return false;
            }
        }
        else
        {
            $this->ftp_connection_id = @ftp_connect($this->server, $this->port, $this->timeout_in_sec);
        }
        
        
        if ($this->ftp_connection_id !== false)
        {
            $message = sprintf(DUP_PRO_U::__('Successfully FTP connected to %1$s:%2$d'), $this->server, $this->port);
            DUP_PRO_LOG::trace($message);
            
            $this->ftp_login_result = ftp_login($this->ftp_connection_id, $this->username, $this->password);

            if($this->ftp_login_result)
            {
                $message = sprintf(DUP_PRO_U::__('Logged user %1$s into %2$s'), $this->username, $this->server);
                DUP_PRO_LOG::trace($message);
                
                if($this->passive_mode)
                {
                    if(ftp_pasv($this->ftp_connection_id, true))
                    {
                        DUP_PRO_LOG::trace('Set connection to passive');
                        $opened = true;
                    }   
                    else
                    {
                        DUP_PRO_LOG::traceError("Couldn't set the connection into passive mode." . $this->get_info());
                    }                    
                }
                else
                {
                    $opened = true;                
                }
            }
            
            if ($this->ftp_login_result == false)
            {
                $message = sprintf(DUP_PRO_U::__('Error logging in user %1$s into %2$s'), $this->username, $this->server);
                DUP_PRO_LOG::trace($message);
            }            
        }
        else
        {
            $message = sprintf(DUP_PRO_U::__('Error connecting to FTP server %1$s:%2$d'), $this->server, $this->port);
            DUP_PRO_LOG::trace($message);
        }     
        
        return $opened;
    }
    
    public function create_directory($directory)
    {
        $parts = explode('/',$directory); // 2013/06/11/username
       
        $part_count = count($parts);

        if($part_count > 0)
        {
            //foreach($parts as $part)
            for($pidx = 0; $pidx < $part_count; $pidx++)
            {
                $part = $parts[$pidx];

                if(trim($part) != '')
                {
                    if(($pidx == 1) && (substr($directory, 0, 1) == '/'))
                    {
                        $part = '/' . $part;
                    }

                    if(!@ftp_chdir($this->ftp_connection_id, $part))
                    {
                        DUP_PRO_LOG::trace("Creating directory $part since it doesn't exist");
                        @ftp_mkdir($this->ftp_connection_id, $part);
                        @ftp_chdir($this->ftp_connection_id, $part);
                    }
                }
            }
        }
        
        return @ftp_chdir($this->ftp_connection_id, $directory);
    }
    
    public function directory_exists($directory)
    {
        return @ftp_chdir($this->ftp_connection_id, $directory);
    }
    
    public function close()
    {
        $closed = false;
        
        if ($this->ftp_connection_id !== false)
        {
            DUP_PRO_LOG::traceObject("closing ftp connection", $this->ftp_connection_id);
            $closed = ftp_close($this->ftp_connection_id);
        }
        else
        {
            $closed = true;
        }
        
        return $closed;
    }
    
    public function is_opened()
    {
        return (($this->ftp_connection_id != false) && ($this->ftp_login_result == true));
    }

    public function upload_file($source_filepath, $storage_folder, $dest_filename = '')
    {
        $uploaded = false;
        
        if($this->is_opened())
        {
			
            $offset = 0;

            $timeout = 15;

            $start_time = time();

            while (!$uploaded)
            {
                DUP_PRO_LOG::trace("file calling upload chunk offset=$offset");

                /* @var $ftp_upload_info DUP_PRO_FTP_UploadInfo */
                $ftp_upload_info = $this->upload_chunk($source_filepath, $storage_folder, $timeout, $offset, 0, $dest_filename);

                DUP_PRO_LOG::trace("after upload chunk file");

                $offset = $ftp_upload_info->next_offset;

                if ($ftp_upload_info->success)
                {
                    DUP_PRO_LOG::trace("1");
                    $uploaded = true;
                }
                else if ($ftp_upload_info->error_details != null)
                {
                    DUP_PRO_LOG::traceError("Error uploading $source_filepath to $storage_folder: $ftp_upload_info->error_details");
                    break;
                }
                else if (time() - $start_time >= $timeout)
                {
                    DUP_PRO_LOG::traceError("File transfer timed out");
                }
                else
                {
                    DUP_PRO_LOG::trace("2");
                 //   $offset = $ftp_upload_info->next_offset;
                }
            }
        }
        else
        {
            DUP_PRO_LOG::traceError("Tried to upload file when connection wasn't opened. Info:" . $this->get_info());
        }

        return $uploaded;
    }

    public function upload_chunk($source_filepath, $storage_folder, $max_upload_time_in_sec = 15, $offset = 0, $server_load_delay = 0,  $dest_filename = '')
    {
		DUP_PRO_LOG::trace("FTP CHUNK OFFSET IN=$offset");
        /* @var $ftp_upload_info DUP_PRO_FTP_UploadInfo */
        $ftp_upload_info = new DUP_PRO_FTP_UploadInfo();
            
        if($this->is_opened())
        {            
            $start_time = time();

            DUP_PRO_LOG::trace("call upload chunk");

            $local_file_handle = fopen($source_filepath, 'rb');

            if ($local_file_handle !== false)
            {
                if(fseek($local_file_handle, $offset) != 0)
                {
                    $error_message = sprintf(DUP_PRO_U::__('Couldnt seek to $offset in %1$s'), $source_filepath);

                    DUP_PRO_LOG::trace($error_message);
                    $ftp_upload_info->error_details = $error_message;
                    $ftp_upload_info->next_offset = $offset;
                    
                    DUP_PRO_LOG::trace("closing local file handle");
                    fclose($local_file_handle);
                    DUP_PRO_LOG::trace("local file handle closed");
                    
                    return $ftp_upload_info;
                }

                $filename = !empty($dest_filename) ? $dest_filename : basename($source_filepath);
                $dest_filepath = "$storage_folder/$filename";
				
				if($offset == 0)
				{
					DUP_PRO_LOG::trace("Deleting $dest_filepath before attempting to upload it");
					// Delete any file that may be there already
					$this->delete($dest_filepath);
				}

                $time_passed = time() - $start_time;               

                // $ret = ftp_nb_fput($this->ftp_connection_id, $dest_filepath, $local_file_handle, FTP_BINARY, $offset);
                if ($offset == 0) {
                    $ret = ftp_nb_fput($this->ftp_connection_id, $dest_filepath, $local_file_handle, FTP_BINARY);
                } else {
                    $ftp_size = ftp_size($this->ftp_connection_id, $dest_filepath);
                    $ret = ftp_nb_fput($this->ftp_connection_id, $dest_filepath, $local_file_handle, FTP_BINARY, $ftp_size);
                }
                

                $next_offset = $offset;
                				
                while (($ret == FTP_MOREDATA) && ($time_passed < $max_upload_time_in_sec))
                {
                    if($server_load_delay !== 0) {
                        usleep($server_load_delay);
                    }
                    if ($ret != FTP_FAILED)
                    {
                        $next_offset = ftell($local_file_handle);
                    }
                                                        
					
                    $time_passed = time() - $start_time;

					if($time_passed < $max_upload_time_in_sec)
					{
						$ret = ftp_nb_continue($this->ftp_connection_id);
					}
                }

                if ($ret == FTP_FAILED)
                {
                    $error_message = sprintf(DUP_PRO_U::__('FTP failed during transfer of %1$s'), $source_filepath);

                    DUP_PRO_LOG::trace($error_message);
                    $ftp_upload_info->error_details = $error_message;
                    $ftp_upload_info->next_offset = $offset;
                }
                else if ($ret == FTP_FINISHED)
                {
                    DUP_PRO_LOG::trace("ftp finished with offset $next_offset");
					
					$next_offset = $this->finish_file_chunk($local_file_handle, $next_offset);
										
					
					if($next_offset == -1)
					{
						$ftp_upload_info->error_details = DUP_PRO_U::__('Problem finishing file chunk transfer');
						$ftp_upload_info->fatal_error = true;
						
						$this->delete($dest_filepath);
					}
					else
					{
						$ftp_upload_info->next_offset = $next_offset;
					}
					                        
                    $ftp_size = ftp_size($this->ftp_connection_id, $dest_filepath);
                    $local_size = filesize($source_filepath);
                    
                    /*
                    error_log('$ftp_size -> '.$ftp_size);
                    error_log('$local_size -> '.$local_size);
                    */

					// rsr temp
			//		$ftp_size = 1;
					
                    if(($ftp_size != -1) && ($ftp_size != $local_size))
                    {
                        $error_message = sprintf(DUP_PRO_U::__('FTP size mismatch for %1$s. Local file=%2$d bytes while server\'s file is %3$d bytes.'), $source_filepath, $local_size, $ftp_size);

                        DUP_PRO_LOG::trace($error_message);
                        $ftp_upload_info->error_details = $error_message;        
						$ftp_upload_info->fatal_error = true;
                        
				     	$this->delete($dest_filepath);
                    }
                    else
                    {
						DUP_PRO_LOG::trace("ftp size={$ftp_size}");
                        DUP_PRO_LOG::trace(sprintf(DUP_PRO_U::__('FTP sizes match for %1$s. Local file=%2$d bytes while server\'s file is %3$d bytes.'), $source_filepath, $local_size, $ftp_size));
                        
                        $ftp_upload_info->success = true;   
                    }
                }
                else
                {
                    DUP_PRO_LOG::trace("timed out so saving off offset $next_offset");
				
					
					$next_offset = $this->finish_file_chunk($local_file_handle, $next_offset);
					
					if($next_offset == -1)
					{
						$ftp_upload_info->error_details = DUP_PRO_U::__('Problem finishing file chunk transfer');
						$ftp_upload_info->fatal_error = true;
					}
					else
					{
						DUP_PRO_LOG::trace("FTP CHUNK OFFSET OUT=$next_offset");
						$ftp_upload_info->next_offset = $next_offset;
					}
					
					
                     //$ret = FTP_MOREDATA                
                }

                DUP_PRO_LOG::trace("closing local file handle");
                fclose($local_file_handle);
                DUP_PRO_LOG::trace("local file handle closed");
            }
            else
            {
                $message = sprintf(DUP_PRO_U::__('Error opening %1$ for FTP'), $source_filepath);
                DUP_PRO_LOG::trace($message);
                $ftp_upload_info->error_details = $message;
            }        
        }
        else
        {
            $message = "Tried to upload file when connection wasn't opened. Info:" . $this->get_info();
            
            $ftp_upload_info->error_details = $message;
            DUP_PRO_LOG::trace($message);
        }
        
        // $this->eo('ftp_upload_info', $ftp_upload_info);
        return $ftp_upload_info;
    }		
    
	private function finish_file_chunk($local_file_handle, $next_offset)
	{
		$matches = false;
		$tries = 0;

		while(!$matches && ($tries < 2))
		{										
			$prev_offset = $next_offset;

			$next_offset = ftell($local_file_handle);

			$matches = ($next_offset == $prev_offset);
			
			DUP_PRO_LOG::trace("Finish file chunk next_offset=$next_offset prev_offset=$prev_offset");
			
			$tries++;

            // Want to give it a couple seconds after it matches to settle down
            usleep(2000000);
		}
		
		if($matches)
		{
			return $next_offset;
		}
		else
		{
			DUP_PRO_LOG::trace("Never was able to finish file chunk transfer");
			return -1;
		}
	}

    public function get_info()
    {
        $ssl_string = DUP_PRO_STR::boolToString($this->ssl);
        $passive_string = DUP_PRO_STR::boolToString($this->passive_mode);
                        
        return sprintf(DUP_PRO_U::__('Server:%1$s Port:%2$d User:%3$s SSL:%4$s Passive:%5$s'), $this->server, $this->port, $this->username, $ssl_string, $passive_string);
    }

    public function get_filelist($directory = '.')
    {
        $items = array();

        if($this->is_opened())
        {
            $parameters = "$directory";
            
            $items =  ftp_nlist($this->ftp_connection_id, $parameters);                   

			for($i = 0; $i < count($items); $i++)
			{
				$items[$i] = basename($items[$i]);
			}
        }
        else
        {
            $items = false;
            $message = "Tried to upload file when connection wasn't opened. Info:" . $this->get_info();

            DUP_PRO_LOG::trace($message);
        }

        return $items;
    }
    
    public function delete($file_path)
    {
        $ret_val = false;
        
        if($this->is_opened())
        {
            $ret_val = @ftp_delete($this->ftp_connection_id, $file_path);
            
            if($ret_val)
            {
                DUP_PRO_LOG::trace("Successfully deleted $file_path from " . $this->server);
            }
        }
        else
        {                 
            DUP_PRO_LOG::trace("Tried to upload file when connection wasn't opened. Info:" . $this->get_info());
        }
        
        return $ret_val;
    }   
    
    // Using straight up downloading right now - not using chunking
    public function download_file($remote_source_filepath, $local, $is_local_directory = true)
    {
        $ret_val = false;

        if($is_local_directory)
        {
            $filename = basename($remote_source_filepath);

            $local_dest_filepath = "$local/$filename";
        }
        else
        {
            $local_dest_filepath = $local;
        }

        $ret_val = ftp_get($this->ftp_connection_id, $local_dest_filepath, $remote_source_filepath, FTP_BINARY);

        if($ret_val == false)
        {
            DUP_PRO_LOG::trace("Error downloading $remote_source_filepath into $local_dest_filepath. FTP Info: " . $this->get_info());
        }
        
        return $ret_val;
    }
}
