fix multiple race condition problems

This commit is contained in:
otsmr 2025-04-07 23:30:24 +02:00
parent 6d67a16840
commit e005d01177
12 changed files with 76 additions and 31 deletions

View file

@ -41,7 +41,6 @@ void main() async {
apiProvider = ApiProvider();
twonlyDatabase = TwonlyDatabase();
setupNotificationWithUsers();
runApp(
MultiProvider(

View file

@ -2,6 +2,7 @@ import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/connection_state.dart';
import 'package:twonly/src/providers/settings_change_provider.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/onboarding/onboarding_view.dart';
import 'package:twonly/src/views/home_view.dart';
@ -44,6 +45,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
setState(() {
_isConnected = isConnected;
});
setupNotificationWithUsers();
};
// WidgetsBinding.instance.addPostFrameCallback((_) {

View file

@ -182,8 +182,9 @@ Future sendTextMessage(
}
Future notifyContactAboutOpeningMessage(
int fromUserId, int messageOtherId) async {
encryptAndSendMessage(
int fromUserId, List<int> messageOtherIds) async {
for (final messageOtherId in messageOtherIds) {
await encryptAndSendMessage(
null,
fromUserId,
MessageJson(
@ -194,6 +195,7 @@ Future notifyContactAboutOpeningMessage(
),
);
}
}
Future notifyContactsAboutProfileChange() async {
List<Contact> contacts =

View file

@ -462,7 +462,8 @@ Future<Uint8List?> getDownloadedMedia(
if (media == null) return null;
// await userOpenedOtherMessage(otherUserId, messageOtherId);
notifyContactAboutOpeningMessage(message.contactId, message.messageOtherId!);
notifyContactAboutOpeningMessage(
message.contactId, [message.messageOtherId!]);
twonlyDatabase.messagesDao.updateMessageByMessageId(
message.messageId, MessagesCompanion(openedAt: Value(DateTime.now())));

View file

@ -23,8 +23,16 @@ import 'package:twonly/src/services/notification_service.dart';
// ignore: library_prefixes
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
bool isBlocked = false;
Future handleServerMessage(server.ServerToClient msg) async {
client.Response? response;
int maxCounter = 0; // only block for 2 seconds
while (isBlocked && maxCounter < 200) {
await Future.delayed(Duration(milliseconds: 10));
maxCounter += 1;
}
isBlocked = true;
try {
if (msg.v0.hasRequestNewPreKeys()) {
@ -38,12 +46,14 @@ Future handleServerMessage(server.ServerToClient msg) async {
} else {
Logger("handleServerMessage")
.shout("Got a new message from the server: $msg");
return;
response = client.Response()..error = ErrorCode.InternalError;
}
} catch (e) {
response = client.Response()..error = ErrorCode.InternalError;
}
isBlocked = false;
var v0 = client.V0()
..seq = msg.v0.seq
..response = response;
@ -68,17 +78,19 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
if (messageId == null) {
Logger("server_messages")
.info("download data received, but unknown messageID");
.shout("download data received, but unknown messageID");
// answers with ok, so the server will delete the message
var ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
}
if (data.fin && data.data.isEmpty) {
Logger("server_messages")
.shout("Got an image message, but was already deleted by the server!");
// media file was deleted by the server. remove the media from device
await twonlyDatabase.messagesDao.deleteMessageById(messageId);
box.delete(boxId);
box.delete("${data.downloadToken}_downloaded");
await box.delete(boxId);
await box.delete("${data.downloadToken}_downloaded");
var ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
}
@ -103,7 +115,7 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
if (!data.fin) {
// download not finished, so waiting for more data...
box.put(boxId, downloadedBytes);
await box.put(boxId, downloadedBytes);
var ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
}
@ -138,7 +150,7 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
final rawBytes =
await xchacha20.decrypt(secretBox, secretKey: secretKeyData);
box.put("${data.downloadToken}_downloaded", rawBytes);
await box.put("${data.downloadToken}_downloaded", rawBytes);
} catch (e) {
Logger("server_messages").info("Decryption error: $e");
// deleting message as this is an invalid image
@ -148,13 +160,14 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
return client.Response()..ok = ok;
}
Logger("server_messages").info("Downloaded: $messageId");
await twonlyDatabase.messagesDao.updateMessageByOtherUser(
msg.contactId,
messageId,
MessagesCompanion(downloadState: Value(DownloadState.downloaded)),
);
box.delete(boxId);
await box.delete(boxId);
var ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
@ -201,7 +214,6 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
final update = ContactsCompanion(accepted: Value(true));
await twonlyDatabase.contactsDao.updateContact(fromUserId, update);
notifyContactsAboutProfileChange();
setupNotificationWithUsers();
break;
case MessageKind.profileChange:
@ -344,7 +356,6 @@ Future<client.Response> handleContactRequest(
Result username = await apiProvider.getUsername(fromUserId);
if (username.isSuccess) {
Uint8List name = username.value.userdata.username;
await twonlyDatabase.contactsDao.insertContact(
ContactsCompanion(
username: Value(utf8.decode(name)),
@ -353,6 +364,7 @@ Future<client.Response> handleContactRequest(
),
);
}
await setupNotificationWithUsers();
var ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
}

View file

@ -16,7 +16,7 @@ Future initMediaStorage() async {
value: base64UrlEncode(key),
);
}
final dir = await getApplicationDocumentsDirectory();
final dir = await getApplicationSupportDirectory();
Hive.init(dir.path);
}

View file

@ -97,7 +97,7 @@ Future setupNotificationWithUsers({bool force = false}) async {
key: List<int>.generate(32, (index) => random.nextInt(256)),
createdAt: DateTime.now(),
);
sendNewPushKey(contact.userId, pushKey);
await sendNewPushKey(contact.userId, pushKey);
pushKeys[contact.userId]!.keys.add(pushKey);
pushKeys[contact.userId]!.displayName = getContactDisplayName(contact);
wasChanged = true;
@ -109,7 +109,7 @@ Future setupNotificationWithUsers({bool force = false}) async {
key: List<int>.generate(32, (index) => random.nextInt(256)),
createdAt: DateTime.now(),
);
sendNewPushKey(contact.userId, pushKey);
await sendNewPushKey(contact.userId, pushKey);
final pushUser = PushUser(
displayName: getContactDisplayName(contact),
keys: [pushKey],
@ -588,7 +588,7 @@ Future<String?> getAvatarIcon(Contact user) async {
final Uint8List pngBytes = byteData!.buffer.asUint8List();
// Get the directory to save the image
final directory = await getApplicationDocumentsDirectory();
final directory = await getApplicationCacheDirectory();
final avatarsDirectory = Directory('${directory.path}/avatars');
// Create the avatars directory if it does not exist

View file

@ -20,7 +20,7 @@ extension ShortCutsExtension on BuildContext {
}
Future<void> writeLogToFile(LogRecord record) async {
final directory = await getApplicationDocumentsDirectory();
final directory = await getApplicationSupportDirectory();
final logFile = File('${directory.path}/app.log');
// Prepare the log message
@ -32,7 +32,7 @@ Future<void> writeLogToFile(LogRecord record) async {
}
Future<bool> deleteLogFile() async {
final directory = await getApplicationDocumentsDirectory();
final directory = await getApplicationSupportDirectory();
final logFile = File('${directory.path}/app.log');
if (await logFile.exists()) {

View file

@ -235,12 +235,14 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
// should be cleared
Map<int, List<Message>> tmpReactionsToMyMessages = {};
Map<int, List<Message>> tmpTeactionsToOtherMessages = {};
List<int> openedMessageOtherIds = [];
for (Message msg in msgs) {
if (msg.kind == MessageKind.textMessage &&
msg.messageOtherId != null &&
msg.openedAt == null) {
updated = true;
notifyContactAboutOpeningMessage(widget.userid, msg.messageOtherId!);
openedMessageOtherIds.add(msg.messageOtherId!);
}
if (msg.responseToMessageId != null) {
@ -261,7 +263,11 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
displayedMessages.add(msg);
}
}
if (openedMessageOtherIds.isNotEmpty) {
notifyContactAboutOpeningMessage(widget.userid, openedMessageOtherIds);
}
twonlyDatabase.messagesDao.openedAllNonMediaMessages(widget.userid);
// should be fixed with that
if (!updated) {
// The stream should be get an update, so only update the UI when all are opened
setState(() {

View file

@ -64,7 +64,19 @@ class _MediaViewerViewState extends State<MediaViewerView> {
_subscription = messages.listen((messages) {
for (Message msg in messages) {
if (!allMediaFiles.any((m) => m.messageId == msg.messageId)) {
// if (!allMediaFiles.any((m) => m.messageId == msg.messageId)) {
// allMediaFiles.add(msg);
// }
// Find the index of the existing message with the same messageId
int index =
allMediaFiles.indexWhere((m) => m.messageId == msg.messageId);
if (index >= 1) {
// to not modify the first message
// If the message exists, replace it
allMediaFiles[index] = msg;
} else {
// If the message does not exist, add it
allMediaFiles.add(msg);
}
}

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
@ -122,6 +123,16 @@ class _SearchUsernameView extends State<SearchUsernameView> {
onSubmitted: (_) {
_addNewUser(context);
},
onChanged: (value) {
searchUserName.text = value.toLowerCase();
searchUserName.selection = TextSelection.fromPosition(
TextPosition(offset: searchUserName.text.length),
);
},
inputFormatters: [
LengthLimitingTextInputFormatter(12),
FilteringTextInputFormatter.allow(RegExp(r'[a-z0-9A-Z]')),
],
controller: searchUserName,
decoration:
getInputDecoration(context.lang.searchUsernameInput),

View file

@ -76,7 +76,7 @@ class DiagnosticsView extends StatelessWidget {
}
Future<String> _loadLogFile() async {
final directory = await getApplicationDocumentsDirectory();
final directory = await getApplicationSupportDirectory();
final logFile = File('${directory.path}/app.log');
if (await logFile.exists()) {