mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-09-03 16:17:03 +00:00
getUserHistory support
This commit is contained in:
parent
0af4bad906
commit
90dca2537a
18 changed files with 210 additions and 14 deletions
|
@ -251,6 +251,9 @@ class PlatformVideo extends PlatformContent {
|
||||||
this.duration = obj.duration ?? -1; //Long
|
this.duration = obj.duration ?? -1; //Long
|
||||||
this.viewCount = obj.viewCount ?? -1; //Long
|
this.viewCount = obj.viewCount ?? -1; //Long
|
||||||
|
|
||||||
|
this.playbackTime = obj.playbackTime ?? -1;
|
||||||
|
this.playbackDate = obj.playbackDate ?? undefined;
|
||||||
|
|
||||||
this.isLive = obj.isLive ?? false; //Boolean
|
this.isLive = obj.isLive ?? false; //Boolean
|
||||||
this.isShort = !!obj.isShort ?? false;
|
this.isShort = !!obj.isShort ?? false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,6 +182,10 @@ interface IPlatformClient {
|
||||||
* Retrieves the subscriptions of the currently logged in user
|
* Retrieves the subscriptions of the currently logged in user
|
||||||
*/
|
*/
|
||||||
fun getUserSubscriptions(): Array<String>;
|
fun getUserSubscriptions(): Array<String>;
|
||||||
|
/**
|
||||||
|
* Retrieves the history of the currently logged in user
|
||||||
|
*/
|
||||||
|
fun getUserHistory(): IPager<IPlatformContent>;
|
||||||
|
|
||||||
|
|
||||||
fun isClaimTypeSupported(claimType: Int): Boolean;
|
fun isClaimTypeSupported(claimType: Int): Boolean;
|
||||||
|
|
|
@ -20,7 +20,8 @@ data class PlatformClientCapabilities(
|
||||||
val hasGetContentChapters: Boolean = false,
|
val hasGetContentChapters: Boolean = false,
|
||||||
val hasPeekChannelContents: Boolean = false,
|
val hasPeekChannelContents: Boolean = false,
|
||||||
val hasGetChannelPlaylists: Boolean = false,
|
val hasGetChannelPlaylists: Boolean = false,
|
||||||
val hasGetContentRecommendations: Boolean = false
|
val hasGetContentRecommendations: Boolean = false,
|
||||||
|
val hasGetUserHistory: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.models.video
|
||||||
|
|
||||||
import com.futo.platformplayer.api.media.models.Thumbnails
|
import com.futo.platformplayer.api.media.models.Thumbnails
|
||||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A search result representing a video (overview data)
|
* A search result representing a video (overview data)
|
||||||
|
@ -12,6 +13,9 @@ interface IPlatformVideo : IPlatformContent {
|
||||||
val duration: Long;
|
val duration: Long;
|
||||||
val viewCount: Long;
|
val viewCount: Long;
|
||||||
|
|
||||||
|
val playbackTime: Long;
|
||||||
|
val playbackDate: OffsetDateTime?;
|
||||||
|
|
||||||
val isLive : Boolean;
|
val isLive : Boolean;
|
||||||
|
|
||||||
val isShort: Boolean;
|
val isShort: Boolean;
|
||||||
|
|
|
@ -6,8 +6,6 @@ import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||||
import com.futo.platformplayer.api.media.models.Thumbnails
|
import com.futo.platformplayer.api.media.models.Thumbnails
|
||||||
import com.futo.platformplayer.api.media.models.contents.ContentType
|
import com.futo.platformplayer.api.media.models.contents.ContentType
|
||||||
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
||||||
import com.futo.polycentric.core.combineHashCodes
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonNames
|
import kotlinx.serialization.json.JsonNames
|
||||||
|
@ -33,6 +31,10 @@ open class SerializedPlatformVideo(
|
||||||
|
|
||||||
override val isLive: Boolean = false;
|
override val isLive: Boolean = false;
|
||||||
|
|
||||||
|
override var playbackTime: Long = -1;
|
||||||
|
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
||||||
|
override var playbackDate: OffsetDateTime? = null;
|
||||||
|
|
||||||
override fun toJson() : String {
|
override fun toJson() : String {
|
||||||
return Json.encodeToString(this);
|
return Json.encodeToString(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import com.futo.platformplayer.api.media.models.ratings.IRating
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.*
|
import com.futo.platformplayer.api.media.models.streams.sources.*
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
@ -43,6 +42,10 @@ open class SerializedPlatformVideoDetails(
|
||||||
) : IPlatformVideo, IPlatformVideoDetails {
|
) : IPlatformVideo, IPlatformVideoDetails {
|
||||||
final override val contentType: ContentType get() = ContentType.MEDIA;
|
final override val contentType: ContentType get() = ContentType.MEDIA;
|
||||||
|
|
||||||
|
override var playbackTime: Long = -1;
|
||||||
|
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
||||||
|
override var playbackDate: OffsetDateTime? = null;
|
||||||
|
|
||||||
override val isLive: Boolean get() = false;
|
override val isLive: Boolean get() = false;
|
||||||
|
|
||||||
override val dash: IDashManifestSource? get() = null;
|
override val dash: IDashManifestSource? get() = null;
|
||||||
|
|
|
@ -272,7 +272,8 @@ open class JSClient : IPlatformClient {
|
||||||
hasGetContentChapters = plugin.executeBoolean("!!source.getContentChapters") ?: false,
|
hasGetContentChapters = plugin.executeBoolean("!!source.getContentChapters") ?: false,
|
||||||
hasPeekChannelContents = plugin.executeBoolean("!!source.peekChannelContents") ?: false,
|
hasPeekChannelContents = plugin.executeBoolean("!!source.peekChannelContents") ?: false,
|
||||||
hasGetChannelPlaylists = plugin.executeBoolean("!!source.getChannelPlaylists") ?: false,
|
hasGetChannelPlaylists = plugin.executeBoolean("!!source.getChannelPlaylists") ?: false,
|
||||||
hasGetContentRecommendations = plugin.executeBoolean("!!source.getContentRecommendations") ?: false
|
hasGetContentRecommendations = plugin.executeBoolean("!!source.getContentRecommendations") ?: false,
|
||||||
|
hasGetUserHistory = plugin.executeBoolean("!!source.getUserHistory") ?: false
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -712,6 +713,13 @@ open class JSClient : IPlatformClient {
|
||||||
.toTypedArray();
|
.toTypedArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JSOptional
|
||||||
|
@JSDocs(23, "source.getUserHistory()", "Gets the history of the current user")
|
||||||
|
override fun getUserHistory(): IPager<IPlatformContent> {
|
||||||
|
ensureEnabled();
|
||||||
|
return JSContentPager(config, this, plugin.executeTyped("source.getUserHistory()"));
|
||||||
|
}
|
||||||
|
|
||||||
fun validate() {
|
fun validate() {
|
||||||
try {
|
try {
|
||||||
plugin.start();
|
plugin.start();
|
||||||
|
|
|
@ -5,10 +5,16 @@ import com.futo.platformplayer.constructs.Event0
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.states.AnnouncementType
|
import com.futo.platformplayer.states.AnnouncementType
|
||||||
import com.futo.platformplayer.states.StateAnnouncement
|
import com.futo.platformplayer.states.StateAnnouncement
|
||||||
|
import com.futo.platformplayer.states.StateApp
|
||||||
|
import com.futo.platformplayer.states.StateHistory
|
||||||
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
import com.futo.platformplayer.views.fields.DropdownFieldOptions
|
import com.futo.platformplayer.views.fields.DropdownFieldOptions
|
||||||
import com.futo.platformplayer.views.fields.FieldForm
|
import com.futo.platformplayer.views.fields.FieldForm
|
||||||
import com.futo.platformplayer.views.fields.FormField
|
import com.futo.platformplayer.views.fields.FormField
|
||||||
|
import com.futo.platformplayer.views.fields.FormFieldButton
|
||||||
import com.futo.platformplayer.views.fields.FormFieldWarning
|
import com.futo.platformplayer.views.fields.FormFieldWarning
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -110,7 +116,28 @@ class SourcePluginDescriptor {
|
||||||
var enableShorts: Boolean? = null;
|
var enableShorts: Boolean? = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FormField(R.string.ratelimit, "group", R.string.ratelimit_description, 3)
|
@FormField(R.string.sync, "group", R.string.sync_desc, 3)
|
||||||
|
var sync = Sync();
|
||||||
|
@Serializable
|
||||||
|
class Sync {
|
||||||
|
@FormField(R.string.sync_history, FieldForm.TOGGLE, R.string.sync_history_desc, 1)
|
||||||
|
var enableHistorySync: Boolean? = null;
|
||||||
|
|
||||||
|
@FormField(R.string.sync_history, FieldForm.BUTTON, R.string.sync_history_desc, 2)
|
||||||
|
@FormFieldButton()
|
||||||
|
fun syncHistoryNow() {
|
||||||
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
|
val clients = StatePlatform.instance.getEnabledClients();
|
||||||
|
for (client in clients) {
|
||||||
|
if (client is JSClient) {//) && client.descriptor.appSettings.sync.enableHistorySync == true) {
|
||||||
|
StateHistory.instance.syncRemoteHistory(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FormField(R.string.ratelimit, "group", R.string.ratelimit_description, 4)
|
||||||
var rateLimit = RateLimit();
|
var rateLimit = RateLimit();
|
||||||
@Serializable
|
@Serializable
|
||||||
class RateLimit {
|
class RateLimit {
|
||||||
|
|
|
@ -8,6 +8,10 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||||
import com.futo.platformplayer.getOrDefault
|
import com.futo.platformplayer.getOrDefault
|
||||||
import com.futo.platformplayer.getOrThrow
|
import com.futo.platformplayer.getOrThrow
|
||||||
|
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
open class JSVideo : JSContent, IPlatformVideo, IPluginSourced {
|
open class JSVideo : JSContent, IPlatformVideo, IPluginSourced {
|
||||||
final override val contentType: ContentType get() = ContentType.MEDIA;
|
final override val contentType: ContentType get() = ContentType.MEDIA;
|
||||||
|
@ -17,6 +21,10 @@ open class JSVideo : JSContent, IPlatformVideo, IPluginSourced {
|
||||||
final override val duration: Long;
|
final override val duration: Long;
|
||||||
final override val viewCount: Long;
|
final override val viewCount: Long;
|
||||||
|
|
||||||
|
override var playbackTime: Long = -1;
|
||||||
|
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
||||||
|
override var playbackDate: OffsetDateTime? = null;
|
||||||
|
|
||||||
final override val isLive: Boolean;
|
final override val isLive: Boolean;
|
||||||
final override val isShort: Boolean;
|
final override val isShort: Boolean;
|
||||||
|
|
||||||
|
@ -29,5 +37,11 @@ open class JSVideo : JSContent, IPlatformVideo, IPluginSourced {
|
||||||
viewCount = _content.getOrThrow(config, "viewCount", contextName);
|
viewCount = _content.getOrThrow(config, "viewCount", contextName);
|
||||||
isLive = _content.getOrThrow(config, "isLive", contextName);
|
isLive = _content.getOrThrow(config, "isLive", contextName);
|
||||||
isShort = _content.getOrDefault(config, "isShort", contextName, false) ?: false;
|
isShort = _content.getOrDefault(config, "isShort", contextName, false) ?: false;
|
||||||
|
playbackTime = _content.getOrDefault<Long>(config, "playbackTime", contextName, -1)?.toLong() ?: -1;
|
||||||
|
val playbackDateInt = _content.getOrDefault<Int>(config, "playbackDate", contextName, null)?.toLong();
|
||||||
|
if(playbackDateInt == null || playbackDateInt == 0.toLong())
|
||||||
|
playbackDate = null;
|
||||||
|
else
|
||||||
|
playbackDate = OffsetDateTime.of(LocalDateTime.ofEpochSecond(playbackDateInt, 0, ZoneOffset.UTC), ZoneOffset.UTC);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,6 @@ import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker
|
||||||
import com.futo.platformplayer.api.media.models.ratings.IRating
|
import com.futo.platformplayer.api.media.models.ratings.IRating
|
||||||
import com.futo.platformplayer.api.media.models.ratings.RatingLikes
|
import com.futo.platformplayer.api.media.models.ratings.RatingLikes
|
||||||
import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
|
import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
|
||||||
import com.futo.platformplayer.api.media.models.streams.DownloadedVideoMuxedSourceDescriptor
|
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
|
||||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||||
|
@ -19,7 +18,7 @@ import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||||
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoFileSource
|
import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoFileSource
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
import com.futo.platformplayer.downloads.VideoLocal
|
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
@ -53,6 +52,10 @@ class LocalVideoDetails: IPlatformVideoDetails {
|
||||||
override val isLive: Boolean = false;
|
override val isLive: Boolean = false;
|
||||||
override val isShort: Boolean = false;
|
override val isShort: Boolean = false;
|
||||||
|
|
||||||
|
override var playbackTime: Long = -1;
|
||||||
|
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
||||||
|
override var playbackDate: OffsetDateTime? = null;
|
||||||
|
|
||||||
constructor(file: File) {
|
constructor(file: File) {
|
||||||
id = PlatformID("Local", file.path, "LOCAL")
|
id = PlatformID("Local", file.path, "LOCAL")
|
||||||
name = file.name;
|
name = file.name;
|
||||||
|
|
|
@ -47,10 +47,10 @@ class DeveloperEndpoints(private val context: Context) {
|
||||||
private val testPluginOrThrow: V8Plugin get() = _testPlugin ?: throw IllegalStateException("Attempted to use test plugin without plugin");
|
private val testPluginOrThrow: V8Plugin get() = _testPlugin ?: throw IllegalStateException("Attempted to use test plugin without plugin");
|
||||||
private val _testPluginVariables: HashMap<String, V8RemoteObject> = hashMapOf();
|
private val _testPluginVariables: HashMap<String, V8RemoteObject> = hashMapOf();
|
||||||
|
|
||||||
private inline fun <reified T> createRemoteObjectArray(objs: Iterable<T>): List<V8RemoteObject> {
|
private inline fun <reified T> createRemoteObjectArray(objs: Iterable<T>): List<V8RemoteObject?> {
|
||||||
val remotes = mutableListOf<V8RemoteObject>();
|
val remotes = mutableListOf<V8RemoteObject?>();
|
||||||
for(obj in objs)
|
for(obj in objs)
|
||||||
remotes.add(createRemoteObject(obj)!!);
|
remotes.add(createRemoteObject(obj));
|
||||||
return remotes;
|
return remotes;
|
||||||
}
|
}
|
||||||
private inline fun <reified T> createRemoteObject(obj: T): V8RemoteObject? {
|
private inline fun <reified T> createRemoteObject(obj: T): V8RemoteObject? {
|
||||||
|
|
|
@ -73,6 +73,10 @@ class VideoLocal: IPlatformVideoDetails, IStoreItem {
|
||||||
|
|
||||||
override val isShort: Boolean get() = videoSerialized.isShort;
|
override val isShort: Boolean get() = videoSerialized.isShort;
|
||||||
|
|
||||||
|
override var playbackTime: Long = -1;
|
||||||
|
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
||||||
|
override var playbackDate: OffsetDateTime? = null;
|
||||||
|
|
||||||
//TODO: Offline subtitles
|
//TODO: Offline subtitles
|
||||||
override val subtitles: List<ISubtitleSource> = listOf();
|
override val subtitles: List<ISubtitleSource> = listOf();
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ class V8RemoteObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun List<V8RemoteObject>.serialize() : String {
|
fun List<V8RemoteObject?>.serialize() : String {
|
||||||
return _gson.toJson(this);
|
return _gson.toJson(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||||
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
|
||||||
import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment
|
import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment
|
||||||
|
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
||||||
import com.futo.platformplayer.views.pills.WidePillButton
|
import com.futo.platformplayer.views.pills.WidePillButton
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
@ -152,6 +153,9 @@ class TutorialFragment : MainFragment() {
|
||||||
override val viewCount: Long = -1
|
override val viewCount: Long = -1
|
||||||
override val video: IVideoSourceDescriptor = TutorialVideoSourceDescriptor(videoUrl, duration, width, height)
|
override val video: IVideoSourceDescriptor = TutorialVideoSourceDescriptor(videoUrl, duration, width, height)
|
||||||
override val isShort: Boolean = false;
|
override val isShort: Boolean = false;
|
||||||
|
override var playbackTime: Long = -1;
|
||||||
|
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
||||||
|
override var playbackDate: OffsetDateTime? = null;
|
||||||
override fun getComments(client: IPlatformClient): IPager<IPlatformComment> {
|
override fun getComments(client: IPlatformClient): IPager<IPlatformComment> {
|
||||||
return EmptyPager()
|
return EmptyPager()
|
||||||
}
|
}
|
||||||
|
|
|
@ -636,6 +636,20 @@ class StateApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
|
val enabledPlugins = StatePlatform.instance.getEnabledClients();
|
||||||
|
for(plugin in enabledPlugins) {
|
||||||
|
try {
|
||||||
|
if(plugin is JSClient) {
|
||||||
|
if(plugin.descriptor.appSettings.sync.enableHistorySync == true)
|
||||||
|
StateHistory.instance.syncRemoteHistory(plugin);
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
Logger.e(TAG, "Failed to update remote history for ${plugin.name}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mainAppStartedWithExternalFiles(context: Context) {
|
fun mainAppStartedWithExternalFiles(context: Context) {
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
package com.futo.platformplayer.states
|
package com.futo.platformplayer.states
|
||||||
|
|
||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
|
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||||
|
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
import com.futo.platformplayer.constructs.Event2
|
import com.futo.platformplayer.constructs.Event2
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.models.HistoryVideo
|
import com.futo.platformplayer.models.HistoryVideo
|
||||||
import com.futo.platformplayer.models.ImportCache
|
import com.futo.platformplayer.models.ImportCache
|
||||||
import com.futo.platformplayer.states.StatePlaylists.Companion
|
import com.futo.platformplayer.states.StateApp.Companion
|
||||||
import com.futo.platformplayer.stores.FragmentedStorage
|
import com.futo.platformplayer.stores.FragmentedStorage
|
||||||
|
import com.futo.platformplayer.stores.StringDateMapStorage
|
||||||
import com.futo.platformplayer.stores.db.ManagedDBStore
|
import com.futo.platformplayer.stores.db.ManagedDBStore
|
||||||
import com.futo.platformplayer.stores.db.types.DBHistory
|
import com.futo.platformplayer.stores.db.types.DBHistory
|
||||||
import com.futo.platformplayer.stores.v2.ReconstructStore
|
import com.futo.platformplayer.stores.v2.ReconstructStore
|
||||||
|
@ -19,7 +22,6 @@ import kotlinx.coroutines.launch
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.ConcurrentMap
|
import java.util.concurrent.ConcurrentMap
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
class StateHistory {
|
class StateHistory {
|
||||||
//Legacy
|
//Legacy
|
||||||
|
@ -31,6 +33,8 @@ class StateHistory {
|
||||||
})
|
})
|
||||||
.load();
|
.load();
|
||||||
|
|
||||||
|
private val _remoteHistoryDatesStore = FragmentedStorage.get<StringDateMapStorage>("remoteHistoryDates");
|
||||||
|
|
||||||
private val historyIndex: ConcurrentMap<Any, DBHistory.Index> = ConcurrentHashMap();
|
private val historyIndex: ConcurrentMap<Any, DBHistory.Index> = ConcurrentHashMap();
|
||||||
val _historyDBStore = ManagedDBStore.create("history", DBHistory.Descriptor())
|
val _historyDBStore = ManagedDBStore.create("history", DBHistory.Descriptor())
|
||||||
.withIndex({ it.url }, historyIndex, false, true)
|
.withIndex({ it.url }, historyIndex, false, true)
|
||||||
|
@ -186,8 +190,95 @@ class StateHistory {
|
||||||
val toDelete = _historyDBStore.getAllIndexes().filter { minutesToDelete == -1L || (now - it.datetime) < minutesToDelete * 60 };
|
val toDelete = _historyDBStore.getAllIndexes().filter { minutesToDelete == -1L || (now - it.datetime) < minutesToDelete * 60 };
|
||||||
for(item in toDelete)
|
for(item in toDelete)
|
||||||
_historyDBStore.delete(item);
|
_historyDBStore.delete(item);
|
||||||
|
_remoteHistoryDatesStore.map = HashMap<String, Long>();
|
||||||
|
_remoteHistoryDatesStore.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun syncRemoteHistory(plugin: JSClient) {
|
||||||
|
if (plugin.capabilities.hasGetUserHistory &&
|
||||||
|
plugin.isLoggedIn) {
|
||||||
|
Logger.i(TAG, "Syncing remote history for plugin [${plugin.name}]");
|
||||||
|
|
||||||
|
val hist = StatePlatform.instance.getUserHistory(plugin.id);
|
||||||
|
|
||||||
|
syncRemoteHistory(plugin.id, hist, 100, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun syncRemoteHistory(pluginId: String, videos: IPager<IPlatformContent>, maxVideos: Int, maxPages: Int) {
|
||||||
|
val lastDate = _remoteHistoryDatesStore.get(pluginId) ?: OffsetDateTime.MIN;
|
||||||
|
val maxVideosCount = if(maxVideos <= 0) 500 else maxVideos;
|
||||||
|
val maxPageCount = if(maxPages <= 0) 3 else maxPages;
|
||||||
|
var exceededDate = false;
|
||||||
|
try {
|
||||||
|
val toSync = mutableListOf<IPlatformVideo>();
|
||||||
|
var pageCount = 0;
|
||||||
|
var videoCount = 0;
|
||||||
|
var isFirst = true;
|
||||||
|
var oldestPlayback = OffsetDateTime.MAX;
|
||||||
|
var newestPlayback = OffsetDateTime.MIN;
|
||||||
|
do {
|
||||||
|
if (!isFirst) videos.nextPage();
|
||||||
|
val newVideos = videos.getResults();
|
||||||
|
|
||||||
|
var foundVideos = false;
|
||||||
|
var toSyncAddedCount = 0;
|
||||||
|
for(video in newVideos) {
|
||||||
|
if(video is IPlatformVideo && video.playbackDate != null) {
|
||||||
|
|
||||||
|
if(video.playbackDate!! < lastDate) {
|
||||||
|
exceededDate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(video.playbackTime > 0) {
|
||||||
|
toSync.add(video);
|
||||||
|
toSyncAddedCount++;
|
||||||
|
foundVideos = true;
|
||||||
|
oldestPlayback = video.playbackDate!!;
|
||||||
|
if(newestPlayback == OffsetDateTime.MIN)
|
||||||
|
newestPlayback = video.playbackDate!!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pageCount++;
|
||||||
|
videoCount += newVideos.size;
|
||||||
|
isFirst = false;
|
||||||
|
|
||||||
|
if(!foundVideos)
|
||||||
|
{
|
||||||
|
Logger.i(TAG, "Found no more videos in remote history");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(videos.hasMorePages() && videoCount <= maxVideosCount && pageCount <= maxPageCount && !exceededDate);
|
||||||
|
|
||||||
|
var updated = 0;
|
||||||
|
if(oldestPlayback < OffsetDateTime.MAX) {
|
||||||
|
for(video in toSync){
|
||||||
|
val hist = getHistoryByVideo(video, true, video.playbackDate);
|
||||||
|
if(hist != null && hist.position < video.playbackTime) {
|
||||||
|
Logger.i(TAG, "Updated history for video [${video.name}] from remote history");
|
||||||
|
updateHistoryPosition(video, hist, true, video.playbackTime, video.playbackDate, false);
|
||||||
|
updated++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(updated > 0) {
|
||||||
|
_remoteHistoryDatesStore.setAndSave(pluginId, newestPlayback);
|
||||||
|
|
||||||
|
try {
|
||||||
|
val client = StatePlatform.instance.getClient(pluginId);
|
||||||
|
UIDialogs.appToast("Updated ${updated} history from ${client.name}")
|
||||||
|
}
|
||||||
|
catch(ex: Throwable){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(ex: Throwable) {
|
||||||
|
val plugin = if(pluginId != StateDeveloper.DEV_ID) StatePlugins.instance.getPlugin(pluginId) else null;
|
||||||
|
Logger.e(TAG, "Sync Remote History failed for [${plugin?.config?.name}] due to: " + ex.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = "StateHistory";
|
val TAG = "StateHistory";
|
||||||
|
|
|
@ -1036,6 +1036,16 @@ class StatePlatform {
|
||||||
return client.getLiveChatWindow(url);
|
return client.getLiveChatWindow(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Account
|
||||||
|
fun getUserHistory(id: String): IPager<IPlatformContent> {
|
||||||
|
val client = getClient(id);
|
||||||
|
if(client is JSClient && client.isLoggedIn) {
|
||||||
|
return client.fromPool(_pagerClientPool).getUserHistory()
|
||||||
|
}
|
||||||
|
return EmptyPager<IPlatformContent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun injectDevPlugin(source: SourcePluginConfig, script: String): String? {
|
fun injectDevPlugin(source: SourcePluginConfig, script: String): String? {
|
||||||
var devId: String? = null;
|
var devId: String? = null;
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
<string name="general">General</string>
|
<string name="general">General</string>
|
||||||
<string name="channel">Channel</string>
|
<string name="channel">Channel</string>
|
||||||
<string name="home">Home</string>
|
<string name="home">Home</string>
|
||||||
|
<string name="sync_history">Sync Remote History</string>
|
||||||
|
<string name="sync_history_desc">Synchronize account history from this platform on startup</string>
|
||||||
<string name="progress_bar">Progress Bar</string>
|
<string name="progress_bar">Progress Bar</string>
|
||||||
<string name="advanced_settings">Advanced Settings</string>
|
<string name="advanced_settings">Advanced Settings</string>
|
||||||
<string name="advanced_settings_description">If advanced settings should be shown, this exposes additional settings to finetune your experience.</string>
|
<string name="advanced_settings_description">If advanced settings should be shown, this exposes additional settings to finetune your experience.</string>
|
||||||
|
@ -592,6 +594,8 @@
|
||||||
<string name="various_tests_against_a_custom_source">Various tests against a custom source</string>
|
<string name="various_tests_against_a_custom_source">Various tests against a custom source</string>
|
||||||
<string name="writes_to_disk_till_no_space_is_left">Writes to disk till no space is left</string>
|
<string name="writes_to_disk_till_no_space_is_left">Writes to disk till no space is left</string>
|
||||||
<string name="visibility">Visibility</string>
|
<string name="visibility">Visibility</string>
|
||||||
|
<string name="sync">Sync</string>
|
||||||
|
<string name="sync_desc">Synchronization of platform data</string>
|
||||||
<string name="check_for_updates_setting">Check for updates</string>
|
<string name="check_for_updates_setting">Check for updates</string>
|
||||||
<string name="check_for_updates_setting_description">If a plugin should be checked for updates on startup</string>
|
<string name="check_for_updates_setting_description">If a plugin should be checked for updates on startup</string>
|
||||||
<string name="automatic_update_setting">Automatic Update</string>
|
<string name="automatic_update_setting">Automatic Update</string>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue