Skip to content

Commit

Permalink
WIP Add sync (#259)
Browse files Browse the repository at this point in the history
Sync., emdiator WIP
  • Loading branch information
illarionov authored Jan 28, 2024
1 parent 627914b commit 2ca9951
Show file tree
Hide file tree
Showing 44 changed files with 994 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import ru.pixnews.domain.model.game.game.beyondGoodEvil2
import ru.pixnews.domain.model.game.game.gta6
import ru.pixnews.domain.model.game.game.hytale
import ru.pixnews.domain.model.game.game.sims5
import ru.pixnews.feature.calendar.data.domain.upcoming.UpcomingRelease
import ru.pixnews.feature.calendar.data.domain.upcoming.ObserveUpcomingReleasesByDateUseCase.UpcomingRelease
import ru.pixnews.feature.calendar.test.constants.UpcomingReleaseGroupId
import ru.pixnews.feature.calendar.test.constants.toGroupId
import ru.pixnews.feature.calendar.test.element.CalendarHeaderElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import ru.pixnews.domain.model.game.GameField
import ru.pixnews.feature.calendar.data.domain.upcoming.ObserveUpcomingReleasesByDateUseCase
import ru.pixnews.feature.calendar.data.domain.upcoming.UpcomingRelease
import ru.pixnews.feature.calendar.data.domain.upcoming.ObserveUpcomingReleasesByDateUseCase.UpcomingRelease
import ru.pixnews.feature.calendar.domain.upcoming.DefaultObserveUpcomingReleasesByDateUseCase
import ru.pixnews.foundation.di.base.scopes.AppScope
import javax.inject.Inject
Expand Down
2 changes: 1 addition & 1 deletion config/diktat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

- name: BACKTICKS_PROHIBITED
enabled: true
ignoreAnnotated: [ Nested, ParameterizedTest ]
ignoreAnnotated: [ Nested, ParameterizedTest, TestFactory ]

- name: COMMENTED_BY_KDOC
enabled: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package ru.pixnews.feature.calendar.data
import kotlinx.datetime.Instant
import ru.pixnews.domain.model.game.Game
import ru.pixnews.domain.model.game.GameField
import ru.pixnews.feature.calendar.data.model.GameModeIgdbDto
import ru.pixnews.library.functional.network.NetworkResult

public interface IgdbDataSource {
Expand All @@ -17,4 +18,10 @@ public interface IgdbDataSource {
offset: Int = 0,
limit: Int = 100,
): NetworkResult<List<Game>>

public suspend fun getGameModes(
updatedLaterThan: Instant?,
offset: Int = 0,
limit: Int = 100,
): NetworkResult<List<GameModeIgdbDto>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ package ru.pixnews.feature.calendar.data.domain.upcoming

import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import ru.pixnews.domain.model.UpcomingReleaseTimeCategory
import ru.pixnews.domain.model.game.Game
import ru.pixnews.domain.model.game.GameField

public interface ObserveUpcomingReleasesByDateUseCase {
public fun createUpcomingReleasesObservable(requiredFields: Set<GameField>): Flow<PagingData<UpcomingRelease>>

public data class UpcomingRelease(
val game: Game,
val group: UpcomingReleaseTimeCategory,
)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2024, the Pixnews project authors and contributors. Please see the AUTHORS file for details.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/

package ru.pixnews.feature.calendar.data.model

import kotlinx.datetime.Instant
import ru.pixnews.domain.model.game.GameMode

public data class GameModeIgdbDto(
val mode: GameMode,
val igdbSlug: String,
val updatedAt: Instant,
)
3 changes: 3 additions & 0 deletions feature/calendar/data/calendar-data.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ android {
dependencies {
api(projects.feature.calendar.dataPublic)
implementation(projects.foundation.appconfig)
implementation(projects.foundation.database)
implementation(projects.foundation.coroutines)
implementation(projects.foundation.di.base)
implementation(projects.foundation.domainModel)
implementation(projects.foundation.network.public)
implementation(projects.library.functional)
implementation(projects.library.kotlinUtils)
implementation(projects.library.coroutines)

api(libs.inject)
implementation(libs.kermit)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2023, the Pixnews project authors and contributors. Please see the AUTHORS file for details.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/

package ru.pixnews.feature.calendar.data

import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.LoadType.APPEND
import androidx.paging.LoadType.PREPEND
import androidx.paging.LoadType.REFRESH
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.paging.RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
import androidx.paging.RemoteMediator.InitializeAction.SKIP_INITIAL_REFRESH
import co.touchlab.kermit.Logger
import kotlinx.datetime.Instant
import ru.pixnews.domain.model.game.Game
import ru.pixnews.domain.model.game.GameField
import ru.pixnews.feature.calendar.data.repository.upcoming.IgdbPagingSourceKey
import ru.pixnews.feature.calendar.data.sync.SyncService

@OptIn(ExperimentalPagingApi::class)
internal class DefaultRemoteMediator(
private val startDate: Instant,
private val requiredFields: Set<GameField>,
private val syncService: SyncService,
logger: Logger = Logger,
) : RemoteMediator<IgdbPagingSourceKey, Game>() {
private val logger = logger.withTag("DefaultRemoteMediator")

override suspend fun initialize(): InitializeAction {
return if (syncService.isDataStale()) {
LAUNCH_INITIAL_REFRESH
} else {
SKIP_INITIAL_REFRESH
}
}

// TODO: tests
override suspend fun load(
loadType: LoadType,
state: PagingState<IgdbPagingSourceKey, Game>,
): MediatorResult {
logger.d { "load() with loadType: $loadType" }
if (loadType == PREPEND) {
logger.d { "PREPEND: nothing to prepend (we refresh always from first page)" }
return MediatorResult.Success(endOfPaginationReached = true)
}
if (loadType == APPEND && state.lastItemOrNull() == null) {
logger.d { "APPEND: Last item is null. No more items after initial REFRESH, nothing to load" }
return MediatorResult.Success(endOfPaginationReached = true)
}

val loadKey = if (loadType == APPEND) {
state.lastItemOrNull()!!.id
} else {
null
}
return try {
syncService.syncGameModes(false, true)

val result = syncService.syncGames(
fullRefresh = loadType == REFRESH,
startDate = startDate,
minimumRequiredFields = requiredFields,
earlierThanGameId = loadKey,
)
MediatorResult.Success(endOfPaginationReached = !result.hasMorePagesToLoad)
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
MediatorResult.Error(e)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2024, the Pixnews project authors and contributors. Please see the AUTHORS file for details.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/

package ru.pixnews.feature.calendar.data.repository.status

import com.squareup.anvil.annotations.ContributesBinding
import dagger.Reusable
import kotlinx.datetime.Instant
import ru.pixnews.foundation.database.PixnewsDatabase
import ru.pixnews.foundation.database.dao.IgdbSyncStatusDao
import ru.pixnews.foundation.database.entity.sync.IgdbSyncStatusEntity
import ru.pixnews.foundation.di.base.scopes.AppScope
import javax.inject.Inject

@ContributesBinding(AppScope::class, boundType = IgdbSyncStatusRepository::class)
@Reusable
public class DefaultIgdbSyncStatusRepository(
private val dao: IgdbSyncStatusDao,
) : IgdbSyncStatusRepository {
@Inject
public constructor(
database: PixnewsDatabase,
) : this(dao = database.igdbSyncStatusDao())

override suspend fun getInstantKey(key: String): Instant {
val epoch = dao.get(key)?.toLongOrNull() ?: 0
return Instant.fromEpochSeconds(epoch)
}

override suspend fun setInstantKey(key: String, value: Instant) {
dao.set(IgdbSyncStatusEntity(key, value.epochSeconds.toString()))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2024, the Pixnews project authors and contributors. Please see the AUTHORS file for details.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/

package ru.pixnews.feature.calendar.data.repository.status

import kotlinx.datetime.Instant

public const val IGDB_GAME_MODE_MAX_UPDATED_AT_KEY: String = "igdb_game_mode_max_updated_at"
public const val IGDB_GAME_MODE_LAST_SYNC_KEY: String = "igdb_game_mode_last_sync"

public interface IgdbSyncStatusRepository {
public suspend fun getInstantKey(key: String): Instant
public suspend fun setInstantKey(key: String, value: Instant)
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,43 @@
/*
* Copyright (c) 2023, the Pixnews project authors and contributors. Please see the AUTHORS file for details.
* Copyright (c) 2024, the Pixnews project authors and contributors. Please see the AUTHORS file for details.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/

package ru.pixnews.feature.calendar.data
package ru.pixnews.feature.calendar.data.repository.upcoming

import androidx.paging.PagingState
import co.touchlab.kermit.Logger
import com.squareup.anvil.annotations.ContributesBinding
import kotlinx.datetime.Instant
import ru.pixnews.domain.model.game.Game
import ru.pixnews.domain.model.game.GameField
import ru.pixnews.feature.calendar.domain.upcoming.IgdbPagingSource
import ru.pixnews.feature.calendar.data.IgdbDataSource
import ru.pixnews.foundation.di.base.scopes.AppScope
import ru.pixnews.library.functional.network.NetworkRequestFailureException
import javax.inject.Inject

@ContributesBinding(AppScope::class)
public class DefaultUpcomingReleasePagingSourceFactory @Inject constructor(
public class DefaultUpcomingReleaseRepository @Inject constructor(
private val igdbDataSource: IgdbDataSource,
) : IgdbPagingSource.Factory {
override fun create(
private val logger: Logger = Logger,
) : UpcomingReleaseRepository {
override fun createUpcomingReleasesPagingSource(
startDate: Instant,
requiredFields: Set<GameField>,
): IgdbPagingSource = UpcomingReleasePagingSource(startDate, requiredFields, igdbDataSource)
): UpcomingReleasesPagingSource = UpcomingReleasePagingSource(startDate, requiredFields, igdbDataSource, logger)

private class UpcomingReleasePagingSource(
private val startDate: Instant,
private val requiredFields: Set<GameField>,
private val igdbDataSource: IgdbDataSource,
) : IgdbPagingSource() {
logger: Logger = Logger,
) : UpcomingReleasesPagingSource() {
private val logger = logger.withTag("DefaultUpcomingReleasePagingSourceFactory")

override fun getRefreshKey(
state: PagingState<IgdbPagingSourceKey, Game>,
): IgdbPagingSourceKey? {
logger.i { "getRefreshKey(${state.anchorPosition})" }
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
?: return@let null
Expand All @@ -54,6 +60,8 @@ public class DefaultUpcomingReleasePagingSourceFactory @Inject constructor(
): LoadResult<IgdbPagingSourceKey, Game> {
val offset = params.key?.offset ?: 0
val limit = params.loadSize
logger.i { "load($offset / $limit)" }

return igdbDataSource.fetchUpcomingReleases(
startDate = startDate,
requiredFields = requiredFields,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2024, the Pixnews project authors and contributors. Please see the AUTHORS file for details.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/

package ru.pixnews.feature.calendar.data.repository.upcoming

@JvmInline
public value class IgdbPagingSourceKey(
public val offset: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2024, the Pixnews project authors and contributors. Please see the AUTHORS file for details.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/

package ru.pixnews.feature.calendar.data.repository.upcoming

import kotlinx.datetime.Instant
import ru.pixnews.domain.model.game.GameField

public interface UpcomingReleaseRepository {
public fun createUpcomingReleasesPagingSource(
startDate: Instant,
requiredFields: Set<GameField>,
): UpcomingReleasesPagingSource
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2024, the Pixnews project authors and contributors. Please see the AUTHORS file for details.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/

package ru.pixnews.feature.calendar.data.repository.upcoming

import androidx.paging.PagingSource
import ru.pixnews.domain.model.game.Game

public abstract class UpcomingReleasesPagingSource : PagingSource<IgdbPagingSourceKey, Game>()
Loading

0 comments on commit 2ca9951

Please sign in to comment.