🔄 Persistent Retry Queue

KNETPersistentRetryQueue to kolejka retry z persystencją, która przetrwa restart aplikacji. Idealna dla offline-first aplikacji.

💡 Kiedy używać?
  • Offline-first aplikacje
  • Krytyczne operacje (płatności, zamówienia)
  • Background sync po przywróceniu połączenia
  • Gwarancja dostarczenia requestów

📦 Import

import rip.nerd.kitsunenet.util.KNETPersistentRetryQueue
import rip.nerd.kitsunenet.util.KNETPersistentRetryQueue.Priority

🚀 Szybki start

val retryQueue = KNETPersistentRetryQueue(context)

// Dodaj request do kolejki po błędzie
try {
    client.request(request)
} catch (e: Exception) {
    retryQueue.enqueue(request)
}

// Retry wszystkich
val results = retryQueue.retryAll(client)
println("Success: ${results.successCount}")
println("Failed: ${results.failedCount}")

⭐ Priorytetyzacja

// Priorytety (od najwyższego)
Priority.CRITICAL  // Najpierw - płatności, auth
Priority.HIGH      // Ważne dane użytkownika
Priority.NORMAL    // Standardowe (default)
Priority.LOW       // Może poczekać - analytics, logs

// Użycie
retryQueue.enqueue(paymentRequest, Priority.CRITICAL)
retryQueue.enqueue(syncRequest, Priority.NORMAL)
retryQueue.enqueue(analyticsRequest, Priority.LOW)

// Kolejność retry: CRITICAL → HIGH → NORMAL → LOW

🔧 Konfiguracja

val retryQueue = KNETPersistentRetryQueue(
    context = context,
    maxRetries = 5,           // Max prób na request
    maxQueueSize = 100,       // Max requestów w kolejce
    retryDelayMs = 5000,      // Delay między retry
    exponentialBackoff = true // Exponential delay
)

⚡ Auto-Retry

// Start automatycznego retry w tle
retryQueue.startAutoRetry(
    client = client,
    intervalMs = 60_000,  // Co minutę

    onSuccess = { request, response ->
        Log.d("Retry", "Success: ${request.request.url}")
    },

    onFailure = { request, error, attempt ->
        Log.w("Retry", "Failed (attempt $attempt): ${error?.message}")
    },

    onBatchComplete = { result ->
        Log.d("Retry", "Batch: ${result.successCount}/${result.retriedCount}")
    }
)

// Sprawdź status
if (retryQueue.isAutoRetryRunning()) {
    println("Auto-retry is active")
}

// Zatrzymaj
retryQueue.stopAutoRetry()

📤 Ręczne Retry

// Retry z progress callback
val results = retryQueue.retryAll(client) { completed, total ->
    updateProgress(completed, total)
}

// Wyniki
results.results.forEach { result ->
    if (result.success) {
        println("✓ ${result.request.request.url}")
    } else {
        println("✗ ${result.request.request.url}: ${result.error?.message}")
    }
}

println("Duration: ${results.durationMs}ms")

📝 Metadata

// Dodaj metadata do requestu
retryQueue.enqueue(
    request = orderRequest,
    priority = Priority.HIGH,
    metadata = mapOf(
        "order_id" to orderId,
        "user_id" to userId,
        "action" to "create_order"
    )
)

// Pobierz request z metadata
val queued = retryQueue.getById(requestId)
val orderId = queued?.metadata?.get("order_id")

📊 Statystyki

val stats = retryQueue.getStats()

println("Queue size: ${stats.queueSize}")
println("Total enqueued: ${stats.totalEnqueued}")
println("Total retried: ${stats.totalRetried}")
println("Total succeeded: ${stats.totalSucceeded}")
println("Total failed: ${stats.totalFailed}")

// Wiek najstarszego requestu
stats.oldestRequestAge?.let { age ->
    println("Oldest: ${age / 1000}s ago")
}

// Podział po priorytetach
stats.byPriority.forEach { (priority, count) ->
    println("$priority: $count")
}

💡 Praktyczne przykłady

Offline-first sync

class OfflineSyncManager(
    context: Context,
    private val client: KNETClient,
    private val networkObserver: KNETNetworkObserver
) {
    private val retryQueue = KNETPersistentRetryQueue(
        context = context,
        maxRetries = 10,
        exponentialBackoff = true
    )

    init {
        // Retry gdy połączenie wraca
        networkObserver.onConnectionRestored {
            retryQueue.retryAll(client)
        }
    }

    suspend fun sync(data: SyncData) {
        val request = KNETRequest.post(
            url = "https://api.example.com/sync",
            data = data.toMap()
        )

        try {
            client.request(request)
        } catch (e: Exception) {
            // Zapisz do retry
            retryQueue.enqueue(request, Priority.HIGH)
        }
    }
}

Krytyczne operacje

class PaymentService(
    context: Context,
    private val client: KNETClient
) {
    private val retryQueue = KNETPersistentRetryQueue(
        context = context,
        maxRetries = 5
    )

    suspend fun processPayment(payment: Payment): PaymentResult {
        val request = KNETRequest.post(
            url = "https://api.example.com/payments",
            data = payment.toMap()
        )

        return try {
            val response = client.request(request)
            PaymentResult.Success(response.json())
        } catch (e: Exception) {
            // Zawsze zachowaj do retry
            val id = retryQueue.enqueue(
                request = request,
                priority = Priority.CRITICAL,
                metadata = mapOf(
                    "payment_id" to payment.id,
                    "amount" to payment.amount.toString()
                )
            )
            PaymentResult.Queued(id)
        }
    }
}

Background Worker

class RetryWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    private val retryQueue = KNETPersistentRetryQueue(applicationContext)
    private val client = KNETClient()

    override suspend fun doWork(): Result {
        val result = retryQueue.retryAll(client)

        return if (result.failedCount == 0) {
            Result.success()
        } else {
            // Reschedule jeśli są błędy
            Result.retry()
        }
    }
}

// Zaplanuj WorkManager
val request = PeriodicWorkRequestBuilder<RetryWorker>(
    15, TimeUnit.MINUTES
).setConstraints(
    Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build()
).build()

WorkManager.getInstance(context).enqueue(request)

🔗 API Reference

KNETPersistentRetryQueue

MetodaOpis
enqueue(request, priority, metadata)Dodaje do kolejki
retryAll(client, onProgress)Retry wszystkich
startAutoRetry(...)Startuje auto-retry
stopAutoRetry()Zatrzymuje auto-retry
getQueue()Lista requestów
getById(id)Pobiera po ID
remove(id)Usuwa z kolejki
clear()Czyści kolejkę
size()Rozmiar kolejki
getStats()Statystyki
shutdown()Zamyka zasoby

📚 Zobacz też