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.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.V8ValueString
import com.futo.platformplayer.activities.SettingsActivity
import com.futo.platformplayer.api.http.ManagedHttpClient
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.SourcePluginConfig
import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.background.BackgroundWorker
import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.serializers.FlexibleBooleanSerializer
@ -28,6 +38,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import java.util.UUID
import java.util.concurrent.TimeUnit
import java.util.stream.IntStream.range
import kotlin.system.measureTimeMillis
@ -87,11 +99,23 @@ class SettingsDev : FragmentedStorageFileJson() {
val cookieManager: CookieManager = CookieManager.getInstance()
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
@Transient
@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();
class V8Benchmarks {
@FormField(
@ -139,7 +163,7 @@ class SettingsDev : FragmentedStorageFileJson() {
@FormField(
R.string.test_v8_communication_speed, FieldForm.BUTTON,
R.string.tests_v8_communication_speeds, 2
R.string.tests_v8_communication_speeds, 4
)
fun testV8RunSpeeds() {
var plugin: V8Plugin? = null;

View file

@ -4,6 +4,8 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.media.MediaSession2Service.MediaNotification
import androidx.concurrent.futures.CallbackToFutureAdapter
import androidx.concurrent.futures.ResolvableFuture
@ -11,8 +13,12 @@ import androidx.core.app.NotificationCompat
import androidx.work.CoroutineWorker
import androidx.work.ListenableWorker
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.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.getNowDiffSeconds
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.Subscription
@ -29,10 +35,10 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
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) {
override suspend fun doWork(): Result {
if(StateApp.instance.isMainActive) {
if(StateApp.instance.isMainActive && !inputData.getBoolean("bypassMainCheck", false)) {
Logger.i("BackgroundWorker", "CANCELLED");
return Result.success();
}
@ -86,9 +92,10 @@ class BackgroundWorker(private val appContext: Context, workerParams: WorkerPara
val newSubChanges = hashSetOf<Subscription>();
val newItems = mutableListOf<IPlatformContent>();
val now = OffsetDateTime.now();
val contentNotifs = mutableListOf<Pair<Subscription, IPlatformContent>>();
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}");
synchronized(manager) {
@ -103,29 +110,46 @@ class BackgroundWorker(private val appContext: Context, workerParams: WorkerPara
synchronized(newSubChanges) {
if(!newSubChanges.contains(sub)) {
newSubChanges.add(sub);
if(sub.doNotifications)
if(sub.doNotifications && content.datetime?.let { it < now } == true)
contentNotifs.add(Pair(sub, 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);
if(newItems.size > 0) {
if(contentNotifs.size > 0) {
try {
val items = contentNotifs.take(5).toList()
for(i in items.indices) {
val contentNotif = items.get(i);
manager.notify(13 + i, NotificationCompat.Builder(appContext, notificationChannel.id)
.setSmallIcon(com.futo.platformplayer.R.drawable.foreground)
.setContentTitle("New video by [${contentNotif.first.channel.name}]")
.setContentText("${contentNotif.second.name}")
.setSilent(true)
.setContentIntent(PendingIntent.getActivity(this.appContext, 0, MainActivity.getVideoIntent(this.appContext, contentNotif.second.url),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE))
.setChannelId(notificationChannel.id).build());
val thumbnail = if(contentNotif.second is IPlatformVideo) (contentNotif.second as IPlatformVideo).thumbnails.getHQThumbnail()
else null;
if(thumbnail != null)
Glide.with(appContext).asBitmap()
.load(thumbnail)
.into(object: CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
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) {
@ -140,4 +164,20 @@ class BackgroundWorker(private val appContext: Context, workerParams: WorkerPara
.setSilent(true)
.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.launch
import java.time.OffsetDateTime
import kotlin.streams.toList
import kotlin.system.measureTimeMillis
class ChannelContentCache {
private val _targetCacheSize = 2000;
private val _targetCacheSize = 3000;
val _channelCacheDir = FragmentedStorage.getOrCreateDirectory("channelCache");
val _channelContents: HashMap<String, ManagedStore<SerializedPlatformContent>>;
init {
@ -29,11 +30,11 @@ class ChannelContentCache {
val initializeTime = measureTimeMillis {
_channelContents = HashMap(allFiles
.filter { it.isDirectory }
.associate {
.parallelStream().map {
Pair(it.name, FragmentedStorage.storeJson(_channelCacheDir, it.name, PlatformContentSerializer())
.withoutBackup()
.load())
});
}.toList().associate { it })
}
val minDays = OffsetDateTime.now().minusDays(10);
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);
var filteredNextPageCounter = 0;
_nextPageHandler = TaskHandler<TPager, List<TResult>>({fragment.lifecycleScope}, {
if (it is IAsyncPager<*>)
it.nextPageAsync();
@ -141,10 +142,15 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
val filteredResults = filterResults(it);
recyclerData.results.addAll(filteredResults);
recyclerData.resultsUnfiltered.addAll(it);
if(filteredResults.isEmpty())
loadNextPage()
else
if(filteredResults.isEmpty()) {
filteredNextPageCounter++
if(filteredNextPageCounter <= 4)
loadNextPage()
}
else {
filteredNextPageCounter = 0;
recyclerData.adapter.notifyItemRangeInserted(recyclerData.adapter.childToParentPosition(posBefore), filteredResults.size);
}
}.exception<Throwable> {
Logger.w(TAG, "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.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
import kotlin.streams.toList
import kotlin.system.measureTimeMillis
/***
@ -250,12 +251,12 @@ class StateSubscriptions {
}
val usePolycentric = true;
val subUrls = getSubscriptions().associateWith {
val subUrls = getSubscriptions().parallelStream().map {
if(usePolycentric)
StatePolycentric.instance.getChannelUrls(it.channel.url, it.channel.id);
Pair(it, StatePolycentric.instance.getChannelUrls(it.channel.url, it.channel.id));
else
listOf(it.channel.url);
};
Pair(it, listOf(it.channel.url));
}.toList().associate { it };
val result = algo.getSubscriptions(subUrls);
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="clear_cookies">Clear Cookies</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="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>