178 lines
6.4 KiB
Dart
178 lines
6.4 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:drift/drift.dart';
|
|
import 'package:drift/native.dart';
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
part 'database.g.dart';
|
|
|
|
/// Visited pages, newest first; the most recent row is the app's start page.
|
|
class HistoryItems extends Table {
|
|
IntColumn get id => integer().autoIncrement()();
|
|
TextColumn get url => text()();
|
|
TextColumn get title => text().nullable()();
|
|
DateTimeColumn get visitedAt => dateTime().withDefault(currentDateAndTime)();
|
|
}
|
|
|
|
class Bookmarks extends Table {
|
|
IntColumn get id => integer().autoIncrement()();
|
|
TextColumn get url => text()();
|
|
TextColumn get title => text().nullable()();
|
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
|
}
|
|
|
|
/// A subscription to one page (spec 6). [entryUrls] is a JSON list of the
|
|
/// entry URLs already seen, so a refresh can report only new posts.
|
|
class Subscriptions extends Table {
|
|
IntColumn get id => integer().autoIncrement()();
|
|
TextColumn get url => text()();
|
|
TextColumn get title => text().nullable()();
|
|
TextColumn get entryUrls => text().withDefault(const Constant('[]'))();
|
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
|
DateTimeColumn get lastCheckedAt => dateTime().nullable()();
|
|
}
|
|
|
|
/// Single-row table (id 0) holding the reader's presentation choices.
|
|
class ReaderSettings extends Table {
|
|
IntColumn get id => integer().withDefault(const Constant(0))();
|
|
TextColumn get font => text().withDefault(const Constant('Atkinson Hyperlegible'))();
|
|
RealColumn get fontScale => real().withDefault(const Constant(1.0))();
|
|
TextColumn get themeMode => text().withDefault(const Constant('system'))();
|
|
TextColumn get variant => text().withDefault(const Constant('site'))();
|
|
|
|
@override
|
|
Set<Column> get primaryKey => {id};
|
|
}
|
|
|
|
/// The fetched source of a page, kept so the reader can reopen it offline.
|
|
class CachedPages extends Table {
|
|
TextColumn get url => text()();
|
|
TextColumn get title => text().nullable()();
|
|
TextColumn get source => text()();
|
|
TextColumn get etag => text().nullable()();
|
|
TextColumn get lastModified => text().nullable()();
|
|
DateTimeColumn get cachedAt => dateTime().withDefault(currentDateAndTime)();
|
|
|
|
@override
|
|
Set<Column> get primaryKey => {url};
|
|
}
|
|
|
|
@DriftDatabase(tables: [HistoryItems, Bookmarks, Subscriptions, ReaderSettings, CachedPages])
|
|
class MewsDatabase extends _$MewsDatabase {
|
|
MewsDatabase() : super(_open());
|
|
|
|
/// For tests.
|
|
MewsDatabase.test(super.executor);
|
|
|
|
@override
|
|
int get schemaVersion => 1;
|
|
|
|
static QueryExecutor _open() => LazyDatabase(() async {
|
|
final dir = await getApplicationDocumentsDirectory();
|
|
return NativeDatabase.createInBackground(File(p.join(dir.path, 'mews.sqlite')));
|
|
});
|
|
|
|
// History -------------------------------------------------------------
|
|
|
|
Future<void> recordVisit(String url, String? title) =>
|
|
(into(historyItems).insert(HistoryItemsCompanion.insert(url: url, title: Value(title))));
|
|
|
|
Future<String?> lastVisitedUrl() async {
|
|
final query = (select(historyItems)
|
|
..orderBy([
|
|
(r) => OrderingTerm.desc(r.visitedAt),
|
|
// Tiebreak pages recorded within the same clock tick.
|
|
(r) => OrderingTerm.desc(r.id),
|
|
])
|
|
..limit(1));
|
|
final row = await query.getSingleOrNull();
|
|
return row?.url;
|
|
}
|
|
|
|
/// The most recent external visits, newest first.
|
|
Future<List<HistoryItem>> recentVisits({int limit = 50}) async {
|
|
final query = (select(historyItems)
|
|
..orderBy([
|
|
(r) => OrderingTerm.desc(r.visitedAt),
|
|
(r) => OrderingTerm.desc(r.id),
|
|
])
|
|
..limit(limit));
|
|
return query.get();
|
|
}
|
|
|
|
/// Bookmarks in the order they were added, newest first.
|
|
Future<List<Bookmark>> bookmarksByDate() async {
|
|
final query = (select(bookmarks)
|
|
..orderBy([
|
|
(r) => OrderingTerm.desc(r.createdAt),
|
|
(r) => OrderingTerm.desc(r.id),
|
|
]));
|
|
return query.get();
|
|
}
|
|
|
|
// Bookmarks -----------------------------------------------------------
|
|
|
|
Future<List<Bookmark>> allBookmarks() => select(bookmarks).get();
|
|
|
|
Future<void> addBookmark(String url, String? title) => into(bookmarks).insert(
|
|
BookmarksCompanion.insert(url: url, title: Value(title)),
|
|
mode: InsertMode.insertOrIgnore);
|
|
|
|
Future<void> removeBookmark(String url) =>
|
|
(delete(bookmarks)..where((r) => r.url.equals(url))).go();
|
|
|
|
// Subscriptions ---------------------------------------------------------
|
|
|
|
Future<void> addSubscription(String url, String? title, List<String> entryUrls) => into(
|
|
subscriptions)
|
|
.insert(
|
|
SubscriptionsCompanion.insert(
|
|
url: url, title: Value(title), entryUrls: Value(jsonEncode(entryUrls))),
|
|
mode: InsertMode.insertOrIgnore);
|
|
|
|
Future<void> removeSubscription(String url) =>
|
|
(delete(subscriptions)..where((r) => r.url.equals(url))).go();
|
|
|
|
Future<List<Subscription>> allSubscriptions() => select(subscriptions).get();
|
|
|
|
Future<void> updateSubscription(String url,
|
|
{String? title, List<String>? entryUrls, DateTime? lastCheckedAt}) =>
|
|
(update(subscriptions)..where((r) => r.url.equals(url))).write(SubscriptionsCompanion(
|
|
title: Value(title),
|
|
entryUrls: entryUrls == null ? const Value.absent() : Value(jsonEncode(entryUrls)),
|
|
lastCheckedAt: Value(lastCheckedAt),
|
|
));
|
|
|
|
// Settings ---------------------------------------------------------------
|
|
|
|
Future<ReaderSetting?> readSettings() async {
|
|
final query = select(readerSettings)..where((r) => r.id.equals(0));
|
|
return query.getSingleOrNull();
|
|
}
|
|
|
|
Future<void> saveSettings({
|
|
required String font,
|
|
required double fontScale,
|
|
required String themeMode,
|
|
required String variant,
|
|
}) =>
|
|
into(readerSettings).insert(
|
|
ReaderSettingsCompanion.insert(
|
|
id: const Value(0),
|
|
font: Value(font),
|
|
fontScale: Value(fontScale),
|
|
themeMode: Value(themeMode),
|
|
variant: Value(variant),
|
|
),
|
|
mode: InsertMode.insertOrReplace);
|
|
|
|
// Cache --------------------------------------------------------------------
|
|
|
|
Future<CachedPage?> cachedPage(String url) async =>
|
|
(select(cachedPages)..where((r) => r.url.equals(url))).getSingleOrNull();
|
|
|
|
Future<void> saveCachedPage(CachedPagesCompanion entry) =>
|
|
into(cachedPages).insert(entry, mode: InsertMode.insertOrReplace);
|
|
}
|