@ -6,6 +6,7 @@ At present the software should be considered in an "alpha" state: though its cor
- Providing more sync protocols (Google Reader, Fever, others)
- Providing more sync protocols (Google Reader, Fever, others)
- Better packaging and configuration samples
- Better packaging and configuration samples
- A user manual
## Requirements
## Requirements
@ -75,7 +76,7 @@ Please refer to `CONTRIBUTING.md` for guidelines on contributing code to The Ars
Functionally there is no reason to prefer either SQLite or PostgreSQL over the other. SQLite is significantly simpler to set up in most cases, requiring only read and write access to a containing directory in order to function; PostgreSQL may perform better than SQLite when serving hundreds of users or more, though this has not been tested.
Functionally there is no reason to prefer either SQLite or PostgreSQL over the other. SQLite is significantly simpler to set up in most cases, requiring only read and write access to a containing directory in order to function; PostgreSQL may perform better than SQLite when serving hundreds of users or more, though this has not been tested.
MySQL, on the other hand, is *not recommended* due to its relatively constrained index prefix limits which may cause some newsfeeds which would otherwise work to be rejected. If using MySQL, special care should also be taken when performing schema upgrades, as errors during the process can leave the database in a half-upgraded state which The Arsse cannot itself recover from.
MySQL, on the other hand, is **not recommended** due to its relatively constrained index prefix limits which may cause some newsfeeds which would otherwise work to be rejected. If using MySQL, special care should also be taken when performing schema upgrades, as errors during the process can leave the database in a half-upgraded state which The Arsse cannot itself recover from.
Note that MariaDB is not compatible with The Arsse: its support for common table expressions is, as of this writing, not sufficient for our needs.
Note that MariaDB is not compatible with The Arsse: its support for common table expressions is, as of this writing, not sufficient for our needs.
/** @var boolean Whether to attempt to automatically update the database when upgrading to a new version with schema changes */
/** @var boolean Whether to attempt to automatically update the database when upgrading to a new version with schema changes */
public $dbAutoUpdate = true;
public $dbAutoUpdate = true;
/** @var \DateInterval Number of seconds to wait before returning a timeout error when connecting to a database (zero waits forever; not applicable to SQLite) */
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when connecting to a database (null waits forever; not applicable to SQLite) */
public $dbTimeoutConnect = 5.0;
public $dbTimeoutConnect = 5.0;
/** @var \DateInterval Number of seconds to wait before returning a timeout error when executing a database operation (zero waits forever; not applicable to SQLite) */
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when executing a database operation (null waits forever; not applicable to SQLite) */
public $dbTimeoutExec = 0.0;
public $dbTimeoutExec = null;
/** @var \DateInterval|null Number of seconds to wait before returning a timeout error when acquiring a database lock (null waits forever) */
public $dbTimeoutLock = 60.0;
/** @var string|null Full path and file name of SQLite database (if using SQLite) */
/** @var string|null Full path and file name of SQLite database (if using SQLite) */
public $dbSQLite3File = null;
public $dbSQLite3File = null;
/** @var string Encryption key to use for SQLite database (if using a version of SQLite with SEE) */
/** @var string Encryption key to use for SQLite database (if using a version of SQLite with SEE) */
public $dbSQLite3Key = "";
public $dbSQLite3Key = "";
/** @var \DateInterval Number of seconds for SQLite to wait before returning a timeout error when trying to acquire a write lock on the database (zero does not wait) */
public $dbSQLite3Timeout = 60.0;
/** @var string Host name, address, or socket path of PostgreSQL database server (if using PostgreSQL) */
/** @var string Host name, address, or socket path of PostgreSQL database server (if using PostgreSQL) */
public $dbPostgreSQLHost = "";
public $dbPostgreSQLHost = "";
/** @var string Log-in user name for PostgreSQL database server (if using PostgreSQL) */
/** @var string Log-in user name for PostgreSQL database server (if using PostgreSQL) */
/** @var string Feed update service driver to use, one of "serial", "subprocess", or "curl". A fully-qualified class name may also be used for custom drivers */
/** @var string Feed update service driver to use, one of "serial" or "subprocess". A fully-qualified class name may also be used for custom drivers */
public $serviceDriver = "subprocess";
public $serviceDriver = "subprocess";
/** @var \DateInterval The interval between checks for new articles, as an ISO 8601 duration
/** @var \DateInterval The interval between checks for new articles, as an ISO 8601 duration
/** @var integer Number of concurrent feed updates to perform */
/** @var integer Number of concurrent feed updates to perform */
public $serviceQueueWidth = 5;
public $serviceQueueWidth = 5;
/** @var string The base server address (with scheme, host, port if necessary, and terminal slash) to connect to the server when performing feed updates using cURL */
public $serviceCurlBase = "http://localhost/";
/** @var string The user name to use when performing feed updates using cURL */
public $serviceCurlUser = "";
/** @var string The password to use when performing feed updates using cURL */
public $serviceCurlPassword = "";
/** @var \DateInterval Number of seconds to wait for data when fetching feeds from foreign servers */
/** @var \DateInterval Number of seconds to wait for data when fetching feeds from foreign servers */
public $fetchTimeout = 10;
public $fetchTimeout = 10.0;
/** @var integer Maximum size, in bytes, of data when fetching feeds from foreign servers */
/** @var integer Maximum size, in bytes, of data when fetching feeds from foreign servers */
public $fetchSizeLimit = 2 * 1024 * 1024;
public $fetchSizeLimit = 2 * 1024 * 1024;
/** @var boolean Whether to allow the possibility of fetching full article contents using an item's URL. Whether fetching will actually happen is also governed by a per-feed setting */
/** @var boolean Whether to allow the possibility of fetching full article contents using an item's URL. Whether fetching will actually happen is also governed by a per-feed setting */
@ -115,6 +109,11 @@ class Conf {
/** @var string Space-separated list of origins from which to deny cross-origin resource sharing */
/** @var string Space-separated list of origins from which to deny cross-origin resource sharing */
public $httpOriginsDenied = "";
public $httpOriginsDenied = "";
### OBSOLETE SETTINGS
/** @var \DateInterval|null (OBSOLETE) Number of seconds for SQLite to wait before returning a timeout error when trying to acquire a write lock on the database (zero does not wait) */
public $dbSQLite3Timeout = null; // previously 60.0
const TYPE_NAMES = [
const TYPE_NAMES = [
Value::T_BOOL => "boolean",
Value::T_BOOL => "boolean",
Value::T_STRING => "string",
Value::T_STRING => "string",
@ -122,6 +121,12 @@ class Conf {
VALUE::T_INT => "integer",
VALUE::T_INT => "integer",
Value::T_INTERVAL => "interval",
Value::T_INTERVAL => "interval",
];
];
const EXPECTED_TYPES = [
'dbTimeoutExec' => "double",
'dbTimeoutLock' => "double",
'dbTimeoutConnect' => "double",
'dbSQLite3Timeout' => "double",
];
protected static $types = [];
protected static $types = [];
@ -184,17 +189,23 @@ class Conf {
/** Outputs configuration settings, either non-default ones or all, as an associative array
/** Outputs configuration settings, either non-default ones or all, as an associative array
* @param bool $full Whether to output all configuration options rather than only changed ones */
* @param bool $full Whether to output all configuration options rather than only changed ones */
public function export(bool $full = false): array {
public function export(bool $full = false): array {
$ref = new self;
$out = [];
$conf = new \ReflectionObject($this);
$conf = new \ReflectionObject($this);
$ref = (new \ReflectionClass($this))->getDefaultProperties();
$out = [];
foreach ($conf->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
foreach ($conf->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
$name = $prop->name;
$name = $prop->name;
// add the property to the output if the value is of a supported type and either:
$value = $prop->getValue($this);
// 1. full output has been requested
if ($prop->isDefault()) {
// 2. the property is not defined in the class
$default = $ref[$name];
// 3. it differs from the default
// if the property is a known property (rather than one added by a hypothetical plug-in)
'Exception.JKingWeb/Arsse/Conf/Exception.semanticMismatch' => 'Configuration parameter "{param}" in file "{file}" is not a valid value. Consult the documentation for possible values',
'Exception.JKingWeb/Arsse/Conf/Exception.semanticMismatch' => 'Configuration parameter "{param}" in file "{file}" is not a valid value. Consult the documentation for possible values',
// indicates programming error
'Exception.JKingWeb/Arsse/Conf/Exception.ambiguousDefault' => 'Preferred type of configuration parameter "{param}" could not be inferred from its default value. The parameter must be added to the Conf::EXPECTED_TYPES array',
'Exception.JKingWeb/Arsse/Db/Exception.extMissing' => 'Required PHP extension for driver "{0}" not installed',
'Exception.JKingWeb/Arsse/Db/Exception.extMissing' => 'Required PHP extension for driver "{0}" not installed',
'Exception.JKingWeb/Arsse/Db/Exception.fileMissing' => 'Database file "{0}" does not exist',
'Exception.JKingWeb/Arsse/Db/Exception.fileMissing' => 'Database file "{0}" does not exist',
'Exception.JKingWeb/Arsse/Db/Exception.fileUnreadable' => 'Insufficient permissions to open database file "{0}" for reading',
'Exception.JKingWeb/Arsse/Db/Exception.fileUnreadable' => 'Insufficient permissions to open database file "{0}" for reading',
-- SQLite has limited ALTER TABLE support, so the table must be re-created
-- SQLite has limited ALTER TABLE support, so the table must be re-created
-- and its data re-entered; other database systems have a much simpler prodecure
-- and its data re-entered; other database systems have a much simpler prodecure
altertablearsse_marksrenametoarsse_marks_old;
createtablearsse_marks_new(
createtablearsse_marks(
-- users' actions on newsfeed entries
-- users' actions on newsfeed entries
articleintegernotnullreferencesarsse_articles(id)ondeletecascade,-- article associated with the marks
articleintegernotnullreferencesarsse_articles(id)ondeletecascade,-- article associated with the marks
subscriptionintegernotnullreferencesarsse_subscriptions(id)ondeletecascadeonupdatecascade,-- subscription associated with the marks; the subscription in turn belongs to a user
subscriptionintegernotnullreferencesarsse_subscriptions(id)ondeletecascadeonupdatecascade,-- subscription associated with the marks; the subscription in turn belongs to a user
@ -43,8 +42,9 @@ create table arsse_marks(
notetextnotnulldefault'',-- Tiny Tiny RSS freeform user note
notetextnotnulldefault'',-- Tiny Tiny RSS freeform user note
primarykey(article,subscription)-- no more than one mark-set per article per user
primarykey(article,subscription)-- no more than one mark-set per article per user
folderintegerreferencesarsse_folders(id)ondeletecascade,-- TT-RSS category (nestable); the first-level category (which acts as NextCloud folder) is joined in when needed
folderintegerreferencesarsse_folders(id)ondeletecascade,-- TT-RSS category (nestable); the first-level category (which acts as NextCloud folder) is joined in when needed
unique(owner,feed)-- a given feed should only appear once for a given owner
unique(owner,feed)-- a given feed should only appear once for a given owner
feedintegernotnullreferencesarsse_feeds(id)ondeletecascade,-- feed for the subscription
feedintegernotnullreferencesarsse_feeds(id)ondeletecascade,-- feed for the subscription
@ -93,22 +92,22 @@ create table arsse_articles(
url_content_hashtextnotnull,-- hash of URL + content, enclosure URL, & content type; used when checking for updates and for identification if there is no guid.
url_content_hashtextnotnull,-- hash of URL + content, enclosure URL, & content type; used when checking for updates and for identification if there is no guid.
title_content_hashtextnotnull-- hash of title + content, enclosure URL, & content type; used when checking for updates and for identification if there is no guid.
title_content_hashtextnotnull-- hash of title + content, enclosure URL, & content type; used when checking for updates and for identification if there is no guid.
-- allow marks to initially have a null date due to changes in how marks are first created
-- allow marks to initially have a null date due to changes in how marks are first created
-- and also add a "touched" column to aid in tracking changes during the course of some transactions
-- and also add a "touched" column to aid in tracking changes during the course of some transactions
altertablearsse_marksrenametoarsse_marks_old;
createtablearsse_marks_new(
createtablearsse_marks(
-- users' actions on newsfeed entries
-- users' actions on newsfeed entries
articleintegernotnullreferencesarsse_articles(id)ondeletecascade,-- article associated with the marks
articleintegernotnullreferencesarsse_articles(id)ondeletecascade,-- article associated with the marks
subscriptionintegernotnullreferencesarsse_subscriptions(id)ondeletecascadeonupdatecascade,-- subscription associated with the marks; the subscription in turn belongs to a user
subscriptionintegernotnullreferencesarsse_subscriptions(id)ondeletecascadeonupdatecascade,-- subscription associated with the marks; the subscription in turn belongs to a user
@ -16,8 +15,9 @@ create table arsse_marks(
touchedbooleannotnulldefault0,-- used to indicate a record has been modified during the course of some transactions
touchedbooleannotnulldefault0,-- used to indicate a record has been modified during the course of some transactions
primarykey(article,subscription)-- no more than one mark-set per article per user
primarykey(article,subscription)-- no more than one mark-set per article per user