Browse Source

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
microsub
J. King 7 years ago
parent
commit
6d4aa4db6e
  1. 10
      arsse.php
  2. 3
      autoload.php
  3. 22
      lib/REST.php
  4. 68
      lib/REST/NextCloudNews/V1_2.php
  5. 42
      lib/Service.php
  6. 11
      lib/Service/Driver.php
  7. 38
      lib/Service/Internal/Driver.php
  8. 25
      lib/User/Internal/Driver.php
  9. 5
      locale/en.php

10
arsse.php

@ -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();
}

3
autoload.php

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

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();
}
}

68
lib/REST/NextCloudNews/V1_2.php

@ -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' => [],
'cleanup' => [
'before-update' => ['GET' => "cleanupBefore"],
'after-update' => ['GET' => "cleanupAfter"],
],
'version' => [
'' => ['GET' => "versionReport"],
'' => ['GET' => "serverVersion"],
],
'status' => [
'' => ['GET' => "serverStatus"],
],
'user' => [
'' => ['GET' => "userStatus"],
],
'status' => [],
'user' => [],
];
// the first path element is the overall scope of the request
$scope = $url[0];
@ -284,11 +292,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 {
@ -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

@ -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

@ -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

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

25
lib/User/Internal/Driver.php

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

5
locale/en.php

@ -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…
Cancel
Save