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