mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Minimally functional, highly experimental, working server
- Basic update service handles only one feed at a time and possibly leaks memory - Output for REST requests is still very basic - No avatar support - No reporting of whether cron works - No cleanup before or after feed updates
This commit is contained in:
parent
d4674c61b2
commit
6d4aa4db6e
9 changed files with 194 additions and 32 deletions
10
arsse.php
Normal file
10
arsse.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
namespace JKingWeb\Arsse;
|
||||
require_once __DIR__.DIRECTORY_SEPARATOR."bootstrap.php";
|
||||
Data::load(new Conf());
|
||||
|
||||
if(\PHP_SAPI=="cli") {
|
||||
(new Service)->watch();
|
||||
} else {
|
||||
(new REST)->dispatch();
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
<?php
|
||||
require_once __DIR__.DIRECTORY_SEPARATOR."bootstrap.php";
|
||||
Data::load(new Conf());
|
22
lib/REST.php
22
lib/REST.php
|
@ -23,6 +23,8 @@ class REST {
|
|||
// Tiny Tiny RSS https://tt-rss.org/gitlab/fox/tt-rss/wikis/ApiReference
|
||||
// Fever https://feedafever.com/api
|
||||
// NewsBlur http://www.newsblur.com/api
|
||||
// Miniflux https://github.com/miniflux/miniflux/blob/master/docs/json-rpc-api.markdown
|
||||
// CommaFeed https://www.commafeed.com/api/
|
||||
];
|
||||
|
||||
function __construct() {
|
||||
|
@ -36,8 +38,22 @@ class REST {
|
|||
$class = $this->apis[$api]['class'];
|
||||
$drv = new $class();
|
||||
$out = $drv->dispatch($req);
|
||||
echo "Status: ".$out->code."\n";
|
||||
echo json_encode($out->payload,\JSON_PRETTY_PRINT);
|
||||
header("Status: ".$out->code." ".Data::$lang->msg("HTTP.Status.".$out->code));
|
||||
if(!is_null($out->payload)) {
|
||||
header("Content-Type: ".$out->type);
|
||||
switch($out->type) {
|
||||
case REST\Response::T_JSON:
|
||||
$body = json_encode($out->payload,\JSON_PRETTY_PRINT);
|
||||
break;
|
||||
default:
|
||||
$body = (string) $out->payload;
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach($out->fields as $field) {
|
||||
header($field);
|
||||
}
|
||||
echo $body;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -49,6 +65,6 @@ class REST {
|
|||
if(strpos($url, $api['match'])===0) return $id;
|
||||
}
|
||||
// or throw an exception otherwise
|
||||
throw new REST\ExceptionURL("apiNotSupported", $url);
|
||||
throw new REST\Exception501();
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ use JKingWeb\Arsse\REST\Exception405;
|
|||
|
||||
class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||
const REALM = "NextCloud News API v1-2";
|
||||
const VERSION = "11.0.5";
|
||||
|
||||
protected $dateFormat = "unix";
|
||||
|
||||
|
@ -110,12 +111,19 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
'star/multiple' => ['PUT' => "articleMarkStarredMulti"],
|
||||
'unstar/multiple' => ['PUT' => "articleMarkStarredMulti"],
|
||||
],
|
||||
'cleanup' => [],
|
||||
'version' => [
|
||||
'' => ['GET' => "versionReport"],
|
||||
'cleanup' => [
|
||||
'before-update' => ['GET' => "cleanupBefore"],
|
||||
'after-update' => ['GET' => "cleanupAfter"],
|
||||
],
|
||||
'version' => [
|
||||
'' => ['GET' => "serverVersion"],
|
||||
],
|
||||
'status' => [
|
||||
'' => ['GET' => "serverStatus"],
|
||||
],
|
||||
'user' => [
|
||||
'' => ['GET' => "userStatus"],
|
||||
],
|
||||
'status' => [],
|
||||
'user' => [],
|
||||
];
|
||||
// the first path element is the overall scope of the request
|
||||
$scope = $url[0];
|
||||
|
@ -285,11 +293,6 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
return new Response(204);
|
||||
}
|
||||
|
||||
// return the server version
|
||||
protected function versionReport(array $url, array $data): Response {
|
||||
return new Response(200, ['version' => \JKingWeb\Arsse\VERSION]);
|
||||
}
|
||||
|
||||
// return list of feeds which should be refreshed
|
||||
protected function feedListStale(array $url, array $data): Response {
|
||||
// function requires admin rights per spec
|
||||
|
@ -461,7 +464,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
$c->reverse(true);
|
||||
}
|
||||
// set the edition mark-off; the database uses an or-equal comparison for internal consistency, but the protocol does not, so we must adjust by one
|
||||
if(isset($data['offset'])) {
|
||||
if(isset($data['offset']) && $data['offset'] > 0) {
|
||||
if($c->reverse) {
|
||||
$c->latestEdition($data['offset'] - 1);
|
||||
} else {
|
||||
|
@ -590,4 +593,49 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
|||
$t->commit();
|
||||
return new Response(204);
|
||||
}
|
||||
|
||||
protected function userStatus(array $url, array $data): Response {
|
||||
// FIXME: stub
|
||||
$data = Data::$db->userPropertiesGet(Data::$user->id);
|
||||
$out = [
|
||||
'userId' => Data::$user->id,
|
||||
'displayName' => $data['name'] ?? Data::$user->id,
|
||||
'lastLoginTimestamp' => time(),
|
||||
'avatar' => null,
|
||||
];
|
||||
return new Response(200, $out);
|
||||
}
|
||||
|
||||
protected function cleanupBefore(array $url, array $data): Response {
|
||||
// function requires admin rights per spec
|
||||
if(Data::$user->rightsGet(Data::$user->id)==User::RIGHTS_NONE) return new Response(403);
|
||||
// FIXME: stub
|
||||
return new Response(204);
|
||||
}
|
||||
|
||||
protected function cleanupAfter(array $url, array $data): Response {
|
||||
// function requires admin rights per spec
|
||||
if(Data::$user->rightsGet(Data::$user->id)==User::RIGHTS_NONE) return new Response(403);
|
||||
// FIXME: stub
|
||||
return new Response(204);
|
||||
}
|
||||
|
||||
// return the server version
|
||||
protected function serverVersion(array $url, array $data): Response {
|
||||
return new Response(200, [
|
||||
'version' => self::VERSION,
|
||||
'arsse_version' => \JKingWeb\Arsse\VERSION,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function serverStatus(array $url, array $data): Response {
|
||||
// FIXME: stub
|
||||
return new Response(200, [
|
||||
'version' => self::VERSION,
|
||||
'arsse_version' => \JKingWeb\Arsse\VERSION,
|
||||
'warnings' => [
|
||||
'improperlyConfiguredCron' => false,
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
42
lib/Service.php
Normal file
42
lib/Service.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse;
|
||||
|
||||
class Service {
|
||||
|
||||
/**
|
||||
* @var Service\Driver
|
||||
*/
|
||||
protected $drv;
|
||||
/**
|
||||
* @var \DateInterval
|
||||
*/
|
||||
protected $interval;
|
||||
|
||||
function __construct() {
|
||||
$driver = Data::$conf->serviceDriver;
|
||||
$this->drv = new $driver();
|
||||
$this->interval = new \DateInterval(Data::$conf->serviceFrequency); // FIXME: this needs to fall back in case of incorrect input
|
||||
}
|
||||
|
||||
function watch() {
|
||||
while(true) {
|
||||
$t = new \DateTime();
|
||||
$list = Data::$db->feedListStale();
|
||||
if($list) {
|
||||
echo date("H:i:s")." Updating feeds ".json_encode($list)."\n";
|
||||
// TODO: pre-cleanup
|
||||
$this->drv->queue(...$list);
|
||||
$this->drv->exec();
|
||||
$this->drv->clean();
|
||||
// TODO: post-cleanup
|
||||
} else {
|
||||
echo date("H:i:s")." No feeds to update; sleeping\n";
|
||||
}
|
||||
$t->add($this->interval);
|
||||
do {
|
||||
@time_sleep_until($t->getTimestamp());
|
||||
} while($t->getTimestamp() > time());
|
||||
}
|
||||
}
|
||||
}
|
11
lib/Service/Driver.php
Normal file
11
lib/Service/Driver.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\Service;
|
||||
|
||||
interface Driver {
|
||||
static function driverName(): string;
|
||||
static function requirementsMet(): bool;
|
||||
function queue(int ...$feeds): int;
|
||||
function exec(): int;
|
||||
function clean(): bool;
|
||||
}
|
38
lib/Service/Internal/Driver.php
Normal file
38
lib/Service/Internal/Driver.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\Service\Internal;
|
||||
use JKingWeb\Arsse\Data;
|
||||
|
||||
class Driver implements \JKingWeb\Arsse\Service\Driver {
|
||||
protected $queue = [];
|
||||
|
||||
static function driverName(): string {
|
||||
return Data::$lang->msg("Driver.Service.Internal.Name");
|
||||
}
|
||||
|
||||
static function requirementsMet(): bool {
|
||||
// this driver has no requirements
|
||||
return true;
|
||||
}
|
||||
|
||||
function __construct() {
|
||||
}
|
||||
|
||||
function queue(int ...$feeds): int {
|
||||
$this->queue = array_merge($this->queue, $feeds);
|
||||
return sizeof($this->queue);
|
||||
}
|
||||
|
||||
function exec(): int {
|
||||
while(sizeof($this->queue)) {
|
||||
$id = array_shift($this->queue);
|
||||
Data::$db->feedUpdate($id);
|
||||
}
|
||||
return Data::$conf->serviceQueueWidth - sizeof($this->queue);
|
||||
}
|
||||
|
||||
function clean(): bool {
|
||||
$this->queue = [];
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,23 +1,22 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\User\Internal;
|
||||
use JKingWeb\Arsse\User\Driver as Iface;
|
||||
|
||||
final class Driver implements Iface {
|
||||
final class Driver implements \JKingWeb\Arsse\User\Driver {
|
||||
use InternalFunctions;
|
||||
|
||||
protected $db;
|
||||
protected $functions = [
|
||||
"auth" => Iface::FUNC_INTERNAL,
|
||||
"userList" => Iface::FUNC_INTERNAL,
|
||||
"userExists" => Iface::FUNC_INTERNAL,
|
||||
"userAdd" => Iface::FUNC_INTERNAL,
|
||||
"userRemove" => Iface::FUNC_INTERNAL,
|
||||
"userPasswordSet" => Iface::FUNC_INTERNAL,
|
||||
"userPropertiesGet" => Iface::FUNC_INTERNAL,
|
||||
"userPropertiesSet" => Iface::FUNC_INTERNAL,
|
||||
"userRightsGet" => Iface::FUNC_INTERNAL,
|
||||
"userRightsSet" => Iface::FUNC_INTERNAL,
|
||||
"auth" => self::FUNC_INTERNAL,
|
||||
"userList" => self::FUNC_INTERNAL,
|
||||
"userExists" => self::FUNC_INTERNAL,
|
||||
"userAdd" => self::FUNC_INTERNAL,
|
||||
"userRemove" => self::FUNC_INTERNAL,
|
||||
"userPasswordSet" => self::FUNC_INTERNAL,
|
||||
"userPropertiesGet" => self::FUNC_INTERNAL,
|
||||
"userPropertiesSet" => self::FUNC_INTERNAL,
|
||||
"userRightsGet" => self::FUNC_INTERNAL,
|
||||
"userRightsSet" => self::FUNC_INTERNAL,
|
||||
];
|
||||
|
||||
static public function driverName(): string {
|
||||
|
@ -29,7 +28,7 @@ final class Driver implements Iface {
|
|||
if(array_key_exists($function, $this->functions)) {
|
||||
return $this->functions[$function];
|
||||
} else {
|
||||
return Iface::FUNC_NOT_IMPLEMENTED;
|
||||
return self::FUNC_NOT_IMPLEMENTED;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
return [
|
||||
'Driver.User.Internal.Name' => 'Internal',
|
||||
|
||||
'Driver.Db.SQLite3.Name' => 'SQLite 3',
|
||||
'Driver.Service.Curl.Name' => 'HTTP (curl)',
|
||||
'Driver.Service.Internal.Name' => 'Internal',
|
||||
'Driver.User.Internal.Name' => 'Internal',
|
||||
|
||||
'HTTP.Status.200' => 'OK',
|
||||
'HTTP.Status.204' => 'No Content',
|
||||
|
|
Loading…
Reference in a new issue