mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-04-18 14:42:54 +00:00
fix: reupload of media files
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
766d482baa
commit
cfc6e945da
12 changed files with 287 additions and 50 deletions
|
|
@ -1,5 +1,11 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
- Fix: Reupload of media files was not working properly
|
||||||
|
- Fix: Chats where ordered wrongly
|
||||||
|
- Fix: Typing indicator was not shown always
|
||||||
|
|
||||||
## 0.1.4
|
## 0.1.4
|
||||||
|
|
||||||
- New: Typing and chat open indicator
|
- New: Typing and chat open indicator
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
// ignore: matching_super_parameters
|
// ignore: matching_super_parameters
|
||||||
MediaFilesDao(super.db);
|
MediaFilesDao(super.db);
|
||||||
|
|
||||||
Future<MediaFile?> insertMedia(MediaFilesCompanion mediaFile) async {
|
Future<MediaFile?> insertOrUpdateMedia(MediaFilesCompanion mediaFile) async {
|
||||||
try {
|
try {
|
||||||
var insertMediaFile = mediaFile;
|
var insertMediaFile = mediaFile;
|
||||||
|
|
||||||
|
|
@ -24,7 +24,9 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final rowId = await into(mediaFiles).insert(insertMediaFile);
|
final rowId = await into(
|
||||||
|
mediaFiles,
|
||||||
|
).insertOnConflictUpdate(insertMediaFile);
|
||||||
|
|
||||||
return await (select(
|
return await (select(
|
||||||
mediaFiles,
|
mediaFiles,
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final rowId = await into(messages).insert(insertMessage);
|
final rowId = await into(messages).insertOnConflictUpdate(insertMessage);
|
||||||
|
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
message.groupId.value,
|
message.groupId.value,
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
if (receipt == null) return;
|
if (receipt == null) return;
|
||||||
|
|
||||||
if (receipt.messageId != null) {
|
if (receipt.messageId != null) {
|
||||||
await into(messageActions).insert(
|
await into(messageActions).insertOnConflictUpdate(
|
||||||
MessageActionsCompanion(
|
MessageActionsCompanion(
|
||||||
messageId: Value(receipt.messageId!),
|
messageId: Value(receipt.messageId!),
|
||||||
contactId: Value(fromUserId),
|
contactId: Value(fromUserId),
|
||||||
|
|
@ -113,6 +113,16 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
Future<List<Receipt>> getReceiptsForRetransmission() async {
|
||||||
final markedRetriesTime = clock.now().subtract(
|
final markedRetriesTime = clock.now().subtract(
|
||||||
const Duration(
|
const Duration(
|
||||||
|
|
@ -132,6 +142,24 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
.get();
|
.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() {
|
Stream<List<Receipt>> watchAll() {
|
||||||
return select(receipts).watch();
|
return select(receipts).watch();
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +183,19 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
)..where((c) => c.receiptId.equals(receiptId))).write(updates);
|
)..where((c) => c.receiptId.equals(receiptId))).write(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
Future<void> updateReceiptWidthUserId(
|
||||||
int fromUserId,
|
int fromUserId,
|
||||||
String receiptId,
|
String receiptId,
|
||||||
|
|
@ -168,9 +209,7 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
|
|
||||||
Future<void> markMessagesForRetry(int contactId) async {
|
Future<void> markMessagesForRetry(int contactId) async {
|
||||||
await (update(receipts)..where(
|
await (update(receipts)..where(
|
||||||
(c) =>
|
(c) => c.contactId.equals(contactId) & c.markForRetry.isNull(),
|
||||||
c.contactId.equals(contactId) &
|
|
||||||
c.willBeRetriedByMediaUpload.equals(false),
|
|
||||||
))
|
))
|
||||||
.write(
|
.write(
|
||||||
ReceiptsCompanion(
|
ReceiptsCompanion(
|
||||||
|
|
|
||||||
|
|
@ -92,12 +92,14 @@ class ApiService {
|
||||||
|
|
||||||
if (globalIsInBackgroundTask) {
|
if (globalIsInBackgroundTask) {
|
||||||
await retransmitRawBytes();
|
await retransmitRawBytes();
|
||||||
await tryTransmitMessages();
|
await retransmitAllMessages();
|
||||||
|
await reuploadMediaFiles();
|
||||||
await tryDownloadAllMediaFiles();
|
await tryDownloadAllMediaFiles();
|
||||||
} else if (!globalIsAppInBackground) {
|
} else if (!globalIsAppInBackground) {
|
||||||
unawaited(retransmitRawBytes());
|
unawaited(retransmitRawBytes());
|
||||||
unawaited(tryTransmitMessages());
|
unawaited(retransmitAllMessages());
|
||||||
unawaited(tryDownloadAllMediaFiles());
|
unawaited(tryDownloadAllMediaFiles());
|
||||||
|
unawaited(reuploadMediaFiles());
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
unawaited(syncFlameCounters());
|
unawaited(syncFlameCounters());
|
||||||
unawaited(setupNotificationWithUsers());
|
unawaited(setupNotificationWithUsers());
|
||||||
|
|
|
||||||
|
|
@ -73,12 +73,38 @@ Future<void> handleMedia(
|
||||||
mediaType = MediaType.audio;
|
mediaType = MediaType.audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mediaIdValue = const Value<String>.absent();
|
||||||
|
|
||||||
final messageTmp = await twonlyDB.messagesDao
|
final messageTmp = await twonlyDB.messagesDao
|
||||||
.getMessageById(media.senderMessageId)
|
.getMessageById(media.senderMessageId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (messageTmp != null) {
|
if (messageTmp != null) {
|
||||||
Log.warn('This message already exit. Message is dropped.');
|
if (messageTmp.senderId != fromUserId) {
|
||||||
return;
|
Log.warn(
|
||||||
|
'$fromUserId tried to modify the message from ${messageTmp.senderId}.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (messageTmp.mediaId == null) {
|
||||||
|
Log.warn(
|
||||||
|
'This message already exit without a mediaId. Message is dropped.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
||||||
|
messageTmp.mediaId!,
|
||||||
|
);
|
||||||
|
if (mediaFile?.downloadState != DownloadState.reuploadRequested) {
|
||||||
|
Log.warn(
|
||||||
|
'This message and media file already exit and was not requested again. Dropping it.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaFile != null) {
|
||||||
|
// media file is reuploaded use the same mediaId
|
||||||
|
mediaIdValue = Value(mediaFile.mediaId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int? displayLimitInMilliseconds;
|
int? displayLimitInMilliseconds;
|
||||||
|
|
@ -95,8 +121,9 @@ Future<void> handleMedia(
|
||||||
late Message? message;
|
late Message? message;
|
||||||
|
|
||||||
await twonlyDB.transaction(() async {
|
await twonlyDB.transaction(() async {
|
||||||
mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
mediaFile = await twonlyDB.mediaFilesDao.insertOrUpdateMedia(
|
||||||
MediaFilesCompanion(
|
MediaFilesCompanion(
|
||||||
|
mediaId: mediaIdValue,
|
||||||
downloadState: const Value(DownloadState.pending),
|
downloadState: const Value(DownloadState.pending),
|
||||||
type: Value(mediaType),
|
type: Value(mediaType),
|
||||||
requiresAuthentication: Value(media.requiresAuthentication),
|
requiresAuthentication: Value(media.requiresAuthentication),
|
||||||
|
|
@ -205,23 +232,6 @@ Future<void> handleMediaUpdate(
|
||||||
|
|
||||||
case EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR:
|
case EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR:
|
||||||
Log.info('Got media file decryption error ${mediaFile.mediaId}');
|
Log.info('Got media file decryption error ${mediaFile.mediaId}');
|
||||||
final reuploadRequestedBy = mediaFile.reuploadRequestedBy ?? [];
|
await reuploadMediaFile(fromUserId, mediaFile, message.messageId);
|
||||||
reuploadRequestedBy.add(fromUserId);
|
|
||||||
await twonlyDB.mediaFilesDao.updateMedia(
|
|
||||||
mediaFile.mediaId,
|
|
||||||
MediaFilesCompanion(
|
|
||||||
uploadState: const Value(UploadState.preprocessing),
|
|
||||||
reuploadRequestedBy: Value(reuploadRequestedBy),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final mediaFileUpdated = await MediaFileService.fromMediaId(
|
|
||||||
mediaFile.mediaId,
|
|
||||||
);
|
|
||||||
if (mediaFileUpdated != null) {
|
|
||||||
if (mediaFileUpdated.uploadRequestPath.existsSync()) {
|
|
||||||
mediaFileUpdated.uploadRequestPath.deleteSync();
|
|
||||||
}
|
|
||||||
unawaited(startBackgroundMediaUpload(mediaFileUpdated));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,148 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:workmanager/workmanager.dart' hide TaskStatus;
|
import 'package:workmanager/workmanager.dart' hide TaskStatus;
|
||||||
|
|
||||||
|
final lockRetransmission = Mutex();
|
||||||
|
|
||||||
|
Future<void> reuploadMediaFiles() async {
|
||||||
|
return lockRetransmission.protect(() async {
|
||||||
|
final receipts = await twonlyDB.receiptsDao
|
||||||
|
.getReceiptsForMediaRetransmissions();
|
||||||
|
|
||||||
|
if (receipts.isEmpty) return;
|
||||||
|
|
||||||
|
Log.info('Reuploading ${receipts.length} media files to the server.');
|
||||||
|
|
||||||
|
final contacts = <int, Contact>{};
|
||||||
|
|
||||||
|
for (final receipt in receipts) {
|
||||||
|
if (receipt.retryCount > 1 && receipt.lastRetry != null) {
|
||||||
|
final twentyFourHoursAgo = DateTime.now().subtract(
|
||||||
|
const Duration(hours: 24),
|
||||||
|
);
|
||||||
|
if (receipt.lastRetry!.isAfter(twentyFourHoursAgo)) {
|
||||||
|
Log.info(
|
||||||
|
'Ignoring ${receipt.receiptId} as it was retried in the last 24h',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var messageId = receipt.messageId;
|
||||||
|
if (receipt.messageId == null) {
|
||||||
|
Log.info('Message not in receipt. Loading it from the content.');
|
||||||
|
try {
|
||||||
|
final content = EncryptedContent.fromBuffer(receipt.message);
|
||||||
|
if (content.hasMedia()) {
|
||||||
|
messageId = content.media.senderMessageId;
|
||||||
|
await twonlyDB.receiptsDao.updateReceipt(
|
||||||
|
receipt.receiptId,
|
||||||
|
ReceiptsCompanion(
|
||||||
|
messageId: Value(messageId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (messageId == null) {
|
||||||
|
Log.error('MessageId is empty for media file receipts');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (receipt.markForRetryAfterAccepted != null) {
|
||||||
|
if (!contacts.containsKey(receipt.contactId)) {
|
||||||
|
final contact = await twonlyDB.contactsDao
|
||||||
|
.getContactByUserId(receipt.contactId)
|
||||||
|
.getSingleOrNull();
|
||||||
|
if (contact == null) {
|
||||||
|
Log.error(
|
||||||
|
'Contact does not exists, but has a record in receipts, this should not be possible, because of the DELETE CASCADE relation.',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
contacts[receipt.contactId] = contact;
|
||||||
|
}
|
||||||
|
if (!(contacts[receipt.contactId]?.accepted ?? true)) {
|
||||||
|
Log.warn(
|
||||||
|
'Could not send message as contact has still not yet accepted.',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receipt.ackByServerAt == null) {
|
||||||
|
// media file must be reuploaded again in case the media files
|
||||||
|
// was deleted by the server, the receiver will request a new media reupload
|
||||||
|
|
||||||
|
final message = await twonlyDB.messagesDao
|
||||||
|
.getMessageById(messageId)
|
||||||
|
.getSingleOrNull();
|
||||||
|
if (message == null || message.mediaId == null) {
|
||||||
|
Log.error(
|
||||||
|
'Message not found for reupload of the receipt (${message == null} - ${message?.mediaId}).',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
||||||
|
message.mediaId!,
|
||||||
|
);
|
||||||
|
if (mediaFile == null) {
|
||||||
|
Log.error(
|
||||||
|
'Mediafile not found for reupload of the receipt (${message.messageId} - ${message.mediaId}).',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await reuploadMediaFile(
|
||||||
|
receipt.contactId,
|
||||||
|
mediaFile,
|
||||||
|
message.messageId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Log.info('Reuploading media file $messageId');
|
||||||
|
// the media file should be still on the server, so it should be enough
|
||||||
|
// to just resend the message containing the download token.
|
||||||
|
await tryToSendCompleteMessage(receipt: receipt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> reuploadMediaFile(
|
||||||
|
int contactId,
|
||||||
|
MediaFile mediaFile,
|
||||||
|
String messageId,
|
||||||
|
) async {
|
||||||
|
Log.info('Reuploading media file: ${mediaFile.mediaId}');
|
||||||
|
|
||||||
|
await twonlyDB.receiptsDao.updateReceiptByContactAndMessageId(
|
||||||
|
contactId,
|
||||||
|
messageId,
|
||||||
|
const ReceiptsCompanion(
|
||||||
|
markForRetry: Value(null),
|
||||||
|
markForRetryAfterAccepted: Value(null),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final reuploadRequestedBy = (mediaFile.reuploadRequestedBy ?? [])
|
||||||
|
..add(contactId);
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
MediaFilesCompanion(
|
||||||
|
uploadState: const Value(UploadState.preprocessing),
|
||||||
|
reuploadRequestedBy: Value(reuploadRequestedBy),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final mediaFileUpdated = await MediaFileService.fromMediaId(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
);
|
||||||
|
if (mediaFileUpdated != null) {
|
||||||
|
if (mediaFileUpdated.uploadRequestPath.existsSync()) {
|
||||||
|
mediaFileUpdated.uploadRequestPath.deleteSync();
|
||||||
|
}
|
||||||
|
unawaited(startBackgroundMediaUpload(mediaFileUpdated));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> finishStartedPreprocessing() async {
|
Future<void> finishStartedPreprocessing() async {
|
||||||
final mediaFiles = await twonlyDB.mediaFilesDao
|
final mediaFiles = await twonlyDB.mediaFilesDao
|
||||||
.getAllMediaFilesPendingUpload();
|
.getAllMediaFilesPendingUpload();
|
||||||
|
|
@ -62,7 +204,7 @@ Future<void> finishStartedPreprocessing() async {
|
||||||
|
|
||||||
/// It can happen, that a media files is uploaded but not yet marked for been uploaded.
|
/// It can happen, that a media files is uploaded but not yet marked for been uploaded.
|
||||||
/// For example because the background_downloader plugin has not yet reported the finished upload.
|
/// For example because the background_downloader plugin has not yet reported the finished upload.
|
||||||
/// In case the the message receipts or a reaction was received, mark the media file as been uploaded.
|
/// In case the message receipts or a reaction was received, mark the media file as been uploaded.
|
||||||
Future<void> handleMediaRelatedResponseFromReceiver(String messageId) async {
|
Future<void> handleMediaRelatedResponseFromReceiver(String messageId) async {
|
||||||
final message = await twonlyDB.messagesDao
|
final message = await twonlyDB.messagesDao
|
||||||
.getMessageById(messageId)
|
.getMessageById(messageId)
|
||||||
|
|
@ -100,6 +242,16 @@ Future<void> markUploadAsSuccessful(MediaFile media) async {
|
||||||
message.messageId,
|
message.messageId,
|
||||||
clock.now(),
|
clock.now(),
|
||||||
);
|
);
|
||||||
|
await twonlyDB.receiptsDao.updateReceiptByContactAndMessageId(
|
||||||
|
contact.contactId,
|
||||||
|
message.messageId,
|
||||||
|
ReceiptsCompanion(
|
||||||
|
ackByServerAt: Value(clock.now()),
|
||||||
|
retryCount: const Value(1),
|
||||||
|
lastRetry: Value(clock.now()),
|
||||||
|
markForRetry: const Value(null),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +274,7 @@ Future<MediaFileService?> initializeMediaUpload(
|
||||||
const MediaFilesCompanion(isDraftMedia: Value(false)),
|
const MediaFilesCompanion(isDraftMedia: Value(false)),
|
||||||
);
|
);
|
||||||
|
|
||||||
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
final mediaFile = await twonlyDB.mediaFilesDao.insertOrUpdateMedia(
|
||||||
MediaFilesCompanion(
|
MediaFilesCompanion(
|
||||||
uploadState: const Value(UploadState.initialized),
|
uploadState: const Value(UploadState.initialized),
|
||||||
displayLimitInMilliseconds: Value(displayLimitInMilliseconds),
|
displayLimitInMilliseconds: Value(displayLimitInMilliseconds),
|
||||||
|
|
@ -313,7 +465,8 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media.mediaFile.reuploadRequestedBy != null) {
|
if (media.mediaFile.reuploadRequestedBy != null) {
|
||||||
type = EncryptedContent_Media_Type.REUPLOAD;
|
// not used any more... Receiver detects automatically if it is an reupload...
|
||||||
|
// type = EncryptedContent_Media_Type.REUPLOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
final notEncryptedContent = EncryptedContent(
|
final notEncryptedContent = EncryptedContent(
|
||||||
|
|
@ -340,6 +493,7 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
||||||
final cipherText = await sendCipherText(
|
final cipherText = await sendCipherText(
|
||||||
groupMember.contactId,
|
groupMember.contactId,
|
||||||
notEncryptedContent,
|
notEncryptedContent,
|
||||||
|
messageId: message.messageId,
|
||||||
onlyReturnEncryptedData: true,
|
onlyReturnEncryptedData: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
final lockRetransmission = Mutex();
|
final lockRetransmission = Mutex();
|
||||||
|
|
||||||
Future<void> tryTransmitMessages() async {
|
Future<void> retransmitAllMessages() async {
|
||||||
return lockRetransmission.protect(() async {
|
return lockRetransmission.protect(() async {
|
||||||
final receipts = await twonlyDB.receiptsDao.getReceiptsForRetransmission();
|
final receipts = await twonlyDB.receiptsDao.getReceiptsForRetransmission();
|
||||||
|
|
||||||
|
|
@ -304,7 +304,11 @@ Future<void> sendCipherTextToGroup(
|
||||||
}) async {
|
}) async {
|
||||||
final groupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(groupId);
|
final groupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(groupId);
|
||||||
|
|
||||||
if (!onlySendIfNoReceiptsAreOpen) {
|
if (messageId != null ||
|
||||||
|
encryptedContent.hasReaction() ||
|
||||||
|
encryptedContent.hasMedia() ||
|
||||||
|
encryptedContent.hasTextMessage()) {
|
||||||
|
// only update the counter in case this is a actual message
|
||||||
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,11 +334,11 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
bool onlySendIfNoReceiptsAreOpen = false,
|
bool onlySendIfNoReceiptsAreOpen = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (onlySendIfNoReceiptsAreOpen) {
|
if (onlySendIfNoReceiptsAreOpen) {
|
||||||
if (await twonlyDB.receiptsDao.getReceiptCountForContact(
|
final openReceipts = await twonlyDB.receiptsDao.getReceiptCountForContact(
|
||||||
contactId,
|
contactId,
|
||||||
) >
|
);
|
||||||
0) {
|
if (openReceipts > 2) {
|
||||||
// this prevents that this message is send in case the receiver is not online
|
// this prevents that these types of messages are send in case the receiver is offline
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -344,12 +348,31 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
..type = pb.Message_Type.CIPHERTEXT
|
..type = pb.Message_Type.CIPHERTEXT
|
||||||
..encryptedContent = encryptedContent.writeToBuffer();
|
..encryptedContent = encryptedContent.writeToBuffer();
|
||||||
|
|
||||||
|
var retryCounter = 0;
|
||||||
|
DateTime? lastRetry;
|
||||||
|
|
||||||
|
if (messageId != null) {
|
||||||
|
final receipts = await twonlyDB.receiptsDao
|
||||||
|
.getReceiptsByContactAndMessageId(contactId, messageId);
|
||||||
|
|
||||||
|
for (final receipt in receipts) {
|
||||||
|
if (receipt.lastRetry != null) {
|
||||||
|
lastRetry = receipt.lastRetry;
|
||||||
|
}
|
||||||
|
retryCounter += 1;
|
||||||
|
Log.info('Removing duplicated receipt for message $messageId');
|
||||||
|
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final receipt = await twonlyDB.receiptsDao.insertReceipt(
|
final receipt = await twonlyDB.receiptsDao.insertReceipt(
|
||||||
ReceiptsCompanion(
|
ReceiptsCompanion(
|
||||||
contactId: Value(contactId),
|
contactId: Value(contactId),
|
||||||
message: Value(response.writeToBuffer()),
|
message: Value(response.writeToBuffer()),
|
||||||
messageId: Value(messageId),
|
messageId: Value(messageId),
|
||||||
willBeRetriedByMediaUpload: Value(onlyReturnEncryptedData),
|
willBeRetriedByMediaUpload: Value(onlyReturnEncryptedData),
|
||||||
|
retryCount: Value(retryCounter),
|
||||||
|
lastRetry: Value(lastRetry),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,13 +52,14 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
||||||
await widget.storeImageAsOriginal!();
|
await widget.storeImageAsOriginal!();
|
||||||
}
|
}
|
||||||
|
|
||||||
final newMediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
final newMediaFile = await twonlyDB.mediaFilesDao
|
||||||
MediaFilesCompanion(
|
.insertOrUpdateMedia(
|
||||||
type: Value(widget.mediaService.mediaFile.type),
|
MediaFilesCompanion(
|
||||||
createdAt: Value(clock.now()),
|
type: Value(widget.mediaService.mediaFile.type),
|
||||||
stored: const Value(true),
|
createdAt: Value(clock.now()),
|
||||||
),
|
stored: const Value(true),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (newMediaFile != null) {
|
if (newMediaFile != null) {
|
||||||
final newService = MediaFileService(newMediaFile);
|
final newService = MediaFileService(newMediaFile);
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
|
|
||||||
if (gUser.typingIndicators) {
|
if (gUser.typingIndicators) {
|
||||||
unawaited(sendTypingIndication(widget.groupId, false));
|
unawaited(sendTypingIndication(widget.groupId, false));
|
||||||
_nextTypingIndicator = Timer.periodic(const Duration(seconds: 5), (
|
_nextTypingIndicator = Timer.periodic(const Duration(seconds: 4), (
|
||||||
_,
|
_,
|
||||||
) async {
|
) async {
|
||||||
await sendTypingIndication(widget.groupId, false);
|
await sendTypingIndication(widget.groupId, false);
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ class _TypingIndicatorState extends State<TypingIndicator>
|
||||||
member.lastChatOpened!,
|
member.lastChatOpened!,
|
||||||
)
|
)
|
||||||
.inSeconds <=
|
.inSeconds <=
|
||||||
8;
|
6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ class _ImportMediaViewState extends State<ImportMediaView> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
final mediaFile = await twonlyDB.mediaFilesDao.insertOrUpdateMedia(
|
||||||
MediaFilesCompanion(
|
MediaFilesCompanion(
|
||||||
type: Value(type),
|
type: Value(type),
|
||||||
createdAt: Value(file.lastModDateTime),
|
createdAt: Value(file.lastModDateTime),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue