mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:28:40 +00:00
fix multiple race condition problems
This commit is contained in:
parent
6d67a16840
commit
e005d01177
12 changed files with 76 additions and 31 deletions
|
|
@ -41,7 +41,6 @@ void main() async {
|
||||||
|
|
||||||
apiProvider = ApiProvider();
|
apiProvider = ApiProvider();
|
||||||
twonlyDatabase = TwonlyDatabase();
|
twonlyDatabase = TwonlyDatabase();
|
||||||
setupNotificationWithUsers();
|
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/components/connection_state.dart';
|
import 'package:twonly/src/components/connection_state.dart';
|
||||||
import 'package:twonly/src/providers/settings_change_provider.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/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/onboarding/onboarding_view.dart';
|
import 'package:twonly/src/views/onboarding/onboarding_view.dart';
|
||||||
import 'package:twonly/src/views/home_view.dart';
|
import 'package:twonly/src/views/home_view.dart';
|
||||||
|
|
@ -44,6 +45,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isConnected = isConnected;
|
_isConnected = isConnected;
|
||||||
});
|
});
|
||||||
|
setupNotificationWithUsers();
|
||||||
};
|
};
|
||||||
|
|
||||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
|
|
||||||
|
|
@ -182,17 +182,19 @@ Future sendTextMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
Future notifyContactAboutOpeningMessage(
|
Future notifyContactAboutOpeningMessage(
|
||||||
int fromUserId, int messageOtherId) async {
|
int fromUserId, List<int> messageOtherIds) async {
|
||||||
encryptAndSendMessage(
|
for (final messageOtherId in messageOtherIds) {
|
||||||
null,
|
await encryptAndSendMessage(
|
||||||
fromUserId,
|
null,
|
||||||
MessageJson(
|
fromUserId,
|
||||||
kind: MessageKind.opened,
|
MessageJson(
|
||||||
messageId: messageOtherId,
|
kind: MessageKind.opened,
|
||||||
content: MessageContent(),
|
messageId: messageOtherId,
|
||||||
timestamp: DateTime.now(),
|
content: MessageContent(),
|
||||||
),
|
timestamp: DateTime.now(),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future notifyContactsAboutProfileChange() async {
|
Future notifyContactsAboutProfileChange() async {
|
||||||
|
|
|
||||||
|
|
@ -462,7 +462,8 @@ Future<Uint8List?> getDownloadedMedia(
|
||||||
if (media == null) return null;
|
if (media == null) return null;
|
||||||
|
|
||||||
// await userOpenedOtherMessage(otherUserId, messageOtherId);
|
// await userOpenedOtherMessage(otherUserId, messageOtherId);
|
||||||
notifyContactAboutOpeningMessage(message.contactId, message.messageOtherId!);
|
notifyContactAboutOpeningMessage(
|
||||||
|
message.contactId, [message.messageOtherId!]);
|
||||||
twonlyDatabase.messagesDao.updateMessageByMessageId(
|
twonlyDatabase.messagesDao.updateMessageByMessageId(
|
||||||
message.messageId, MessagesCompanion(openedAt: Value(DateTime.now())));
|
message.messageId, MessagesCompanion(openedAt: Value(DateTime.now())));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,16 @@ import 'package:twonly/src/services/notification_service.dart';
|
||||||
// ignore: library_prefixes
|
// ignore: library_prefixes
|
||||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||||
|
|
||||||
|
bool isBlocked = false;
|
||||||
|
|
||||||
Future handleServerMessage(server.ServerToClient msg) async {
|
Future handleServerMessage(server.ServerToClient msg) async {
|
||||||
client.Response? response;
|
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 {
|
try {
|
||||||
if (msg.v0.hasRequestNewPreKeys()) {
|
if (msg.v0.hasRequestNewPreKeys()) {
|
||||||
|
|
@ -38,12 +46,14 @@ Future handleServerMessage(server.ServerToClient msg) async {
|
||||||
} else {
|
} else {
|
||||||
Logger("handleServerMessage")
|
Logger("handleServerMessage")
|
||||||
.shout("Got a new message from the server: $msg");
|
.shout("Got a new message from the server: $msg");
|
||||||
return;
|
response = client.Response()..error = ErrorCode.InternalError;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
response = client.Response()..error = ErrorCode.InternalError;
|
response = client.Response()..error = ErrorCode.InternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isBlocked = false;
|
||||||
|
|
||||||
var v0 = client.V0()
|
var v0 = client.V0()
|
||||||
..seq = msg.v0.seq
|
..seq = msg.v0.seq
|
||||||
..response = response;
|
..response = response;
|
||||||
|
|
@ -68,17 +78,19 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
|
|
||||||
if (messageId == null) {
|
if (messageId == null) {
|
||||||
Logger("server_messages")
|
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
|
// answers with ok, so the server will delete the message
|
||||||
var ok = client.Response_Ok()..none = true;
|
var ok = client.Response_Ok()..none = true;
|
||||||
return client.Response()..ok = ok;
|
return client.Response()..ok = ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.fin && data.data.isEmpty) {
|
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
|
// media file was deleted by the server. remove the media from device
|
||||||
await twonlyDatabase.messagesDao.deleteMessageById(messageId);
|
await twonlyDatabase.messagesDao.deleteMessageById(messageId);
|
||||||
box.delete(boxId);
|
await box.delete(boxId);
|
||||||
box.delete("${data.downloadToken}_downloaded");
|
await box.delete("${data.downloadToken}_downloaded");
|
||||||
var ok = client.Response_Ok()..none = true;
|
var ok = client.Response_Ok()..none = true;
|
||||||
return client.Response()..ok = ok;
|
return client.Response()..ok = ok;
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +115,7 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
|
|
||||||
if (!data.fin) {
|
if (!data.fin) {
|
||||||
// download not finished, so waiting for more data...
|
// download not finished, so waiting for more data...
|
||||||
box.put(boxId, downloadedBytes);
|
await box.put(boxId, downloadedBytes);
|
||||||
var ok = client.Response_Ok()..none = true;
|
var ok = client.Response_Ok()..none = true;
|
||||||
return client.Response()..ok = ok;
|
return client.Response()..ok = ok;
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +150,7 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
final rawBytes =
|
final rawBytes =
|
||||||
await xchacha20.decrypt(secretBox, secretKey: secretKeyData);
|
await xchacha20.decrypt(secretBox, secretKey: secretKeyData);
|
||||||
|
|
||||||
box.put("${data.downloadToken}_downloaded", rawBytes);
|
await box.put("${data.downloadToken}_downloaded", rawBytes);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger("server_messages").info("Decryption error: $e");
|
Logger("server_messages").info("Decryption error: $e");
|
||||||
// deleting message as this is an invalid image
|
// deleting message as this is an invalid image
|
||||||
|
|
@ -148,13 +160,14 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
return client.Response()..ok = ok;
|
return client.Response()..ok = ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger("server_messages").info("Downloaded: $messageId");
|
||||||
await twonlyDatabase.messagesDao.updateMessageByOtherUser(
|
await twonlyDatabase.messagesDao.updateMessageByOtherUser(
|
||||||
msg.contactId,
|
msg.contactId,
|
||||||
messageId,
|
messageId,
|
||||||
MessagesCompanion(downloadState: Value(DownloadState.downloaded)),
|
MessagesCompanion(downloadState: Value(DownloadState.downloaded)),
|
||||||
);
|
);
|
||||||
|
|
||||||
box.delete(boxId);
|
await box.delete(boxId);
|
||||||
|
|
||||||
var ok = client.Response_Ok()..none = true;
|
var ok = client.Response_Ok()..none = true;
|
||||||
return client.Response()..ok = ok;
|
return client.Response()..ok = ok;
|
||||||
|
|
@ -201,7 +214,6 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
final update = ContactsCompanion(accepted: Value(true));
|
final update = ContactsCompanion(accepted: Value(true));
|
||||||
await twonlyDatabase.contactsDao.updateContact(fromUserId, update);
|
await twonlyDatabase.contactsDao.updateContact(fromUserId, update);
|
||||||
notifyContactsAboutProfileChange();
|
notifyContactsAboutProfileChange();
|
||||||
setupNotificationWithUsers();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageKind.profileChange:
|
case MessageKind.profileChange:
|
||||||
|
|
@ -344,7 +356,6 @@ Future<client.Response> handleContactRequest(
|
||||||
Result username = await apiProvider.getUsername(fromUserId);
|
Result username = await apiProvider.getUsername(fromUserId);
|
||||||
if (username.isSuccess) {
|
if (username.isSuccess) {
|
||||||
Uint8List name = username.value.userdata.username;
|
Uint8List name = username.value.userdata.username;
|
||||||
|
|
||||||
await twonlyDatabase.contactsDao.insertContact(
|
await twonlyDatabase.contactsDao.insertContact(
|
||||||
ContactsCompanion(
|
ContactsCompanion(
|
||||||
username: Value(utf8.decode(name)),
|
username: Value(utf8.decode(name)),
|
||||||
|
|
@ -353,6 +364,7 @@ Future<client.Response> handleContactRequest(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
await setupNotificationWithUsers();
|
||||||
var ok = client.Response_Ok()..none = true;
|
var ok = client.Response_Ok()..none = true;
|
||||||
return client.Response()..ok = ok;
|
return client.Response()..ok = ok;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Future initMediaStorage() async {
|
||||||
value: base64UrlEncode(key),
|
value: base64UrlEncode(key),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final dir = await getApplicationDocumentsDirectory();
|
final dir = await getApplicationSupportDirectory();
|
||||||
Hive.init(dir.path);
|
Hive.init(dir.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ Future setupNotificationWithUsers({bool force = false}) async {
|
||||||
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
sendNewPushKey(contact.userId, pushKey);
|
await sendNewPushKey(contact.userId, pushKey);
|
||||||
pushKeys[contact.userId]!.keys.add(pushKey);
|
pushKeys[contact.userId]!.keys.add(pushKey);
|
||||||
pushKeys[contact.userId]!.displayName = getContactDisplayName(contact);
|
pushKeys[contact.userId]!.displayName = getContactDisplayName(contact);
|
||||||
wasChanged = true;
|
wasChanged = true;
|
||||||
|
|
@ -109,7 +109,7 @@ Future setupNotificationWithUsers({bool force = false}) async {
|
||||||
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
);
|
);
|
||||||
sendNewPushKey(contact.userId, pushKey);
|
await sendNewPushKey(contact.userId, pushKey);
|
||||||
final pushUser = PushUser(
|
final pushUser = PushUser(
|
||||||
displayName: getContactDisplayName(contact),
|
displayName: getContactDisplayName(contact),
|
||||||
keys: [pushKey],
|
keys: [pushKey],
|
||||||
|
|
@ -588,7 +588,7 @@ Future<String?> getAvatarIcon(Contact user) async {
|
||||||
final Uint8List pngBytes = byteData!.buffer.asUint8List();
|
final Uint8List pngBytes = byteData!.buffer.asUint8List();
|
||||||
|
|
||||||
// Get the directory to save the image
|
// Get the directory to save the image
|
||||||
final directory = await getApplicationDocumentsDirectory();
|
final directory = await getApplicationCacheDirectory();
|
||||||
final avatarsDirectory = Directory('${directory.path}/avatars');
|
final avatarsDirectory = Directory('${directory.path}/avatars');
|
||||||
|
|
||||||
// Create the avatars directory if it does not exist
|
// Create the avatars directory if it does not exist
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ extension ShortCutsExtension on BuildContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> writeLogToFile(LogRecord record) async {
|
Future<void> writeLogToFile(LogRecord record) async {
|
||||||
final directory = await getApplicationDocumentsDirectory();
|
final directory = await getApplicationSupportDirectory();
|
||||||
final logFile = File('${directory.path}/app.log');
|
final logFile = File('${directory.path}/app.log');
|
||||||
|
|
||||||
// Prepare the log message
|
// Prepare the log message
|
||||||
|
|
@ -32,7 +32,7 @@ Future<void> writeLogToFile(LogRecord record) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> deleteLogFile() async {
|
Future<bool> deleteLogFile() async {
|
||||||
final directory = await getApplicationDocumentsDirectory();
|
final directory = await getApplicationSupportDirectory();
|
||||||
final logFile = File('${directory.path}/app.log');
|
final logFile = File('${directory.path}/app.log');
|
||||||
|
|
||||||
if (await logFile.exists()) {
|
if (await logFile.exists()) {
|
||||||
|
|
|
||||||
|
|
@ -235,12 +235,14 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
// should be cleared
|
// should be cleared
|
||||||
Map<int, List<Message>> tmpReactionsToMyMessages = {};
|
Map<int, List<Message>> tmpReactionsToMyMessages = {};
|
||||||
Map<int, List<Message>> tmpTeactionsToOtherMessages = {};
|
Map<int, List<Message>> tmpTeactionsToOtherMessages = {};
|
||||||
|
|
||||||
|
List<int> openedMessageOtherIds = [];
|
||||||
for (Message msg in msgs) {
|
for (Message msg in msgs) {
|
||||||
if (msg.kind == MessageKind.textMessage &&
|
if (msg.kind == MessageKind.textMessage &&
|
||||||
msg.messageOtherId != null &&
|
msg.messageOtherId != null &&
|
||||||
msg.openedAt == null) {
|
msg.openedAt == null) {
|
||||||
updated = true;
|
updated = true;
|
||||||
notifyContactAboutOpeningMessage(widget.userid, msg.messageOtherId!);
|
openedMessageOtherIds.add(msg.messageOtherId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.responseToMessageId != null) {
|
if (msg.responseToMessageId != null) {
|
||||||
|
|
@ -261,7 +263,11 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
displayedMessages.add(msg);
|
displayedMessages.add(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (openedMessageOtherIds.isNotEmpty) {
|
||||||
|
notifyContactAboutOpeningMessage(widget.userid, openedMessageOtherIds);
|
||||||
|
}
|
||||||
twonlyDatabase.messagesDao.openedAllNonMediaMessages(widget.userid);
|
twonlyDatabase.messagesDao.openedAllNonMediaMessages(widget.userid);
|
||||||
|
// should be fixed with that
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
// The stream should be get an update, so only update the UI when all are opened
|
// The stream should be get an update, so only update the UI when all are opened
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,19 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
|
|
||||||
_subscription = messages.listen((messages) {
|
_subscription = messages.listen((messages) {
|
||||||
for (Message msg in 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);
|
allMediaFiles.add(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:drift/drift.dart' hide Column;
|
import 'package:drift/drift.dart' hide Column;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:twonly/src/components/alert_dialog.dart';
|
import 'package:twonly/src/components/alert_dialog.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
|
|
@ -122,6 +123,16 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
_addNewUser(context);
|
_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,
|
controller: searchUserName,
|
||||||
decoration:
|
decoration:
|
||||||
getInputDecoration(context.lang.searchUsernameInput),
|
getInputDecoration(context.lang.searchUsernameInput),
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ class DiagnosticsView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _loadLogFile() async {
|
Future<String> _loadLogFile() async {
|
||||||
final directory = await getApplicationDocumentsDirectory();
|
final directory = await getApplicationSupportDirectory();
|
||||||
final logFile = File('${directory.path}/app.log');
|
final logFile = File('${directory.path}/app.log');
|
||||||
|
|
||||||
if (await logFile.exists()) {
|
if (await logFile.exists()) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue