Add Supa account and search changes
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:weblibre/data/database/extensions/database_table_size.dart';
|
||||
import 'package:weblibre/features/user/data/icon_cache_marker.dart';
|
||||
import 'package:weblibre/features/user/data/database/daos/cache.drift.dart';
|
||||
import 'package:weblibre/features/user/data/database/database.dart';
|
||||
import 'package:weblibre/features/user/data/database/definitions.drift.dart';
|
||||
@@ -43,6 +44,14 @@ class CacheDao extends DatabaseAccessor<UserDatabase> with $CacheDaoMixin {
|
||||
return query.map((row) => row.read(db.iconCache.iconData));
|
||||
}
|
||||
|
||||
SingleOrNullSelectable<DateTime?> getCachedIconFetchDate(String origin) {
|
||||
final query = selectOnly(db.iconCache)
|
||||
..addColumns([db.iconCache.fetchDate])
|
||||
..where(db.iconCache.origin.equals(origin));
|
||||
|
||||
return query.map((row) => row.read(db.iconCache.fetchDate));
|
||||
}
|
||||
|
||||
Future<int> cacheIcon(String origin, Uint8List bytes) {
|
||||
return db.iconCache.insertOne(
|
||||
IconCacheCompanion.insert(
|
||||
@@ -58,4 +67,15 @@ class CacheDao extends DatabaseAccessor<UserDatabase> with $CacheDaoMixin {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> cacheIconIfAbsent(String origin, Uint8List bytes) async {
|
||||
final existing = await getCachedIcon(origin).getSingleOrNull();
|
||||
if (existing == null || isMissingIconMarker(existing)) {
|
||||
await cacheIcon(origin, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> cacheMissingIcon(String origin) {
|
||||
return cacheIcon(origin, missingIconMarkerBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:weblibre/features/user/data/database/database.dart';
|
||||
import 'package:weblibre/features/user/data/database/definitions.drift.dart';
|
||||
|
||||
class ReservedToken {
|
||||
final int id;
|
||||
final Uint8List token;
|
||||
const ReservedToken({required this.id, required this.token});
|
||||
}
|
||||
|
||||
@DriftAccessor()
|
||||
class SearchTokensDao extends DatabaseAccessor<UserDatabase> {
|
||||
SearchTokensDao(super.attachedDatabase);
|
||||
|
||||
Future<void> addTokens(
|
||||
List<Uint8List> tokens, {
|
||||
required String issuerKeyVersion,
|
||||
}) {
|
||||
return batch((b) {
|
||||
b.insertAll(
|
||||
db.searchTokens,
|
||||
tokens
|
||||
.map(
|
||||
(t) => SearchTokensCompanion.insert(
|
||||
token: t,
|
||||
issuerKeyVersion: issuerKeyVersion,
|
||||
insertedAt: DateTime.now(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Reserve the oldest unreserved token. Stamps `reserved_at` so the same
|
||||
/// row won't be handed out twice. Caller MUST follow with either
|
||||
/// [commitReserved] (server consumed it) or [releaseReserved] (we know the
|
||||
/// server didn't see it).
|
||||
Future<ReservedToken?> reserveOne() {
|
||||
return transaction(() async {
|
||||
final row =
|
||||
await (select(db.searchTokens)
|
||||
..where((t) => t.reservedAt.isNull())
|
||||
..orderBy([(t) => OrderingTerm.asc(t.id)])
|
||||
..limit(1))
|
||||
.getSingleOrNull();
|
||||
if (row == null) return null;
|
||||
await (update(db.searchTokens)..where((t) => t.id.equals(row.id))).write(
|
||||
SearchTokensCompanion(reservedAt: Value(DateTime.now())),
|
||||
);
|
||||
return ReservedToken(id: row.id, token: row.token);
|
||||
});
|
||||
}
|
||||
|
||||
/// Permanently delete a reserved token after a successful redemption.
|
||||
Future<void> commitReserved(int id) async {
|
||||
await (delete(db.searchTokens)..where((t) => t.id.equals(id))).go();
|
||||
}
|
||||
|
||||
/// Clear the reservation so the token is handed out again. Use only when
|
||||
/// the server is known not to have consumed it.
|
||||
Future<void> releaseReserved(int id) async {
|
||||
await (update(db.searchTokens)..where((t) => t.id.equals(id))).write(
|
||||
const SearchTokensCompanion(reservedAt: Value(null)),
|
||||
);
|
||||
}
|
||||
|
||||
/// Release any reservation older than [maxAge] — covers crashes mid-flight.
|
||||
/// Returns the number of rows released.
|
||||
Future<int> releaseStaleReservations(Duration maxAge) async {
|
||||
final cutoff = DateTime.now().subtract(maxAge);
|
||||
return (update(db.searchTokens)..where(
|
||||
(t) =>
|
||||
t.reservedAt.isNotNull() &
|
||||
t.reservedAt.isSmallerThanValue(cutoff),
|
||||
))
|
||||
.write(const SearchTokensCompanion(reservedAt: Value(null)));
|
||||
}
|
||||
|
||||
Future<int> count() async {
|
||||
final c = db.searchTokens.id.count();
|
||||
final query = selectOnly(db.searchTokens)
|
||||
..addColumns([c])
|
||||
..where(db.searchTokens.reservedAt.isNull());
|
||||
final row = await query.getSingle();
|
||||
return row.read(c) ?? 0;
|
||||
}
|
||||
|
||||
Stream<int> watchCount() {
|
||||
final c = db.searchTokens.id.count();
|
||||
final query = selectOnly(db.searchTokens)
|
||||
..addColumns([c])
|
||||
..where(db.searchTokens.reservedAt.isNull());
|
||||
return query.map((row) => row.read(c) ?? 0).watchSingle();
|
||||
}
|
||||
|
||||
Future<int> clear() {
|
||||
return delete(db.searchTokens).go();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// dart format width=80
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart' as i0;
|
||||
import 'package:weblibre/features/user/data/database/database.dart' as i1;
|
||||
|
||||
mixin $SearchTokensDaoMixin on i0.DatabaseAccessor<i1.UserDatabase> {
|
||||
SearchTokensDaoManager get managers => SearchTokensDaoManager(this);
|
||||
}
|
||||
|
||||
class SearchTokensDaoManager {
|
||||
final $SearchTokensDaoMixin _db;
|
||||
SearchTokensDaoManager(this._db);
|
||||
}
|
||||
Reference in New Issue
Block a user