clrghouz/app/Models/Netmail.php
Deon George 81f59dcbb8
All checks were successful
Create Docker Image / Build Docker Image (x86_64) (push) Successful in 42s
Create Docker Image / Build Docker Image (arm64) (push) Successful in 1m50s
Create Docker Image / Final Docker Image Manifest (push) Successful in 11s
Remove EncodeUTF8 infavour of using attribute casting only. The implementation of EncodeUTF8 was not correct, essentially removing any previous casting causing issues when saving a record.
2024-06-01 10:46:02 +10:00

236 lines
6.0 KiB
PHP

<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Casts\{CollectionOrNull,CompressedStringOrNull,UTF8StringOrNull};
use App\Interfaces\Packet;
use App\Pivots\ViaPivot;
use App\Traits\{MessageAttributes,MsgID};
final class Netmail extends Model implements Packet
{
use SoftDeletes,MsgID,MessageAttributes;
private const LOGKEY = 'MN-';
private const PATH_REGEX = '/^([0-9]+:[0-9]+\/[0-9]+(\..*)?)\s+@([0-9.a-zA-Z]+)\s+(.*)$/';
/**
* Kludges that we absorb in this model
*/
private const kludges = [
'MSGID:'=>'msgid',
'REPLY:'=>'replyid',
'Via' => 'set_path',
];
protected $casts = [
'to' => UTF8StringOrNull::class,
'from' => UTF8StringOrNull::class,
'subject' => UTF8StringOrNull::class,
'origin' => UTF8StringOrNull::class,
'tearline' => UTF8StringOrNull::class,
'tagline' => UTF8StringOrNull::class,
'datetime' => 'datetime:Y-m-d H:i:s',
'kludges' => CollectionOrNull::class,
'msg' => CompressedStringOrNull::class,
'msg_src' => CompressedStringOrNull::class,
'sent_at' => 'datetime:Y-m-d H:i:s',
];
public function __set($key,$value)
{
switch ($key) {
case 'kludges':
if (! count($value))
return;
if (array_key_exists($value[0],self::kludges)) {
$this->{self::kludges[$value[0]]} = $value[1];
} else {
$this->kludges->put($value[0],$value[1]);
}
break;
case 'set_fftn':
case 'set_tftn':
// Values that we pass to boot() to record how we got this netmail
case 'set_pkt':
case 'set_recvtime':
case 'set_sender':
// @todo We'll normalise these values when saving the netmail
case 'set_tagline':
case 'set_tearline':
case 'set_origin':
$this->set->put($key,$value);
break;
// The path the netmail went through to get here
case 'set_path':
if (! $this->set->has($key))
$this->set->put($key,collect());
$this->set->get($key)->push($value);
break;
default:
parent::__set($key,$value);
}
}
public static function boot()
{
parent::boot();
static::creating(function($model) {
if (isset($model->errors) && $model->errors->count())
throw new \Exception('Cannot save, validation errors exist');
});
static::created(function($model) {
$nodes = collect();
// Parse PATH
// <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]
if ($model->set->has('set_path')) {
foreach ($model->set->get('set_path') as $line) {
$m = [];
if (preg_match(self::PATH_REGEX,$line,$m)) {
// Address
// @todo Do we need to add a domain here, since the path line may not include one
$ao = Address::findFTN($m[1]);
// Time
$t = [];
$datetime = '';
if (! preg_match('/^([0-9]+\.[0-9]+)(\.?(.*))?$/',$m[3],$t))
Log::alert(sprintf('%s:! Unable to determine time from [%s]',self::LOGKEY,$m[3]));
else
$datetime = Carbon::createFromFormat('Ymd.His',$t[1],$t[3] ?? '');
if (! $ao) {
Log::alert(sprintf('%s:! Undefined Node [%s] in netmail path.',self::LOGKEY,$m[1]));
//$rogue->push(['node'=>$m[1],'datetime'=>$datetime,'program'=>$m[4]]);
} else {
$nodes->push(['node'=>$ao,'datetime'=>$datetime,'program'=>$m[4]]);
}
}
}
// If there are no details (Mystic), we'll create a blank
} elseif ($model->set->has('set_sender')) {
$nodes->push(['node'=>$model->set->get('set_sender'),'datetime'=>Carbon::now(),'program'=>'Unknown']);
}
// Save the Path
$ppoid = NULL;
foreach ($nodes as $path) {
$po = DB::select('INSERT INTO netmail_path (netmail_id,address_id,parent_id,datetime,program) VALUES (?,?,?,?,?) RETURNING id',[
$model->id,
$path['node']->id,
$ppoid,
(string)$path['datetime'],
$path['program'],
]);
$ppoid = $po[0]->id;
}
// Our last node in the path is our sender
if ($nodes->count() && $model->set->has('set_pkt') && $model->set->has('set_sender') && $model->set->has('set_recvtime')) {
DB::update('UPDATE netmail_path set recv_pkt=?,recv_at=?,recv_id=? where address_id=? and netmail_id=?',[
$model->set->get('set_pkt'),
$model->set->get('set_recvtime'),
$model->set->get('set_sender')->id,
Arr::get($nodes->last(),'node')->id,
$model->id,
]);
}
// Save our origin, tearline & tagline
if ($model->set->has('set_tagline'))
$model->tagline = $model->set->get('set_tagline');
if ($model->set->has('set_tearline'))
$model->tearline = $model->set->get('set_tearline');
if ($model->set->has('set_origin'))
$model->origin = $model->set->get('set_origin');
$model->save();
});
}
/* RELATIONS */
public function fftn()
{
return $this
->belongsTo(Address::class)
->withTrashed();
}
public function path()
{
return $this->belongsToMany(Address::class,'netmail_path')
->withPivot(['id','parent_id','datetime','program','recv_pkt','recv_id'])
->orderBy('netmail_path.id')
->using(ViaPivot::class);
}
public function tftn()
{
return $this
->belongsTo(Address::class)
->withTrashed();
}
/* ATTRIBUTES */
/**
* Enable rendering the path even if the model hasnt been saved
*
* @return Collection
*/
public function getPathAttribute(): Collection
{
return ((! $this->exists) && $this->set->has('set_path'))
? $this->set->get('set_path')->map(function($item) {
$m = [];
preg_match(self::PATH_REGEX,$item,$m);
return $m[1];
})
: $this->getRelationValue('path');
}
/* METHODS */
/**
* Render the via line
*
* @param Address $ao
* @return string
* @throws \Exception
*/
public function via(Address $ao): string
{
if (! $ao->pivot)
throw new \Exception('Cannot render the via line without an address record without a path pivot');
return sprintf('%s @%s.UTC %s',
$ao->ftn3d,
$ao->pivot->datetime->format('Ymd.His'),
$ao->pivot->program);
}
}