v0.6.4
This commit is contained in:
1
core/data/.gitignore
vendored
Normal file
1
core/data/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
23
core/data/build.gradle.kts
Normal file
23
core/data/build.gradle.kts
Normal file
@@ -0,0 +1,23 @@
|
||||
plugins {
|
||||
alias(libs.plugins.looker.android.library)
|
||||
alias(libs.plugins.looker.hilt.work)
|
||||
alias(libs.plugins.looker.lint)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.looker.core.data"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
modules(
|
||||
Modules.coreCommon,
|
||||
Modules.coreDatabase,
|
||||
Modules.coreDatastore,
|
||||
Modules.coreDI,
|
||||
Modules.coreDomain,
|
||||
Modules.coreNetwork,
|
||||
Modules.sync,
|
||||
)
|
||||
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
}
|
||||
5
core/data/src/main/AndroidManifest.xml
Normal file
5
core/data/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.looker.core.data.di
|
||||
|
||||
import com.looker.core.domain.AppRepository
|
||||
import com.looker.core.domain.RepoRepository
|
||||
import com.looker.core.data.repository.OfflineFirstAppRepository
|
||||
import com.looker.core.data.repository.OfflineFirstRepoRepository
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface DataModule {
|
||||
|
||||
@Binds
|
||||
fun bindsAppRepository(
|
||||
appRepository: OfflineFirstAppRepository
|
||||
): AppRepository
|
||||
|
||||
@Binds
|
||||
fun bindsRepoRepository(
|
||||
repoRepository: OfflineFirstRepoRepository
|
||||
): RepoRepository
|
||||
|
||||
}
|
||||
132
core/data/src/main/java/com/looker/core/data/fdroid/Mapper.kt
Normal file
132
core/data/src/main/java/com/looker/core/data/fdroid/Mapper.kt
Normal file
@@ -0,0 +1,132 @@
|
||||
package com.looker.core.data.fdroid
|
||||
|
||||
import com.looker.core.database.model.AntiFeatureEntity
|
||||
import com.looker.core.database.model.AppEntity
|
||||
import com.looker.core.database.model.CategoryEntity
|
||||
import com.looker.core.database.model.PackageEntity
|
||||
import com.looker.core.database.model.PermissionEntity
|
||||
import com.looker.core.database.model.RepoEntity
|
||||
import com.looker.sync.fdroid.v2.model.PackageV2
|
||||
import com.looker.sync.fdroid.v2.model.RepoV2
|
||||
import com.looker.sync.fdroid.v2.model.VersionV2
|
||||
|
||||
fun PackageV2.toEntity(
|
||||
packageName: String,
|
||||
repoId: Long,
|
||||
allowUnstable: Boolean = false
|
||||
): AppEntity =
|
||||
AppEntity(
|
||||
repoId = repoId,
|
||||
packageName = packageName,
|
||||
categories = metadata.categories,
|
||||
summary = metadata.summary ?: emptyMap(),
|
||||
description = metadata.description ?: emptyMap(),
|
||||
changelog = metadata.changelog ?: "",
|
||||
translation = metadata.translation ?: "",
|
||||
issueTracker = metadata.issueTracker ?: "",
|
||||
sourceCode = metadata.sourceCode ?: "",
|
||||
binaries = "",
|
||||
name = metadata.name ?: emptyMap(),
|
||||
authorName = metadata.authorName ?: "",
|
||||
authorEmail = metadata.authorEmail ?: "",
|
||||
authorWebSite = metadata.authorWebsite ?: "",
|
||||
donate = metadata.donate.firstOrNull() ?: "",
|
||||
liberapayID = metadata.liberapay ?: "",
|
||||
liberapay = metadata.liberapay ?: "",
|
||||
openCollective = metadata.openCollective ?: "",
|
||||
bitcoin = metadata.bitcoin ?: "",
|
||||
litecoin = metadata.litecoin ?: "",
|
||||
flattrID = metadata.flattrID ?: "",
|
||||
suggestedVersionCode = versions.values.firstOrNull()?.manifest?.versionCode ?: -1,
|
||||
suggestedVersionName = versions.values.firstOrNull()?.manifest?.versionName ?: "",
|
||||
license = metadata.license ?: "",
|
||||
webSite = metadata.webSite ?: "",
|
||||
added = metadata.added,
|
||||
icon = metadata.icon?.mapValues { it.value.name } ?: emptyMap(),
|
||||
lastUpdated = metadata.lastUpdated,
|
||||
phoneScreenshots = metadata.screenshots?.phone?.mapValues { it.value.map { it.name } }
|
||||
?: emptyMap(),
|
||||
tenInchScreenshots = metadata.screenshots?.tenInch?.mapValues { it.value.map { it.name } }
|
||||
?: emptyMap(),
|
||||
sevenInchScreenshots = metadata.screenshots?.sevenInch
|
||||
?.mapValues { it.value.map { it.name } } ?: emptyMap(),
|
||||
tvScreenshots = metadata.screenshots?.tv?.mapValues { it.value.map { it.name } }
|
||||
?: emptyMap(),
|
||||
wearScreenshots = metadata.screenshots?.wear?.mapValues { it.value.map { it.name } }
|
||||
?: emptyMap(),
|
||||
featureGraphic = metadata.featureGraphic?.mapValues { it.value.name } ?: emptyMap(),
|
||||
promoGraphic = metadata.promoGraphic?.mapValues { it.value.name } ?: emptyMap(),
|
||||
tvBanner = metadata.tvBanner?.mapValues { it.value.name } ?: emptyMap(),
|
||||
video = metadata.video ?: emptyMap(),
|
||||
packages = versions.values.map(VersionV2::toPackage).checkUnstable(
|
||||
allowUnstable,
|
||||
versions.values.firstOrNull()?.manifest?.versionCode ?: -1
|
||||
)
|
||||
)
|
||||
|
||||
private fun List<PackageEntity>.checkUnstable(
|
||||
allowUnstable: Boolean,
|
||||
suggestedVersionCode: Long
|
||||
): List<PackageEntity> = filter {
|
||||
allowUnstable || (suggestedVersionCode > 0L && it.versionCode >= suggestedVersionCode)
|
||||
}
|
||||
|
||||
fun VersionV2.toPackage(): PackageEntity = PackageEntity(
|
||||
added = added,
|
||||
hash = file.sha256!!,
|
||||
features = manifest.features.map { it.name },
|
||||
apkName = file.name,
|
||||
hashType = "SHA-256",
|
||||
minSdkVersion = manifest.minSdkVersion ?: -1,
|
||||
maxSdkVersion = manifest.maxSdkVersion ?: -1,
|
||||
signer = manifest.signer?.sha256?.firstOrNull() ?: "",
|
||||
size = file.size ?: -1,
|
||||
usesPermission = manifest.usesPermission.map {
|
||||
PermissionEntity(name = it.name, maxSdk = it.maxSdkVersion)
|
||||
} + manifest.usesPermissionSdk23.map {
|
||||
PermissionEntity(name = it.name, maxSdk = it.maxSdkVersion, minSdk = 23)
|
||||
},
|
||||
versionCode = manifest.versionCode,
|
||||
versionName = manifest.versionName,
|
||||
srcName = src?.name ?: "",
|
||||
nativeCode = manifest.nativecode,
|
||||
antiFeatures = antiFeatures.keys.toList(),
|
||||
targetSdkVersion = manifest.usesSdk?.targetSdkVersion ?: -1,
|
||||
sig = signer?.sha256?.firstOrNull() ?: "",
|
||||
whatsNew = whatsNew
|
||||
)
|
||||
|
||||
fun RepoV2.toEntity(
|
||||
id: Long,
|
||||
fingerprint: String,
|
||||
etag: String,
|
||||
username: String,
|
||||
password: String,
|
||||
enabled: Boolean = true
|
||||
) = RepoEntity(
|
||||
id = id,
|
||||
enabled = enabled,
|
||||
fingerprint = fingerprint,
|
||||
mirrors = mirrors.map { it.url },
|
||||
address = address,
|
||||
name = name,
|
||||
description = description,
|
||||
timestamp = timestamp,
|
||||
etag = etag,
|
||||
username = username,
|
||||
password = password,
|
||||
antiFeatures = antiFeatures.mapValues {
|
||||
AntiFeatureEntity(
|
||||
name = it.value.name,
|
||||
icon = it.value.icon.mapValues { it.value.name },
|
||||
description = it.value.description
|
||||
)
|
||||
},
|
||||
categories = categories.mapValues {
|
||||
CategoryEntity(
|
||||
name = it.value.name,
|
||||
icon = it.value.icon.mapValues { it.value.name },
|
||||
description = it.value.description
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.looker.core.data.fdroid.sync.workers
|
||||
|
||||
import android.content.Context
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface HiltWorkerFactoryEntryPoint {
|
||||
fun hiltWorkerFactory(): HiltWorkerFactory
|
||||
}
|
||||
|
||||
private const val WORKER_CLASS_NAME = "RouterWorkerDelegateClassName"
|
||||
|
||||
internal fun KClass<out CoroutineWorker>.delegatedData() =
|
||||
Data.Builder()
|
||||
.putString(WORKER_CLASS_NAME, qualifiedName)
|
||||
.build()
|
||||
|
||||
internal class DelegatingWorker(
|
||||
appContext: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : CoroutineWorker(appContext, workerParams) {
|
||||
|
||||
private val workerClassName =
|
||||
workerParams.inputData.getString(WORKER_CLASS_NAME) ?: ""
|
||||
|
||||
private val delegateWorker =
|
||||
EntryPointAccessors.fromApplication<HiltWorkerFactoryEntryPoint>(appContext)
|
||||
.hiltWorkerFactory()
|
||||
.createWorker(appContext, workerClassName, workerParams)
|
||||
as? CoroutineWorker
|
||||
?: throw IllegalArgumentException("Unable to find appropriate worker")
|
||||
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo =
|
||||
delegateWorker.getForegroundInfo()
|
||||
|
||||
override suspend fun doWork(): Result =
|
||||
delegateWorker.doWork()
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.looker.core.data.fdroid.sync.workers
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Context
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.work.ForegroundInfo
|
||||
import com.looker.core.common.createNotificationChannel
|
||||
import com.looker.core.common.R as CommonR
|
||||
|
||||
private const val SyncNotificationID = 12
|
||||
private const val SyncNotificationChannelID = "SyncNotificationChannelID"
|
||||
|
||||
fun Context.syncForegroundInfo() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
ForegroundInfo(
|
||||
SyncNotificationID,
|
||||
syncWorkNotification(),
|
||||
FOREGROUND_SERVICE_TYPE_DATA_SYNC,
|
||||
)
|
||||
} else {
|
||||
ForegroundInfo(
|
||||
SyncNotificationID,
|
||||
syncWorkNotification(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Context.syncWorkNotification(): Notification {
|
||||
createNotificationChannel(
|
||||
id = SyncNotificationChannelID,
|
||||
name = getString(CommonR.string.sync_repositories),
|
||||
description = getString(CommonR.string.sync_repositories),
|
||||
)
|
||||
return NotificationCompat.Builder(
|
||||
this,
|
||||
SyncNotificationChannelID
|
||||
)
|
||||
.setSmallIcon(CommonR.drawable.ic_sync)
|
||||
.setContentTitle(getString(CommonR.string.syncing))
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.looker.core.data.fdroid.sync.workers
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import com.looker.core.domain.RepoRepository
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@HiltWorker
|
||||
class SyncWorker @AssistedInject constructor(
|
||||
@Assisted private val appContext: Context,
|
||||
@Assisted workParams: WorkerParameters,
|
||||
private val repoRepository: RepoRepository
|
||||
) : CoroutineWorker(appContext, workParams) {
|
||||
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo =
|
||||
appContext.syncForegroundInfo()
|
||||
|
||||
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
|
||||
Log.i(SYNC_WORK, "Start Sync")
|
||||
setForegroundAsync(appContext.syncForegroundInfo())
|
||||
val isSuccess = try {
|
||||
repoRepository.syncAll()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return@withContext Result.failure()
|
||||
}
|
||||
if (isSuccess) Result.success() else Result.failure()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SYNC_WORK = "sync_work"
|
||||
|
||||
fun cancelSyncWork(context: Context) {
|
||||
WorkManager.getInstance(context).cancelUniqueWork(SYNC_WORK)
|
||||
}
|
||||
|
||||
fun scheduleSyncWork(context: Context, constraints: Constraints) {
|
||||
WorkManager.getInstance(context).apply {
|
||||
val work = PeriodicWorkRequestBuilder<DelegatingWorker>(12L, TimeUnit.HOURS)
|
||||
.setConstraints(constraints)
|
||||
.setInputData(SyncWorker::class.delegatedData())
|
||||
.build()
|
||||
enqueueUniquePeriodicWork(SYNC_WORK, ExistingPeriodicWorkPolicy.REPLACE, work)
|
||||
}
|
||||
}
|
||||
|
||||
fun startSyncWork(context: Context) {
|
||||
WorkManager.getInstance(context).apply {
|
||||
val netRequired = Constraints(
|
||||
requiredNetworkType = NetworkType.CONNECTED
|
||||
)
|
||||
val work = OneTimeWorkRequestBuilder<DelegatingWorker>()
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.setConstraints(netRequired)
|
||||
.setInputData(SyncWorker::class.delegatedData())
|
||||
.build()
|
||||
beginUniqueWork(
|
||||
SYNC_WORK,
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
work
|
||||
).enqueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.looker.core.data.repository
|
||||
|
||||
import com.looker.core.domain.model.PackageName
|
||||
import com.looker.core.domain.AppRepository
|
||||
import com.looker.core.database.dao.AppDao
|
||||
import com.looker.core.database.dao.InstalledDao
|
||||
import com.looker.core.database.model.AppEntity
|
||||
import com.looker.core.database.model.InstalledEntity
|
||||
import com.looker.core.database.model.PackageEntity
|
||||
import com.looker.core.database.model.toExternal
|
||||
import com.looker.core.datastore.SettingsRepository
|
||||
import com.looker.core.datastore.get
|
||||
import com.looker.core.domain.model.App
|
||||
import com.looker.core.domain.model.Author
|
||||
import com.looker.core.domain.model.Package
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class OfflineFirstAppRepository @Inject constructor(
|
||||
installedDao: InstalledDao,
|
||||
private val appDao: AppDao,
|
||||
private val settingsRepository: SettingsRepository
|
||||
) : AppRepository {
|
||||
|
||||
private val localePreference = settingsRepository.get { language }
|
||||
|
||||
private val installedFlow = installedDao.getInstalledStream()
|
||||
|
||||
override fun getApps(): Flow<List<App>> =
|
||||
appDao.getAppStream().localizedAppList(localePreference, installedFlow)
|
||||
|
||||
override fun getApp(packageName: PackageName): Flow<List<App>> =
|
||||
appDao.getApp(packageName.name).localizedAppList(localePreference, installedFlow)
|
||||
|
||||
override fun getAppFromAuthor(author: Author): Flow<List<App>> =
|
||||
appDao.getAppsFromAuthor(author.name).localizedAppList(localePreference, installedFlow)
|
||||
|
||||
override fun getPackages(packageName: PackageName): Flow<List<Package>> =
|
||||
appDao.getPackages(packageName.name)
|
||||
.localizedPackages(packageName, localePreference, installedFlow)
|
||||
|
||||
override suspend fun addToFavourite(packageName: PackageName): Boolean = coroutineScope {
|
||||
val isFavourite =
|
||||
async {
|
||||
settingsRepository
|
||||
.getInitial()
|
||||
.favouriteApps
|
||||
.any { it == packageName.name }
|
||||
}
|
||||
launch {
|
||||
settingsRepository.toggleFavourites(packageName.name)
|
||||
}
|
||||
!isFavourite.await()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Flow<List<AppEntity>>.localizedAppList(
|
||||
preference: Flow<String>,
|
||||
installedFlow: Flow<List<InstalledEntity>>
|
||||
): Flow<List<App>> =
|
||||
combine(this, preference, installedFlow) { appsList, locale, installedList ->
|
||||
appsList.toExternal(locale) {
|
||||
it.findInstalled(installedList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Flow<List<PackageEntity>>.localizedPackages(
|
||||
packageName: PackageName,
|
||||
preference: Flow<String>,
|
||||
installedFlow: Flow<List<InstalledEntity>>
|
||||
): Flow<List<Package>> =
|
||||
combine(this, preference, installedFlow) { packagesList, locale, installedList ->
|
||||
packagesList.toExternal(locale) {
|
||||
InstalledEntity(packageName.name, it.versionCode, it.sig) in installedList
|
||||
}
|
||||
}
|
||||
|
||||
private fun AppEntity.findInstalled(list: List<InstalledEntity>): PackageEntity? =
|
||||
packages.find {
|
||||
InstalledEntity(packageName, it.versionCode, it.sig) in list
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.looker.core.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import com.looker.core.common.extension.exceptCancellation
|
||||
import com.looker.core.data.fdroid.toEntity
|
||||
import com.looker.core.database.dao.AppDao
|
||||
import com.looker.core.database.dao.RepoDao
|
||||
import com.looker.core.database.model.toExternal
|
||||
import com.looker.core.database.model.update
|
||||
import com.looker.core.datastore.SettingsRepository
|
||||
import com.looker.core.di.ApplicationScope
|
||||
import com.looker.core.di.DefaultDispatcher
|
||||
import com.looker.core.domain.RepoRepository
|
||||
import com.looker.core.domain.model.Repo
|
||||
import com.looker.network.Downloader
|
||||
import com.looker.sync.fdroid.v2.EntrySyncable
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class OfflineFirstRepoRepository @Inject constructor(
|
||||
@ApplicationContext context: Context,
|
||||
private val appDao: AppDao,
|
||||
private val repoDao: RepoDao,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
downloader: Downloader,
|
||||
@DefaultDispatcher private val dispatcher: CoroutineDispatcher,
|
||||
@ApplicationScope private val scope: CoroutineScope
|
||||
) : RepoRepository {
|
||||
|
||||
private val preference = runBlocking {
|
||||
settingsRepository.getInitial()
|
||||
}
|
||||
|
||||
private val locale = preference.language
|
||||
|
||||
override suspend fun getRepo(id: Long): Repo = withContext(dispatcher) {
|
||||
repoDao.getRepoById(id).toExternal(locale)
|
||||
}
|
||||
|
||||
override fun getRepos(): Flow<List<Repo>> =
|
||||
repoDao.getRepoStream().map { it.toExternal(locale) }
|
||||
|
||||
override suspend fun updateRepo(repo: Repo) {
|
||||
scope.launch {
|
||||
val entity = repoDao.getRepoById(repo.id)
|
||||
repoDao.upsertRepo(entity.update(repo))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun enableRepository(repo: Repo, enable: Boolean) {
|
||||
scope.launch {
|
||||
val entity = repoDao.getRepoById(repo.id)
|
||||
repoDao.upsertRepo(entity.copy(enabled = enable))
|
||||
if (enable) sync(repo)
|
||||
}
|
||||
}
|
||||
|
||||
private val syncable = EntrySyncable(context, downloader, dispatcher)
|
||||
|
||||
override suspend fun sync(repo: Repo): Boolean = coroutineScope {
|
||||
try {
|
||||
val (fingerprint, indexV2) = syncable.sync(repo)
|
||||
if (indexV2 == null) return@coroutineScope true
|
||||
val updatedRepo = indexV2.repo.toEntity(
|
||||
id = repo.id,
|
||||
fingerprint = fingerprint.value,
|
||||
etag = "",
|
||||
username = repo.authentication.username,
|
||||
password = repo.authentication.password,
|
||||
)
|
||||
val apps = indexV2.packages
|
||||
.map { (packageName, packageV2) ->
|
||||
packageV2.toEntity(
|
||||
packageName = packageName,
|
||||
repoId = repo.id,
|
||||
allowUnstable = preference.unstableUpdate,
|
||||
)
|
||||
}
|
||||
repoDao.upsertRepo(updatedRepo)
|
||||
appDao.upsertApps(apps)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.exceptCancellation()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun syncAll(): Boolean = supervisorScope {
|
||||
val repos = repoDao
|
||||
.getRepoStream()
|
||||
.first()
|
||||
.filter { it.enabled }
|
||||
repos.forEach {
|
||||
sync(it.toExternal("en-US"))
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.looker.core.data.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import com.looker.core.common.extension.connectivityManager
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
|
||||
class ConnectivityManagerNetworkMonitor
|
||||
@Inject constructor(
|
||||
@ApplicationContext context: Context
|
||||
) : NetworkMonitor {
|
||||
override val isOnline: Flow<Boolean> = callbackFlow {
|
||||
val callback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
channel.trySend(true)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
channel.trySend(false)
|
||||
}
|
||||
}
|
||||
|
||||
val connectivityManager = context.connectivityManager
|
||||
|
||||
connectivityManager?.registerNetworkCallback(
|
||||
NetworkRequest.Builder()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.build(),
|
||||
callback
|
||||
)
|
||||
|
||||
channel.trySend(connectivityManager.isCurrentlyConnected())
|
||||
|
||||
awaitClose {
|
||||
connectivityManager?.unregisterNetworkCallback(callback)
|
||||
}
|
||||
}.conflate()
|
||||
|
||||
private fun ConnectivityManager?.isCurrentlyConnected() = when (this) {
|
||||
null -> false
|
||||
else ->
|
||||
activeNetwork
|
||||
?.let(::getNetworkCapabilities)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
?: false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.looker.core.data.utils
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NetworkMonitor {
|
||||
val isOnline: Flow<Boolean>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.looker.core.data.utils
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface SyncStatusMonitor {
|
||||
val isSyncing: Flow<Boolean>
|
||||
}
|
||||
Reference in New Issue
Block a user