android: cancel avatar operations before native teardown and bound image preparation
This commit is contained in:
1 parent
15d0772a20
commit
9c70eab075
3 files changed
+16
-9
No files matched your search
@@ -27,6 +27,7 @@ class AvatarRepository(context: Context) {
|
||||
private val app = context.applicationContext
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
private val transfers = Semaphore(2)
|
||||
private val preparations = Semaphore(2)
|
||||
private val ids = AtomicLong(System.nanoTime())
|
||||
private var generation = 0L
|
||||
private var account = 0L
|
||||
@@ -73,7 +74,7 @@ class AvatarRepository(context: Context) {
|
||||
}
|
||||
|
||||
private data class Pending(val type: Int, val group: Long, val server: Long, val owner: Long,
|
||||
val command: Int, val result: CompletableDeferred<ByteArray>)
|
||||
val command: Int, val result: CompletableDeferred<ByteArray>, var cancelled: Boolean = false)
|
||||
private fun log(level: String, message: String) = LogManager.addLog(level, "Avatar", message)
|
||||
private fun changed() { _revision.value++ }
|
||||
private fun alive(epoch: Long) { if (generation != epoch || account == 0L) throw CancellationException("avatar session changed") }
|
||||
@@ -157,11 +158,15 @@ class AvatarRepository(context: Context) {
|
||||
private fun reset() {
|
||||
log("INFO", "Reset account=${AvatarProtocol.hex(account)} pending=${pending.size} cacheBytes=${cache.size()}")
|
||||
generation++
|
||||
pending.forEach { (id, p) ->
|
||||
if (p.type in 49..50 && NativeLib.isReady()) { p.cancelled = true; NativeLib.cancelAvatar(id) }
|
||||
}
|
||||
scope.coroutineContext.cancelChildren()
|
||||
pending.clear(); loads.clear(); replications.clear(); dirtyGroups.clear(); memberRequests.clear(); editTokens.clear()
|
||||
account = 0; defaultAvatar = null; cache.evictAll()
|
||||
_members.value = emptyMap(); _groups.value = emptyMap(); _edits.value = emptyMap(); changed()
|
||||
}
|
||||
fun stop() { reset() }
|
||||
private fun start() {
|
||||
val uid = NativeLib.getMyNodeId()
|
||||
if (uid == 0L || uid == account) return
|
||||
@@ -188,7 +193,8 @@ class AvatarRepository(context: Context) {
|
||||
enqueue: (Long, Long) -> Boolean): ByteArray {
|
||||
val id = ids.incrementAndGet(); val gid = AvatarProtocol.node(group)
|
||||
val result = CompletableDeferred<ByteArray>()
|
||||
pending[id] = Pending(type, gid, server, owner, command, result)
|
||||
val operation = Pending(type, gid, server, owner, command, result)
|
||||
pending[id] = operation
|
||||
var complete = false
|
||||
try {
|
||||
if (!enqueue(id, gid)) throw IOException("native enqueue failed id=$id type=$type group=$group")
|
||||
@@ -197,7 +203,7 @@ class AvatarRepository(context: Context) {
|
||||
return data
|
||||
} finally {
|
||||
pending.remove(id)
|
||||
if (!complete && type in 49..50 && NativeLib.isReady()) NativeLib.cancelAvatar(id)
|
||||
if (!complete && !operation.cancelled && type in 49..50 && NativeLib.isReady()) NativeLib.cancelAvatar(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +437,7 @@ class AvatarRepository(context: Context) {
|
||||
}
|
||||
|
||||
/** Чтение URI, EXIF, crop и кодирование выполняются вне UI и потока ядра. */
|
||||
private suspend fun prepare(uri: Uri, group: Boolean): ByteArray = withContext(Dispatchers.IO) {
|
||||
private suspend fun prepare(uri: Uri, group: Boolean): ByteArray = preparations.withPermit { withContext(Dispatchers.IO) {
|
||||
val temp = File.createTempFile("avatar-source-", ".image", app.cacheDir)
|
||||
try {
|
||||
app.contentResolver.openInputStream(uri)?.use { input ->
|
||||
@@ -445,7 +451,7 @@ class AvatarRepository(context: Context) {
|
||||
}
|
||||
} ?: throw IOException("cannot read selected image")
|
||||
val (w, h) = PhotoUtil.orientedDimensions(temp.path)
|
||||
require(w > 0 && h > 0 && w.toLong() * h <= 100_000_000) { "invalid source dimensions $w x $h" }
|
||||
require(w > 0 && h > 0 && w.toLong() * h <= 16L * 1024 * 1024) { "invalid source dimensions $w x $h" }
|
||||
var sample = 1
|
||||
while (maxOf(w, h) / sample > 1024) sample *= 2
|
||||
val decoded = PhotoUtil.decodeOriented(temp.path, sample) ?: throw IOException("source decode failed")
|
||||
@@ -468,5 +474,5 @@ class AvatarRepository(context: Context) {
|
||||
decoded.recycle()
|
||||
}
|
||||
} finally { if (!temp.delete()) log("WARN", "Cannot delete source temp=$temp") }
|
||||
}
|
||||
} }
|
||||
}
|
||||
@@ -42,7 +42,7 @@ object NativeLib {
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
runBlocking(Dispatchers.Main.immediate) { CallController.reset() }
|
||||
runBlocking(Dispatchers.Main.immediate) { CallController.reset(); ChatApplication.instance.avatars.stop() }
|
||||
if (!isReady()) return
|
||||
nativeStop()
|
||||
}
|
||||
@@ -54,12 +54,12 @@ object NativeLib {
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
runBlocking(Dispatchers.Main.immediate) { CallController.reset() }
|
||||
runBlocking(Dispatchers.Main.immediate) { CallController.reset(); ChatApplication.instance.avatars.stop() }
|
||||
nativeDestroy()
|
||||
}
|
||||
|
||||
fun restart(dbPath: String, controlPort: Int, configText: String): Boolean {
|
||||
runBlocking(Dispatchers.Main.immediate) { CallController.reset() }
|
||||
runBlocking(Dispatchers.Main.immediate) { CallController.reset(); ChatApplication.instance.avatars.stop() }
|
||||
return nativeRestart(configText)
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ fun GroupPropertiesDialog(channel: Channel, onDismiss: () -> Unit) {
|
||||
Text(stringResource(R.string.sound), modifier = Modifier.weight(1f))
|
||||
Switch(checked = soundOn, onCheckedChange = {
|
||||
soundOn = it; NotificationSound.setMuted(channel.id, !it)
|
||||
NativeLib.setSoundOnMessage(channel.id, it)
|
||||
scope.launch { ChatApplication.instance.configProvider.setChannelMuted(channel.id, !it) }
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user