fix app not properly cancelling data fetching jobs in download card, filename template and home screne

pull/1198/head
deniscerri 3 months ago
parent 7d99c0faaa
commit edff15255f
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -19,11 +19,13 @@ import com.deniscerri.ytdl.util.extractors.GoogleApiUtil
import com.deniscerri.ytdl.util.extractors.YoutubeApiUtil
import com.deniscerri.ytdl.util.extractors.newpipe.NewPipeUtil
import com.deniscerri.ytdl.util.extractors.ytdlp.YTDLPUtil
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) {
class ResultRepository(private val resultDao: ResultDao, commandTemplateDao: CommandTemplateDao, private val context: Context) {
val YTDLNIS_SEARCH = "YTDLNIS_SEARCH"
val allResults : Flow<List<ResultItem>> = resultDao.getResults()
var itemCount = MutableStateFlow(-1)
@ -75,6 +77,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
}
itemCount.value = items.size
currentCoroutineContext().ensureActive()
resultDao.insertMultiple(items)
}
@ -112,6 +115,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
ytdlpUtil.getFromYTDL(inputQuery, resultsGenerated = {})
}
currentCoroutineContext().ensureActive()
itemCount.value = items.size
if (addToResults){
val ids = resultDao.insertMultiple(items)
@ -128,16 +132,15 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
//throw ExecuteException("Youtube Watch Videos is not yet supported in data fetching. You can download it directly by clicking Continue Anyway or by Quick Downloading it!")
val items = mutableListOf<ResultItem>()
val newpipeExtractorResult = if (isUsingNewPipeExtractorDataFetching()) {
newPipeUtil.getPlaylistData(inputQuery) {
newPipeUtil.getPlaylistData(inputQuery) { chunk ->
currentCoroutineContext().ensureActive()
if (addToResults){
runBlocking {
val ids = resultDao.insertMultiple(it)
ids.forEachIndexed { index, id ->
it[index].id = id
}
val ids = resultDao.insertMultiple(chunk)
ids.forEachIndexed { index, id ->
chunk[index].id = id
}
}
items.addAll(it)
items.addAll(chunk)
}
}else {
Result.failure(Throwable())
@ -147,6 +150,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
newpipeExtractorResult.getOrElse { items }
}else{
val res = ytdlpUtil.getFromYTDL(inputQuery, resultsGenerated = {})
currentCoroutineContext().ensureActive()
if (addToResults) {
val ids = resultDao.insertMultiple(res)
ids.forEachIndexed { index, id ->
@ -173,6 +177,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
}else{
ytdlpUtil.getFromYTDL( inputQuery, resultsGenerated = {})
}
currentCoroutineContext().ensureActive()
if (resetResults) {
deleteAll()
@ -195,16 +200,15 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
if (resetResults) deleteAll()
val items = mutableListOf<ResultItem>()
val ytExtractorResult = if (isUsingNewPipeExtractorDataFetching()){
newPipeUtil.getPlaylistData(playlistURL) {
newPipeUtil.getPlaylistData(playlistURL) { chunk ->
currentCoroutineContext().ensureActive()
if (addToResults){
runBlocking {
val ids = resultDao.insertMultiple(it)
ids.forEachIndexed { index, id ->
it[index].id = id
}
val ids = resultDao.insertMultiple(chunk)
ids.forEachIndexed { index, id ->
chunk[index].id = id
}
}
items.addAll(it)
items.addAll(chunk)
}
}else {
Result.failure(Throwable())
@ -219,12 +223,11 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
}else {
var itemCounts = 0
ytdlpUtil.getFromYTDL(inputQuery) {
currentCoroutineContext().ensureActive()
if (addToResults) {
runBlocking {
val ids = resultDao.insertMultiple(it)
ids.forEachIndexed { index, id ->
it[index].id = id
}
val ids = resultDao.insertMultiple(it)
ids.forEachIndexed { index, id ->
it[index].id = id
}
}
finalResults.addAll(it)
@ -241,12 +244,11 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
val items = mutableListOf<ResultItem>()
val ytExtractorResult = if (isUsingNewPipeExtractorDataFetching()) {
newPipeUtil.getChannelData(url) {
currentCoroutineContext().ensureActive()
if (addToResults){
runBlocking {
val ids = resultDao.insertMultiple(it)
ids.forEachIndexed { index, id ->
it[index].id = id
}
val ids = resultDao.insertMultiple(it)
ids.forEachIndexed { index, id ->
it[index].id = id
}
}
items.addAll(it)
@ -264,12 +266,11 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
}else {
var itemCounts = 0
ytdlpUtil.getFromYTDL(url) {
currentCoroutineContext().ensureActive()
if (addToResults) {
runBlocking {
val ids = resultDao.insertMultiple(it)
ids.forEachIndexed { index, id ->
it[index].id = id
}
val ids = resultDao.insertMultiple(it)
ids.forEachIndexed { index, id ->
it[index].id = id
}
}
finalResults.addAll(it)
@ -290,17 +291,16 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
val itemsToReturn = mutableListOf<ResultItem>()
ytdlpUtil.getFromYTDL(inputQuery, singleItem) { results ->
currentCoroutineContext().ensureActive()
if (resetResults) {
itemsCount += results.size
itemCount.value = itemsCount
}
results.filter { it.playlistTitle.isBlank() }.forEach { it.playlistTitle = YTDLNIS_SEARCH }
if (addToResults) {
runBlocking {
val ids = resultDao.insertMultiple(results)
ids.forEachIndexed { index, id ->
results[index].id = id
}
val ids = resultDao.insertMultiple(results)
ids.forEachIndexed { index, id ->
results[index].id = id
}
}
itemsToReturn.addAll(results)

@ -26,9 +26,13 @@ import com.deniscerri.ytdl.database.repository.SearchHistoryRepository
import com.deniscerri.ytdl.util.NotificationUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
@ -146,9 +150,10 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
}
fun cancelParsingQueries(){
parsingQueries?.cancel(CancellationException())
parsingQueriesJobList.forEach { it.cancel(CancellationException()) }
uiState.update {it.copy(processing = false)}
parsingQueries?.cancel()
parsingQueriesJobList.forEach { it.cancel() }
parsingQueriesJobList.clear()
uiState.update { it.copy(processing = false) }
}
private suspend fun parseQueriesImpl(inputQueries: List<String>, onResult: (list: List<ResultItem?>) -> Unit) {
@ -157,33 +162,41 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
}
val resetResults = inputQueries.size == 1
uiState.update {it.copy(processing = true, errorMessage = null)}
val res = mutableListOf<ResultItem?>()
val res = mutableListOf<ResultItem?>()
val requestSemaphore = Semaphore(10)
inputQueries.forEach { inputQuery ->
parsingQueriesJobList.add(viewModelScope.launch(Dispatchers.IO){
requestSemaphore.withPermit {
try {
res.addAll(repository.getResultsFromSource(inputQuery, resetResults))
} catch (e: Exception) {
if (updateResultDataJob?.isCancelled == false || e is ExecuteException){
uiState.update {it.copy(
processing = false,
errorMessage = e.message.toString(),
)}
Log.e(tag, e.toString())
}
try {
coroutineScope {
inputQueries.forEach { inputQuery ->
launch(Dispatchers.IO) {
requestSemaphore.withPermit {
try {
val results = repository.getResultsFromSource(inputQuery, resetResults)
synchronized(res) { res.addAll(results) }
} catch (e: Exception) {
if (e is CancellationException) throw e
if (isActive) {
uiState.update { it.copy(processing = false, errorMessage = e.message.toString()) }
}
}
}
}
}
})
}
parsingQueriesJobList.joinAll()
if (!isForegrounded() && inputQueries.size > 1){
notificationUtil.showQueriesFinished()
}
if (currentCoroutineContext().isActive) {
onResult(res)
}
} catch (e: CancellationException) {
Log.d(tag, "Parsing queries cancelled.")
} finally {
uiState.update { it.copy(processing = false) }
if (!isForegrounded() && inputQueries.size > 1) {
notificationUtil.showQueriesFinished()
}
}
uiState.update {it.copy(processing = false)}
onResult(res)
}
suspend fun parseQueries(inputQueries: List<String>, onResult: (list: List<ResultItem?>) -> Unit) {
@ -268,29 +281,23 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
suspend fun updateItemData(res: ResultItem){
if (updateResultDataJob == null || updateResultDataJob?.isCancelled == true || updateResultDataJob?.isCompleted == true){
updateResultDataJob = viewModelScope.launch(Dispatchers.IO) {
updatingData.emit(true)
if (updateResultDataJob?.isActive == true) return
updateResultData.emit(null)
updateResultData.value = null
updateResultDataJob = viewModelScope.launch(Dispatchers.IO) {
try {
updatingData.value = true
parseQueriesImpl(listOf(res.url)){ result ->
viewModelScope.launch(Dispatchers.IO){
updatingData.emit(false)
updatingData.value = false
updateResultData.emit(result)
parseQueriesImpl(listOf(res.url)) { result ->
if (isActive) {
updateResultData.value = result
}
}
}
} catch (e: CancellationException) {
} finally {
updatingData.emit(false)
updatingData.value = false
//updateResultDataJob?.start()
updateResultDataJob?.invokeOnCompletion {
if (it != null){
viewModelScope.launch(Dispatchers.IO) {
updatingData.emit(false)
updatingData.value = false
updateResultData.emit(null)
updateResultData.value = null
}
}
}
}
}
@ -312,44 +319,32 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
}
suspend fun updateFormatItemData(result: ResultItem){
if (updateFormatsResultDataJob == null || updateFormatsResultDataJob?.isCancelled == true || updateFormatsResultDataJob?.isCompleted == true) {
updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) {
updatingFormats.emit(true)
try {
val formats = getFormats(result.url)
updatingFormats.emit(false)
formats.apply {
if (formats.isNotEmpty() && updateFormatsResultDataJob?.isCancelled == false) {
getItemByURL(result.url)?.apply {
this.formats = formats.toMutableList()
update(this)
}
updateFormatsResultData.emit(formats.toMutableList())
}
}
}catch(e: Exception) {
if (e !is kotlinx.coroutines.CancellationException) {
uiState.update {
it.copy(
processing = false,
errorMessage = e.message.toString(),
)
}
Log.e(tag, e.toString())
if (updateFormatsResultDataJob?.isActive == true) return
updateFormatsResultData.emit(null)
updateFormatsResultData.value = null
updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) {
try {
updatingFormats.value = true
val formats = getFormats(result.url)
ensureActive()
if (formats.isNotEmpty()) {
getItemByURL(result.url)?.apply {
this.formats = formats.toMutableList()
update(this)
}
updatingFormats.emit(false)
updateFormatsResultData.emit(formats.toMutableList())
}
}
updateFormatsResultDataJob?.start()
updateFormatsResultDataJob?.invokeOnCompletion {
if (it != null){
viewModelScope.launch(Dispatchers.IO) {
updatingFormats.emit(false)
updatingFormats.value = false
updateFormatsResultData.emit(null)
updateFormatsResultData.value = null
}
} catch (e: CancellationException) {
} catch (e: Exception) {
uiState.update {
it.copy(processing = false, errorMessage = e.message.toString())
}
Log.e(tag, "Error updating formats: $e")
} finally {
updatingFormats.emit(false)
updatingFormats.value = false
}
}
}

@ -258,7 +258,7 @@ object Extensions {
val tmp = this.getString(it)
if (tmp != "null") {
return try {
Json.decodeFromString<List<String>>(tmp).joinToString(", ")
Json.decodeFromString<List<String>>(tmp).distinct().joinToString(", ")
} catch (e: Exception) {
tmp
}

@ -91,9 +91,11 @@ import com.google.android.material.timepicker.TimeFormat
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@ -2213,17 +2215,20 @@ object UiUtil {
val previewTemplateLoading = view.findViewById<ProgressBar>(R.id.filename_template_preview_loading)
val previewTemplateText = view.findViewById<TextView>(R.id.filename_template_preview_text)
var previewJob: Job? = null;
previewTemplateBtn.setOnClickListener {
previewTemplateLoading.isVisible = true
previewTemplateBtn.isVisible = false
CoroutineScope(Dispatchers.IO).launch {
previewJob = CoroutineScope(Dispatchers.IO).launch {
val preview = ytdlpViewModel!!.getFilenameTemplatePreview(itemContext!!, editText.text.toString())
withContext(Dispatchers.Main) {
previewTemplateLoading.isVisible = false
previewTemplateText.isVisible = true
previewTemplateText.text = preview
if (isActive) {
withContext(Dispatchers.Main) {
previewTemplateLoading.isVisible = false
previewTemplateText.isVisible = true
previewTemplateText.text = preview
}
}
}
}
@ -2233,6 +2238,7 @@ object UiUtil {
previewTemplateText.isVisible = false
previewTemplateLoading.isVisible = false
previewTemplateBtn.isVisible = true
previewJob?.cancel(CancellationException())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

@ -148,7 +148,7 @@ class NewPipeUtil(context: Context) {
}
}
fun getChannelData(url: String, progress: (pagedResults: MutableList<ResultItem>) -> Unit) : Result<List<ResultItem>> {
suspend fun getChannelData(url: String, progress: suspend (pagedResults: MutableList<ResultItem>) -> Unit) : Result<List<ResultItem>> {
try {
//return Result.failure(Throwable())
val req = ChannelInfo.getInfo(ServiceList.YouTube, url)
@ -170,7 +170,7 @@ class NewPipeUtil(context: Context) {
}
}
private fun getChannelTabData(linkHandler: ListLinkHandler, tabInfo: ChannelTabInfo, channelName: String, playlistURL: String, progress: (pagedResults: MutableList<ResultItem>) -> Unit) : Result<List<ResultItem>> {
private suspend fun getChannelTabData(linkHandler: ListLinkHandler, tabInfo: ChannelTabInfo, channelName: String, playlistURL: String, progress: suspend (pagedResults: MutableList<ResultItem>) -> Unit) : Result<List<ResultItem>> {
try {
val totalItems = mutableListOf<ResultItem>()
var nextPage : Page? = null
@ -215,7 +215,7 @@ class NewPipeUtil(context: Context) {
}
}
fun getPlaylistData(playlistURL: String, progress: (pagedResults: MutableList<ResultItem>) -> Unit) : Result<List<ResultItem>> {
suspend fun getPlaylistData(playlistURL: String, progress: suspend (pagedResults: MutableList<ResultItem>) -> Unit) : Result<List<ResultItem>> {
try {
val totalItems = mutableListOf<ResultItem>()
var nextPage : Page? = null

@ -37,6 +37,7 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject
@ -115,7 +116,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
}
@SuppressLint("RestrictedApi")
fun getFromYTDL(query: String, singleItem: Boolean = false, resultsGenerated: (results: List<ResultItem>) -> Unit): List<ResultItem> {
suspend fun getFromYTDL(query: String, singleItem: Boolean = false, resultsGenerated: suspend (pagedResults: List<ResultItem>) -> Unit): List<ResultItem> {
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
val request : YTDLRequest
@ -171,11 +172,15 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
finalResults.addAll(generatedResults)
if (!postedProgress) {
if (finalResults.size > 1) {
resultsGenerated(finalResults)
runBlocking {
resultsGenerated(finalResults)
}
postedProgress = true
}
}else{
resultsGenerated(generatedResults)
runBlocking {
resultsGenerated(generatedResults)
}
}
}
}

Loading…
Cancel
Save