fix android segfault in flwtch worker; try/catch to catch future bugs in flwtch worker; resume servers load sync status from cwtch; add bg color to sync progress bar; showdown now synchronous so completes

This commit is contained in:
Dan Ballard 2022-03-03 17:00:36 -08:00
parent 8eaa3974c9
commit 1d0cb785c1
12 changed files with 314 additions and 297 deletions

View File

@ -1 +1 @@
2022-02-23-17-17-v1.6.0-2-ge8b2def
2022-03-03-19-53-v1.6.0-4-g4b881b9

View File

@ -1 +1 @@
2022-02-23-22-17-v1.6.0-2-ge8b2def
2022-03-04-00-54-v1.6.0-4-g4b881b9

View File

@ -38,7 +38,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
val args = inputData.getString(KEY_ARGS)
?: return Result.failure()
// Mark the Worker as important
val progress = "Cwtch is keeping Tor running in the background"//todo:translate
val progress = "Cwtch is keeping Tor running in the background" // TODO: translate
setForeground(createForegroundInfo(progress))
return handleCwtch(method, args)
}
@ -52,25 +52,27 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
}
private fun handleCwtch(method: String, args: String): Result {
val a = JSONObject(args)
when (method) {
"Start" -> {
Log.i("FlwtchWorker.kt", "handleAppInfo Start")
val appDir = (a.get("appDir") as? String) ?: ""
val torPath = (a.get("torPath") as? String) ?: "tor"
Log.i("FlwtchWorker.kt", "appDir: '$appDir' torPath: '$torPath'")
try {
val a = JSONObject(args)
when (method) {
"Start" -> {
Log.i("FlwtchWorker.kt", "handleAppInfo Start")
val appDir = (a.get("appDir") as? String) ?: ""
val torPath = (a.get("torPath") as? String) ?: "tor"
Log.i("FlwtchWorker.kt", "appDir: '$appDir' torPath: '$torPath'")
if (Cwtch.startCwtch(appDir, torPath) != 0.toLong()) return Result.failure()
if (Cwtch.startCwtch(appDir, torPath) != 0.toLong()) return Result.failure()
Log.i("FlwtchWorker.kt", "startCwtch success, starting coroutine AppbusEvent loop...")
val downloadIDs = mutableMapOf<String, Int>()
while(true) {
val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent())
if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") {
val data = JSONObject(evt.Data)
val handle = if (evt.EventType == "NewMessageFromPeer") data.getString("RemotePeer") else data.getString("GroupID");
if (data["RemotePeer"] != data["ProfileOnion"]) {
val notification = data["notification"]
Log.i("FlwtchWorker.kt", "startCwtch success, starting coroutine AppbusEvent loop...")
val downloadIDs = mutableMapOf<String, Int>()
while (true) {
val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent())
// TODO replace this notification block with the NixNotification manager in dart as it has access to contact names and also needs less working around
if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") {
val data = JSONObject(evt.Data)
val handle = data.getString("RemotePeer");
if (data["RemotePeer"] != data["ProfileOnion"]) {
val notification = data["notification"]
if (notification == "SimpleEvent") {
val channelId =
@ -118,7 +120,8 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
val newNotification = NotificationCompat.Builder(applicationContext, channelId)
.setContentTitle(data.getString("Nick"))
.setContentText((notificationConversationInfo ?: "New Message From %1").replace("%1", data.getString("Nick")))
.setContentText((notificationConversationInfo
?: "New Message From %1").replace("%1", data.getString("Nick")))
.setLargeIcon(BitmapFactory.decodeStream(fh))
.setSmallIcon(R.mipmap.knott_transparent)
.setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT))
@ -128,291 +131,296 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), handle), newNotification)
}
}
} else if (evt.EventType == "FileDownloadProgressUpdate") {
try {
}
} else if (evt.EventType == "FileDownloadProgressUpdate") {
try {
val data = JSONObject(evt.Data);
val fileKey = data.getString("FileKey");
val title = data.getString("NameSuggestion");
val progress = data.getString("Progress").toInt();
val progressMax = data.getString("FileSizeInChunks").toInt();
if (!downloadIDs.containsKey(fileKey)) {
downloadIDs.put(fileKey, downloadIDs.count());
}
var dlID = downloadIDs.get(fileKey);
if (dlID == null) {
dlID = 0;
}
if (progress >= 0) {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createDownloadNotificationChannel(fileKey, fileKey)
} else {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
};
val newNotification = NotificationCompat.Builder(applicationContext, channelId)
.setOngoing(true)
.setContentTitle("Downloading")//todo: translate
.setContentText(title)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setProgress(progressMax, progress, false)
.setSound(null)
//.setSilent(true)
.build();
notificationManager.notify(dlID, newNotification);
}
} catch (e: Exception) {
Log.d("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace());
}
} else if (evt.EventType == "FileDownloaded") {
Log.d("FlwtchWorker", "file downloaded!");
val data = JSONObject(evt.Data);
val tempFile = data.getString("TempFile");
val fileKey = data.getString("FileKey");
val title = data.getString("NameSuggestion");
val progress = data.getString("Progress").toInt();
val progressMax = data.getString("FileSizeInChunks").toInt();
if (!downloadIDs.containsKey(fileKey)) {
downloadIDs.put(fileKey, downloadIDs.count());
if (tempFile != "" && tempFile != data.getString("FilePath")) {
val filePath = data.getString("FilePath");
Log.i("FlwtchWorker", "moving " + tempFile + " to " + filePath);
val sourcePath = Paths.get(tempFile);
val targetUri = Uri.parse(filePath);
val os = this.applicationContext.getContentResolver().openOutputStream(targetUri);
val bytesWritten = Files.copy(sourcePath, os);
Log.d("FlwtchWorker", "copied " + bytesWritten.toString() + " bytes");
if (bytesWritten != 0L) {
os?.flush();
os?.close();
Files.delete(sourcePath);
}
}
var dlID = downloadIDs.get(fileKey);
if (dlID == null) {
dlID = 0;
}
if (progress >= 0) {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createDownloadNotificationChannel(fileKey, fileKey)
} else {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
};
val newNotification = NotificationCompat.Builder(applicationContext, channelId)
.setOngoing(true)
.setContentTitle("Downloading")//todo: translate
.setContentText(title)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setProgress(progressMax, progress, false)
.setSound(null)
//.setSilent(true)
.build();
notificationManager.notify(dlID, newNotification);
}
} catch (e: Exception) {
Log.d("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace());
}
} else if (evt.EventType == "FileDownloaded") {
Log.d("FlwtchWorker", "file downloaded!");
val data = JSONObject(evt.Data);
val tempFile = data.getString("TempFile");
val fileKey = data.getString("FileKey");
if (tempFile != "" && tempFile != data.getString("FilePath")) {
val filePath = data.getString("FilePath");
Log.i("FlwtchWorker", "moving "+tempFile+" to "+filePath);
val sourcePath = Paths.get(tempFile);
val targetUri = Uri.parse(filePath);
val os = this.applicationContext.getContentResolver().openOutputStream(targetUri);
val bytesWritten = Files.copy(sourcePath, os);
Log.d("FlwtchWorker", "copied " + bytesWritten.toString() + " bytes");
if (bytesWritten != 0L) {
os?.flush();
os?.close();
Files.delete(sourcePath);
if (downloadIDs.containsKey(fileKey)) {
notificationManager.cancel(downloadIDs.get(fileKey) ?: 0);
}
}
if (downloadIDs.containsKey(fileKey)) {
notificationManager.cancel(downloadIDs.get(fileKey)?:0);
}
}
Intent().also { intent ->
intent.action = "im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS"
intent.putExtra("EventType", evt.EventType)
intent.putExtra("Data", evt.Data)
intent.putExtra("EventID", evt.EventID)
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
Intent().also { intent ->
intent.action = "im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS"
intent.putExtra("EventType", evt.EventType)
intent.putExtra("Data", evt.Data)
intent.putExtra("EventID", evt.EventID)
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
}
}
}
}
"ReconnectCwtchForeground" -> {
Cwtch.reconnectCwtchForeground()
}
"CreateProfile" -> {
val nick = (a.get("nick") as? String) ?: ""
val pass = (a.get("pass") as? String) ?: ""
Cwtch.createProfile(nick, pass)
}
"LoadProfiles" -> {
val pass = (a.get("pass") as? String) ?: ""
Cwtch.loadProfiles(pass)
}
"ChangePassword" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val pass = (a.get("OldPass") as? String) ?: ""
val passNew = (a.get("NewPass") as? String) ?: ""
val passNew2 = (a.get("NewPassAgain") as? String) ?: ""
Cwtch.changePassword(profile, pass, passNew, passNew2)
}
"GetMessage" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val indexI = a.getInt("index").toLong()
Log.d("FlwtchWorker", "Cwtch GetMessage " + profile + " " + conversation.toString() + " " + indexI.toString())
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, conversation, indexI)).build())
}
"GetMessageByID" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val id = a.getInt("id").toLong()
return Result.success(Data.Builder().putString("result", Cwtch.getMessageByID(profile, conversation, id)).build())
}
"GetMessageByContentHash" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val contentHash = (a.get("contentHash") as? String) ?: ""
return Result.success(Data.Builder().putString("result", Cwtch.getMessagesByContentHash(profile, conversation, contentHash)).build())
}
"UpdateMessageAttribute" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val channel = a.getInt("chanenl").toLong()
val midx = a.getInt("midx").toLong()
val key = (a.get("key") as? String) ?: ""
val value = (a.get("value") as? String) ?: ""
Cwtch.setMessageAttribute(profile, conversation, channel, midx, key, value)
}
"AcceptConversation" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
Cwtch.acceptConversation(profile, conversation)
}
"BlockContact" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
Cwtch.blockContact(profile, conversation)
}
"UnblockContact" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
Cwtch.unblockContact(profile, conversation)
}
"SendMessage" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val message = (a.get("message") as? String) ?: ""
Cwtch.sendMessage(profile, conversation, message)
}
"SendInvitation" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val target = a.getInt("target").toLong()
Cwtch.sendInvitation(profile, conversation, target)
}
"ShareFile" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val filepath = (a.get("filepath") as? String) ?: ""
Cwtch.shareFile(profile, conversation, filepath)
}
"DownloadFile" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val filepath = (a.get("filepath") as? String) ?: ""
val manifestpath = (a.get("manifestpath") as? String) ?: ""
val filekey = (a.get("filekey") as? String) ?: ""
// FIXME: Prevent spurious calls by Intent
if (profile != "") {
Cwtch.downloadFile(profile, conversation, filepath, manifestpath, filekey)
"ReconnectCwtchForeground" -> {
Cwtch.reconnectCwtchForeground()
}
"CreateProfile" -> {
val nick = (a.get("nick") as? String) ?: ""
val pass = (a.get("pass") as? String) ?: ""
Cwtch.createProfile(nick, pass)
}
"LoadProfiles" -> {
val pass = (a.get("pass") as? String) ?: ""
Cwtch.loadProfiles(pass)
}
"ChangePassword" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val pass = (a.get("OldPass") as? String) ?: ""
val passNew = (a.get("NewPass") as? String) ?: ""
val passNew2 = (a.get("NewPassAgain") as? String) ?: ""
Cwtch.changePassword(profile, pass, passNew, passNew2)
}
"GetMessage" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val indexI = a.getInt("index").toLong()
Log.d("FlwtchWorker", "Cwtch GetMessage " + profile + " " + conversation.toString() + " " + indexI.toString())
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, conversation, indexI)).build())
}
"GetMessageByID" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val id = a.getInt("id").toLong()
return Result.success(Data.Builder().putString("result", Cwtch.getMessageByID(profile, conversation, id)).build())
}
"GetMessageByContentHash" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val contentHash = (a.get("contentHash") as? String) ?: ""
return Result.success(Data.Builder().putString("result", Cwtch.getMessagesByContentHash(profile, conversation, contentHash)).build())
}
"UpdateMessageAttribute" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val channel = a.getInt("chanenl").toLong()
val midx = a.getInt("midx").toLong()
val key = (a.get("key") as? String) ?: ""
val value = (a.get("value") as? String) ?: ""
Cwtch.setMessageAttribute(profile, conversation, channel, midx, key, value)
}
"AcceptConversation" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
Cwtch.acceptConversation(profile, conversation)
}
"BlockContact" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
Cwtch.blockContact(profile, conversation)
}
"UnblockContact" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
Cwtch.unblockContact(profile, conversation)
}
"SendMessage" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val message = (a.get("message") as? String) ?: ""
Log.i("FlwtchWorker.kt", "SendMessage: $message")
Cwtch.sendMessage(profile, conversation, message)
}
"SendInvitation" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val target = a.getInt("target").toLong()
Cwtch.sendInvitation(profile, conversation, target)
}
"ShareFile" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val filepath = (a.get("filepath") as? String) ?: ""
Cwtch.shareFile(profile, conversation, filepath)
}
"DownloadFile" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val filepath = (a.get("filepath") as? String) ?: ""
val manifestpath = (a.get("manifestpath") as? String) ?: ""
val filekey = (a.get("filekey") as? String) ?: ""
// FIXME: Prevent spurious calls by Intent
if (profile != "") {
Cwtch.downloadFile(profile, conversation, filepath, manifestpath, filekey)
}
}
"CheckDownloadStatus" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val fileKey = (a.get("fileKey") as? String) ?: ""
Cwtch.checkDownloadStatus(profile, fileKey)
}
"VerifyOrResumeDownload" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val fileKey = (a.get("fileKey") as? String) ?: ""
Cwtch.verifyOrResumeDownload(profile, conversation, fileKey)
}
"SendProfileEvent" -> {
val onion = (a.get("onion") as? String) ?: ""
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
Cwtch.sendProfileEvent(onion, jsonEvent)
}
"SendAppEvent" -> {
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
Cwtch.sendAppEvent(jsonEvent)
}
"ResetTor" -> {
Cwtch.resetTor()
}
"ImportBundle" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val bundle = (a.get("bundle") as? String) ?: ""
Cwtch.importBundle(profile, bundle)
}
"CreateGroup" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val server = (a.get("server") as? String) ?: ""
val groupName = (a.get("groupName") as? String) ?: ""
Cwtch.createGroup(profile, server, groupName)
}
"DeleteProfile" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val pass = (a.get("pass") as? String) ?: ""
Cwtch.deleteProfile(profile, pass)
}
"ArchiveConversation" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
Cwtch.archiveConversation(profile, conversation)
}
"DeleteConversation" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
Cwtch.deleteContact(profile, conversation)
}
"SetProfileAttribute" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val key = (a.get("Key") as? String) ?: ""
val v = (a.get("Val") as? String) ?: ""
Cwtch.setProfileAttribute(profile, key, v)
}
"SetConversationAttribute" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val key = (a.get("Key") as? String) ?: ""
val v = (a.get("Val") as? String) ?: ""
Cwtch.setConversationAttribute(profile, conversation, key, v)
}
"Shutdown" -> {
Cwtch.shutdownCwtch();
return Result.success()
}
"LoadServers" -> {
val password = (a.get("Password") as? String) ?: ""
Cwtch.loadServers(password)
}
"CreateServer" -> {
val password = (a.get("Password") as? String) ?: ""
val desc = (a.get("Description") as? String) ?: ""
val autostart = (a.get("Autostart") as? Boolean) ?: false
Cwtch.createServer(password, desc, autostart)
}
"DeleteServer" -> {
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
val password = (a.get("Password") as? String) ?: ""
Cwtch.deleteServer(serverOnion, password)
}
"LaunchServers" -> {
Cwtch.launchServers()
}
"LaunchServer" -> {
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
Cwtch.launchServer(serverOnion)
}
"StopServer" -> {
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
Cwtch.stopServer(serverOnion)
}
"StopServers" -> {
Cwtch.stopServers()
}
"DestroyServers" -> {
Cwtch.destroyServers()
}
"SetServerAttribute" -> {
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
val key = (a.get("Key") as? String) ?: ""
val v = (a.get("Val") as? String) ?: ""
Cwtch.setServerAttribute(serverOnion, key, v)
}
"L10nInit" -> {
notificationSimple = (a.get("notificationSimple") as? String) ?: "New Message"
notificationConversationInfo = (a.get("notificationConversationInfo") as? String)
?: "New Message From "
}
else -> {
Log.i("FlwtchWorker", "unknown command: " + method);
return Result.failure()
}
}
"CheckDownloadStatus" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val fileKey = (a.get("fileKey") as? String) ?: ""
Cwtch.checkDownloadStatus(profile, fileKey)
}
"VerifyOrResumeDownload" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val fileKey = (a.get("fileKey") as? String) ?: ""
Cwtch.verifyOrResumeDownload(profile, conversation, fileKey)
}
"SendProfileEvent" -> {
val onion = (a.get("onion") as? String) ?: ""
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
Cwtch.sendProfileEvent(onion, jsonEvent)
}
"SendAppEvent" -> {
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
Cwtch.sendAppEvent(jsonEvent)
}
"ResetTor" -> {
Cwtch.resetTor()
}
"ImportBundle" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val bundle = (a.get("bundle") as? String) ?: ""
Cwtch.importBundle(profile, bundle)
}
"CreateGroup" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val server = (a.get("server") as? String) ?: ""
val groupName = (a.get("groupName") as? String) ?: ""
Cwtch.createGroup(profile, server, groupName)
}
"DeleteProfile" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val pass = (a.get("pass") as? String) ?: ""
Cwtch.deleteProfile(profile, pass)
}
"ArchiveConversation" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
Cwtch.archiveConversation(profile, conversation)
}
"DeleteConversation" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
Cwtch.deleteContact(profile, conversation)
}
"SetProfileAttribute" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val key = (a.get("Key") as? String) ?: ""
val v = (a.get("Val") as? String) ?: ""
Cwtch.setProfileAttribute(profile, key, v)
}
"SetConversationAttribute" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""
val conversation = a.getInt("conversation").toLong()
val key = (a.get("Key") as? String) ?: ""
val v = (a.get("Val") as? String) ?: ""
Cwtch.setConversationAttribute(profile, conversation, key, v)
}
"Shutdown" -> {
Cwtch.shutdownCwtch();
return Result.success()
}
"LoadServers" -> {
val password = (a.get("Password") as? String) ?: ""
Cwtch.loadServers(password)
}
"CreateServer" -> {
val password = (a.get("Password") as? String) ?: ""
val desc = (a.get("Description") as? String) ?: ""
val autostart = (a.get("Autostart") as? Boolean) ?: false
Cwtch.createServer(password, desc, autostart)
}
"DeleteServer" -> {
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
val password = (a.get("Password") as? String) ?: ""
Cwtch.deleteServer(serverOnion, password)
}
"LaunchServers" -> {
Cwtch.launchServers()
}
"LaunchServer" -> {
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
Cwtch.launchServer(serverOnion)
}
"StopServer" -> {
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
Cwtch.stopServer(serverOnion)
}
"StopServers" -> {
Cwtch.stopServers()
}
"DestroyServers" -> {
Cwtch.destroyServers()
}
"SetServerAttribute" -> {
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
val key = (a.get("Key") as? String) ?: ""
val v = (a.get("Val") as? String) ?: ""
Cwtch.setServerAttribute(serverOnion, key, v)
}
"L10nInit" -> {
notificationSimple = (a.get("notificationSimple") as? String) ?: "New Message"
notificationConversationInfo = (a.get("notificationConversationInfo") as? String)
?: "New Message From "
}
else -> {
Log.i("FlwtchWorker", "unknown command: " + method);
return Result.failure()
}
return Result.success()
} catch (e: Exception) {
Log.e("FlwtchWorker", "Error in handleCwtch: " + e.toString() + " :: " + e.getStackTrace())
return Result.failure()
}
return Result.success()
}
// Creates an instance of ForegroundInfo which can be used to update the
// ongoing notification.
private fun createForegroundInfo(progress: String): ForegroundInfo {
val id = "flwtch"
val title = "Flwtch"
val cancel = "Shut down"//todo: translate
val title = "Flwtch" // TODO: change
val cancel = "Shut down" // TODO: translate
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createForegroundNotificationChannel(id, id)

View File

@ -100,7 +100,7 @@ abstract class Cwtch {
void SetServerAttribute(String serverOnion, String key, String val);
// ignore: non_constant_identifier_names
void Shutdown();
Future<void> Shutdown();
// non-ffi
String defaultDownloadPath();

View File

@ -317,7 +317,7 @@ class CwtchNotifier {
server: groupInvite["ServerHost"],
status: status,
isGroup: true,
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(0)));
lastMessageTime: DateTime.now()));
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.fromMillisecondsSinceEpoch(0));
}
}

