mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 04:52:39 +02:00
Merge branch 'possiblebusyfix' into 'master'
Possible busy fix See merge request videostreaming/grayjay!173
This commit is contained in:
@@ -118,14 +118,13 @@ inline fun <reified T> V8ValueArray.expectV8Variants(config: IV8PluginConfig, co
|
|||||||
inline fun V8Plugin.ensureIsBusy() {
|
inline fun V8Plugin.ensureIsBusy() {
|
||||||
this.let {
|
this.let {
|
||||||
if (!it.isThreadAlreadyBusy()) {
|
if (!it.isThreadAlreadyBusy()) {
|
||||||
//throw IllegalStateException("Tried to access V8Plugin without busy");
|
|
||||||
val stacktrace = Thread.currentThread().stackTrace;
|
val stacktrace = Thread.currentThread().stackTrace;
|
||||||
Logger.w("Extensions_V8",
|
val message = "V8 USE OUTSIDE BUSY: " + stacktrace.drop(3)?.firstOrNull().toString() +
|
||||||
"V8 USE OUTSIDE BUSY: " + stacktrace.drop(3)?.firstOrNull().toString() +
|
|
||||||
", " + stacktrace.drop(4)?.firstOrNull().toString() +
|
", " + stacktrace.drop(4)?.firstOrNull().toString() +
|
||||||
", " + stacktrace.drop(5)?.firstOrNull()?.toString() +
|
", " + stacktrace.drop(5)?.firstOrNull()?.toString() +
|
||||||
", " + stacktrace.drop(6)?.firstOrNull()?.toString()
|
", " + stacktrace.drop(6)?.firstOrNull()?.toString();
|
||||||
);
|
Logger.w("Extensions_V8", message);
|
||||||
|
throw IllegalStateException(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +135,6 @@ inline fun V8Value.ensureIsBusy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T> V8Value.expectV8Variant(config: IV8PluginConfig, contextName: String): T {
|
inline fun <reified T> V8Value.expectV8Variant(config: IV8PluginConfig, contextName: String): T {
|
||||||
if(false)
|
|
||||||
ensureIsBusy();
|
ensureIsBusy();
|
||||||
return when(T::class) {
|
return when(T::class) {
|
||||||
String::class -> this.expectOrThrow<V8ValueString>(config, contextName).value as T;
|
String::class -> this.expectOrThrow<V8ValueString>(config, contextName).value as T;
|
||||||
@@ -186,10 +184,14 @@ inline fun <reified T> V8Value.expectV8Variant(config: IV8PluginConfig, contextN
|
|||||||
else -> throw NotImplementedError("Type ${T::class.simpleName} not implemented conversion");
|
else -> throw NotImplementedError("Type ${T::class.simpleName} not implemented conversion");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun V8ArrayToStringList(obj: V8ValueArray): List<String> = obj.keys.map { obj.getString(it) };
|
fun V8ArrayToStringList(obj: V8ValueArray): List<String> {
|
||||||
|
obj.ensureIsBusy();
|
||||||
|
return obj.keys.map { obj.getString(it) };
|
||||||
|
}
|
||||||
fun V8ObjectToHashMap(obj: V8ValueObject?): HashMap<String, String> {
|
fun V8ObjectToHashMap(obj: V8ValueObject?): HashMap<String, String> {
|
||||||
if(obj == null)
|
if(obj == null)
|
||||||
return hashMapOf();
|
return hashMapOf();
|
||||||
|
obj.ensureIsBusy();
|
||||||
val map = hashMapOf<String, String>();
|
val map = hashMapOf<String, String>();
|
||||||
for(prop in obj.ownPropertyNames.keys.map { obj.ownPropertyNames.get<V8Value>(it).toString() })
|
for(prop in obj.ownPropertyNames.keys.map { obj.ownPropertyNames.get<V8Value>(it).toString() })
|
||||||
map.put(prop, obj.getString(prop));
|
map.put(prop, obj.getString(prop));
|
||||||
@@ -203,6 +205,7 @@ fun <T: V8Value> V8ValuePromise.toV8ValueBlocking(plugin: V8Plugin): T {
|
|||||||
plugin.busy {
|
plugin.busy {
|
||||||
this.register(object: IV8ValuePromise.IListener {
|
this.register(object: IV8ValuePromise.IListener {
|
||||||
override fun onFulfilled(p0: V8Value?) {
|
override fun onFulfilled(p0: V8Value?) {
|
||||||
|
plugin.busy {
|
||||||
if(p0 is V8ValueError)
|
if(p0 is V8ValueError)
|
||||||
promiseException = ScriptExecutionException(plugin.config, p0.message);
|
promiseException = ScriptExecutionException(plugin.config, p0.message);
|
||||||
else {
|
else {
|
||||||
@@ -210,14 +213,19 @@ fun <T: V8Value> V8ValuePromise.toV8ValueBlocking(plugin: V8Plugin): T {
|
|||||||
p0.setWeak();
|
p0.setWeak();
|
||||||
promiseResult = p0 as T;
|
promiseResult = p0 as T;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
override fun onRejected(p0: V8Value?) {
|
override fun onRejected(p0: V8Value?) {
|
||||||
|
plugin.busy {
|
||||||
promiseException = p0?.toException(plugin.config);
|
promiseException = p0?.toException(plugin.config);
|
||||||
|
}
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
override fun onCatch(p0: V8Value?) {
|
override fun onCatch(p0: V8Value?) {
|
||||||
|
plugin.busy {
|
||||||
promiseException = p0?.toException(plugin.config);
|
promiseException = p0?.toException(plugin.config);
|
||||||
|
}
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -229,8 +237,11 @@ fun <T: V8Value> V8ValuePromise.toV8ValueBlocking(plugin: V8Plugin): T {
|
|||||||
}
|
}
|
||||||
//Logger.i("V8", "V8ValueBlocking started (Busy) [" + blockCount + "]" + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString()+ ", " + Thread.currentThread().stackTrace.drop(5)?.firstOrNull()?.toString());
|
//Logger.i("V8", "V8ValueBlocking started (Busy) [" + blockCount + "]" + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString()+ ", " + Thread.currentThread().stackTrace.drop(5)?.firstOrNull()?.toString());
|
||||||
|
|
||||||
|
val isPending = plugin.busy {
|
||||||
if(!promise.isPending) {
|
promise.isPending
|
||||||
|
};
|
||||||
|
if(!isPending) {
|
||||||
|
plugin.busy {
|
||||||
try {
|
try {
|
||||||
Logger.i("V8", "V8Promise resolved synchronously");
|
Logger.i("V8", "V8Promise resolved synchronously");
|
||||||
if(promise.isFulfilled)
|
if(promise.isFulfilled)
|
||||||
@@ -242,7 +253,7 @@ fun <T: V8Value> V8ValuePromise.toV8ValueBlocking(plugin: V8Plugin): T {
|
|||||||
promiseException = ex;
|
promiseException = ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
} else {
|
||||||
plugin.unbusy {
|
plugin.unbusy {
|
||||||
latch.await();
|
latch.await();
|
||||||
}
|
}
|
||||||
@@ -266,26 +277,32 @@ fun <T: V8Value> V8ValuePromise.toV8ValueAsync(plugin: V8Plugin): V8Deferred<T>
|
|||||||
plugin.busy {
|
plugin.busy {
|
||||||
this.register(object: IV8ValuePromise.IListener {
|
this.register(object: IV8ValuePromise.IListener {
|
||||||
override fun onFulfilled(p0: V8Value?) {
|
override fun onFulfilled(p0: V8Value?) {
|
||||||
|
plugin.busy {
|
||||||
plugin.resolvePromise(promise);
|
plugin.resolvePromise(promise);
|
||||||
underlyingDef.complete(p0 as T);
|
underlyingDef.complete(p0 as T);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
override fun onRejected(p0: V8Value?) {
|
override fun onRejected(p0: V8Value?) {
|
||||||
try {
|
try {
|
||||||
|
plugin.busy {
|
||||||
plugin.resolvePromise(promise);
|
plugin.resolvePromise(promise);
|
||||||
val exceptionFound = p0?.toException(plugin.config) ?: NotImplementedError("onRejected promise not implemented..");
|
val exceptionFound = p0?.toException(plugin.config) ?: NotImplementedError("onRejected promise not implemented..");
|
||||||
Logger.i("V8", "Promise rejected, setting exception");
|
Logger.i("V8", "Promise rejected, setting exception");
|
||||||
underlyingDef.completeExceptionally(CancellationException(exceptionFound.message, exceptionFound));
|
underlyingDef.completeExceptionally(CancellationException(exceptionFound.message, exceptionFound));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable) {
|
catch(ex: Throwable) {
|
||||||
Logger.e("V8", "Rejection handling failed?" , ex);
|
Logger.e("V8", "Rejection handling failed?" , ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun onCatch(p0: V8Value?) {
|
override fun onCatch(p0: V8Value?) {
|
||||||
try {
|
try {
|
||||||
|
plugin.busy {
|
||||||
plugin.resolvePromise(promise);
|
plugin.resolvePromise(promise);
|
||||||
val exceptionFound = p0?.toException(plugin.config) ?: NotImplementedError("onCatch promise not implemented..");
|
val exceptionFound = p0?.toException(plugin.config) ?: NotImplementedError("onCatch promise not implemented..");
|
||||||
underlyingDef.completeExceptionally(CancellationException(exceptionFound.message, exceptionFound));
|
underlyingDef.completeExceptionally(CancellationException(exceptionFound.message, exceptionFound));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable) {
|
catch(ex: Throwable) {
|
||||||
Logger.e("V8", "Catching handling failed?" , ex);
|
Logger.e("V8", "Catching handling failed?" , ex);
|
||||||
}
|
}
|
||||||
@@ -300,6 +317,7 @@ fun <T: V8Value> V8ValuePromise.toV8ValueAsync(plugin: V8Plugin): V8Deferred<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun V8Value.toException(config: IV8PluginConfig): Throwable {
|
fun V8Value.toException(config: IV8PluginConfig): Throwable {
|
||||||
|
ensureIsBusy();
|
||||||
val p0 = this;
|
val p0 = this;
|
||||||
if(p0 is V8ValueObject) {
|
if(p0 is V8ValueObject) {
|
||||||
return V8Plugin.getExceptionFromPlugin(config, p0, null, null, null, "P:");
|
return V8Plugin.getExceptionFromPlugin(config, p0, null, null, null, "P:");
|
||||||
@@ -349,6 +367,7 @@ class V8Deferred<T>(val deferred: Deferred<T>, val estDuration: Int = -1): Defer
|
|||||||
|
|
||||||
|
|
||||||
fun <T: V8Value> V8ValueObject.invokeV8(method: String, vararg obj: Any?): T {
|
fun <T: V8Value> V8ValueObject.invokeV8(method: String, vararg obj: Any?): T {
|
||||||
|
ensureIsBusy();
|
||||||
var result = this.invoke<V8Value>(method, *obj);
|
var result = this.invoke<V8Value>(method, *obj);
|
||||||
if(result is V8ValuePromise) {
|
if(result is V8ValuePromise) {
|
||||||
return result.toV8ValueBlocking(this.getSourcePlugin()!!);
|
return result.toV8ValueBlocking(this.getSourcePlugin()!!);
|
||||||
@@ -356,6 +375,7 @@ fun <T: V8Value> V8ValueObject.invokeV8(method: String, vararg obj: Any?): T {
|
|||||||
return result as T;
|
return result as T;
|
||||||
}
|
}
|
||||||
fun <T: V8Value> V8ValueObject.invokeV8Async(method: String, vararg obj: Any?): V8Deferred<T> {
|
fun <T: V8Value> V8ValueObject.invokeV8Async(method: String, vararg obj: Any?): V8Deferred<T> {
|
||||||
|
ensureIsBusy();
|
||||||
var result = this.invoke<V8Value>(method, *obj);
|
var result = this.invoke<V8Value>(method, *obj);
|
||||||
if(result is V8ValuePromise) {
|
if(result is V8ValuePromise) {
|
||||||
return result.toV8ValueAsync(this.getSourcePlugin()!!);
|
return result.toV8ValueAsync(this.getSourcePlugin()!!);
|
||||||
@@ -363,6 +383,7 @@ fun <T: V8Value> V8ValueObject.invokeV8Async(method: String, vararg obj: Any?):
|
|||||||
return V8Deferred(CompletableDeferred(result as T));
|
return V8Deferred(CompletableDeferred(result as T));
|
||||||
}
|
}
|
||||||
fun V8ValueObject.invokeV8Void(method: String, vararg obj: Any?): V8Value {
|
fun V8ValueObject.invokeV8Void(method: String, vararg obj: Any?): V8Value {
|
||||||
|
ensureIsBusy();
|
||||||
var result = this.invoke<V8Value>(method, *obj);
|
var result = this.invoke<V8Value>(method, *obj);
|
||||||
if(result is V8ValuePromise) {
|
if(result is V8ValuePromise) {
|
||||||
return result.toV8ValueBlocking(this.getSourcePlugin()!!);
|
return result.toV8ValueBlocking(this.getSourcePlugin()!!);
|
||||||
@@ -370,6 +391,7 @@ fun V8ValueObject.invokeV8Void(method: String, vararg obj: Any?): V8Value {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
fun V8ValueObject.invokeV8VoidAsync(method: String, vararg obj: Any?): V8Deferred<V8Value> {
|
fun V8ValueObject.invokeV8VoidAsync(method: String, vararg obj: Any?): V8Deferred<V8Value> {
|
||||||
|
ensureIsBusy();
|
||||||
var result = this.invoke<V8Value>(method, *obj);
|
var result = this.invoke<V8Value>(method, *obj);
|
||||||
if(result is V8ValuePromise) {
|
if(result is V8ValuePromise) {
|
||||||
val result = result.toV8ValueAsync<V8Value>(this.getSourcePlugin()!!);
|
val result = result.toV8ValueAsync<V8Value>(this.getSourcePlugin()!!);
|
||||||
|
|||||||
@@ -488,13 +488,14 @@ open class JSClient : IPlatformClient {
|
|||||||
if (_peekChannelTypes != null) {
|
if (_peekChannelTypes != null) {
|
||||||
return _peekChannelTypes!!;
|
return _peekChannelTypes!!;
|
||||||
}
|
}
|
||||||
|
return busy {
|
||||||
val arr: V8ValueArray = plugin.executeTyped("source.getPeekChannelTypes()");
|
val arr: V8ValueArray = plugin.executeTyped("source.getPeekChannelTypes()");
|
||||||
|
|
||||||
_peekChannelTypes = arr.keys.mapNotNull {
|
_peekChannelTypes = arr.keys.mapNotNull {
|
||||||
val str = arr.get<V8ValueString>(it);
|
val str = arr.get<V8ValueString>(it);
|
||||||
return@mapNotNull str.value;
|
return@mapNotNull str.value;
|
||||||
};
|
};
|
||||||
return _peekChannelTypes ?: listOf();
|
return@busy _peekChannelTypes ?: listOf();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(ex: Throwable) {
|
catch(ex: Throwable) {
|
||||||
announcePluginUnhandledException("getPeekChannelTypes", ex);
|
announcePluginUnhandledException("getPeekChannelTypes", ex);
|
||||||
@@ -523,10 +524,12 @@ open class JSClient : IPlatformClient {
|
|||||||
if(!capabilities.hasGetChannelUrlByClaim)
|
if(!capabilities.hasGetChannelUrlByClaim)
|
||||||
throw IllegalStateException("This plugin does not support channel url by claim");
|
throw IllegalStateException("This plugin does not support channel url by claim");
|
||||||
|
|
||||||
|
return busy {
|
||||||
val value = plugin.executeTyped<V8Value>("source.getChannelUrlByClaim(${claimType}, ${Json.encodeToString(claimValues)})");
|
val value = plugin.executeTyped<V8Value>("source.getChannelUrlByClaim(${claimType}, ${Json.encodeToString(claimValues)})");
|
||||||
if(value !is V8ValueString)
|
if(value !is V8ValueString)
|
||||||
return null;
|
return@busy null;
|
||||||
return value.value;
|
return@busy value.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@JSOptional
|
@JSOptional
|
||||||
@JSDocs(12, "source.getChannelTemplateByClaimMap()", "Get a map for every supported claimtype mapping field to urls")
|
@JSDocs(12, "source.getChannelTemplateByClaimMap()", "Get a map for every supported claimtype mapping field to urls")
|
||||||
@@ -536,9 +539,10 @@ open class JSClient : IPlatformClient {
|
|||||||
if(!capabilities.hasGetChannelTemplateByClaimMap)
|
if(!capabilities.hasGetChannelTemplateByClaimMap)
|
||||||
throw IllegalStateException("This plugin does not support channel template by claim map");
|
throw IllegalStateException("This plugin does not support channel template by claim map");
|
||||||
|
|
||||||
|
return busy {
|
||||||
val value = plugin.executeTyped<V8Value>("source.getChannelTemplateByClaimMap()");
|
val value = plugin.executeTyped<V8Value>("source.getChannelTemplateByClaimMap()");
|
||||||
if(value !is V8ValueObject)
|
if(value !is V8ValueObject)
|
||||||
return mapOf();
|
return@busy mapOf();
|
||||||
|
|
||||||
val claimTypes = mutableMapOf<Int, Map<Int, String>>();
|
val claimTypes = mutableMapOf<Int, Map<Int, String>>();
|
||||||
|
|
||||||
@@ -557,7 +561,8 @@ open class JSClient : IPlatformClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
channelClaimTemplates = claimTypes.toMap();
|
channelClaimTemplates = claimTypes.toMap();
|
||||||
return claimTypes;
|
return@busy claimTypes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -698,27 +703,33 @@ open class JSClient : IPlatformClient {
|
|||||||
@JSDocs(22, "source.getUserPlaylists()", "Gets the playlist of the current user")
|
@JSDocs(22, "source.getUserPlaylists()", "Gets the playlist of the current user")
|
||||||
override fun getUserPlaylists(): Array<String> {
|
override fun getUserPlaylists(): Array<String> {
|
||||||
ensureEnabled();
|
ensureEnabled();
|
||||||
return plugin.executeTyped<V8ValueArray>("source.getUserPlaylists()")
|
return busy {
|
||||||
|
return@busy plugin.executeTyped<V8ValueArray>("source.getUserPlaylists()")
|
||||||
.toArray()
|
.toArray()
|
||||||
.map { (it as V8ValueString).value }
|
.map { (it as V8ValueString).value }
|
||||||
.toTypedArray();
|
.toTypedArray();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JSOptional
|
@JSOptional
|
||||||
@JSDocs(23, "source.getUserSubscriptions()", "Gets the subscriptions of the current user")
|
@JSDocs(23, "source.getUserSubscriptions()", "Gets the subscriptions of the current user")
|
||||||
override fun getUserSubscriptions(): Array<String> {
|
override fun getUserSubscriptions(): Array<String> {
|
||||||
ensureEnabled();
|
ensureEnabled();
|
||||||
return plugin.executeTyped<V8ValueArray>("source.getUserSubscriptions()")
|
return busy {
|
||||||
|
return@busy plugin.executeTyped<V8ValueArray>("source.getUserSubscriptions()")
|
||||||
.toArray()
|
.toArray()
|
||||||
.map { (it as V8ValueString).value }
|
.map { (it as V8ValueString).value }
|
||||||
.toTypedArray();
|
.toTypedArray();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JSOptional
|
@JSOptional
|
||||||
@JSDocs(23, "source.getUserHistory()", "Gets the history of the current user")
|
@JSDocs(23, "source.getUserHistory()", "Gets the history of the current user")
|
||||||
override fun getUserHistory(): IPager<IPlatformContent> {
|
override fun getUserHistory(): IPager<IPlatformContent> {
|
||||||
ensureEnabled();
|
ensureEnabled();
|
||||||
return JSContentPager(config, this, plugin.executeTyped("source.getUserHistory()"));
|
return isBusyWith("getUserHistory") {
|
||||||
|
return@isBusyWith JSContentPager(config, this, plugin.executeTyped("source.getUserHistory()"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validate() {
|
fun validate() {
|
||||||
|
|||||||
+2
-1
@@ -21,6 +21,7 @@ import com.futo.platformplayer.api.media.structures.IPager
|
|||||||
import com.futo.platformplayer.getOrDefault
|
import com.futo.platformplayer.getOrDefault
|
||||||
import com.futo.platformplayer.getOrThrow
|
import com.futo.platformplayer.getOrThrow
|
||||||
import com.futo.platformplayer.getOrThrowNullableList
|
import com.futo.platformplayer.getOrThrowNullableList
|
||||||
|
import com.futo.platformplayer.getSourcePlugin
|
||||||
import com.futo.platformplayer.states.StateDeveloper
|
import com.futo.platformplayer.states.StateDeveloper
|
||||||
|
|
||||||
open class JSArticle(
|
open class JSArticle(
|
||||||
@@ -34,7 +35,7 @@ open class JSArticle(
|
|||||||
obj.getOrDefault<String>(config, "summary", "PlatformArticle", "") ?: ""
|
obj.getOrDefault<String>(config, "summary", "PlatformArticle", "") ?: ""
|
||||||
|
|
||||||
override val thumbnails: Thumbnails? =
|
override val thumbnails: Thumbnails? =
|
||||||
if (obj.has("thumbnails"))
|
if (obj.getSourcePlugin()?.busy { obj.has("thumbnails") } ?: obj.has("thumbnails"))
|
||||||
Thumbnails.fromV8(
|
Thumbnails.fromV8(
|
||||||
config,
|
config,
|
||||||
obj.getOrThrow<V8ValueObject>(config, "thumbnails", "PlatformArticle")
|
obj.getOrThrow<V8ValueObject>(config, "thumbnails", "PlatformArticle")
|
||||||
|
|||||||
+27
-11
@@ -31,18 +31,20 @@ open class JSArticleDetails(
|
|||||||
|
|
||||||
final override val contentType: ContentType = ContentType.ARTICLE
|
final override val contentType: ContentType = ContentType.ARTICLE
|
||||||
|
|
||||||
private val _hasGetComments: Boolean = _content.has("getComments")
|
private val _hasGetComments: Boolean = client.busy { _content.has("getComments") }
|
||||||
private val _hasGetContentRecommendations: Boolean = _content.has("getContentRecommendations")
|
private val _hasGetContentRecommendations: Boolean = client.busy { _content.has("getContentRecommendations") }
|
||||||
|
|
||||||
override val rating: IRating =
|
override val rating: IRating = client.busy {
|
||||||
obj.getOrDefault<V8ValueObject>(client.config, "rating", "PlatformArticle", null)
|
obj.getOrDefault<V8ValueObject>(client.config, "rating", "PlatformArticle", null)
|
||||||
?.let { IRating.fromV8(client.config, it, "PlatformArticle") }
|
?.let { IRating.fromV8(client.config, it, "PlatformArticle") }
|
||||||
?: RatingLikes(0)
|
?: RatingLikes(0)
|
||||||
|
}
|
||||||
|
|
||||||
override val summary: String =
|
override val summary: String = client.busy {
|
||||||
_content.getOrThrow(client.config, "summary", "PlatformArticle")
|
_content.getOrThrow(client.config, "summary", "PlatformArticle")
|
||||||
|
}
|
||||||
|
|
||||||
override val thumbnails: Thumbnails? =
|
override val thumbnails: Thumbnails? = client.busy {
|
||||||
if (_content.has("thumbnails"))
|
if (_content.has("thumbnails"))
|
||||||
Thumbnails.fromV8(
|
Thumbnails.fromV8(
|
||||||
client.config,
|
client.config,
|
||||||
@@ -50,14 +52,19 @@ open class JSArticleDetails(
|
|||||||
)
|
)
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
|
}
|
||||||
|
|
||||||
override val segments: List<IJSArticleSegment> =
|
override val segments: List<IJSArticleSegment> = client.busy {
|
||||||
obj.getOrThrowNullableList<V8ValueObject>(client.config, "segments", "PlatformArticle")
|
obj.getOrThrowNullableList<V8ValueObject>(client.config, "segments", "PlatformArticle")
|
||||||
?.mapNotNull { fromV8Segment(client, it) }
|
?.mapNotNull { fromV8Segment(client, it) }
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getComments(client: IPlatformClient): IPager<IPlatformComment>? {
|
override fun getComments(client: IPlatformClient): IPager<IPlatformComment>? {
|
||||||
if(!_hasGetComments || _content.isClosed)
|
val canGetComments = this.client.busy {
|
||||||
|
_hasGetComments && !_content.isClosed
|
||||||
|
}
|
||||||
|
if(!canGetComments)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if(client is DevJSClient)
|
if(client is DevJSClient)
|
||||||
@@ -73,7 +80,10 @@ open class JSArticleDetails(
|
|||||||
override fun getPlaybackTracker(): IPlaybackTracker? = null;
|
override fun getPlaybackTracker(): IPlaybackTracker? = null;
|
||||||
|
|
||||||
override fun getContentRecommendations(client: IPlatformClient): IPager<IPlatformContent>? {
|
override fun getContentRecommendations(client: IPlatformClient): IPager<IPlatformContent>? {
|
||||||
if(!_hasGetContentRecommendations || _content.isClosed)
|
val canGetContentRecommendations = this.client.busy {
|
||||||
|
_hasGetContentRecommendations && !_content.isClosed
|
||||||
|
}
|
||||||
|
if(!canGetContentRecommendations)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if(client is DevJSClient)
|
if(client is DevJSClient)
|
||||||
@@ -87,20 +97,25 @@ open class JSArticleDetails(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getContentRecommendationsJS(client: JSClient): JSContentPager {
|
private fun getContentRecommendationsJS(client: JSClient): JSContentPager {
|
||||||
|
return client.busy {
|
||||||
val contentPager = _content.invokeV8<V8ValueObject>("getContentRecommendations", arrayOf<Any>());
|
val contentPager = _content.invokeV8<V8ValueObject>("getContentRecommendations", arrayOf<Any>());
|
||||||
return JSContentPager(_pluginConfig, client, contentPager);
|
return@busy JSContentPager(_pluginConfig, client, contentPager);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCommentsJS(client: JSClient): JSCommentPager {
|
private fun getCommentsJS(client: JSClient): JSCommentPager {
|
||||||
|
return client.busy {
|
||||||
val commentPager = _content.invokeV8<V8ValueObject>("getComments", arrayOf<Any>());
|
val commentPager = _content.invokeV8<V8ValueObject>("getComments", arrayOf<Any>());
|
||||||
return JSCommentPager(_pluginConfig, client, commentPager);
|
return@busy JSCommentPager(_pluginConfig, client, commentPager);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromV8Segment(client: JSClient, obj: V8ValueObject): IJSArticleSegment? {
|
fun fromV8Segment(client: JSClient, obj: V8ValueObject): IJSArticleSegment? {
|
||||||
|
return client.busy {
|
||||||
if(!obj.has("type"))
|
if(!obj.has("type"))
|
||||||
throw IllegalArgumentException("Object missing type field");
|
throw IllegalArgumentException("Object missing type field");
|
||||||
return when(SegmentType.fromInt(obj.getOrThrow(client.config, "type", "JSArticle.Segment"))) {
|
return@busy when(SegmentType.fromInt(obj.getOrThrow(client.config, "type", "JSArticle.Segment"))) {
|
||||||
SegmentType.TEXT -> JSTextSegment(client, obj);
|
SegmentType.TEXT -> JSTextSegment(client, obj);
|
||||||
SegmentType.IMAGES -> JSImagesSegment(client, obj);
|
SegmentType.IMAGES -> JSImagesSegment(client, obj);
|
||||||
SegmentType.HEADER -> JSHeaderSegment(client, obj);
|
SegmentType.HEADER -> JSHeaderSegment(client, obj);
|
||||||
@@ -109,6 +124,7 @@ open class JSArticleDetails(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class SegmentType(val value: Int) {
|
enum class SegmentType(val value: Int) {
|
||||||
|
|||||||
+32
-10
@@ -46,23 +46,45 @@ class JSComment : IPlatformComment {
|
|||||||
_comment = obj;
|
_comment = obj;
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
|
|
||||||
|
var parsedContextUrl: String? = null;
|
||||||
|
var parsedAuthor: PlatformAuthorLink? = null;
|
||||||
|
var parsedMessage: String? = null;
|
||||||
|
var parsedRating: IRating? = null;
|
||||||
|
var parsedDate: OffsetDateTime? = null;
|
||||||
|
var parsedReplyCount: Int? = null;
|
||||||
|
var parsedContext: Map<String, String>? = null;
|
||||||
|
var parsedHasGetReplies = false;
|
||||||
|
|
||||||
|
plugin.busy {
|
||||||
val contextName = "Comment";
|
val contextName = "Comment";
|
||||||
contextUrl = _comment!!.getOrThrow(config, "contextUrl", contextName);
|
parsedContextUrl = _comment!!.getOrThrow(config, "contextUrl", contextName);
|
||||||
author = PlatformAuthorLink.fromV8(_config!!, _comment!!.getOrThrow(config, "author", contextName));
|
parsedAuthor = PlatformAuthorLink.fromV8(_config!!, _comment!!.getOrThrow(config, "author", contextName));
|
||||||
message = _comment!!.getOrThrow(config, "message", contextName);
|
parsedMessage = _comment!!.getOrThrow(config, "message", contextName);
|
||||||
rating = IRating.fromV8(config, _comment!!.getOrThrow(config, "rating", contextName));
|
parsedRating = IRating.fromV8(config, _comment!!.getOrThrow(config, "rating", contextName));
|
||||||
date = _comment!!.getOrThrowNullable<Int>(config, "date", contextName)?.let { OffsetDateTime.of(LocalDateTime.ofEpochSecond(it.toLong(), 0, ZoneOffset.UTC), ZoneOffset.UTC) }
|
parsedDate = _comment!!.getOrThrowNullable<Int>(config, "date", contextName)?.let { OffsetDateTime.of(LocalDateTime.ofEpochSecond(it.toLong(), 0, ZoneOffset.UTC), ZoneOffset.UTC) };
|
||||||
replyCount = _comment!!.getOrThrowNullable(config, "replyCount", contextName);
|
parsedReplyCount = _comment!!.getOrThrowNullable(config, "replyCount", contextName);
|
||||||
context = _comment!!.getOrDefault(config, "context", contextName, hashMapOf()) ?: hashMapOf();
|
parsedContext = _comment!!.getOrDefault(config, "context", contextName, hashMapOf()) ?: hashMapOf();
|
||||||
_hasGetReplies = _comment!!.has("getReplies");
|
parsedHasGetReplies = _comment!!.has("getReplies");
|
||||||
|
}
|
||||||
|
|
||||||
|
contextUrl = parsedContextUrl ?: "";
|
||||||
|
author = parsedAuthor ?: PlatformAuthorLink.UNKNOWN;
|
||||||
|
message = parsedMessage ?: "";
|
||||||
|
rating = parsedRating ?: throw IllegalStateException("Missing comment rating");
|
||||||
|
date = parsedDate;
|
||||||
|
replyCount = parsedReplyCount;
|
||||||
|
context = parsedContext ?: hashMapOf();
|
||||||
|
_hasGetReplies = parsedHasGetReplies;
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReplies(client: IPlatformClient): IPager<IPlatformComment>? {
|
override fun getReplies(client: IPlatformClient): IPager<IPlatformComment>? {
|
||||||
if(!_hasGetReplies)
|
if(!_hasGetReplies)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
val obj = _comment!!.invokeV8<V8ValueObject>("getReplies", arrayOf<Any>());
|
|
||||||
val plugin = if(client is JSClient) client else throw NotImplementedError("Only implemented for JSClient");
|
val plugin = if(client is JSClient) client else throw NotImplementedError("Only implemented for JSClient");
|
||||||
return JSCommentPager(_config!!, plugin, obj);
|
return plugin.busy {
|
||||||
|
val obj = _comment!!.invokeV8<V8ValueObject>("getReplies", arrayOf<Any>());
|
||||||
|
return@busy JSCommentPager(_config!!, plugin, obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-1
@@ -11,6 +11,7 @@ import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
|||||||
import com.futo.platformplayer.decodeUnicode
|
import com.futo.platformplayer.decodeUnicode
|
||||||
import com.futo.platformplayer.getOrDefault
|
import com.futo.platformplayer.getOrDefault
|
||||||
import com.futo.platformplayer.getOrThrow
|
import com.futo.platformplayer.getOrThrow
|
||||||
|
import com.futo.platformplayer.getSourcePlugin
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
@@ -23,7 +24,8 @@ open class JSContent(
|
|||||||
|
|
||||||
override val contentType: ContentType = ContentType.UNKNOWN
|
override val contentType: ContentType = ContentType.UNKNOWN
|
||||||
|
|
||||||
protected val _hasGetDetails: Boolean = _content.has("getDetails")
|
protected val _hasGetDetails: Boolean =
|
||||||
|
_content.getSourcePlugin()?.busy { _content.has("getDetails") } ?: false
|
||||||
|
|
||||||
override val id: PlatformID =
|
override val id: PlatformID =
|
||||||
PlatformID.fromV8(_pluginConfig, _content.getOrThrow(_pluginConfig, "id", CTX))
|
PlatformID.fromV8(_pluginConfig, _content.getOrThrow(_pluginConfig, "id", CTX))
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ abstract class JSPager<T> : IPager<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun hasMorePages(): Boolean {
|
override fun hasMorePages(): Boolean {
|
||||||
return _hasMorePages && !pager.isClosed;
|
return plugin.getUnderlyingPlugin().busy {
|
||||||
|
_hasMorePages && !pager.isClosed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nextPage() {
|
override fun nextPage() {
|
||||||
|
|||||||
+45
-17
@@ -15,6 +15,7 @@ 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.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
import com.futo.platformplayer.getOrDefault
|
import com.futo.platformplayer.getOrDefault
|
||||||
|
import com.futo.platformplayer.getSourcePlugin
|
||||||
import com.futo.platformplayer.invokeV8
|
import com.futo.platformplayer.invokeV8
|
||||||
import com.futo.platformplayer.states.StateDeveloper
|
import com.futo.platformplayer.states.StateDeveloper
|
||||||
|
|
||||||
@@ -30,52 +31,79 @@ class JSPostDetails : JSPost, IPlatformPost, IPlatformPostDetails {
|
|||||||
|
|
||||||
|
|
||||||
constructor(config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) {
|
constructor(config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) {
|
||||||
|
var parsedRating: IRating? = null;
|
||||||
|
var parsedTextType: TextType? = null;
|
||||||
|
var parsedContent: String? = null;
|
||||||
|
var parsedHasGetComments = false;
|
||||||
|
var parsedHasGetContentRecommendations = false;
|
||||||
|
|
||||||
|
val parse = {
|
||||||
val contextName = "PlatformPostDetails";
|
val contextName = "PlatformPostDetails";
|
||||||
|
|
||||||
rating = obj.getOrDefault<V8ValueObject>(config, "rating", contextName, null)?.let { IRating.fromV8(config, it, contextName) } ?: RatingLikes(0);
|
parsedRating = obj.getOrDefault<V8ValueObject>(config, "rating", contextName, null)?.let { IRating.fromV8(config, it, contextName) } ?: RatingLikes(0);
|
||||||
textType = TextType.fromInt((obj.getOrDefault<Int>(config, "textType", contextName, null) ?: 0));
|
parsedTextType = TextType.fromInt((obj.getOrDefault<Int>(config, "textType", contextName, null) ?: 0));
|
||||||
content = obj.getOrDefault(config, "content", contextName, "") ?: "";
|
parsedContent = obj.getOrDefault(config, "content", contextName, "") ?: "";
|
||||||
|
|
||||||
_hasGetComments = _content.has("getComments");
|
parsedHasGetComments = _content.has("getComments");
|
||||||
_hasGetContentRecommendations = _content.has("getContentRecommendations");
|
parsedHasGetContentRecommendations = _content.has("getContentRecommendations");
|
||||||
|
};
|
||||||
|
obj.getSourcePlugin()?.busy {
|
||||||
|
parse();
|
||||||
|
} ?: parse()
|
||||||
|
|
||||||
|
rating = parsedRating ?: RatingLikes(0);
|
||||||
|
textType = parsedTextType ?: TextType.RAW;
|
||||||
|
content = parsedContent ?: "";
|
||||||
|
_hasGetComments = parsedHasGetComments;
|
||||||
|
_hasGetContentRecommendations = parsedHasGetContentRecommendations;
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getComments(client: IPlatformClient): IPager<IPlatformComment>? {
|
override fun getComments(client: IPlatformClient): IPager<IPlatformComment>? {
|
||||||
if(!_hasGetComments || _content.isClosed)
|
val jsClient = client as? JSClient;
|
||||||
|
if(jsClient == null)
|
||||||
|
return null;
|
||||||
|
val canGetComments = jsClient.busy {
|
||||||
|
_hasGetComments && !_content.isClosed
|
||||||
|
}
|
||||||
|
if(!canGetComments)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if(client is DevJSClient)
|
if(client is DevJSClient)
|
||||||
return StateDeveloper.instance.handleDevCall(client.devID, "videoDetail.getComments()") {
|
return StateDeveloper.instance.handleDevCall(client.devID, "videoDetail.getComments()") {
|
||||||
return@handleDevCall getCommentsJS(client);
|
return@handleDevCall getCommentsJS(client);
|
||||||
}
|
}
|
||||||
else if(client is JSClient)
|
return getCommentsJS(jsClient);
|
||||||
return getCommentsJS(client);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
override fun getPlaybackTracker(): IPlaybackTracker? = null;
|
override fun getPlaybackTracker(): IPlaybackTracker? = null;
|
||||||
|
|
||||||
override fun getContentRecommendations(client: IPlatformClient): IPager<IPlatformContent>? {
|
override fun getContentRecommendations(client: IPlatformClient): IPager<IPlatformContent>? {
|
||||||
if(!_hasGetContentRecommendations || _content.isClosed)
|
val jsClient = client as? JSClient;
|
||||||
|
if(jsClient == null)
|
||||||
|
return null;
|
||||||
|
val canGetContentRecommendations = jsClient.busy {
|
||||||
|
_hasGetContentRecommendations && !_content.isClosed
|
||||||
|
}
|
||||||
|
if(!canGetContentRecommendations)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if(client is DevJSClient)
|
if(client is DevJSClient)
|
||||||
return StateDeveloper.instance.handleDevCall(client.devID, "postDetail.getContentRecommendations()") {
|
return StateDeveloper.instance.handleDevCall(client.devID, "postDetail.getContentRecommendations()") {
|
||||||
return@handleDevCall getContentRecommendationsJS(client);
|
return@handleDevCall getContentRecommendationsJS(client);
|
||||||
}
|
}
|
||||||
else if(client is JSClient)
|
return getContentRecommendationsJS(jsClient);
|
||||||
return getContentRecommendationsJS(client);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
private fun getContentRecommendationsJS(client: JSClient): JSContentPager {
|
private fun getContentRecommendationsJS(client: JSClient): JSContentPager {
|
||||||
|
return client.busy {
|
||||||
val contentPager = _content.invokeV8<V8ValueObject>("getContentRecommendations", arrayOf<Any>());
|
val contentPager = _content.invokeV8<V8ValueObject>("getContentRecommendations", arrayOf<Any>());
|
||||||
return JSContentPager(_pluginConfig, client, contentPager);
|
return@busy JSContentPager(_pluginConfig, client, contentPager);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCommentsJS(client: JSClient): JSCommentPager {
|
private fun getCommentsJS(client: JSClient): JSCommentPager {
|
||||||
|
return client.busy {
|
||||||
val commentPager = _content.invokeV8<V8ValueObject>("getComments", arrayOf<Any>());
|
val commentPager = _content.invokeV8<V8ValueObject>("getComments", arrayOf<Any>());
|
||||||
return JSCommentPager(_pluginConfig, client, commentPager);
|
return@busy JSCommentPager(_pluginConfig, client, commentPager);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+13
-5
@@ -41,21 +41,27 @@ class JSRequestExecutor: AutoCloseable {
|
|||||||
this._config = plugin.config;
|
this._config = plugin.config;
|
||||||
val config = plugin.config;
|
val config = plugin.config;
|
||||||
|
|
||||||
urlPrefix = executor.getOrDefault(config, "urlPrefix", "RequestExecutor", null);
|
var parsedUrlPrefix: String? = null;
|
||||||
|
var parsedHasCleanup = false;
|
||||||
|
plugin.busy {
|
||||||
|
parsedUrlPrefix = executor.getOrDefault(config, "urlPrefix", "RequestExecutor", null);
|
||||||
|
|
||||||
if(!executor.has("executeRequest"))
|
if(!executor.has("executeRequest"))
|
||||||
throw ScriptImplementationException(config, "RequestExecutor is missing executeRequest", null);
|
throw ScriptImplementationException(config, "RequestExecutor is missing executeRequest", null);
|
||||||
hasCleanup = executor.has("cleanup");
|
parsedHasCleanup = executor.has("cleanup");
|
||||||
|
}
|
||||||
|
|
||||||
|
urlPrefix = parsedUrlPrefix;
|
||||||
|
hasCleanup = parsedHasCleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Executor properties?
|
//TODO: Executor properties?
|
||||||
@Throws(ScriptException::class)
|
@Throws(ScriptException::class)
|
||||||
open fun executeRequest(method: String, url: String, body: ByteArray?, headers: Map<String, String>): ByteArray {
|
open fun executeRequest(method: String, url: String, body: ByteArray?, headers: Map<String, String>): ByteArray {
|
||||||
|
return _plugin.getUnderlyingPlugin().busy {
|
||||||
if (_executor.isClosed)
|
if (_executor.isClosed)
|
||||||
throw IllegalStateException("Executor object is closed");
|
throw IllegalStateException("Executor object is closed");
|
||||||
|
|
||||||
return _plugin.getUnderlyingPlugin().busy {
|
|
||||||
|
|
||||||
val result = if(_plugin is DevJSClient)
|
val result = if(_plugin is DevJSClient)
|
||||||
StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") {
|
StateDeveloper.instance.handleDevCall(_plugin.devID, "requestExecutor.executeRequest()") {
|
||||||
V8Plugin.catchScriptErrors<Any>(
|
V8Plugin.catchScriptErrors<Any>(
|
||||||
@@ -108,11 +114,13 @@ class JSRequestExecutor: AutoCloseable {
|
|||||||
|
|
||||||
|
|
||||||
open fun cleanup() {
|
open fun cleanup() {
|
||||||
|
_plugin.busy {
|
||||||
synchronized(_cleanLock) {
|
synchronized(_cleanLock) {
|
||||||
if (!hasCleanup || _executor.isClosed || _cleaned)
|
if (!hasCleanup || _executor.isClosed || _cleaned)
|
||||||
return;
|
return@busy;
|
||||||
_cleaned = true;
|
_cleaned = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Logger.i("JSRequestExecutor", "JSRequestExecutor cleanup requested");
|
Logger.i("JSRequestExecutor", "JSRequestExecutor cleanup requested");
|
||||||
_plugin.busy {
|
_plugin.busy {
|
||||||
if(_plugin is DevJSClient)
|
if(_plugin is DevJSClient)
|
||||||
|
|||||||
+2
-2
@@ -36,11 +36,11 @@ class JSRequestModifier: IRequestModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun modifyRequest(url: String, headers: Map<String, String>): IRequest {
|
override fun modifyRequest(url: String, headers: Map<String, String>): IRequest {
|
||||||
|
return _plugin.busy {
|
||||||
if (_modifier.isClosed) {
|
if (_modifier.isClosed) {
|
||||||
return Request(url, headers);
|
return@busy Request(url, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _plugin.busy {
|
|
||||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSRequestModifier", "builder.modifyRequest()") {
|
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSRequestModifier", "builder.modifyRequest()") {
|
||||||
_modifier.invokeV8("modifyRequest", url, headers);
|
_modifier.invokeV8("modifyRequest", url, headers);
|
||||||
} as V8ValueObject;
|
} as V8ValueObject;
|
||||||
|
|||||||
+21
-5
@@ -29,12 +29,28 @@ class JSSubtitleSource : ISubtitleSource {
|
|||||||
constructor(config: SourcePluginConfig, v8Value: V8ValueObject) {
|
constructor(config: SourcePluginConfig, v8Value: V8ValueObject) {
|
||||||
_obj = v8Value;
|
_obj = v8Value;
|
||||||
|
|
||||||
|
var parsedName: String? = null;
|
||||||
|
var parsedLanguage: String? = null;
|
||||||
|
var parsedUrl: String? = null;
|
||||||
|
var parsedFormat: String? = null;
|
||||||
|
var parsedHasFetch = false;
|
||||||
|
val parse = {
|
||||||
val context = "JSSubtitles";
|
val context = "JSSubtitles";
|
||||||
name = v8Value.getOrThrow(config, "name", context, false);
|
parsedName = v8Value.getOrThrow(config, "name", context, false);
|
||||||
language = v8Value.getOrDefault(config, "language", context, null);
|
parsedLanguage = v8Value.getOrDefault(config, "language", context, null);
|
||||||
url = v8Value.getOrThrow(config, "url", context, true);
|
parsedUrl = v8Value.getOrThrow(config, "url", context, true);
|
||||||
format = v8Value.getOrThrow(config, "format", context, true);
|
parsedFormat = v8Value.getOrThrow(config, "format", context, true);
|
||||||
hasFetch = v8Value.has("getSubtitles");
|
parsedHasFetch = v8Value.has("getSubtitles");
|
||||||
|
};
|
||||||
|
v8Value.getSourcePlugin()?.busy {
|
||||||
|
parse();
|
||||||
|
} ?: parse()
|
||||||
|
|
||||||
|
name = parsedName ?: "";
|
||||||
|
language = parsedLanguage;
|
||||||
|
url = parsedUrl;
|
||||||
|
format = parsedFormat;
|
||||||
|
hasFetch = parsedHasFetch;
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSubtitles(): String {
|
override fun getSubtitles(): String {
|
||||||
|
|||||||
+54
-17
@@ -52,34 +52,63 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
|||||||
override val subtitles: List<ISubtitleSource>;
|
override val subtitles: List<ISubtitleSource>;
|
||||||
|
|
||||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin.config, obj) {
|
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin.config, obj) {
|
||||||
val contextName = "VideoDetails";
|
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
|
var parsedDescription: String? = null;
|
||||||
|
var parsedVideo: IVideoSourceDescriptor? = null;
|
||||||
|
var parsedDash: IDashManifestSource? = null;
|
||||||
|
var parsedHls: IHLSManifestSource? = null;
|
||||||
|
var parsedLive: IVideoSource? = null;
|
||||||
|
var parsedRating: IRating? = null;
|
||||||
|
var parsedSubtitles: List<ISubtitleSource>? = null;
|
||||||
|
var parsedHasGetComments = false;
|
||||||
|
var parsedHasGetPlaybackTracker = false;
|
||||||
|
var parsedHasGetContentRecommendations = false;
|
||||||
|
var parsedHasGetVODEvents = false;
|
||||||
|
|
||||||
|
plugin.busy {
|
||||||
|
val contextName = "VideoDetails";
|
||||||
val config = plugin.config;
|
val config = plugin.config;
|
||||||
description = _content.getOrThrow(config, "description", contextName);
|
parsedDescription = _content.getOrThrow(config, "description", contextName);
|
||||||
video = JSVideoSourceDescriptor.fromV8(plugin, _content.getOrThrow(config, "video", contextName));
|
parsedVideo = JSVideoSourceDescriptor.fromV8(plugin, _content.getOrThrow(config, "video", contextName));
|
||||||
dash = JSSource.fromV8DashNullable(plugin, _content.getOrThrowNullable<V8ValueObject>(config, "dash", contextName));
|
parsedDash = JSSource.fromV8DashNullable(plugin, _content.getOrThrowNullable<V8ValueObject>(config, "dash", contextName));
|
||||||
hls = JSSource.fromV8HLSNullable(plugin, _content.getOrThrowNullable<V8ValueObject>(config, "hls", contextName));
|
parsedHls = JSSource.fromV8HLSNullable(plugin, _content.getOrThrowNullable<V8ValueObject>(config, "hls", contextName));
|
||||||
live = JSSource.fromV8VideoNullable(plugin, _content.getOrThrowNullable<V8ValueObject>(config, "live", contextName));
|
parsedLive = JSSource.fromV8VideoNullable(plugin, _content.getOrThrowNullable<V8ValueObject>(config, "live", contextName));
|
||||||
rating = IRating.fromV8OrDefault(config, _content.getOrDefault<V8ValueObject>(config, "rating", contextName, null), RatingLikes(0));
|
parsedRating = IRating.fromV8OrDefault(config, _content.getOrDefault<V8ValueObject>(config, "rating", contextName, null), RatingLikes(0));
|
||||||
|
|
||||||
if(!_content.has("subtitles"))
|
if(!_content.has("subtitles"))
|
||||||
subtitles = listOf();
|
parsedSubtitles = listOf();
|
||||||
else {
|
else {
|
||||||
val subArrs = _content.getOrThrowNullable<V8ValueArray>(config, "subtitles", contextName);
|
val subArrs = _content.getOrThrowNullable<V8ValueArray>(config, "subtitles", contextName);
|
||||||
if(subArrs != null)
|
if(subArrs != null)
|
||||||
subtitles = subArrs.keys.map { JSSubtitleSource.fromV8(config, subArrs.get(it)) };
|
parsedSubtitles = subArrs.keys.map { JSSubtitleSource.fromV8(config, subArrs.get(it)) };
|
||||||
else
|
else
|
||||||
subtitles = listOf();
|
parsedSubtitles = listOf();
|
||||||
}
|
}
|
||||||
|
|
||||||
_hasGetComments = _content.has("getComments");
|
parsedHasGetComments = _content.has("getComments");
|
||||||
_hasGetPlaybackTracker = _content.has("getPlaybackTracker");
|
parsedHasGetPlaybackTracker = _content.has("getPlaybackTracker");
|
||||||
_hasGetContentRecommendations = _content.has("getContentRecommendations");
|
parsedHasGetContentRecommendations = _content.has("getContentRecommendations");
|
||||||
_hasGetVODEvents = _content.has("getVODEvents");
|
parsedHasGetVODEvents = _content.has("getVODEvents");
|
||||||
|
}
|
||||||
|
|
||||||
|
description = parsedDescription ?: "";
|
||||||
|
video = parsedVideo ?: throw IllegalStateException("Missing video source descriptor");
|
||||||
|
dash = parsedDash;
|
||||||
|
hls = parsedHls;
|
||||||
|
live = parsedLive;
|
||||||
|
rating = parsedRating ?: RatingLikes(0);
|
||||||
|
subtitles = parsedSubtitles ?: listOf();
|
||||||
|
_hasGetComments = parsedHasGetComments;
|
||||||
|
_hasGetPlaybackTracker = parsedHasGetPlaybackTracker;
|
||||||
|
_hasGetContentRecommendations = parsedHasGetContentRecommendations;
|
||||||
|
_hasGetVODEvents = parsedHasGetVODEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPlaybackTracker(): IPlaybackTracker? {
|
override fun getPlaybackTracker(): IPlaybackTracker? {
|
||||||
if(!_hasGetPlaybackTracker || _content.isClosed)
|
val canGetPlaybackTracker = _plugin.busy {
|
||||||
|
_hasGetPlaybackTracker && !_content.isClosed
|
||||||
|
}
|
||||||
|
if(!canGetPlaybackTracker)
|
||||||
return null;
|
return null;
|
||||||
if(_pluginConfig.id == StateDeveloper.DEV_ID)
|
if(_pluginConfig.id == StateDeveloper.DEV_ID)
|
||||||
return StateDeveloper.instance.handleDevCall(_pluginConfig.id, "videoDetail.getComments()") {
|
return StateDeveloper.instance.handleDevCall(_pluginConfig.id, "videoDetail.getComments()") {
|
||||||
@@ -102,7 +131,10 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getContentRecommendations(client: IPlatformClient): IPager<IPlatformContent>? {
|
override fun getContentRecommendations(client: IPlatformClient): IPager<IPlatformContent>? {
|
||||||
if(!_hasGetContentRecommendations || _content.isClosed)
|
val canGetContentRecommendations = _plugin.busy {
|
||||||
|
_hasGetContentRecommendations && !_content.isClosed
|
||||||
|
}
|
||||||
|
if(!canGetContentRecommendations)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if(client is DevJSClient)
|
if(client is DevJSClient)
|
||||||
@@ -122,7 +154,12 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getComments(client: IPlatformClient): IPager<IPlatformComment>? {
|
override fun getComments(client: IPlatformClient): IPager<IPlatformComment>? {
|
||||||
if(client !is JSClient || !_hasGetComments || _content.isClosed)
|
if(client !is JSClient)
|
||||||
|
return null;
|
||||||
|
val canGetComments = _plugin.busy {
|
||||||
|
_hasGetComments && !_content.isClosed
|
||||||
|
}
|
||||||
|
if(!canGetComments)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if(client is DevJSClient)
|
if(client is DevJSClient)
|
||||||
|
|||||||
+2
-2
@@ -39,10 +39,10 @@ open class JSAudioUrlSource(
|
|||||||
?: "$container $bitrate"
|
?: "$container $bitrate"
|
||||||
|
|
||||||
override var priority: Boolean =
|
override var priority: Boolean =
|
||||||
if (_obj.has("priority")) _obj.getOrThrow<Boolean>(cfg, "priority", ctx) else false
|
plugin.busy { if (_obj.has("priority")) _obj.getOrThrow<Boolean>(cfg, "priority", ctx) else false }
|
||||||
|
|
||||||
override var original: Boolean =
|
override var original: Boolean =
|
||||||
if (_obj.has("original")) _obj.getOrThrow<Boolean>(cfg, "original", ctx) else false
|
plugin.busy { if (_obj.has("original")) _obj.getOrThrow<Boolean>(cfg, "original", ctx) else false }
|
||||||
|
|
||||||
override fun getAudioUrl(): String = url
|
override fun getAudioUrl(): String = url
|
||||||
|
|
||||||
|
|||||||
+6
-4
@@ -19,21 +19,23 @@ class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource {
|
|||||||
val config = plugin.config
|
val config = plugin.config
|
||||||
|
|
||||||
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||||
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
|
hasLicenseRequestExecutor = plugin.busy { obj.has("getLicenseRequestExecutor") }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||||
|
return _plugin.busy {
|
||||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||||
return null
|
return@busy null
|
||||||
|
|
||||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSAudioUrlWidevineSource", "obj.getLicenseRequestExecutor()") {
|
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSAudioUrlWidevineSource", "obj.getLicenseRequestExecutor()") {
|
||||||
_obj.invokeV8("getLicenseRequestExecutor", arrayOf<Any>())
|
_obj.invokeV8("getLicenseRequestExecutor", arrayOf<Any>())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result !is V8ValueObject)
|
if (result !is V8ValueObject)
|
||||||
return null
|
return@busy null
|
||||||
|
|
||||||
return JSRequestExecutor(_plugin, result)
|
return@busy JSRequestExecutor(_plugin, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
|||||||
+3
-3
@@ -55,7 +55,7 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
|
|||||||
priority = _obj.getOrDefault(config, "priority", contextName, false) ?: false;
|
priority = _obj.getOrDefault(config, "priority", contextName, false) ?: false;
|
||||||
language = _obj.getOrDefault(config, "language", contextName, Language.UNKNOWN) ?: Language.UNKNOWN;
|
language = _obj.getOrDefault(config, "language", contextName, Language.UNKNOWN) ?: Language.UNKNOWN;
|
||||||
original = obj.getOrNull(config, "original", contextName) ?: false;
|
original = obj.getOrNull(config, "original", contextName) ?: false;
|
||||||
hasGenerate = _obj.has("generate");
|
hasGenerate = plugin.busy { _obj.has("generate") };
|
||||||
}
|
}
|
||||||
|
|
||||||
private var _pregenerate: V8Deferred<String?>? = null;
|
private var _pregenerate: V8Deferred<String?>? = null;
|
||||||
@@ -67,7 +67,7 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
|
|||||||
override fun generateAsync(scope: CoroutineScope): V8Deferred<String?> {
|
override fun generateAsync(scope: CoroutineScope): V8Deferred<String?> {
|
||||||
if(!hasGenerate)
|
if(!hasGenerate)
|
||||||
return V8Deferred(CompletableDeferred(manifest));
|
return V8Deferred(CompletableDeferred(manifest));
|
||||||
if(_obj.isClosed)
|
if(_plugin.busy { _obj.isClosed })
|
||||||
throw IllegalStateException("Source object already closed");
|
throw IllegalStateException("Source object already closed");
|
||||||
|
|
||||||
val pregenerated = _pregenerate;
|
val pregenerated = _pregenerate;
|
||||||
@@ -111,7 +111,7 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS
|
|||||||
override fun generate(): String? {
|
override fun generate(): String? {
|
||||||
if(!hasGenerate)
|
if(!hasGenerate)
|
||||||
return manifest;
|
return manifest;
|
||||||
if(_obj.isClosed)
|
if(_plugin.busy { _obj.isClosed })
|
||||||
throw IllegalStateException("Source object already closed");
|
throw IllegalStateException("Source object already closed");
|
||||||
|
|
||||||
val plugin = _plugin.getUnderlyingPlugin();
|
val plugin = _plugin.getUnderlyingPlugin();
|
||||||
|
|||||||
+3
-3
@@ -73,7 +73,7 @@ open class JSDashManifestRawSource(
|
|||||||
override var manifest: String? =
|
override var manifest: String? =
|
||||||
_obj.getOrDefault<String>(cfg, "manifest", ctx, null)
|
_obj.getOrDefault<String>(cfg, "manifest", ctx, null)
|
||||||
|
|
||||||
override val hasGenerate: Boolean = _obj.has("generate")
|
override val hasGenerate: Boolean = plugin.busy { _obj.has("generate") }
|
||||||
|
|
||||||
val canMerge: Boolean =
|
val canMerge: Boolean =
|
||||||
_obj.getOrDefault<Boolean>(cfg, "canMerge", ctx, false) ?: false
|
_obj.getOrDefault<Boolean>(cfg, "canMerge", ctx, false) ?: false
|
||||||
@@ -89,7 +89,7 @@ open class JSDashManifestRawSource(
|
|||||||
override fun generateAsync(scope: CoroutineScope): V8Deferred<String?> {
|
override fun generateAsync(scope: CoroutineScope): V8Deferred<String?> {
|
||||||
if(!hasGenerate)
|
if(!hasGenerate)
|
||||||
return V8Deferred(CompletableDeferred(manifest));
|
return V8Deferred(CompletableDeferred(manifest));
|
||||||
if(_obj.isClosed)
|
if(_plugin.busy { _obj.isClosed })
|
||||||
throw IllegalStateException("Source object already closed");
|
throw IllegalStateException("Source object already closed");
|
||||||
val pregenerated = _pregenerate;
|
val pregenerated = _pregenerate;
|
||||||
if(pregenerated != null) {
|
if(pregenerated != null) {
|
||||||
@@ -133,7 +133,7 @@ open class JSDashManifestRawSource(
|
|||||||
override open fun generate(): String? {
|
override open fun generate(): String? {
|
||||||
if(!hasGenerate)
|
if(!hasGenerate)
|
||||||
return manifest;
|
return manifest;
|
||||||
if(_obj.isClosed)
|
if(_plugin.busy { _obj.isClosed })
|
||||||
throw IllegalStateException("Source object already closed");
|
throw IllegalStateException("Source object already closed");
|
||||||
|
|
||||||
var result: String? = null;
|
var result: String? = null;
|
||||||
|
|||||||
+6
-4
@@ -42,24 +42,26 @@ class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource,
|
|||||||
priority = obj.getOrNull(config, "priority", contextName) ?: false
|
priority = obj.getOrNull(config, "priority", contextName) ?: false
|
||||||
|
|
||||||
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||||
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
|
hasLicenseRequestExecutor = plugin.busy { obj.has("getLicenseRequestExecutor") }
|
||||||
|
|
||||||
language = _obj.getOrNull(config, "language", contextName);
|
language = _obj.getOrNull(config, "language", contextName);
|
||||||
original = _obj.getOrNull(config, "original", contextName);
|
original = _obj.getOrNull(config, "original", contextName);
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||||
|
return _plugin.busy {
|
||||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||||
return null
|
return@busy null
|
||||||
|
|
||||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSDashManifestWidevineSource", "obj.getLicenseRequestExecutor()") {
|
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSDashManifestWidevineSource", "obj.getLicenseRequestExecutor()") {
|
||||||
_obj.invokeV8("getLicenseRequestExecutor", arrayOf<Any>())
|
_obj.invokeV8("getLicenseRequestExecutor", arrayOf<Any>())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result !is V8ValueObject)
|
if (result !is V8ValueObject)
|
||||||
return null
|
return@busy null
|
||||||
|
|
||||||
return JSRequestExecutor(_plugin, result)
|
return@busy JSRequestExecutor(_plugin, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getVideoUrl(): String {
|
override fun getVideoUrl(): String {
|
||||||
|
|||||||
+16
-5
@@ -44,15 +44,26 @@ abstract class JSSource {
|
|||||||
this._obj = obj;
|
this._obj = obj;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
||||||
_requestModifier = obj.getOrDefault<V8ValueObject>(_config, "requestModifier", "JSSource.requestModifier", null)?.let {
|
var parsedRequestModifier: JSRequest? = null;
|
||||||
|
var parsedHasRequestModifier = false;
|
||||||
|
var parsedRequestExecutor: JSRequest? = null;
|
||||||
|
var parsedHasRequestExecutor = false;
|
||||||
|
plugin.busy {
|
||||||
|
parsedRequestModifier = obj.getOrDefault<V8ValueObject>(_config, "requestModifier", "JSSource.requestModifier", null)?.let {
|
||||||
JSRequest(plugin, it, null, null, true);
|
JSRequest(plugin, it, null, null, true);
|
||||||
}
|
};
|
||||||
hasRequestModifier = _requestModifier != null || obj.has("getRequestModifier");
|
parsedHasRequestModifier = parsedRequestModifier != null || obj.has("getRequestModifier");
|
||||||
|
|
||||||
_requestExecutor = obj.getOrDefault<V8ValueObject>(_config, "requestExecutor", "JSSource.requestExecutor", null)?.let {
|
parsedRequestExecutor = obj.getOrDefault<V8ValueObject>(_config, "requestExecutor", "JSSource.requestExecutor", null)?.let {
|
||||||
JSRequest(plugin, it, null, null, true);
|
JSRequest(plugin, it, null, null, true);
|
||||||
|
};
|
||||||
|
parsedHasRequestExecutor = parsedRequestExecutor != null || obj.has("getRequestExecutor");
|
||||||
}
|
}
|
||||||
hasRequestExecutor = _requestExecutor != null || obj.has("getRequestExecutor");
|
|
||||||
|
_requestModifier = parsedRequestModifier;
|
||||||
|
hasRequestModifier = parsedHasRequestModifier;
|
||||||
|
_requestExecutor = parsedRequestExecutor;
|
||||||
|
hasRequestExecutor = parsedHasRequestExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRequestModifier(): IRequestModifier? = _plugin.isBusyWith("getRequestModifier") {
|
fun getRequestModifier(): IRequestModifier? = _plugin.isBusyWith("getRequestModifier") {
|
||||||
|
|||||||
+6
-4
@@ -18,21 +18,23 @@ class JSVideoUrlWidevineSource : JSVideoUrlSource, IVideoUrlWidevineSource {
|
|||||||
val config = plugin.config
|
val config = plugin.config
|
||||||
|
|
||||||
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||||
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
|
hasLicenseRequestExecutor = plugin.busy { obj.has("getLicenseRequestExecutor") }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||||
|
return _plugin.busy {
|
||||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||||
return null
|
return@busy null
|
||||||
|
|
||||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSAudioUrlWidevineSource", "obj.getLicenseRequestExecutor()") {
|
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSAudioUrlWidevineSource", "obj.getLicenseRequestExecutor()") {
|
||||||
_obj.invokeV8("getLicenseRequestExecutor", arrayOf<Any>())
|
_obj.invokeV8("getLicenseRequestExecutor", arrayOf<Any>())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result !is V8ValueObject)
|
if (result !is V8ValueObject)
|
||||||
return null
|
return@busy null
|
||||||
|
|
||||||
return JSRequestExecutor(_plugin, result)
|
return@busy JSRequestExecutor(_plugin, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
|||||||
@@ -139,13 +139,17 @@ class VideoDownload {
|
|||||||
@Contextual
|
@Contextual
|
||||||
@kotlinx.serialization.Transient
|
@kotlinx.serialization.Transient
|
||||||
var videoSourceLive: JSSource? = null;
|
var videoSourceLive: JSSource? = null;
|
||||||
val isLiveVideoSourceValid get() = videoSourceLive?.getUnderlyingObject()?.isClosed?.let { !it } ?: false;
|
val isLiveVideoSourceValid get() = videoSourceLive?.getUnderlyingPlugin()?.busy {
|
||||||
|
videoSourceLive?.getUnderlyingObject()?.isClosed?.let { !it } ?: false;
|
||||||
|
} ?: false;
|
||||||
|
|
||||||
var requiresLiveAudioSource: Boolean = false;
|
var requiresLiveAudioSource: Boolean = false;
|
||||||
@Contextual
|
@Contextual
|
||||||
@kotlinx.serialization.Transient
|
@kotlinx.serialization.Transient
|
||||||
var audioSourceLive: JSSource? = null;
|
var audioSourceLive: JSSource? = null;
|
||||||
val isLiveAudioSourceValid get() = audioSourceLive?.getUnderlyingObject()?.isClosed?.let { !it } ?: false;
|
val isLiveAudioSourceValid get() = audioSourceLive?.getUnderlyingPlugin()?.busy {
|
||||||
|
audioSourceLive?.getUnderlyingObject()?.isClosed?.let { !it } ?: false;
|
||||||
|
} ?: false;
|
||||||
|
|
||||||
var hasVideoRequestExecutor: Boolean = false;
|
var hasVideoRequestExecutor: Boolean = false;
|
||||||
var hasAudioRequestExecutor: Boolean = false;
|
var hasAudioRequestExecutor: Boolean = false;
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import kotlinx.coroutines.Dispatchers.IO
|
|||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import kotlin.concurrent.withLock
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
@@ -95,6 +96,9 @@ class V8Plugin {
|
|||||||
private val _busyLock = ReentrantLock()
|
private val _busyLock = ReentrantLock()
|
||||||
val isBusy get() = _busyLock.isLocked;
|
val isBusy get() = _busyLock.isLocked;
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var _busyHolder: Thread? = null;
|
||||||
|
|
||||||
var allowDevSubmit: Boolean = false
|
var allowDevSubmit: Boolean = false
|
||||||
private set(value) {
|
private set(value) {
|
||||||
field = value;
|
field = value;
|
||||||
@@ -161,9 +165,10 @@ class V8Plugin {
|
|||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
val script = _script ?: throw IllegalStateException("Attempted to start V8 without script");
|
val script = _script ?: throw IllegalStateException("Attempted to start V8 without script");
|
||||||
|
tryBusy(BUSY_STARTUP_MS) {
|
||||||
synchronized(_runtimeLock) {
|
synchronized(_runtimeLock) {
|
||||||
if (_runtime != null)
|
if (_runtime != null)
|
||||||
return;
|
return@tryBusy;
|
||||||
runtimeId = runtimeId + 1;
|
runtimeId = runtimeId + 1;
|
||||||
//V8RuntimeOptions.V8_FLAGS.setUseStrict(true);
|
//V8RuntimeOptions.V8_FLAGS.setUseStrict(true);
|
||||||
val host = V8Host.getV8Instance();
|
val host = V8Host.getV8Instance();
|
||||||
@@ -209,6 +214,7 @@ class V8Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fun stop(){
|
fun stop(){
|
||||||
Logger.i(TAG, "Stopping plugin [${config.name}]");
|
Logger.i(TAG, "Stopping plugin [${config.name}]");
|
||||||
busy {
|
busy {
|
||||||
@@ -254,27 +260,30 @@ class V8Plugin {
|
|||||||
fun isThreadAlreadyBusy(): Boolean {
|
fun isThreadAlreadyBusy(): Boolean {
|
||||||
return _busyLock.isHeldByCurrentThread;
|
return _busyLock.isHeldByCurrentThread;
|
||||||
}
|
}
|
||||||
fun <T> busy(handle: ()->T): T {
|
fun <T> busy(handle: ()->T): T = busyInternal(BUSY_FATAL_MS, true, "busy(enter)", handle)
|
||||||
_busyLock.lock();
|
|
||||||
//Logger.i(TAG, "Busy Enter [" + _busyLock.holdCount + "]" + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(5)?.firstOrNull()?.toString())
|
fun <T> tryBusy(maxWaitMs: Long, handle: ()->T): T = busyInternal(maxWaitMs, false, "tryBusy(enter)", handle)
|
||||||
|
|
||||||
|
private fun <T> busyInternal(maxWaitMs: Long, allowUnwedge: Boolean, context: String, handle: ()->T): T {
|
||||||
|
acquireBusyOrThrow(context, maxWaitMs, allowUnwedge);
|
||||||
|
_busyHolder = Thread.currentThread();
|
||||||
try {
|
try {
|
||||||
return handle();
|
return handle();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
//Logger.i(TAG, "Busy Leave [" + _busyLock.holdCount + "]" + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString()+ ", " + Thread.currentThread().stackTrace.drop(5)?.firstOrNull()?.toString())
|
if (_busyLock.isHeldByCurrentThread) {
|
||||||
|
if (_busyLock.holdCount == 1)
|
||||||
|
_busyHolder = null;
|
||||||
_busyLock.unlock();
|
_busyLock.unlock();
|
||||||
}
|
}
|
||||||
/*
|
}
|
||||||
_busyLock.withLock {
|
|
||||||
//Logger.i(TAG, "Entered busy: " + Thread.currentThread().stackTrace.drop(3)?.firstOrNull()?.toString() + ", " + Thread.currentThread().stackTrace.drop(4)?.firstOrNull()?.toString());
|
|
||||||
return handle();
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
fun <T> unbusy(handle: ()->T): T {
|
fun <T> unbusy(handle: ()->T): T {
|
||||||
val wasLocked = isThreadAlreadyBusy();
|
val wasLocked = isThreadAlreadyBusy();
|
||||||
if(!wasLocked)
|
if(!wasLocked)
|
||||||
return handle();
|
return handle();
|
||||||
val lockCount = _busyLock.holdCount;
|
val lockCount = _busyLock.holdCount;
|
||||||
|
_busyHolder = null;
|
||||||
for(i in 1..lockCount)
|
for(i in 1..lockCount)
|
||||||
_busyLock.unlock();
|
_busyLock.unlock();
|
||||||
try {
|
try {
|
||||||
@@ -283,9 +292,90 @@ class V8Plugin {
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
Logger.w(TAG, "Relocking V8 thread for [${config.name}] for a blocking resolve of a promise")
|
Logger.w(TAG, "Relocking V8 thread for [${config.name}] for a blocking resolve of a promise")
|
||||||
|
var acquired = 0;
|
||||||
|
try {
|
||||||
|
for (i in 1..lockCount) {
|
||||||
|
acquireBusyOrThrow("unbusy(relock)");
|
||||||
|
acquired++;
|
||||||
|
}
|
||||||
|
_busyHolder = Thread.currentThread();
|
||||||
|
}
|
||||||
|
catch (timeout: Throwable) {
|
||||||
|
for (j in 1..acquired)
|
||||||
|
_busyLock.unlock();
|
||||||
|
throw timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(i in 1..lockCount)
|
private fun acquireBusyOrThrow(context: String, maxWaitMs: Long = BUSY_FATAL_MS, allowUnwedge: Boolean = true) {
|
||||||
_busyLock.lock();
|
val warnAt = Math.min(BUSY_WARN_MS, maxWaitMs);
|
||||||
|
if (_busyLock.tryLock(warnAt, TimeUnit.MILLISECONDS))
|
||||||
|
return;
|
||||||
|
logBusyContention(context);
|
||||||
|
val remaining = maxWaitMs - warnAt;
|
||||||
|
if (remaining > 0 && _busyLock.tryLock(remaining, TimeUnit.MILLISECONDS))
|
||||||
|
return;
|
||||||
|
if (!allowUnwedge)
|
||||||
|
throw IllegalStateException("V8 busy lock for [${config.name}] timed out after ${maxWaitMs}ms in $context (fast-fail)");
|
||||||
|
unwedgeBusyHolder(context);
|
||||||
|
if (_busyLock.tryLock(BUSY_RECOVERY_MS, TimeUnit.MILLISECONDS))
|
||||||
|
return;
|
||||||
|
throw IllegalStateException("V8 busy lock for [${config.name}] timed out after ${maxWaitMs + BUSY_RECOVERY_MS}ms in $context; holder did not release after recovery");
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unwedgeBusyHolder(context: String) {
|
||||||
|
val holder = _busyHolder;
|
||||||
|
Logger.w(TAG, "V8 busy lock for [${config.name}] still held in $context after ${BUSY_FATAL_MS}ms; attempting to unwedge holder ${holder?.name ?: "unknown"}");
|
||||||
|
try {
|
||||||
|
val rt = _runtime;
|
||||||
|
if (rt != null && !rt.isClosed && !rt.isDead) {
|
||||||
|
Logger.w(TAG, "Calling terminateExecution() on [${config.name}] runtime");
|
||||||
|
rt.terminateExecution();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ex: Throwable) {
|
||||||
|
Logger.e(TAG, "terminateExecution() failed for [${config.name}]", ex);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
holder?.interrupt();
|
||||||
|
}
|
||||||
|
catch (ex: Throwable) {
|
||||||
|
Logger.e(TAG, "Interrupting holder thread for [${config.name}] failed", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logBusyContention(context: String) {
|
||||||
|
try {
|
||||||
|
val holder = _busyHolder;
|
||||||
|
val sb = StringBuilder();
|
||||||
|
sb.append("V8 BUSY CONTENTION [${config.name}] in $context: queueLength=${_busyLock.queueLength}, holdCount=${_busyLock.holdCount}, waited>${BUSY_WARN_MS}ms\n");
|
||||||
|
if (holder != null) {
|
||||||
|
sb.append("Lock holder: ${holder.name} (id=${holder.id}, state=${holder.state})\n");
|
||||||
|
for (frame in holder.stackTrace.take(40))
|
||||||
|
sb.append(" at ").append(frame.toString()).append("\n");
|
||||||
|
} else {
|
||||||
|
sb.append("Lock holder unknown (likely already released or never set)\n");
|
||||||
|
}
|
||||||
|
sb.append("Suspect waiting/blocked threads:\n");
|
||||||
|
val cur = Thread.currentThread();
|
||||||
|
for ((thread, stack) in Thread.getAllStackTraces()) {
|
||||||
|
if (thread == cur || thread == holder) continue;
|
||||||
|
if (thread.state != Thread.State.WAITING && thread.state != Thread.State.BLOCKED && thread.state != Thread.State.TIMED_WAITING) continue;
|
||||||
|
if (stack.none {
|
||||||
|
val cn = it.className;
|
||||||
|
cn.contains("V8Plugin") || cn.contains("JSClient") || cn.contains("Extensions_V8")
|
||||||
|
|| cn.contains("Subscription") || cn.contains("PackageHttp") || cn.contains("JSPager")
|
||||||
|
|| cn.contains("JSContent")
|
||||||
|
}) continue;
|
||||||
|
sb.append(" ${thread.name} (state=${thread.state}):\n");
|
||||||
|
for (frame in stack.take(20))
|
||||||
|
sb.append(" at ").append(frame.toString()).append("\n");
|
||||||
|
}
|
||||||
|
Logger.w(TAG, sb.toString());
|
||||||
|
}
|
||||||
|
catch (ex: Throwable) {
|
||||||
|
Logger.e(TAG, "Failed to log busy contention", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun execute(js: String) : V8Value {
|
fun execute(js: String) : V8Value {
|
||||||
@@ -430,6 +520,11 @@ class V8Plugin {
|
|||||||
|
|
||||||
val TAG = "V8Plugin";
|
val TAG = "V8Plugin";
|
||||||
|
|
||||||
|
private const val BUSY_WARN_MS = 10_000L;
|
||||||
|
private const val BUSY_FATAL_MS = 60_000L;
|
||||||
|
private const val BUSY_RECOVERY_MS = 5_000L;
|
||||||
|
const val BUSY_STARTUP_MS = 5_000L;
|
||||||
|
|
||||||
fun getPluginFromRuntime(runtime: V8Runtime): V8Plugin? {
|
fun getPluginFromRuntime(runtime: V8Runtime): V8Plugin? {
|
||||||
return _runtimeMap.getOrDefault(runtime, null);
|
return _runtimeMap.getOrDefault(runtime, null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,8 +128,10 @@ class PackageBridge : V8Package {
|
|||||||
@V8Function
|
@V8Function
|
||||||
fun dispose(value: V8Value) {
|
fun dispose(value: V8Value) {
|
||||||
Logger.e(TAG, "Manual dispose: " + value.javaClass.name);
|
Logger.e(TAG, "Manual dispose: " + value.javaClass.name);
|
||||||
|
_plugin.busy {
|
||||||
value.close();
|
value.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var timeoutCounter = 0;
|
var timeoutCounter = 0;
|
||||||
var timeoutMap = ConcurrentHashMap<Int, Any?>();
|
var timeoutMap = ConcurrentHashMap<Int, Any?>();
|
||||||
|
|||||||
@@ -651,14 +651,17 @@ class PackageHttp: V8Package {
|
|||||||
|
|
||||||
@V8Function
|
@V8Function
|
||||||
fun connect(socketObj: V8ValueObject) {
|
fun connect(socketObj: V8ValueObject) {
|
||||||
val hasOpen = socketObj.has("open");
|
val (hasOpen, hasMessage, hasClosing, hasClosed, hasFailure) = _package._plugin.busy {
|
||||||
val hasMessage = socketObj.has("message");
|
val open = socketObj.has("open");
|
||||||
val hasClosing = socketObj.has("closing");
|
val message = socketObj.has("message");
|
||||||
val hasClosed = socketObj.has("closed");
|
val closing = socketObj.has("closing");
|
||||||
val hasFailure = socketObj.has("failure");
|
val closed = socketObj.has("closed");
|
||||||
|
val failure = socketObj.has("failure");
|
||||||
|
|
||||||
socketObj.setWeak(); //We have to manage this lifecycle
|
socketObj.setWeak(); //We have to manage this lifecycle
|
||||||
_listeners = socketObj;
|
_listeners = socketObj;
|
||||||
|
Quintuple(open, message, closing, closed, failure);
|
||||||
|
};
|
||||||
|
|
||||||
_socket = _packageClient.logExceptions {
|
_socket = _packageClient.logExceptions {
|
||||||
val client = _client;
|
val client = _client;
|
||||||
@@ -666,52 +669,51 @@ class PackageHttp: V8Package {
|
|||||||
override fun open() {
|
override fun open() {
|
||||||
Logger.i(TAG, "Websocket opened: " + _url);
|
Logger.i(TAG, "Websocket opened: " + _url);
|
||||||
_isOpen = true;
|
_isOpen = true;
|
||||||
if(hasOpen && _listeners?.isClosed != true) {
|
|
||||||
try {
|
try {
|
||||||
_package._plugin.busy {
|
_package._plugin.busy {
|
||||||
|
if(hasOpen && _listeners?.isClosed != true) {
|
||||||
_listeners?.invokeV8Void("open", arrayOf<Any>());
|
_listeners?.invokeV8Void("open", arrayOf<Any>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable){
|
catch(ex: Throwable){
|
||||||
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] open failed: " + ex.message, ex);
|
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] open failed: " + ex.message, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
override fun message(msg: String) {
|
override fun message(msg: String) {
|
||||||
if(hasMessage && _listeners?.isClosed != true) {
|
|
||||||
try {
|
try {
|
||||||
_package._plugin.busy {
|
_package._plugin.busy {
|
||||||
|
if(hasMessage && _listeners?.isClosed != true) {
|
||||||
_listeners?.invokeV8Void("message", msg);
|
_listeners?.invokeV8Void("message", msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable) {}
|
catch(ex: Throwable) {}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
override fun closing(code: Int, reason: String) {
|
override fun closing(code: Int, reason: String) {
|
||||||
if(hasClosing && _listeners?.isClosed != true)
|
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
_package._plugin.busy {
|
_package._plugin.busy {
|
||||||
|
if(hasClosing && _listeners?.isClosed != true) {
|
||||||
_listeners?.invokeV8Void("closing", code, reason);
|
_listeners?.invokeV8Void("closing", code, reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable){
|
catch(ex: Throwable){
|
||||||
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closing failed: " + ex.message, ex);
|
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closing failed: " + ex.message, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
override fun closed(code: Int, reason: String) {
|
override fun closed(code: Int, reason: String) {
|
||||||
_isOpen = false;
|
_isOpen = false;
|
||||||
if(hasClosed && _listeners?.isClosed != true) {
|
|
||||||
try {
|
try {
|
||||||
_package._plugin.busy {
|
_package._plugin.busy {
|
||||||
|
if(hasClosed && _listeners?.isClosed != true) {
|
||||||
_listeners?.invokeV8Void("closed", code, reason);
|
_listeners?.invokeV8Void("closed", code, reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable){
|
catch(ex: Throwable){
|
||||||
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex);
|
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Logger.w(TAG, "PackageHttp Socket removed");
|
Logger.w(TAG, "PackageHttp Socket removed");
|
||||||
synchronized(_package.aliveSockets) {
|
synchronized(_package.aliveSockets) {
|
||||||
_package.aliveSockets.remove(this@SocketResult);
|
_package.aliveSockets.remove(this@SocketResult);
|
||||||
@@ -720,17 +722,17 @@ class PackageHttp: V8Package {
|
|||||||
override fun failure(exception: Throwable) {
|
override fun failure(exception: Throwable) {
|
||||||
_isOpen = false;
|
_isOpen = false;
|
||||||
Logger.e(TAG, "Websocket failure: ${exception.message} (${_url})", exception);
|
Logger.e(TAG, "Websocket failure: ${exception.message} (${_url})", exception);
|
||||||
if(hasFailure && _listeners?.isClosed != true) {
|
|
||||||
try {
|
try {
|
||||||
_package._plugin.busy {
|
_package._plugin.busy {
|
||||||
|
if(hasFailure && _listeners?.isClosed != true) {
|
||||||
_listeners?.invokeV8Void("failure", exception.message);
|
_listeners?.invokeV8Void("failure", exception.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch(ex: Throwable){
|
catch(ex: Throwable){
|
||||||
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex);
|
Logger.e(TAG, "Socket for [${_packageClient.parentConfig.name}] closed failed: " + ex.message, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -747,9 +749,19 @@ class PackageHttp: V8Package {
|
|||||||
@V8Function
|
@V8Function
|
||||||
fun close(code: Int?, reason: String?) {
|
fun close(code: Int?, reason: String?) {
|
||||||
_socket?.close(code ?: 1000, reason ?: "");
|
_socket?.close(code ?: 1000, reason ?: "");
|
||||||
|
_package._plugin.busy {
|
||||||
_listeners?.close()
|
_listeners?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Quintuple<A, B, C, D, E>(
|
||||||
|
val first: A,
|
||||||
|
val second: B,
|
||||||
|
val third: C,
|
||||||
|
val fourth: D,
|
||||||
|
val fifth: E
|
||||||
|
)
|
||||||
|
|
||||||
data class RequestDescriptor(
|
data class RequestDescriptor(
|
||||||
val method: String,
|
val method: String,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
|||||||
import com.futo.platformplayer.api.media.platforms.js.DevJSClient
|
import com.futo.platformplayer.api.media.platforms.js.DevJSClient
|
||||||
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.local.LocalClient
|
import com.futo.platformplayer.api.media.platforms.local.LocalClient
|
||||||
import com.futo.platformplayer.api.media.structures.EmptyPager
|
import com.futo.platformplayer.api.media.structures.EmptyPager
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
@@ -181,11 +182,14 @@ class StatePlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
var toDisables = mutableListOf<IPlatformClient>();
|
val toDispose = mutableListOf<IPlatformClient>();
|
||||||
var enabled: Array<String>;
|
var enabled: Array<String>;
|
||||||
synchronized(_clientsLock) {
|
synchronized(_clientsLock) {
|
||||||
for(e in _enabledClients) {
|
val previousAvailable = _availableClients.toList();
|
||||||
toDisables.add(e);
|
val reusableByDescriptor = HashMap<SourcePluginDescriptor, JSClient>();
|
||||||
|
for (prev in previousAvailable) {
|
||||||
|
if (prev is JSClient)
|
||||||
|
reusableByDescriptor[prev.descriptor] = prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
_enabledClients.clear();
|
_enabledClients.clear();
|
||||||
@@ -200,16 +204,26 @@ class StatePlatform {
|
|||||||
|
|
||||||
for (plugin in StatePlugins.instance.getPlugins()) {
|
for (plugin in StatePlugins.instance.getPlugins()) {
|
||||||
try {
|
try {
|
||||||
val client = JSClient(context, plugin);
|
val reused = reusableByDescriptor[plugin];
|
||||||
client.onCaptchaException.subscribe { c, ex ->
|
val isReused = reused != null && reused.descriptor === plugin;
|
||||||
|
val client: JSClient = if (isReused) {
|
||||||
|
reused!!;
|
||||||
|
} else {
|
||||||
|
JSClient(context, plugin).also { fresh ->
|
||||||
|
fresh.onCaptchaException.subscribe { c, ex ->
|
||||||
StateApp.instance.handleCaptchaException(c, ex);
|
StateApp.instance.handleCaptchaException(c, ex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_icons[plugin.config.id] = StatePlugins.instance.getPluginIconOrNull(plugin.config.id) ?:
|
_icons[plugin.config.id] = StatePlugins.instance.getPluginIconOrNull(plugin.config.id) ?:
|
||||||
ImageVariable(plugin.config.absoluteIconUrl, null);
|
ImageVariable(plugin.config.absoluteIconUrl, null);
|
||||||
_iconsByName[plugin.config.name.lowercase()] = StatePlugins.instance.getPluginIconOrNull(plugin.config.id) ?:
|
_iconsByName[plugin.config.name.lowercase()] = StatePlugins.instance.getPluginIconOrNull(plugin.config.id) ?:
|
||||||
ImageVariable(plugin.config.absoluteIconUrl, null);
|
ImageVariable(plugin.config.absoluteIconUrl, null);
|
||||||
_availableClients.add(client);
|
_availableClients.add(client);
|
||||||
|
|
||||||
|
if (isReused)
|
||||||
|
reusableByDescriptor.remove(plugin);
|
||||||
}
|
}
|
||||||
catch(ex: Throwable) {
|
catch(ex: Throwable) {
|
||||||
Logger.e(TAG, "Failed to initialize plugin [${plugin.config.name}]", ex);
|
Logger.e(TAG, "Failed to initialize plugin [${plugin.config.name}]", ex);
|
||||||
@@ -219,6 +233,8 @@ class StatePlatform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toDispose.addAll(reusableByDescriptor.values);
|
||||||
|
|
||||||
if(_availableClients.distinctBy { it.id }.count() < _availableClients.size) {
|
if(_availableClients.distinctBy { it.id }.count() < _availableClients.size) {
|
||||||
val dups = _availableClients.filter { x-> _availableClients.count { it.id == x.id } > 1 };
|
val dups = _availableClients.filter { x-> _availableClients.count { it.id == x.id } > 1 };
|
||||||
val overrideClients = _availableClients.distinctBy { it.id }
|
val overrideClients = _availableClients.distinctBy { it.id }
|
||||||
@@ -244,7 +260,7 @@ class StatePlatform {
|
|||||||
}
|
}
|
||||||
selectClients(*enabled);
|
selectClients(*enabled);
|
||||||
|
|
||||||
for(toDisable in toDisables) {
|
for(toDisable in toDispose) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
toDisable.disable();
|
toDisable.disable();
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import kotlin.streams.asSequence
|
|||||||
* Used to maintain subscriptions
|
* Used to maintain subscriptions
|
||||||
*/
|
*/
|
||||||
class StateSubscriptions {
|
class StateSubscriptions {
|
||||||
|
|
||||||
private val _subscriptions = FragmentedStorage.storeJson<Subscription>("subscriptions")
|
private val _subscriptions = FragmentedStorage.storeJson<Subscription>("subscriptions")
|
||||||
.withUnique { it.channel.url }
|
.withUnique { it.channel.url }
|
||||||
.withRestore(object: ReconstructStore<Subscription>(){
|
.withRestore(object: ReconstructStore<Subscription>(){
|
||||||
|
|||||||
+11
-1
@@ -40,6 +40,8 @@ import java.time.OffsetDateTime
|
|||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import java.util.concurrent.ForkJoinPool
|
import java.util.concurrent.ForkJoinPool
|
||||||
import java.util.concurrent.ForkJoinTask
|
import java.util.concurrent.ForkJoinTask
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
abstract class SubscriptionsTaskFetchAlgorithm(
|
abstract class SubscriptionsTaskFetchAlgorithm(
|
||||||
@@ -125,7 +127,7 @@ abstract class SubscriptionsTaskFetchAlgorithm(
|
|||||||
val timeTotal = measureTimeMillis {
|
val timeTotal = measureTimeMillis {
|
||||||
for(task in forkTasks) {
|
for(task in forkTasks) {
|
||||||
try {
|
try {
|
||||||
val result = task.get();
|
val result = task.get(TASK_TIMEOUT_S, TimeUnit.SECONDS);
|
||||||
if(result != null) {
|
if(result != null) {
|
||||||
if(result.pager != null) {
|
if(result.pager != null) {
|
||||||
taskResults.add(result);
|
taskResults.add(result);
|
||||||
@@ -148,6 +150,10 @@ abstract class SubscriptionsTaskFetchAlgorithm(
|
|||||||
} else {
|
} else {
|
||||||
throw ex.cause ?: ex;
|
throw ex.cause ?: ex;
|
||||||
}
|
}
|
||||||
|
} catch (ex: TimeoutException) {
|
||||||
|
Logger.w(TAG, "Subscription task timed out after ${TASK_TIMEOUT_S}s, abandoning");
|
||||||
|
task.cancel(true);
|
||||||
|
exs.add(ex);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,4 +388,8 @@ abstract class SubscriptionsTaskFetchAlgorithm(
|
|||||||
val pager: IPager<IPlatformContent>?,
|
val pager: IPager<IPlatformContent>?,
|
||||||
val exception: Throwable?
|
val exception: Throwable?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TASK_TIMEOUT_S = 90L;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user