2021-04-01 10:59:15 +00:00
< ? php
namespace App\Classes\File ;
2023-08-05 11:32:45 +00:00
use Carbon\Carbon ;
2022-11-13 13:29:55 +00:00
use Illuminate\Support\Arr ;
2021-04-01 10:59:15 +00:00
use Illuminate\Support\Facades\Log ;
use Symfony\Component\HttpFoundation\File\Exception\FileException ;
2023-09-13 10:58:22 +00:00
use App\Classes\Protocol ;
2023-07-02 13:40:08 +00:00
use App\Exceptions\FileGrewException ;
2023-09-13 10:58:22 +00:00
use App\Jobs\ { PacketProcess , TicProcess };
2021-07-18 12:10:21 +00:00
use App\Models\Address ;
2021-04-01 10:59:15 +00:00
/**
* Object representing the files we are receiving
*
* @ property - read resource $fd
* @ property - read int total_recv
* @ property - read int total_recv_bytes
*/
2023-07-17 06:36:53 +00:00
class Receive extends Base
2021-04-01 10:59:15 +00:00
{
2021-08-17 13:49:39 +00:00
private const LOGKEY = 'IR-' ;
2023-07-02 13:40:08 +00:00
private const compression = [
'BZ2' ,
'GZ' ,
];
2021-07-18 12:10:21 +00:00
private Address $ao ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
/** @var ?string The compression used by the incoming file */
private ? string $comp ;
/** @var string|null The compressed data received */
private ? string $comp_data ;
2023-11-22 10:04:58 +00:00
private $queue = FALSE ;
2021-04-01 10:59:15 +00:00
public function __construct ()
{
// Initialise our variables
2023-07-17 06:36:53 +00:00
if ( get_class ( $this ) === self :: class ) {
$this -> list = collect ();
$this -> f = NULL ;
}
2021-04-01 10:59:15 +00:00
}
public function __get ( $key )
{
switch ( $key ) {
2023-07-17 06:36:53 +00:00
case 'completed' :
return $this -> list
-> filter ( function ( $item ) { return $item -> complete === TRUE ; });
2021-04-01 10:59:15 +00:00
case 'fd' :
return is_resource ( $this -> f );
2023-07-19 06:34:09 +00:00
case 'recvmtime' :
case 'recvsize' :
2021-04-01 10:59:15 +00:00
case 'mtime' :
2023-07-17 06:36:53 +00:00
case 'nameas' :
2021-04-01 10:59:15 +00:00
case 'size' :
2021-08-15 14:41:43 +00:00
case 'name_size_time' :
2023-09-23 12:01:18 +00:00
case 'pref_name' :
2023-07-17 06:36:53 +00:00
return $this -> receiving -> { $key };
case 'pos' :
return $this -> pos ;
2021-08-15 14:41:43 +00:00
2023-07-17 06:36:53 +00:00
case 'receiving' :
return $this -> list -> get ( $this -> index );
case 'ready' :
return ( ! is_null ( $this -> index ));
case 'togo_count' :
2021-04-01 10:59:15 +00:00
return $this -> list
2023-07-17 06:36:53 +00:00
-> filter ( function ( $item ) { return $item -> complete === FALSE ; })
2021-04-01 10:59:15 +00:00
-> count ();
case 'total_recv' :
2023-07-17 06:36:53 +00:00
return $this -> completed -> count ();
2021-04-01 10:59:15 +00:00
case 'total_recv_bytes' :
2023-07-19 00:49:57 +00:00
return $this -> completed -> sum ( function ( $item ) { return $item -> recvsize ; });
2021-04-01 10:59:15 +00:00
default :
2023-06-27 07:39:11 +00:00
throw new \Exception ( 'Unknown key: ' . $key );
2021-04-01 10:59:15 +00:00
}
}
/**
* Close the file descriptor for our incoming file
*
2023-06-27 07:39:11 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
public function close () : void
{
2023-07-02 13:40:08 +00:00
if ( ! $this -> receiving )
2023-06-27 07:39:11 +00:00
throw new \Exception ( 'No file to close' );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
if ( $this -> f ) {
2023-08-05 11:32:45 +00:00
$rcvd_time = Carbon :: now ();
2023-07-17 06:36:53 +00:00
if ( $this -> pos !== $this -> receiving -> recvsize )
Log :: warning ( sprintf ( '%s:- Closing [%s], but missing [%d] bytes' , self :: LOGKEY , $this -> receiving -> nameas , $this -> receiving -> recvsize - $this -> pos ));
else
$this -> receiving -> complete = TRUE ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
$end = time () - $this -> start ;
2023-07-17 06:36:53 +00:00
Log :: debug ( sprintf ( '%s:- Closing [%s], received in [%d]' , self :: LOGKEY , $this -> receiving -> nameas , $end ));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
if ( $this -> comp )
2023-07-17 06:36:53 +00:00
Log :: info ( sprintf ( '%s:= Compressed file using [%s] was [%d] bytes (%d). Compression rate [%3.2f%%]' , self :: LOGKEY , $this -> comp , $x = strlen ( $this -> comp_data ), $this -> receiving -> recvsize , $x / $this -> receiving -> recvsize * 100 ));
2021-07-18 12:10:21 +00:00
2023-07-02 13:40:08 +00:00
fclose ( $this -> f );
// Set our mtime
2023-09-19 00:21:17 +00:00
Log :: debug ( sprintf ( '%s:= Setting file [%s] to time [%s]' , self :: LOGKEY , $this -> receiving -> full_name , $this -> receiving -> recvmtime ));
2023-09-13 05:13:04 +00:00
touch ( $this -> receiving -> full_name , $this -> receiving -> recvmtime );
2023-07-02 13:40:08 +00:00
$this -> f = NULL ;
2021-08-12 11:59:48 +00:00
2023-07-17 06:36:53 +00:00
// If we received a packet, we'll dispatch a job to process it, if we got it all
if ( $this -> receiving -> complete )
2023-09-13 10:58:22 +00:00
switch ( $this -> receiving -> whatType ()) {
2023-07-02 13:40:08 +00:00
case self :: IS_ARC :
case self :: IS_PKT :
try {
2023-09-13 10:58:22 +00:00
// If packet is greater than a size, lets queue it
2023-11-22 10:04:58 +00:00
if ( $this -> queue || ( $this -> receiving -> size > config ( 'fido.queue_size' , 0 ))) {
Log :: info ( sprintf ( '%s:- Packet [%s] will be sent to the queue for processing because its [%d] size, or queue forced' , self :: LOGKEY , $this -> receiving -> full_name , $this -> receiving -> size ));
2023-09-13 10:58:22 +00:00
PacketProcess :: dispatch ( $this -> receiving , $this -> ao , $rcvd_time );
} else
PacketProcess :: dispatchSync ( $this -> receiving , $this -> ao , $rcvd_time );
2023-01-11 02:15:30 +00:00
2023-07-02 13:40:08 +00:00
} catch ( \Exception $e ) {
2023-09-13 10:58:22 +00:00
Log :: error ( sprintf ( '%s:! Got error dispatching packet [%s] (%d:%s-%s).' , self :: LOGKEY , $this -> receiving -> full_name , $e -> getLine (), $e -> getFile (), $e -> getMessage ()));
2023-07-02 13:40:08 +00:00
}
2021-07-19 14:26:12 +00:00
2023-07-02 13:40:08 +00:00
break ;
2021-07-18 12:10:21 +00:00
2023-07-02 13:40:08 +00:00
case self :: IS_TIC :
2023-07-17 06:36:53 +00:00
Log :: info ( sprintf ( '%s:- Processing TIC file [%s]' , self :: LOGKEY , $this -> receiving -> nameas ));
2022-11-01 11:24:36 +00:00
2023-07-02 13:40:08 +00:00
// Queue the tic to be processed later, in case the referenced file hasnt been received yet
2023-10-04 11:22:01 +00:00
TicProcess :: dispatch ( $this -> receiving -> pref_name ) -> delay ( 60 );
2022-11-01 11:24:36 +00:00
2023-07-02 13:40:08 +00:00
break ;
2022-11-01 11:24:36 +00:00
2023-07-02 13:40:08 +00:00
default :
2023-07-17 06:36:53 +00:00
Log :: debug ( sprintf ( '%s:- Leaving file [%s] in the inbound dir' , self :: LOGKEY , $this -> receiving -> nameas ));
2023-07-02 13:40:08 +00:00
}
}
2021-07-18 12:10:21 +00:00
2023-07-17 06:36:53 +00:00
$this -> index = NULL ;
2021-04-01 10:59:15 +00:00
}
/**
2023-07-17 06:36:53 +00:00
* Add a new file to receive
2021-04-01 10:59:15 +00:00
*
2023-11-22 10:04:58 +00:00
* @ param array $file The name of the receiving file
* @ param Address $ao Sender sending a file to us
* @ param bool $queue Force sending received items to the queue
2023-07-17 06:36:53 +00:00
* @ throws \Exception
*/
2023-11-22 10:04:58 +00:00
public function new ( array $file , Address $ao , $queue = FALSE ) : void
2023-07-17 06:36:53 +00:00
{
Log :: debug ( sprintf ( '%s:+ Receiving new file [%s]' , self :: LOGKEY , join ( '|' , $file )));
if ( $this -> index )
throw new \Exception ( 'Can only have 1 file receiving at a time' );
$this -> ao = $ao ;
2023-11-22 10:04:58 +00:00
$this -> queue = $queue ;
2023-07-17 06:36:53 +00:00
$this -> list -> push ( new Item ( $ao , Arr :: get ( $file , 'name' ),( int ) Arr :: get ( $file , 'mtime' ),( int ) Arr :: get ( $file , 'size' )));
$this -> index = $this -> list -> count () - 1 ;
}
/**
* Open the file descriptor to receive a file
*
2021-04-01 10:59:15 +00:00
* @ param bool $check
2023-07-02 13:40:08 +00:00
* @ param string | null $comp If the incoming file will be compressed
* @ return int
2023-06-27 07:39:11 +00:00
* @ throws \Exception
2023-07-02 13:40:08 +00:00
* @ todo $comp should be parsed , in case it contains other items
2021-04-01 10:59:15 +00:00
*/
2023-07-17 06:36:53 +00:00
public function open ( bool $check = FALSE , string $comp = NULL ) : int
2021-04-01 10:59:15 +00:00
{
// @todo implement return 4 - SUSPEND(?) file
2023-07-17 06:36:53 +00:00
if ( is_null ( $this -> index ))
2023-06-27 07:39:11 +00:00
throw new \Exception ( 'No files currently receiving' );
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
$this -> comp_data = '' ;
$this -> comp = $comp ;
/*
if ( $this -> receiving -> size <= self :: MAX_COMPSIZE ) {
$this -> comp = $comp ;
} else {
Log :: alert ( sprintf ( '%s:- Compression [%s] disabled for file [%s], its size is too big [%d]' , self :: LOGKEY , $comp , $this -> receiving -> name , $this -> receiving -> size ));
}
*/
if ( $this -> comp && ( ! in_array ( $this -> comp , self :: compression )))
throw new \Exception ( 'Unsupported compression:' . $this -> comp );
elseif ( $this -> comp )
Log :: debug ( sprintf ( '%s:- Receiving file with [%s] compression' , self :: LOGKEY , $this -> comp ));
2023-07-17 06:36:53 +00:00
$this -> pos = 0 ;
2021-04-01 10:59:15 +00:00
$this -> start = time ();
2023-07-02 13:40:08 +00:00
2023-07-17 06:36:53 +00:00
if ( $this -> receiving -> exists && $this -> receiving -> match_mtime && $this -> receiving -> match_size ) {
Log :: alert ( sprintf ( '%s:- File already exists - skipping [%s]' , self :: LOGKEY , $this -> receiving -> nameas ));
2023-07-11 07:22:31 +00:00
return Protocol :: FOP_GOT ;
2023-07-02 13:40:08 +00:00
2023-07-17 06:36:53 +00:00
} elseif ( $this -> receiving -> exists && $this -> receiving -> size > 0 ) {
Log :: alert ( sprintf ( '%s:- File exists with different details - skipping [%s] (size: %d, mtime: %d)' , self :: LOGKEY , $this -> receiving -> nameas , $this -> receiving -> size , $this -> receiving -> mtime ));
2023-07-11 07:22:31 +00:00
return Protocol :: FOP_SKIP ;
2023-07-02 13:40:08 +00:00
} else {
2023-07-09 01:18:57 +00:00
// @todo I dont think we are enabling resumable sessions - need to check
2023-07-17 06:36:53 +00:00
Log :: debug ( sprintf ( '%s:- Opening [%s]' , self :: LOGKEY , $this -> receiving -> nameas ));
2023-07-02 13:40:08 +00:00
}
// If we are only checking, we'll return (NR mode)
if ( $check )
return Protocol :: FOP_OK ;
2021-04-01 10:59:15 +00:00
2023-07-17 06:36:53 +00:00
$this -> f = fopen ( $this -> receiving -> full_name , 'wb' );
2021-04-01 10:59:15 +00:00
if ( ! $this -> f ) {
2023-07-17 06:36:53 +00:00
Log :: error ( sprintf ( '%s:! Unable to open file [%s] for writing' , self :: LOGKEY , $this -> receiving -> nameas ));
2023-07-02 13:40:08 +00:00
return Protocol :: FOP_ERROR ;
2021-04-01 10:59:15 +00:00
}
2023-07-17 06:36:53 +00:00
Log :: info ( sprintf ( '%s:= File [%s] opened for writing' , self :: LOGKEY , $this -> receiving -> nameas ));
2023-07-02 13:40:08 +00:00
return Protocol :: FOP_OK ;
2021-04-01 10:59:15 +00:00
}
/**
* Write data to the file we are receiving
*
* @ param string $buf
2023-07-02 13:40:08 +00:00
* @ return bool
2023-06-27 07:39:11 +00:00
* @ throws \Exception
2021-04-01 10:59:15 +00:00
*/
2023-07-02 13:40:08 +00:00
public function write ( string $buf ) : bool
2021-04-01 10:59:15 +00:00
{
2023-07-17 06:36:53 +00:00
if ( ! $this -> fd )
2023-07-02 13:40:08 +00:00
throw new \Exception ( 'No file open for write' );
$data = '' ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// If we are using compression mode, then we need to buffer the right until we have everything
if ( $this -> comp ) {
$this -> comp_data .= $buf ;
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
// See if we can uncompress the data yet
switch ( $this -> comp ) {
case 'BZ2' :
if (( $data = bzdecompress ( $this -> comp_data , TRUE )) === FALSE )
throw new FileException ( 'BZ2 decompression failed?' );
elseif ( is_numeric ( $data ))
throw new FileException ( sprintf ( 'BZ2 decompression failed with (:%d)?' , $data ));
break ;
case 'GZ' :
if (( $data = gzdeflate ( $this -> comp_data )) === FALSE )
throw new FileException ( 'BZ2 decompression failed?' );
break ;
}
// Compressed file grew
if ( ! strlen ( $data )) {
if ( strlen ( $this -> comp_data ) > $this -> receiving -> size ) {
fclose ( $this -> f );
$this -> f = NULL ;
throw new FileGrewException ( sprintf ( 'Error compressed file grew, rejecting [%d] -> [%d]' , strlen ( $this -> comp_data ), $this -> receiving -> size ));
}
return TRUE ;
}
} else {
$data = $buf ;
}
2023-07-17 06:36:53 +00:00
if (( $x = $this -> pos + strlen ( $data )) > $this -> receiving -> recvsize )
throw new \Exception ( sprintf ( 'Too many bytes received [%d] (%d)?' , $x , $this -> receiving -> recvsize ));
2023-07-02 13:40:08 +00:00
$rc = fwrite ( $this -> f , $data );
2021-04-01 10:59:15 +00:00
if ( $rc === FALSE )
throw new FileException ( 'Error while writing to file' );
2023-07-17 06:36:53 +00:00
$this -> pos += $rc ;
Log :: debug ( sprintf ( '%s:- Write [%d] bytes, file pos now [%d] of [%d] (%d)' , self :: LOGKEY , $rc , $this -> pos , $this -> receiving -> recvsize , strlen ( $buf )));
2023-07-02 13:40:08 +00:00
2023-07-17 06:36:53 +00:00
if ( strlen ( $this -> comp_data ) > $this -> receiving -> recvsize )
Log :: alert ( sprintf ( '%s:- Compression grew the file during transfer (%d->%d)' , self :: LOGKEY , $this -> receiving -> recvsize , strlen ( $this -> comp_data )));
2021-04-01 10:59:15 +00:00
2023-07-02 13:40:08 +00:00
return TRUE ;
2021-04-01 10:59:15 +00:00
}
}