311 lines
7.8 KiB
PHP
311 lines
7.8 KiB
PHP
<?php
|
|
|
|
namespace App\Classes\File;
|
|
|
|
use Exception;
|
|
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Log;
|
|
use League\Flysystem\UnreadableFileEncountered;
|
|
|
|
use App\Models\Address;
|
|
|
|
/**
|
|
* Object representing the files we are sending
|
|
*
|
|
* @property-read resource fd
|
|
* @property-read int file_mtime
|
|
* @property-read int file_size
|
|
* @property-read string file_name
|
|
* @property-read int mail_size
|
|
* @property-read int total_count
|
|
* @property-read int total_sent
|
|
* @property-read int total_sent_bytes
|
|
*/
|
|
final class Send extends Item
|
|
{
|
|
private const LOGKEY = 'IS-';
|
|
|
|
private Collection $list;
|
|
private ?Item $sending;
|
|
private Collection $packets;
|
|
|
|
private mixed $f; // File descriptor
|
|
private int $start; // Time we started sending
|
|
private int $file_pos; // Current read pointer
|
|
|
|
public function __construct()
|
|
{
|
|
// Initialise our variables
|
|
$this->list = collect();
|
|
$this->packets = collect();
|
|
$this->sending = NULL;
|
|
$this->file_pos = 0;
|
|
$this->f = NULL;
|
|
}
|
|
|
|
public function __get($key)
|
|
{
|
|
switch ($key) {
|
|
case 'fd':
|
|
return is_resource($this->f) ?: $this->f;
|
|
|
|
case 'file_count':
|
|
return $this->list
|
|
->filter(function($item) { return $item->isType(self::IS_FILE); })
|
|
->count();
|
|
|
|
case 'file_size':
|
|
return $this->list
|
|
->filter(function($item) { return $item->isType(self::IS_FILE); })
|
|
->sum(function($item) { return $item->file_size; });
|
|
|
|
case 'filepos':
|
|
return $this->file_pos;
|
|
|
|
case 'mail_count':
|
|
return $this->list
|
|
->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); })
|
|
->count()
|
|
+ $this->packets->count();
|
|
|
|
case 'mail_size':
|
|
return $this->list
|
|
->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); })
|
|
->sum(function($item) { return $item->file_size; })
|
|
+ $this->packets->sum(function($item) { return $item->file_size; });
|
|
|
|
case 'sendas':
|
|
return $this->sending ? $this->sending->{$key} : NULL;
|
|
|
|
case 'name':
|
|
case 'mtime':
|
|
case 'size':
|
|
return $this->sending ? $this->sending->{'file_'.$key} : NULL;
|
|
|
|
case 'total_sent':
|
|
return $this->list
|
|
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
|
|
->count()
|
|
+ $this->packets
|
|
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
|
|
->count();
|
|
|
|
case 'total_sent_bytes':
|
|
return $this->list
|
|
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
|
|
->sum(function($item) { return $item->file_size; })
|
|
+ $this->packets
|
|
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
|
|
->sum(function($item) { return $item->file_size; });
|
|
|
|
case 'total_count':
|
|
return $this->list
|
|
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
|
|
->count()
|
|
+ $this->packets
|
|
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
|
|
->count();
|
|
|
|
case 'total_size':
|
|
return $this->list
|
|
->sum(function($item) { return $item->file_size; })
|
|
+ $this->packets->sum(function($item) { return $item->file_size; });
|
|
|
|
default:
|
|
throw new Exception('Unknown key: '.$key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a file to the list of files to send
|
|
*
|
|
* @param string $file
|
|
* @throws Exception
|
|
* @todo Catch if we add the same file twice
|
|
*/
|
|
public function add(string $file): void
|
|
{
|
|
Log::debug(sprintf('%s:+ add [%s]',self::LOGKEY,$file));
|
|
|
|
try {
|
|
$this->list->push(new Item($file,self::I_SEND));
|
|
|
|
} catch (FileNotFoundException) {
|
|
Log::error(sprintf('%s:! Item [%s] doesnt exist',self::LOGKEY,$file));
|
|
return;
|
|
|
|
} catch (UnreadableFileEncountered) {
|
|
Log::error(sprintf('%s:! Item [%s] cannot be read',self::LOGKEY,$file));
|
|
return;
|
|
|
|
// Uncaught, rethrow the error
|
|
} catch (Exception $e) {
|
|
throw new Exception($e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Close the file descriptor of the file we are sending
|
|
*
|
|
* @param bool $successful
|
|
* @throws Exception
|
|
*/
|
|
public function close(bool $successful): void
|
|
{
|
|
if (! $this->f)
|
|
throw new Exception('No file to close');
|
|
|
|
if ($successful) {
|
|
$this->sending->sent = TRUE;
|
|
$end = time()-$this->start;
|
|
Log::debug(sprintf('%s: - Closing [%s], sent in [%d]',self::LOGKEY,$this->sending->file_name,$end));
|
|
}
|
|
|
|
if (! $this->sending instanceof Mail)
|
|
fclose($this->f);
|
|
|
|
$this->sending = NULL;
|
|
$this->file_pos = 0;
|
|
$this->f = NULL;
|
|
}
|
|
|
|
/**
|
|
* Check if we are at the end of the file
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function feof(): bool
|
|
{
|
|
return ($this->sending instanceof Mail) ? ($this->file_pos == $this->size) : feof($this->f);
|
|
}
|
|
|
|
/**
|
|
* Open a file for sending
|
|
*
|
|
* @return bool
|
|
* @throws Exception
|
|
*/
|
|
public function open(): bool
|
|
{
|
|
Log::debug(sprintf('%s:+ open',self::LOGKEY));
|
|
|
|
// If we have mail, we'll send that first
|
|
if ($this->sending = $this->packets
|
|
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
|
|
->first())
|
|
{
|
|
$this->file_pos = 0;
|
|
$this->start = time();
|
|
$this->f = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
$this->sending = $this->list
|
|
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === FALSE; })
|
|
->first();
|
|
|
|
if (! $this->sending)
|
|
throw new Exception('No files to open');
|
|
|
|
$this->file_pos = 0;
|
|
$this->start = time();
|
|
|
|
$this->f = fopen($this->sending->file_name,'rb');
|
|
if (! $this->f) {
|
|
Log::error(sprintf('%s:! Unable to open file [%s] for reading',self::LOGKEY,$this->sending->file_name));
|
|
return FALSE;
|
|
}
|
|
|
|
Log::info(sprintf('%s:= open - File [%s] opened with size [%d]',self::LOGKEY,$this->sending->file_name,$this->sending->file_size));
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Add our mail to the send queue
|
|
*
|
|
* @param Address $ao
|
|
* @throws Exception
|
|
* @todo We need to make this into a transaction, incase the transfer fails.
|
|
*/
|
|
public function mail(Address $ao): void
|
|
{
|
|
// If the node is marked as hold - dont send any mail.
|
|
if ($ao->system->hold) {
|
|
Log::info(sprintf('%s: - System [%d] mail is marked as hold - not checking for mail.',self::LOGKEY,$ao->system_id));
|
|
return;
|
|
}
|
|
|
|
// Netmail
|
|
if ($x=$ao->getNetmail()) {
|
|
Log::debug(sprintf('%s: - Netmail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
|
|
|
|
$this->packets->push(new Mail($x,self::I_SEND));
|
|
}
|
|
|
|
// Echomail
|
|
if ($x=$ao->getEchomail()) {
|
|
Log::debug(sprintf('%s: - Echomail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
|
|
|
|
$this->packets->push(new Mail($x,self::I_SEND));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read bytes of the sending file
|
|
*
|
|
* @param int $length
|
|
* @return string|null
|
|
* @throws UnreadableFileEncountered
|
|
* @throws Exception
|
|
*/
|
|
public function read(int $length): ?string
|
|
{
|
|
if (! $this->f)
|
|
throw new Exception('No file open for read');
|
|
|
|
// We are sending mail
|
|
if ($this->sending instanceof Mail) {
|
|
$data = $this->sending->read($this->file_pos,$length);
|
|
} else {
|
|
$data = fread($this->f,$length);
|
|
}
|
|
|
|
$this->file_pos += strlen($data);
|
|
Log::debug(sprintf('%s: - Read [%d] bytes, file pos now [%d]',self::LOGKEY,strlen($data),$this->file_pos));
|
|
|
|
if ($data === FALSE)
|
|
throw new UnreadableFileEncountered('Error reading file: '.$this->sending->file_name);
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Seek to a specific position of our file
|
|
*
|
|
* @param int $pos
|
|
* @return bool
|
|
* @throws Exception
|
|
*/
|
|
public function seek(int $pos): bool
|
|
{
|
|
if (! $this->f)
|
|
throw new Exception('No file open for seek');
|
|
|
|
if ($this->sending instanceof Mail) {
|
|
$pos = ($pos < $this->size) ? $pos : $this->size;
|
|
$rc = TRUE;
|
|
|
|
} else {
|
|
$rc = (fseek($this->f,$pos,SEEK_SET) === 0);
|
|
}
|
|
|
|
if ($rc)
|
|
$this->file_pos = $pos;
|
|
|
|
Log::debug(sprintf('%s:= Seeked to [%d]',self::LOGKEY,$this->file_pos));
|
|
|
|
return $rc;
|
|
}
|
|
} |