View File

@ -179,9 +179,9 @@ class FlwtchState extends State<Flwtch> with WindowListener {
}
Future<void> shutdown() async {
cwtch.Shutdown();
await cwtch.Shutdown();
// Wait a few seconds as shutting down things takes a little time..
Future.delayed(Duration(seconds: 2)).then((value) {
Future.delayed(Duration(seconds: 1)).then((value) {
if (Platform.isAndroid) {
SystemNavigator.pop();
} else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {

View File

@ -80,7 +80,9 @@ class ProfileInfoState extends ChangeNotifier {
List<dynamic> servers = jsonDecode(serversJson);
this._servers.replace(servers.map((server) {
// TODO Keys...
return RemoteServerInfoState(server["onion"], server["identifier"], server["description"], server["status"]);
var preSyncStartTime = DateTime.tryParse(server["syncProgress"]["startTime"]);
var lastMessageTime = DateTime.tryParse(server["syncProgress"]["lastMessageTime"]);
return RemoteServerInfoState(server["onion"], server["identifier"], server["description"], server["status"], lastPreSyncMessageTime: preSyncStartTime, mostRecentMessageTime: lastMessageTime);
}));
this._contacts.contacts.forEach((contact) {

View File

@ -12,7 +12,12 @@ class RemoteServerInfoState extends ChangeNotifier {
double syncProgress = 0;
DateTime lastPreSyncMessagTime = new DateTime(2020);
RemoteServerInfoState(this.onion, this.identifier, this.description, this._status);
RemoteServerInfoState(this.onion, this.identifier, this.description, this._status, {lastPreSyncMessageTime, mostRecentMessageTime}) {
if (_status == "Authenticated") {
this.lastPreSyncMessagTime = lastPreSyncMessageTime;
updateSyncProgressFor(mostRecentMessageTime);
}
}
void updateDescription(String newDescription) {
this.description = newDescription;

View File

@ -117,7 +117,7 @@ class NixNotificationManager implements NotificationsManager {
}
NotificationsManager newDesktopNotificationsManager(Future<void> Function(String profileOnion, int convoId) notificationSelectConvo) {
if (Platform.isLinux || Platform.isMacOS) {
if ((Platform.isLinux && !Platform.isAndroid) || Platform.isMacOS) {
try {
return NixNotificationManager(notificationSelectConvo);
} catch (e) {

View File

@ -32,6 +32,7 @@ class _ContactRowState extends State<ContactRow> {
visible: contact.isGroup && contact.status == "Authenticated",
child: LinearProgressIndicator(
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
backgroundColor: Provider.of<Settings>(context).theme.defaultButtonDisabledColor,
value: Provider.of<ProfileInfoState>(context).serverList.getServer(contact.server ?? "")?.syncProgress,
));
}

View File

@ -61,6 +61,7 @@ class _RemoteServerRowState extends State<RemoteServerRow> {
visible: server.status == "Authenticated",
child: LinearProgressIndicator(
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
backgroundColor: Provider.of<Settings>(context).theme.defaultButtonDisabledColor,
value: server.syncProgress,
)),
],

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.6.1+26
version: 1.6.2+27
environment:
sdk: ">=2.15.0 <3.0.0"