-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* rewrited paging with jetpack paging 3; * db schema changes.
- Loading branch information
1 parent
d6489b4
commit ee4d14b
Showing
29 changed files
with
908 additions
and
364 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
app/src/main/kotlin/me/nanova/subspace/data/TorrentRemoteMediator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package me.nanova.subspace.data | ||
|
||
import androidx.paging.ExperimentalPagingApi | ||
import androidx.paging.LoadType | ||
import androidx.paging.PagingState | ||
import androidx.paging.RemoteMediator | ||
import androidx.room.withTransaction | ||
import me.nanova.subspace.data.api.QTApiService | ||
import me.nanova.subspace.data.db.AppDatabase | ||
import me.nanova.subspace.domain.model.QTListParams | ||
import me.nanova.subspace.domain.model.RemoteKeys | ||
import me.nanova.subspace.domain.model.Torrent | ||
import me.nanova.subspace.domain.model.toEntity | ||
|
||
@OptIn(ExperimentalPagingApi::class) | ||
class TorrentRemoteMediator( | ||
private val currentAccountId: Long, | ||
private val query: QTListParams, | ||
private val database: AppDatabase, | ||
private val networkService: QTApiService | ||
) : RemoteMediator<Int, Torrent>() { | ||
|
||
private val torrentDao = database.torrentDao() | ||
private val remoteKeyDao = database.remoteKeyDao() | ||
|
||
override suspend fun initialize(): InitializeAction { | ||
// return InitializeAction.SKIP_INITIAL_REFRESH | ||
return InitializeAction.LAUNCH_INITIAL_REFRESH | ||
} | ||
|
||
override suspend fun load( | ||
loadType: LoadType, | ||
state: PagingState<Int, Torrent> | ||
): MediatorResult { | ||
return try { | ||
val offset = when (loadType) { | ||
LoadType.REFRESH -> 0 | ||
LoadType.APPEND -> { | ||
val remoteKeys = state.pages | ||
.lastOrNull() { it.data.isNotEmpty() } // Find the first page with items | ||
?.data?.lastOrNull() // Get the first item in that page | ||
?.let { remoteKeyDao.remoteKeysItemId(it.id) } | ||
remoteKeys?.nextOffset | ||
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) | ||
} | ||
LoadType.PREPEND -> { | ||
val remoteKeys = state.pages | ||
.firstOrNull { it.data.isNotEmpty() } | ||
?.data?.firstOrNull() | ||
?.let { remoteKeyDao.remoteKeysItemId(it.id) } | ||
remoteKeys?.prevOffset | ||
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) | ||
} | ||
} | ||
|
||
// fetch api | ||
val response = networkService.list( | ||
query.copy(offset = offset, limit = state.config.pageSize).toMap() | ||
) | ||
val endOfPaginationReached = response.size < state.config.pageSize | ||
|
||
// update db | ||
database.withTransaction { | ||
if (loadType == LoadType.REFRESH) { | ||
remoteKeyDao.clearRemoteKeys(currentAccountId) | ||
torrentDao.clearAll(currentAccountId) | ||
} | ||
|
||
val entities = response.map { it.toEntity(currentAccountId) } | ||
torrentDao.insertAll(entities) | ||
val prevOffset = if (offset == 0) null else offset - state.config.pageSize | ||
val nextOffset = | ||
if (endOfPaginationReached) null else offset + state.config.pageSize | ||
val keys = entities.map { | ||
RemoteKeys( | ||
torrentId = it.id, | ||
prevOffset = prevOffset, | ||
nextOffset = nextOffset, | ||
accountId = currentAccountId | ||
) | ||
} | ||
remoteKeyDao.insertAll(keys) | ||
} | ||
|
||
MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) | ||
} catch (e: Exception) { | ||
// might display db data when network is unavailable, but not sure the user case, let decide in future | ||
MediatorResult.Error(e) | ||
} | ||
} | ||
} |
60 changes: 34 additions & 26 deletions
60
app/src/main/kotlin/me/nanova/subspace/data/TorrentRepoImpl.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,55 @@ | ||
package me.nanova.subspace.data | ||
|
||
import kotlinx.coroutines.flow.map | ||
import androidx.paging.ExperimentalPagingApi | ||
import androidx.paging.Pager | ||
import androidx.paging.PagingConfig | ||
import androidx.paging.PagingData | ||
import androidx.sqlite.db.SimpleSQLiteQuery | ||
import androidx.sqlite.db.SupportSQLiteQuery | ||
import kotlinx.coroutines.flow.Flow | ||
import me.nanova.subspace.data.api.QTApiService | ||
import me.nanova.subspace.data.db.AppDatabase | ||
import me.nanova.subspace.data.db.TorrentDao | ||
import me.nanova.subspace.data.db.TorrentDao.Companion.buildQuery | ||
import me.nanova.subspace.domain.model.Account | ||
import me.nanova.subspace.domain.model.QTFilterState | ||
import me.nanova.subspace.domain.model.QTListParams | ||
import me.nanova.subspace.domain.model.Torrent | ||
import me.nanova.subspace.domain.model.toEntity | ||
import me.nanova.subspace.domain.repo.TorrentRepo | ||
import javax.inject.Inject | ||
import javax.inject.Provider | ||
|
||
class TorrentRepoImpl @Inject constructor( | ||
private val database: AppDatabase, | ||
private val torrentDao: TorrentDao, | ||
private val storage: Storage, | ||
private val apiService: Provider<QTApiService> | ||
) : TorrentRepo { | ||
override suspend fun apiVersion() = apiService.get().version() | ||
|
||
// override fun torrents() = | ||
// torrentDao.getAll().map { model -> model.map { it.toModel() } } | ||
|
||
|
||
override suspend fun refresh(params: Map<String, String?>) { | ||
val list = apiService.get().getTorrents(params) | ||
storage.currentAccountId.collect { id -> | ||
val copy = list.map { | ||
it.toEntity(id ?: throw RuntimeException("no current account")) | ||
// override suspend fun apiVersion() = apiService.get().version() | ||
|
||
@OptIn(ExperimentalPagingApi::class) | ||
override fun torrents(account: Account, filter: QTListParams): Flow<PagingData<Torrent>> { | ||
return Pager( | ||
config = PagingConfig( | ||
pageSize = PAGE_SIZE, | ||
prefetchDistance = 1, | ||
enablePlaceholders = true, | ||
), | ||
remoteMediator = TorrentRemoteMediator( | ||
account.id, | ||
filter, | ||
database, | ||
apiService.get() | ||
), | ||
pagingSourceFactory = { | ||
torrentDao.pagingSource(buildQuery(account.id, filter)) | ||
} | ||
torrentDao.insertAll(copy) | ||
} | ||
).flow | ||
} | ||
|
||
override suspend fun fetch(params: QTListParams): List<Torrent> { | ||
val list = apiService.get().list(params.toMap()) | ||
|
||
// storage.currentAccountId.collect { id -> | ||
// val copy = list.map { | ||
// it.toEntity(id ?: throw RuntimeException("no current account")) | ||
// } | ||
// torrentDao.insertAll(copy) | ||
// } | ||
return list | ||
companion object { | ||
const val PAGE_SIZE = 20 | ||
} | ||
|
||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 72 additions & 3 deletions
75
app/src/main/kotlin/me/nanova/subspace/data/db/AppDatabase.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,86 @@ | ||
package me.nanova.subspace.data.db | ||
|
||
import android.util.Log | ||
import androidx.room.Database | ||
import androidx.room.RoomDatabase | ||
import androidx.room.migration.Migration | ||
import androidx.sqlite.db.SupportSQLiteDatabase | ||
import me.nanova.subspace.domain.model.Account | ||
import me.nanova.subspace.domain.model.TorrentDB | ||
import me.nanova.subspace.domain.model.RemoteKeys | ||
import me.nanova.subspace.domain.model.TorrentEntity | ||
|
||
@Database( | ||
entities = [ | ||
Account::class, | ||
TorrentDB::class | ||
], version = 1 | ||
TorrentEntity::class, | ||
RemoteKeys::class | ||
], version = 2 | ||
) | ||
abstract class AppDatabase : RoomDatabase() { | ||
abstract fun torrentDao(): TorrentDao | ||
abstract fun remoteKeyDao(): RemoteKeyDao | ||
abstract fun accountDao(): AccountDao | ||
|
||
companion object { | ||
const val DATABASE_NAME = "subspace.db" | ||
|
||
val MIGRATION_1_2 = object : Migration(1, 2) { | ||
override fun migrate(db: SupportSQLiteDatabase) { | ||
try { | ||
db.execSQL("ALTER TABLE Account ADD COLUMN use_lan_switch INTEGER NOT NULL DEFAULT 0;") | ||
db.execSQL("ALTER TABLE Account ADD COLUMN lan_url TEXT NOT NULL DEFAULT '';") | ||
db.execSQL("ALTER TABLE Account ADD COLUMN lan_ssid TEXT NOT NULL DEFAULT '';") | ||
// sqlite table name is case-insensitive | ||
db.execSQL("ALTER TABLE Account RENAME TO tmp_account;") | ||
db.execSQL("ALTER TABLE tmp_account RENAME TO account;") | ||
|
||
// previous version didn't use this table | ||
db.execSQL("DROP TABLE torrent;") | ||
db.execSQL( | ||
""" | ||
CREATE TABLE torrent ( | ||
id TEXT PRIMARY KEY NOT NULL, | ||
hash TEXT NOT NULL, | ||
account_id INTEGER NOT NULL, | ||
name TEXT NOT NULL, | ||
added_on INTEGER NOT NULL, | ||
size INTEGER NOT NULL, | ||
downloaded INTEGER NOT NULL, | ||
uploaded INTEGER NOT NULL, | ||
progress REAL NOT NULL, | ||
eta INTEGER NOT NULL, | ||
state TEXT NOT NULL, | ||
category TEXT, | ||
tags TEXT, | ||
dlspeed INTEGER NOT NULL, | ||
upspeed INTEGER NOT NULL, | ||
ratio REAL NOT NULL, | ||
leechs INTEGER NOT NULL, | ||
seeds INTEGER NOT NULL, | ||
priority INTEGER NOT NULL, | ||
last_updated INTEGER NOT NULL | ||
); | ||
""" | ||
) | ||
db.execSQL("CREATE INDEX index_torrent_hash ON torrent (hash);") | ||
db.execSQL("CREATE INDEX index_torrent_account_id ON torrent (account_id);") | ||
|
||
db.execSQL( | ||
""" | ||
CREATE TABLE remote_keys ( | ||
torrent_id TEXT PRIMARY KEY NOT NULL, | ||
account_id INTEGER NOT NULL, | ||
prev_offset INTEGER, | ||
next_offset INTEGER | ||
); | ||
""" | ||
) | ||
db.execSQL("CREATE INDEX index_remote_keys_account_id ON remote_keys (account_id);") | ||
} catch (e: Exception) { | ||
Log.e("AppDatabase", "DB migration error: $e") | ||
} | ||
} | ||
} | ||
} | ||
|
||
} |
19 changes: 19 additions & 0 deletions
19
app/src/main/kotlin/me/nanova/subspace/data/db/RemoteKeyDao.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package me.nanova.subspace.data.db | ||
|
||
import androidx.room.Dao | ||
import androidx.room.Insert | ||
import androidx.room.OnConflictStrategy | ||
import androidx.room.Query | ||
import me.nanova.subspace.domain.model.RemoteKeys | ||
|
||
@Dao | ||
interface RemoteKeyDao { | ||
@Insert(onConflict = OnConflictStrategy.REPLACE) | ||
suspend fun insertAll(remoteKey: List<RemoteKeys>) | ||
|
||
@Query("SELECT * FROM remote_keys WHERE torrent_id = :id") | ||
suspend fun remoteKeysItemId(id: String): RemoteKeys? | ||
|
||
@Query("DELETE FROM remote_keys WHERE account_id = :accountId") | ||
suspend fun clearRemoteKeys(accountId: Long) | ||
} |
Oops, something went wrong.