clrghouz/app/Classes/File/Send.php

399 lines
9.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 Illuminate\Support\Facades\Storage;
use League\Flysystem\UnreadableFileEncountered;
use App\Models\Address;
/**
* Object representing the files we are sending
*
* @property-read resource fd
* @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 string $comp_data;
public function __construct()
{
// Initialise our variables
$this->list = collect();
$this->packets = collect();
$this->sending = NULL;
}
public function __get($key)
{
switch ($key) {
case 'fd':
return is_resource($this->f) ?: $this->f;
case 'files_count':
return $this->list
->filter(function($item) { return $item->isType(self::IS_FILE|self::IS_TIC); })
->count();
case 'files_size':
return $this->list
->filter(function($item) { return $item->isType(self::IS_FILE|self::IS_TIC); })
->sum(function($item) { return $item->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->size; })
+ $this->packets->sum(function($item) { return $item->size; });
case 'sendas':
return $this->sending?->{$key};
// The mtime is the time of the youngest message in the packet for the sending packet
case 'mtime':
return $this->sending?->youngest()->timestamp;
// The name is derived from the youngest message in the packet
case 'name':
return sprintf('%08x',timew($this->sending?->youngest()));
case 'size':
return strlen($this->sending?->file);
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->size; })
+ $this->packets
->filter(function($item) { return ($item->action & self::I_SEND) && $item->sent === TRUE; })
->sum(function($item) { return $item->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->size; })
+ $this->packets->sum(function($item) { return $item->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());
}
}
/*
private function compress(string $comp_mode): void
{
switch ($comp_mode) {
case 'BZ2':
$this->comp_data = bzcompress($buf);
break;
case 'GZ':
$this->comp_data = gzcompress($buf);
break;
}
}
*/
/**
* 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->name,$end));
}
// @todo This should be done better isType == file?
if ((! $this->sending instanceof Mail) && (! $this->sending->isType(self::IS_TIC)))
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->sending->isType(self::IS_TIC)))
? ($this->file_pos === $this->size)
: feof($this->f);
}
/**
* Add our mail to the send queue
*
* @param Address $ao
* @return bool
* @throws Exception
* @todo We need to make this into a transaction, incase the transfer fails.
*/
public function files(Address $ao): bool
{
$file = FALSE;
// If the node is marked as hold - dont send any files.
if ($ao->system->hold) {
Log::info(sprintf('%s: - System [%d] is marked as hold - not checking for files.',self::LOGKEY,$ao->system_id));
return FALSE;
}
// Files
if (($x=$ao->getFiles())->count()) {
Log::debug(sprintf('%s:- [%d] Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn));
// Add Files
foreach ($x as $fo) {
$this->list->push(new Item($fo,self::I_SEND));
$this->list->push(new Tic($ao,$fo,self::I_SEND));
}
$file = TRUE;
}
return $file;
}
/**
* Open a file for sending
*
* @param string $compress
* @return bool
* @throws Exception
*/
public function open(string $compress=''): 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;
/*
if ($compress)
$this->comp_data = $this->compdata($compress);
*/
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();
// If sending->file is a string, then we dont need to actually open anything
if ($this->sending->isType(self::IS_TIC)) {
$this->f = TRUE;
return TRUE;
}
// If sending file is a File::class, then our file is s3
if (! $this->sending->name && $this->sending->filemodel) {
$this->f = Storage::readStream($this->sending->filemodel->full_storage_path);
return TRUE;
} else {
$this->f = fopen($this->sending->name,'rb');
if (! $this->f) {
Log::error(sprintf('%s:! Unable to open file [%s] for reading',self::LOGKEY,$this->sending->name));
return FALSE;
}
Log::info(sprintf('%s:= open - File [%s] opened with size [%d]',self::LOGKEY,$this->sending->name,$this->sending->size));
return TRUE;
}
}
/**
* Add our mail to the send queue
*
* @param Address $ao
* @return bool
* @throws Exception
* @todo We need to make this into a transaction, incase the transfer fails.
*/
public function mail(Address $ao,bool $update=TRUE): bool
{
$mail = FALSE;
// If the node is marked as hold - dont send any mail.
if ($ao->system->hold) {
Log::info(sprintf('%s: - System [%d] is marked as hold - not checking for mail.',self::LOGKEY,$ao->system_id));
return FALSE;
}
// Netmail
if ($x=$ao->getNetmail($update)) {
Log::debug(sprintf('%s: - Netmail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
$this->packets->push(new Mail($x,self::I_SEND));
$mail = TRUE;
}
// Echomail
if ($x=$ao->getEchomail($update)) {
Log::debug(sprintf('%s: - Echomail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));
$this->packets->push(new Mail($x,self::I_SEND));
$mail = TRUE;
}
return $mail;
}
/**
* 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);
// We are sending a tic file
} else if ($this->sending->isType(self::IS_TIC)) {
$data = $this->sending->read($this->file_pos,$length);
} else {
$data = fread($this->f,$length);
}
$this->file_pos += strlen($data);
if ($this->DEBUG)
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->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) || $this->sending->isType(self::IS_TIC)) {
$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;
}
}