mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-29 22:24:29 +02:00
Subscription persistence fixes, home toggle fixes, subs exchange gzip, etc
This commit is contained in:
parent
b14518edb1
commit
7b355139fb
@ -27,14 +27,18 @@ import com.futo.platformplayer.logging.Logger
|
|||||||
import com.futo.platformplayer.models.PlatformVideoWithTime
|
import com.futo.platformplayer.models.PlatformVideoWithTime
|
||||||
import com.futo.platformplayer.others.PlatformLinkMovementMethod
|
import com.futo.platformplayer.others.PlatformLinkMovementMethod
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
import java.time.OffsetDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ThreadLocalRandom
|
import java.util.concurrent.ThreadLocalRandom
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
|
import java.util.zip.GZIPOutputStream
|
||||||
|
|
||||||
private val _allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ";
|
private val _allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ";
|
||||||
fun getRandomString(sizeOfRandomString: Int): String {
|
fun getRandomString(sizeOfRandomString: Int): String {
|
||||||
@ -279,3 +283,34 @@ fun ByteBuffer.toUtf8String(): String {
|
|||||||
get(remainingBytes)
|
get(remainingBytes)
|
||||||
return String(remainingBytes, Charsets.UTF_8)
|
return String(remainingBytes, Charsets.UTF_8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun ByteArray.toGzip(): ByteArray {
|
||||||
|
if (this == null || this.isEmpty()) return ByteArray(0)
|
||||||
|
|
||||||
|
val gzipTimeStart = OffsetDateTime.now();
|
||||||
|
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
GZIPOutputStream(outputStream).use { gzip ->
|
||||||
|
gzip.write(this)
|
||||||
|
}
|
||||||
|
val result = outputStream.toByteArray();
|
||||||
|
Logger.i("Utility", "Gzip compression time: ${gzipTimeStart.getNowDiffMiliseconds()}ms");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.fromGzip(): ByteArray {
|
||||||
|
if (this == null || this.isEmpty()) return ByteArray(0)
|
||||||
|
|
||||||
|
val inputStream = ByteArrayInputStream(this)
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
|
||||||
|
GZIPInputStream(inputStream).use { gzip ->
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
var bytesRead: Int
|
||||||
|
while (gzip.read(buffer).also { bytesRead = it } != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputStream.toByteArray()
|
||||||
|
}
|
@ -250,39 +250,53 @@ class HomeFragment : MainFragment() {
|
|||||||
layoutParams =
|
layoutParams =
|
||||||
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
|
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
|
||||||
}
|
}
|
||||||
fragment._togglePluginsDisabled.clear();
|
|
||||||
synchronized(_filterLock) {
|
synchronized(_filterLock) {
|
||||||
val buttonsPlugins = (if (_togglesConfig.contains("plugins"))
|
var buttonsPlugins: List<ToggleBar.Toggle> = listOf()
|
||||||
|
buttonsPlugins = (if (_togglesConfig.contains("plugins"))
|
||||||
(StatePlatform.instance.getEnabledClients()
|
(StatePlatform.instance.getEnabledClients()
|
||||||
.filter { it is JSClient && it.enableInHome }
|
.filter { it is JSClient && it.enableInHome }
|
||||||
.map { plugin ->
|
.map { plugin ->
|
||||||
ToggleBar.Toggle(if(Settings.instance.home.showHomeFiltersPluginNames) plugin.name else "", plugin.icon, !fragment._togglePluginsDisabled.contains(plugin.id), {
|
ToggleBar.Toggle(if(Settings.instance.home.showHomeFiltersPluginNames) plugin.name else "", plugin.icon, !fragment._togglePluginsDisabled.contains(plugin.id), { view, active ->
|
||||||
if (it) {
|
var dontSwap = false;
|
||||||
|
if (active) {
|
||||||
if (fragment._togglePluginsDisabled.contains(plugin.id))
|
if (fragment._togglePluginsDisabled.contains(plugin.id))
|
||||||
fragment._togglePluginsDisabled.remove(plugin.id);
|
fragment._togglePluginsDisabled.remove(plugin.id);
|
||||||
} else {
|
} else {
|
||||||
if (!fragment._togglePluginsDisabled.contains(plugin.id))
|
if (!fragment._togglePluginsDisabled.contains(plugin.id)) {
|
||||||
fragment._togglePluginsDisabled.add(plugin.id);
|
val enabledClients = StatePlatform.instance.getEnabledClients();
|
||||||
|
val availableAfterDisable = enabledClients.count { !fragment._togglePluginsDisabled.contains(it.id) && it.id != plugin.id };
|
||||||
|
if(availableAfterDisable > 0)
|
||||||
|
fragment._togglePluginsDisabled.add(plugin.id);
|
||||||
|
else {
|
||||||
|
UIDialogs.appToast("Home needs atleast 1 plugin active");
|
||||||
|
dontSwap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!dontSwap)
|
||||||
|
reloadForFilters();
|
||||||
|
else {
|
||||||
|
view.setToggle(!active);
|
||||||
}
|
}
|
||||||
reloadForFilters();
|
|
||||||
}).withTag("plugins")
|
}).withTag("plugins")
|
||||||
})
|
})
|
||||||
else listOf())
|
else listOf())
|
||||||
val buttons = (listOf<ToggleBar.Toggle?>(
|
val buttons = (listOf<ToggleBar.Toggle?>(
|
||||||
(if (_togglesConfig.contains("today"))
|
(if (_togglesConfig.contains("today"))
|
||||||
ToggleBar.Toggle("Today", fragment._toggleRecent) {
|
ToggleBar.Toggle("Today", fragment._toggleRecent) { view, active ->
|
||||||
fragment._toggleRecent = it; reloadForFilters()
|
fragment._toggleRecent = active; reloadForFilters()
|
||||||
}
|
}
|
||||||
.withTag("today") else null),
|
.withTag("today") else null),
|
||||||
(if (_togglesConfig.contains("watched"))
|
(if (_togglesConfig.contains("watched"))
|
||||||
ToggleBar.Toggle("Unwatched", fragment._toggleWatched) {
|
ToggleBar.Toggle("Unwatched", fragment._toggleWatched) { view, active ->
|
||||||
fragment._toggleWatched = it; reloadForFilters()
|
fragment._toggleWatched = active; reloadForFilters()
|
||||||
}
|
}
|
||||||
.withTag("watched") else null),
|
.withTag("watched") else null),
|
||||||
).filterNotNull() + buttonsPlugins)
|
).filterNotNull() + buttonsPlugins)
|
||||||
.sortedBy { _togglesConfig.indexOf(it.tag ?: "") } ?: listOf()
|
.sortedBy { _togglesConfig.indexOf(it.tag ?: "") } ?: listOf()
|
||||||
|
|
||||||
val buttonSettings = ToggleBar.Toggle("", R.drawable.ic_settings, true, {
|
val buttonSettings = ToggleBar.Toggle("", R.drawable.ic_settings, true, { view, active ->
|
||||||
showOrderOverlay(_overlayContainer,
|
showOrderOverlay(_overlayContainer,
|
||||||
"Visible home filters",
|
"Visible home filters",
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -18,6 +18,7 @@ import com.futo.platformplayer.constructs.TaskHandler
|
|||||||
import com.futo.platformplayer.engine.exceptions.PluginException
|
import com.futo.platformplayer.engine.exceptions.PluginException
|
||||||
import com.futo.platformplayer.exceptions.ChannelException
|
import com.futo.platformplayer.exceptions.ChannelException
|
||||||
import com.futo.platformplayer.exceptions.RateLimitException
|
import com.futo.platformplayer.exceptions.RateLimitException
|
||||||
|
import com.futo.platformplayer.fragment.mainactivity.main.SubscriptionsFeedFragment.SubscriptionsFeedView.FeedFilterSettings
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.models.SearchType
|
import com.futo.platformplayer.models.SearchType
|
||||||
import com.futo.platformplayer.models.SubscriptionGroup
|
import com.futo.platformplayer.models.SubscriptionGroup
|
||||||
@ -56,6 +57,9 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||||||
private var _group: SubscriptionGroup? = null;
|
private var _group: SubscriptionGroup? = null;
|
||||||
private var _cachedRecyclerData: FeedView.RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null;
|
private var _cachedRecyclerData: FeedView.RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null;
|
||||||
|
|
||||||
|
private val _filterLock = Object();
|
||||||
|
private val _filterSettings = FragmentedStorage.get<FeedFilterSettings>("subFeedFilter");
|
||||||
|
|
||||||
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
|
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
|
||||||
super.onShownWithView(parameter, isBack);
|
super.onShownWithView(parameter, isBack);
|
||||||
_view?.onShown();
|
_view?.onShown();
|
||||||
@ -184,8 +188,6 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||||||
return Json.encodeToString(this);
|
return Json.encodeToString(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private val _filterLock = Object();
|
|
||||||
private val _filterSettings = FragmentedStorage.get<FeedFilterSettings>("subFeedFilter");
|
|
||||||
|
|
||||||
private var _bypassRateLimit = false;
|
private var _bypassRateLimit = false;
|
||||||
private val _lastExceptions: List<Throwable>? = null;
|
private val _lastExceptions: List<Throwable>? = null;
|
||||||
@ -284,13 +286,18 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||||||
fragment.navigate<SubscriptionGroupFragment>(g);
|
fragment.navigate<SubscriptionGroupFragment>(g);
|
||||||
};
|
};
|
||||||
|
|
||||||
synchronized(_filterLock) {
|
synchronized(fragment._filterLock) {
|
||||||
_subscriptionBar?.setToggles(
|
_subscriptionBar?.setToggles(
|
||||||
SubscriptionBar.Toggle(context.getString(R.string.videos), _filterSettings.allowContentTypes.contains(ContentType.MEDIA)) { toggleFilterContentTypes(listOf(ContentType.MEDIA, ContentType.NESTED_VIDEO), it); },
|
SubscriptionBar.Toggle(context.getString(R.string.videos), fragment._filterSettings.allowContentTypes.contains(ContentType.MEDIA)) { view, active ->
|
||||||
SubscriptionBar.Toggle(context.getString(R.string.posts), _filterSettings.allowContentTypes.contains(ContentType.POST)) { toggleFilterContentType(ContentType.POST, it); },
|
toggleFilterContentTypes(listOf(ContentType.MEDIA, ContentType.NESTED_VIDEO), active); },
|
||||||
SubscriptionBar.Toggle(context.getString(R.string.live), _filterSettings.allowLive) { _filterSettings.allowLive = it; _filterSettings.save(); loadResults(false); },
|
SubscriptionBar.Toggle(context.getString(R.string.posts), fragment._filterSettings.allowContentTypes.contains(ContentType.POST)) { view, active ->
|
||||||
SubscriptionBar.Toggle(context.getString(R.string.planned), _filterSettings.allowPlanned) { _filterSettings.allowPlanned = it; _filterSettings.save(); loadResults(false); },
|
toggleFilterContentType(ContentType.POST, active); },
|
||||||
SubscriptionBar.Toggle(context.getString(R.string.watched), _filterSettings.allowWatched) { _filterSettings.allowWatched = it; _filterSettings.save(); loadResults(false); }
|
SubscriptionBar.Toggle(context.getString(R.string.live), fragment._filterSettings.allowLive) { view, active ->
|
||||||
|
fragment._filterSettings.allowLive = active; fragment._filterSettings.save(); loadResults(false); },
|
||||||
|
SubscriptionBar.Toggle(context.getString(R.string.planned), fragment._filterSettings.allowPlanned) { view, active ->
|
||||||
|
fragment._filterSettings.allowPlanned = active; fragment._filterSettings.save(); loadResults(false); },
|
||||||
|
SubscriptionBar.Toggle(context.getString(R.string.watched), fragment._filterSettings.allowWatched) { view, active ->
|
||||||
|
fragment._filterSettings.allowWatched = active; fragment._filterSettings.save(); loadResults(false); }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,13 +308,13 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||||||
toggleFilterContentType(contentType, isTrue);
|
toggleFilterContentType(contentType, isTrue);
|
||||||
}
|
}
|
||||||
private fun toggleFilterContentType(contentType: ContentType, isTrue: Boolean) {
|
private fun toggleFilterContentType(contentType: ContentType, isTrue: Boolean) {
|
||||||
synchronized(_filterLock) {
|
synchronized(fragment._filterLock) {
|
||||||
if(!isTrue) {
|
if(!isTrue) {
|
||||||
_filterSettings.allowContentTypes.remove(contentType);
|
fragment._filterSettings.allowContentTypes.remove(contentType);
|
||||||
} else if(!_filterSettings.allowContentTypes.contains(contentType)) {
|
} else if(!fragment._filterSettings.allowContentTypes.contains(contentType)) {
|
||||||
_filterSettings.allowContentTypes.add(contentType)
|
fragment._filterSettings.allowContentTypes.add(contentType)
|
||||||
}
|
}
|
||||||
_filterSettings.save();
|
fragment._filterSettings.save();
|
||||||
};
|
};
|
||||||
if(Settings.instance.subscriptions.fetchOnTabOpen) { //TODO: Do this different, temporary workaround
|
if(Settings.instance.subscriptions.fetchOnTabOpen) { //TODO: Do this different, temporary workaround
|
||||||
loadResults(false);
|
loadResults(false);
|
||||||
@ -320,9 +327,9 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||||||
val nowSoon = OffsetDateTime.now().plusMinutes(5);
|
val nowSoon = OffsetDateTime.now().plusMinutes(5);
|
||||||
val filterGroup = subGroup;
|
val filterGroup = subGroup;
|
||||||
return results.filter {
|
return results.filter {
|
||||||
val allowedContentType = _filterSettings.allowContentTypes.contains(if(it.contentType == ContentType.NESTED_VIDEO || it.contentType == ContentType.LOCKED) ContentType.MEDIA else it.contentType);
|
val allowedContentType = fragment._filterSettings.allowContentTypes.contains(if(it.contentType == ContentType.NESTED_VIDEO || it.contentType == ContentType.LOCKED) ContentType.MEDIA else it.contentType);
|
||||||
|
|
||||||
if(it is IPlatformVideo && it.duration > 0 && !_filterSettings.allowWatched && StateHistory.instance.isHistoryWatched(it.url, it.duration))
|
if(it is IPlatformVideo && it.duration > 0 && !fragment._filterSettings.allowWatched && StateHistory.instance.isHistoryWatched(it.url, it.duration))
|
||||||
return@filter false;
|
return@filter false;
|
||||||
|
|
||||||
//TODO: Check against a sub cache
|
//TODO: Check against a sub cache
|
||||||
@ -331,11 +338,11 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||||||
|
|
||||||
|
|
||||||
if(it.datetime?.isAfter(nowSoon) == true) {
|
if(it.datetime?.isAfter(nowSoon) == true) {
|
||||||
if(!_filterSettings.allowPlanned)
|
if(!fragment._filterSettings.allowPlanned)
|
||||||
return@filter false;
|
return@filter false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_filterSettings.allowLive) { //If allowLive, always show live
|
if(fragment._filterSettings.allowLive) { //If allowLive, always show live
|
||||||
if(it is IPlatformVideo && it.isLive)
|
if(it is IPlatformVideo && it.isLive)
|
||||||
return@filter true;
|
return@filter true;
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,14 @@ import com.futo.platformplayer.api.media.structures.EmptyPager
|
|||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
import com.futo.platformplayer.api.media.structures.MultiChronoContentPager
|
import com.futo.platformplayer.api.media.structures.MultiChronoContentPager
|
||||||
import com.futo.platformplayer.api.media.structures.PlatformContentPager
|
import com.futo.platformplayer.api.media.structures.PlatformContentPager
|
||||||
|
import com.futo.platformplayer.debug.Stopwatch
|
||||||
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.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
|
||||||
|
import com.futo.platformplayer.getNowDiffMiliseconds
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.models.Subscription
|
import com.futo.platformplayer.models.Subscription
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
@ -32,6 +34,8 @@ import com.futo.platformplayer.subsexchange.ChannelRequest
|
|||||||
import com.futo.platformplayer.subsexchange.ChannelResolve
|
import com.futo.platformplayer.subsexchange.ChannelResolve
|
||||||
import com.futo.platformplayer.subsexchange.ExchangeContract
|
import com.futo.platformplayer.subsexchange.ExchangeContract
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import java.util.concurrent.ForkJoinPool
|
import java.util.concurrent.ForkJoinPool
|
||||||
@ -149,42 +153,56 @@ abstract class SubscriptionsTaskFetchAlgorithm(
|
|||||||
|
|
||||||
//Resolve Subscription Exchange
|
//Resolve Subscription Exchange
|
||||||
if(contract != null) {
|
if(contract != null) {
|
||||||
try {
|
fun resolve() {
|
||||||
resolveTime = measureTimeMillis {
|
try {
|
||||||
val resolves = taskResults.filter { it.pager != null && (it.task.type == ResultCapabilities.TYPE_MIXED || it.task.type == ResultCapabilities.TYPE_VIDEOS) && contract!!.required.contains(it.task.url) }.map {
|
resolveTime = measureTimeMillis {
|
||||||
ChannelResolve(
|
val resolves = taskResults.filter { it.pager != null && (it.task.type == ResultCapabilities.TYPE_MIXED || it.task.type == ResultCapabilities.TYPE_VIDEOS) && contract!!.required.contains(it.task.url) }.map {
|
||||||
it.task.url,
|
ChannelResolve(
|
||||||
it.pager!!.getResults().filter { it is IPlatformVideo }.map { SerializedPlatformVideo.fromVideo(it as IPlatformVideo) }
|
it.task.url,
|
||||||
)
|
it.pager!!.getResults().filter { it is IPlatformVideo }.map { SerializedPlatformVideo.fromVideo(it as IPlatformVideo) }
|
||||||
}.toTypedArray()
|
)
|
||||||
val resolve = subsExchangeClient?.resolveContract(
|
}.toTypedArray()
|
||||||
contract!!,
|
|
||||||
*resolves
|
val resolveRequestStart = OffsetDateTime.now();
|
||||||
);
|
|
||||||
if (resolve != null) {
|
val resolve = subsExchangeClient?.resolveContract(
|
||||||
resolveCount = resolves.size;
|
contract!!,
|
||||||
UIDialogs.appToast("SubsExchange (Res: ${resolves.size}, Prov: ${resolve.size}")
|
*resolves
|
||||||
for(result in resolve){
|
);
|
||||||
val task = providedTasks?.find { it.url == result.channelUrl };
|
|
||||||
if(task != null) {
|
Logger.i(TAG, "Subscription Exchange contract resolved request in ${resolveRequestStart.getNowDiffMiliseconds()}ms");
|
||||||
taskResults.add(SubscriptionTaskResult(task, PlatformContentPager(result.content, result.content.size), null));
|
|
||||||
providedTasks?.remove(task);
|
if (resolve != null) {
|
||||||
|
resolveCount = resolves.size;
|
||||||
|
UIDialogs.appToast("SubsExchange (Res: ${resolves.size}, Prov: ${resolve.size}")
|
||||||
|
for(result in resolve){
|
||||||
|
val task = providedTasks?.find { it.url == result.channelUrl };
|
||||||
|
if(task != null) {
|
||||||
|
taskResults.add(SubscriptionTaskResult(task, PlatformContentPager(result.content, result.content.size), null));
|
||||||
|
providedTasks?.remove(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (providedTasks != null) {
|
||||||
|
for(task in providedTasks!!) {
|
||||||
|
taskResults.add(SubscriptionTaskResult(task, null, IllegalStateException("No data received from exchange")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (providedTasks != null) {
|
Logger.i(TAG, "Subscription Exchange contract resolved in ${resolveTime}ms");
|
||||||
for(task in providedTasks!!) {
|
|
||||||
taskResults.add(SubscriptionTaskResult(task, null, IllegalStateException("No data received from exchange")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Logger.i(TAG, "Subscription Exchange contract resolved in ${resolveTime}ms");
|
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(ex: Throwable) {
|
||||||
|
//TODO: fetch remainder after all?
|
||||||
|
Logger.e(TAG, "Failed to resolve Subscription Exchange contract due to: " + ex.message, ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(ex: Throwable) {
|
if(providedTasks?.size ?: 0 == 0)
|
||||||
//TODO: fetch remainder after all?
|
scope.launch(Dispatchers.IO) {
|
||||||
Logger.e(TAG, "Failed to resolve Subscription Exchange contract due to: " + ex.message, ex);
|
resolve();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import com.futo.platformplayer.api.media.Serializer
|
import com.futo.platformplayer.api.media.Serializer
|
||||||
|
import com.futo.platformplayer.getNowDiffMiliseconds
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.subscription.SubscriptionFetchAlgorithm.Companion.TAG
|
||||||
import com.futo.platformplayer.subsexchange.ChannelRequest
|
import com.futo.platformplayer.subsexchange.ChannelRequest
|
||||||
import com.futo.platformplayer.subsexchange.ChannelResolve
|
import com.futo.platformplayer.subsexchange.ChannelResolve
|
||||||
import com.futo.platformplayer.subsexchange.ChannelResult
|
import com.futo.platformplayer.subsexchange.ChannelResult
|
||||||
import com.futo.platformplayer.subsexchange.ExchangeContract
|
import com.futo.platformplayer.subsexchange.ExchangeContract
|
||||||
import com.futo.platformplayer.subsexchange.ExchangeContractResolve
|
import com.futo.platformplayer.subsexchange.ExchangeContractResolve
|
||||||
|
import com.futo.platformplayer.toGzip
|
||||||
|
import com.futo.platformplayer.toHumanBytesSize
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -26,6 +30,7 @@ import java.nio.charset.StandardCharsets
|
|||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.spec.PKCS8EncodedKeySpec
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
import java.security.spec.RSAPublicKeySpec
|
import java.security.spec.RSAPublicKeySpec
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
|
||||||
class SubsExchangeClient(private val server: String, private val privateKey: String, private val contractTimeout: Int = 1000) {
|
class SubsExchangeClient(private val server: String, private val privateKey: String, private val contractTimeout: Int = 1000) {
|
||||||
@ -40,24 +45,27 @@ class SubsExchangeClient(private val server: String, private val privateKey: Str
|
|||||||
|
|
||||||
// Endpoint: Contract
|
// Endpoint: Contract
|
||||||
fun requestContract(vararg channels: ChannelRequest): ExchangeContract {
|
fun requestContract(vararg channels: ChannelRequest): ExchangeContract {
|
||||||
val data = post("/api/Channel/Contract", Json.encodeToString(channels), "application/json", contractTimeout)
|
val data = post("/api/Channel/Contract", Json.encodeToString(channels).toByteArray(Charsets.UTF_8), "application/json", contractTimeout)
|
||||||
return Json.decodeFromString(data)
|
return Json.decodeFromString(data)
|
||||||
}
|
}
|
||||||
suspend fun requestContractAsync(vararg channels: ChannelRequest): ExchangeContract {
|
suspend fun requestContractAsync(vararg channels: ChannelRequest): ExchangeContract {
|
||||||
val data = postAsync("/api/Channel/Contract", Json.encodeToString(channels), "application/json")
|
val data = postAsync("/api/Channel/Contract", Json.encodeToString(channels).toByteArray(Charsets.UTF_8), "application/json")
|
||||||
return Json.decodeFromString(data)
|
return Json.decodeFromString(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endpoint: Resolve
|
// Endpoint: Resolve
|
||||||
fun resolveContract(contract: ExchangeContract, vararg resolves: ChannelResolve): Array<ChannelResult> {
|
fun resolveContract(contract: ExchangeContract, vararg resolves: ChannelResolve): Array<ChannelResult> {
|
||||||
val contractResolve = convertResolves(*resolves)
|
val contractResolve = convertResolves(*resolves)
|
||||||
val result = post("/api/Channel/Resolve?contractId=${contract.id}", Serializer.json.encodeToString(contractResolve), "application/json")
|
val contractResolveJson = Serializer.json.encodeToString(contractResolve);
|
||||||
Logger.v("SubsExchangeClient", "Resolve:" + result);
|
val contractResolveTimeStart = OffsetDateTime.now();
|
||||||
|
val result = post("/api/Channel/Resolve?contractId=${contract.id}", contractResolveJson.toByteArray(Charsets.UTF_8), "application/json", 0, true)
|
||||||
|
val contractResolveTime = contractResolveTimeStart.getNowDiffMiliseconds();
|
||||||
|
Logger.v("SubsExchangeClient", "Subscription Exchange Resolve Request [${contractResolveTime}ms]:" + result);
|
||||||
return Serializer.json.decodeFromString(result)
|
return Serializer.json.decodeFromString(result)
|
||||||
}
|
}
|
||||||
suspend fun resolveContractAsync(contract: ExchangeContract, vararg resolves: ChannelResolve): Array<ChannelResult> {
|
suspend fun resolveContractAsync(contract: ExchangeContract, vararg resolves: ChannelResolve): Array<ChannelResult> {
|
||||||
val contractResolve = convertResolves(*resolves)
|
val contractResolve = convertResolves(*resolves)
|
||||||
val result = postAsync("/api/Channel/Resolve?contractId=${contract.id}", Serializer.json.encodeToString(contractResolve), "application/json")
|
val result = postAsync("/api/Channel/Resolve?contractId=${contract.id}", Serializer.json.encodeToString(contractResolve).toByteArray(Charsets.UTF_8), "application/json", true)
|
||||||
return Serializer.json.decodeFromString(result)
|
return Serializer.json.decodeFromString(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +82,7 @@ class SubsExchangeClient(private val server: String, private val privateKey: Str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IO methods
|
// IO methods
|
||||||
private fun post(query: String, body: String, contentType: String, timeout: Int = 0): String {
|
private fun post(query: String, body: ByteArray, contentType: String, timeout: Int = 0, gzip: Boolean = false): String {
|
||||||
val url = URL("${server.trim('/')}$query")
|
val url = URL("${server.trim('/')}$query")
|
||||||
with(url.openConnection() as HttpURLConnection) {
|
with(url.openConnection() as HttpURLConnection) {
|
||||||
if(timeout > 0)
|
if(timeout > 0)
|
||||||
@ -82,7 +90,16 @@ class SubsExchangeClient(private val server: String, private val privateKey: Str
|
|||||||
requestMethod = "POST"
|
requestMethod = "POST"
|
||||||
setRequestProperty("Content-Type", contentType)
|
setRequestProperty("Content-Type", contentType)
|
||||||
doOutput = true
|
doOutput = true
|
||||||
OutputStreamWriter(outputStream, StandardCharsets.UTF_8).use { it.write(body); it.flush() }
|
|
||||||
|
|
||||||
|
if(gzip) {
|
||||||
|
val gzipData = body.toGzip();
|
||||||
|
setRequestProperty("Content-Encoding", "gzip");
|
||||||
|
outputStream.write(gzipData);
|
||||||
|
Logger.i("SubsExchangeClient", "SubsExchange using gzip (${body.size.toHumanBytesSize()} => ${gzipData.size.toHumanBytesSize()}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
outputStream.write(body);
|
||||||
|
|
||||||
val status = responseCode;
|
val status = responseCode;
|
||||||
Logger.i("SubsExchangeClient", "POST [${url}]: ${status}");
|
Logger.i("SubsExchangeClient", "POST [${url}]: ${status}");
|
||||||
@ -105,9 +122,9 @@ class SubsExchangeClient(private val server: String, private val privateKey: Str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private suspend fun postAsync(query: String, body: String, contentType: String): String {
|
private suspend fun postAsync(query: String, body: ByteArray, contentType: String, gzip: Boolean = false): String {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
post(query, body, contentType)
|
post(query, body, contentType, 0, gzip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ class ToggleBar : LinearLayout {
|
|||||||
this.setInfo(button.iconVariable, button.name, button.isActive, button.isButton);
|
this.setInfo(button.iconVariable, button.name, button.isActive, button.isButton);
|
||||||
else
|
else
|
||||||
this.setInfo(button.name, button.isActive, button.isButton);
|
this.setInfo(button.name, button.isActive, button.isButton);
|
||||||
this.onClick.subscribe { button.action(it); };
|
this.onClick.subscribe({ view, enabled -> button.action(view, enabled); });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,27 +62,27 @@ class ToggleBar : LinearLayout {
|
|||||||
val name: String;
|
val name: String;
|
||||||
val icon: Int;
|
val icon: Int;
|
||||||
val iconVariable: ImageVariable?;
|
val iconVariable: ImageVariable?;
|
||||||
val action: (Boolean)->Unit;
|
val action: (ToggleTagView, Boolean)->Unit;
|
||||||
val isActive: Boolean;
|
val isActive: Boolean;
|
||||||
var isButton: Boolean = false
|
var isButton: Boolean = false
|
||||||
private set;
|
private set;
|
||||||
var tag: String? = null;
|
var tag: String? = null;
|
||||||
|
|
||||||
constructor(name: String, icon: ImageVariable?, isActive: Boolean = false, action: (Boolean)->Unit) {
|
constructor(name: String, icon: ImageVariable?, isActive: Boolean = false, action: (ToggleTagView, Boolean)->Unit) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.icon = 0;
|
this.icon = 0;
|
||||||
this.iconVariable = icon;
|
this.iconVariable = icon;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.isActive = isActive;
|
this.isActive = isActive;
|
||||||
}
|
}
|
||||||
constructor(name: String, icon: Int, isActive: Boolean = false, action: (Boolean)->Unit) {
|
constructor(name: String, icon: Int, isActive: Boolean = false, action: (ToggleTagView, Boolean)->Unit) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
this.iconVariable = null;
|
this.iconVariable = null;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.isActive = isActive;
|
this.isActive = isActive;
|
||||||
}
|
}
|
||||||
constructor(name: String, isActive: Boolean = false, action: (Boolean)->Unit) {
|
constructor(name: String, isActive: Boolean = false, action: (ToggleTagView, Boolean)->Unit) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.icon = 0;
|
this.icon = 0;
|
||||||
this.iconVariable = null;
|
this.iconVariable = null;
|
||||||
|
@ -12,8 +12,10 @@ import android.widget.TextView
|
|||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
|
import com.futo.platformplayer.constructs.Event2
|
||||||
import com.futo.platformplayer.images.GlideHelper
|
import com.futo.platformplayer.images.GlideHelper
|
||||||
import com.futo.platformplayer.models.ImageVariable
|
import com.futo.platformplayer.models.ImageVariable
|
||||||
|
import com.futo.platformplayer.views.ToggleBar
|
||||||
|
|
||||||
class ToggleTagView : LinearLayout {
|
class ToggleTagView : LinearLayout {
|
||||||
private val _root: FrameLayout;
|
private val _root: FrameLayout;
|
||||||
@ -26,7 +28,7 @@ class ToggleTagView : LinearLayout {
|
|||||||
var isButton: Boolean = false
|
var isButton: Boolean = false
|
||||||
private set;
|
private set;
|
||||||
|
|
||||||
var onClick = Event1<Boolean>();
|
var onClick = Event2<ToggleTagView, Boolean>();
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
||||||
LayoutInflater.from(context).inflate(R.layout.view_toggle_tag, this, true);
|
LayoutInflater.from(context).inflate(R.layout.view_toggle_tag, this, true);
|
||||||
@ -36,7 +38,7 @@ class ToggleTagView : LinearLayout {
|
|||||||
_root.setOnClickListener {
|
_root.setOnClickListener {
|
||||||
if(!isButton)
|
if(!isButton)
|
||||||
setToggle(!isActive);
|
setToggle(!isActive);
|
||||||
onClick.emit(isActive);
|
onClick.emit(this, isActive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +54,24 @@ class ToggleTagView : LinearLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setInfo(toggle: ToggleBar.Toggle){
|
||||||
|
_text = toggle.name;
|
||||||
|
_textTag.text = toggle.name;
|
||||||
|
setToggle(toggle.isActive);
|
||||||
|
if(toggle.iconVariable != null) {
|
||||||
|
toggle.iconVariable.setImageView(_image, R.drawable.ic_error_pred);
|
||||||
|
_image.visibility = View.GONE;
|
||||||
|
}
|
||||||
|
else if(toggle.icon > 0) {
|
||||||
|
_image.setImageResource(toggle.icon);
|
||||||
|
_image.visibility = View.GONE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_image.visibility = View.VISIBLE;
|
||||||
|
_textTag.visibility = if(!toggle.name.isNullOrEmpty()) View.VISIBLE else View.GONE;
|
||||||
|
this.isButton = isButton;
|
||||||
|
}
|
||||||
|
|
||||||
fun setInfo(imageResource: Int, text: String, isActive: Boolean, isButton: Boolean = false) {
|
fun setInfo(imageResource: Int, text: String, isActive: Boolean, isButton: Boolean = false) {
|
||||||
_text = text;
|
_text = text;
|
||||||
_textTag.text = text;
|
_textTag.text = text;
|
||||||
|
@ -158,7 +158,7 @@ class SubscriptionBar : LinearLayout {
|
|||||||
for(button in buttons) {
|
for(button in buttons) {
|
||||||
_tagsContainer.addView(ToggleTagView(context).apply {
|
_tagsContainer.addView(ToggleTagView(context).apply {
|
||||||
this.setInfo(button.name, button.isActive);
|
this.setInfo(button.name, button.isActive);
|
||||||
this.onClick.subscribe { button.action(it); };
|
this.onClick.subscribe({ view, value -> button.action(view, value); });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,16 +166,16 @@ class SubscriptionBar : LinearLayout {
|
|||||||
class Toggle {
|
class Toggle {
|
||||||
val name: String;
|
val name: String;
|
||||||
val icon: Int;
|
val icon: Int;
|
||||||
val action: (Boolean)->Unit;
|
val action: (ToggleTagView, Boolean)->Unit;
|
||||||
val isActive: Boolean;
|
val isActive: Boolean;
|
||||||
|
|
||||||
constructor(name: String, icon: Int, isActive: Boolean = false, action: (Boolean)->Unit) {
|
constructor(name: String, icon: Int, isActive: Boolean = false, action: (ToggleTagView, Boolean)->Unit) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.isActive = isActive;
|
this.isActive = isActive;
|
||||||
}
|
}
|
||||||
constructor(name: String, isActive: Boolean = false, action: (Boolean)->Unit) {
|
constructor(name: String, isActive: Boolean = false, action: (ToggleTagView, Boolean)->Unit) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.icon = 0;
|
this.icon = 0;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user