Skip to content
Closed
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
45 changes: 45 additions & 0 deletions app/src/main/java/app/gamenative/service/ServiceSyncPolicy.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package app.gamenative.service

object ServiceSyncPolicy {

fun shouldSyncOnColdStart(
hasPerformedInitialSync: Boolean,
lastSyncTimestamp: Long,
syncThrottleMillis: Long,
now: Long = System.currentTimeMillis(),
): Boolean {
if (!hasPerformedInitialSync) return true
return now - lastSyncTimestamp >= syncThrottleMillis
}

fun shouldSyncForAction(
action: String?,
manualSyncAction: String,
autoSyncAction: String,
hasPerformedInitialSync: Boolean,
lastSyncTimestamp: Long,
syncThrottleMillis: Long,
now: Long = System.currentTimeMillis(),
): Boolean {
return when (action) {
manualSyncAction -> true
autoSyncAction -> true
null -> shouldSyncOnColdStart(
hasPerformedInitialSync = hasPerformedInitialSync,
lastSyncTimestamp = lastSyncTimestamp,
syncThrottleMillis = syncThrottleMillis,
now = now,
)
else -> false
}
}

fun remainingThrottleMinutes(
lastSyncTimestamp: Long,
syncThrottleMillis: Long,
now: Long = System.currentTimeMillis(),
): Long {
val remainingMs = (lastSyncTimestamp + syncThrottleMillis - now).coerceAtLeast(0L)
return remainingMs / 1000 / 60
}
}
66 changes: 33 additions & 33 deletions app/src/main/java/app/gamenative/service/amazon/AmazonService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import app.gamenative.db.dao.AmazonGameDao
import app.gamenative.enums.Marker
import app.gamenative.events.AndroidEvent
import app.gamenative.service.NotificationHelper
import app.gamenative.service.ServiceSyncPolicy
import app.gamenative.utils.ContainerUtils
import app.gamenative.utils.ExecutableSelectionUtils
import app.gamenative.utils.MarkerUtils
Expand Down Expand Up @@ -109,24 +110,24 @@ class AmazonService : Service() {
}

val intent = Intent(context, AmazonService::class.java)
val shouldSync = ServiceSyncPolicy.shouldSyncOnColdStart(
hasPerformedInitialSync = hasPerformedInitialSync,
lastSyncTimestamp = lastSyncTimestamp,
syncThrottleMillis = SYNC_THROTTLE_MILLIS,
)

// First-time start: always sync without throttle
if (!hasPerformedInitialSync) {
Timber.i("[Amazon] First-time start — starting service with initial sync")
intent.action = ACTION_SYNC_LIBRARY
context.startForegroundService(intent)
return
}

// Subsequent starts: check throttle for sync
val now = System.currentTimeMillis()
val timeSinceLastSync = now - lastSyncTimestamp

