LCOV - code coverage report
Current view: top level - lib/models - messagecache.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 84 0.0 %
Date: 2024-09-12 20:27:43 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'package:flutter/foundation.dart';
       3             : import 'message.dart';
       4             : 
       5             : // we only count up to 100 unread messages, if more than that we can't accurately resync message cache, just reset
       6             : // https://git.openprivacy.ca/cwtch.im/libcwtch-go/src/branch/trunk/utils/eventHandler.go#L210
       7             : const MaxUnreadBeforeCacheReset = 100;
       8             : 
       9             : class MessageInfo {
      10             :   late MessageMetadata metadata;
      11             :   late String wrapper;
      12             : 
      13           0 :   MessageInfo(this.metadata, this.wrapper);
      14             : 
      15           0 :   int size() {
      16           0 :     var wrapperSize = wrapper.length * 2;
      17             :     return wrapperSize;
      18             :   }
      19             : }
      20             : 
      21             : class LocalIndexMessage {
      22             :   late bool cacheOnly;
      23             :   late bool isLoading;
      24             :   late Future<void> loaded;
      25             :   late Completer<void> loader;
      26             : 
      27             :   late int? messageId;
      28             : 
      29           0 :   LocalIndexMessage(int? messageId, {cacheOnly = false, isLoading = false}) {
      30           0 :     this.messageId = messageId;
      31           0 :     this.cacheOnly = cacheOnly;
      32           0 :     this.isLoading = isLoading;
      33           0 :     loader = Completer<void>();
      34           0 :     loaded = loader.future;
      35             :     if (!isLoading) {
      36           0 :       loader.complete(); // complete this
      37             :     }
      38             :   }
      39             : 
      40           0 :   void finishLoad(int messageId) {
      41           0 :     this.messageId = messageId;
      42           0 :     if (!loader.isCompleted) {
      43           0 :       isLoading = false;
      44           0 :       loader.complete(true);
      45             :     }
      46             :   }
      47             : 
      48           0 :   void failLoad() {
      49           0 :     this.messageId = null;
      50           0 :     if (!loader.isCompleted) {
      51           0 :       isLoading = false;
      52           0 :       loader.complete(true);
      53             :     }
      54             :   }
      55             : 
      56           0 :   Future<void> waitForLoad() {
      57           0 :     return loaded;
      58             :   }
      59             : 
      60           0 :   Future<int?> get() async {
      61           0 :     if (isLoading) {
      62           0 :       await waitForLoad();
      63             :     }
      64           0 :     return messageId;
      65             :   }
      66             : }
      67             : 
      68             : // Message cache stores messages for use by the UI and uses MessageHandler and associated ByX loaders
      69             : // the cache stores messages in a cache indexed by their storage Id, and has two secondary indexes into it, content hash, and local index
      70             : // Index is the primary way to access the cache as it is a sequential ordered access and is used by the message pane
      71             : // contentHash is used for fetching replies
      72             : // by Id is used when composing a reply
      73             : // cacheByIndex supports additional features than just a direct index into the cache (byID)
      74             : // it allows locking of ranges in order to support bulk sequential loading (see ByIndex in message.dart)
      75             : // cacheByIndex allows allows inserting temporarily non storage backed messages so that Send Message can be respected instantly and then updated upon insertion into backend
      76             : // the message cache needs storageMessageCount maintained by the system so it can inform bulk loading when it's reaching the end of fetchable messages
      77             : class MessageCache extends ChangeNotifier {
      78             :   // cache of MessageId to Message
      79             :   late Map<int, MessageInfo> cache;
      80             : 
      81             :   // local index to MessageId
      82             :   late List<LocalIndexMessage> cacheByIndex;
      83             :   // index unsynced is used on android on reconnect to tell us new messages are in the backend that should be at the front of the index cache
      84             :   int _indexUnsynced = 0;
      85             : 
      86             :   // map of content hash to MessageId
      87             :   late Map<String, int> cacheByHash;
      88             : 
      89             :   late int _storageMessageCount;
      90             : 
      91           0 :   MessageCache(int storageMessageCount) {
      92           0 :     cache = {};
      93           0 :     cacheByIndex = List.empty(growable: true);
      94           0 :     cacheByHash = {};
      95           0 :     this._storageMessageCount = storageMessageCount;
      96             :   }
      97             : 
      98           0 :   int get storageMessageCount => _storageMessageCount;
      99           0 :   set storageMessageCount(int newval) {
     100           0 :     this._storageMessageCount = newval;
     101             :   }
     102             : 
     103             :   // On android reconnect, if backend supplied message count > UI message count, add the difference to the front of the index
     104           0 :   void addFrontIndexGap(int count) {
     105           0 :     this._indexUnsynced = count;
     106             :   }
     107             : 
     108           0 :   int get indexUnsynced => _indexUnsynced;
     109             : 
     110           0 :   MessageInfo? getById(int id) => cache[id];
     111             : 
     112           0 :   Future<MessageInfo?> getByIndex(int index) async {
     113           0 :     if (index >= cacheByIndex.length) {
     114             :       return null;
     115             :     }
     116           0 :     var id = await cacheByIndex[index].get();
     117             :     if (id == null) {
     118           0 :       return Future<MessageInfo?>.value(null);
     119             :     }
     120           0 :     return cache[id];
     121             :   }
     122             : 
     123           0 :   int findIndex(int id) {
     124           0 :     return cacheByIndex.indexWhere((element) => element.messageId == id);
     125             :   }
     126             : 
     127           0 :   MessageInfo? getByContentHash(String contenthash) => cache[cacheByHash[contenthash]];
     128             : 
     129           0 :   void addNew(String profileOnion, int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data, String contenthash) {
     130           0 :     this.cache[messageID] = MessageInfo(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto, contenthash), data);
     131           0 :     this.cache[messageID]?.metadata.lastChecked = DateTime.now(); // Don't check straight away...
     132           0 :     this.cacheByIndex.insert(0, LocalIndexMessage(messageID));
     133           0 :     if (contenthash != "") {
     134           0 :       this.cacheByHash[contenthash] = messageID;
     135             :     }
     136             :   }
     137             : 
     138             :   // inserts place holder values into the index cache that will block on .get() until .finishLoad() is called on them with message contents
     139             :   // or .failLoad() is called on them to mark them malformed
     140             :   // this prevents successive ui message build requests from triggering multiple GetMesssage requests to the backend, as the first one locks a block of messages and the rest wait on that
     141           0 :   void lockIndexes(int start, int end) {
     142           0 :     for (var i = start; i < end; i++) {
     143           0 :       this.cacheByIndex.insert(i, LocalIndexMessage(null, isLoading: true));
     144             :       // if there are unsynced messages on the index cache it means there are messages at the front, and by the logic in message/ByIndex/get() we will be loading those
     145             :       // there for we can decrement the count as this will be one of them
     146           0 :       if (this._indexUnsynced > 0) {
     147           0 :         this._indexUnsynced--;
     148             :       }
     149             :     }
     150             :   }
     151             : 
     152           0 :   void malformIndexes(int start, int end) {
     153           0 :     for (var i = start; i < end; i++) {
     154           0 :       this.cacheByIndex[i].failLoad();
     155             :     }
     156             :   }
     157             : 
     158           0 :   void addIndexed(MessageInfo messageInfo, int index) {
     159           0 :     this.cache[messageInfo.metadata.messageID] = messageInfo;
     160           0 :     if (index < this.cacheByIndex.length) {
     161           0 :       this.cacheByIndex[index].finishLoad(messageInfo.metadata.messageID);
     162             :     } else {
     163           0 :       this.cacheByIndex.insert(index, LocalIndexMessage(messageInfo.metadata.messageID));
     164             :     }
     165           0 :     this.cacheByHash[messageInfo.metadata.contenthash] = messageInfo.metadata.messageID;
     166             :   }
     167             : 
     168           0 :   void addUnindexed(MessageInfo messageInfo) {
     169           0 :     this.cache[messageInfo.metadata.messageID] = messageInfo;
     170           0 :     if (messageInfo.metadata.contenthash != "") {
     171           0 :       this.cacheByHash[messageInfo.metadata.contenthash] = messageInfo.metadata.messageID;
     172             :     }
     173             :   }
     174             : 
     175           0 :   void ackCache(int messageID) {
     176           0 :     cache[messageID]?.metadata.ackd = true;
     177           0 :     notifyListeners();
     178             :   }
     179             : 
     180           0 :   void errCache(int messageID) {
     181           0 :     cache[messageID]?.metadata.error = true;
     182           0 :     notifyListeners();
     183             :   }
     184             : 
     185           0 :   void notifyUpdate(int messageID) {
     186           0 :     notifyListeners();
     187             :   }
     188             : 
     189           0 :   int size() {
     190             :     // very naive cache size, assuming MessageInfo are fairly large on average
     191             :     // and everything else is small in comparison
     192           0 :     int cacheSize = cache.entries.map((e) => e.value.size()).fold(0, (previousValue, element) => previousValue + element);
     193           0 :     return cacheSize + cacheByHash.length * 64 + cacheByIndex.length * 16;
     194             :   }
     195             : 
     196           0 :   void updateTranslationEvent(int messageID, String translation) {
     197           0 :     cache[messageID]?.metadata.updateTranslationEvent(translation);
     198           0 :     notifyListeners();
     199             :   }
     200             : }

Generated by: LCOV version 1.14