mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-04-02 17:45:28 +00:00
refactor response model to multi-protocol connection interface
This commit is contained in:
parent
3316a149a6
commit
f0024a0855
8 changed files with 652 additions and 350 deletions
123
src/Abstract/Model/Connection.php
Normal file
123
src/Abstract/Model/Connection.php
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Yggverse\Yoda\Abstract\Model;
|
||||||
|
|
||||||
|
abstract class Connection implements \Yggverse\Yoda\Interface\Model\Connection
|
||||||
|
{
|
||||||
|
// Status
|
||||||
|
protected bool $_completed = false;
|
||||||
|
|
||||||
|
// Response
|
||||||
|
protected ?string $_title = null;
|
||||||
|
protected ?string $_subtitle = null;
|
||||||
|
protected ?string $_tooltip = null;
|
||||||
|
protected ?string $_mime = null;
|
||||||
|
protected ?string $_data = null;
|
||||||
|
protected ?string $_redirect = null;
|
||||||
|
protected ?array $_request = null;
|
||||||
|
|
||||||
|
public function isCompleted(): bool
|
||||||
|
{
|
||||||
|
return $this->_completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCompleted(
|
||||||
|
bool $completed
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$this->_completed = $completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): ?string
|
||||||
|
{
|
||||||
|
return $this->_title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTitle(
|
||||||
|
?string $title = null
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$this->_title = $title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubtitle(): ?string
|
||||||
|
{
|
||||||
|
return $this->_subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSubtitle(
|
||||||
|
?string $subtitle = null
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$this->_subtitle = $subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTooltip(): ?string
|
||||||
|
{
|
||||||
|
return $this->_tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTooltip(
|
||||||
|
?string $tooltip = null
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$this->_tooltip = $tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMime(): ?string
|
||||||
|
{
|
||||||
|
return $this->_mime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMime(
|
||||||
|
?string $mime = null
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$this->_mime = $mime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData(): ?string
|
||||||
|
{
|
||||||
|
return $this->_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setData(
|
||||||
|
?string $data = null
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$this->_data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRedirect(): ?string
|
||||||
|
{
|
||||||
|
return $this->_redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRedirect(
|
||||||
|
?string $redirect = null
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$this->_redirect = $redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequest(): ?array
|
||||||
|
{
|
||||||
|
return $this->_request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRequest(
|
||||||
|
?array $request = null
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$this->_request = $request; // @TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLength(): ?int
|
||||||
|
{
|
||||||
|
return mb_strlen(
|
||||||
|
$this->_data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -135,8 +135,11 @@ class Page
|
||||||
// Hide response form
|
// Hide response form
|
||||||
$this->response->hide();
|
$this->response->hide();
|
||||||
|
|
||||||
// Update content by multi-protocol responser
|
// Update content using multi-protocol driver
|
||||||
$response = new \Yggverse\Yoda\Model\Response(
|
$connection = new \Yggverse\Yoda\Model\Connection;
|
||||||
|
|
||||||
|
// Async request
|
||||||
|
$connection->request(
|
||||||
$this->navbar->request->getValue(),
|
$this->navbar->request->getValue(),
|
||||||
$timeout
|
$timeout
|
||||||
);
|
);
|
||||||
|
|
@ -147,10 +150,10 @@ class Page
|
||||||
// Listen response
|
// Listen response
|
||||||
\Gtk::timeout_add(
|
\Gtk::timeout_add(
|
||||||
$refresh,
|
$refresh,
|
||||||
function() use ($response, $expire)
|
function() use ($connection, $expire)
|
||||||
{
|
{
|
||||||
// Redirect requested
|
// Redirect requested
|
||||||
if ($location = $response->getRedirect())
|
if ($location = $connection->getRedirect())
|
||||||
{
|
{
|
||||||
$this->open(
|
$this->open(
|
||||||
$location
|
$location
|
||||||
|
|
@ -163,7 +166,7 @@ class Page
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response form requested
|
// Response form requested
|
||||||
if ($request = $response->getRequest())
|
if ($request = $connection->getRequest())
|
||||||
{
|
{
|
||||||
$this->response->show(
|
$this->response->show(
|
||||||
$request['placeholder'],
|
$request['placeholder'],
|
||||||
|
|
@ -178,20 +181,20 @@ class Page
|
||||||
|
|
||||||
// Update title
|
// Update title
|
||||||
$this->title->set(
|
$this->title->set(
|
||||||
$response->getTitle(),
|
$connection->getTitle(),
|
||||||
$response->getSubtitle(),
|
$connection->getSubtitle(),
|
||||||
$response->getTooltip()
|
$connection->getTooltip()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update content
|
// Update content
|
||||||
switch ($response->getMime())
|
switch ($connection->getMime())
|
||||||
{
|
{
|
||||||
case 'text/gemini':
|
case 'text/gemini':
|
||||||
|
|
||||||
$title = null;
|
$title = null;
|
||||||
|
|
||||||
$this->content->setGemtext(
|
$this->content->setGemtext(
|
||||||
(string) $response->getData(),
|
(string) $connection->getData(),
|
||||||
$title
|
$title
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -207,7 +210,7 @@ class Page
|
||||||
case 'text/plain':
|
case 'text/plain':
|
||||||
|
|
||||||
$this->content->setPlain(
|
$this->content->setPlain(
|
||||||
(string) $response->getData()
|
(string) $connection->getData()
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -220,7 +223,7 @@ class Page
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop event loop on request completed
|
// Stop event loop on request completed
|
||||||
if ($response->isCompleted())
|
if ($connection->isCompleted())
|
||||||
{
|
{
|
||||||
// Hide progressbar
|
// Hide progressbar
|
||||||
$this->progressbar->hide();
|
$this->progressbar->hide();
|
||||||
|
|
|
||||||
70
src/Interface/Model/Connection.php
Normal file
70
src/Interface/Model/Connection.php
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Yggverse\Yoda\Interface\Model;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Single API for multiple protocols
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
interface Connection
|
||||||
|
{
|
||||||
|
public function request(
|
||||||
|
string $request,
|
||||||
|
int $timeout = 5
|
||||||
|
): void;
|
||||||
|
|
||||||
|
public const MIME_TEXT_GEMINI = 'text/gemini';
|
||||||
|
public const MIME_TEXT_PLAIN = 'text/plain';
|
||||||
|
|
||||||
|
public function isCompleted(): bool;
|
||||||
|
|
||||||
|
public function setCompleted(
|
||||||
|
bool $completed
|
||||||
|
): void;
|
||||||
|
|
||||||
|
public function getTitle(): ?string;
|
||||||
|
|
||||||
|
public function setTitle(
|
||||||
|
?string $title = null
|
||||||
|
): void;
|
||||||
|
|
||||||
|
public function getSubtitle(): ?string;
|
||||||
|
|
||||||
|
public function setSubtitle(
|
||||||
|
?string $subtitle = null
|
||||||
|
): void;
|
||||||
|
|
||||||
|
public function getTooltip(): ?string;
|
||||||
|
|
||||||
|
public function setTooltip(
|
||||||
|
?string $tooltip = null
|
||||||
|
): void;
|
||||||
|
|
||||||
|
public function getMime(): ?string;
|
||||||
|
|
||||||
|
public function setMime(
|
||||||
|
?string $mime = null
|
||||||
|
): void;
|
||||||
|
|
||||||
|
public function getData(): ?string;
|
||||||
|
|
||||||
|
public function setData(
|
||||||
|
?string $data = null
|
||||||
|
): void;
|
||||||
|
|
||||||
|
public function getRedirect(): ?string;
|
||||||
|
|
||||||
|
public function setRedirect(
|
||||||
|
?string $redirect = null
|
||||||
|
): void;
|
||||||
|
|
||||||
|
public function getRequest(): ?array;
|
||||||
|
|
||||||
|
public function setRequest(
|
||||||
|
?array $request = null
|
||||||
|
): void;
|
||||||
|
|
||||||
|
public function getLength(): ?int;
|
||||||
|
}
|
||||||
100
src/Model/Connection.php
Normal file
100
src/Model/Connection.php
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Yggverse\Yoda\Model;
|
||||||
|
|
||||||
|
use \Yggverse\Net\Address;
|
||||||
|
|
||||||
|
use \Yggverse\Yoda\Model\Connection\File;
|
||||||
|
use \Yggverse\Yoda\Model\Connection\Gemini;
|
||||||
|
use \Yggverse\Yoda\Model\Connection\Nex;
|
||||||
|
|
||||||
|
class Connection extends \Yggverse\Yoda\Abstract\Model\Connection
|
||||||
|
{
|
||||||
|
public function request(
|
||||||
|
string $request,
|
||||||
|
int $timeout = 5
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
// Build address instance
|
||||||
|
$address = new Address(
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
|
||||||
|
// Detect protocol
|
||||||
|
switch ($address->getScheme())
|
||||||
|
{
|
||||||
|
case 'file':
|
||||||
|
|
||||||
|
(new File($this))->sync(
|
||||||
|
$address
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'gemini':
|
||||||
|
|
||||||
|
(new Gemini($this))->sync(
|
||||||
|
$address,
|
||||||
|
$timeout
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'nex':
|
||||||
|
|
||||||
|
(new Nex($this))->sync(
|
||||||
|
$address,
|
||||||
|
$timeout
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case null: // no scheme provided
|
||||||
|
|
||||||
|
// Use gemini protocol by default
|
||||||
|
$redirect = new Address(
|
||||||
|
sprintf(
|
||||||
|
'gemini://%s',
|
||||||
|
$address->get()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hostname valid
|
||||||
|
if (filter_var(
|
||||||
|
$redirect->getHost(),
|
||||||
|
FILTER_VALIDATE_DOMAIN,
|
||||||
|
FILTER_FLAG_HOSTNAME
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Redirect
|
||||||
|
$this->setRedirect(
|
||||||
|
$redirect->get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to default search provider
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// @TODO custom providers
|
||||||
|
$this->setRedirect(
|
||||||
|
sprintf(
|
||||||
|
'gemini://tlgs.one/search?%s',
|
||||||
|
urlencode(
|
||||||
|
$request
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
throw new \Exception(
|
||||||
|
_('Protocol not supported')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
131
src/Model/Connection/File.php
Normal file
131
src/Model/Connection/File.php
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Yggverse\Yoda\Model\Connection;
|
||||||
|
|
||||||
|
use \Yggverse\Net\Address;
|
||||||
|
|
||||||
|
use \Yggverse\Yoda\Model\Connection;
|
||||||
|
use \Yggverse\Yoda\Model\Filesystem;
|
||||||
|
|
||||||
|
class File
|
||||||
|
{
|
||||||
|
private Connection $_connection;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Connection $connection
|
||||||
|
) {
|
||||||
|
$this->_connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sync(
|
||||||
|
Address $address
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$this->_connection->setTitle(
|
||||||
|
basename(
|
||||||
|
$address->getPath()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setSubtitle(
|
||||||
|
$address->getPath()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setTooltip(
|
||||||
|
$address->getPath()
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (true)
|
||||||
|
{
|
||||||
|
case ( // is directory
|
||||||
|
$list = Filesystem::getList(
|
||||||
|
$address->getPath()
|
||||||
|
)
|
||||||
|
):
|
||||||
|
$tree = [];
|
||||||
|
|
||||||
|
foreach ($list as $item)
|
||||||
|
{
|
||||||
|
$tree[] = trim(
|
||||||
|
sprintf(
|
||||||
|
'=> file://%s %s',
|
||||||
|
$item['path'],
|
||||||
|
$item['name'] . (
|
||||||
|
// append slash indicator
|
||||||
|
$item['file'] ? null : '/'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_connection->setMime(
|
||||||
|
$this->_connection::MIME_TEXT_GEMINI
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setData(
|
||||||
|
implode(
|
||||||
|
PHP_EOL,
|
||||||
|
$tree
|
||||||
|
) . PHP_EOL
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case file_exists( // is file
|
||||||
|
$address->getPath()
|
||||||
|
) && is_readable(
|
||||||
|
$address->getPath()
|
||||||
|
):
|
||||||
|
$this->_connection->setData(
|
||||||
|
strval(
|
||||||
|
file_get_contents(
|
||||||
|
$address->getPath()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setMime(
|
||||||
|
strval(
|
||||||
|
mime_content_type(
|
||||||
|
$address->getPath()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($this->_connection::MIME_TEXT_PLAIN == $this->_connection->getMime())
|
||||||
|
{
|
||||||
|
$extension = pathinfo(
|
||||||
|
strval(
|
||||||
|
$address->getPath()
|
||||||
|
),
|
||||||
|
PATHINFO_EXTENSION
|
||||||
|
);
|
||||||
|
|
||||||
|
if (in_array($extension, ['gmi', 'gemini']))
|
||||||
|
{
|
||||||
|
$this->_connection->setMime(
|
||||||
|
$this->_connection::MIME_TEXT_GEMINI
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
$this->_connection->setTitle(
|
||||||
|
_('Failure')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setData(
|
||||||
|
_('Could not open location')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_connection->setCompleted(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
144
src/Model/Connection/Gemini.php
Normal file
144
src/Model/Connection/Gemini.php
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Yggverse\Yoda\Model\Connection;
|
||||||
|
|
||||||
|
use \Yggverse\Gemini\Client\Request;
|
||||||
|
use \Yggverse\Gemini\Client\Response;
|
||||||
|
use \Yggverse\Net\Address;
|
||||||
|
|
||||||
|
use \Yggverse\Yoda\Model\Connection;
|
||||||
|
|
||||||
|
class Gemini
|
||||||
|
{
|
||||||
|
private Connection $_connection;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Connection $connection
|
||||||
|
) {
|
||||||
|
$this->_connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sync(
|
||||||
|
Address $address,
|
||||||
|
int $timeout = 5
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$request = new Request(
|
||||||
|
$address->get()
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = new Response(
|
||||||
|
$request->getResponse(
|
||||||
|
$timeout
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Route status code
|
||||||
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-codes
|
||||||
|
switch ($response->getCode())
|
||||||
|
{
|
||||||
|
case 10: // response expected
|
||||||
|
case 11: // sensitive input
|
||||||
|
|
||||||
|
$this->_connection->setMime(
|
||||||
|
$this->_connection::MIME_TEXT_GEMINI
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setRequest(
|
||||||
|
[
|
||||||
|
'placeholder' => $response->getMeta(),
|
||||||
|
'visible' => 11 !== $response->getCode()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 20: // ok
|
||||||
|
|
||||||
|
$this->_connection->setData(
|
||||||
|
$response->getBody()
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (true)
|
||||||
|
{
|
||||||
|
case str_contains(
|
||||||
|
$response->getMeta(),
|
||||||
|
self::MIME_TEXT_GEMINI
|
||||||
|
):
|
||||||
|
|
||||||
|
$this->_connection->setMime(
|
||||||
|
$this->_connection::MIME_TEXT_GEMINI
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case str_contains(
|
||||||
|
$response->getMeta(),
|
||||||
|
self::MIME_TEXT_PLAIN
|
||||||
|
):
|
||||||
|
|
||||||
|
$this->_connection->setMime(
|
||||||
|
$this->_connection::MIME_TEXT_PLAIN
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
throw new \Exception(
|
||||||
|
sprintf(
|
||||||
|
_('MIME type not implemented for %s'),
|
||||||
|
$response->getMeta()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 31: // redirect
|
||||||
|
// show link, no follow
|
||||||
|
|
||||||
|
$this->_connection->setTitle(
|
||||||
|
_('Redirect!')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setData(
|
||||||
|
sprintf(
|
||||||
|
'=> %s',
|
||||||
|
$response->getMeta()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setMime(
|
||||||
|
$this->_connection::MIME_TEXT_GEMINI
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
$this->_connection->setTitle(
|
||||||
|
_('Oops!')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setData(
|
||||||
|
sprintf(
|
||||||
|
'Could not open request (code: %d)',
|
||||||
|
intval(
|
||||||
|
$response->getCode()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setMime(
|
||||||
|
$this->_connection::MIME_TEXT_GEMINI
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_connection->setCompleted(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/Model/Connection/Nex.php
Normal file
69
src/Model/Connection/Nex.php
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Yggverse\Yoda\Model\Connection;
|
||||||
|
|
||||||
|
use \Yggverse\Net\Address;
|
||||||
|
use \Yggverse\Nex\Client;
|
||||||
|
|
||||||
|
use \Yggverse\Yoda\Model\Connection;
|
||||||
|
|
||||||
|
class Nex
|
||||||
|
{
|
||||||
|
private Connection $_connection;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Connection $connection
|
||||||
|
) {
|
||||||
|
$this->_connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO
|
||||||
|
public function sync(
|
||||||
|
Address $address,
|
||||||
|
int $timeout = 5
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
$response = (new \Yggverse\Nex\Client)->request(
|
||||||
|
$address->get(),
|
||||||
|
$timeout
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($response)
|
||||||
|
{
|
||||||
|
$this->_connection->setTitle(
|
||||||
|
strval(
|
||||||
|
$address->getHost()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setData(
|
||||||
|
$response
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setMime(
|
||||||
|
$this->_connection::MIME_TEXT_PLAIN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->_connection->setTitle(
|
||||||
|
_('Oops!')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setData(
|
||||||
|
_('Could not open request')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_connection->setMime(
|
||||||
|
$this->_connection::MIME_TEXT_GEMINI
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_connection->setCompleted(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,338 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Yggverse\Yoda\Model;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Single response API for multiple protocol providers
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class Response
|
|
||||||
{
|
|
||||||
// Subject
|
|
||||||
private \Yggverse\Net\Address $_address;
|
|
||||||
|
|
||||||
// Async status
|
|
||||||
private bool $_completed = false;
|
|
||||||
|
|
||||||
// Response
|
|
||||||
private ?string $_title = null;
|
|
||||||
private ?string $_subtitle = null;
|
|
||||||
private ?string $_tooltip = null;
|
|
||||||
private ?string $_mime = null;
|
|
||||||
private ?string $_data = null;
|
|
||||||
private ?string $_redirect = null;
|
|
||||||
private ?array $_request = null;
|
|
||||||
|
|
||||||
// Config
|
|
||||||
public const MIME_TEXT_GEMINI = 'text/gemini';
|
|
||||||
public const MIME_TEXT_PLAIN = 'text/plain';
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
string $request,
|
|
||||||
int $timeout = 5
|
|
||||||
) {
|
|
||||||
// Build address instance
|
|
||||||
$this->_address = new \Yggverse\Net\Address(
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
|
|
||||||
// Detect protocol
|
|
||||||
switch ($this->_address->getScheme())
|
|
||||||
{
|
|
||||||
case 'file':
|
|
||||||
|
|
||||||
$this->_title = basename(
|
|
||||||
$this->_address->getPath()
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->_subtitle = $this->_address->getPath();
|
|
||||||
$this->_tooltip = $this->_address->getPath();
|
|
||||||
|
|
||||||
switch (true)
|
|
||||||
{
|
|
||||||
case (
|
|
||||||
$list = \Yggverse\Yoda\Model\Filesystem::getList(
|
|
||||||
$this->_address->getPath()
|
|
||||||
)
|
|
||||||
): // directory
|
|
||||||
$tree = [];
|
|
||||||
|
|
||||||
foreach ($list as $item)
|
|
||||||
{
|
|
||||||
$tree[] = trim(
|
|
||||||
sprintf(
|
|
||||||
'=> file://%s %s',
|
|
||||||
$item['path'],
|
|
||||||
$item['name'] . (
|
|
||||||
$item['file'] ? null : '/'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->_mime = self::MIME_TEXT_GEMINI;
|
|
||||||
|
|
||||||
$this->_data = implode(
|
|
||||||
PHP_EOL,
|
|
||||||
$tree
|
|
||||||
) . PHP_EOL;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case file_exists(
|
|
||||||
$this->_address->getPath()
|
|
||||||
) && is_readable(
|
|
||||||
$this->_address->getPath()
|
|
||||||
):
|
|
||||||
$this->_data = strval(
|
|
||||||
file_get_contents(
|
|
||||||
$this->_address->getPath()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->_mime = strval(
|
|
||||||
mime_content_type(
|
|
||||||
$this->_address->getPath()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($this->_mime == self::MIME_TEXT_PLAIN)
|
|
||||||
{
|
|
||||||
$extension = pathinfo(
|
|
||||||
strval(
|
|
||||||
$this->_address->getPath()
|
|
||||||
),
|
|
||||||
PATHINFO_EXTENSION
|
|
||||||
);
|
|
||||||
|
|
||||||
if (in_array($extension, ['gmi', 'gemini']))
|
|
||||||
{
|
|
||||||
$this->_mime = self::MIME_TEXT_GEMINI;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
|
|
||||||
$this->_title = _(
|
|
||||||
'Failure'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->_data = _(
|
|
||||||
'Could not open location'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->_completed = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'gemini': // @TODO async
|
|
||||||
|
|
||||||
$request = new \Yggverse\Gemini\Client\Request(
|
|
||||||
$this->_address->get()
|
|
||||||
);
|
|
||||||
|
|
||||||
$response = new \Yggverse\Gemini\Client\Response(
|
|
||||||
$request->getResponse(
|
|
||||||
$timeout
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Route status code
|
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-codes
|
|
||||||
switch ($response->getCode())
|
|
||||||
{
|
|
||||||
case 10: // response expected
|
|
||||||
case 11: // sensitive input
|
|
||||||
|
|
||||||
$this->_mime = self::MIME_TEXT_GEMINI;
|
|
||||||
|
|
||||||
$this->_request =
|
|
||||||
[
|
|
||||||
'placeholder' => $response->getMeta(),
|
|
||||||
'visible' => 11 !== $response->getCode()
|
|
||||||
];
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 20: // ok
|
|
||||||
|
|
||||||
$this->_data = $response->getBody();
|
|
||||||
|
|
||||||
switch (true)
|
|
||||||
{
|
|
||||||
case str_contains(
|
|
||||||
$response->getMeta(),
|
|
||||||
self::MIME_TEXT_GEMINI
|
|
||||||
):
|
|
||||||
|
|
||||||
$this->_mime = self::MIME_TEXT_GEMINI;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case str_contains(
|
|
||||||
$response->getMeta(),
|
|
||||||
self::MIME_TEXT_PLAIN
|
|
||||||
):
|
|
||||||
|
|
||||||
$this->_mime = self::MIME_TEXT_PLAIN;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
|
|
||||||
throw new \Exception(
|
|
||||||
sprintf(
|
|
||||||
_('MIME type not implemented for %s'),
|
|
||||||
$response->getMeta()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->_completed = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 31: // redirect
|
|
||||||
// show link, no follow
|
|
||||||
|
|
||||||
$this->_data = sprintf(
|
|
||||||
'=> %s',
|
|
||||||
$response->getMeta()
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->_mime = self::MIME_TEXT_GEMINI;
|
|
||||||
|
|
||||||
$this->_completed = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
|
|
||||||
$this->_title = _(
|
|
||||||
'Oops!'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->_data = sprintf(
|
|
||||||
'Could not open request (code: %d)',
|
|
||||||
intval(
|
|
||||||
$response->getCode()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->_mime = self::MIME_TEXT_GEMINI;
|
|
||||||
|
|
||||||
$this->_completed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'nex': // @TODO async
|
|
||||||
|
|
||||||
$this->_data = (
|
|
||||||
new \Yggverse\Nex\Client
|
|
||||||
)->request(
|
|
||||||
$this->_address->get(),
|
|
||||||
$timeout
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->_mime = self::MIME_TEXT_PLAIN; // @TODO
|
|
||||||
|
|
||||||
$this->_completed = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case null:
|
|
||||||
|
|
||||||
// Build gemini protocol address
|
|
||||||
$address = new \Yggverse\Net\Address(
|
|
||||||
sprintf(
|
|
||||||
'gemini://%s',
|
|
||||||
$this->_address->get()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Validate hostname
|
|
||||||
if (filter_var(
|
|
||||||
$address->getHost(),
|
|
||||||
FILTER_VALIDATE_DOMAIN,
|
|
||||||
FILTER_FLAG_HOSTNAME
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// Request redirect
|
|
||||||
$this->_redirect = $address->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request redirect to search provider
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// @TODO custom providers
|
|
||||||
$this->_redirect = sprintf(
|
|
||||||
'gemini://tlgs.one/search?%s',
|
|
||||||
urlencode(
|
|
||||||
$request
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
default:
|
|
||||||
|
|
||||||
throw new \Exception(
|
|
||||||
_('Protocol not supported')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isCompleted(): bool
|
|
||||||
{
|
|
||||||
return $this->_completed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTitle(): ?string
|
|
||||||
{
|
|
||||||
return $this->_title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSubtitle(): ?string
|
|
||||||
{
|
|
||||||
return $this->_subtitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTooltip(): ?string
|
|
||||||
{
|
|
||||||
return $this->_tooltip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMime(): ?string
|
|
||||||
{
|
|
||||||
return $this->_mime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getData(): ?string
|
|
||||||
{
|
|
||||||
return $this->_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRedirect(): ?string
|
|
||||||
{
|
|
||||||
return $this->_redirect;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRequest(): ?array
|
|
||||||
{
|
|
||||||
return $this->_request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLength(): ?int
|
|
||||||
{
|
|
||||||
return mb_strlen(
|
|
||||||
$this->_data
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue