fix: use Kotlin coroutines for git clone (fixes #1452)

This commit is contained in:
Akash Yadav
2023-12-17 00:40:58 +05:30
parent 46226345d3
commit 46fcd0a73d
3 changed files with 59 additions and 29 deletions
@@ -1,11 +1,13 @@
package com.itsaky.androidide.fragments
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.viewModels
import com.blankj.utilcode.util.ThreadUtils
import com.google.android.material.progressindicator.LinearProgressIndicator
@@ -14,23 +16,29 @@ import com.itsaky.androidide.activities.PreferencesActivity
import com.itsaky.androidide.activities.TerminalActivity
import com.itsaky.androidide.adapters.MainActionsListAdapter
import com.itsaky.androidide.app.BaseApplication
import com.itsaky.androidide.app.BaseIDEActivity
import com.itsaky.androidide.common.databinding.LayoutDialogProgressBinding
import com.itsaky.androidide.databinding.FragmentMainBinding
import com.itsaky.androidide.lsp.java.utils.CancelChecker
import com.itsaky.androidide.models.MainScreenAction
import com.itsaky.androidide.preferences.databinding.LayoutDialogTextInputBinding
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.tasks.executeAsyncProvideError
import com.itsaky.androidide.tasks.runOnUiThread
import com.itsaky.androidide.utils.DialogUtils
import com.itsaky.androidide.utils.Environment
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashError
import com.itsaky.androidide.utils.flashSuccess
import com.itsaky.androidide.viewmodel.MainViewModel
import com.termux.shared.termux.TermuxConstants
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.ProgressMonitor
import java.io.File
import java.util.concurrent.CancellationException
class MainFragment : BaseFragment() {
@@ -142,36 +150,47 @@ class MainFragment : BaseFragment() {
val targetDir = File(Environment.PROJECTS_DIR, repoName)
val progress = GitCloneProgressMonitor(binding.progress, binding.message)
var git: Git? = null
val future = executeAsyncProvideError({
return@executeAsyncProvideError Git.cloneRepository()
.setURI(url)
.setDirectory(targetDir)
.setProgressMonitor(progress)
.call()
.also { git = it }
}, { _, _ -> })
val coroutineScope = (activity as? BaseIDEActivity?)?.activityScope ?: viewLifecycleScope
var getDialog: Function0<AlertDialog?>? = null
val cloneJob = coroutineScope.launch(Dispatchers.IO) {
val git = try {
Git.cloneRepository()
.setURI(url)
.setDirectory(targetDir)
.setProgressMonitor(progress)
.call()
} catch (err: Throwable) {
if (!progress.isCancelled) {
err.printStackTrace()
withContext(Dispatchers.Main) {
getDialog?.invoke()?.also { if (it.isShowing) it.dismiss() }
showCloneError(err)
}
}
null
}
try {
git?.close()
} finally {
val success = git != null
getDialog?.invoke()?.also { dialog ->
if (dialog.isShowing) dialog.dismiss()
if (success) flashSuccess(string.git_clone_success)
}
}
}
builder.setPositiveButton(android.R.string.cancel) { iface, _ ->
iface.dismiss()
progress.cancel()
git?.close()
future.cancel(true)
cloneJob.cancel(CancellationException("Cancelled by user"))
}
val dialog = builder.show()
future.whenComplete { result, error ->
ThreadUtils.runOnUiThread {
dialog?.dismiss()
result?.close()
if (result == null || error != null) {
if (!future.isCancelled) {
showCloneError(error)
}
} else flashSuccess(string.git_clone_success)
}
}
getDialog = { builder.show() }
}
private fun showCloneError(error: Throwable?) {
@@ -203,15 +222,15 @@ class MainFragment : BaseFragment() {
}
override fun start(totalTasks: Int) {
ThreadUtils.runOnUiThread { progress.max = totalTasks }
runOnUiThread { progress.max = totalTasks }
}
override fun beginTask(title: String?, totalWork: Int) {
ThreadUtils.runOnUiThread { message.text = title }
runOnUiThread { message.text = title }
}
override fun update(completed: Int) {
ThreadUtils.runOnUiThread { progress.progress = completed }
runOnUiThread { progress.progress = completed }
}
override fun showDuration(enabled: Boolean) {
@@ -47,7 +47,7 @@ abstract class BaseIDEActivity : AppCompatActivity() {
/**
* [CoroutineScope] for executing tasks with the [Default][Dispatchers.Default] dispatcher.
*/
protected val activityScope = CoroutineScope(Dispatchers.Default)
val activityScope = CoroutineScope(Dispatchers.Default)
val isStoragePermissionGranted: Boolean
get() =
@@ -28,6 +28,10 @@ import androidx.fragment.app.Fragment
import com.itsaky.androidide.common.R
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.utils.flashError
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import java.io.File
open class BaseFragment @JvmOverloads constructor(contentLayoutId: Int = 0) :
@@ -37,11 +41,18 @@ open class BaseFragment @JvmOverloads constructor(contentLayoutId: Int = 0) :
private val allowedAuthorities =
setOf(ANDROID_DOCS_AUTHORITY, ANDROIDIDE_DOCS_AUTHORITY)
protected val viewLifecycleScope = CoroutineScope(Dispatchers.Default + CoroutineName(javaClass.simpleName))
companion object {
const val ANDROID_DOCS_AUTHORITY = "com.android.externalstorage.documents"
const val ANDROIDIDE_DOCS_AUTHORITY = "com.itsaky.androidide.documents"
}
override fun onDestroyView() {
super.onDestroyView()
viewLifecycleScope.cancel("${javaClass.simpleName} is being destroyed")
}
private val startForResult =
registerForActivityResult(StartActivityForResult()) {
val context = requireContext()