2021-04-01 10:59:15 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Classes\File;
|
|
|
|
|
|
|
|
use Exception;
|
|
|
|
use Illuminate\Support\Collection;
|
|
|
|
use Illuminate\Support\Facades\Log;
|
2021-07-18 12:10:21 +00:00
|
|
|
use Symfony\Component\HttpFoundation\File\File;
|
2021-04-01 10:59:15 +00:00
|
|
|
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
|
|
|
|
2021-07-18 12:10:21 +00:00
|
|
|
use App\Classes\FTN\Packet;
|
|
|
|
use App\Jobs\ProcessPacket;
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
final class Receive extends Item
|
|
|
|
{
|
2021-07-18 12:10:21 +00:00
|
|
|
private Address $ao;
|
2021-04-01 10:59:15 +00:00
|
|
|
private Collection $list;
|
|
|
|
private ?Item $receiving;
|
|
|
|
|
|
|
|
private mixed $f; // File descriptor
|
|
|
|
private int $start; // Time we started receiving
|
|
|
|
private int $file_pos; // Current write pointer
|
2021-07-18 12:10:21 +00:00
|
|
|
private string $file; // Local filename for file received
|
2021-04-01 10:59:15 +00:00
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
// Initialise our variables
|
|
|
|
$this->list = collect();
|
|
|
|
$this->receiving = NULL;
|
|
|
|
$this->file_pos = 0;
|
|
|
|
$this->f = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function __get($key)
|
|
|
|
{
|
|
|
|
switch ($key) {
|
|
|
|
case 'fd':
|
|
|
|
return is_resource($this->f);
|
|
|
|
|
|
|
|
case 'filepos':
|
|
|
|
return $this->file_pos;
|
|
|
|
|
|
|
|
case 'mtime':
|
|
|
|
case 'name':
|
|
|
|
case 'size':
|
|
|
|
return $this->receiving ? $this->receiving->{'file_'.$key} : NULL;
|
|
|
|
|
|
|
|
case 'to_get':
|
|
|
|
return $this->list
|
|
|
|
->filter(function($item) { return ($item->action & self::I_RECV) && $item->received === FALSE; })
|
|
|
|
->count();
|
|
|
|
|
|
|
|
case 'total_recv':
|
|
|
|
return $this->list
|
|
|
|
->filter(function($item) { return ($item->action & self::I_RECV) && $item->received === TRUE; })
|
|
|
|
->count();
|
|
|
|
|
|
|
|
case 'total_recv_bytes':
|
|
|
|
return $this->list
|
|
|
|
->filter(function($item) { return ($item->action & self::I_RECV) && $item->received === TRUE; })
|
|
|
|
->sum(function($item) { return $item->file_size; });
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new Exception('Unknown key: '.$key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Close the file descriptor for our incoming file
|
|
|
|
*
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
public function close(): void
|
|
|
|
{
|
|
|
|
if (! $this->f)
|
|
|
|
throw new Exception('No file to close');
|
|
|
|
|
|
|
|
if (! $this->file_pos != $this->receiving->file_size)
|
|
|
|
Log::warning(sprintf('%s: - Closing [%s], but missing [%d] bytes',__METHOD__,$this->receiving->file_name,$this->receiving->file_size-$this->file_pos));
|
|
|
|
|
|
|
|
$this->receiving->received = TRUE;
|
|
|
|
|
|
|
|
$end = time()-$this->start;
|
|
|
|
Log::debug(sprintf('%s: - Closing [%s], received in [%d]',__METHOD__,$this->receiving->file_name,$end));
|
|
|
|
|
|
|
|
fclose($this->f);
|
|
|
|
$this->file_pos = 0;
|
|
|
|
$this->f = NULL;
|
2021-07-18 12:10:21 +00:00
|
|
|
|
|
|
|
// If we received a packet, we'll dispatch a job to process it
|
|
|
|
switch ($this->receiving->file_type) {
|
|
|
|
case self::IS_PKT:
|
|
|
|
Log::info(sprintf('%s: - Processing mail packet [%s]',__METHOD__,$this->file));
|
|
|
|
|
|
|
|
foreach (Packet::open(new File($this->file),$this->ao->zone->domain)->messages as $msg) {
|
|
|
|
Log::info(sprintf('%s: - Mail from [%s] to [%s]',__METHOD__,$msg->fftn,$msg->tftn));
|
|
|
|
|
|
|
|
// @todo Quick check that the packet should be processed by us.
|
|
|
|
// @todo validate that the packet's zone is in the domain.
|
|
|
|
|
|
|
|
// Dispatch job.
|
|
|
|
ProcessPacket::dispatchSync($msg);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
Log::debug(sprintf('%s: - Leaving file [%s] in the inbound dir',__METHOD__,$this->file));
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->receiving = NULL;
|
2021-04-01 10:59:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Open the file descriptor to receive a file
|
|
|
|
*
|
|
|
|
* @param bool $check
|
|
|
|
* @return bool
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
2021-07-18 12:10:21 +00:00
|
|
|
public function open(Address $ao,bool $check=FALSE): bool
|
2021-04-01 10:59:15 +00:00
|
|
|
{
|
|
|
|
Log::debug(sprintf('%s: + Start [%d]',__METHOD__,$check));
|
|
|
|
|
|
|
|
// Check we can open this file
|
|
|
|
// @todo
|
|
|
|
// @todo implement return 2 - SKIP file
|
|
|
|
// @todo implement return 4 - SUSPEND(?) file
|
|
|
|
if ($check) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! $this->receiving)
|
|
|
|
throw new Exception('No files currently receiving');
|
|
|
|
|
2021-07-18 12:10:21 +00:00
|
|
|
$this->ao = $ao;
|
2021-04-01 10:59:15 +00:00
|
|
|
$this->file_pos = 0;
|
|
|
|
$this->start = time();
|
2021-07-18 12:10:21 +00:00
|
|
|
$this->file = sprintf('storage/app/%s/%04X-%s',config('app.fido'),$this->ao->id,$this->receiving->recvas);
|
2021-04-01 10:59:15 +00:00
|
|
|
|
2021-07-18 12:10:21 +00:00
|
|
|
Log::debug(sprintf('%s: - Opening [%s]',__METHOD__,$this->file));
|
|
|
|
$this->f = fopen($this->file,'wb');
|
2021-04-01 10:59:15 +00:00
|
|
|
if (! $this->f) {
|
|
|
|
Log::error(sprintf('%s: ! Unable to open file [%s] for writing',__METHOD__,$this->receiving->file_name));
|
|
|
|
return 3; // @todo change to const
|
|
|
|
}
|
|
|
|
|
|
|
|
Log::info(sprintf('%s: = End - File [%s] opened for writing',__METHOD__,$this->receiving->file_name));
|
|
|
|
return 0; // @todo change to const
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a new file to receive
|
|
|
|
*
|
|
|
|
* @param array $file
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
public function new(array $file): void
|
|
|
|
{
|
|
|
|
Log::debug(sprintf('%s: + Start',__METHOD__),['file'=>$file]);
|
|
|
|
|
|
|
|
if ($this->receiving)
|
|
|
|
throw new Exception('Can only have 1 file receiving at a time');
|
|
|
|
|
|
|
|
$o = new Item($file,self::I_RECV);
|
|
|
|
$this->list->push($o);
|
|
|
|
|
|
|
|
$this->receiving = $o;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write data to the file we are receiving
|
|
|
|
*
|
|
|
|
* @param string $buf
|
|
|
|
* @return int
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
public function write(string $buf): int
|
|
|
|
{
|
|
|
|
if (! $this->f)
|
|
|
|
throw new Exception('No file open for read');
|
|
|
|
|
|
|
|
if ($this->file_pos+strlen($buf) > $this->receiving->file_size)
|
|
|
|
throw new Exception(sprintf('Too many bytes received [%d] (%d)?',$this->file_pos+strlen($buf),$this->receiving->file_size));
|
|
|
|
|
|
|
|
$rc = fwrite($this->f,$buf);
|
|
|
|
|
|
|
|
if ($rc === FALSE)
|
|
|
|
throw new FileException('Error while writing to file');
|
|
|
|
|
|
|
|
$this->file_pos += $rc;
|
|
|
|
Log::debug(sprintf('%s: - Write [%d] bytes, file pos now [%d] of [%d]',__METHOD__,$rc,$this->file_pos,$this->receiving->file_size));
|
|
|
|
|
|
|
|
return $rc;
|
|
|
|
}
|
|
|
|
}
|