Refs, History/subgroup import/export, broadcast sub change to synced devices

This commit is contained in:
Kelvin 2024-11-14 23:15:15 +01:00
parent 70cbc77381
commit 349437c06b
9 changed files with 106 additions and 24 deletions

View file

@ -11,6 +11,7 @@ import com.futo.platformplayer.activities.IWithResultLauncher
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.activities.SettingsActivity
import com.futo.platformplayer.api.media.models.channels.SerializedChannel
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
import com.futo.platformplayer.copyTo
import com.futo.platformplayer.encryption.GPasswordEncryptionProvider
@ -18,7 +19,9 @@ import com.futo.platformplayer.encryption.GPasswordEncryptionProviderV0
import com.futo.platformplayer.fragment.mainactivity.main.ImportSubscriptionsFragment
import com.futo.platformplayer.getNowDiffHours
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.HistoryVideo
import com.futo.platformplayer.models.ImportCache
import com.futo.platformplayer.models.SubscriptionGroup
import com.futo.platformplayer.readBytes
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.v2.ManagedStore
@ -61,9 +64,9 @@ class StateBackup {
StatePlaylists.instance.toMigrateCheck()
).flatten();
fun getCache(): ImportCache {
fun getCache(additionalVideos: List<SerializedPlatformVideo> = listOf()): ImportCache {
val allPlaylists = StatePlaylists.instance.getPlaylists();
val videos = allPlaylists.flatMap { it.videos }.distinctBy { it.url };
val videos = allPlaylists.flatMap { it.videos }.plus(additionalVideos).distinctBy { it.url };
val allSubscriptions = StateSubscriptions.instance.getSubscriptions();
val channels = allSubscriptions.map { it.channel };
@ -240,6 +243,23 @@ class StateBackup {
.associateBy { it.name }
.mapValues { it.value.getAllReconstructionStrings() }
.toMutableMap();
var historyVideos: List<SerializedPlatformVideo>? = null;
try {
storesToSave.set("subscription_groups", StateSubscriptionGroups.instance.getSubscriptionGroups().map { Json.encodeToString(it) });
}
catch(ex: Throwable) {
Logger.e(TAG, "Failed to serialize subscription groups");
}
try {
val history = StateHistory.instance.getRecentHistory(OffsetDateTime.MIN, 2000);
historyVideos = history.map { it.video };
storesToSave.set("history", history.map { it.toReconString() });
}
catch(ex: Throwable) {
Logger.e(TAG, "Failed to serialize history");
}
val settings = Settings.instance.encode();
val pluginSettings = StatePlugins.instance.getPlugins()
.associateBy { it.config.id }
@ -249,7 +269,7 @@ class StateBackup {
.associateBy { it.config.id }
.mapValues { it.value.config.sourceUrl!! };
val cache = getCache();
val cache = getCache(historyVideos ?: listOf());
val export = ExportStructure(exportInfo, settings, storesToSave, pluginUrls, pluginSettings, cache);
@ -333,19 +353,64 @@ class StateBackup {
if(doImportStores) {
for(store in export.stores) {
Logger.i(TAG, "Importing store [${store.key}]");
val relevantStore = availableStores.find { it.name == store.key };
if(relevantStore == null) {
Logger.w(TAG, "Unknown store [${store.key}] import");
continue;
if(store.key == "history") {
withContext(Dispatchers.Main) {
UIDialogs.showDialog(context, R.drawable.ic_move_up, "Import History", "Would you like to import history?", null, 0,
UIDialogs.Action("No", {
}, UIDialogs.ActionStyle.NONE),
UIDialogs.Action("Yes", {
for(historyStr in store.value) {
try {
val histObj = HistoryVideo.fromReconString(historyStr) { url ->
return@fromReconString export.cache?.videos?.firstOrNull { it.url == url };
}
val hist = StateHistory.instance.getHistoryByVideo(histObj.video, true, histObj.date);
if(hist != null)
StateHistory.instance.updateHistoryPosition(histObj.video, hist, true, histObj.position, histObj.date, false);
}
catch(ex: Throwable) {
Logger.e(TAG, "Failed to import subscription group", ex);
}
}
}, UIDialogs.ActionStyle.PRIMARY))
}
}
withContext(Dispatchers.Main) {
UIDialogs.showImportDialog(context, relevantStore, store.key, store.value, export.cache) {
synchronized(toAwait) {
toAwait.remove(store.key);
if(toAwait.isEmpty())
onConclusion();
}
};
else if(store.key == "subscription_groups") {
withContext(Dispatchers.Main) {
UIDialogs.showDialog(context, R.drawable.ic_move_up, "Import Subscription Groups", "Would you like to import subscription groups?\nExisting groups with the same id will be overridden!", null, 0,
UIDialogs.Action("No", {
}, UIDialogs.ActionStyle.NONE),
UIDialogs.Action("Yes", {
for(groupStr in store.value) {
try {
val group = Json.decodeFromString<SubscriptionGroup>(groupStr);
val existing = StateSubscriptionGroups.instance.getSubscriptionGroup(group.id);
if(existing != null)
StateSubscriptionGroups.instance.deleteSubscriptionGroup(existing.id, false);
StateSubscriptionGroups.instance.updateSubscriptionGroup(group);
}
catch(ex: Throwable) {
Logger.e(TAG, "Failed to import subscription group", ex);
}
}
}, UIDialogs.ActionStyle.PRIMARY))
}
}
else {
val relevantStore = availableStores.find { it.name == store.key };
if (relevantStore == null) {
Logger.w(TAG, "Unknown store [${store.key}] import");
continue;
}
withContext(Dispatchers.Main) {
UIDialogs.showImportDialog(context, relevantStore, store.key, store.value, export.cache) {
synchronized(toAwait) {
toAwait.remove(store.key);
if(toAwait.isEmpty())
onConclusion();
}
};
}
}
}
}

View file

@ -59,7 +59,6 @@ class StateHistory {
return getHistoryPosition(url) > duration * 0.7;
}
fun updateHistoryPosition(liveObj: IPlatformVideo, index: DBHistory.Index, updateExisting: Boolean, position: Long = -1L, date: OffsetDateTime? = null, isUserAction: Boolean = false): Long {
val pos = if(position < 0) 0 else position;
val historyVideo = index.obj;

View file

@ -293,8 +293,26 @@ class StateSubscriptions {
if(sub != null) {
_subscriptions.delete(sub);
onSubscriptionsChanged.emit(getSubscriptions(), false);
if(isUserAction)
_subscriptionsRemoved.setAndSave(sub.channel.url, OffsetDateTime.now());
if(isUserAction) {
val removalTime = OffsetDateTime.now();
_subscriptionsRemoved.setAndSave(sub.channel.url, removalTime);
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
try {
StateSync.instance.broadcast(
GJSyncOpcodes.syncSubscriptions, Json.encodeToString(
SyncSubscriptionsPackage(
listOf(),
mapOf(Pair(sub.channel.url, removalTime.toEpochSecond()))
)
)
);
}
catch(ex: Exception) {
Logger.w(TAG, "Failed to send subs changes to sync clients", ex);
}
}
}
if(StateSubscriptionGroups.instance.hasSubscriptionGroup(sub.channel.url))
getSubscriptionOtherOrCreate(sub.channel.url, sub.channel.name, sub.channel.thumbnail);

@ -1 +1 @@
Subproject commit 5b1919934d20f8c53de9959b04bdb66e0c6af3e9
Subproject commit 7b66aea99f08303eedea879b236c49132669d2b8

@ -1 +1 @@
Subproject commit 1ccd471cf4a6ef505983bc6ff4dd300624933fdd
Subproject commit 75ca0c0f1e31394ec4c82d5320fa9330df849f6f

@ -1 +1 @@
Subproject commit 41e91634965b0236c4e5ed9044da301dd3fd8c97
Subproject commit 80c9b4d3b48739170b40b313be930329dcc59fe4

@ -1 +1 @@
Subproject commit 5b1919934d20f8c53de9959b04bdb66e0c6af3e9
Subproject commit 7b66aea99f08303eedea879b236c49132669d2b8

@ -1 +1 @@
Subproject commit 1ccd471cf4a6ef505983bc6ff4dd300624933fdd
Subproject commit 75ca0c0f1e31394ec4c82d5320fa9330df849f6f

@ -1 +1 @@
Subproject commit 41e91634965b0236c4e5ed9044da301dd3fd8c97
Subproject commit 80c9b4d3b48739170b40b313be930329dcc59fe4