Compare commits

...

4 Commits

4 changed files with 163 additions and 118 deletions
@@ -207,7 +207,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
.build()
)
}
ApiMethods.UserAgent = "Grayjay Android (${BuildConfig.VERSION_CODE})";
Thread.setDefaultUncaughtExceptionHandler { _, throwable ->
@@ -249,6 +249,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
override fun onCreate(savedInstanceState: Bundle?) {
Logger.i(TAG, "MainActivity Starting");
ApiMethods.initCache(cacheDir);
StateApp.instance.setGlobalContext(this, lifecycleScope);
StateApp.instance.mainAppStarting(this);
@@ -23,6 +23,7 @@ import com.futo.polycentric.core.getClaimIfValid
import com.futo.polycentric.core.getValidClaims
import com.google.protobuf.ByteString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@@ -56,83 +57,102 @@ class PolycentricCache {
private val _taskGetProfile = BatchedTaskHandler<PublicKey, CachedPolycentricProfile>(_scope,
{ system ->
val signedEventsList = ApiMethods.getQueryLatest(
SERVER,
system.toProto(),
listOf(
ContentType.BANNER.value,
ContentType.AVATAR.value,
ContentType.USERNAME.value,
ContentType.DESCRIPTION.value,
ContentType.STORE.value,
ContentType.SERVER.value,
ContentType.STORE_DATA.value,
ContentType.PROMOTION_BANNER.value,
ContentType.PROMOTION.value,
ContentType.MEMBERSHIP_URLS.value,
ContentType.DONATION_DESTINATIONS.value
)
).eventsList.map { e -> SignedEvent.fromProto(e) };
val signedProfileEvents = signedEventsList.groupBy { e -> e.event.contentType }
.map { (_, events) -> events.maxBy { it.event.unixMilliseconds ?: 0 } };
val storageSystemState = StorageTypeSystemState.create()
for (signedEvent in signedProfileEvents) {
storageSystemState.update(signedEvent.event)
}
val signedClaimEvents = ApiMethods.getQueryIndex(
SERVER,
system.toProto(),
ContentType.CLAIM.value,
limit = 200
).eventsList.map { e -> SignedEvent.fromProto(e) };
val ownedClaims: ArrayList<OwnedClaim> = arrayListOf()
for (signedEvent in signedClaimEvents) {
if (signedEvent.event.contentType != ContentType.CLAIM.value) {
continue;
coroutineScope {
// Launch concurrent requests for getQueryLatest and getQueryIndex
val signedEventsDeferred = async {
ApiMethods.getQueryLatest(
SERVER,
system.toProto(),
listOf(
ContentType.BANNER.value,
ContentType.AVATAR.value,
ContentType.USERNAME.value,
ContentType.DESCRIPTION.value,
ContentType.STORE.value,
ContentType.SERVER.value,
ContentType.STORE_DATA.value,
ContentType.PROMOTION_BANNER.value,
ContentType.PROMOTION.value,
ContentType.MEMBERSHIP_URLS.value,
ContentType.DONATION_DESTINATIONS.value
)
).eventsList.map { e -> SignedEvent.fromProto(e) }
}
val response = ApiMethods.getQueryReferences(
SERVER,
Protocol.Reference.newBuilder()
.setReference(signedEvent.toPointer().toProto().toByteString())
.setReferenceType(2)
.build(),
null,
Protocol.QueryReferencesRequestEvents.newBuilder()
.setFromType(ContentType.VOUCH.value)
.build()
);
val ownedClaim = response.itemsList.map { SignedEvent.fromProto(it.event) }.getClaimIfValid(signedEvent);
if (ownedClaim != null) {
ownedClaims.add(ownedClaim);
val signedClaimEventsDeferred = async {
ApiMethods.getQueryIndex(
SERVER,
system.toProto(),
ContentType.CLAIM.value,
limit = 200
).eventsList.map { e -> SignedEvent.fromProto(e) }
}
}
Logger.i(TAG, "Retrieved profile (ownedClaims = $ownedClaims)");
val systemState = SystemState.fromStorageTypeSystemState(storageSystemState);
return@BatchedTaskHandler CachedPolycentricProfile(PolycentricProfile(system, systemState, ownedClaims));
// Await both requests concurrently
val signedEventsList = signedEventsDeferred.await()
val signedClaimEvents = signedClaimEventsDeferred.await()
// Process profile events
val signedProfileEvents = signedEventsList
.groupBy { it.event.contentType }
.map { (_, events) -> events.maxByOrNull { it.event.unixMilliseconds ?: 0 } }
.filterNotNull()
val storageSystemState = StorageTypeSystemState.create().apply {
signedProfileEvents.forEach { update(it.event) }
}
// Launch concurrent requests for getQueryReferences
val ownedClaimsDeferred = signedClaimEvents.mapNotNull { signedEvent ->
if (signedEvent.event.contentType != ContentType.CLAIM.value) {
null
} else {
async {
try {
val response = ApiMethods.getQueryReferences(
SERVER,
Protocol.Reference.newBuilder()
.setReference(signedEvent.toPointer().toProto().toByteString())
.setReferenceType(2)
.build(),
null,
Protocol.QueryReferencesRequestEvents.newBuilder()
.setFromType(ContentType.VOUCH.value)
.build()
)
response.itemsList
.map { SignedEvent.fromProto(it.event) }
.getClaimIfValid(signedEvent)
} catch (e: Exception) {
Logger.e(TAG, "Failed to get query references for ${signedEvent.toPointer()}", e)
null
}
}
}
}.filterNotNull()
val ownedClaims = ownedClaimsDeferred.mapNotNull { it.await() }.toCollection(ArrayList())
Logger.i(TAG, "Retrieved profile (ownedClaims = $ownedClaims)")
val systemState = SystemState.fromStorageTypeSystemState(storageSystemState)
CachedPolycentricProfile(PolycentricProfile(system, systemState, ownedClaims))
}
},
{ system -> return@BatchedTaskHandler getCachedProfile(system); },
{ system, result ->
synchronized(_cache) {
_profileCache[system] = result;
_profileCache[system] = result
if (result.profile != null) {
for (claim in result.profile.ownedClaims) {
val urls = claim.claim.resolveChannelUrls();
for (url in urls)
_profileUrlCache.map[url] = result;
result.profile?.ownedClaims?.forEach { claim ->
claim.claim.resolveChannelUrls().forEach { url ->
_profileUrlCache.map[url] = result
}
}
_profileUrlCache.save();
_profileUrlCache.save()
}
});
})
private val _batchTaskGetClaims = BatchedTaskHandler<PlatformID, CachedOwnedClaims>(_scope,
{ id ->
@@ -44,9 +44,11 @@ import com.futo.polycentric.core.toBase64
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
import com.google.protobuf.ByteString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import userpackage.Protocol
import userpackage.Protocol.Reference
@@ -510,67 +512,88 @@ class StatePolycentric {
};
}
private suspend fun mapQueryReferences(contextUrl: String, response: Protocol.QueryReferencesResponse): List<PolycentricPlatformComment> {
return response.itemsList.mapNotNull {
val sev = SignedEvent.fromProto(it.event);
val ev = sev.event;
if (ev.contentType != ContentType.POST.value) {
return@mapNotNull null;
}
private suspend fun mapQueryReferences(contextUrl: String, response: Protocol.QueryReferencesResponse): List<PolycentricPlatformComment> = coroutineScope {
response.itemsList.map { item ->
async {
val sev = SignedEvent.fromProto(item.event)
val ev = sev.event
if (ev.contentType != ContentType.POST.value) {
return@async null
}
try {
val post = Protocol.Post.parseFrom(ev.content);
val likes = it.countsList[0];
val dislikes = it.countsList[1];
val replies = it.countsList[2];
try {
val post = Protocol.Post.parseFrom(ev.content)
val likes = item.countsList.getOrNull(0)
val dislikes = item.countsList.getOrNull(1)
val replies = item.countsList.getOrNull(2)
val profileEvents = ApiMethods.getQueryLatest(
PolycentricCache.SERVER,
ev.system.toProto(),
listOf(
ContentType.AVATAR.value,
ContentType.USERNAME.value
)
).eventsList.map { e -> SignedEvent.fromProto(e) }.groupBy { e -> e.event.contentType }
.map { (_, events) -> events.maxBy { x -> x.event.unixMilliseconds ?: 0 } };
val profileEvents = ApiMethods.getQueryLatest(
PolycentricCache.SERVER,
ev.system.toProto(),
listOf(
ContentType.AVATAR.value,
ContentType.USERNAME.value
)
).eventsList.map { e -> SignedEvent.fromProto(e) }.groupBy { e -> e.event.contentType }
.map { (_, events) -> events.maxBy { x -> x.event.unixMilliseconds ?: 0 } };
val nameEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.USERNAME.value };
val avatarEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.AVATAR.value };
val imageBundle = if (avatarEvent != null) {
val lwwElementValue = avatarEvent.event.lwwElement?.value;
if (lwwElementValue != null) {
Protocol.ImageBundle.parseFrom(lwwElementValue)
val nameEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.USERNAME.value };
val avatarEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.AVATAR.value };
val imageBundle = if (avatarEvent != null) {
val lwwElementValue = avatarEvent.event.lwwElement?.value;
if (lwwElementValue != null) {
Protocol.ImageBundle.parseFrom(lwwElementValue)
} else {
null
}
} else {
null
}
} else {
val unixMilliseconds = ev.unixMilliseconds
//TODO: Don't use single hardcoded server here
val systemLinkUrl = ev.system.systemToURLInfoSystemLinkUrl(listOf(PolycentricCache.SERVER))
val dp_25 = 25.dp(StateApp.instance.context.resources)
PolycentricPlatformComment(
contextUrl = contextUrl,
author = PlatformAuthorLink(
id = PlatformID(
"polycentric",
systemLinkUrl,
null,
ClaimType.POLYCENTRIC.value.toInt()
),
name = nameEvent?.event?.lwwElement?.value?.decodeToString() ?: "Unknown",
url = systemLinkUrl,
thumbnail = imageBundle?.selectBestImage(dp_25 * dp_25)?.let { img ->
img.toURLInfoSystemLinkUrl(
ev.system.toProto(),
img.process,
listOf(PolycentricCache.SERVER)
)
},
subscribers = null
),
msg = if (post.content.length > PolycentricPlatformComment.MAX_COMMENT_SIZE) {
post.content.substring(0, PolycentricPlatformComment.MAX_COMMENT_SIZE)
} else {
post.content
},
rating = RatingLikeDislikes(likes ?: 0, dislikes ?: 0),
date = unixMilliseconds?.let {
Instant.ofEpochMilli(it).atOffset(ZoneOffset.UTC)
} ?: OffsetDateTime.MIN,
replyCount = replies?.toInt() ?: 0,
eventPointer = sev.toPointer(),
parentReference = sev.event.references.getOrNull(0)
)
} catch (e: Throwable) {
null
}
val unixMilliseconds = ev.unixMilliseconds
//TODO: Don't use single hardcoded sderver here
val systemLinkUrl = ev.system.systemToURLInfoSystemLinkUrl(listOf(PolycentricCache.SERVER));
val dp_25 = 25.dp(StateApp.instance.context.resources)
return@mapNotNull PolycentricPlatformComment(
contextUrl = contextUrl,
author = PlatformAuthorLink(
id = PlatformID("polycentric", systemLinkUrl, null, ClaimType.POLYCENTRIC.value.toInt()),
name = nameEvent?.event?.lwwElement?.value?.decodeToString() ?: "Unknown",
url = systemLinkUrl,
thumbnail = imageBundle?.selectBestImage(dp_25 * dp_25)?.let { img -> img.toURLInfoSystemLinkUrl(ev.system.toProto(), img.process, listOf(PolycentricCache.SERVER)) },
subscribers = null
),
msg = if (post.content.count() > PolycentricPlatformComment.MAX_COMMENT_SIZE) post.content.substring(0, PolycentricPlatformComment.MAX_COMMENT_SIZE) else post.content,
rating = RatingLikeDislikes(likes, dislikes),
date = if (unixMilliseconds != null) Instant.ofEpochMilli(unixMilliseconds).atOffset(ZoneOffset.UTC) else OffsetDateTime.MIN,
replyCount = replies.toInt(),
eventPointer = sev.toPointer(),
parentReference = sev.event.references.getOrNull(0)
);
} catch (e: Throwable) {
return@mapNotNull null;
}
};
}.awaitAll().filterNotNull()
}
companion object {