|
|
|
|
@ -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") } |
|
|
|
|
} |
|
|
|
|
} } |
|
|
|
|
} |
|
|
|
|
|