diff --git a/.gitignore b/.gitignore index 4c2a7fad..4ea01fca 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ vendor/JKingWeb/DrUUID/* #temp files cache/* test.php -/db +newssync.db* # Windows image file caches Thumbs.db diff --git a/sql/SQLite3/0.feeds.sql b/sql/SQLite3/0.feeds.sql deleted file mode 100644 index 82a92c5d..00000000 --- a/sql/SQLite3/0.feeds.sql +++ /dev/null @@ -1,49 +0,0 @@ --- newsfeeds, deduplicated -create table feeds.newssync_feeds( - id integer primary key not null, -- sequence number - url TEXT not null, -- URL of feed - title TEXT, -- default title of feed - favicon TEXT, -- URL of favicon - source TEXT, -- URL of site to which the feed belongs - updated datetime, -- time at which the feed was last fetched - modified datetime not null default CURRENT_TIMESTAMP, -- - err_count integer not null default 0, -- count of successive times update resulted in error since last successful update - err_msg TEXT, -- last error message - username TEXT, -- HTTP authentication username - password TEXT, -- HTTP authentication password (this is stored in plain text) - unique(url,username,password) -- a URL with particular credentials should only appear once -); - --- entries in newsfeeds -create table feeds.newssync_articles( - id integer primary key not null, -- sequence number - feed integer not null references newssync_feeds(id) on delete cascade, -- feed for the subscription - url TEXT not null, -- URL of article - title TEXT, -- article title - author TEXT, -- author's name - published datetime, -- time of original publication - edited datetime, -- time of last edit - guid TEXT, -- GUID - content TEXT, -- content, as (X)HTML - modified datetime not null default CURRENT_TIMESTAMP, -- date when article properties were last modified - hash varchar(64) not null, -- ownCloud hash - fingerprint varchar(64) not null, -- ownCloud fingerprint - enclosures_hash varchar(64), -- hash of enclosures, if any; since enclosures are not uniquely identified, we need to know when they change - tags_hash varchar(64) -- hash of RSS/Atom categories included in article; since these categories are not uniquely identified, we need to know when they change -); - --- enclosures associated with articles -create table feeds.newssync_enclosures( - article integer not null references newssync_articles(id) on delete cascade, - url TEXT, - type varchar(255) -); - --- author labels ("categories" in RSS/Atom parlance) associated with newsfeed entries -create table feeds.newssync_tags( - article integer not null references newssync_articles(id) on delete cascade, - name TEXT -); - --- set version marker -pragma feeds.user_version = 1; diff --git a/sql/SQLite3/0.main.sql b/sql/SQLite3/0.main.sql deleted file mode 100644 index e88f09ee..00000000 --- a/sql/SQLite3/0.main.sql +++ /dev/null @@ -1,64 +0,0 @@ --- settings -create table main.newssync_settings( - key varchar(255) primary key not null, -- - value varchar(255), -- - type varchar(255) not null check( - type in('int','numeric','text','timestamp','date','time','bool','null','json') - ) -- -); - --- users -create table main.newssync_users( - id TEXT primary key not null, -- user id - password TEXT, -- password, salted and hashed; if using external authentication this would be blank - name TEXT, -- display name - avatar_type TEXT, -- avatar image's MIME content type - avatar_data BLOB, -- avatar image's binary data - admin boolean not null default 0 -- whether the user is an administrator -); - --- TT-RSS categories and ownCloud folders -create table main.newssync_categories( - id integer primary key not null, -- sequence number - owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of category - parent integer, -- parent category id - folder integer not null, -- first-level category (ownCloud folder) - name TEXT not null, -- category name - modified datetime not null default CURRENT_TIMESTAMP, -- - unique(owner,name,parent) -- cannot have multiple categories with the same name under the same parent for the same owner -); - --- users' subscriptions to newsfeeds, with settings -create table main.newssync_subscriptions( - id integer primary key not null, -- sequence number - owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of subscription - feed integer not null references newssync_feeds(id) on delete cascade, -- feed for the subscription - added datetime not null default CURRENT_TIMESTAMP, -- time at which feed was added - modified datetime not null default CURRENT_TIMESTAMP, -- date at which subscription properties were last modified - title TEXT, -- user-supplied title - order_type int not null default 0, -- ownCloud sort order - pinned boolean not null default 0, -- whether feed is pinned (always sorts at top) - category integer not null references newssync_categories(id) on delete set null, -- TT-RSS category (nestable); the first-level category (which acts as ownCloud folder) is joined in when needed - unique(owner,feed) -- a given feed should only appear once for a given owner -); - --- users' actions on newsfeed entries -create table main.newssync_subscription_articles( - id integer primary key not null, - article integer not null references newssync_articles(id) on delete cascade, - read boolean not null default 0, - starred boolean not null default 0, - modified datetime not null default CURRENT_TIMESTAMP -); - --- user labels associated with newsfeed entries -create table main.newssync_labels( - sub_article integer not null references newssync_subscription_articles(id) on delete cascade, -- - owner TEXT not null references newssync_users(id) on delete cascade on update cascade, - name TEXT -); -create index main.newssync_label_names on newssync_labels(name); - --- set version marker -pragma main.user_version = 1; -insert into main.newssync_settings values('schema_version',1,'int'); \ No newline at end of file diff --git a/sql/SQLite3/0.sql b/sql/SQLite3/0.sql new file mode 100644 index 00000000..7765ee13 --- /dev/null +++ b/sql/SQLite3/0.sql @@ -0,0 +1,111 @@ +-- newsfeeds, deduplicated +create table newssync_feeds( + id integer primary key not null, -- sequence number + url TEXT not null, -- URL of feed + title TEXT, -- default title of feed + favicon TEXT, -- URL of favicon + source TEXT, -- URL of site to which the feed belongs + updated datetime, -- time at which the feed was last fetched + modified datetime not null default CURRENT_TIMESTAMP, -- + err_count integer not null default 0, -- count of successive times update resulted in error since last successful update + err_msg TEXT, -- last error message + username TEXT, -- HTTP authentication username + password TEXT, -- HTTP authentication password (this is stored in plain text) + unique(url,username,password) -- a URL with particular credentials should only appear once +); + +-- entries in newsfeeds +create table newssync_articles( + id integer primary key not null, -- sequence number + feed integer not null references newssync_feeds(id) on delete cascade, -- feed for the subscription + url TEXT not null, -- URL of article + title TEXT, -- article title + author TEXT, -- author's name + published datetime, -- time of original publication + edited datetime, -- time of last edit + guid TEXT, -- GUID + content TEXT, -- content, as (X)HTML + modified datetime not null default CURRENT_TIMESTAMP, -- date when article properties were last modified + hash varchar(64) not null, -- ownCloud hash + fingerprint varchar(64) not null, -- ownCloud fingerprint + enclosures_hash varchar(64), -- hash of enclosures, if any; since enclosures are not uniquely identified, we need to know when they change + tags_hash varchar(64) -- hash of RSS/Atom categories included in article; since these categories are not uniquely identified, we need to know when they change +); + +-- enclosures associated with articles +create table newssync_enclosures( + article integer not null references newssync_articles(id) on delete cascade, + url TEXT, + type varchar(255) +); + +-- author labels ("categories" in RSS/Atom parlance) associated with newsfeed entries +create table newssync_tags( + article integer not null references newssync_articles(id) on delete cascade, + name TEXT +); + +-- settings +create table newssync_settings( + key varchar(255) primary key not null, -- + value varchar(255), -- + type varchar(255) not null check( + type in('int','numeric','text','timestamp','date','time','bool','null','json') + ) default 'text' -- +); + +-- users +create table newssync_users( + id TEXT primary key not null, -- user id + password TEXT, -- password, salted and hashed; if using external authentication this would be blank + name TEXT, -- display name + avatar_type TEXT, -- avatar image's MIME content type + avatar_data BLOB, -- avatar image's binary data + admin boolean not null default 0 -- whether the user is an administrator +); + +-- TT-RSS categories and ownCloud folders +create table newssync_categories( + id integer primary key not null, -- sequence number + owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of category + parent integer, -- parent category id + folder integer not null, -- first-level category (ownCloud folder) + name TEXT not null, -- category name + modified datetime not null default CURRENT_TIMESTAMP, -- + unique(owner,name,parent) -- cannot have multiple categories with the same name under the same parent for the same owner +); + +-- users' subscriptions to newsfeeds, with settings +create table newssync_subscriptions( + id integer primary key not null, -- sequence number + owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of subscription + feed integer not null references newssync_feeds(id) on delete cascade, -- feed for the subscription + added datetime not null default CURRENT_TIMESTAMP, -- time at which feed was added + modified datetime not null default CURRENT_TIMESTAMP, -- date at which subscription properties were last modified + title TEXT, -- user-supplied title + order_type int not null default 0, -- ownCloud sort order + pinned boolean not null default 0, -- whether feed is pinned (always sorts at top) + category integer references newssync_categories(id) on delete set null, -- TT-RSS category (nestable); the first-level category (which acts as ownCloud folder) is joined in when needed + unique(owner,feed) -- a given feed should only appear once for a given owner +); + +-- users' actions on newsfeed entries +create table newssync_subscription_articles( + id integer primary key not null, + article integer not null references newssync_articles(id) on delete cascade, + read boolean not null default 0, + starred boolean not null default 0, + modified datetime not null default CURRENT_TIMESTAMP +); + +-- user labels associated with newsfeed entries +create table newssync_labels( + sub_article integer not null references newssync_subscription_articles(id) on delete cascade, -- + owner TEXT not null references newssync_users(id) on delete cascade on update cascade, + name TEXT +); +create index newssync_label_names on newssync_labels(name); + +-- set version marker +pragma user_version = 1; +insert into newssync_settings values('schema_version',1,'int'); \ No newline at end of file diff --git a/vendor/JKingWeb/NewsSync/Conf.php b/vendor/JKingWeb/NewsSync/Conf.php index c9d90b68..54bcb92b 100644 --- a/vendor/JKingWeb/NewsSync/Conf.php +++ b/vendor/JKingWeb/NewsSync/Conf.php @@ -6,7 +6,7 @@ class Conf { public $lang = "en"; public $dbClass = NS_BASE."Db\\DriverSQLite3"; - public $dbSQLite3Path = BASE."db"; + public $dbSQLite3File = BASE."newssync.db"; public $dbSQLite3Key = ""; public $dbSQLite3AutoUpd = true; public $dbPostgreSQLHost = "localhost"; diff --git a/vendor/JKingWeb/NewsSync/Database.php b/vendor/JKingWeb/NewsSync/Database.php index 4fe7b72f..9da2934b 100644 --- a/vendor/JKingWeb/NewsSync/Database.php +++ b/vendor/JKingWeb/NewsSync/Database.php @@ -45,7 +45,12 @@ class Database { return $this->db->schemaVersion(); } - public function getSetting(string $key) { + public function dbUpdate(): bool { + if($this->db->schemaVersion() < self::SCHEMA_VERSION) return $this->db->update(self::SCHEMA_VERSION); + return false; + } + + public function settingGet(string $key) { $row = $this->db->prepare("SELECT value, type from newssync_settings where key = ?", "str")->run($key)->get(); if(!$row) return null; switch($row['type']) { @@ -62,7 +67,7 @@ class Database { } } - public function setSetting(string $key, $in, string $type = null): bool { + public function settingSet(string $key, $in, string $type = null): bool { if(!$type) { switch(gettype($in)) { case "boolean": $type = "bool"; break; @@ -156,7 +161,7 @@ class Database { $this->db->prepare("REPLACE INTO newssync_settings(key,value,type) values(?,?,?)", "str", (($type=="null") ? "null" : "str"), "str")->run($key, $value, "text"); } - public function clearSetting(string $key): bool { + public function settingClear(string $key): bool { $this->db->prepare("DELETE from newssync_settings where key = ?", "str")->run($key); return true; } @@ -165,13 +170,14 @@ class Database { $this->db->prepare("INSERT INTO newssync_users(id,password,admin) values(?,?,?)", "str", "str", "bool")->run($username,$password,$admin); return $username; } - + public function subscriptionAdd(string $user, string $url, string $fetchUser = null, string $fetchPassword = null): int { $this->db->begin(); - $qFeed = $this->db->prepare("SELECT id from newssync_feeds where url = ? and username = ? and password = ?", "str", "str", "str"); + $qFeed = $this->db->prepare("SELECT id from newssync_feeds where url is ? and username is ? and password is ?", "str", "str", "str"); if(is_null($id = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle())) { $this->db->prepare("INSERT INTO newssync_feeds(url,username,password) values(?,?,?)", "str", "str", "str")->run($url, $fetchUser, $fetchPassword); $id = $qFeed->run($url, $fetchUser, $fetchPassword)->getSingle(); + var_export($id); } $this->db->prepare("INSERT INTO newssync_subscriptions(owner,feed) values(?,?)", "str", "int")->run($user,$id); $this->db->commit(); diff --git a/vendor/JKingWeb/NewsSync/Db/Common.php b/vendor/JKingWeb/NewsSync/Db/Common.php index 394e0f95..de92561c 100644 --- a/vendor/JKingWeb/NewsSync/Db/Common.php +++ b/vendor/JKingWeb/NewsSync/Db/Common.php @@ -43,14 +43,14 @@ Trait Common { if($this->schemaVersion() < 1) return true; if($this->isLocked()) return false; $uuid = UUID::mintStr(); - if(!$this->data->db->setSetting("lock", $uuid)) return false; + if(!$this->data->db->settingSet("lock", $uuid)) return false; sleep(1); - if($this->data->db->getSetting("lock") != $uuid) return false; + if($this->data->db->settingGet("lock") != $uuid) return false; return true; } public function unlock(): bool { - return $this->data->db->clearSetting("lock"); + return $this->data->db->settingClear("lock"); } public function isLocked(): bool { diff --git a/vendor/JKingWeb/NewsSync/Db/CommonSQLite3.php b/vendor/JKingWeb/NewsSync/Db/CommonSQLite3.php index d6de3ed5..7ebc4b6d 100644 --- a/vendor/JKingWeb/NewsSync/Db/CommonSQLite3.php +++ b/vendor/JKingWeb/NewsSync/Db/CommonSQLite3.php @@ -8,8 +8,8 @@ Trait CommonSQLite3 { return "SQLite 3"; } - public function schemaVersion(string $schema = "main"): int { - return $this->query("PRAGMA $schema.user_version")->getSingle(); + public function schemaVersion(): int { + return $this->query("PRAGMA user_version")->getSingle(); } public function update(int $to): bool { @@ -18,27 +18,24 @@ Trait CommonSQLite3 { if($ver >= $to) throw new Update\Exception("tooNew", ['difference' => ($ver - $to), 'current' => $ver, 'target' => $to, 'driver_name' => $this->driverName()]); $sep = \DIRECTORY_SEPARATOR; $path = \JKingWeb\NewsSync\BASE."sql".$sep."SQLite3".$sep; - $schemas = ["feeds", "main"]; $this->lock(); $this->begin(); for($a = $ver; $a < $to; $a++) { $this->begin(); - foreach($schemas as $schema) { - try { - $file = $path.$a.".".$schema.".sql"; - if(!file_exists($file)) throw new Update\Exception("missing", ['file' => $file, 'driver_name' => $this->driverName()]); - if(!is_readable($file)) throw new Update\Exception("unreadable", ['file' => $file, 'driver_name' => $this->driverName()]); - $sql = @file_get_contents($file); - if($sql===false) throw new Update\Exception("unusable", ['file' => $file, 'driver_name' => $this->driverName()]); - $this->exec($sql); - } catch(\Throwable $e) { - // undo any partial changes from the failed update - $this->rollback(); - // commit any successful updates if updating by more than one version - $this->commit(true); - // throw the error received - throw $e; - } + try { + $file = $path.$a.".sql"; + if(!file_exists($file)) throw new Update\Exception("missing", ['file' => $file, 'driver_name' => $this->driverName()]); + if(!is_readable($file)) throw new Update\Exception("unreadable", ['file' => $file, 'driver_name' => $this->driverName()]); + $sql = @file_get_contents($file); + if($sql===false) throw new Update\Exception("unusable", ['file' => $file, 'driver_name' => $this->driverName()]); + $this->exec($sql); + } catch(\Throwable $e) { + // undo any partial changes from the failed update + $this->rollback(); + // commit any successful updates if updating by more than one version + $this->commit(true); + // throw the error received + throw $e; } $this->commit(); } diff --git a/vendor/JKingWeb/NewsSync/Db/DriverSQLite3.php b/vendor/JKingWeb/NewsSync/Db/DriverSQLite3.php index 8d95607a..3bd2333c 100644 --- a/vendor/JKingWeb/NewsSync/Db/DriverSQLite3.php +++ b/vendor/JKingWeb/NewsSync/Db/DriverSQLite3.php @@ -10,32 +10,22 @@ class DriverSQLite3 implements Driver { private function __construct(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false) { $this->data = $data; - // normalize the path - $path = $data->conf->dbSQLite3Path; - $sep = \DIRECTORY_SEPARATOR; - if(substr($path,-(strlen($sep))) != $sep) $path .= $sep; - $mainfile = $path."newssync-main.db"; - $feedfile = $path."newssync-feeds.db"; - // if the files exists (or we're initializing the database), try to open it and set initial options + $file = $data->conf->dbSQLite3File; + // if the file exists (or we're initializing the database), try to open it and set initial options try { - $this->db = new \SQLite3($mainfile, ($install) ? \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE : \SQLITE3_OPEN_READWRITE, $data->conf->dbSQLite3Key); + $this->db = new \SQLite3($file, ($install) ? \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE : \SQLITE3_OPEN_READWRITE, $data->conf->dbSQLite3Key); $this->db->enableExceptions(true); - $attach = "'".$this->db->escapeString($feedfile)."'"; - $this->exec("ATTACH DATABASE $attach AS feeds"); - $this->exec("PRAGMA main.journal_mode = wal"); - $this->exec("PRAGMA feeds.journal_mode = wal"); + $this->exec("PRAGMA journal_mode = wal"); $this->exec("PRAGMA foreign_keys = yes"); } catch(\Throwable $e) { // if opening the database doesn't work, check various pre-conditions to find out what the problem might be - foreach([$mainfile, $feedfile] as $file) { - if(!file_exists($file)) { - if($install && !is_writable(dirname($file))) throw new Exception("fileUncreatable", dirname($file)); - throw new Exception("fileMissing", $file); - } - if(!is_readable($file) && !is_writable($file)) throw new Exception("fileUnusable", $file); - if(!is_readable($file)) throw new Exception("fileUnreadable", $file); - if(!is_writable($file)) throw new Exception("fileUnwritable", $file); + if(!file_exists($file)) { + if($install && !is_writable(dirname($file))) throw new Exception("fileUncreatable", dirname($file)); + throw new Exception("fileMissing", $file); } + if(!is_readable($file) && !is_writable($file)) throw new Exception("fileUnusable", $file); + if(!is_readable($file)) throw new Exception("fileUnreadable", $file); + if(!is_writable($file)) throw new Exception("fileUnwritable", $file); // otherwise the database is probably corrupt throw new Exception("fileCorrupt", $mainfile); } diff --git a/vendor/JKingWeb/NewsSync/Db/StatementSQLite3.php b/vendor/JKingWeb/NewsSync/Db/StatementSQLite3.php index 32e1e18d..5c15ada2 100644 --- a/vendor/JKingWeb/NewsSync/Db/StatementSQLite3.php +++ b/vendor/JKingWeb/NewsSync/Db/StatementSQLite3.php @@ -6,7 +6,7 @@ class StatementSQLite3 implements Statement { protected $st; protected $types; - public function __construct(\SQLite3Stmt $st, array $bindings = null) { + public function __construct($st, array $bindings = null) { $this->st = $st; $this->types = []; foreach($bindings as $binding) {