1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 21:22:40 +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:
J. King 2017-07-15 16:44:06 -04:00
parent d4674c61b2
commit 6d4aa4db6e
9 changed files with 194 additions and 32 deletions

10
arsse.php Normal file
View 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();
}

View file

@ -1,3 +0,0 @@
<?php
require_once __DIR__.DIRECTORY_SEPARATOR."bootstrap.php";
Data::load(new Conf());

View file

@ -23,6 +23,8 @@ class REST {
// Tiny Tiny RSS https://tt-rss.org/gitlab/fox/tt-rss/wikis/ApiReference // Tiny Tiny RSS https://tt-rss.org/gitlab/fox/tt-rss/wikis/ApiReference
// Fever https://feedafever.com/api // Fever https://feedafever.com/api
// NewsBlur http://www.newsblur.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() { function __construct() {
@ -36,8 +38,22 @@ class REST {
$class = $this->apis[$api]['class']; $class = $this->apis[$api]['class'];
$drv = new $class(); $drv = new $class();
$out = $drv->dispatch($req); $out = $drv->dispatch($req);
echo "Status: ".$out->code."\n"; header("Status: ".$out->code." ".Data::$lang->msg("HTTP.Status.".$out->code));
echo json_encode($out->payload,\JSON_PRETTY_PRINT); 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; return true;
} }
@ -49,6 +65,6 @@ class REST {
if(strpos($url, $api['match'])===0) return $id; if(strpos($url, $api['match'])===0) return $id;
} }
// or throw an exception otherwise // or throw an exception otherwise
throw new REST\ExceptionURL("apiNotSupported", $url); throw new REST\Exception501();
} }
} }

View file

@ -13,6 +13,7 @@ use JKingWeb\Arsse\REST\Exception405;
class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler { class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
const REALM = "NextCloud News API v1-2"; const REALM = "NextCloud News API v1-2";
const VERSION = "11.0.5";
protected $dateFormat = "unix"; protected $dateFormat = "unix";
@ -110,12 +111,19 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
'star/multiple' => ['PUT' => "articleMarkStarredMulti"], 'star/multiple' => ['PUT' => "articleMarkStarredMulti"],
'unstar/multiple' => ['PUT' => "articleMarkStarredMulti"], 'unstar/multiple' => ['PUT' => "articleMarkStarredMulti"],
], ],
'cleanup' => [], 'cleanup' => [
'version' => [ 'before-update' => ['GET' => "cleanupBefore"],
'' => ['GET' => "versionReport"], '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 // the first path element is the overall scope of the request
$scope = $url[0]; $scope = $url[0];
@ -285,11 +293,6 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
return new Response(204); 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 // return list of feeds which should be refreshed
protected function feedListStale(array $url, array $data): Response { protected function feedListStale(array $url, array $data): Response {
// function requires admin rights per spec // function requires admin rights per spec
@ -461,7 +464,7 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
$c->reverse(true); $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 // 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) { if($c->reverse) {
$c->latestEdition($data['offset'] - 1); $c->latestEdition($data['offset'] - 1);
} else { } else {
@ -590,4 +593,49 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
$t->commit(); $t->commit();
return new Response(204); 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
View 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
View 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;
}

View 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;
}
}

View file

@ -1,23 +1,22 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\User\Internal; 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; use InternalFunctions;
protected $db; protected $db;
protected $functions = [ protected $functions = [
"auth" => Iface::FUNC_INTERNAL, "auth" => self::FUNC_INTERNAL,
"userList" => Iface::FUNC_INTERNAL, "userList" => self::FUNC_INTERNAL,
"userExists" => Iface::FUNC_INTERNAL, "userExists" => self::FUNC_INTERNAL,
"userAdd" => Iface::FUNC_INTERNAL, "userAdd" => self::FUNC_INTERNAL,
"userRemove" => Iface::FUNC_INTERNAL, "userRemove" => self::FUNC_INTERNAL,
"userPasswordSet" => Iface::FUNC_INTERNAL, "userPasswordSet" => self::FUNC_INTERNAL,
"userPropertiesGet" => Iface::FUNC_INTERNAL, "userPropertiesGet" => self::FUNC_INTERNAL,
"userPropertiesSet" => Iface::FUNC_INTERNAL, "userPropertiesSet" => self::FUNC_INTERNAL,
"userRightsGet" => Iface::FUNC_INTERNAL, "userRightsGet" => self::FUNC_INTERNAL,
"userRightsSet" => Iface::FUNC_INTERNAL, "userRightsSet" => self::FUNC_INTERNAL,
]; ];
static public function driverName(): string { static public function driverName(): string {
@ -29,7 +28,7 @@ final class Driver implements Iface {
if(array_key_exists($function, $this->functions)) { if(array_key_exists($function, $this->functions)) {
return $this->functions[$function]; return $this->functions[$function];
} else { } else {
return Iface::FUNC_NOT_IMPLEMENTED; return self::FUNC_NOT_IMPLEMENTED;
} }
} }

View file

@ -1,8 +1,9 @@
<?php <?php
return [ return [
'Driver.User.Internal.Name' => 'Internal',
'Driver.Db.SQLite3.Name' => 'SQLite 3', '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.200' => 'OK',
'HTTP.Status.204' => 'No Content', 'HTTP.Status.204' => 'No Content',