Add no purge, manual delete options (#12)
- Add htmx, hx attributes - Only add/update items since last check - Move messages to session to persist across redirects - Polish styles a bit (still WIP)
This commit is contained in:
@@ -30,6 +30,9 @@ class Feed {
|
||||
private const string USER_AGENT =
|
||||
'FeedReaderCentral/' . FRC_VERSION . ' +https://bitbadger.solutions/open-source/feed-reader-central';
|
||||
|
||||
/** @var int Do not purge items */
|
||||
public const int PURGE_NONE = 0;
|
||||
|
||||
/** @var int Purge all read items (will not purge unread items) */
|
||||
public const int PURGE_READ = 1;
|
||||
|
||||
@@ -312,38 +315,33 @@ class Feed {
|
||||
*
|
||||
* @param int $feedId The ID of the feed to which these items belong
|
||||
* @param Feed $feed The extracted Atom or RSS feed items
|
||||
* @param DateTimeInterface $lastChecked When this feed was last checked (only new items will be added)
|
||||
* @return array ['ok' => true] if successful, ['error' => message] if not
|
||||
*/
|
||||
public static function updateItems(int $feedId, Feed $feed, SQLite3 $db): array {
|
||||
|
||||
// Do not add items that are older than the oldest we currently have; this keeps us from re-adding items that
|
||||
// have been purged already
|
||||
$oldestQuery = $db->prepare(
|
||||
'SELECT MIN(coalesce(updated_on, published_on)) FROM item where feed_id = :feed AND is_bookmarked = 0');
|
||||
$oldestQuery->bindValue(':feed', $feedId);
|
||||
if (!($oldest = $oldestQuery->execute())) return Data::error($db);
|
||||
$minDate = date_create_immutable($oldest->fetchArray(SQLITE3_NUM)[0] ?? '1993-04-30T00:00:00+00:00');
|
||||
|
||||
foreach ($feed->items as $item) {
|
||||
if (date_create_immutable($item->updatedOn ?? $item->publishedOn) < $minDate) continue;
|
||||
$existsQuery = $db->prepare(
|
||||
'SELECT id, published_on, updated_on FROM item WHERE feed_id = :feed AND item_guid = :guid');
|
||||
$existsQuery->bindValue(':feed', $feedId);
|
||||
$existsQuery->bindValue(':guid', $item->guid);
|
||||
if ($exists = $existsQuery->execute()) {
|
||||
if ($existing = $exists->fetchArray(SQLITE3_ASSOC)) {
|
||||
if ( $existing['published_on'] != $item->publishedOn
|
||||
|| ($existing['updated_on'] ?? '') != ($item->updatedOn ?? '')) {
|
||||
if (!self::updateItem($existing['id'], $item, $db)) return Data::error($db);
|
||||
public static function updateItems(int $feedId, Feed $feed, DateTimeInterface $lastChecked, SQLite3 $db): array {
|
||||
$results =
|
||||
array_map(function ($item) use ($db, $feedId) {
|
||||
$existsQuery = $db->prepare(
|
||||
'SELECT id, published_on, updated_on FROM item WHERE feed_id = :feed AND item_guid = :guid');
|
||||
$existsQuery->bindValue(':feed', $feedId);
|
||||
$existsQuery->bindValue(':guid', $item->guid);
|
||||
if ($exists = $existsQuery->execute()) {
|
||||
if ($existing = $exists->fetchArray(SQLITE3_ASSOC)) {
|
||||
if ( $existing['published_on'] != $item->publishedOn
|
||||
|| ($existing['updated_on'] ?? '') != ($item->updatedOn ?? '')) {
|
||||
if (!self::updateItem($existing['id'], $item, $db)) return Data::error($db);
|
||||
}
|
||||
} else {
|
||||
if (!self::addItem($feedId, $item, $db)) return Data::error($db);
|
||||
}
|
||||
} else {
|
||||
if (!self::addItem($feedId, $item, $db)) return Data::error($db);
|
||||
return Data::error($db);
|
||||
}
|
||||
} else {
|
||||
return Data::error($db);
|
||||
}
|
||||
}
|
||||
return ['ok', true];
|
||||
return ['ok' => true];
|
||||
}, array_filter($feed->items,
|
||||
fn($it) => date_create_immutable($it->updatedOn ?? $it->publishedOn) >= $lastChecked));
|
||||
$errors = array_map(fn($it) => $it['error'], array_filter($results, fn($it) => array_key_exists('error', $it)));
|
||||
return sizeof($errors) > 0 ? ['error' => implode("\n", $errors)] : ['ok' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,7 +352,6 @@ class Feed {
|
||||
* @return array|string[]|true[] ['ok' => true] if purging was successful, ['error' => message] if not
|
||||
*/
|
||||
private static function purgeItems(int $feedId, SQLite3 $db): array {
|
||||
|
||||
if (!array_search(PURGE_TYPE, [self::PURGE_READ, self::PURGE_BY_DAYS, self::PURGE_BY_COUNT])) {
|
||||
return ['error' => 'Unrecognized purge type ' . PURGE_TYPE];
|
||||
}
|
||||
@@ -365,8 +362,7 @@ class Feed {
|
||||
self::PURGE_BY_DAYS => 'AND date(coalesce(updated_on, published_on)) < date(:oldest)',
|
||||
self::PURGE_BY_COUNT => 'AND id IN (SELECT id FROM item WHERE feed_id = :feed
|
||||
ORDER BY date(coalesce(updated_on, published_on)) DESC
|
||||
LIMIT -1 OFFSET :keep)',
|
||||
default => 'AND 1 = 0'
|
||||
LIMIT -1 OFFSET :keep)'
|
||||
};
|
||||
|
||||
$purge = $db->prepare("DELETE FROM item WHERE feed_id = :feed AND is_bookmarked = 0 $sql");
|
||||
@@ -391,12 +387,18 @@ class Feed {
|
||||
* @return array|string[]|true[] ['ok' => true] if successful, ['error' => message] if not
|
||||
*/
|
||||
public static function refreshFeed(int $feedId, string $url, SQLite3 $db): array {
|
||||
$feedRetrieval = self::retrieveFeed($url);
|
||||
if (array_key_exists('error', $feedRetrieval)) return $feedRetrieval;
|
||||
$feed = $feedRetrieval['ok'];
|
||||
|
||||
$feedExtract = self::retrieveFeed($url);
|
||||
if (array_key_exists('error', $feedExtract)) return $feedExtract;
|
||||
$lastCheckedQuery = $db->prepare('SELECT checked_on FROM feed where id = :id');
|
||||
$lastCheckedQuery->bindValue(':id', $feedId);
|
||||
if (!($lastCheckedResult = $lastCheckedQuery->execute())) return Data::error($db);
|
||||
if (!($lastChecked = date_create_immutable($lastCheckedResult->fetchArray(SQLITE3_NUM)[0] ?? WWW_EPOCH))) {
|
||||
return ['error' => 'Could not derive date last checked for feed'];
|
||||
}
|
||||
|
||||
$feed = $feedExtract['ok'];
|
||||
$itemUpdate = self::updateItems($feedId, $feed, $db);
|
||||
$itemUpdate = self::updateItems($feedId, $feed, $lastChecked, $db);
|
||||
if (array_key_exists('error', $itemUpdate)) return $itemUpdate;
|
||||
|
||||
$urlUpdate = $url == $feed->url ? '' : ', url = :url';
|
||||
@@ -415,7 +417,7 @@ class Feed {
|
||||
if ($urlUpdate != '') $feedUpdate->bindValue(':url', $feed->url);
|
||||
if (!$feedUpdate->execute()) return Data::error($db);
|
||||
|
||||
return self::purgeItems($feedId, $db);
|
||||
return PURGE_TYPE == self::PURGE_NONE ? ['ok' => true] : self::purgeItems($feedId, $db);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,7 +453,7 @@ class Feed {
|
||||
if (!$query->execute()) return Data::error($db);
|
||||
|
||||
$feedId = $db->lastInsertRowID();
|
||||
$result = self::updateItems($feedId, $feed, $db);
|
||||
$result = self::updateItems($feedId, $feed, date_create_immutable(WWW_EPOCH), $db);
|
||||
if (array_key_exists('error', $result)) return $result;
|
||||
|
||||
return ['ok' => $feedId];
|
||||
@@ -497,7 +499,8 @@ class Feed {
|
||||
* Refresh all feeds
|
||||
*
|
||||
* @param SQLite3 $db The database connection to use for refreshing feeds
|
||||
* @return array|true[] ['ok => true] if successful, ['error' => message] if not (may have multiple error lines)
|
||||
* @return array|true[]|string[] ['ok' => true] if successful,
|
||||
* ['error' => message] if not (may have multiple error lines)
|
||||
*/
|
||||
public static function refreshAll(SQLite3 $db): array {
|
||||
$feeds = self::retrieveAll($db, $_SESSION[Key::USER_ID]);
|
||||
|
||||
Reference in New Issue
Block a user