mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-09 23:48:41 +01:00
chore: coderabbit comments
Fixing coderabbit raised issues Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
This commit is contained in:
@@ -16,12 +16,13 @@ import androidx.tvprovider.media.tv.PreviewProgram
|
|||||||
import androidx.tvprovider.media.tv.TvContractCompat
|
import androidx.tvprovider.media.tv.TvContractCompat
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
internal object TvRecommendationsPublisher {
|
internal object TvRecommendationsPublisher {
|
||||||
private const val TAG = "TvRecommendations"
|
private const val TAG = "TvRecommendations"
|
||||||
private const val PREFS_NAME = "StreamyfinTvRecommendations"
|
private const val PREFS_NAME = "StreamyfinTvRecommendations"
|
||||||
private const val KEY_PAYLOAD = "payload"
|
private const val KEY_PAYLOAD = "payload"
|
||||||
private const val KEY_CHANNEL_ID = "channelId"
|
private const val KEY_CHANNEL_ID_PREFIX = "channelId_"
|
||||||
private const val KEY_PROGRAM_IDS = "programIds"
|
private const val KEY_PROGRAM_IDS = "programIds"
|
||||||
private const val DEFAULT_CHANNEL_NAME = "Continue and Next Up"
|
private const val DEFAULT_CHANNEL_NAME = "Continue and Next Up"
|
||||||
|
|
||||||
@@ -162,16 +163,24 @@ internal object TvRecommendationsPublisher {
|
|||||||
val prefs = preferences(context)
|
val prefs = preferences(context)
|
||||||
val programIdsJson = prefs.getString(KEY_PROGRAM_IDS, null) ?: return
|
val programIdsJson = prefs.getString(KEY_PROGRAM_IDS, null) ?: return
|
||||||
try {
|
try {
|
||||||
val programIds = JSONObject(programIdsJson)
|
val channelMap = JSONObject(programIdsJson)
|
||||||
val keys = programIds.keys()
|
val channelKeys = channelMap.keys()
|
||||||
while (keys.hasNext()) {
|
while (channelKeys.hasNext()) {
|
||||||
val key = keys.next()
|
val channelId = channelKeys.next()
|
||||||
if (programIds.optLong(key, -1L) == programId) {
|
val inner = channelMap.optJSONObject(channelId) ?: continue
|
||||||
programIds.remove(key)
|
val providerKeys = inner.keys()
|
||||||
break
|
while (providerKeys.hasNext()) {
|
||||||
|
val providerId = providerKeys.next()
|
||||||
|
if (inner.optLong(providerId, -1L) == programId) {
|
||||||
|
inner.remove(providerId)
|
||||||
|
if (inner.length() == 0) {
|
||||||
|
channelMap.remove(channelId)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prefs.edit().putString(KEY_PROGRAM_IDS, programIds.toString()).apply()
|
prefs.edit().putString(KEY_PROGRAM_IDS, channelMap.toString()).apply()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "removeProgramFromPrefs(): failed to update prefs for programId=$programId", e)
|
Log.w(TAG, "removeProgramFromPrefs(): failed to update prefs for programId=$programId", e)
|
||||||
}
|
}
|
||||||
@@ -322,7 +331,8 @@ internal object TvRecommendationsPublisher {
|
|||||||
|
|
||||||
private fun getOrCreateChannel(context: Context, displayName: String): Long {
|
private fun getOrCreateChannel(context: Context, displayName: String): Long {
|
||||||
val prefs = preferences(context)
|
val prefs = preferences(context)
|
||||||
val existingChannelId = prefs.getLong(KEY_CHANNEL_ID, -1L)
|
val channelKey = getChannelKey(displayName)
|
||||||
|
val existingChannelId = prefs.getLong(channelKey, -1L)
|
||||||
val contentResolver = context.contentResolver
|
val contentResolver = context.contentResolver
|
||||||
|
|
||||||
if (existingChannelId > 0L) {
|
if (existingChannelId > 0L) {
|
||||||
@@ -363,7 +373,7 @@ internal object TvRecommendationsPublisher {
|
|||||||
|
|
||||||
// Channel truly doesn't exist in provider — recreate
|
// Channel truly doesn't exist in provider — recreate
|
||||||
Log.w(TAG, "getOrCreateChannel(): channelId=$existingChannelId not in provider, recreating")
|
Log.w(TAG, "getOrCreateChannel(): channelId=$existingChannelId not in provider, recreating")
|
||||||
prefs.edit().remove(KEY_CHANNEL_ID).apply()
|
prefs.edit().remove(channelKey).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new channel
|
// Create a new channel
|
||||||
@@ -384,6 +394,7 @@ internal object TvRecommendationsPublisher {
|
|||||||
} ?: return -1L
|
} ?: return -1L
|
||||||
|
|
||||||
val channelId = ContentUris.parseId(channelUri)
|
val channelId = ContentUris.parseId(channelUri)
|
||||||
|
prefs.edit().putLong(channelKey, channelId).apply()
|
||||||
TvContractCompat.requestChannelBrowsable(context, channelId)
|
TvContractCompat.requestChannelBrowsable(context, channelId)
|
||||||
storeChannelLogo(context, channelId)
|
storeChannelLogo(context, channelId)
|
||||||
Log.d(TAG, "getOrCreateChannel(): created new channelId=$channelId displayName=\"$displayName\"")
|
Log.d(TAG, "getOrCreateChannel(): created new channelId=$channelId displayName=\"$displayName\"")
|
||||||
@@ -391,6 +402,10 @@ internal object TvRecommendationsPublisher {
|
|||||||
return channelId
|
return channelId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getChannelKey(displayName: String): String {
|
||||||
|
return KEY_CHANNEL_ID_PREFIX + displayName.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
private fun upsertPreviewProgram(
|
private fun upsertPreviewProgram(
|
||||||
context: Context,
|
context: Context,
|
||||||
channelId: Long,
|
channelId: Long,
|
||||||
@@ -462,13 +477,18 @@ internal object TvRecommendationsPublisher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Append a cache-busting parameter to ensure unique URIs when images change.
|
* Append a stable cache key derived from the image URL.
|
||||||
* Per Android docs: "Use unique Uris for all images... the old image will
|
* The Jellyfin image URLs already include a `tag=` query param (etag)
|
||||||
* continue to appear if you don't change the Uri."
|
* that changes whenever the image content changes, so a deterministic
|
||||||
|
* hash of the URL is sufficient — the param only changes when the URL
|
||||||
|
* (and therefore the image) actually changes, avoiding unnecessary
|
||||||
|
* re-downloads on every sync.
|
||||||
*/
|
*/
|
||||||
private fun appendCacheBuster(imageUrl: String): String {
|
private fun appendCacheBuster(imageUrl: String): String {
|
||||||
|
val digest = MessageDigest.getInstance("MD5").digest(imageUrl.toByteArray())
|
||||||
|
val hash = digest.joinToString("") { "%02x".format(it) }.substring(0, 16)
|
||||||
val separator = if (imageUrl.contains("?")) "&" else "?"
|
val separator = if (imageUrl.contains("?")) "&" else "?"
|
||||||
return "$imageUrl${separator}_t=${System.currentTimeMillis()}"
|
return "$imageUrl${separator}_v=$hash"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildIntentUri(context: Context, deepLink: String): Uri {
|
private fun buildIntentUri(context: Context, deepLink: String): Uri {
|
||||||
@@ -531,8 +551,8 @@ internal object TvRecommendationsPublisher {
|
|||||||
return bitmap
|
return bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getChannelId(context: Context): Long {
|
fun getChannelId(context: Context, displayName: String = DEFAULT_CHANNEL_NAME): Long {
|
||||||
return preferences(context).getLong(KEY_CHANNEL_ID, -1L)
|
return preferences(context).getLong(getChannelKey(displayName), -1L)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun preferences(context: Context): SharedPreferences {
|
private fun preferences(context: Context): SharedPreferences {
|
||||||
|
|||||||
@@ -3,16 +3,24 @@ package expo.modules.tvrecommendations
|
|||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.ContentUris
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.tvprovider.media.tv.TvContractCompat
|
import androidx.tvprovider.media.tv.TvContractCompat
|
||||||
|
|
||||||
class TvRecommendationsReceiver : BroadcastReceiver() {
|
class TvRecommendationsReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (intent.action != TvContractCompat.ACTION_INITIALIZE_PROGRAMS) {
|
when (intent.action) {
|
||||||
return
|
TvContractCompat.ACTION_INITIALIZE_PROGRAMS -> {
|
||||||
|
Log.d("TvRecommendations", "Handling INITIALIZE_PROGRAMS broadcast")
|
||||||
|
TvRecommendationsPublisher.refreshFromCache(context)
|
||||||
|
}
|
||||||
|
"android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED" -> {
|
||||||
|
val programId = intent.data?.let { ContentUris.parseId(it) } ?: -1L
|
||||||
|
if (programId > 0L) {
|
||||||
|
Log.d("TvRecommendations", "Handling PREVIEW_PROGRAM_BROWSABLE_DISABLED for programId=$programId")
|
||||||
|
TvRecommendationsPublisher.deletePreviewProgram(context, programId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("TvRecommendations", "Handling INITIALIZE_PROGRAMS broadcast")
|
|
||||||
TvRecommendationsPublisher.refreshFromCache(context)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user