From d6839c62e38b5a65bb14c9567b99efedf92fa24f Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 1 Dec 2021 04:17:48 -0800 Subject: [PATCH] Move Attribute Updates / File Downloading / Contact Requests / Invites to new API --- lib/cwtch/cwtch.dart | 5 ++- lib/cwtch/cwtchNotifier.dart | 66 +++++++++++++++---------------- lib/cwtch/ffi.dart | 18 +++++++++ lib/cwtch/gomobile.dart | 5 +++ lib/models/message.dart | 20 ++++------ lib/views/groupsettingsview.dart | 5 ++- lib/views/messageview.dart | 4 +- lib/widgets/contactrow.dart | 2 + lib/widgets/filebubble.dart | 18 ++++----- lib/widgets/invitationbubble.dart | 10 ++--- 10 files changed, 87 insertions(+), 66 deletions(-) diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index d04fd13b..9e82c817 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -73,8 +73,9 @@ abstract class Cwtch { // ignore: non_constant_identifier_names void SetProfileAttribute(String profile, String key, String val); // ignore: non_constant_identifier_names - void SetConversationAttribute(String profile, int contact, String key, String val); - + void SetConversationAttribute(String profile, int conversation, String key, String val); + // ignore: non_constant_identifier_names + void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val); // ignore: non_constant_identifier_names void LoadServers(String password); // ignore: non_constant_identifier_names diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 274026bf..6adb09b9 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -79,7 +79,6 @@ class CwtchNotifier { } break; case "GroupCreated": - // Retrieve Server Status from Cache... String status = ""; RemoteServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]); @@ -131,19 +130,24 @@ class CwtchNotifier { case "NewMessageFromPeer": notificationManager.notify("New Message From Peer!"); var identifier = int.parse(data["ConversationID"]); - if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != identifier) { - profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.unreadMessages++; - } else { - profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++; - } - profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages++; - profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.now()); - // We only ever see messages from authenticated peers. - // If the contact is marked as offline then override this - can happen when the contact is removed from the front - // end during syncing. - if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.isOnline() == false) { - profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.status = "Authenticated"; + // We might not have received a contact created for this contact yet... + // In that case the **next** event we receive will actually update these values... + if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier) != null) { + if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != identifier) { + profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.unreadMessages++; + } else { + profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++; + } + profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages++; + profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.now()); + + // We only ever see messages from authenticated peers. + // If the contact is marked as offline then override this - can happen when the contact is removed from the front + // end during syncing. + if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.isOnline() == false) { + profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.status = "Authenticated"; + } } break; @@ -206,17 +210,7 @@ class CwtchNotifier { notificationManager.notify("New Message From Group!"); } } else { - // from me (already displayed - do not update counter) - var idx = data["Signature"]; - var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)?.getMessageKey(idx); - if (key == null) break; - try { - var message = Provider.of(key.currentContext!, listen: false); - if (message == null) break; - message.ackd = true; - } catch (e) { - // ignore, we likely have an old key that has been replaced with an actual signature - } + // This is not dealt with by IndexedAcknowledgment } break; case "MessageCounterResync": @@ -242,7 +236,7 @@ class CwtchNotifier { break; case "SendMessageToGroupError": // from me (already displayed - do not update counter) - EnvironmentConfig.debugLog("SendMessageToGroupError"); + EnvironmentConfig.debugLog("SendMessageToGroupError: $data"); var idx = data["Signature"]; var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["GroupID"])!.getMessageKey(idx); if (key == null) break; @@ -289,7 +283,6 @@ class CwtchNotifier { profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]); break; case "NewGroup": - EnvironmentConfig.debugLog("new group"); String invite = data["GroupInvite"].toString(); if (invite.startsWith("torv3")) { String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5))); @@ -303,7 +296,8 @@ class CwtchNotifier { } if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) { - profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["ConversationID"], groupInvite["GroupID"], + var identifier = int.parse(data["ConversationID"]); + profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], identifier, groupInvite["GroupID"], authorization: ContactAuthorization.approved, imagePath: data["PicturePath"], nickname: groupInvite["GroupName"], @@ -311,7 +305,7 @@ class CwtchNotifier { status: status, isGroup: true, lastMessageTime: DateTime.fromMillisecondsSinceEpoch(0))); - profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.fromMillisecondsSinceEpoch(0)); + profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.fromMillisecondsSinceEpoch(0)); } } break; @@ -323,7 +317,7 @@ class CwtchNotifier { break; case "ServerStateChange": // Update the Server Cache - EnvironmentConfig.debugLog("server state changes $data"); + //EnvironmentConfig.debugLog("server state changes $data"); profileCN.getProfile(data["ProfileOnion"])?.updateServerStatusCache(data["GroupServer"], data["ConnectionState"]); profileCN.getProfile(data["ProfileOnion"])?.contactList.contacts.forEach((contact) { if (contact.isGroup == true && contact.server == data["GroupServer"]) { @@ -361,13 +355,17 @@ class CwtchNotifier { } break; case "NewRetValMessageFromPeer": - if (data["Path"] == "name" && data["Data"].toString().trim().length > 0) { - // Update locally on the UI... - if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]) != null) { - profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.nickname = data["Data"]; + if (data["Path"] == "profile.name") { + if (data["Data"].toString().trim().length > 0) { + // Update locally on the UI... + if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]) != null) { + profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"])!.nickname = data["Data"]; + } } + } else if (data['Path'] == "profile.picture") { + // Not yet.. } else { - EnvironmentConfig.debugLog("unhandled peer attribute event: ${data['Path']}"); + EnvironmentConfig.debugLog("unhandled ret val event: ${data['Path']}"); } break; case "ManifestSaved": diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index 23835614..38448abb 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -84,6 +84,9 @@ typedef VoidFromStringIntStringFn = void Function(Pointer, int, int, Point typedef void_from_string_int_string_string_function = Void Function(Pointer, Int32, Int32, Pointer, Int32, Pointer, Int32); typedef VoidFromStringIntStringStringFn = void Function(Pointer, int, int, Pointer, int, Pointer, int); +typedef void_from_string_int_int_int_string_string_function = Void Function(Pointer, Int32, Int32, Int32, Int32, Pointer, Int32, Pointer, Int32); +typedef VoidFromStringIntIntIntStringStringFn = void Function(Pointer, int, int, int, int, Pointer, int, Pointer, int); + typedef void_from_string_int_int_function = Void Function(Pointer, Int32, Int32, Int32); typedef VoidFromStringIntIntFn = void Function(Pointer, int, int, int); @@ -549,6 +552,21 @@ class CwtchFfi implements Cwtch { malloc.free(u4); } + @override + // ignore: non_constant_identifier_names + void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val) { + var setMessageAttribute = library.lookup>("c_SetMessageAttribute"); + // ignore: non_constant_identifier_names + final SetMessageAttribute = setMessageAttribute.asFunction(); + final u1 = profile.toNativeUtf8(); + final u3 = key.toNativeUtf8(); + final u4 = val.toNativeUtf8(); + SetMessageAttribute(u1, u1.length, conversation, channel, message, u3, u3.length, u4, u4.length); + malloc.free(u1); + malloc.free(u3); + malloc.free(u4); + } + @override // ignore: non_constant_identifier_names void LoadServers(String password) { diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index fa39a0be..a2cac25d 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -281,4 +281,9 @@ class CwtchGomobile implements Cwtch { Future GetMessageByContentHash(String profile, int handle, String contentHash) { return cwtchPlatform.invokeMethod("GetMessageByContentHash", {"profile": profile, "contact": handle, "contentHash": contentHash}); } + + @override + void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val) { + cwtchPlatform.invokeMethod("SetMessageAttribute", {"ProfileOnion": profile, "Conversation": conversation, "Channel": channel, "Message": message, "Key": key, "Val": val}); + } } diff --git a/lib/models/message.dart b/lib/models/message.dart index d6bac00f..9789bba9 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -43,7 +43,7 @@ Future messageHandler(BuildContext context, String profileOnion, int co } return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) { - var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, -1, DateTime.now(), "", "", null, 0, false, true); + var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, -1, DateTime.now(), "", "", "", {}, false, true); try { dynamic messageWrapper = jsonDecode(rawMessageEnvelope); // There are 2 conditions in which this error condition can be met: @@ -67,11 +67,11 @@ Future messageHandler(BuildContext context, String profileOnion, int co var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!; var senderHandle = messageWrapper['PeerID']; var senderImage = messageWrapper['ContactImage']; - var flags = int.parse(messageWrapper['Flags'].toString()); + var attributes = messageWrapper['Attributes']; var ackd = messageWrapper['Acknowledged']; var error = messageWrapper['Error'] != null; var signature = messageWrapper['Signature']; - metadata = MessageMetadata(profileOnion, conversationIdentifier, index, messageID, timestamp, senderHandle, senderImage, signature, flags, ackd, error); + metadata = MessageMetadata(profileOnion, conversationIdentifier, index, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error); dynamic message = jsonDecode(messageWrapper['Message']); var content = message['d'] as dynamic; @@ -97,7 +97,7 @@ Future messageHandler(BuildContext context, String profileOnion, int co } }); } catch (e) { - return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, index, -1, DateTime.now(), "", "", null, 0, false, true))); + return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, index, -1, DateTime.now(), "", "", "", {}, false, true))); } } @@ -111,17 +111,13 @@ class MessageMetadata extends ChangeNotifier { final DateTime timestamp; final String senderHandle; final String? senderImage; - int _flags; + final dynamic _attributes; bool _ackd; bool _error; final String? signature; - int get flags => this._flags; - set flags(int newVal) { - this._flags = newVal; - notifyListeners(); - } + dynamic get attributes => this._attributes; bool get ackd => this._ackd; set ackd(bool newVal) { @@ -135,6 +131,6 @@ class MessageMetadata extends ChangeNotifier { notifyListeners(); } - MessageMetadata( - this.profileOnion, this.conversationIdentifier, this.messageIndex, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._flags, this._ackd, this._error); + MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageIndex, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, + this._error); } diff --git a/lib/views/groupsettingsview.dart b/lib/views/groupsettingsview.dart index 45dd0405..463a99b2 100644 --- a/lib/views/groupsettingsview.dart +++ b/lib/views/groupsettingsview.dart @@ -195,10 +195,11 @@ class _GroupSettingsViewState extends State { child: Text(AppLocalizations.of(context)!.yesLeave), onPressed: () { var profileOnion = Provider.of(context, listen: false).profileOnion; - var handle = Provider.of(context, listen: false).identifier; + var identifier = Provider.of(context, listen: false).identifier; // locally update cache... Provider.of(context, listen: false).isArchived = true; - Provider.of(context, listen: false).cwtch.DeleteContact(profileOnion, handle); + Provider.of(context, listen: false).contactList.removeContact(identifier); + Provider.of(context, listen: false).cwtch.DeleteContact(profileOnion, identifier); Future.delayed(Duration(milliseconds: 500), () { Provider.of(context, listen: false).selectedConversation = null; Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 8c34116b..2b5d225d 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -353,7 +353,7 @@ class _MessageViewState extends State { return contact.onion != Provider.of(context).onion; }, onChanged: (newVal) { setState(() { - this.selectedContact = newVal; + this.selectedContact = Provider.of(context).contactList.findContact(newVal)!.identifier; }); })), SizedBox( @@ -362,7 +362,7 @@ class _MessageViewState extends State { ElevatedButton( child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn), onPressed: () { - if (this.selectedContact != "") { + if (this.selectedContact != -1) { this._sendInvitation(); } Navigator.pop(bcontext); diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index c34e4668..d013e28b 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -111,6 +111,8 @@ class _ContactRowState extends State { } void _btnApprove() { + // Update the UI + Provider.of(context, listen: false).authorization = ContactAuthorization.approved; Provider.of(context, listen: false) .cwtch .AcceptContact(Provider.of(context, listen: false).profileOnion, Provider.of(context, listen: false).identifier); diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index bc75a6d9..ea26052a 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:cwtch/config.dart'; import 'package:cwtch/models/message.dart'; import 'package:file_picker_desktop/file_picker_desktop.dart'; import 'package:flutter/cupertino.dart'; @@ -41,7 +42,7 @@ class FileBubbleState extends State { @override Widget build(BuildContext context) { var fromMe = Provider.of(context).senderHandle == Provider.of(context).onion; - var flagStarted = Provider.of(context).flags & 0x02 > 0; + var flagStarted = Provider.of(context).attributes["file-downloaded"] == "true"; var borderRadiousEh = 15.0; var showFileSharing = Provider.of(context).isExperimentEnabled(FileSharingExperiment); var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of(context).timestamp); @@ -146,14 +147,13 @@ class FileBubbleState extends State { String? selectedFileName; File? file; var profileOnion = Provider.of(context, listen: false).onion; - var handle = Provider.of(context, listen: false).senderHandle; - var contact = Provider.of(context, listen: false).onion; - var idx = Provider.of(context, listen: false).messageIndex; + var conversation = Provider.of(context, listen: false).identifier; + var idx = Provider.of(context, listen: false).messageID; if (Platform.isAndroid) { Provider.of(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil()); - //Provider.of(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of(context, listen: false).flags | 0x02); - Provider.of(context, listen: false).flags |= 0x02; + Provider.of(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true"); + //Provider.of(context, listen: false).attributes |= 0x02; ContactInfoState? contact = Provider.of(context).contactList.findContact(Provider.of(context).senderHandle); if (contact != null) { Provider.of(context, listen: false).cwtch.CreateDownloadableFile(profileOnion, contact.identifier, widget.nameSuggestion, widget.fileKey()); @@ -165,11 +165,11 @@ class FileBubbleState extends State { ); if (selectedFileName != null) { file = File(selectedFileName); - print("saving to " + file.path); + EnvironmentConfig.debugLog("saving to " + file.path); var manifestPath = file.path + ".manifest"; Provider.of(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil()); - //Provider.of(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of(context, listen: false).flags | 0x02); - Provider.of(context, listen: false).flags |= 0x02; + Provider.of(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true"); + //Provider.of(context, listen: false).flags |= 0x02; ContactInfoState? contact = Provider.of(context).contactList.findContact(Provider.of(context).senderHandle); if (contact != null) { Provider.of(context, listen: false).cwtch.DownloadFile(profileOnion, contact.identifier, file.path, manifestPath, widget.fileKey()); diff --git a/lib/widgets/invitationbubble.dart b/lib/widgets/invitationbubble.dart index e3452d91..483ed833 100644 --- a/lib/widgets/invitationbubble.dart +++ b/lib/widgets/invitationbubble.dart @@ -39,7 +39,7 @@ class InvitationBubbleState extends State { isAccepted = Provider.of(context).contactList.findContact(widget.inviteTarget) != null; var borderRadiousEh = 15.0; var showGroupInvite = Provider.of(context).isExperimentEnabled(TapirGroupsExperiment); - rejected = Provider.of(context).flags & 0x01 == 0x01; + rejected = Provider.of(context).attributes["rejected-invite"] == "true"; var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of(context).timestamp); // If the sender is not us, then we want to give them a nickname... @@ -128,10 +128,10 @@ class InvitationBubbleState extends State { void _btnReject() { setState(() { var profileOnion = Provider.of(context, listen: false).onion; - var contact = Provider.of(context, listen: false).onion; - var idx = Provider.of(context, listen: false).messageIndex; - //Provider.of(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of(context, listen: false).flags | 0x01); - Provider.of(context, listen: false).flags |= 0x01; + var conversation = Provider.of(context, listen: false).identifier; + var idx = Provider.of(context, listen: false).messageID; + Provider.of(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "rejected-invite", "true"); + //Provider.of(context, listen: false).flags |= 0x01; }); }