Implemented file sending during BINKP and EMSI sessions
This commit is contained in:
parent
58341db0fb
commit
b1b86ca04a
@ -18,6 +18,7 @@ use App\Traits\EncodeUTF8;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Message
|
* Class Message
|
||||||
|
* Represents the structure of a message in a packet
|
||||||
* NOTE: FTN Echomail Messages are ZONE agnostic.
|
* NOTE: FTN Echomail Messages are ZONE agnostic.
|
||||||
*
|
*
|
||||||
* @package App\Classes
|
* @package App\Classes
|
||||||
|
@ -12,6 +12,9 @@ use Symfony\Component\HttpFoundation\File\File;
|
|||||||
use App\Classes\FTN as FTNBase;
|
use App\Classes\FTN as FTNBase;
|
||||||
use App\Models\{Address,Setup,Software,System,Zone};
|
use App\Models\{Address,Setup,Software,System,Zone};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the structure of a packet
|
||||||
|
*/
|
||||||
class Packet extends FTNBase implements \Iterator, \Countable
|
class Packet extends FTNBase implements \Iterator, \Countable
|
||||||
{
|
{
|
||||||
private const LOGKEY = 'PKT';
|
private const LOGKEY = 'PKT';
|
||||||
|
@ -4,6 +4,9 @@ namespace App\Classes\FTN;
|
|||||||
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class to hold the common functions for automatic responding to echomail/netmail messages
|
||||||
|
*/
|
||||||
abstract class Process
|
abstract class Process
|
||||||
{
|
{
|
||||||
private const LOGKEY = 'R--';
|
private const LOGKEY = 'R--';
|
||||||
|
@ -4,16 +4,18 @@ namespace App\Classes\FTN;
|
|||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use League\Flysystem\UnableToWriteFile;
|
use League\Flysystem\UnableToWriteFile;
|
||||||
|
|
||||||
use App\Classes\FTN as FTNBase;
|
use App\Classes\FTN as FTNBase;
|
||||||
use App\Models\{Address,File,Filearea};
|
use App\Models\{Address,File,Filearea,Setup};
|
||||||
use App\Traits\EncodeUTF8;
|
use App\Traits\EncodeUTF8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class TIC
|
* Class TIC
|
||||||
|
* Used create the structure of TIC files
|
||||||
*
|
*
|
||||||
* @package App\Classes
|
* @package App\Classes
|
||||||
*/
|
*/
|
||||||
@ -50,34 +52,96 @@ class Tic extends FTNBase
|
|||||||
'pw' => FALSE, // Password
|
'pw' => FALSE, // Password
|
||||||
];
|
];
|
||||||
|
|
||||||
private File $file;
|
private File $fo;
|
||||||
private Filearea $area;
|
private Filearea $area;
|
||||||
private ?string $areadesc = NULL;
|
private Collection $values;
|
||||||
private ?string $pw = NULL;
|
|
||||||
|
|
||||||
private Address $origin; // Should be first address in Path
|
private Address $origin; // Should be first address in Path
|
||||||
private Address $from; // Should be last address in Path
|
private Address $from; // Should be last address in Path
|
||||||
private Address $to; // Should be me
|
private Address $to; // Should be me
|
||||||
|
|
||||||
public function __construct(private string $filename) {
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->fo = new File;
|
||||||
|
|
||||||
|
$this->fo->kludges = collect();
|
||||||
|
$this->fo->set_path = collect();
|
||||||
|
$this->fo->set_seenby = collect();
|
||||||
|
$this->fo->rogue_path = collect();
|
||||||
|
$this->fo->rogue_seenby = collect();
|
||||||
|
|
||||||
|
$this->values = collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this TIC file bring us a nodelist
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isNodelist(): bool
|
||||||
|
{
|
||||||
|
return (($this->fo->nodelist_filearea_id === $this->fo->filearea->domain->filearea_id)
|
||||||
|
&& (preg_match(str_replace(['.','?'],['\.','.'],'#^'.$this->fo->filearea->domain->nodelist_filename.'$#i'),$this->fo->file)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a TIC file for an address
|
||||||
|
*
|
||||||
|
* @param Address $ao
|
||||||
|
* @param File $fo
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate(Address $ao,File $fo): string
|
||||||
|
{
|
||||||
|
$sysaddress = Setup::findOrFail(config('app.id'))->system->match($ao->zone)->first();
|
||||||
|
|
||||||
|
$result = collect();
|
||||||
|
|
||||||
|
// Origin is the first address in our path
|
||||||
|
$result->put('ORIGIN',$fo->path->first()->ftn3d);
|
||||||
|
$result->put('FROM',$sysaddress->ftn3d);
|
||||||
|
$result->put('TO',$ao->ftn3d);
|
||||||
|
$result->put('FILE',$fo->file);
|
||||||
|
$result->put('SIZE',$fo->size);
|
||||||
|
if ($fo->description)
|
||||||
|
$result->put('DESC',$fo->description);
|
||||||
|
$result->put('AREA',$fo->filearea->name);
|
||||||
|
$result->put('AREADESC',$fo->filearea->description);
|
||||||
|
$result->put('PW',$ao->session('ticpass'));
|
||||||
|
$result->put('CRC',sprintf("%X",$fo->crc));
|
||||||
|
|
||||||
|
$out = '';
|
||||||
|
foreach ($result as $key=>$value)
|
||||||
|
$out .= sprintf("%s %s\r\n",$key,$value);
|
||||||
|
|
||||||
|
foreach ($fo->path as $o)
|
||||||
|
$out .= sprintf("PATH %s %s %s\r\n",$o->ftn3d,$o->pivot->datetime,$o->pivot->extra);
|
||||||
|
|
||||||
|
foreach ($fo->seenby as $o)
|
||||||
|
$out .= sprintf("SEENBY %s\r\n",$o->ftn3d);
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a TIC file from an existing filename
|
||||||
|
*
|
||||||
|
* @param string $filename
|
||||||
|
* @return void
|
||||||
|
* @throws FileNotFoundException
|
||||||
|
*/
|
||||||
|
public function load(string $filename): void
|
||||||
|
{
|
||||||
Log::info(sprintf('%s:Processing TIC file [%s]',self::LOGKEY,$filename));
|
Log::info(sprintf('%s:Processing TIC file [%s]',self::LOGKEY,$filename));
|
||||||
|
|
||||||
$fo = new File;
|
|
||||||
|
|
||||||
$fo->kludges = collect();
|
|
||||||
$fo->set_path = collect();
|
|
||||||
$fo->set_seenby = collect();
|
|
||||||
$fo->rogue_path = collect();
|
|
||||||
$fo->rogue_seenby = collect();
|
|
||||||
|
|
||||||
list($hex,$name) = explode('-',$filename);
|
list($hex,$name) = explode('-',$filename);
|
||||||
$hex = basename($hex);
|
$hex = basename($hex);
|
||||||
|
|
||||||
if (! file_exists($filename))
|
if (! file_exists($filename))
|
||||||
throw new FileNotFoundException(sprintf('%s:File [%s] doesnt exist',self::LOGKEY,realpath($filename)));
|
throw new FileNotFoundException(sprintf('%s:File [%s] doesnt exist',self::LOGKEY,realpath($filename)));
|
||||||
|
|
||||||
if (! is_writable($filename))
|
if (! is_readable($filename))
|
||||||
throw new UnableToWriteFile(sprintf('%s:File [%s] is not writable',self::LOGKEY,realpath($filename)));
|
throw new UnableToWriteFile(sprintf('%s:File [%s] is not readable',self::LOGKEY,realpath($filename)));
|
||||||
|
|
||||||
$f = fopen($filename,'rb');
|
$f = fopen($filename,'rb');
|
||||||
if (! $f) {
|
if (! $f) {
|
||||||
@ -112,38 +176,43 @@ class Tic extends FTNBase
|
|||||||
if (! Storage::disk('local')->exists($x=sprintf('%s/%s-%s',config('app.fido'),$hex,$matches[2])))
|
if (! Storage::disk('local')->exists($x=sprintf('%s/%s-%s',config('app.fido'),$hex,$matches[2])))
|
||||||
throw new FileNotFoundException(sprintf('File not found? [%s]',$x));
|
throw new FileNotFoundException(sprintf('File not found? [%s]',$x));
|
||||||
|
|
||||||
$fo->{$k} = $matches[2];
|
$this->fo->{$k} = $matches[2];
|
||||||
$fo->fullname = $x;
|
$this->fo->fullname = $x;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'areadesc':
|
case 'areadesc':
|
||||||
case 'pw':
|
$areadesc = $matches[2];
|
||||||
case 'created':
|
|
||||||
$this->{$k} = $matches[2];
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'lfile':
|
case 'created':
|
||||||
case 'size':
|
// ignored
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pw':
|
||||||
|
$pw = $matches[2];
|
||||||
|
|
||||||
case 'desc':
|
case 'desc':
|
||||||
|
case 'lfile':
|
||||||
case 'magic':
|
case 'magic':
|
||||||
case 'replaces':
|
case 'replaces':
|
||||||
$fo->{$k} = $matches[2];
|
case 'size':
|
||||||
|
$this->fo->{$k} = $matches[2];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'fullname':
|
case 'fullname':
|
||||||
$fo->lfile = $matches[2];
|
$this->fo->lfile = $matches[2];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'date':
|
case 'date':
|
||||||
$fo->datetime = Carbon::create($matches[2]);
|
$this->fo->datetime = Carbon::create($matches[2]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ldesc':
|
case 'ldesc':
|
||||||
$fo->{$k} .= $matches[2];
|
$this->fo->{$k} .= $matches[2];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'crc':
|
case 'crc':
|
||||||
$fo->{$k} = hexdec($matches[2]);
|
$this->fo->{$k} = hexdec($matches[2]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'path':
|
case 'path':
|
||||||
@ -152,9 +221,9 @@ class Tic extends FTNBase
|
|||||||
$ao = Address::findFTN($x[1]);
|
$ao = Address::findFTN($x[1]);
|
||||||
|
|
||||||
if (! $ao) {
|
if (! $ao) {
|
||||||
$fo->rogue_path->push($matches[2]);
|
$this->fo->rogue_path->push($matches[2]);
|
||||||
} else {
|
} else {
|
||||||
$fo->set_path->push(['address'=>$ao,'datetime'=>Carbon::createFromTimestamp($x[8]),'extra'=>$x[9]]);
|
$this->fo->set_path->push(['address'=>$ao,'datetime'=>Carbon::createFromTimestamp($x[8]),'extra'=>$x[9]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -163,36 +232,36 @@ class Tic extends FTNBase
|
|||||||
$ao = Address::findFTN($matches[2]);
|
$ao = Address::findFTN($matches[2]);
|
||||||
|
|
||||||
if (! $ao) {
|
if (! $ao) {
|
||||||
$fo->rogue_seenby->push($matches[2]);
|
$this->fo->rogue_seenby->push($matches[2]);
|
||||||
} else {
|
} else {
|
||||||
$fo->set_seenby->push($ao->id);
|
$this->fo->set_seenby->push($ao->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$fo->kludges->push($line);
|
$this->fo->kludges->push($line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose($f);
|
fclose($f);
|
||||||
|
|
||||||
$f = fopen($x=Storage::disk('local')->path($fo->fullname),'rb');
|
$f = fopen($x=Storage::disk('local')->path($this->fo->fullname),'rb');
|
||||||
$stat = fstat($f);
|
$stat = fstat($f);
|
||||||
fclose($f);
|
fclose($f);
|
||||||
|
|
||||||
// Validate Size
|
// Validate Size
|
||||||
if ($fo->size !== ($y=$stat['size']))
|
if ($this->fo->size !== ($y=$stat['size']))
|
||||||
throw new \Exception(sprintf('TIC file size [%d] doesnt match file [%s] (%d)',$fo->size,$fo->fullname,$y));
|
throw new \Exception(sprintf('TIC file size [%d] doesnt match file [%s] (%d)',$this->fo->size,$this->fo->fullname,$y));
|
||||||
|
|
||||||
// Validate CRC
|
// Validate CRC
|
||||||
if (sprintf('%08x',$fo->crc) !== ($y=hash_file('crc32b',$x)))
|
if (sprintf('%08x',$this->fo->crc) !== ($y=hash_file('crc32b',$x)))
|
||||||
throw new \Exception(sprintf('TIC file CRC [%08x] doesnt match file [%s] (%s)',$fo->crc,$fo->fullname,$y));
|
throw new \Exception(sprintf('TIC file CRC [%08x] doesnt match file [%s] (%s)',$this->fo->crc,$this->fo->fullname,$y));
|
||||||
|
|
||||||
// Validate Password
|
// Validate Password
|
||||||
if ($this->pw !== ($y=$this->from->session('ticpass')))
|
if ($pw !== ($y=$this->from->session('ticpass')))
|
||||||
throw new \Exception(sprintf('TIC file PASSWORD [%s] doesnt match system [%s] (%s)',$this->pw,$this->from->ftn,$y));
|
throw new \Exception(sprintf('TIC file PASSWORD [%s] doesnt match system [%s] (%s)',$pw,$this->from->ftn,$y));
|
||||||
|
|
||||||
// Validate Sender is linked (and permitted to send)
|
// Validate Sender is linked (and permitted to send)
|
||||||
if ($this->from->fileareas->search(function($item) { return $item->id === $this->area->id; }) === FALSE)
|
if ($this->from->fileareas->search(function($item) { return $item->id === $this->area->id; }) === FALSE)
|
||||||
@ -200,7 +269,7 @@ class Tic extends FTNBase
|
|||||||
|
|
||||||
// If the filearea is to be autocreated, create it
|
// If the filearea is to be autocreated, create it
|
||||||
if (! $this->area->exists) {
|
if (! $this->area->exists) {
|
||||||
$this->area->description = $this->areadesc;
|
$this->area->description = $areadesc;
|
||||||
$this->area->active = TRUE;
|
$this->area->active = TRUE;
|
||||||
$this->area->public = FALSE;
|
$this->area->public = FALSE;
|
||||||
$this->area->notes = 'Autocreated';
|
$this->area->notes = 'Autocreated';
|
||||||
@ -208,21 +277,13 @@ class Tic extends FTNBase
|
|||||||
$this->area->save();
|
$this->area->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
$fo->filearea_id = $this->area->id;
|
$this->fo->filearea_id = $this->area->id;
|
||||||
$fo->fftn_id = $this->origin->id;
|
$this->fo->fftn_id = $this->origin->id;
|
||||||
|
|
||||||
// If the file create time is blank, we'll take the files
|
// If the file create time is blank, we'll take the files
|
||||||
if (! $fo->datetime)
|
if (! $this->fo->datetime)
|
||||||
$fo->datetime = Carbon::createFromTimestamp($stat['ctime']);
|
$this->fo->datetime = Carbon::createFromTimestamp($stat['ctime']);
|
||||||
|
|
||||||
$fo->save();
|
$this->fo->save();
|
||||||
|
|
||||||
$this->fo = $fo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isNodelist(): bool
|
|
||||||
{
|
|
||||||
return (($this->fo->nodelist_filearea_id === $this->fo->filearea->domain->filearea_id)
|
|
||||||
&& (preg_match(str_replace(['.','?'],['\.','.'],'#^'.$this->fo->filearea->domain->nodelist_filename.'$#i'),$this->fo->file)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,8 +4,11 @@ namespace App\Classes\File;
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use League\Flysystem\UnreadableFileEncountered;
|
use League\Flysystem\UnreadableFileEncountered;
|
||||||
|
|
||||||
|
use App\Models\File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A file we are sending or receiving
|
* A file we are sending or receiving
|
||||||
*
|
*
|
||||||
@ -35,6 +38,7 @@ class Item
|
|||||||
protected int $file_mtime = 0;
|
protected int $file_mtime = 0;
|
||||||
protected int $file_type = 0;
|
protected int $file_type = 0;
|
||||||
protected int $action = 0;
|
protected int $action = 0;
|
||||||
|
protected File $filemodel;
|
||||||
|
|
||||||
public bool $sent = FALSE;
|
public bool $sent = FALSE;
|
||||||
public bool $received = FALSE;
|
public bool $received = FALSE;
|
||||||
@ -51,19 +55,27 @@ class Item
|
|||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case self::I_SEND:
|
case self::I_SEND:
|
||||||
if (! is_string($file))
|
if ($file instanceof File) {
|
||||||
throw new Exception('Invalid object creation - file should be a string');
|
$this->filemodel = $file;
|
||||||
|
// @todo We should catch any exceptions if the default storage is s3 (it is) and we cannot find the file, or the s3 call fails
|
||||||
|
$this->file_size = Storage::size($file->full_storage_path);
|
||||||
|
$this->file_mtime = Storage::lastModified($file->full_storage_path);
|
||||||
|
|
||||||
if (! file_exists($file))
|
} else {
|
||||||
throw new FileNotFoundException('Item doesnt exist: '.$file);
|
if (! is_string($file))
|
||||||
|
throw new Exception('Invalid object creation - file should be a string');
|
||||||
|
|
||||||
if (! is_readable($file))
|
if (! file_exists($file))
|
||||||
throw new UnreadableFileEncountered('Item cannot be read: '.$file);
|
throw new FileNotFoundException('Item doesnt exist: '.$file);
|
||||||
|
|
||||||
$this->file_name = $file;
|
if (! is_readable($file))
|
||||||
$x = stat($file);
|
throw new UnreadableFileEncountered('Item cannot be read: '.$file);
|
||||||
$this->file_size = $x['size'];
|
|
||||||
$this->file_mtime = $x['mtime'];
|
$this->file_name = $file;
|
||||||
|
$x = stat($file);
|
||||||
|
$this->file_size = $x['size'];
|
||||||
|
$this->file_mtime = $x['mtime'];
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -104,7 +116,7 @@ class Item
|
|||||||
return $this->file_name;
|
return $this->file_name;
|
||||||
|
|
||||||
case 'sendas':
|
case 'sendas':
|
||||||
return basename($this->file_name);
|
return $this->file_name ? basename($this->file_name) : $this->filemodel->file;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Exception('Unknown key: '.$key);
|
throw new Exception('Unknown key: '.$key);
|
||||||
|
@ -6,6 +6,7 @@ use Exception;
|
|||||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use League\Flysystem\UnreadableFileEncountered;
|
use League\Flysystem\UnreadableFileEncountered;
|
||||||
|
|
||||||
use App\Models\Address;
|
use App\Models\Address;
|
||||||
@ -52,12 +53,12 @@ final class Send extends Item
|
|||||||
|
|
||||||
case 'file_count':
|
case 'file_count':
|
||||||
return $this->list
|
return $this->list
|
||||||
->filter(function($item) { return $item->isType(self::IS_FILE); })
|
->filter(function($item) { return $item->isType(self::IS_FILE|self::IS_TIC); })
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
case 'file_size':
|
case 'file_size':
|
||||||
return $this->list
|
return $this->list
|
||||||
->filter(function($item) { return $item->isType(self::IS_FILE); })
|
->filter(function($item) { return $item->isType(self::IS_FILE|self::IS_TIC); })
|
||||||
->sum(function($item) { return $item->file_size; });
|
->sum(function($item) { return $item->file_size; });
|
||||||
|
|
||||||
case 'filepos':
|
case 'filepos':
|
||||||
@ -162,7 +163,8 @@ final class Send extends Item
|
|||||||
Log::debug(sprintf('%s: - Closing [%s], sent in [%d]',self::LOGKEY,$this->sending->file_name,$end));
|
Log::debug(sprintf('%s: - Closing [%s], sent in [%d]',self::LOGKEY,$this->sending->file_name,$end));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->sending instanceof Mail)
|
// @todo This should be done better isType == file?
|
||||||
|
if ((! $this->sending instanceof Mail) && (! $this->sending->isType(self::IS_TIC)))
|
||||||
fclose($this->f);
|
fclose($this->f);
|
||||||
|
|
||||||
$this->sending = NULL;
|
$this->sending = NULL;
|
||||||
@ -177,7 +179,44 @@ final class Send extends Item
|
|||||||
*/
|
*/
|
||||||
public function feof(): bool
|
public function feof(): bool
|
||||||
{
|
{
|
||||||
return ($this->sending instanceof Mail) ? ($this->file_pos == $this->size) : feof($this->f);
|
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 $xx) {
|
||||||
|
$this->list->push(new Item($xx,self::I_SEND));
|
||||||
|
$this->list->push(new Tic($ao,$xx,self::I_SEND));
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,14 +251,27 @@ final class Send extends Item
|
|||||||
$this->file_pos = 0;
|
$this->file_pos = 0;
|
||||||
$this->start = time();
|
$this->start = time();
|
||||||
|
|
||||||
$this->f = fopen($this->sending->file_name,'rb');
|
// If sending->file is a string, then we dont need to actually open anything
|
||||||
if (! $this->f) {
|
if ($this->sending->isType(self::IS_TIC)) {
|
||||||
Log::error(sprintf('%s:! Unable to open file [%s] for reading',self::LOGKEY,$this->sending->file_name));
|
$this->f = TRUE;
|
||||||
return FALSE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::info(sprintf('%s:= open - File [%s] opened with size [%d]',self::LOGKEY,$this->sending->file_name,$this->sending->file_size));
|
// If sending file is a File::class, then our file is s3
|
||||||
return TRUE;
|
if (! $this->sending->file_name && $this->sending->filemodel) {
|
||||||
|
$this->f = Storage::readStream($this->sending->filemodel->full_storage_path);
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,7 +288,7 @@ final class Send extends Item
|
|||||||
|
|
||||||
// If the node is marked as hold - dont send any mail.
|
// If the node is marked as hold - dont send any mail.
|
||||||
if ($ao->system->hold) {
|
if ($ao->system->hold) {
|
||||||
Log::info(sprintf('%s: - System [%d] mail is marked as hold - not checking for mail.',self::LOGKEY,$ao->system_id));
|
Log::info(sprintf('%s: - System [%d] is marked as hold - not checking for mail.',self::LOGKEY,$ao->system_id));
|
||||||
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
@ -276,6 +328,11 @@ final class Send extends Item
|
|||||||
// We are sending mail
|
// We are sending mail
|
||||||
if ($this->sending instanceof Mail) {
|
if ($this->sending instanceof Mail) {
|
||||||
$data = $this->sending->read($this->file_pos,$length);
|
$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 {
|
} else {
|
||||||
$data = fread($this->f,$length);
|
$data = fread($this->f,$length);
|
||||||
}
|
}
|
||||||
@ -303,7 +360,7 @@ final class Send extends Item
|
|||||||
if (! $this->f)
|
if (! $this->f)
|
||||||
throw new Exception('No file open for seek');
|
throw new Exception('No file open for seek');
|
||||||
|
|
||||||
if ($this->sending instanceof Mail) {
|
if (($this->sending instanceof Mail) || $this->sending->isType(self::IS_TIC)) {
|
||||||
$pos = ($pos < $this->size) ? $pos : $this->size;
|
$pos = ($pos < $this->size) ? $pos : $this->size;
|
||||||
$rc = TRUE;
|
$rc = TRUE;
|
||||||
|
|
||||||
|
38
app/Classes/File/Tic.php
Normal file
38
app/Classes/File/Tic.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Classes\File;
|
||||||
|
|
||||||
|
use App\Classes\FTN\Tic as FTNTic;
|
||||||
|
use App\Models\{Address,File};
|
||||||
|
|
||||||
|
class Tic extends Item
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function __construct(Address $ao,File $fo,int $action)
|
||||||
|
{
|
||||||
|
$this->action |= $action;
|
||||||
|
|
||||||
|
$tic = new FTNTic;
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case self::I_SEND:
|
||||||
|
$this->file = $tic->generate($ao,$fo);
|
||||||
|
$this->file_type = self::IS_TIC;
|
||||||
|
$this->file_name = sprintf('%s.tic',sprintf('%08x',$fo->id));
|
||||||
|
$this->file_size = strlen($this->file);
|
||||||
|
$this->file_mtime = $fo->created_at->timestamp;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new \Exception('Unknown action: '.$action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function read(int $start,int $length): string
|
||||||
|
{
|
||||||
|
return substr($this->file,$start,$length);
|
||||||
|
}
|
||||||
|
}
|
@ -752,6 +752,7 @@ final class Binkp extends BaseProtocol
|
|||||||
foreach ($this->node->aka_remote_authed as $ao) {
|
foreach ($this->node->aka_remote_authed as $ao) {
|
||||||
Log::debug(sprintf('%s: - M_eob Checking for any new mail to [%s]',self::LOGKEY,$ao->ftn));
|
Log::debug(sprintf('%s: - M_eob Checking for any new mail to [%s]',self::LOGKEY,$ao->ftn));
|
||||||
$this->send->mail($ao);
|
$this->send->mail($ao);
|
||||||
|
$this->send->files($ao);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->send->total_count)
|
if ($this->send->total_count)
|
||||||
@ -920,7 +921,7 @@ final class Binkp extends BaseProtocol
|
|||||||
{
|
{
|
||||||
// @todo Commit our mail transaction if the remote end confirmed receipt of the file.
|
// @todo Commit our mail transaction if the remote end confirmed receipt of the file.
|
||||||
if ($this->sessionGet(self::SE_SENDFILE)) {
|
if ($this->sessionGet(self::SE_SENDFILE)) {
|
||||||
Log::debug(sprintf('%s:Packet [%s] sent. (%s)',self::LOGKEY,$this->send->sendas,$this->send->name));
|
Log::debug(sprintf('%s:Packet/File [%s] sent. (%s)',self::LOGKEY,$this->send->sendas,$this->send->name));
|
||||||
$this->sessionClear(self::SE_SENDFILE);
|
$this->sessionClear(self::SE_SENDFILE);
|
||||||
$this->send->close(TRUE);
|
$this->send->close(TRUE);
|
||||||
|
|
||||||
@ -928,7 +929,7 @@ final class Binkp extends BaseProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->sessionGet(self::SE_WAITGOT)) {
|
if ($this->sessionGet(self::SE_WAITGOT)) {
|
||||||
Log::debug(sprintf('%s:Packet [%s] sent. (%s)',self::LOGKEY,$this->send->sendas,$this->send->name));
|
Log::debug(sprintf('%s:Packet/File [%s] sent. (%s)',self::LOGKEY,$this->send->sendas,$this->send->name));
|
||||||
$this->sessionClear(self::SE_WAITGOT);
|
$this->sessionClear(self::SE_WAITGOT);
|
||||||
$this->send->close(TRUE);
|
$this->send->close(TRUE);
|
||||||
|
|
||||||
@ -1107,6 +1108,7 @@ final class Binkp extends BaseProtocol
|
|||||||
if ($this->node->aka_authed)
|
if ($this->node->aka_authed)
|
||||||
foreach ($this->node->aka_remote_authed as $ao) {
|
foreach ($this->node->aka_remote_authed as $ao) {
|
||||||
$this->send->mail($ao);
|
$this->send->mail($ao);
|
||||||
|
$this->send->files($ao);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->file_size));
|
$this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->file_size));
|
||||||
@ -1191,6 +1193,7 @@ final class Binkp extends BaseProtocol
|
|||||||
if ($this->node->aka_authed)
|
if ($this->node->aka_authed)
|
||||||
foreach ($this->node->aka_remote_authed as $ao) {
|
foreach ($this->node->aka_remote_authed as $ao) {
|
||||||
$this->send->mail($ao);
|
$this->send->mail($ao);
|
||||||
|
$this->send->files($ao);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->file_size));
|
$this->msgs(self::BPM_NUL,sprintf('TRF %lu %lu',$this->send->mail_size,$this->send->file_size));
|
||||||
|
@ -1186,12 +1186,21 @@ final class EMSI extends BaseProtocol implements CRCInterface,ZmodemInterface
|
|||||||
// Add our mail to the queue if we have authenticated
|
// Add our mail to the queue if we have authenticated
|
||||||
if ($this->node->aka_authed)
|
if ($this->node->aka_authed)
|
||||||
foreach ($this->node->aka_remote_authed as $ao) {
|
foreach ($this->node->aka_remote_authed as $ao) {
|
||||||
|
// Send mail
|
||||||
while ($this->send->mail($ao)) {
|
while ($this->send->mail($ao)) {
|
||||||
$z = new Zmodem;
|
$z = new Zmodem;
|
||||||
|
|
||||||
if (! $z->zmodem_sendinit($this->client,$zap) && $this->send->total_count)
|
if (! $z->zmodem_sendinit($this->client,$zap) && $this->send->total_count)
|
||||||
$z->zmodem_sendfile($this->send);
|
$z->zmodem_sendfile($this->send);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send files
|
||||||
|
while ($this->send->files($ao)) {
|
||||||
|
$z = new Zmodem;
|
||||||
|
|
||||||
|
if (! $z->zmodem_sendinit($this->client,$zap) && $this->send->total_count)
|
||||||
|
$z->zmodem_sendfile($this->send);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::debug(sprintf('%s:- Finished sending',self::LOGKEY));
|
Log::debug(sprintf('%s:- Finished sending',self::LOGKEY));
|
||||||
|
@ -233,8 +233,17 @@ final class SocketClient {
|
|||||||
*/
|
*/
|
||||||
public function close(): void
|
public function close(): void
|
||||||
{
|
{
|
||||||
socket_shutdown($this->connection);
|
try {
|
||||||
socket_close($this->connection);
|
socket_shutdown($this->connection);
|
||||||
|
} catch (\ErrorException $e) {
|
||||||
|
Log::error(sprintf('%s:+ Shutting down socket [%s]',self::LOGKEY,$e->getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket_close($this->connection);
|
||||||
|
} catch (\ErrorException $e) {
|
||||||
|
Log::error(sprintf('%s:+ Closing socket [%s]',self::LOGKEY,$e->getMessage()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Classes\Sock;
|
namespace App\Classes\Sock;
|
||||||
|
|
||||||
|
// @todo Can we change this to use socket_strerr() && socket_last_error()
|
||||||
final class SocketException extends \Exception {
|
final class SocketException extends \Exception {
|
||||||
public const CANT_CREATE_SOCKET = 1;
|
public const CANT_CREATE_SOCKET = 1;
|
||||||
public const CANT_BIND_SOCKET = 2;
|
public const CANT_BIND_SOCKET = 2;
|
||||||
@ -11,6 +12,7 @@ final class SocketException extends \Exception {
|
|||||||
public const SOCKET_ERROR = 6;
|
public const SOCKET_ERROR = 6;
|
||||||
public const SOCKET_EAGAIN = 11;
|
public const SOCKET_EAGAIN = 11;
|
||||||
public const SOCKET_READ = 22;
|
public const SOCKET_READ = 22;
|
||||||
|
public const CONNECTION_RESET = 104;
|
||||||
|
|
||||||
private array $messages = [
|
private array $messages = [
|
||||||
self::CANT_CREATE_SOCKET => 'Can\'t create socket: "%s"',
|
self::CANT_CREATE_SOCKET => 'Can\'t create socket: "%s"',
|
||||||
@ -21,6 +23,7 @@ final class SocketException extends \Exception {
|
|||||||
self::SOCKET_ERROR => 'Socket Error: "%s"',
|
self::SOCKET_ERROR => 'Socket Error: "%s"',
|
||||||
self::SOCKET_EAGAIN => 'Socket Resource Temporarily Unavailable - Try again',
|
self::SOCKET_EAGAIN => 'Socket Resource Temporarily Unavailable - Try again',
|
||||||
self::SOCKET_READ => 'Unable to read from socket',
|
self::SOCKET_READ => 'Unable to read from socket',
|
||||||
|
self::CONNECTION_RESET => 'Connection reset by peer',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(int $code,string $params=NULL) {
|
public function __construct(int $code,string $params=NULL) {
|
||||||
|
@ -41,7 +41,8 @@ class TicProcess implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$to = new Tic($this->file);
|
$to = new Tic;
|
||||||
|
$to->load($this->file);
|
||||||
|
|
||||||
Log::info(sprintf('%s:Processed [%s] storing [%s] as id [%d]',self::LOGKEY,$this->file,$to->fo->file,$to->fo->id));
|
Log::info(sprintf('%s:Processed [%s] storing [%s] as id [%d]',self::LOGKEY,$this->file,$to->fo->file,$to->fo->id));
|
||||||
|
|
||||||
|
@ -572,6 +572,32 @@ class Address extends Model
|
|||||||
return $pkt;
|
return $pkt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get files for this node (including it's children)
|
||||||
|
*
|
||||||
|
* @param bool $update
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getFiles(bool $update=TRUE): Collection
|
||||||
|
{
|
||||||
|
if (($files=$this->filesWaiting())
|
||||||
|
->count())
|
||||||
|
{
|
||||||
|
Log::debug(sprintf('%s:= Got [%d] files for [%s] for sending',self::LOGKEY,$files->count(),$this->ftn));
|
||||||
|
|
||||||
|
// @todo This should be transactional, incase the transfer fails
|
||||||
|
if ($files->count() && $update)
|
||||||
|
DB::table('file_seenby')
|
||||||
|
->whereIn('file_id',$files->pluck('id'))
|
||||||
|
->where('address_id',$this->id)
|
||||||
|
->whereNull('sent_at')
|
||||||
|
->whereNotNull('export_at')
|
||||||
|
->update(['sent_at'=>Carbon::now()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get netmail for this node (including it's children)
|
* Get netmail for this node (including it's children)
|
||||||
*
|
*
|
||||||
|
@ -154,7 +154,7 @@ class File extends Model
|
|||||||
public function path()
|
public function path()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Address::class,'file_path')
|
return $this->belongsToMany(Address::class,'file_path')
|
||||||
->withPivot(['id','parent_id','extra']);
|
->withPivot(['id','parent_id','datetime','extra']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ATTRIBUTES */
|
/* ATTRIBUTES */
|
||||||
|
Loading…
Reference in New Issue
Block a user