From e7b9f5bb9688b394c97d8b08186cbc7dac8a185f Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Tue, 18 Jan 2022 16:26:52 -0500 Subject: [PATCH 1/6] move all classes in model.dart to their own models/X.dart --- lib/models/appstate.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/appstate.dart b/lib/models/appstate.dart index 8386b8dd..39ae8454 100644 --- a/lib/models/appstate.dart +++ b/lib/models/appstate.dart @@ -68,4 +68,4 @@ class AppState extends ChangeNotifier { } bool isLandscape(BuildContext c) => MediaQuery.of(c).size.width > MediaQuery.of(c).size.height; -} +} \ No newline at end of file -- 2.25.1 From d5cb37ed9cb4c27941c7602db526dffc47689622 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Tue, 18 Jan 2022 18:31:10 -0500 Subject: [PATCH 2/6] stub of new cache --- lib/models/contact.dart | 3 +-- lib/models/messagecache.dart | 29 +++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 55076712..5f632df7 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -19,7 +19,6 @@ class ContactInfoState extends ChangeNotifier { late int _totalMessages = 0; late DateTime _lastMessageTime; late Map> keys; - late List messageCache; int _newMarker = 0; DateTime _newMarkerClearAt = DateTime.now(); @@ -198,7 +197,7 @@ class ContactInfoState extends ChangeNotifier { } void updateMessageCache(int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data) { - this.messageCache.insert(0, MessageCache(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto), data)); + this.messageCache.insert(0, MessageInfo(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto), data)); this.totalMessages += 1; } diff --git a/lib/models/messagecache.dart b/lib/models/messagecache.dart index f7fb1085..166d2d1f 100644 --- a/lib/models/messagecache.dart +++ b/lib/models/messagecache.dart @@ -1,7 +1,32 @@ import 'message.dart'; -class MessageCache { +class MessageInfo { final MessageMetadata metadata; final String wrapper; - MessageCache(this.metadata, this.wrapper); + MessageInfo(this.metadata, this.wrapper); } + +class MessageCache { + late Map cache; + late List cacheByIndex; + + MessageCache() { + this.cache = {}; + this.cacheByIndex = List.empty(growable: true); + } + + + void addNew(int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data) { + this.cache[messageID] = MessageInfo(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto), data); + this.cacheByIndex.insert(0, messageID); + } + + void bumpMessageCache() { + this.messageCache.insert(0, null); + this.totalMessages += 1; + } + + void ackCache(int messageID) { + cache[messageID]?.metadata.ackd = true; + } +} \ No newline at end of file -- 2.25.1 From 793b6e2e1ad61b2a43adb192c66161f7b7766aff Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Thu, 20 Jan 2022 09:13:54 -0500 Subject: [PATCH 3/6] message cache expansion: stores all messages fetched, indexed by hash and id where possible --- lib/cwtch/cwtchNotifier.dart | 36 ++----- lib/models/contact.dart | 28 +++-- lib/models/contactlist.dart | 7 +- lib/models/message.dart | 143 +++++++++++++++++++++---- lib/models/messagecache.dart | 40 ++++++- lib/models/messages/quotedmessage.dart | 43 +------- lib/models/profile.dart | 1 - lib/views/messageview.dart | 7 +- lib/widgets/messagelist.dart | 2 +- lib/widgets/messagerow.dart | 4 +- 10 files changed, 202 insertions(+), 109 deletions(-) diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index ef270f90..74f395ab 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -143,25 +143,10 @@ class CwtchNotifier { var senderHandle = data['RemotePeer']; var senderImage = data['Picture']; var isAuto = data['Auto'] == "true"; + String? contenthash = data['ContentHash']; + var selectedConversation = appState.selectedProfile == data["ProfileOnion"] && appState.selectedConversation == identifier; - // 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.updateLastMessageTime(identifier, DateTime.now()); - profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data["Data"]); - - // 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"; - } - } + profileCN.getProfile(data["ProfileOnion"])?.contactList.newMessage(identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data["Data"], contenthash, selectedConversation, ); break; case "PeerAcknowledgement": @@ -200,18 +185,12 @@ class CwtchNotifier { var timestampSent = DateTime.tryParse(data['TimestampSent'])!; var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages; var isAuto = data['Auto'] == "true"; + String? contenthash = data['ContentHash']; + var selectedConversation = appState.selectedProfile == data["ProfileOnion"] && appState.selectedConversation == identifier; + // Only bother to do anything if we know about the group and the provided index is greater than our current total... if (currentTotal != null && idx >= currentTotal) { - profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, idx, timestampSent, senderHandle, senderImage, isAuto, data["Data"]); - - //if not currently open - 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++; - } - // TODO: There are 2 timestamps associated with a new group message - time sent and time received. // Sent refers to the time a profile alleges they sent a message // Received refers to the time we actually saw the message from the server @@ -222,7 +201,8 @@ class CwtchNotifier { // For now we perform some minimal checks on the sent timestamp to use to provide a useful ordering for honest contacts // and ensure that malicious contacts in groups can only set this timestamp to a value within the range of `last seen message time` // and `local now`. - profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, timestampSent.toLocal()); + profileCN.getProfile(data["ProfileOnion"])?.contactList.newMessage(identifier, idx, timestampSent, senderHandle, senderImage, isAuto, data["Data"], contenthash, selectedConversation); + notificationManager.notify("New Message From Group!"); } } else { diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 5f632df7..3793fb91 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -21,6 +21,8 @@ class ContactInfoState extends ChangeNotifier { late Map> keys; int _newMarker = 0; DateTime _newMarkerClearAt = DateTime.now(); + //late List messageCache; + late MessageCache messageCache; // todo: a nicer way to model contacts, groups and other "entities" late bool _isGroup; @@ -54,7 +56,8 @@ class ContactInfoState extends ChangeNotifier { this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime; this._server = server; this._archived = archived; - this.messageCache = List.empty(growable: true); + //this.messageCache = List.empty(growable: true); + this.messageCache = new MessageCache(); keys = Map>(); } @@ -196,18 +199,31 @@ class ContactInfoState extends ChangeNotifier { return ret; } - void updateMessageCache(int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data) { - this.messageCache.insert(0, MessageInfo(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto), data)); + void newMessage(int identifier, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data, String? contenthash, bool selectedConversation) { + if (!selectedConversation) { + unreadMessages++; + } else { + newMarker++; + } + + this.messageCache.addNew(profileOnion, identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash); this.totalMessages += 1; + + // 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 (isOnline() == false) { + status = "Authenticated"; + } } void bumpMessageCache() { - this.messageCache.insert(0, null); + this.messageCache.bumpMessageCache(); this.totalMessages += 1; } void ackCache(int messageID) { - this.messageCache.firstWhere((element) => element?.metadata.messageID == messageID)?.metadata.ackd = true; + this.messageCache.ackCache(messageID); notifyListeners(); } -} +} \ No newline at end of file diff --git a/lib/models/contactlist.dart b/lib/models/contactlist.dart index a00121d9..d54dfdf3 100644 --- a/lib/models/contactlist.dart +++ b/lib/models/contactlist.dart @@ -122,4 +122,9 @@ class ContactListState extends ChangeNotifier { int idx = _contacts.indexWhere((element) => element.onion == byHandle); return idx >= 0 ? _contacts[idx] : null; } -} + + void newMessage(int identifier, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data, String? contenthash, bool selectedConversation) { + getContact(identifier)?.newMessage(identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash, selectedConversation); + updateLastMessageTime(identifier, DateTime.now()); + } +} \ No newline at end of file diff --git a/lib/models/message.dart b/lib/models/message.dart index 91701cc8..82fa7cd2 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -1,9 +1,11 @@ import 'dart:convert'; import 'package:cwtch/config.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; import '../main.dart'; +import 'messagecache.dart'; import 'messages/filemessage.dart'; import 'messages/invitemessage.dart'; import 'messages/malformedmessage.dart'; @@ -28,7 +30,9 @@ const GroupConversationHandleLength = 32; abstract class Message { MessageMetadata getMetadata(); + Widget getWidget(BuildContext context, Key key); + Widget getPreviewWidget(BuildContext context); } @@ -57,48 +61,108 @@ Message compileOverlay(MessageMetadata metadata, String messageData) { } } -Future messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, int index, {bool byID = false}) { +Future messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, + {bool byIndex = false, int? index, bool byID = false, int? id, bool byHash = false, String? hash}) { + var malformedMetadata = MessageMetadata(profileOnion, conversationIdentifier, 0, DateTime.now(), "", "", "", {}, false, true, false); + if (!byIndex && !byID && !byHash) { + EnvironmentConfig.debugLog("Error calling messageHandler: one of byIndex, byID, byHash must be set"); + return Future.value(MalformedMessage(malformedMetadata)); + } + if ((byID && id == null) || (byIndex && index == null) || (byHash && hash == null)) { + EnvironmentConfig.debugLog("Error calling messageHandler: byType needs corresponding value and it was not set"); + return Future.value(MalformedMessage(malformedMetadata)); + } + + // Hit cache + MessageInfo? messageInfo = getMessageInfoFromCache(context, profileOnion, conversationIdentifier, byIndex: byIndex, index: index, byID: byID, id: id, byHash: byHash, hash: hash); + if (messageInfo != null) { + return Future.value(compileOverlay(messageInfo.metadata, messageInfo.wrapper)); + } + + // Fetch and Cache + var messageInfoFuture = fetchAndCacheMessageInfo(context, profileOnion, conversationIdentifier, byIndex: byIndex, index: index, byID: byID, id: id, byHash: byHash, hash: hash); + return messageInfoFuture.then( (MessageInfo? messageInfo) { + if (messageInfo != null) { + return compileOverlay(messageInfo.metadata, messageInfo.wrapper); + } else { + return MalformedMessage(malformedMetadata); + } + }); +} + +MessageInfo? getMessageInfoFromCache(BuildContext context, String profileOnion, int conversationIdentifier, +{bool byIndex = false, int? index, bool byID = false, int? id, bool byHash = false, String? hash}) { + // Hit cache try { var cache = Provider.of(context, listen: false).contactList.getContact(conversationIdentifier)?.messageCache; - if (cache != null && cache.length > index) { - if (cache[index] != null) { - return Future.value(compileOverlay(cache[index]!.metadata, cache[index]!.wrapper)); + if (cache != null) { + MessageInfo? messageInfo = null; + if (byID) { + messageInfo = cache.getById(id!); + } else if (byHash) { + messageInfo = cache.getByContentHash(hash!); + } else { + messageInfo = cache.getByIndex(index!); + } + if (messageInfo != null) { + return messageInfo; } } } catch (e) { + EnvironmentConfig.debugLog("message handler exception on get from cache: $e"); // provider check failed...make an expensive call... } + return null; +} +Future fetchAndCacheMessageInfo(BuildContext context, String profileOnion, int conversationIdentifier, +{bool byIndex = false, int? index, bool byID = false, int? id, bool byHash = false, String? hash}) { +// Load and cache try { Future rawMessageEnvelopeFuture; if (byID) { - rawMessageEnvelopeFuture = Provider.of(context, listen: false).cwtch.GetMessageByID(profileOnion, conversationIdentifier, index); + rawMessageEnvelopeFuture = Provider + .of(context, listen: false) + .cwtch + .GetMessageByID(profileOnion, conversationIdentifier, id!); + } else if (byHash) { + rawMessageEnvelopeFuture = Provider + .of(context, listen: false) + .cwtch + .GetMessageByContentHash(profileOnion, conversationIdentifier, hash!); } else { - rawMessageEnvelopeFuture = Provider.of(context, listen: false).cwtch.GetMessage(profileOnion, conversationIdentifier, index); + rawMessageEnvelopeFuture = Provider + .of(context, listen: false) + .cwtch + .GetMessage(profileOnion, conversationIdentifier, index!); } return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) { - var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, DateTime.now(), "", "", "", {}, false, true, false); try { dynamic messageWrapper = jsonDecode(rawMessageEnvelope); - // There are 2 conditions in which this error condition can be met: - // 1. The application == nil, in which case this instance of the UI is already - // broken beyond repair, and will either be replaced by a new version, or requires a complete - // restart. - // 2. This index was incremented and we happened to fetch the timeline prior to the messages inclusion. - // This should be rare as Timeline addition/fetching is mutex protected and Dart itself will pipeline the - // calls to libCwtch-go - however because we use goroutines on the backend there is always a chance that one - // will find itself delayed. - // The second case is recoverable by tail-recursing this future. +// There are 2 conditions in which this error condition can be met: +// 1. The application == nil, in which case this instance of the UI is already +// broken beyond repair, and will either be replaced by a new version, or requires a complete +// restart. +// 2. This index was incremented and we happened to fetch the timeline prior to the messages inclusion. +// This should be rare as Timeline addition/fetching is mutex protected and Dart itself will pipeline the +// calls to libCwtch-go - however because we use goroutines on the backend there is always a chance that one +// will find itself delayed. +// The second case is recoverable by tail-recursing this future. if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') { return Future.delayed(Duration(seconds: 2), () { print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug."); - return messageHandler(context, profileOnion, conversationIdentifier, -1, byID: byID).then((value) => value); + return fetchAndCacheMessageInfo(context, profileOnion, conversationIdentifier, byIndex: byIndex, + index: index, + byID: byID, + id: id, + byHash: byHash, + hash: hash).then((value) => value); }); } - // Construct the initial metadata +// Construct the initial metadata var messageID = messageWrapper['ID']; var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!; var senderHandle = messageWrapper['PeerID']; @@ -107,16 +171,47 @@ Future messageHandler(BuildContext context, String profileOnion, int co var ackd = messageWrapper['Acknowledged']; var error = messageWrapper['Error'] != null; var signature = messageWrapper['Signature']; - metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error, false); + var contenthash = messageWrapper['ContentHash']; + var localIndex = messageWrapper['LocalIndex']; + var metadata = MessageMetadata( + profileOnion, + conversationIdentifier, + messageID, + timestamp, + senderHandle, + senderImage, + signature, + attributes, + ackd, + error, + false); + var messageInfo = new MessageInfo(metadata, messageWrapper['Message']); - return compileOverlay(metadata, messageWrapper['Message']); + var cache = Provider + .of(context, listen: false) + .contactList + .getContact(conversationIdentifier) + ?.messageCache; + + if (cache != null) { + if (byID) { + cache.addUnindexed(messageInfo, contenthash); + } else if (byHash) { + cache.addUnindexed(messageInfo, contenthash); + } else { + cache.add(messageInfo, index!, contenthash); + } + } + + return messageInfo; } catch (e) { - EnvironmentConfig.debugLog("an error! " + e.toString()); - return MalformedMessage(metadata); + EnvironmentConfig.debugLog("message handler exception on parse message and cache: " + e.toString()); + return null; } }); } catch (e) { - return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, -1, DateTime.now(), "", "", "", {}, false, true, false))); + EnvironmentConfig.debugLog("message handler exeption on get message: $e"); + return Future.value(null); } } @@ -139,12 +234,14 @@ class MessageMetadata extends ChangeNotifier { dynamic get attributes => this._attributes; bool get ackd => this._ackd; + set ackd(bool newVal) { this._ackd = newVal; notifyListeners(); } bool get error => this._error; + set error(bool newVal) { this._error = newVal; notifyListeners(); diff --git a/lib/models/messagecache.dart b/lib/models/messagecache.dart index 166d2d1f..3c11e526 100644 --- a/lib/models/messagecache.dart +++ b/lib/models/messagecache.dart @@ -9,21 +9,51 @@ class MessageInfo { class MessageCache { late Map cache; late List cacheByIndex; + late Map cacheByHash; MessageCache() { - this.cache = {}; - this.cacheByIndex = List.empty(growable: true); + cache = {}; + cacheByIndex = List.empty(growable: true); + cacheByHash = {}; } + int get indexedLength => cacheByIndex.length; - void addNew(int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data) { + MessageInfo? getById(int id) => cache[id]; + MessageInfo? getByIndex(int index) { + if (index >= cacheByIndex.length) { + return null; + } + return cache[cacheByIndex[index]]; + } + MessageInfo? getByContentHash(String contenthash) => cache[cacheByHash[contenthash]]; + + void addNew(String profileOnion, int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data, String? contenthash) { this.cache[messageID] = MessageInfo(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto), data); this.cacheByIndex.insert(0, messageID); + if (contenthash != null && contenthash != "") { + this.cacheByHash[contenthash] = messageID; + } } + void add(MessageInfo messageInfo, int index, String? contenthash) { + this.cache[messageInfo.metadata.messageID] = messageInfo; + this.cacheByIndex.insert(index, messageInfo.metadata.messageID); + if (contenthash != null && contenthash != "") { + this.cacheByHash[contenthash] = messageInfo.metadata.messageID; + } + } + + void addUnindexed(MessageInfo messageInfo, String? contenthash) { + this.cache[messageInfo.metadata.messageID] = messageInfo; + if (contenthash != null && contenthash != "") { + this.cacheByHash[contenthash] = messageInfo.metadata.messageID; + } + } + + // TODO inserting nulls travel down list causing fails for all void bumpMessageCache() { - this.messageCache.insert(0, null); - this.totalMessages += 1; + this.cacheByIndex.insert(0, null); } void ackCache(int messageID) { diff --git a/lib/models/messages/quotedmessage.dart b/lib/models/messages/quotedmessage.dart index 7d34c1b5..68615f18 100644 --- a/lib/models/messages/quotedmessage.dart +++ b/lib/models/messages/quotedmessage.dart @@ -9,6 +9,8 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; import '../../main.dart'; +import '../messagecache.dart'; +import '../profile.dart'; class QuotedMessageStructure { final String quotedHash; @@ -21,22 +23,6 @@ class QuotedMessageStructure { }; } -class LocallyIndexedMessage { - final dynamic message; - final int index; - - LocallyIndexedMessage(this.message, this.index); - - LocallyIndexedMessage.fromJson(Map json) - : message = json['Message'], - index = json['LocalIndex']; - - Map toJson() => { - 'Message': message, - 'LocalIndex': index, - }; -} - class QuotedMessage extends Message { final MessageMetadata metadata; final String content; @@ -70,34 +56,11 @@ class QuotedMessage extends Message { return MalformedBubble(); } - var quotedMessagePotentials = Provider.of(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.conversationIdentifier, message["quotedHash"]); - Future quotedMessage = quotedMessagePotentials.then((matchingMessages) { - if (matchingMessages == "[]") { - return null; - } - // reverse order the messages from newest to oldest and return the - // first matching message where it's index is less than the index of this - // message - try { - var list = (jsonDecode(matchingMessages) as List).map((data) => LocallyIndexedMessage.fromJson(data)).toList(); - LocallyIndexedMessage candidate = list.reversed.first; - return candidate; - } catch (e) { - // Malformed Message will be returned... - return null; - } - }); - return ChangeNotifierProvider.value( value: this.metadata, builder: (bcontext, child) { return MessageRow( - QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) { - if (localIndex != null) { - return messageHandler(bcontext, metadata.profileOnion, metadata.conversationIdentifier, localIndex.index); - } - return MalformedMessage(this.metadata); - })), + QuotedMessageBubble(message["body"], messageHandler(bcontext, metadata.profileOnion, metadata.conversationIdentifier, byHash: true, hash: message["quotedHash"])), key: key); }); } catch (e) { diff --git a/lib/models/profile.dart b/lib/models/profile.dart index a9191653..ddca5aba 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -132,7 +132,6 @@ class ProfileInfoState extends ChangeNotifier { @override void dispose() { super.dispose(); - print("profileinfostate.dispose()"); } void updateFrom(String onion, String name, String picture, String contactsJson, String serverJson, bool online) { diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 877addb7..e2a22ba6 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -225,7 +225,10 @@ class _MessageViewState extends State { ctrlrCompose.clear(); focusNode.requestFocus(); Future.delayed(const Duration(milliseconds: 80), () { - Provider.of(context, listen: false).contactList.getContact(Provider.of(context, listen: false).identifier)?.bumpMessageCache(); + var profile = Provider.of(context, listen: false).profileOnion; + var identifier = Provider.of(context, listen: false).identifier; + //Provider.of(context, listen: false).contactList.getContact(Provider.of(context, listen: false).identifier)?.bumpMessageCache(); + fetchAndCacheMessageInfo(context, profile, identifier, byIndex: true, index: 0); Provider.of(context, listen: false).newMarker++; // Resort the contact list... Provider.of(context, listen: false).contactList.updateLastMessageTime(Provider.of(context, listen: false).identifier, DateTime.now()); @@ -282,7 +285,7 @@ class _MessageViewState extends State { if (Provider.of(context).selectedConversation != null && Provider.of(context).selectedIndex != null) { var quoted = FutureBuilder( future: - messageHandler(context, Provider.of(context).selectedProfile!, Provider.of(context).selectedConversation!, Provider.of(context).selectedIndex!, byID: true), + messageHandler(context, Provider.of(context).selectedProfile!, Provider.of(context).selectedConversation!, id: Provider.of(context).selectedIndex!, byID: true), builder: (context, snapshot) { if (snapshot.hasData) { var message = snapshot.data! as Message; diff --git a/lib/widgets/messagelist.dart b/lib/widgets/messagelist.dart index aa29baf2..4756381d 100644 --- a/lib/widgets/messagelist.dart +++ b/lib/widgets/messagelist.dart @@ -83,7 +83,7 @@ class _MessageListState extends State { var messageIndex = index; return FutureBuilder( - future: messageHandler(outerContext, profileOnion, contactHandle, messageIndex), + future: messageHandler(outerContext, profileOnion, contactHandle, byIndex: true, index: messageIndex), builder: (context, snapshot) { if (snapshot.hasData) { var message = snapshot.data as Message; diff --git a/lib/widgets/messagerow.dart b/lib/widgets/messagerow.dart index 8661305d..f7b112fc 100644 --- a/lib/widgets/messagerow.dart +++ b/lib/widgets/messagerow.dart @@ -220,8 +220,8 @@ class MessageRowState extends State with SingleTickerProviderStateMi ))))); var mark = Provider.of(context).newMarker; if (mark > 0 && - Provider.of(context).messageCache.length > mark && - Provider.of(context).messageCache[mark - 1]?.metadata.messageID == Provider.of(context).messageID) { + Provider.of(context).messageCache.indexedLength > mark && + Provider.of(context).messageCache.getByIndex(mark - 1)?.metadata.messageID == Provider.of(context).messageID) { return Column(crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [Align(alignment: Alignment.center, child: _bubbleNew()), mr]); } else { return mr; -- 2.25.1 From 589bc4c36cde956df74b333002f532a209ff1d11 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Thu, 20 Jan 2022 13:05:11 -0500 Subject: [PATCH 4/6] new lcg; cleanup --- LIBCWTCH-GO-MACOS.version | 2 +- LIBCWTCH-GO.version | 2 +- lib/models/contact.dart | 16 ++++++++-------- lib/models/message.dart | 18 +++++++++--------- lib/models/messagecache.dart | 5 ----- lib/views/messageview.dart | 2 +- 6 files changed, 20 insertions(+), 25 deletions(-) diff --git a/LIBCWTCH-GO-MACOS.version b/LIBCWTCH-GO-MACOS.version index c6c568fa..c0ed5239 100644 --- a/LIBCWTCH-GO-MACOS.version +++ b/LIBCWTCH-GO-MACOS.version @@ -1 +1 @@ -2022-01-19-16-16-v1.5.4-11-g84d451f \ No newline at end of file +2022-01-20-12-53-v1.5.4-14-g6865ec1 \ No newline at end of file diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version index 8c390298..ee031103 100644 --- a/LIBCWTCH-GO.version +++ b/LIBCWTCH-GO.version @@ -1 +1 @@ -2022-01-19-21-15-v1.5.4-11-g84d451f \ No newline at end of file +2022-01-20-17-53-v1.5.4-14-g6865ec1 \ No newline at end of file diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 3793fb91..6e432f1a 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -21,7 +21,6 @@ class ContactInfoState extends ChangeNotifier { late Map> keys; int _newMarker = 0; DateTime _newMarkerClearAt = DateTime.now(); - //late List messageCache; late MessageCache messageCache; // todo: a nicer way to model contacts, groups and other "entities" @@ -56,7 +55,6 @@ class ContactInfoState extends ChangeNotifier { this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime; this._server = server; this._archived = archived; - //this.messageCache = List.empty(growable: true); this.messageCache = new MessageCache(); keys = Map>(); } @@ -66,6 +64,7 @@ class ContactInfoState extends ChangeNotifier { String get savePeerHistory => this._savePeerHistory; String? get acnCircuit => this._acnCircuit; + set acnCircuit(String? acnCircuit) { this._acnCircuit = acnCircuit; notifyListeners(); @@ -92,6 +91,7 @@ class ContactInfoState extends ChangeNotifier { } bool get isGroup => this._isGroup; + set isGroup(bool newVal) { this._isGroup = newVal; notifyListeners(); @@ -112,12 +112,14 @@ class ContactInfoState extends ChangeNotifier { } String get status => this._status; + set status(String newVal) { this._status = newVal; notifyListeners(); } int get unreadMessages => this._unreadMessages; + set unreadMessages(int newVal) { // don't reset newMarker position when unreadMessages is being cleared if (newVal > 0) { @@ -151,18 +153,21 @@ class ContactInfoState extends ChangeNotifier { } int get totalMessages => this._totalMessages; + set totalMessages(int newVal) { this._totalMessages = newVal; notifyListeners(); } String get imagePath => this._imagePath; + set imagePath(String newVal) { this._imagePath = newVal; notifyListeners(); } DateTime get lastMessageTime => this._lastMessageTime; + set lastMessageTime(DateTime newVal) { this._lastMessageTime = newVal; notifyListeners(); @@ -217,13 +222,8 @@ class ContactInfoState extends ChangeNotifier { } } - void bumpMessageCache() { - this.messageCache.bumpMessageCache(); - this.totalMessages += 1; - } - void ackCache(int messageID) { this.messageCache.ackCache(messageID); notifyListeners(); } -} \ No newline at end of file +} diff --git a/lib/models/message.dart b/lib/models/message.dart index 82fa7cd2..5ccfec23 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -141,15 +141,15 @@ Future fetchAndCacheMessageInfo(BuildContext context, String profi return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) { try { dynamic messageWrapper = jsonDecode(rawMessageEnvelope); -// There are 2 conditions in which this error condition can be met: -// 1. The application == nil, in which case this instance of the UI is already -// broken beyond repair, and will either be replaced by a new version, or requires a complete -// restart. -// 2. This index was incremented and we happened to fetch the timeline prior to the messages inclusion. -// This should be rare as Timeline addition/fetching is mutex protected and Dart itself will pipeline the -// calls to libCwtch-go - however because we use goroutines on the backend there is always a chance that one -// will find itself delayed. -// The second case is recoverable by tail-recursing this future. + // There are 2 conditions in which this error condition can be met: + // 1. The application == nil, in which case this instance of the UI is already + // broken beyond repair, and will either be replaced by a new version, or requires a complete + // restart. + // 2. This index was incremented and we happened to fetch the timeline prior to the messages inclusion. + // This should be rare as Timeline addition/fetching is mutex protected and Dart itself will pipeline the + // calls to libCwtch-go - however because we use goroutines on the backend there is always a chance that one + // will find itself delayed. + // The second case is recoverable by tail-recursing this future. if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') { return Future.delayed(Duration(seconds: 2), () { print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug."); diff --git a/lib/models/messagecache.dart b/lib/models/messagecache.dart index 3c11e526..d8eaa999 100644 --- a/lib/models/messagecache.dart +++ b/lib/models/messagecache.dart @@ -51,11 +51,6 @@ class MessageCache { } } - // TODO inserting nulls travel down list causing fails for all - void bumpMessageCache() { - this.cacheByIndex.insert(0, null); - } - void ackCache(int messageID) { cache[messageID]?.metadata.ackd = true; } diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index e2a22ba6..d1768161 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -227,9 +227,9 @@ class _MessageViewState extends State { Future.delayed(const Duration(milliseconds: 80), () { var profile = Provider.of(context, listen: false).profileOnion; var identifier = Provider.of(context, listen: false).identifier; - //Provider.of(context, listen: false).contactList.getContact(Provider.of(context, listen: false).identifier)?.bumpMessageCache(); fetchAndCacheMessageInfo(context, profile, identifier, byIndex: true, index: 0); Provider.of(context, listen: false).newMarker++; + Provider.of(context, listen: false).totalMessages += 1; // Resort the contact list... Provider.of(context, listen: false).contactList.updateLastMessageTime(Provider.of(context, listen: false).identifier, DateTime.now()); }); -- 2.25.1 From 889d398343bd26e43f27d9ac55419fe1c2c34208 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Thu, 20 Jan 2022 13:37:09 -0500 Subject: [PATCH 5/6] add notifyListen to newMessage in contact; format --- lib/cwtch/cwtchNotifier.dart | 13 ++++++- lib/models/appstate.dart | 2 +- lib/models/contact.dart | 1 + lib/models/contactlist.dart | 2 +- lib/models/message.dart | 51 ++++++-------------------- lib/models/messagecache.dart | 3 +- lib/models/messages/quotedmessage.dart | 3 +- lib/views/messageview.dart | 4 +- 8 files changed, 30 insertions(+), 49 deletions(-) diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 74f395ab..ea93c064 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -146,7 +146,17 @@ class CwtchNotifier { String? contenthash = data['ContentHash']; var selectedConversation = appState.selectedProfile == data["ProfileOnion"] && appState.selectedConversation == identifier; - profileCN.getProfile(data["ProfileOnion"])?.contactList.newMessage(identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data["Data"], contenthash, selectedConversation, ); + profileCN.getProfile(data["ProfileOnion"])?.contactList.newMessage( + identifier, + messageID, + timestamp, + senderHandle, + senderImage, + isAuto, + data["Data"], + contenthash, + selectedConversation, + ); break; case "PeerAcknowledgement": @@ -188,7 +198,6 @@ class CwtchNotifier { String? contenthash = data['ContentHash']; var selectedConversation = appState.selectedProfile == data["ProfileOnion"] && appState.selectedConversation == identifier; - // Only bother to do anything if we know about the group and the provided index is greater than our current total... if (currentTotal != null && idx >= currentTotal) { // TODO: There are 2 timestamps associated with a new group message - time sent and time received. diff --git a/lib/models/appstate.dart b/lib/models/appstate.dart index 39ae8454..8386b8dd 100644 --- a/lib/models/appstate.dart +++ b/lib/models/appstate.dart @@ -68,4 +68,4 @@ class AppState extends ChangeNotifier { } bool isLandscape(BuildContext c) => MediaQuery.of(c).size.width > MediaQuery.of(c).size.height; -} \ No newline at end of file +} diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 6e432f1a..d39aa193 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -220,6 +220,7 @@ class ContactInfoState extends ChangeNotifier { if (isOnline() == false) { status = "Authenticated"; } + notifyListeners(); } void ackCache(int messageID) { diff --git a/lib/models/contactlist.dart b/lib/models/contactlist.dart index d54dfdf3..f4aaadcf 100644 --- a/lib/models/contactlist.dart +++ b/lib/models/contactlist.dart @@ -127,4 +127,4 @@ class ContactListState extends ChangeNotifier { getContact(identifier)?.newMessage(identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash, selectedConversation); updateLastMessageTime(identifier, DateTime.now()); } -} \ No newline at end of file +} diff --git a/lib/models/message.dart b/lib/models/message.dart index 5ccfec23..52070666 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -74,14 +74,14 @@ Future messageHandler(BuildContext context, String profileOnion, int co } // Hit cache - MessageInfo? messageInfo = getMessageInfoFromCache(context, profileOnion, conversationIdentifier, byIndex: byIndex, index: index, byID: byID, id: id, byHash: byHash, hash: hash); + MessageInfo? messageInfo = getMessageInfoFromCache(context, profileOnion, conversationIdentifier, byIndex: byIndex, index: index, byID: byID, id: id, byHash: byHash, hash: hash); if (messageInfo != null) { return Future.value(compileOverlay(messageInfo.metadata, messageInfo.wrapper)); } // Fetch and Cache - var messageInfoFuture = fetchAndCacheMessageInfo(context, profileOnion, conversationIdentifier, byIndex: byIndex, index: index, byID: byID, id: id, byHash: byHash, hash: hash); - return messageInfoFuture.then( (MessageInfo? messageInfo) { + var messageInfoFuture = fetchAndCacheMessageInfo(context, profileOnion, conversationIdentifier, byIndex: byIndex, index: index, byID: byID, id: id, byHash: byHash, hash: hash); + return messageInfoFuture.then((MessageInfo? messageInfo) { if (messageInfo != null) { return compileOverlay(messageInfo.metadata, messageInfo.wrapper); } else { @@ -91,7 +91,7 @@ Future messageHandler(BuildContext context, String profileOnion, int co } MessageInfo? getMessageInfoFromCache(BuildContext context, String profileOnion, int conversationIdentifier, -{bool byIndex = false, int? index, bool byID = false, int? id, bool byHash = false, String? hash}) { + {bool byIndex = false, int? index, bool byID = false, int? id, bool byHash = false, String? hash}) { // Hit cache try { var cache = Provider.of(context, listen: false).contactList.getContact(conversationIdentifier)?.messageCache; @@ -116,26 +116,17 @@ MessageInfo? getMessageInfoFromCache(BuildContext context, String profileOnion, } Future fetchAndCacheMessageInfo(BuildContext context, String profileOnion, int conversationIdentifier, -{bool byIndex = false, int? index, bool byID = false, int? id, bool byHash = false, String? hash}) { + {bool byIndex = false, int? index, bool byID = false, int? id, bool byHash = false, String? hash}) { // Load and cache try { Future rawMessageEnvelopeFuture; if (byID) { - rawMessageEnvelopeFuture = Provider - .of(context, listen: false) - .cwtch - .GetMessageByID(profileOnion, conversationIdentifier, id!); + rawMessageEnvelopeFuture = Provider.of(context, listen: false).cwtch.GetMessageByID(profileOnion, conversationIdentifier, id!); } else if (byHash) { - rawMessageEnvelopeFuture = Provider - .of(context, listen: false) - .cwtch - .GetMessageByContentHash(profileOnion, conversationIdentifier, hash!); + rawMessageEnvelopeFuture = Provider.of(context, listen: false).cwtch.GetMessageByContentHash(profileOnion, conversationIdentifier, hash!); } else { - rawMessageEnvelopeFuture = Provider - .of(context, listen: false) - .cwtch - .GetMessage(profileOnion, conversationIdentifier, index!); + rawMessageEnvelopeFuture = Provider.of(context, listen: false).cwtch.GetMessage(profileOnion, conversationIdentifier, index!); } return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) { @@ -153,12 +144,7 @@ Future fetchAndCacheMessageInfo(BuildContext context, String profi if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') { return Future.delayed(Duration(seconds: 2), () { print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug."); - return fetchAndCacheMessageInfo(context, profileOnion, conversationIdentifier, byIndex: byIndex, - index: index, - byID: byID, - id: id, - byHash: byHash, - hash: hash).then((value) => value); + return fetchAndCacheMessageInfo(context, profileOnion, conversationIdentifier, byIndex: byIndex, index: index, byID: byID, id: id, byHash: byHash, hash: hash).then((value) => value); }); } @@ -173,25 +159,10 @@ Future fetchAndCacheMessageInfo(BuildContext context, String profi var signature = messageWrapper['Signature']; var contenthash = messageWrapper['ContentHash']; var localIndex = messageWrapper['LocalIndex']; - var metadata = MessageMetadata( - profileOnion, - conversationIdentifier, - messageID, - timestamp, - senderHandle, - senderImage, - signature, - attributes, - ackd, - error, - false); + var metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error, false); var messageInfo = new MessageInfo(metadata, messageWrapper['Message']); - var cache = Provider - .of(context, listen: false) - .contactList - .getContact(conversationIdentifier) - ?.messageCache; + var cache = Provider.of(context, listen: false).contactList.getContact(conversationIdentifier)?.messageCache; if (cache != null) { if (byID) { diff --git a/lib/models/messagecache.dart b/lib/models/messagecache.dart index d8eaa999..a2deae4e 100644 --- a/lib/models/messagecache.dart +++ b/lib/models/messagecache.dart @@ -26,6 +26,7 @@ class MessageCache { } return cache[cacheByIndex[index]]; } + MessageInfo? getByContentHash(String contenthash) => cache[cacheByHash[contenthash]]; void addNew(String profileOnion, int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data, String? contenthash) { @@ -54,4 +55,4 @@ class MessageCache { void ackCache(int messageID) { cache[messageID]?.metadata.ackd = true; } -} \ No newline at end of file +} diff --git a/lib/models/messages/quotedmessage.dart b/lib/models/messages/quotedmessage.dart index 68615f18..7f5053d9 100644 --- a/lib/models/messages/quotedmessage.dart +++ b/lib/models/messages/quotedmessage.dart @@ -59,8 +59,7 @@ class QuotedMessage extends Message { return ChangeNotifierProvider.value( value: this.metadata, builder: (bcontext, child) { - return MessageRow( - QuotedMessageBubble(message["body"], messageHandler(bcontext, metadata.profileOnion, metadata.conversationIdentifier, byHash: true, hash: message["quotedHash"])), + return MessageRow(QuotedMessageBubble(message["body"], messageHandler(bcontext, metadata.profileOnion, metadata.conversationIdentifier, byHash: true, hash: message["quotedHash"])), key: key); }); } catch (e) { diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index d1768161..bc811f67 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -284,8 +284,8 @@ class _MessageViewState extends State { var children; if (Provider.of(context).selectedConversation != null && Provider.of(context).selectedIndex != null) { var quoted = FutureBuilder( - future: - messageHandler(context, Provider.of(context).selectedProfile!, Provider.of(context).selectedConversation!, id: Provider.of(context).selectedIndex!, byID: true), + future: messageHandler(context, Provider.of(context).selectedProfile!, Provider.of(context).selectedConversation!, + id: Provider.of(context).selectedIndex!, byID: true), builder: (context, snapshot) { if (snapshot.hasData) { var message = snapshot.data! as Message; -- 2.25.1 From ccdd7d0e277d0d6563f166470c7810974015b5b6 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Thu, 20 Jan 2022 15:58:14 -0500 Subject: [PATCH 6/6] remove byType bools and replace with interface and structs for type safety --- lib/models/message.dart | 111 ++++++++++++++++--------- lib/models/messages/quotedmessage.dart | 3 +- lib/views/messageview.dart | 5 +- lib/widgets/messagelist.dart | 2 +- 4 files changed, 74 insertions(+), 47 deletions(-) diff --git a/lib/models/message.dart b/lib/models/message.dart index 52070666..6ada0469 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:cwtch/config.dart'; +import 'package:cwtch/cwtch/cwtch.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; @@ -61,26 +62,76 @@ Message compileOverlay(MessageMetadata metadata, String messageData) { } } -Future messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, - {bool byIndex = false, int? index, bool byID = false, int? id, bool byHash = false, String? hash}) { - var malformedMetadata = MessageMetadata(profileOnion, conversationIdentifier, 0, DateTime.now(), "", "", "", {}, false, true, false); - if (!byIndex && !byID && !byHash) { - EnvironmentConfig.debugLog("Error calling messageHandler: one of byIndex, byID, byHash must be set"); - return Future.value(MalformedMessage(malformedMetadata)); - } - if ((byID && id == null) || (byIndex && index == null) || (byHash && hash == null)) { - EnvironmentConfig.debugLog("Error calling messageHandler: byType needs corresponding value and it was not set"); - return Future.value(MalformedMessage(malformedMetadata)); +abstract class CacheHandler { + MessageInfo? lookup(MessageCache cache); + Future fetch(Cwtch cwtch, String profileOnion, int conversationIdentifier); + void add(MessageCache cache, MessageInfo messageInfo, String contenthash); +} + +class ByIndex implements CacheHandler { + int index; + + ByIndex(this.index); + + MessageInfo? lookup(MessageCache cache) { + return cache.getByIndex(index); } + Future fetch(Cwtch cwtch, String profileOnion, int conversationIdentifier) { + return cwtch.GetMessage(profileOnion, conversationIdentifier, index); + } + + void add(MessageCache cache, MessageInfo messageInfo, String contenthash) { + cache.add(messageInfo, index, contenthash); + } +} + +class ById implements CacheHandler { + int id; + + ById(this.id); + + MessageInfo? lookup(MessageCache cache) { + return cache.getById(id); + } + + Future fetch(Cwtch cwtch, String profileOnion, int conversationIdentifier) { + return cwtch.GetMessageByID(profileOnion, conversationIdentifier, id); + } + + void add(MessageCache cache, MessageInfo messageInfo, String contenthash) { + cache.addUnindexed(messageInfo, contenthash); + } +} + +class ByContentHash implements CacheHandler { + String hash; + + ByContentHash(this.hash); + + MessageInfo? lookup(MessageCache cache) { + return cache.getByContentHash(hash); + } + + Future fetch(Cwtch cwtch, String profileOnion, int conversationIdentifier) { + return cwtch.GetMessageByContentHash(profileOnion, conversationIdentifier, hash); + } + + void add(MessageCache cache, MessageInfo messageInfo, String contenthash) { + cache.addUnindexed(messageInfo, contenthash); + } +} + +Future messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, CacheHandler cacheHandler) { + var malformedMetadata = MessageMetadata(profileOnion, conversationIdentifier, 0, DateTime.now(), "", "", "", {}, false, true, false); // Hit cache - MessageInfo? messageInfo = getMessageInfoFromCache(context, profileOnion, conversationIdentifier, byIndex: byIndex, index: index, byID: byID, id: id, byHash: byHash, hash: hash); + MessageInfo? messageInfo = getMessageInfoFromCache(context, profileOnion, conversationIdentifier, cacheHandler); if (messageInfo != null) { return Future.value(compileOverlay(messageInfo.metadata, messageInfo.wrapper)); } // Fetch and Cache - var messageInfoFuture = fetchAndCacheMessageInfo(context, profileOnion, conversationIdentifier, byIndex: byIndex, index: index, byID: byID, id: id, byHash: byHash, hash: hash); + var messageInfoFuture = fetchAndCacheMessageInfo(context, profileOnion, conversationIdentifier, cacheHandler); return messageInfoFuture.then((MessageInfo? messageInfo) { if (messageInfo != null) { return compileOverlay(messageInfo.metadata, messageInfo.wrapper); @@ -90,20 +141,12 @@ Future messageHandler(BuildContext context, String profileOnion, int co }); } -MessageInfo? getMessageInfoFromCache(BuildContext context, String profileOnion, int conversationIdentifier, - {bool byIndex = false, int? index, bool byID = false, int? id, bool byHash = false, String? hash}) { +MessageInfo? getMessageInfoFromCache(BuildContext context, String profileOnion, int conversationIdentifier, CacheHandler cacheHandler) { // Hit cache try { var cache = Provider.of(context, listen: false).contactList.getContact(conversationIdentifier)?.messageCache; if (cache != null) { - MessageInfo? messageInfo = null; - if (byID) { - messageInfo = cache.getById(id!); - } else if (byHash) { - messageInfo = cache.getByContentHash(hash!); - } else { - messageInfo = cache.getByIndex(index!); - } + MessageInfo? messageInfo = cacheHandler.lookup(cache); if (messageInfo != null) { return messageInfo; } @@ -115,19 +158,12 @@ MessageInfo? getMessageInfoFromCache(BuildContext context, String profileOnion, return null; } -Future fetchAndCacheMessageInfo(BuildContext context, String profileOnion, int conversationIdentifier, - {bool byIndex = false, int? index, bool byID = false, int? id, bool byHash = false, String? hash}) { +Future fetchAndCacheMessageInfo(BuildContext context, String profileOnion, int conversationIdentifier, CacheHandler cacheHandler) { // Load and cache try { Future rawMessageEnvelopeFuture; - if (byID) { - rawMessageEnvelopeFuture = Provider.of(context, listen: false).cwtch.GetMessageByID(profileOnion, conversationIdentifier, id!); - } else if (byHash) { - rawMessageEnvelopeFuture = Provider.of(context, listen: false).cwtch.GetMessageByContentHash(profileOnion, conversationIdentifier, hash!); - } else { - rawMessageEnvelopeFuture = Provider.of(context, listen: false).cwtch.GetMessage(profileOnion, conversationIdentifier, index!); - } + rawMessageEnvelopeFuture = cacheHandler.fetch(Provider.of(context, listen: false).cwtch, profileOnion, conversationIdentifier); return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) { try { @@ -144,11 +180,11 @@ Future fetchAndCacheMessageInfo(BuildContext context, String profi if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') { return Future.delayed(Duration(seconds: 2), () { print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug."); - return fetchAndCacheMessageInfo(context, profileOnion, conversationIdentifier, byIndex: byIndex, index: index, byID: byID, id: id, byHash: byHash, hash: hash).then((value) => value); + return fetchAndCacheMessageInfo(context, profileOnion, conversationIdentifier, cacheHandler); }); } -// Construct the initial metadata + // Construct the initial metadata var messageID = messageWrapper['ID']; var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!; var senderHandle = messageWrapper['PeerID']; @@ -163,15 +199,8 @@ Future fetchAndCacheMessageInfo(BuildContext context, String profi var messageInfo = new MessageInfo(metadata, messageWrapper['Message']); var cache = Provider.of(context, listen: false).contactList.getContact(conversationIdentifier)?.messageCache; - if (cache != null) { - if (byID) { - cache.addUnindexed(messageInfo, contenthash); - } else if (byHash) { - cache.addUnindexed(messageInfo, contenthash); - } else { - cache.add(messageInfo, index!, contenthash); - } + cacheHandler.add(cache, messageInfo, contenthash); } return messageInfo; diff --git a/lib/models/messages/quotedmessage.dart b/lib/models/messages/quotedmessage.dart index 7f5053d9..c43ac12c 100644 --- a/lib/models/messages/quotedmessage.dart +++ b/lib/models/messages/quotedmessage.dart @@ -59,8 +59,7 @@ class QuotedMessage extends Message { return ChangeNotifierProvider.value( value: this.metadata, builder: (bcontext, child) { - return MessageRow(QuotedMessageBubble(message["body"], messageHandler(bcontext, metadata.profileOnion, metadata.conversationIdentifier, byHash: true, hash: message["quotedHash"])), - key: key); + return MessageRow(QuotedMessageBubble(message["body"], messageHandler(bcontext, metadata.profileOnion, metadata.conversationIdentifier, ByContentHash(message["quotedHash"]))), key: key); }); } catch (e) { return MalformedBubble(); diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index bc811f67..dd81c1c2 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -227,7 +227,7 @@ class _MessageViewState extends State { Future.delayed(const Duration(milliseconds: 80), () { var profile = Provider.of(context, listen: false).profileOnion; var identifier = Provider.of(context, listen: false).identifier; - fetchAndCacheMessageInfo(context, profile, identifier, byIndex: true, index: 0); + fetchAndCacheMessageInfo(context, profile, identifier, ByIndex(0)); Provider.of(context, listen: false).newMarker++; Provider.of(context, listen: false).totalMessages += 1; // Resort the contact list... @@ -284,8 +284,7 @@ class _MessageViewState extends State { var children; if (Provider.of(context).selectedConversation != null && Provider.of(context).selectedIndex != null) { var quoted = FutureBuilder( - future: messageHandler(context, Provider.of(context).selectedProfile!, Provider.of(context).selectedConversation!, - id: Provider.of(context).selectedIndex!, byID: true), + future: messageHandler(context, Provider.of(context).selectedProfile!, Provider.of(context).selectedConversation!, ById(Provider.of(context).selectedIndex!)), builder: (context, snapshot) { if (snapshot.hasData) { var message = snapshot.data! as Message; diff --git a/lib/widgets/messagelist.dart b/lib/widgets/messagelist.dart index 4756381d..da8b3ea5 100644 --- a/lib/widgets/messagelist.dart +++ b/lib/widgets/messagelist.dart @@ -83,7 +83,7 @@ class _MessageListState extends State { var messageIndex = index; return FutureBuilder( - future: messageHandler(outerContext, profileOnion, contactHandle, byIndex: true, index: messageIndex), + future: messageHandler(outerContext, profileOnion, contactHandle, ByIndex(messageIndex)), builder: (context, snapshot) { if (snapshot.hasData) { var message = snapshot.data as Message; -- 2.25.1