DOMParser toNodeTree, Better subscription errors, Watch later ordering

This commit is contained in:
Kelvin 2024-01-09 22:25:41 +01:00
commit 195163840b
5 changed files with 54 additions and 8 deletions

View file

@ -5,8 +5,11 @@ import com.caoccao.javet.annotations.V8Function
import com.caoccao.javet.annotations.V8Property import com.caoccao.javet.annotations.V8Property
import com.caoccao.javet.enums.V8ConversionMode import com.caoccao.javet.enums.V8ConversionMode
import com.caoccao.javet.enums.V8ProxyMode import com.caoccao.javet.enums.V8ProxyMode
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.engine.internal.V8BindObject import com.futo.platformplayer.engine.internal.V8BindObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@ -65,7 +68,7 @@ class PackageDOMParser : V8Package {
return result; return result;
} }
@V8Property @V8Property
fun attributes(): Map<String, String> = _element.attributes().dataset(); fun attributes(): Map<String, String> = _element.attributes().associate { Pair(it.key, it.value) }
@V8Property @V8Property
fun innerHTML(): String = _element.html(); fun innerHTML(): String = _element.html();
@V8Property @V8Property
@ -138,10 +141,32 @@ class PackageDOMParser : V8Package {
super.dispose(); 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 { companion object {
fun parse(parser: PackageDOMParser, str: String): DOMNode { fun parse(parser: PackageDOMParser, str: String): DOMNode {
return DOMNode(parser, Jsoup.parse(str)); return DOMNode(parser, Jsoup.parse(str));
} }
} }
@Serializable
class SerializedNode(
val children: List<SerializedNode>,
val name: String,
val value: String,
val attributes: Map<String, String>
);
} }
} }

View file

@ -27,6 +27,7 @@ import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.engine.exceptions.PluginException
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException 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.FeedView
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
@ -336,8 +337,11 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
context?.let { context?.let {
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
try { try {
val channel = if(kv.value is ChannelException) (kv.value as ChannelException).channelNameOrUrl else null;
if(jsVideoPager != 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 else
UIDialogs.toast(it, kv.value.message ?: "", false); UIDialogs.toast(it, kv.value.message ?: "", false);
} catch (e: Throwable) { } catch (e: Throwable) {

View file

@ -31,6 +31,7 @@ import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.FragmentedStorageFileJson import com.futo.platformplayer.stores.FragmentedStorageFileJson
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.NoResultsView 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.ContentPreviewViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.InsertedViewHolder import com.futo.platformplayer.views.adapters.InsertedViewHolder
@ -44,6 +45,7 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.nio.channels.Channel
import java.time.OffsetDateTime import java.time.OffsetDateTime
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -440,14 +442,17 @@ class SubscriptionsFeedFragment : MainFragment() {
} }
Logger.e(TAG, "Channel [${channel}] failed", ex); Logger.e(TAG, "Channel [${channel}] failed", ex);
if (toShow is PluginException) if (toShow is PluginException)
UIDialogs.appToast( UIDialogs.appToast(ToastView.Toast(
context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", toShow.config.name).replace("{message}", toShow.message ?: "") toShow.message +
(if(channel != null) "\nChannel: " + channel else ""), false, null,
"Plugin ${toShow.config.name} failed")
); );
else else
UIDialogs.appToast(ex.message ?: ""); UIDialogs.appToast(ex.message ?: "");
} }
} }
else { 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) } 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 } .map { if(it is ChannelException) (it.cause as PluginException) else if(it is PluginException) it else null }
.filter { it != null } .filter { it != null }
@ -456,6 +461,9 @@ class SubscriptionsFeedFragment : MainFragment() {
.toList(); .toList();
for(distinctPluginFail in failedPlugins) for(distinctPluginFail in failedPlugins)
UIDialogs.appToast(context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", distinctPluginFail.config.name).replace("{message}", distinctPluginFail.message ?: "")); 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) { } catch (e: Throwable) {
Logger.e(TAG, "Failed to handle exceptions", e) Logger.e(TAG, "Failed to handle exceptions", e)

View file

@ -16,6 +16,7 @@ import com.futo.platformplayer.exceptions.ReconstructionException
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.models.Playlist
import com.futo.platformplayer.stores.FragmentedStorage 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.ManagedStore
import com.futo.platformplayer.stores.v2.ReconstructStore import com.futo.platformplayer.stores.v2.ReconstructStore
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
@ -35,6 +36,8 @@ class StatePlaylists {
= SerializedPlatformVideo.fromVideo(StatePlatform.instance.getContentDetails(backup).await() as IPlatformVideoDetails); = SerializedPlatformVideo.fromVideo(StatePlatform.instance.getContentDetails(backup).await() as IPlatformVideoDetails);
}) })
.load(); .load();
private val _watchlistOrderStore = FragmentedStorage.get<StringArrayStorage>("watchListOrder"); //Temporary workaround to add order..
val playlistStore = FragmentedStorage.storeJson<Playlist>("playlists") val playlistStore = FragmentedStorage.storeJson<Playlist>("playlists")
.withRestore(PlaylistBackup()) .withRestore(PlaylistBackup())
.load(); .load();
@ -48,26 +51,32 @@ class StatePlaylists {
} }
fun getWatchLater() : List<SerializedPlatformVideo> { fun getWatchLater() : List<SerializedPlatformVideo> {
synchronized(_watchlistStore) { synchronized(_watchlistStore) {
return _watchlistStore.getItems(); val order = _watchlistOrderStore.getAllValues();
return _watchlistStore.getItems().sortedBy { order.indexOf(it.url) };
} }
} }
fun updateWatchLater(updated: List<SerializedPlatformVideo>) { fun updateWatchLater(updated: List<SerializedPlatformVideo>) {
synchronized(_watchlistStore) { synchronized(_watchlistStore) {
_watchlistStore.deleteAll(); _watchlistStore.deleteAll();
_watchlistStore.saveAllAsync(updated); _watchlistStore.saveAllAsync(updated);
_watchlistOrderStore.set(*updated.map { it.url }.toTypedArray());
_watchlistOrderStore.save();
} }
onWatchLaterChanged.emit(); onWatchLaterChanged.emit();
} }
fun removeFromWatchLater(video: SerializedPlatformVideo) { fun removeFromWatchLater(video: SerializedPlatformVideo) {
synchronized(_watchlistStore) { synchronized(_watchlistStore) {
_watchlistStore.delete(video); _watchlistStore.delete(video);
_watchlistOrderStore.set(*_watchlistOrderStore.values.filter { it != video.url }.toTypedArray());
_watchlistOrderStore.save();
} }
onWatchLaterChanged.emit(); onWatchLaterChanged.emit();
} }
fun addToWatchLater(video: SerializedPlatformVideo) { fun addToWatchLater(video: SerializedPlatformVideo) {
synchronized(_watchlistStore) { synchronized(_watchlistStore) {
_watchlistStore.saveAsync(video); _watchlistStore.saveAsync(video);
_watchlistOrderStore.set(*(listOf(video.url) + _watchlistOrderStore.values) .toTypedArray());
_watchlistOrderStore.save();
} }
onWatchLaterChanged.emit(); onWatchLaterChanged.emit();
} }

