feat: check every subscription in the background and add the posts page
This commit is contained in:
parent
953ae798eb
commit
1b426ccd8b
15 changed files with 688 additions and 58 deletions
|
|
@ -277,6 +277,9 @@ class BrowserController {
|
|||
currentPage.value = page;
|
||||
error.value = null;
|
||||
visitedUrls.value = {...visitedUrls.value, url};
|
||||
// Opening a post is reading it. Nothing happens when the address is
|
||||
// not one, so this needs no knowledge of the reader's subscriptions.
|
||||
await _db.markEntryRead(url);
|
||||
if (!notModified) {
|
||||
await _db.saveCachedPage(CachedPagesCompanion.insert(
|
||||
url: url,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,34 @@
|
|||
import 'package:actors/actors.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:signals/signals.dart';
|
||||
|
||||
import '../../database/database.dart';
|
||||
import '../../mews/document.dart';
|
||||
import '../actors/fetch_actor.dart';
|
||||
|
||||
/// Which sites the reader subscribes to (spec 6). The posts themselves live in
|
||||
/// the database, so an unread post survives a restart; only the set of
|
||||
/// addresses is held here, because that is what the chrome watches.
|
||||
class SubscriptionsController {
|
||||
SubscriptionsController(this._db);
|
||||
SubscriptionsController({
|
||||
required MewsDatabase db,
|
||||
required Actor<FetchCommand, FetchResult> fetch,
|
||||
}) : _db = db,
|
||||
_fetch = fetch;
|
||||
|
||||
final MewsDatabase _db;
|
||||
final Actor<FetchCommand, FetchResult> _fetch;
|
||||
|
||||
final subscribedUrls = signal(<String>{});
|
||||
|
||||
/// Whether a check is running, so a page can say so instead of looking idle.
|
||||
final refreshing = signal(false);
|
||||
|
||||
/// Called after a check changes something a page would show. Set by the
|
||||
/// dependency container to the navigator's rerender; holding the navigator
|
||||
/// directly would make the wiring circular.
|
||||
void Function()? onChanged;
|
||||
|
||||
Future<void> load() async {
|
||||
final rows = await _db.allSubscriptions();
|
||||
subscribedUrls.value = rows.map((row) => row.url).toSet();
|
||||
|
|
@ -47,4 +63,85 @@ class SubscriptionsController {
|
|||
await _db.touchSubscription(page.url, DateTime.now());
|
||||
return fresh;
|
||||
}
|
||||
|
||||
/// Checks every subscribed site for posts. Sites are checked one after
|
||||
/// another on purpose: the fetch actor answers concurrent sends, so a
|
||||
/// parallel pass would open one request per site at once against small
|
||||
/// servers, for a wait nobody is sitting through.
|
||||
Future<void> refreshAll() async {
|
||||
if (refreshing.value) return;
|
||||
refreshing.value = true;
|
||||
onChanged?.call();
|
||||
try {
|
||||
for (final site in await _db.allSubscriptions()) {
|
||||
await _check(site.url);
|
||||
}
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
onChanged?.call();
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks one site, on the reader's request.
|
||||
Future<void> refreshOne(String siteUrl) async {
|
||||
if (refreshing.value || !isSubscribed(siteUrl)) return;
|
||||
refreshing.value = true;
|
||||
onChanged?.call();
|
||||
try {
|
||||
await _check(siteUrl);
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
onChanged?.call();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markRead(String url, {String? siteUrl}) async {
|
||||
await _db.markEntryRead(url, siteUrl: siteUrl);
|
||||
onChanged?.call();
|
||||
}
|
||||
|
||||
Future<void> markUnread(String url, {String? siteUrl}) async {
|
||||
await _db.markEntryUnread(url, siteUrl: siteUrl);
|
||||
onChanged?.call();
|
||||
}
|
||||
|
||||
/// Fetches one site and stores whatever it publishes. A site that cannot be
|
||||
/// reached is still recorded as checked and keeps the posts already held, so
|
||||
/// one site being down never stops the rest of the pass.
|
||||
Future<void> _check(String siteUrl) async {
|
||||
try {
|
||||
final cached = await _db.cachedPage(siteUrl);
|
||||
final result = await _fetch.send(FetchPage(
|
||||
url: siteUrl,
|
||||
etag: cached?.etag,
|
||||
lastModified: cached?.lastModified,
|
||||
cachedSource: cached?.source,
|
||||
));
|
||||
if (result case FetchPageOk(
|
||||
:final page,
|
||||
:final source,
|
||||
:final etag,
|
||||
:final lastModified,
|
||||
:final notModified,
|
||||
)) {
|
||||
await _db.describeSubscription(siteUrl,
|
||||
title: page.title, atomHref: page.atomFeedHref);
|
||||
await _db.addEntries(siteUrl, page.entries);
|
||||
if (!notModified) {
|
||||
// Keep the cache current so the site also reads offline, but do not
|
||||
// record a visit: the reader did not open it.
|
||||
await _db.saveCachedPage(CachedPagesCompanion.insert(
|
||||
url: siteUrl,
|
||||
source: source,
|
||||
title: Value(page.title),
|
||||
etag: Value(etag),
|
||||
lastModified: Value(lastModified),
|
||||
));
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
// Nothing to tell the reader: the site's "last checked" date says it.
|
||||
}
|
||||
await _db.touchSubscription(siteUrl, DateTime.now());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue