Notification improvements, Polycentric subscription parallelization, Cache load parallelization

This commit is contained in:
Kelvin 2023-11-02 22:23:24 +01:00
parent f8ee340499
commit 88ca90c13a
6 changed files with 99 additions and 25 deletions

View File

@ -2,14 +2,24 @@ package com.futo.platformplayer
import android.content.Context import android.content.Context
import android.webkit.CookieManager import android.webkit.CookieManager
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.caoccao.javet.values.primitive.V8ValueInteger import com.caoccao.javet.values.primitive.V8ValueInteger
import com.caoccao.javet.values.primitive.V8ValueString import com.caoccao.javet.values.primitive.V8ValueString
import com.futo.platformplayer.activities.SettingsActivity
import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.platforms.js.JSClient
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor
import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.background.BackgroundWorker
import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.serializers.FlexibleBooleanSerializer import com.futo.platformplayer.serializers.FlexibleBooleanSerializer
@ -28,6 +38,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import java.util.UUID
import java.util.concurrent.TimeUnit
import java.util.stream.IntStream.range import java.util.stream.IntStream.range
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -87,11 +99,23 @@ class SettingsDev : FragmentedStorageFileJson() {
val cookieManager: CookieManager = CookieManager.getInstance() val cookieManager: CookieManager = CookieManager.getInstance()
cookieManager.removeAllCookies(null); cookieManager.removeAllCookies(null);
} }
@FormField(R.string.test_background_worker, FieldForm.BUTTON,
R.string.test_background_worker_description, 3)
fun triggerBackgroundUpdate() {
val act = SettingsActivity.getActivity()!!;
UIDialogs.toast(SettingsActivity.getActivity()!!, "Starting test background worker");
val wm = WorkManager.getInstance(act);
val req = OneTimeWorkRequestBuilder<BackgroundWorker>()
.setInputData(Data.Builder().putBoolean("bypassMainCheck", true).build())
.build();
wm.enqueue(req);
}
@Contextual @Contextual
@Transient @Transient
@FormField(R.string.v8_benchmarks, FieldForm.GROUP, @FormField(R.string.v8_benchmarks, FieldForm.GROUP,
R.string.various_benchmarks_using_the_integrated_v8_engine, 3) R.string.various_benchmarks_using_the_integrated_v8_engine, 4)
val v8Benchmarks: V8Benchmarks = V8Benchmarks(); val v8Benchmarks: V8Benchmarks = V8Benchmarks();
class V8Benchmarks { class V8Benchmarks {
@FormField( @FormField(
@ -139,7 +163,7 @@ class SettingsDev : FragmentedStorageFileJson() {
@FormField( @FormField(
R.string.test_v8_communication_speed, FieldForm.BUTTON, R.string.test_v8_communication_speed, FieldForm.BUTTON,
R.string.tests_v8_communication_speeds, 2 R.string.tests_v8_communication_speeds, 4
) )
fun testV8RunSpeeds() { fun testV8RunSpeeds() {
var plugin: V8Plugin? = null; var plugin: V8Plugin? = null;

View File

@ -4,6 +4,8 @@ import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.media.MediaSession2Service.MediaNotification import android.media.MediaSession2Service.MediaNotification
import androidx.concurrent.futures.CallbackToFutureAdapter import androidx.concurrent.futures.CallbackToFutureAdapter
import androidx.concurrent.futures.ResolvableFuture import androidx.concurrent.futures.ResolvableFuture
@ -11,8 +13,12 @@ import androidx.core.app.NotificationCompat
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.ListenableWorker import androidx.work.ListenableWorker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.getNowDiffSeconds import com.futo.platformplayer.getNowDiffSeconds
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.models.Subscription
@ -29,10 +35,10 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.time.OffsetDateTime import java.time.OffsetDateTime
class BackgroundWorker(private val appContext: Context, workerParams: WorkerParameters) : class BackgroundWorker(private val appContext: Context, private val workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) { CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
if(StateApp.instance.isMainActive) { if(StateApp.instance.isMainActive && !inputData.getBoolean("bypassMainCheck", false)) {
Logger.i("BackgroundWorker", "CANCELLED"); Logger.i("BackgroundWorker", "CANCELLED");
return Result.success(); return Result.success();
} }
@ -86,9 +92,10 @@ class BackgroundWorker(private val appContext: Context, workerParams: WorkerPara
val newSubChanges = hashSetOf<Subscription>(); val newSubChanges = hashSetOf<Subscription>();
val newItems = mutableListOf<IPlatformContent>(); val newItems = mutableListOf<IPlatformContent>();
val now = OffsetDateTime.now();
val contentNotifs = mutableListOf<Pair<Subscription, IPlatformContent>>(); val contentNotifs = mutableListOf<Pair<Subscription, IPlatformContent>>();
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
StateSubscriptions.instance.getSubscriptionsFeedWithExceptions(true, false,this, { progress, total -> val results = StateSubscriptions.instance.getSubscriptionsFeedWithExceptions(true, false,this, { progress, total ->
Logger.i("BackgroundWorker", "SUBSCRIPTION PROGRESS: ${progress}/${total}"); Logger.i("BackgroundWorker", "SUBSCRIPTION PROGRESS: ${progress}/${total}");
synchronized(manager) { synchronized(manager) {
@ -103,29 +110,46 @@ class BackgroundWorker(private val appContext: Context, workerParams: WorkerPara
synchronized(newSubChanges) { synchronized(newSubChanges) {
if(!newSubChanges.contains(sub)) { if(!newSubChanges.contains(sub)) {
newSubChanges.add(sub); newSubChanges.add(sub);
if(sub.doNotifications) if(sub.doNotifications && content.datetime?.let { it < now } == true)
contentNotifs.add(Pair(sub, content)); contentNotifs.add(Pair(sub, content));
} }
newItems.add(content); newItems.add(content);
} }
}); });
//Only for testing notifications
val testNotifs = 0;
if(contentNotifs.size == 0 && testNotifs > 0) {
results.first.getResults().filter { it is IPlatformVideo && it.datetime?.let { it < now } == true }
.take(testNotifs).forEach {
contentNotifs.add(Pair(StateSubscriptions.instance.getSubscriptions().first(), it));
}
}
} }
manager.cancel(12); manager.cancel(12);
if(newItems.size > 0) { if(contentNotifs.size > 0) {
try { try {
val items = contentNotifs.take(5).toList() val items = contentNotifs.take(5).toList()
for(i in items.indices) { for(i in items.indices) {
val contentNotif = items.get(i); val contentNotif = items.get(i);
manager.notify(13 + i, NotificationCompat.Builder(appContext, notificationChannel.id) val thumbnail = if(contentNotif.second is IPlatformVideo) (contentNotif.second as IPlatformVideo).thumbnails.getHQThumbnail()
.setSmallIcon(com.futo.platformplayer.R.drawable.foreground) else null;
.setContentTitle("New video by [${contentNotif.first.channel.name}]") if(thumbnail != null)
.setContentText("${contentNotif.second.name}") Glide.with(appContext).asBitmap()
.setSilent(true) .load(thumbnail)
.setContentIntent(PendingIntent.getActivity(this.appContext, 0, MainActivity.getVideoIntent(this.appContext, contentNotif.second.url), .into(object: CustomTarget<Bitmap>() {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)) override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
.setChannelId(notificationChannel.id).build()); notifyNewContent(manager, notificationChannel, 13 + i, contentNotif.first, contentNotif.second, resource);
}
override fun onLoadCleared(placeholder: Drawable?) {}
override fun onLoadFailed(errorDrawable: Drawable?) {
notifyNewContent(manager, notificationChannel, 13 + i, contentNotif.first, contentNotif.second, null);
}
})
else
notifyNewContent(manager, notificationChannel, 13 + i, contentNotif.first, contentNotif.second, null);
} }
} }
catch(ex: Throwable) { catch(ex: Throwable) {
@ -140,4 +164,20 @@ class BackgroundWorker(private val appContext: Context, workerParams: WorkerPara
.setSilent(true) .setSilent(true)
.setChannelId(notificationChannel.id).build());*/ .setChannelId(notificationChannel.id).build());*/
} }
fun notifyNewContent(manager: NotificationManager, notificationChannel: NotificationChannel, id: Int, sub: Subscription, content: IPlatformContent, thumbnail: Bitmap? = null) {
val notifBuilder = NotificationCompat.Builder(appContext, notificationChannel.id)
.setSmallIcon(com.futo.platformplayer.R.drawable.foreground)
.setContentTitle("New by [${sub.channel.name}]")
.setContentText("${content.name}")
.setSilent(true)
.setContentIntent(PendingIntent.getActivity(this.appContext, 0, MainActivity.getVideoIntent(this.appContext, content.url),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE))
.setChannelId(notificationChannel.id);
if(thumbnail != null) {
//notifBuilder.setLargeIcon(thumbnail);
notifBuilder.setStyle(NotificationCompat.BigPictureStyle().bigPicture(thumbnail).bigLargeIcon(null as Bitmap?));
}
manager.notify(id, notifBuilder.build());
}
} }

View File

@ -18,10 +18,11 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.OffsetDateTime import java.time.OffsetDateTime
import kotlin.streams.toList
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
class ChannelContentCache { class ChannelContentCache {
private val _targetCacheSize = 2000; private val _targetCacheSize = 3000;
val _channelCacheDir = FragmentedStorage.getOrCreateDirectory("channelCache"); val _channelCacheDir = FragmentedStorage.getOrCreateDirectory("channelCache");
val _channelContents: HashMap<String, ManagedStore<SerializedPlatformContent>>; val _channelContents: HashMap<String, ManagedStore<SerializedPlatformContent>>;
init { init {
@ -29,11 +30,11 @@ class ChannelContentCache {
val initializeTime = measureTimeMillis { val initializeTime = measureTimeMillis {
_channelContents = HashMap(allFiles _channelContents = HashMap(allFiles
.filter { it.isDirectory } .filter { it.isDirectory }
.associate { .parallelStream().map {
Pair(it.name, FragmentedStorage.storeJson(_channelCacheDir, it.name, PlatformContentSerializer()) Pair(it.name, FragmentedStorage.storeJson(_channelCacheDir, it.name, PlatformContentSerializer())
.withoutBackup() .withoutBackup()
.load()) .load())
}); }.toList().associate { it })
} }
val minDays = OffsetDateTime.now().minusDays(10); val minDays = OffsetDateTime.now().minusDays(10);
val totalItems = _channelContents.map { it.value.count() }.sum(); val totalItems = _channelContents.map { it.value.count() }.sum();

View File

@ -122,6 +122,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
_toolbarContentView = findViewById(R.id.container_toolbar_content); _toolbarContentView = findViewById(R.id.container_toolbar_content);
var filteredNextPageCounter = 0;
_nextPageHandler = TaskHandler<TPager, List<TResult>>({fragment.lifecycleScope}, { _nextPageHandler = TaskHandler<TPager, List<TResult>>({fragment.lifecycleScope}, {
if (it is IAsyncPager<*>) if (it is IAsyncPager<*>)
it.nextPageAsync(); it.nextPageAsync();
@ -141,10 +142,15 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
val filteredResults = filterResults(it); val filteredResults = filterResults(it);
recyclerData.results.addAll(filteredResults); recyclerData.results.addAll(filteredResults);
recyclerData.resultsUnfiltered.addAll(it); recyclerData.resultsUnfiltered.addAll(it);
if(filteredResults.isEmpty()) if(filteredResults.isEmpty()) {
filteredNextPageCounter++
if(filteredNextPageCounter <= 4)
loadNextPage() loadNextPage()
else }
else {
filteredNextPageCounter = 0;
recyclerData.adapter.notifyItemRangeInserted(recyclerData.adapter.childToParentPosition(posBefore), filteredResults.size); recyclerData.adapter.notifyItemRangeInserted(recyclerData.adapter.childToParentPosition(posBefore), filteredResults.size);
}
}.exception<Throwable> { }.exception<Throwable> {
Logger.w(TAG, "Failed to load next page.", it); Logger.w(TAG, "Failed to load next page.", it);
UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_next_page), it, { UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_next_page), it, {

View File

@ -39,6 +39,7 @@ import java.util.concurrent.ForkJoinTask
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import kotlin.streams.toList
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
/*** /***
@ -250,12 +251,12 @@ class StateSubscriptions {
} }
val usePolycentric = true; val usePolycentric = true;
val subUrls = getSubscriptions().associateWith { val subUrls = getSubscriptions().parallelStream().map {
if(usePolycentric) if(usePolycentric)
StatePolycentric.instance.getChannelUrls(it.channel.url, it.channel.id); Pair(it, StatePolycentric.instance.getChannelUrls(it.channel.url, it.channel.id));
else else
listOf(it.channel.url); Pair(it, listOf(it.channel.url));
}; }.toList().associate { it };
val result = algo.getSubscriptions(subUrls); val result = algo.getSubscriptions(subUrls);
return Pair(result.pager, result.exceptions); return Pair(result.pager, result.exceptions);

View File

@ -276,6 +276,8 @@
<string name="change_the_external_storage_for_download_files">Change the external storage for download files</string> <string name="change_the_external_storage_for_download_files">Change the external storage for download files</string>
<string name="clear_cookies">Clear Cookies</string> <string name="clear_cookies">Clear Cookies</string>
<string name="clear_cookies_on_logout">Clear Cookies on Logout</string> <string name="clear_cookies_on_logout">Clear Cookies on Logout</string>
<string name="test_background_worker">Test Background Worker</string>
<string name="test_background_worker_description"></string>
<string name="clear_payment">Clear Payment</string> <string name="clear_payment">Clear Payment</string>
<string name="clears_cookies_when_you_log_out">Clears cookies when you log out</string> <string name="clears_cookies_when_you_log_out">Clears cookies when you log out</string>
<string name="clears_in_app_browser_cookies">Clears in-app browser cookies</string> <string name="clears_in_app_browser_cookies">Clears in-app browser cookies</string>