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