twonly-app/lib/src/database/daos/receipts.dao.dart
otsmr ebc643cbe4
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Fix: Reliability of receiving media files.
2026-05-15 23:17:40 +02:00

258 lines
7.3 KiB
Dart

import 'package:clock/clock.dart';
import 'package:drift/drift.dart';
import 'package:hashlib/random.dart';
import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/tables/receipts.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
import 'package:twonly/src/utils/log.dart';
part 'receipts.dao.g.dart';
@DriftAccessor(
tables: [Receipts, Messages, MessageActions, ReceivedReceipts, Contacts],
)
class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
// this constructor is required so that the main database can create an instance
// of this object.
// ignore: matching_super_parameters
ReceiptsDao(super.db);
Future<void> confirmReceipt(String receiptId, int fromUserId) async {
final receipt =
await (select(receipts)..where(
(t) =>
t.receiptId.equals(receiptId) &
t.contactId.equals(fromUserId),
))
.getSingleOrNull();
if (receipt == null) return;
if (receipt.messageId != null) {
await into(messageActions).insertOnConflictUpdate(
MessageActionsCompanion(
messageId: Value(receipt.messageId!),
contactId: Value(fromUserId),
type: const Value(MessageActionType.ackByUserAt),
),
);
await handleMediaRelatedResponseFromReceiver(receipt.messageId!);
}
await (delete(receipts)..where(
(t) => t.receiptId.equals(receiptId) & t.contactId.equals(fromUserId),
))
.go();
}
Future<void> deleteReceipt(String receiptId) async {
await (delete(receipts)..where(
(t) => t.receiptId.equals(receiptId),
))
.go();
}
Future<void> deleteReceiptsByMessageId(String messageId) async {
await (delete(receipts)..where(
(t) => t.messageId.equals(messageId),
))
.go();
}
Future<void> deleteReceiptForUser(int contactId) async {
await (delete(receipts)..where(
(t) => t.contactId.equals(contactId),
))
.go();
}
Future<void> purgeReceivedReceipts() async {
await (delete(receivedReceipts)..where(
(t) => (t.createdAt.isSmallerThanValue(
clock.now().subtract(
const Duration(days: 25),
),
)),
))
.go();
final deletedContacts = await (select(
contacts,
)..where((t) => t.accountDeleted.equals(true))).get();
for (final contact in deletedContacts) {
await (delete(receipts)..where(
(t) => t.contactId.equals(contact.userId),
))
.go();
}
}
Future<Receipt?> insertReceipt(ReceiptsCompanion entry) async {
try {
var insertEntry = entry;
if (entry.receiptId == const Value.absent()) {
insertEntry = entry.copyWith(
receiptId: Value(uuid.v4()),
);
}
await into(receipts).insert(insertEntry);
final receiptId = insertEntry.receiptId.value;
return await (select(
receipts,
)..where((t) => t.receiptId.equals(receiptId))).getSingle();
} catch (e) {
// ignore error, receipts is already in the database...
return null;
}
}
Future<Receipt?> getReceiptById(String receiptId) async {
try {
return await (select(receipts)..where(
(t) => t.receiptId.equals(receiptId),
))
.getSingleOrNull();
} catch (e) {
Log.error(e);
return null;
}
}
Future<List<Receipt>> getReceiptsByContactAndMessageId(
int contactId,
String messageId,
) async {
return (select(receipts)..where(
(t) => t.contactId.equals(contactId) & t.messageId.equals(messageId),
))
.get();
}
Future<List<Receipt>> getReceiptsForRetransmission() async {
final markedRetriesTime = clock.now().subtract(
const Duration(
// give the server time to transmit all messages to the client
seconds: 20,
),
);
return (select(receipts)..where(
(t) =>
(t.ackByServerAt.isNull() |
t.markForRetry.isSmallerThanValue(markedRetriesTime) |
t.markForRetryAfterAccepted.isSmallerThanValue(
markedRetriesTime,
)) &
t.willBeRetriedByMediaUpload.equals(false),
))
.get();
}
Future<List<Receipt>> getReceiptsForMediaRetransmissions() async {
final markedRetriesTime = clock.now().subtract(
const Duration(
// give the server time to transmit all messages to the client
seconds: 20,
),
);
return (select(receipts)..where(
(t) =>
(t.markForRetry.isSmallerThanValue(markedRetriesTime) |
t.markForRetryAfterAccepted.isSmallerThanValue(
markedRetriesTime,
)) &
t.willBeRetriedByMediaUpload.equals(true),
))
.get();
}
Stream<List<Receipt>> watchAll() {
return select(receipts).watch();
}
Future<int> getReceiptCountForContact(int contactId) {
final countExp = countAll();
final query = selectOnly(receipts)
..addColumns([countExp])
..where(receipts.contactId.equals(contactId));
return query.map((row) => row.read(countExp)!).getSingle();
}
Future<void> updateReceipt(
String receiptId,
ReceiptsCompanion updates,
) async {
await (update(
receipts,
)..where((c) => c.receiptId.equals(receiptId))).write(updates);
}
Future<Receipt?> rotateReceiptId(String oldReceiptId) async {
final newReceiptId = uuid.v4();
await updateReceipt(
oldReceiptId,
ReceiptsCompanion(
receiptId: Value(newReceiptId),
),
);
final updatedReceipt = await getReceiptById(newReceiptId);
if (updatedReceipt == null) {
Log.error(
'Tried to change the receipt ID, but could not get the updated receipt...',
);
}
return updatedReceipt;
}
Future<void> updateReceiptByContactAndMessageId(
int contactId,
String messageId,
ReceiptsCompanion updates,
) async {
await (update(
receipts,
)..where(
(c) => c.contactId.equals(contactId) & c.messageId.equals(messageId),
))
.write(updates);
}
Future<void> updateReceiptWidthUserId(
int fromUserId,
String receiptId,
ReceiptsCompanion updates,
) async {
await (update(receipts)..where(
(c) => c.receiptId.equals(receiptId) & c.contactId.equals(fromUserId),
))
.write(updates);
}
Future<void> markMessagesForRetry(int contactId) async {
await (update(receipts)..where(
(c) => c.contactId.equals(contactId) & c.markForRetry.isNull(),
))
.write(
ReceiptsCompanion(
markForRetry: Value(clock.now()),
),
);
}
Future<bool> isDuplicated(String receiptId) async {
return await (select(
receivedReceipts,
)..where((t) => t.receiptId.equals(receiptId))).getSingleOrNull() !=
null;
}
Future<void> gotReceipt(String receiptId) async {
await into(
receivedReceipts,
).insert(ReceivedReceiptsCompanion(receiptId: Value(receiptId)));
}
}