View file

@ -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.PluginException
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptCriticalException import com.futo.platformplayer.engine.exceptions.ScriptCriticalException
import com.futo.platformplayer.engine.exceptions.ScriptException
import com.futo.platformplayer.exceptions.ChannelException import com.futo.platformplayer.exceptions.ChannelException
import com.futo.platformplayer.findNonRuntimeException import com.futo.platformplayer.findNonRuntimeException
import com.futo.platformplayer.fragment.mainactivity.main.SubscriptionsFeedFragment import com.futo.platformplayer.fragment.mainactivity.main.SubscriptionsFeedFragment
@ -69,7 +70,6 @@ abstract class SubscriptionsTaskFetchAlgorithm(
val cachedChannels = mutableListOf<String>() val cachedChannels = mutableListOf<String>()
val forkTasks = executeSubscriptionTasks(tasks, failedPlugins, cachedChannels); val forkTasks = executeSubscriptionTasks(tasks, failedPlugins, cachedChannels);
val taskResults = arrayListOf<SubscriptionTaskResult>(); val taskResults = arrayListOf<SubscriptionTaskResult>();
val timeTotal = measureTimeMillis { val timeTotal = measureTimeMillis {
for(task in forkTasks) { for(task in forkTasks) {
@ -200,7 +200,7 @@ abstract class SubscriptionsTaskFetchAlgorithm(
else { else {
Logger.i(StateSubscriptions.TAG, "Channel ${task.sub.channel.name} failed, substituting with cache"); Logger.i(StateSubscriptions.TAG, "Channel ${task.sub.channel.name} failed, substituting with cache");
pager = StateCache.instance.getChannelCachePager(task.sub.channel.url); pager = StateCache.instance.getChannelCachePager(task.sub.channel.url);
taskEx = ex; taskEx = channelEx;
return@submit SubscriptionTaskResult(task, pager, taskEx); return@submit SubscriptionTaskResult(task, pager, taskEx);
} }
} }