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