if (timeSinceLastSync >= SYNC_THROTTLE_MILLIS) {
Timber.i("[Amazon] Starting service with automatic sync (throttle passed)")
if (shouldSync) {
intent.action = ACTION_SYNC_LIBRARY
if (!hasPerformedInitialSync) {
Timber.i("[Amazon] First-time start — starting service with initial sync")
} else {
Timber.i("[Amazon] Starting service with automatic sync (throttle passed)")
}
} else {
val remainingMinutes = (SYNC_THROTTLE_MILLIS - timeSinceLastSync) / 1000 / 60
val remainingMinutes = ServiceSyncPolicy.remainingThrottleMinutes(
lastSyncTimestamp = lastSyncTimestamp,
syncThrottleMillis = SYNC_THROTTLE_MILLIS,
)
Timber.i("[Amazon] Starting service without sync — throttled (${remainingMinutes}min remaining)")
}
context.startForegroundService(intent)
Expand Down Expand Up @@ -775,30 +776,29 @@ class AmazonService : Service() {
val notification = notificationHelper.createForegroundNotification("Connected")
startForeground(1, notification)

val shouldSync = when (intent?.action) {
ACTION_MANUAL_SYNC -> {
Timber.i("[Amazon] Manual sync requested — bypassing throttle")
true
}
ACTION_SYNC_LIBRARY -> {
Timber.i("[Amazon] Automatic sync requested")
true
}
val now = System.currentTimeMillis()
val shouldSync = ServiceSyncPolicy.shouldSyncForAction(
action = intent?.action,
manualSyncAction = ACTION_MANUAL_SYNC,
autoSyncAction = ACTION_SYNC_LIBRARY,
hasPerformedInitialSync = hasPerformedInitialSync,
lastSyncTimestamp = lastSyncTimestamp,
syncThrottleMillis = SYNC_THROTTLE_MILLIS,
now = now,
)

when (intent?.action) {
ACTION_MANUAL_SYNC -> Timber.i("[Amazon] Manual sync requested — bypassing throttle")
ACTION_SYNC_LIBRARY -> Timber.i("[Amazon] Automatic sync requested")
null -> {
// Service restarted by Android (START_STICKY)
val timeSinceLastSync = System.currentTimeMillis() - lastSyncTimestamp
val shouldResync = !hasPerformedInitialSync || timeSinceLastSync >= SYNC_THROTTLE_MILLIS
if (shouldResync) {
val timeSinceLastSync = now - lastSyncTimestamp
if (shouldSync) {
Timber.i("[Amazon] Service restarted by Android — performing sync (initial=$hasPerformedInitialSync, elapsed=${timeSinceLastSync}ms)")
} else {
Timber.d("[Amazon] Service restarted by Android — skipping sync (throttled)")
}
shouldResync
}
else -> {
Timber.d("[Amazon] Service started without sync action")
false
}
else -> Timber.d("[Amazon] Service started without sync action")
}

if (shouldSync) {
Expand Down
78 changes: 34 additions & 44 deletions app/src/main/java/app/gamenative/service/epic/EpicService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import app.gamenative.events.AndroidEvent
import app.gamenative.PluviaApp
import app.gamenative.utils.ContainerUtils
import app.gamenative.service.NotificationHelper
import app.gamenative.service.ServiceSyncPolicy
import dagger.hilt.android.AndroidEntryPoint
import java.io.File
import java.util.concurrent.ConcurrentHashMap
Expand Down Expand Up @@ -49,33 +50,31 @@ class EpicService : Service() {
fun start(context: Context) {

Timber.tag("EPIC").d("Starting service...")
// If already running, do nothing
if (isRunning) {
Timber.tag("EPIC").d("[EpicService] Service already running, skipping start")
return
}

// First-time start: always sync without throttle
if (!hasPerformedInitialSync) {
Timber.tag("EPIC").i("[EpicService] First-time start - starting service with initial sync")
val intent = Intent(context, EpicService::class.java)
intent.action = ACTION_SYNC_LIBRARY
context.startForegroundService(intent)
return
}

// Subsequent starts: always start service, but check throttle for sync
val now = System.currentTimeMillis()
val timeSinceLastSync = now - lastSyncTimestamp

val intent = Intent(context, EpicService::class.java)
if (timeSinceLastSync >= SYNC_THROTTLE_MILLIS) {
Timber.tag("EPIC").i("[EpicService] Starting service with automatic sync (throttle passed)")
val shouldSync = ServiceSyncPolicy.shouldSyncOnColdStart(
hasPerformedInitialSync = hasPerformedInitialSync,
lastSyncTimestamp = lastSyncTimestamp,
syncThrottleMillis = SYNC_THROTTLE_MILLIS,
)

if (shouldSync) {
intent.action = ACTION_SYNC_LIBRARY
if (!hasPerformedInitialSync) {
Timber.tag("EPIC").i("[EpicService] First-time start - starting service with initial sync")
} else {
Timber.tag("EPIC").i("[EpicService] Starting service with automatic sync (throttle passed)")
}
} else {
val remainingMinutes = (SYNC_THROTTLE_MILLIS - timeSinceLastSync) / 1000 / 60
val remainingMinutes = ServiceSyncPolicy.remainingThrottleMinutes(
lastSyncTimestamp = lastSyncTimestamp,
syncThrottleMillis = SYNC_THROTTLE_MILLIS,
)
Timber.tag("EPIC").i("Starting service without sync - throttled (${remainingMinutes}min remaining)")
// Start service without sync action
}
context.startForegroundService(intent)
}
Expand Down Expand Up @@ -505,38 +504,29 @@ class EpicService : Service() {
val notification = notificationHelper.createForegroundNotification("Connected")
startForeground(1, notification)

// Determine if we should sync based on the action
val shouldSync = when (intent?.action) {
ACTION_MANUAL_SYNC -> {
Timber.tag("EPIC").i("Manual sync requested - bypassing throttle")
true
}

ACTION_SYNC_LIBRARY -> {
Timber.tag("EPIC").i("Automatic sync requested")
true
}

val now = System.currentTimeMillis()
val shouldSync = ServiceSyncPolicy.shouldSyncForAction(
action = intent?.action,
manualSyncAction = ACTION_MANUAL_SYNC,
autoSyncAction = ACTION_SYNC_LIBRARY,
hasPerformedInitialSync = hasPerformedInitialSync,
lastSyncTimestamp = lastSyncTimestamp,
syncThrottleMillis = SYNC_THROTTLE_MILLIS,
now = now,
)

when (intent?.action) {
ACTION_MANUAL_SYNC -> Timber.tag("EPIC").i("Manual sync requested - bypassing throttle")
ACTION_SYNC_LIBRARY -> Timber.tag("EPIC").i("Automatic sync requested")
null -> {
// Service restarted by Android with null intent (START_STICKY behavior)
// Only sync if we haven't done initial sync yet, or if it's been a while
val timeSinceLastSync = System.currentTimeMillis() - lastSyncTimestamp
val shouldResync = !hasPerformedInitialSync || timeSinceLastSync >= SYNC_THROTTLE_MILLIS

if (shouldResync) {
val timeSinceLastSync = now - lastSyncTimestamp
if (shouldSync) {
Timber.tag("EPIC").i("Service restarted by Android - performing sync (hasPerformedInitialSync=$hasPerformedInitialSync, timeSinceLastSync=${timeSinceLastSync}ms)")
true
} else {
Timber.tag("EPIC").d("Service restarted by Android - skipping sync (throttled)")
false
}
}

else -> {
// Service started without sync action (e.g., just to keep it alive)
Timber.tag("EPIC").d(" Service started without sync action")
false
}
else -> Timber.tag("EPIC").d(" Service started without sync action")
}

// Start background library sync if requested
Expand Down
78 changes: 34 additions & 44 deletions app/src/main/java/app/gamenative/service/gog/GOGService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import app.gamenative.data.LibraryItem
import app.gamenative.events.AndroidEvent
import app.gamenative.PluviaApp
import app.gamenative.service.NotificationHelper
import app.gamenative.service.ServiceSyncPolicy
import app.gamenative.utils.ContainerUtils
import dagger.hilt.android.AndroidEntryPoint
import java.io.File
Expand Down Expand Up @@ -56,33 +57,31 @@ class GOGService : Service() {
get() = instance != null

fun start(context: Context) {
// If already running, do nothing
if (isRunning) {
Timber.d("[GOGService] Service already running, skipping start")
return
}

// First-time start: always sync without throttle
if (!hasPerformedInitialSync) {
Timber.i("[GOGService] First-time start - starting service with initial sync")
val intent = Intent(context, GOGService::class.java)
intent.action = ACTION_SYNC_LIBRARY
context.startForegroundService(intent)
return
}

// Subsequent starts: always start service, but check throttle for sync
val now = System.currentTimeMillis()
val timeSinceLastSync = now - lastSyncTimestamp

val intent = Intent(context, GOGService::class.java)
if (timeSinceLastSync >= SYNC_THROTTLE_MILLIS) {
Timber.i("[GOGService] Starting service with automatic sync (throttle passed)")
val shouldSync = ServiceSyncPolicy.shouldSyncOnColdStart(
hasPerformedInitialSync = hasPerformedInitialSync,
lastSyncTimestamp = lastSyncTimestamp,
syncThrottleMillis = SYNC_THROTTLE_MILLIS,
)

if (shouldSync) {
intent.action = ACTION_SYNC_LIBRARY
if (!hasPerformedInitialSync) {
Timber.i("[GOGService] First-time start - starting service with initial sync")
} else {
Timber.i("[GOGService] Starting service with automatic sync (throttle passed)")
}
} else {
val remainingMinutes = (SYNC_THROTTLE_MILLIS - timeSinceLastSync) / 1000 / 60
val remainingMinutes = ServiceSyncPolicy.remainingThrottleMinutes(
lastSyncTimestamp = lastSyncTimestamp,
syncThrottleMillis = SYNC_THROTTLE_MILLIS,
)
Timber.d("[GOGService] Starting service without sync - throttled (${remainingMinutes}min remaining)")
// Start service without sync action
}
context.startForegroundService(intent)
}
Expand Down Expand Up @@ -588,38 +587,29 @@ class GOGService : Service() {
val notification = notificationHelper.createForegroundNotification("Connected")
startForeground(1, notification)

// Determine if we should sync based on the action
val shouldSync = when (intent?.action) {
ACTION_MANUAL_SYNC -> {
Timber.i("[GOGService] Manual sync requested - bypassing throttle")
true
}

ACTION_SYNC_LIBRARY -> {
Timber.i("[GOGService] Automatic sync requested")
true
}

val now = System.currentTimeMillis()
val shouldSync = ServiceSyncPolicy.shouldSyncForAction(
action = intent?.action,
manualSyncAction = ACTION_MANUAL_SYNC,
autoSyncAction = ACTION_SYNC_LIBRARY,
hasPerformedInitialSync = hasPerformedInitialSync,
lastSyncTimestamp = lastSyncTimestamp,
syncThrottleMillis = SYNC_THROTTLE_MILLIS,
now = now,
)

when (intent?.action) {
ACTION_MANUAL_SYNC -> Timber.i("[GOGService] Manual sync requested - bypassing throttle")
ACTION_SYNC_LIBRARY -> Timber.i("[GOGService] Automatic sync requested")
null -> {
// Service restarted by Android with null intent (START_STICKY behavior)
// Only sync if we haven't done initial sync yet, or if it's been a while
val timeSinceLastSync = System.currentTimeMillis() - lastSyncTimestamp
val shouldResync = !hasPerformedInitialSync || timeSinceLastSync >= SYNC_THROTTLE_MILLIS

if (shouldResync) {
val timeSinceLastSync = now - lastSyncTimestamp
if (shouldSync) {
Timber.i("[GOGService] Service restarted by Android - performing sync (hasPerformedInitialSync=$hasPerformedInitialSync, timeSinceLastSync=${timeSinceLastSync}ms)")
true
} else {
Timber.d("[GOGService] Service restarted by Android - skipping sync (throttled)")
false
}
}

else -> {
// Service started without sync action (e.g., just to keep it alive)
Timber.d("[GOGService] Service started without sync action")
false
}
else -> Timber.d("[GOGService] Service started without sync action")
}

// Start background library sync if requested
Expand Down