mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-20 03:24:50 +00:00
Subscription group sync, playlist sync, send to device support mobile to desktop
This commit is contained in:
parent
790331e798
commit
db7c09291f
11 changed files with 258 additions and 8 deletions
|
@ -180,7 +180,7 @@ class SubscriptionGroupFragment : MainFragment() {
|
|||
UIDialogs.showDialog(context, R.drawable.ic_trash, "Delete Group", "Are you sure you want to this group?\n[${g.name}]?", null, 0,
|
||||
UIDialogs.Action("Cancel", {}),
|
||||
UIDialogs.Action("Delete", {
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(g.id);
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(g.id, true);
|
||||
_didDelete = true;
|
||||
fragment.close(true);
|
||||
}, UIDialogs.ActionStyle.DANGEROUS))
|
||||
|
|
|
@ -60,7 +60,7 @@ class SubscriptionGroupListFragment : MainFragment() {
|
|||
val loc = _subs.indexOf(group);
|
||||
_subs.remove(group);
|
||||
_list?.adapter?.notifyItemRangeRemoved(loc);
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(group.id);
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(group.id, true);
|
||||
};
|
||||
it.onDragDrop.subscribe {
|
||||
_touchHelper?.startDrag(it);
|
||||
|
|
|
@ -111,9 +111,12 @@ import com.futo.platformplayer.states.StatePlaylists
|
|||
import com.futo.platformplayer.states.StatePlugins
|
||||
import com.futo.platformplayer.states.StatePolycentric
|
||||
import com.futo.platformplayer.states.StateSubscriptions
|
||||
import com.futo.platformplayer.states.StateSync
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.StringArrayStorage
|
||||
import com.futo.platformplayer.stores.db.types.DBHistory
|
||||
import com.futo.platformplayer.sync.internal.GJSyncOpcodes
|
||||
import com.futo.platformplayer.sync.models.SendToDevicePackage
|
||||
import com.futo.platformplayer.toHumanBitrate
|
||||
import com.futo.platformplayer.toHumanBytesSize
|
||||
import com.futo.platformplayer.toHumanNowDiffString
|
||||
|
@ -637,6 +640,27 @@ class VideoDetailView : ConstraintLayout {
|
|||
StatePlayer.instance.onVideoChanging.subscribe(this) {
|
||||
setVideoOverview(it);
|
||||
};
|
||||
|
||||
var hadDevice = false;
|
||||
StateSync.instance.deviceUpdatedOrAdded.subscribe(this) { id, session ->
|
||||
val hasDevice = StateSync.instance.hasAtLeastOneOnlineDevice();
|
||||
if(hasDevice != hadDevice) {
|
||||
hadDevice = hasDevice;
|
||||
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
updateMoreButtons();
|
||||
}
|
||||
}
|
||||
};
|
||||
StateSync.instance.deviceRemoved.subscribe(this) { id ->
|
||||
val hasDevice = StateSync.instance.hasAtLeastOneOnlineDevice();
|
||||
if(hasDevice != hadDevice) {
|
||||
hadDevice = hasDevice;
|
||||
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
updateMoreButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediaControlReceiver.onLowerVolumeReceived.subscribe(this) { handleLowerVolume() };
|
||||
MediaControlReceiver.onPlayReceived.subscribe(this) { handlePlay() };
|
||||
MediaControlReceiver.onPauseReceived.subscribe(this) { handlePause() };
|
||||
|
@ -878,6 +902,22 @@ class VideoDetailView : ConstraintLayout {
|
|||
};
|
||||
_slideUpOverlay?.hide();
|
||||
},
|
||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||
RoundButton(context, R.drawable.ic_device, context.getString(R.string.send_to_device), TAG_SEND_TO_DEVICE) {
|
||||
val devices = StateSync.instance.getSessions();
|
||||
val videoToSend = video ?: return@RoundButton;
|
||||
if(devices.size > 1) {
|
||||
//not implemented
|
||||
}
|
||||
else if(devices.size == 1){
|
||||
val device = devices.first();
|
||||
UIDialogs.showConfirmationDialog(context, "Would you like to open\n[${videoToSend.name}]\non ${device.remotePublicKey}" , {
|
||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||
device.sendJson(GJSyncOpcodes.sendToDevices, SendToDevicePackage(videoToSend.url, (lastPositionMilliseconds/1000).toInt()));
|
||||
}
|
||||
})
|
||||
}
|
||||
}} else null,
|
||||
RoundButton(context, R.drawable.ic_refresh, context.getString(R.string.reload), "Reload") {
|
||||
reloadVideo();
|
||||
_slideUpOverlay?.hide();
|
||||
|
@ -1025,6 +1065,8 @@ class VideoDetailView : ConstraintLayout {
|
|||
StateApp.instance.preventPictureInPicture.remove(this);
|
||||
StatePlayer.instance.onQueueChanged.remove(this);
|
||||
StatePlayer.instance.onVideoChanging.remove(this);
|
||||
StateSync.instance.deviceUpdatedOrAdded.remove(this);
|
||||
StateSync.instance.deviceRemoved.remove(this);
|
||||
MediaControlReceiver.onLowerVolumeReceived.remove(this);
|
||||
MediaControlReceiver.onPlayReceived.remove(this);
|
||||
MediaControlReceiver.onPauseReceived.remove(this);
|
||||
|
@ -2860,6 +2902,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
const val TAG_OVERLAY = "overlay";
|
||||
const val TAG_LIVECHAT = "livechat";
|
||||
const val TAG_OPEN = "open";
|
||||
const val TAG_SEND_TO_DEVICE = "send_to_device";
|
||||
const val TAG_MORE = "MORE";
|
||||
|
||||
private val _buttonPinStore = FragmentedStorage.get<StringArrayStorage>("videoPinnedButtons");
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.futo.platformplayer.models
|
||||
|
||||
import com.futo.platformplayer.serializers.OffsetDateTimeSerializer
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.UUID
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
|
@ -10,6 +12,11 @@ open class SubscriptionGroup {
|
|||
var urls: MutableList<String> = mutableListOf();
|
||||
var priority: Int = 99;
|
||||
|
||||
@kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class)
|
||||
var lastChange : OffsetDateTime = OffsetDateTime.MIN;
|
||||
@kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class)
|
||||
var creationTime : OffsetDateTime = OffsetDateTime.now();
|
||||
|
||||
constructor(name: String) {
|
||||
this.name = name;
|
||||
}
|
||||
|
@ -19,6 +26,8 @@ open class SubscriptionGroup {
|
|||
this.image = parent.image;
|
||||
this.urls = parent.urls;
|
||||
this.priority = parent.priority;
|
||||
this.lastChange = parent.lastChange;
|
||||
this.creationTime = parent.creationTime;
|
||||
}
|
||||
|
||||
class Selectable(parent: SubscriptionGroup, isSelected: Boolean = false): SubscriptionGroup(parent) {
|
||||
|
|
|
@ -17,10 +17,18 @@ import com.futo.platformplayer.exceptions.ReconstructionException
|
|||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.ImportCache
|
||||
import com.futo.platformplayer.models.Playlist
|
||||
import com.futo.platformplayer.states.StateSubscriptionGroups.Companion
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.StringArrayStorage
|
||||
import com.futo.platformplayer.stores.StringDateMapStorage
|
||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||
import com.futo.platformplayer.stores.v2.ReconstructStore
|
||||
import com.futo.platformplayer.sync.internal.GJSyncOpcodes
|
||||
import com.futo.platformplayer.sync.models.SyncPlaylistsPackage
|
||||
import com.futo.platformplayer.sync.models.SyncSubscriptionGroupsPackage
|
||||
import com.futo.platformplayer.sync.models.SyncSubscriptionsPackage
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
@ -45,6 +53,7 @@ class StatePlaylists {
|
|||
val playlistStore = FragmentedStorage.storeJson<Playlist>("playlists")
|
||||
.withRestore(PlaylistBackup())
|
||||
.load();
|
||||
private val _playlistRemoved = FragmentedStorage.get<StringDateMapStorage>("playlist_removed");
|
||||
|
||||
val playlistShareDir = FragmentedStorage.getOrCreateDirectory("shares");
|
||||
|
||||
|
@ -118,6 +127,9 @@ class StatePlaylists {
|
|||
return playlistStore.findItem { it.id == id };
|
||||
}
|
||||
|
||||
fun getPlaylistRemovals(): Map<String, Long> {
|
||||
return _playlistRemoved.all();
|
||||
}
|
||||
|
||||
fun didPlay(playlistId: String) {
|
||||
val playlist = getPlaylist(playlistId);
|
||||
|
@ -148,13 +160,15 @@ class StatePlaylists {
|
|||
createOrUpdatePlaylist(newPlaylist);
|
||||
return newPlaylist;
|
||||
}
|
||||
fun createOrUpdatePlaylist(playlist: Playlist) {
|
||||
fun createOrUpdatePlaylist(playlist: Playlist, isUserInteraction: Boolean = true) {
|
||||
playlist.dateUpdate = OffsetDateTime.now();
|
||||
playlistStore.saveAsync(playlist, true);
|
||||
if(playlist.id.isNotEmpty()) {
|
||||
if (StateDownloads.instance.isPlaylistCached(playlist.id)) {
|
||||
StateDownloads.instance.checkForOutdatedPlaylistVideos(playlist.id);
|
||||
}
|
||||
if(isUserInteraction)
|
||||
broadcastSyncPlaylist(playlist);
|
||||
}
|
||||
}
|
||||
fun addToPlaylist(id: String, video: IPlatformVideo) {
|
||||
|
@ -163,14 +177,41 @@ class StatePlaylists {
|
|||
playlist.videos.add(SerializedPlatformVideo.fromVideo(video));
|
||||
playlist.dateUpdate = OffsetDateTime.now();
|
||||
playlistStore.saveAsync(playlist, true);
|
||||
|
||||
broadcastSyncPlaylist(playlist);
|
||||
}
|
||||
}
|
||||
|
||||
fun removePlaylist(playlist: Playlist) {
|
||||
private fun broadcastSyncPlaylist(playlist: Playlist){
|
||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||
Logger.i(StateSubscriptionGroups.TAG, "SyncPlaylist (${playlist.name})");
|
||||
StateSync.instance.broadcastJson(
|
||||
GJSyncOpcodes.syncPlaylists,
|
||||
SyncPlaylistsPackage(listOf(playlist), mapOf())
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fun removePlaylist(playlist: Playlist, isUserInteraction: Boolean = true) {
|
||||
playlistStore.delete(playlist);
|
||||
if(StateDownloads.instance.isPlaylistCached(playlist.id)) {
|
||||
StateDownloads.instance.deleteCachedPlaylist(playlist.id);
|
||||
}
|
||||
if(isUserInteraction) {
|
||||
_playlistRemoved.setAndSave(playlist.id, OffsetDateTime.now());
|
||||
|
||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||
Logger.i(StateSubscriptionGroups.TAG, "SyncPlaylist (${playlist.name})");
|
||||
StateSync.instance.broadcastJson(
|
||||
GJSyncOpcodes.syncPlaylists,
|
||||
SyncPlaylistsPackage(listOf(), mapOf(Pair(playlist.id, OffsetDateTime.now().toEpochSecond())))
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fun createPlaylistShareUri(context: Context, playlist: Playlist): Uri {
|
||||
|
@ -194,6 +235,16 @@ class StatePlaylists {
|
|||
return FileProvider.getUriForFile(context, context.resources.getString(R.string.authority), newFile);
|
||||
}
|
||||
|
||||
|
||||
fun getSyncPlaylistsPackageString(): String{
|
||||
return Json.encodeToString(
|
||||
SyncPlaylistsPackage(
|
||||
getPlaylists(),
|
||||
getPlaylistRemovals()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = "StatePlaylists";
|
||||
private var _instance : StatePlaylists? = null;
|
||||
|
|
|
@ -25,13 +25,20 @@ import com.futo.platformplayer.models.Subscription
|
|||
import com.futo.platformplayer.models.SubscriptionGroup
|
||||
import com.futo.platformplayer.polycentric.PolycentricCache
|
||||
import com.futo.platformplayer.resolveChannelUrl
|
||||
import com.futo.platformplayer.states.StateHistory.Companion
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.StringDateMapStorage
|
||||
import com.futo.platformplayer.stores.SubscriptionStorage
|
||||
import com.futo.platformplayer.stores.v2.ReconstructStore
|
||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||
import com.futo.platformplayer.subscription.SubscriptionFetchAlgorithm
|
||||
import com.futo.platformplayer.subscription.SubscriptionFetchAlgorithms
|
||||
import com.futo.platformplayer.sync.internal.GJSyncOpcodes
|
||||
import com.futo.platformplayer.sync.models.SyncSubscriptionGroupsPackage
|
||||
import com.futo.platformplayer.sync.models.SyncSubscriptionsPackage
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
|
@ -51,6 +58,9 @@ class StateSubscriptionGroups {
|
|||
.withUnique { it.id }
|
||||
.load();
|
||||
|
||||
|
||||
private val _groupsRemoved = FragmentedStorage.get<StringDateMapStorage>("group_removed");
|
||||
|
||||
val onGroupsChanged = Event0();
|
||||
|
||||
fun getSubscriptionGroup(id: String): SubscriptionGroup? {
|
||||
|
@ -59,20 +69,58 @@ class StateSubscriptionGroups {
|
|||
fun getSubscriptionGroups(): List<SubscriptionGroup> {
|
||||
return _subGroups.getItems();
|
||||
}
|
||||
fun updateSubscriptionGroup(subGroup: SubscriptionGroup, preventNotify: Boolean = false) {
|
||||
fun getSubscriptionGroupsRemovals(): Map<String, Long> {
|
||||
return _groupsRemoved.all();
|
||||
}
|
||||
fun updateSubscriptionGroup(subGroup: SubscriptionGroup, preventNotify: Boolean = false, preventSync: Boolean = false) {
|
||||
subGroup.lastChange = OffsetDateTime.now();
|
||||
_subGroups.save(subGroup);
|
||||
if(!preventNotify)
|
||||
onGroupsChanged.emit();
|
||||
if(!preventSync) {
|
||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||
Logger.i(TAG, "SyncSubscriptionGroup (${subGroup.name})");
|
||||
StateSync.instance.broadcastJson(
|
||||
GJSyncOpcodes.syncSubscriptionGroups,
|
||||
SyncSubscriptionGroupsPackage(listOf(subGroup), mapOf())
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
fun deleteSubscriptionGroup(id: String){
|
||||
fun deleteSubscriptionGroup(id: String, isUserInteraction: Boolean = true){
|
||||
val group = getSubscriptionGroup(id);
|
||||
if(group != null) {
|
||||
_subGroups.delete(group);
|
||||
onGroupsChanged.emit();
|
||||
|
||||
if(isUserInteraction) {
|
||||
_groupsRemoved.setAndSave(id, OffsetDateTime.now());
|
||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||
Logger.i(TAG, "SyncSubscriptionGroup delete (${group.name})");
|
||||
StateSync.instance.broadcastJson(
|
||||
GJSyncOpcodes.syncSubscriptionGroups,
|
||||
SyncSubscriptionGroupsPackage(listOf(), mapOf(Pair(id, OffsetDateTime.now().toEpochSecond())))
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun getSyncSubscriptionGroupsPackageString(): String{
|
||||
return Json.encodeToString(
|
||||
SyncSubscriptionGroupsPackage(
|
||||
getSubscriptionGroups(),
|
||||
getSubscriptionGroupsRemovals()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
const val TAG = "StateSubscriptionGroups";
|
||||
const val VERSION = 1;
|
||||
|
|
|
@ -11,5 +11,7 @@ class GJSyncOpcodes {
|
|||
val syncSubscriptions: UByte = 202.toUByte();
|
||||
|
||||
val syncHistory: UByte = 203.toUByte();
|
||||
val syncSubscriptionGroups: UByte = 204.toUByte();
|
||||
val syncPlaylists: UByte = 205.toUByte();
|
||||
}
|
||||
}
|
|
@ -6,15 +6,20 @@ import com.futo.platformplayer.api.media.Serializer
|
|||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.HistoryVideo
|
||||
import com.futo.platformplayer.models.Subscription
|
||||
import com.futo.platformplayer.models.SubscriptionGroup
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateBackup
|
||||
import com.futo.platformplayer.states.StateHistory
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.states.StatePlaylists
|
||||
import com.futo.platformplayer.states.StateSubscriptionGroups
|
||||
import com.futo.platformplayer.states.StateSubscriptions
|
||||
import com.futo.platformplayer.states.StateSync
|
||||
import com.futo.platformplayer.sync.SyncSessionData
|
||||
import com.futo.platformplayer.sync.internal.SyncSocketSession.Opcode
|
||||
import com.futo.platformplayer.sync.models.SendToDevicePackage
|
||||
import com.futo.platformplayer.sync.models.SyncPlaylistsPackage
|
||||
import com.futo.platformplayer.sync.models.SyncSubscriptionGroupsPackage
|
||||
import com.futo.platformplayer.sync.models.SyncSubscriptionsPackage
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -22,7 +27,9 @@ import kotlinx.serialization.encodeToString
|
|||
import kotlinx.serialization.json.Json
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.time.Instant
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
interface IAuthorizable {
|
||||
val isAuthorized: Boolean
|
||||
|
@ -158,6 +165,8 @@ class SyncSession : IAuthorizable {
|
|||
|
||||
|
||||
send(GJSyncOpcodes.syncSubscriptions, StateSubscriptions.instance.getSyncSubscriptionsPackageString());
|
||||
send(GJSyncOpcodes.syncSubscriptionGroups, StateSubscriptionGroups.instance.getSyncSubscriptionGroupsPackageString());
|
||||
send(GJSyncOpcodes.syncPlaylists, StatePlaylists.instance.getSyncPlaylistsPackageString())
|
||||
|
||||
val recentHistory = StateHistory.instance.getRecentHistory(syncSessionData.lastHistory);
|
||||
if(recentHistory.size > 0)
|
||||
|
@ -205,6 +214,67 @@ class SyncSession : IAuthorizable {
|
|||
}
|
||||
}
|
||||
|
||||
GJSyncOpcodes.syncSubscriptionGroups -> {
|
||||
val dataBody = ByteArray(data.remaining());
|
||||
data.get(dataBody);
|
||||
val json = String(dataBody, Charsets.UTF_8);
|
||||
val pack = Serializer.json.decodeFromString<SyncSubscriptionGroupsPackage>(json);
|
||||
|
||||
var lastSubgroupChange = OffsetDateTime.MIN;
|
||||
for(group in pack.groups){
|
||||
if(group.lastChange > lastSubgroupChange)
|
||||
lastSubgroupChange = group.lastChange;
|
||||
|
||||
val existing = StateSubscriptionGroups.instance.getSubscriptionGroup(group.id);
|
||||
|
||||
if(existing == null)
|
||||
StateSubscriptionGroups.instance.updateSubscriptionGroup(group, false, true);
|
||||
else if(existing.lastChange < group.lastChange) {
|
||||
existing.name = group.name;
|
||||
existing.urls = group.urls;
|
||||
existing.image = group.image;
|
||||
existing.priority = group.priority;
|
||||
existing.lastChange = group.lastChange;
|
||||
StateSubscriptionGroups.instance.updateSubscriptionGroup(existing, false, true);
|
||||
}
|
||||
}
|
||||
for(removal in pack.groupRemovals) {
|
||||
val creation = StateSubscriptionGroups.instance.getSubscriptionGroup(removal.key);
|
||||
val removalTime = OffsetDateTime.ofInstant(Instant.ofEpochSecond(removal.value, 0), ZoneOffset.UTC);
|
||||
if(creation != null && creation.creationTime < removalTime)
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(removal.key, false);
|
||||
}
|
||||
}
|
||||
|
||||
GJSyncOpcodes.syncPlaylists -> {
|
||||
val dataBody = ByteArray(data.remaining());
|
||||
data.get(dataBody);
|
||||
val json = String(dataBody, Charsets.UTF_8);
|
||||
val pack = Serializer.json.decodeFromString<SyncPlaylistsPackage>(json);
|
||||
|
||||
for(playlist in pack.playlists) {
|
||||
val existing = StatePlaylists.instance.getPlaylist(playlist.id);
|
||||
|
||||
if(existing == null)
|
||||
StatePlaylists.instance.createOrUpdatePlaylist(playlist, false);
|
||||
else if(existing.dateUpdate.toLocalDateTime() < playlist.dateUpdate.toLocalDateTime()) {
|
||||
existing.dateUpdate = playlist.dateUpdate;
|
||||
existing.name = playlist.name;
|
||||
existing.videos = playlist.videos;
|
||||
existing.dateCreation = playlist.dateCreation;
|
||||
existing.datePlayed = playlist.datePlayed;
|
||||
StatePlaylists.instance.createOrUpdatePlaylist(existing, false);
|
||||
}
|
||||
}
|
||||
for(removal in pack.playlistRemovals) {
|
||||
val creation = StatePlaylists.instance.getPlaylist(removal.key);
|
||||
val removalTime = OffsetDateTime.ofInstant(Instant.ofEpochSecond(removal.value, 0), ZoneOffset.UTC);
|
||||
if(creation != null && creation.dateCreation < removalTime)
|
||||
StatePlaylists.instance.removePlaylist(creation, false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
GJSyncOpcodes.syncHistory -> {
|
||||
val dataBody = ByteArray(data.remaining());
|
||||
data.get(dataBody);
|
||||
|
@ -242,8 +312,7 @@ class SyncSession : IAuthorizable {
|
|||
if(!StateSubscriptions.instance.isSubscribed(sub.channel)) {
|
||||
val removalTime = StateSubscriptions.instance.getSubscriptionRemovalTime(sub.channel.url);
|
||||
if(sub.creationTime > removalTime) {
|
||||
val newSub =
|
||||
StateSubscriptions.instance.addSubscription(sub.channel, sub.creationTime);
|
||||
val newSub = StateSubscriptions.instance.addSubscription(sub.channel, sub.creationTime);
|
||||
added.add(newSub);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.futo.platformplayer.sync.models
|
||||
|
||||
import com.futo.platformplayer.models.Playlist
|
||||
import com.futo.platformplayer.models.Subscription
|
||||
import com.futo.platformplayer.models.SubscriptionGroup
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.Dictionary
|
||||
|
||||
@Serializable
|
||||
class SyncPlaylistsPackage(
|
||||
var playlists: List<Playlist>,
|
||||
var playlistRemovals: Map<String, Long>
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package com.futo.platformplayer.sync.models
|
||||
|
||||
import com.futo.platformplayer.models.Subscription
|
||||
import com.futo.platformplayer.models.SubscriptionGroup
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.Dictionary
|
||||
|
||||
@Serializable
|
||||
class SyncSubscriptionGroupsPackage(
|
||||
var groups: List<SubscriptionGroup>,
|
||||
var groupRemovals: Map<String, Long>
|
||||
)
|
|
@ -608,6 +608,7 @@
|
|||
<string name="do_you_want_to_convert_channel_channelname_to_a_playlist">Do you want to convert channel {channelName} to a playlist?</string>
|
||||
<string name="failed_to_convert_channel">Failed to convert channel</string>
|
||||
<string name="page">Page</string>
|
||||
<string name="send_to_device">Sync Video</string>
|
||||
<string name="hide">Hide</string>
|
||||
<string name="hide_from_home">Hide from Home</string>
|
||||
<string name="hide_creator_from_home">Hide Creator from Home</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue