Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -747,8 +747,14 @@ class CloudSyncRepository @Inject constructor(
)
}

val effectivePayload = if (existingRemotePayload != null && !iptvRepository.isGroupOrderLocallyDirty()) {
mergeRemoteGroupOrder(payload, existingRemotePayload)
} else {
payload
}

val payloadHash = runCatching {
JSONObject(payload).apply { remove("updatedAt") }.toString().hashCode()
JSONObject(effectivePayload).apply { remove("updatedAt") }.toString().hashCode()
}.getOrNull()

if (!force && payloadHash != null && payloadHash == lastPushedPayloadHash && !isPushDirty && pushFailureCount == 0) {
Expand All @@ -760,15 +766,15 @@ class CloudSyncRepository @Inject constructor(
return Result.success(Unit)
}

val result = authRepository.saveAccountSyncPayload(payload)
val result = authRepository.saveAccountSyncPayload(effectivePayload)
if (result.isSuccess) {
clearLocalDirtyAfterSuccessfulPush()
lastPushedPayloadHash = payloadHash
pushFailureCount = 0
Log.i(TAG, "Push succeeded size=${payloadSizeBucket(payload)}")
Log.i(TAG, "Push succeeded size=${payloadSizeBucket(effectivePayload)}")
AppLogger.breadcrumb(
tag = "CloudSync",
message = "push_success size=${payloadSizeBucket(payload)} user=${userId.take(8)}",
message = "push_success size=${payloadSizeBucket(effectivePayload)} user=${userId.take(8)}",
severity = "info"
)
onPushCompleted?.invoke()
Expand All @@ -780,22 +786,42 @@ class CloudSyncRepository @Inject constructor(
pushFailureCount++
Log.w(
TAG,
"Push failed size=${payloadSizeBucket(payload)} failures=$pushFailureCount error=${result.exceptionOrNull()?.message}"
"Push failed size=${payloadSizeBucket(effectivePayload)} failures=$pushFailureCount error=${result.exceptionOrNull()?.message}"
)
AppLogger.recordException(
throwable = result.exceptionOrNull() ?: IllegalStateException("Cloud push failed"),
context = mapOf(
"error_area" to "CloudSync",
"cloud_flow" to "push_save_payload",
"dirty" to isPushDirty.toString(),
"payload_size" to payloadSizeBucket(payload),
"payload_size" to payloadSizeBucket(effectivePayload),
"failure_count" to pushFailureCount.toString()
)
)
}
return result
}

private fun mergeRemoteGroupOrder(localPayload: String, remotePayload: String): String {
return runCatching {
val local = JSONObject(localPayload)
val remote = JSONObject(remotePayload)
val localByProfile = local.optJSONObject("iptvByProfile") ?: return@runCatching localPayload
val remoteByProfile = remote.optJSONObject("iptvByProfile") ?: return@runCatching localPayload
val remoteKeys = remoteByProfile.keys()
while (remoteKeys.hasNext()) {
val profileId = remoteKeys.next()
val remoteProfile = remoteByProfile.optJSONObject(profileId) ?: continue
val localProfile = localByProfile.optJSONObject(profileId) ?: continue
val remoteGroupOrder = remoteProfile.optJSONArray("groupOrder") ?: continue
if (remoteGroupOrder.length() > 0) {
localProfile.put("groupOrder", remoteGroupOrder)
}
}
local.toString()
}.getOrDefault(localPayload)
}

// ══════════════════════════════════════════════════════════
// PULL CLOUD STATE TO LOCAL
// ══════════════════════════════════════════════════════════
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ class IptvRepository @Inject constructor(
private var cachedGroupedChannels: Map<String, List<IptvChannel>> = emptyMap()
@Volatile var cachedStalkerApi: com.arflix.tv.data.api.StalkerApi? = null

@Volatile
private var groupOrderLocallyDirty = false

fun isGroupOrderLocallyDirty(): Boolean = groupOrderLocallyDirty

@Volatile
private var cachedNowNext: ConcurrentHashMap<String, IptvNowNext> = ConcurrentHashMap()
private val emptyShortEpgCooldownUntil = ConcurrentHashMap<String, Long>()
Expand Down Expand Up @@ -1390,6 +1395,7 @@ class IptvRepository @Inject constructor(
if (idx > 0) { order.removeAt(idx); order.add(idx - 1, target) }
prefs[groupOrderKey()] = gson.toJson(order)
}
groupOrderLocallyDirty = true
invalidationBus.markDirty(CloudSyncScope.IPTV, profileManager.getProfileIdSync(), "move group up")
}

Expand All @@ -1405,6 +1411,7 @@ class IptvRepository @Inject constructor(
order.add(0, target)
prefs[groupOrderKey()] = gson.toJson(order)
}
groupOrderLocallyDirty = true
invalidationBus.markDirty(CloudSyncScope.IPTV, profileManager.getProfileIdSync(), "move group to top")
}

Expand All @@ -1419,6 +1426,7 @@ class IptvRepository @Inject constructor(
if (idx >= 0 && idx < order.size - 1) { order.removeAt(idx); order.add(idx + 1, target) }
prefs[groupOrderKey()] = gson.toJson(order)
}
groupOrderLocallyDirty = true
invalidationBus.markDirty(CloudSyncScope.IPTV, profileManager.getProfileIdSync(), "move group down")
}

Expand All @@ -1428,6 +1436,7 @@ class IptvRepository @Inject constructor(
existing.removeAll { PlaylistGroupKey(it).playlistId == playlistId }
prefs[groupOrderKey()] = gson.toJson(existing)
}
groupOrderLocallyDirty = true
invalidationBus.markDirty(CloudSyncScope.IPTV, profileManager.getProfileIdSync(), "reset group order")
}

Expand Down Expand Up @@ -2946,6 +2955,7 @@ class IptvRepository @Inject constructor(
prefs.remove(tvSessionKeyFor(safeProfileId))
}
}
groupOrderLocallyDirty = false
if (profileManager.getProfileIdSync() == safeProfileId) {
invalidateCache()
}
Expand Down
Loading