LCOV - code coverage report
Current view: top level - lib/cwtch - cwtchNotifier.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 277 0.0 %
Date: 2024-02-26 20:09:01 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:convert';
       2             : import 'package:cwtch/cwtch/cwtch.dart';
       3             : import 'package:cwtch/main.dart';
       4             : import 'package:cwtch/models/appstate.dart';
       5             : import 'package:cwtch/models/contact.dart';
       6             : import 'package:cwtch/models/profilelist.dart';
       7             : import 'package:cwtch/models/remoteserver.dart';
       8             : import 'package:cwtch/models/servers.dart';
       9             : import 'package:cwtch/notification_manager.dart';
      10             : 
      11             : import 'package:cwtch/torstatus.dart';
      12             : 
      13             : import '../config.dart';
      14             : import '../errorHandler.dart';
      15             : import '../settings.dart';
      16             : 
      17             : typedef SeenMessageCallback = Function(String, int, DateTime);
      18             : 
      19             : // Class that handles libcwtch-go events (received either via ffi with an isolate or gomobile over a method channel from kotlin)
      20             : //   Takes Notifiers and triggers them on appropriate events
      21             : class CwtchNotifier {
      22             :   late ProfileListState profileCN;
      23             :   late Settings settings;
      24             :   late ErrorHandler error;
      25             :   late TorStatus torStatus;
      26             :   late NotificationsManager notificationManager;
      27             :   late AppState appState;
      28             :   late ServerListState serverListState;
      29             :   late FlwtchState flwtchState;
      30             : 
      31             :   String? notificationSimple;
      32             :   String? notificationConversationInfo;
      33             : 
      34             :   SeenMessageCallback? seenMessageCallback;
      35             : 
      36           0 :   CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN,
      37             :       ServerListState serverListStateCN, FlwtchState flwtchStateCN) {
      38           0 :     profileCN = pcn;
      39           0 :     settings = settingsCN;
      40           0 :     error = errorCN;
      41           0 :     torStatus = torStatusCN;
      42           0 :     notificationManager = notificationManagerP;
      43           0 :     appState = appStateCN;
      44           0 :     serverListState = serverListStateCN;
      45           0 :     flwtchState = flwtchStateCN;
      46             :   }
      47             : 
      48           0 :   void l10nInit(String notificationSimple, String notificationConversationInfo) {
      49           0 :     this.notificationSimple = notificationSimple;
      50           0 :     this.notificationConversationInfo = notificationConversationInfo;
      51             :   }
      52             : 
      53           0 :   void setMessageSeenCallback(SeenMessageCallback callback) {
      54           0 :     seenMessageCallback = callback;
      55             :   }
      56             : 
      57           0 :   void handleMessage(String type, dynamic data) {
      58             :     // EnvironmentConfig.debugLog("NewEvent $type $data");
      59             :     switch (type) {
      60           0 :       case "CwtchStarted":
      61           0 :         flwtchState.cwtch.LoadProfiles(DefaultPassword);
      62           0 :         appState.SetCwtchInit();
      63             :         break;
      64           0 :       case "CwtchStartError":
      65           0 :         appState.SetAppError(data["Error"]);
      66             :         break;
      67           0 :       case "NewPeer":
      68             :         // EnvironmentConfig.debugLog("NewPeer $data");
      69             :         // if tag != v1-defaultPassword then it is either encrypted OR it is an unencrypted account created during pre-beta...
      70           0 :         profileCN.add(
      71           0 :           data["Identity"],
      72           0 :           data["name"],
      73           0 :           data["picture"],
      74           0 :           data["defaultPicture"],
      75           0 :           data["ContactsJson"],
      76           0 :           data["ServerList"],
      77           0 :           data["Online"] == "true",
      78           0 :           data["autostart"] == "true",
      79           0 :           data["tag"] != "v1-defaultPassword",
      80           0 :           data["appearOffline"] == "true",
      81             :         );
      82             : 
      83             :         // Update Profile Attributes
      84           0 :         EnvironmentConfig.debugLog("Looking up Profile Attributes ${data["Identity"]} ${profileCN.getProfile(data["Identity"])}");
      85           0 :         flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-1").then((value) => profileCN.getProfile(data["Identity"])?.setAttribute(0, value));
      86           0 :         flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-2").then((value) => profileCN.getProfile(data["Identity"])?.setAttribute(1, value));
      87           0 :         flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-3").then((value) => profileCN.getProfile(data["Identity"])?.setAttribute(2, value));
      88           0 :         flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-status").then((value) => profileCN.getProfile(data["Identity"])?.setAvailabilityStatus(value ?? ""));
      89             : 
      90           0 :         EnvironmentConfig.debugLog("Looking up Profile Information for Contact...");
      91           0 :         profileCN.getProfile(data["Identity"])?.contactList.contacts.forEach((contact) {
      92           0 :           flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-1").then((value) => contact.setAttribute(0, value));
      93           0 :           flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-2").then((value) => contact.setAttribute(1, value));
      94           0 :           flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-3").then((value) => contact.setAttribute(2, value));
      95           0 :           flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-status").then((value) => contact.setAvailabilityStatus(value ?? ""));
      96             :         });
      97             : 
      98             :         break;
      99           0 :       case "ContactCreated":
     100           0 :         EnvironmentConfig.debugLog("ContactCreated $data");
     101             : 
     102           0 :         profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], int.parse(data["ConversationID"]), data["RemotePeer"],
     103           0 :             nickname: data["nick"],
     104           0 :             status: data["status"],
     105           0 :             imagePath: data["picture"],
     106           0 :             defaultImagePath: data["defaultPicture"],
     107           0 :             blocked: data["blocked"] == "true",
     108           0 :             accepted: data["accepted"] == "true",
     109           0 :             savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"],
     110           0 :             numMessages: int.parse(data["numMessages"]),
     111           0 :             numUnread: int.parse(data["unread"]),
     112             :             isGroup: false, // by definition
     113             :             server: null,
     114             :             archived: false,
     115           0 :             lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet
     116           0 :             notificationPolicy: data["notificationPolicy"] ?? "ConversationNotificationPolicy.Default"));
     117             : 
     118             :         break;
     119           0 :       case "NewServer":
     120           0 :         EnvironmentConfig.debugLog("NewServer $data");
     121           0 :         serverListState.add(data["Onion"], data["ServerBundle"], data["Running"] == "true", data["Description"], data["Autostart"] == "true", data["StorageType"] == "storage-password");
     122             :         break;
     123           0 :       case "ServerIntentUpdate":
     124           0 :         EnvironmentConfig.debugLog("ServerIntentUpdate $data");
     125           0 :         var server = serverListState.getServer(data["Identity"]);
     126             :         if (server != null) {
     127           0 :           server.setRunning(data["Intent"] == "running");
     128             :         }
     129             :         break;
     130           0 :       case "ServerStatsUpdate":
     131           0 :         EnvironmentConfig.debugLog("ServerStatsUpdate $data");
     132           0 :         var totalMessages = int.parse(data["TotalMessages"]);
     133           0 :         var connections = int.parse(data["Connections"]);
     134           0 :         serverListState.updateServerStats(data["Identity"], totalMessages, connections);
     135             :         break;
     136           0 :       case "GroupCreated":
     137             :         // Retrieve Server Status from Cache...
     138             :         String status = "";
     139           0 :         RemoteServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]);
     140             :         if (serverInfoState != null) {
     141           0 :           status = serverInfoState.status;
     142             :         }
     143           0 :         if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(int.parse(data["ConversationID"])) == null) {
     144           0 :           profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], int.parse(data["ConversationID"]), data["GroupID"],
     145             :               blocked: false, // we created
     146             :               accepted: true, // we created
     147           0 :               imagePath: data["picture"],
     148           0 :               defaultImagePath: data["picture"],
     149           0 :               nickname: data["GroupName"],
     150             :               status: status,
     151           0 :               server: data["GroupServer"],
     152             :               isGroup: true,
     153           0 :               lastMessageTime: DateTime.now(),
     154           0 :               notificationPolicy: data["notificationPolicy"] ?? "ConversationNotificationPolicy.Default"));
     155             : 
     156           0 :           profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageReceivedTime(int.parse(data["ConversationID"]), DateTime.now());
     157             :         }
     158             :         break;
     159           0 :       case "PeerDeleted":
     160           0 :         profileCN.delete(data["Identity"]);
     161             :         // todo standarize
     162           0 :         error.handleUpdate("deleteprofile.success");
     163             :         break;
     164           0 :       case "ServerDeleted":
     165           0 :         error.handleUpdate("deletedserver." + data["Status"]);
     166           0 :         if (data["Status"] == "success") {
     167           0 :           serverListState.delete(data["Identity"]);
     168             :         }
     169             :         break;
     170           0 :       case "DeleteContact":
     171           0 :         var identifier = int.parse(data["ConversationID"]);
     172           0 :         profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(identifier);
     173             :         break;
     174           0 :       case "PeerStateChange":
     175           0 :         ContactInfoState? contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
     176             :         if (contact != null) {
     177           0 :           if (data["ConnectionState"] != null) {
     178           0 :             contact.status = data["ConnectionState"];
     179             :           }
     180           0 :           profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
     181             :         }
     182             :         break;
     183           0 :       case "NewMessageFromPeer":
     184           0 :         var identifier = int.parse(data["ConversationID"]);
     185           0 :         var messageID = int.parse(data["Index"]);
     186           0 :         var timestamp = DateTime.tryParse(data['TimestampReceived'])!;
     187           0 :         var senderHandle = data['RemotePeer'];
     188           0 :         var senderImage = data['picture'];
     189           0 :         var isAuto = data['Auto'] == "true";
     190           0 :         String contenthash = data['ContentHash'];
     191           0 :         var selectedProfile = appState.selectedProfile == data["ProfileOnion"];
     192           0 :         var selectedConversation = selectedProfile && appState.selectedConversation == identifier;
     193           0 :         var notification = data["notification"];
     194             : 
     195           0 :         if (selectedConversation && seenMessageCallback != null) {
     196           0 :           seenMessageCallback!(data["ProfileOnion"]!, identifier, DateTime.now().toUtc());
     197             :         }
     198             : 
     199           0 :         if (notification == "SimpleEvent") {
     200           0 :           notificationManager.notify(notificationSimple ?? "New Message", "", 0);
     201           0 :         } else if (notification == "ContactInfo") {
     202           0 :           var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier);
     203           0 :           notificationManager.notify((notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString())), data["ProfileOnion"], identifier);
     204             :         }
     205             : 
     206           0 :         profileCN.getProfile(data["ProfileOnion"])?.newMessage(
     207             :               identifier,
     208             :               messageID,
     209             :               timestamp,
     210             :               senderHandle,
     211             :               senderImage,
     212             :               isAuto,
     213           0 :               data["Data"],
     214             :               contenthash,
     215             :               selectedProfile,
     216             :               selectedConversation,
     217             :             );
     218           0 :         appState.notifyProfileUnread();
     219             :         break;
     220           0 :       case "PeerAcknowledgement":
     221             :         // We don't use these anymore, IndexedAcknowledgement is more suited to the UI front end...
     222             :         break;
     223           0 :       case "IndexedAcknowledgement":
     224           0 :         var conversation = int.parse(data["ConversationID"]);
     225           0 :         var messageID = int.parse(data["Index"]);
     226             : 
     227             :         // We only ever see acks from authenticated peers.
     228             :         // If the contact is marked as offline then override this - can happen when the contact is removed from the front
     229             :         // end during syncing.
     230           0 :         if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(conversation)!.isOnline() == false) {
     231           0 :           profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(conversation)!.status = "Authenticated";
     232             :         }
     233           0 :         profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(conversation)!.ackCache(messageID);
     234             : 
     235             :         break;
     236           0 :       case "NewMessageFromGroup":
     237           0 :         var identifier = int.parse(data["ConversationID"]);
     238           0 :         if (data["ProfileOnion"] != data["RemotePeer"]) {
     239           0 :           var idx = int.parse(data["Index"]);
     240           0 :           var senderHandle = data['RemotePeer'];
     241           0 :           var senderImage = data['picture'];
     242           0 :           var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
     243           0 :           var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier);
     244           0 :           var currentTotal = contact!.totalMessages;
     245           0 :           var isAuto = data['Auto'] == "true";
     246           0 :           String contenthash = data['ContentHash'];
     247           0 :           var selectedProfile = appState.selectedProfile == data["ProfileOnion"];
     248           0 :           var selectedConversation = selectedProfile && appState.selectedConversation == identifier;
     249           0 :           var notification = data["notification"];
     250             : 
     251             :           // Only bother to do anything if we know about the group and the provided index is greater than our current total...
     252           0 :           if (idx >= currentTotal) {
     253             :             // TODO: There are 2 timestamps associated with a new group message - time sent and time received.
     254             :             // Sent refers to the time a profile alleges they sent a message
     255             :             // Received refers to the time we actually saw the message from the server
     256             :             // These can obviously be very different for legitimate reasons.
     257             :             // We also maintain a relative hash-link through PreviousMessageSignature which is the ground truth for
     258             :             // order.
     259             :             // In the future we will want to combine these 3 ordering mechanisms into a cohesive view of the timeline
     260             :             // For now we perform some minimal checks on the sent timestamp to use to provide a useful ordering for honest contacts
     261             :             // and ensure that malicious contacts in groups can only set this timestamp to a value within the range of `last seen message time`
     262             :             // and `local now`.
     263           0 :             profileCN.getProfile(data["ProfileOnion"])?.newMessage(identifier, idx, timestampSent, senderHandle, senderImage, isAuto, data["Data"], contenthash, selectedProfile, selectedConversation);
     264           0 :             if (selectedConversation && seenMessageCallback != null) {
     265           0 :               seenMessageCallback!(data["ProfileOnion"]!, identifier, DateTime.now().toUtc());
     266             :             }
     267             : 
     268           0 :             if (notification == "SimpleEvent") {
     269           0 :               notificationManager.notify(notificationSimple ?? "New Message", "", 0);
     270           0 :             } else if (notification == "ContactInfo") {
     271           0 :               var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier);
     272           0 :               notificationManager.notify((notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString())), data["ProfileOnion"], identifier);
     273             :             }
     274           0 :             appState.notifyProfileUnread();
     275             :           }
     276           0 :           RemoteServerInfoState? server = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(contact.server ?? "");
     277           0 :           server?.updateSyncProgressFor(timestampSent);
     278             :         } else {
     279             :           // This is dealt with by IndexedAcknowledgment
     280           0 :           EnvironmentConfig.debugLog("new message from group from yourself - this should not happen");
     281             :         }
     282             :         break;
     283           0 :       case "IndexedFailure":
     284           0 :         var identifier = int.parse(data["ConversationID"]);
     285           0 :         var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier);
     286           0 :         var messageID = int.parse(data["Index"]);
     287           0 :         contact!.errCache(messageID);
     288             :         break;
     289           0 :       case "AppError":
     290           0 :         EnvironmentConfig.debugLog("New App Error: $data");
     291             :         // special case for delete error (todo: standardize cwtch errors)
     292           0 :         if (data["Error"] == "Password did not match") {
     293           0 :           error.handleUpdate("deleteprofile.error");
     294           0 :         } else if (data["Data"] != null) {
     295           0 :           error.handleUpdate(data["Data"]);
     296             :         }
     297             :         break;
     298           0 :       case "UpdateGlobalSettings":
     299           0 :         settings.handleUpdate(jsonDecode(data["Data"]));
     300             :         break;
     301           0 :       case "UpdatedProfileAttribute":
     302           0 :         if (data["Key"] == "public.profile.name") {
     303           0 :           profileCN.getProfile(data["ProfileOnion"])?.nickname = data["Data"];
     304           0 :         } else if (data["Key"].toString().startsWith("local.filesharing.")) {
     305           0 :           if (data["Key"].toString().endsWith(".path")) {
     306             :             // local.conversation.filekey.path
     307           0 :             List<String> keyparts = data["Key"].toString().split(".");
     308           0 :             if (keyparts.length == 5) {
     309           0 :               String filekey = keyparts[2] + "." + keyparts[3];
     310           0 :               profileCN.getProfile(data["ProfileOnion"])?.downloadSetPathForSender(filekey, data["Data"]);
     311             :             }
     312             :           }
     313           0 :         } else if (data["Key"].toString().startsWith("public.profile.profile-attribute")) {
     314             :           // ignore these events...
     315           0 :         } else if (data["Key"].toString().startsWith("public.profile.profile-status")) {
     316           0 :           profileCN.getProfile(data["ProfileOnion"])?.setAvailabilityStatus(data["Data"]);
     317             :         } else {
     318           0 :           EnvironmentConfig.debugLog("unhandled set attribute event: ${data['Key']}");
     319             :         }
     320             :         break;
     321           0 :       case "NetworkError":
     322           0 :         var isOnline = data["Status"] == "Success";
     323           0 :         profileCN.getProfile(data["ProfileOnion"])?.isOnline = isOnline;
     324             :         break;
     325           0 :       case "ACNStatus":
     326           0 :         EnvironmentConfig.debugLog("acn status: $data");
     327           0 :         torStatus.handleUpdate(int.parse(data["Progress"]), data["Status"]);
     328             :         break;
     329           0 :       case "ACNVersion":
     330           0 :         EnvironmentConfig.debugLog("acn version: $data");
     331           0 :         torStatus.updateVersion(data["Data"]);
     332             :         break;
     333           0 :       case "UpdateServerInfo":
     334           0 :         EnvironmentConfig.debugLog("NewEvent  UpdateServerInfo $type $data");
     335           0 :         profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]);
     336             :         break;
     337           0 :       case "TokenManagerInfo":
     338             :         try {
     339           0 :           List<dynamic> associatedGroups = jsonDecode(data["Data"]);
     340           0 :           int count = int.parse(data["ServerTokenCount"]);
     341           0 :           associatedGroups.forEach((identifier) {
     342           0 :             profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(int.parse(identifier.toString()))!.antispamTickets = count;
     343             :           });
     344           0 :           EnvironmentConfig.debugLog("update server token count for $associatedGroups, $count");
     345             :         } catch (e) {
     346             :           //  No tokens in data...
     347             :         }
     348             :         break;
     349           0 :       case "NewGroup":
     350           0 :         String invite = data["GroupInvite"].toString();
     351           0 :         if (invite.startsWith("torv3")) {
     352           0 :           String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5)));
     353           0 :           dynamic groupInvite = jsonDecode(inviteJson);
     354             : 
     355             :           // Retrieve Server Status from Cache...
     356             :           String status = "";
     357           0 :           RemoteServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])!.serverList.getServer(groupInvite["ServerHost"]);
     358             :           if (serverInfoState != null) {
     359           0 :             status = serverInfoState.status;
     360             :           }
     361             : 
     362           0 :           if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) {
     363           0 :             var identifier = int.parse(data["ConversationID"]);
     364           0 :             profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], identifier, groupInvite["GroupID"],
     365             :                 blocked: false, // NewGroup only issued on accepting invite
     366             :                 accepted: true, // NewGroup only issued on accepting invite
     367           0 :                 imagePath: data["picture"],
     368           0 :                 nickname: groupInvite["GroupName"],
     369           0 :                 server: groupInvite["ServerHost"],
     370             :                 status: status,
     371             :                 isGroup: true,
     372           0 :                 lastMessageTime: DateTime.now()));
     373             : 
     374           0 :             profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageReceivedTime(identifier, DateTime.fromMillisecondsSinceEpoch(0));
     375             :           }
     376             :           // request a new server update...
     377             :           // NOTE: In the future this should also update the TokenManagerInfo
     378             :           // This is not currently communicated by ServerUpdateInfo (but it probably should be)git
     379           0 :           flwtchState.cwtch.PublishServerUpdate(data["ProfileOnion"]);
     380             :         }
     381             :         break;
     382           0 :       case "ServerStateChange":
     383             :         // Update the Server Cache
     384           0 :         profileCN.getProfile(data["ProfileOnion"])?.updateServerStatusCache(data["GroupServer"], data["ConnectionState"]);
     385           0 :         profileCN.getProfile(data["ProfileOnion"])?.contactList.contacts.forEach((contact) {
     386           0 :           if (contact.isGroup == true && contact.server == data["GroupServer"]) {
     387           0 :             contact.status = data["ConnectionState"];
     388             :           }
     389             :         });
     390           0 :         profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
     391             :         break;
     392           0 :       case "UpdatedConversationAttribute":
     393           0 :         if (data["Path"] == "profile.name") {
     394           0 :           if (data["Data"].toString().trim().length > 0) {
     395             :             // Update locally on the UI...
     396           0 :             if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]) != null) {
     397           0 :               profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"])!.nickname = data["Data"];
     398             :             }
     399             :           }
     400           0 :         } else if (data['Path'] == "profile.custom-profile-image") {
     401           0 :           EnvironmentConfig.debugLog("received ret val of custom profile image: $data");
     402           0 :           String fileKey = data['Data'];
     403           0 :           var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
     404             :           if (contact != null) {
     405           0 :             EnvironmentConfig.debugLog("waiting for download from $contact");
     406           0 :             profileCN.getProfile(data["ProfileOnion"])?.waitForDownloadComplete(contact.identifier, fileKey);
     407             :           }
     408           0 :         } else if (data['Path'] == "profile.profile-attribute-1" || data['Path'] == "profile.profile-attribute-2" || data['Path'] == "profile.profile-attribute-3") {
     409           0 :           var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
     410             :           if (contact != null) {
     411           0 :             switch (data['Path']) {
     412           0 :               case "profile.profile-attribute-1":
     413           0 :                 contact.setAttribute(0, data["Data"]);
     414             :                 break;
     415           0 :               case "profile.profile-attribute-2":
     416           0 :                 contact.setAttribute(1, data["Data"]);
     417             :                 break;
     418           0 :               case "profile.profile-attribute-3":
     419           0 :                 contact.setAttribute(2, data["Data"]);
     420             :                 break;
     421             :             }
     422             :           }
     423           0 :         } else if (data['Path'] == "profile.profile-status") {
     424           0 :           var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
     425             :           if (contact != null) {
     426           0 :             contact.setAvailabilityStatus(data['Data']);
     427             :           }
     428             :         } else {
     429           0 :           EnvironmentConfig.debugLog("unhandled ret val event: ${data['Path']}");
     430             :         }
     431             :         break;
     432           0 :       case "ManifestSizeReceived":
     433           0 :         if (!profileCN.getProfile(data["ProfileOnion"])!.downloadActive(data["FileKey"])) {
     434           0 :           profileCN.getProfile(data["ProfileOnion"])?.downloadUpdate(data["FileKey"], 0, 1);
     435             :         }
     436             :         break;
     437           0 :       case "ManifestSaved":
     438           0 :         profileCN.getProfile(data["ProfileOnion"])?.downloadMarkManifest(data["FileKey"]);
     439             :         break;
     440           0 :       case "FileDownloadProgressUpdate":
     441           0 :         var progress = int.parse(data["Progress"]);
     442           0 :         profileCN.getProfile(data["ProfileOnion"])?.downloadUpdate(data["FileKey"], progress, int.parse(data["FileSizeInChunks"]));
     443             :         // progress == -1 is a "download was interrupted" message and should contain a path
     444           0 :         if (progress < 0) {
     445           0 :           profileCN.getProfile(data["ProfileOnion"])?.downloadSetPath(data["FileKey"], data["FilePath"]);
     446             :         }
     447             :         break;
     448           0 :       case "FileDownloaded":
     449           0 :         profileCN.getProfile(data["ProfileOnion"])?.downloadMarkFinished(data["FileKey"], data["FilePath"]);
     450             :         break;
     451           0 :       case "ImportingProfileEvent":
     452             :         break;
     453           0 :       case "StartingStorageMigration":
     454           0 :         appState.SetModalState(ModalState.storageMigration);
     455             :         break;
     456           0 :       case "DoneStorageMigration":
     457           0 :         appState.SetModalState(ModalState.none);
     458             :         break;
     459           0 :       case "BlodeuweddSummary":
     460           0 :         var identifier = int.parse(data["ConversationID"]);
     461           0 :         profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)?.updateSummaryEvent(data["Summary"]);
     462             :         break;
     463           0 :       case "BlodeuweddTranslation":
     464           0 :         var identifier = int.parse(data["ConversationID"]);
     465           0 :         var mid = int.parse(data["Index"]);
     466           0 :         EnvironmentConfig.debugLog("received translation event: $identifier $mid $data");
     467           0 :         profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)?.updateTranslationEvent(mid, data["Translation"]);
     468             :         break;
     469           0 :       case "ACNInfo":
     470           0 :         var key = data["Key"];
     471           0 :         var handle = data["Handle"];
     472           0 :         if (key == "circuit") {
     473           0 :           profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(handle)?.acnCircuit = data["Data"];
     474             :         }
     475             :         break;
     476           0 :       case "SearchResult":
     477           0 :         String searchID = data["SearchID"];
     478           0 :         var conversationIdentifier = int.parse(data["ConversationID"]);
     479           0 :         var messageIndex = int.parse(data["RowIndex"]);
     480           0 :         profileCN.getProfile(data["ProfileOnion"])?.handleSearchResult(searchID, conversationIdentifier, messageIndex);
     481             :         break;
     482             :       default:
     483           0 :         EnvironmentConfig.debugLog("unhandled event: $type");
     484             :     }
     485             :   }
     486             : }

Generated by: LCOV version 1.14