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