From 195163840b0e18f11f67af6f03c60eeaac86e858 Mon Sep 17 00:00:00 2001 From: Kelvin <kelvin@futo.org> Date: Tue, 9 Jan 2024 22:25:41 +0100 Subject: [PATCH] DOMParser toNodeTree, Better subscription errors, Watch later ordering --- .../engine/packages/PackageDOMParser.kt | 27 ++++++++++++++++++- .../channel/tab/ChannelContentsFragment.kt | 6 ++++- .../main/SubscriptionsFeedFragment.kt | 12 +++++++-- .../platformplayer/states/StatePlaylists.kt | 13 +++++++-- .../SubscriptionsTaskFetchAlgorithm.kt | 4 +-- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt index c261487d..6460b2a9 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt @@ -5,8 +5,11 @@ import com.caoccao.javet.annotations.V8Function import com.caoccao.javet.annotations.V8Property import com.caoccao.javet.enums.V8ConversionMode import com.caoccao.javet.enums.V8ProxyMode +import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.engine.internal.V8BindObject +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json import org.jsoup.Jsoup import org.jsoup.nodes.Element @@ -65,7 +68,7 @@ class PackageDOMParser : V8Package { return result; } @V8Property - fun attributes(): Map<String, String> = _element.attributes().dataset(); + fun attributes(): Map<String, String> = _element.attributes().associate { Pair(it.key, it.value) } @V8Property fun innerHTML(): String = _element.html(); @V8Property @@ -138,10 +141,32 @@ class PackageDOMParser : V8Package { super.dispose(); } + @V8Function + fun toNodeTree(): SerializedNode { + return SerializedNode( + childNodes().map { it.toNodeTree() }, + _element.tagName(), + _element.text(), + attributes() + ); + } + @V8Function + fun toNodeTreeJson(): String { + return Json.encodeToString(SerializedNode.serializer(), toNodeTree()); + } + companion object { fun parse(parser: PackageDOMParser, str: String): DOMNode { return DOMNode(parser, Jsoup.parse(str)); } } + + @Serializable + class SerializedNode( + val children: List<SerializedNode>, + val name: String, + val value: String, + val attributes: Map<String, String> + ); } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt index 1aec934b..98fda554 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt @@ -27,6 +27,7 @@ import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException +import com.futo.platformplayer.exceptions.ChannelException import com.futo.platformplayer.fragment.mainactivity.main.FeedView import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.logging.Logger @@ -336,8 +337,11 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment { context?.let { lifecycleScope.launch(Dispatchers.Main) { try { + val channel = if(kv.value is ChannelException) (kv.value as ChannelException).channelNameOrUrl else null; if(jsVideoPager != null) - UIDialogs.toast(it, "Plugin ${jsVideoPager.getPluginConfig().name} failed:\n${kv.value.message}", false); + UIDialogs.toast(it, "Plugin ${jsVideoPager.getPluginConfig().name} failed:\n" + + (if(!channel.isNullOrEmpty()) "(${channel}) " else "") + + "${kv.value.message}", false); else UIDialogs.toast(it, kv.value.message ?: "", false); } catch (e: Throwable) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt index bf6ba06f..76c60e1f 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt @@ -31,6 +31,7 @@ import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.FragmentedStorageFileJson import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.NoResultsView +import com.futo.platformplayer.views.ToastView import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.InsertedViewHolder @@ -44,6 +45,7 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import java.nio.channels.Channel import java.time.OffsetDateTime import kotlin.system.measureTimeMillis @@ -440,14 +442,17 @@ class SubscriptionsFeedFragment : MainFragment() { } Logger.e(TAG, "Channel [${channel}] failed", ex); if (toShow is PluginException) - UIDialogs.appToast( - context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", toShow.config.name).replace("{message}", toShow.message ?: "") + UIDialogs.appToast(ToastView.Toast( + toShow.message + + (if(channel != null) "\nChannel: " + channel else ""), false, null, + "Plugin ${toShow.config.name} failed") ); else UIDialogs.appToast(ex.message ?: ""); } } else { + val failedChannels = exs.filterIsInstance<ChannelException>().map { it.channelNameOrUrl }.distinct().toList(); val failedPlugins = exs.filter { it is PluginException || (it is ChannelException && it.cause is PluginException) } .map { if(it is ChannelException) (it.cause as PluginException) else if(it is PluginException) it else null } .filter { it != null } @@ -456,6 +461,9 @@ class SubscriptionsFeedFragment : MainFragment() { .toList(); for(distinctPluginFail in failedPlugins) UIDialogs.appToast(context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", distinctPluginFail.config.name).replace("{message}", distinctPluginFail.message ?: "")); + if(failedChannels.isNotEmpty()) + UIDialogs.appToast(ToastView.Toast(failedChannels.take(3).map { "- ${it}" }.joinToString("\n") + + (if(failedChannels.size >= 3) "\nAnd ${failedChannels.size - 3} more" else ""), false, null, "Failed Channels")); } } catch (e: Throwable) { Logger.e(TAG, "Failed to handle exceptions", e) diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt index 5aff46e4..008a5d1c 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt @@ -16,6 +16,7 @@ import com.futo.platformplayer.exceptions.ReconstructionException import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringArrayStorage import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.stores.v2.ReconstructStore import kotlinx.serialization.encodeToString @@ -35,6 +36,8 @@ class StatePlaylists { = SerializedPlatformVideo.fromVideo(StatePlatform.instance.getContentDetails(backup).await() as IPlatformVideoDetails); }) .load(); + private val _watchlistOrderStore = FragmentedStorage.get<StringArrayStorage>("watchListOrder"); //Temporary workaround to add order.. + val playlistStore = FragmentedStorage.storeJson<Playlist>("playlists") .withRestore(PlaylistBackup()) .load(); @@ -48,26 +51,32 @@ class StatePlaylists { } fun getWatchLater() : List<SerializedPlatformVideo> { synchronized(_watchlistStore) { - return _watchlistStore.getItems(); + val order = _watchlistOrderStore.getAllValues(); + return _watchlistStore.getItems().sortedBy { order.indexOf(it.url) }; } } fun updateWatchLater(updated: List<SerializedPlatformVideo>) { synchronized(_watchlistStore) { _watchlistStore.deleteAll(); _watchlistStore.saveAllAsync(updated); + _watchlistOrderStore.set(*updated.map { it.url }.toTypedArray()); + _watchlistOrderStore.save(); } onWatchLaterChanged.emit(); } fun removeFromWatchLater(video: SerializedPlatformVideo) { synchronized(_watchlistStore) { _watchlistStore.delete(video); + _watchlistOrderStore.set(*_watchlistOrderStore.values.filter { it != video.url }.toTypedArray()); + _watchlistOrderStore.save(); } - onWatchLaterChanged.emit(); } fun addToWatchLater(video: SerializedPlatformVideo) { synchronized(_watchlistStore) { _watchlistStore.saveAsync(video); + _watchlistOrderStore.set(*(listOf(video.url) + _watchlistOrderStore.values) .toTypedArray()); + _watchlistOrderStore.save(); } onWatchLaterChanged.emit(); } diff --git a/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt b/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt index a7b57b74..5acbc4cd 100644 --- a/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt +++ b/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt @@ -13,6 +13,7 @@ import com.futo.platformplayer.api.media.structures.MultiChronoContentPager import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.engine.exceptions.ScriptCriticalException +import com.futo.platformplayer.engine.exceptions.ScriptException import com.futo.platformplayer.exceptions.ChannelException import com.futo.platformplayer.findNonRuntimeException import com.futo.platformplayer.fragment.mainactivity.main.SubscriptionsFeedFragment @@ -69,7 +70,6 @@ abstract class SubscriptionsTaskFetchAlgorithm( val cachedChannels = mutableListOf<String>() val forkTasks = executeSubscriptionTasks(tasks, failedPlugins, cachedChannels); - val taskResults = arrayListOf<SubscriptionTaskResult>(); val timeTotal = measureTimeMillis { for(task in forkTasks) { @@ -200,7 +200,7 @@ abstract class SubscriptionsTaskFetchAlgorithm( else { Logger.i(StateSubscriptions.TAG, "Channel ${task.sub.channel.name} failed, substituting with cache"); pager = StateCache.instance.getChannelCachePager(task.sub.channel.url); - taskEx = ex; + taskEx = channelEx; return@submit SubscriptionTaskResult(task, pager, taskEx); } } -- GitLab