Fix some memory leaks and cleanup Repository feature

This commit is contained in:
Iamlooker 2022-06-30 00:44:26 +05:30
parent c599cbc75f
commit 615751fbb1
No known key found for this signature in database
GPG Key ID: 16F53B972BAECA48
7 changed files with 50 additions and 42 deletions

View File

@ -5,6 +5,7 @@ import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import com.looker.droidify.database.entity.Repository import com.looker.droidify.database.entity.Repository
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface RepositoryDao : BaseDao<Repository> { interface RepositoryDao : BaseDao<Repository> {
@ -27,6 +28,9 @@ interface RepositoryDao : BaseDao<Repository> {
@Query("SELECT * FROM repository WHERE _id = :id") @Query("SELECT * FROM repository WHERE _id = :id")
fun getLive(id: Long): LiveData<Repository?> fun getLive(id: Long): LiveData<Repository?>
@Query("SELECT * FROM repository ORDER BY _id ASC")
fun getAllRepositories(): Flow<List<Repository>>
@get:Query("SELECT * FROM repository ORDER BY _id ASC") @get:Query("SELECT * FROM repository ORDER BY _id ASC")
val all: List<Repository> val all: List<Repository>
@ -38,7 +42,7 @@ interface RepositoryDao : BaseDao<Repository> {
// TODO clean up products and other tables afterwards // TODO clean up products and other tables afterwards
@Query("DELETE FROM repository WHERE _id = :id") @Query("DELETE FROM repository WHERE _id = :id")
fun deleteById(id: Long): Int fun deleteById(id: Long)
@Query("SELECT MAX(_id) FROM repository") @Query("SELECT MAX(_id) FROM repository")
fun latestAddedId(): Long fun latestAddedId(): Long

View File

@ -43,6 +43,7 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -117,7 +118,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
fun installApps(products: List<ProductItem>) = batchUpdate(products, true) fun installApps(products: List<ProductItem>) = batchUpdate(products, true)
fun sync(request: SyncRequest) { fun sync(request: SyncRequest) {
GlobalScope.launch { scope.launch {
val ids = db.repositoryDao.all.filter { it.enabled }.map { it.id }.toList() val ids = db.repositoryDao.all.filter { it.enabled }.map { it.id }.toList()
sync(ids, request) sync(ids, request)
} }
@ -143,29 +144,30 @@ class SyncService : ConnectionService<SyncService.Binder>() {
} }
} }
fun setEnabled(repository: Repository, enabled: Boolean): Boolean { suspend fun setEnabled(repository: Repository, enabled: Boolean): Boolean =
db.repositoryDao.put(repository.enable(enabled)) withContext(Dispatchers.IO) {
if (enabled) { db.repositoryDao.put(repository.enable(enabled))
if (repository.id != currentTask?.task?.repositoryId && !tasks.any { it.repositoryId == repository.id }) { if (enabled) {
synchronized(tasks) { tasks += Task(repository.id, true) } if (repository.id != currentTask?.task?.repositoryId && !tasks.any { it.repositoryId == repository.id }) {
handleNextTask(false) synchronized(tasks) { tasks += Task(repository.id, true) }
handleNextTask(false)
}
} else {
cancelTasks { it.repositoryId == repository.id }
synchronized(tasks) { db.cleanUp(setOf(Pair(repository.id, false))) }
val cancelledTask = cancelCurrentTask { it.task?.repositoryId == repository.id }
handleNextTask(cancelledTask?.hasUpdates == true)
} }
} else { true
cancelTasks { it.repositoryId == repository.id }
synchronized(tasks) { db.cleanUp(setOf(Pair(repository.id, false))) }
val cancelledTask = cancelCurrentTask { it.task?.repositoryId == repository.id }
handleNextTask(cancelledTask?.hasUpdates == true)
} }
return true
suspend fun isCurrentlySyncing(repositoryId: Long): Boolean = withContext(Dispatchers.IO) {
currentTask?.task?.repositoryId == repositoryId
} }
fun isCurrentlySyncing(repositoryId: Long): Boolean { suspend fun deleteRepository(repositoryId: Long): Boolean = withContext(Dispatchers.IO) {
return currentTask?.task?.repositoryId == repositoryId
}
fun deleteRepository(repositoryId: Long): Boolean {
val repository = db.repositoryDao.get(repositoryId) val repository = db.repositoryDao.get(repositoryId)
return repository != null && run { repository != null && run {
setEnabled(repository, false) setEnabled(repository, false)
db.repositoryDao.deleteById(repository.id) db.repositoryDao.deleteById(repository.id)
true true
@ -325,7 +327,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
private fun handleNextTask(hasUpdates: Boolean) { private fun handleNextTask(hasUpdates: Boolean) {
if (currentTask == null) { if (currentTask == null) {
GlobalScope.launch { scope.launch {
if (tasks.isNotEmpty()) { if (tasks.isNotEmpty()) {
val task = tasks.removeAt(0) val task = tasks.removeAt(0)
val repository = db.repositoryDao.get(task.repositoryId) val repository = db.repositoryDao.get(task.repositoryId)
@ -425,7 +427,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
* @see SyncService.displayUpdatesNotification * @see SyncService.displayUpdatesNotification
*/ */
private fun batchUpdate(productItems: List<ProductItem>, install: Boolean = false) { private fun batchUpdate(productItems: List<ProductItem>, install: Boolean = false) {
if (Preferences[Preferences.Key.InstallAfterSync]) GlobalScope.launch { if (Preferences[Preferences.Key.InstallAfterSync]) scope.launch {
// run startUpdate on every item // run startUpdate on every item
productItems.map { productItem -> productItems.map { productItem ->
Triple( Triple(

View File

@ -29,7 +29,7 @@ fun RepositoryPage(viewModel: RepositoriesViewModelX) {
} }
} }
) { ) {
val sortedRepoList = remember { repos.sortedBy { !it.enabled } } val sortedRepoList = remember(repos) { repos.sortedBy { !it.enabled } }
RepositoriesRecycler( RepositoriesRecycler(
repositoriesList = sortedRepoList, repositoriesList = sortedRepoList,
onClick = { viewModel.toggleRepository(it, it.enabled) }, onClick = { viewModel.toggleRepository(it, it.enabled) },

View File

@ -15,6 +15,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.looker.droidify.EXTRA_REPOSITORY_ID import com.looker.droidify.EXTRA_REPOSITORY_ID
import com.looker.droidify.R import com.looker.droidify.R
@ -33,9 +34,8 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
@ -138,12 +138,14 @@ class EditRepositorySheetX() : FullscreenBottomSheetDialogFragment(), RepoManage
.show(childFragmentManager) .show(childFragmentManager)
} }
GlobalScope.launch { lifecycleScope.launchWhenStarted {
val list = viewModel.db.repositoryDao.all val reposFlow = viewModel.db.repositoryDao.getAllRepositories()
takenAddresses = list.asSequence().filter { it.id != repositoryId } reposFlow.collectLatest { list ->
.flatMap { (it.mirrors + it.address).asSequence() } takenAddresses = list.asSequence().filter { it.id != repositoryId }
.map { it.withoutKnownPath }.toSet() .flatMap { (it.mirrors + it.address).asSequence() }
MainScope().launch { invalidateAddress() } .map { it.withoutKnownPath }.toSet()
MainScope().launch { invalidateAddress() }
}
} }
} }
@ -389,7 +391,7 @@ class EditRepositorySheetX() : FullscreenBottomSheetDialogFragment(), RepoManage
address: String, address: String,
fingerprint: String, fingerprint: String,
authentication: String, authentication: String,
) = GlobalScope.launch { ) = lifecycleScope.launch {
val binder = syncConnection.binder val binder = syncConnection.binder
if (binder != null) { if (binder != null) {
if (binder.isCurrentlySyncing(repositoryId)) { if (binder.isCurrentlySyncing(repositoryId)) {
@ -435,7 +437,7 @@ class EditRepositorySheetX() : FullscreenBottomSheetDialogFragment(), RepoManage
} }
override fun onDeleteConfirm() { override fun onDeleteConfirm() {
GlobalScope.launch(Dispatchers.IO) { lifecycleScope.launch {
if (syncConnection.binder?.deleteRepository(repositoryId) == true) if (syncConnection.binder?.deleteRepository(repositoryId) == true)
dismissAllowingStateLoss() dismissAllowingStateLoss()
} }

View File

@ -32,7 +32,7 @@ class PrefsRepositoriesFragment : BaseNavFragment() {
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
): View { ): View {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenStarted {
viewModel.showSheet.collectLatest { viewModel.showSheet.collectLatest {
it?.let { it?.let {
RepositorySheetX(it).showNow(childFragmentManager, "Repository $it") RepositorySheetX(it).showNow(childFragmentManager, "Repository $it")

View File

@ -9,6 +9,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.looker.droidify.EXTRA_REPOSITORY_ID import com.looker.droidify.EXTRA_REPOSITORY_ID
import com.looker.droidify.R import com.looker.droidify.R
import com.looker.droidify.RepoManager import com.looker.droidify.RepoManager
@ -19,8 +20,6 @@ import com.looker.droidify.service.SyncService
import com.looker.droidify.ui.activities.PrefsActivityX import com.looker.droidify.ui.activities.PrefsActivityX
import com.looker.droidify.ui.viewmodels.RepositoryViewModelX import com.looker.droidify.ui.viewmodels.RepositoryViewModelX
import com.looker.droidify.utility.extension.resources.getColorFromAttr import com.looker.droidify.utility.extension.resources.getColorFromAttr
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.* import java.util.*
@ -140,7 +139,7 @@ class RepositorySheetX() : FullscreenBottomSheetDialogFragment(), RepoManager {
} }
override fun onDeleteConfirm() { override fun onDeleteConfirm() {
GlobalScope.launch(Dispatchers.IO) { lifecycleScope.launch {
if (syncConnection.binder?.deleteRepository(repositoryId) == true) if (syncConnection.binder?.deleteRepository(repositoryId) == true)
dismissAllowingStateLoss() dismissAllowingStateLoss()
} }

View File

@ -13,6 +13,7 @@ import com.looker.droidify.service.SyncService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -30,7 +31,9 @@ class RepositoriesViewModelX(val repositoryDao: RepositoryDao) : ViewModel() {
init { init {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
_repositories.emit(repositoryDao.all) repositoryDao.getAllRepositories().collectLatest {
_repositories.emit(it)
}
} }
toLaunch.value = null toLaunch.value = null
} }
@ -40,13 +43,11 @@ class RepositoriesViewModelX(val repositoryDao: RepositoryDao) : ViewModel() {
} }
fun showRepositorySheet(repositoryId: Long) { fun showRepositorySheet(repositoryId: Long) {
viewModelScope.launch { viewModelScope.launch { _showSheet.emit(repositoryId) }
_showSheet.emit(repositoryId)
}
} }
fun toggleRepository(repository: Repository, isEnabled: Boolean) { fun toggleRepository(repository: Repository, isEnabled: Boolean) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch {
syncConnection.binder?.setEnabled(repository, isEnabled) syncConnection.binder?.setEnabled(repository, isEnabled)
} }
} }