summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarsh Shandilya <msfjarvis@gmail.com>2020-08-05 19:02:24 +0530
committerGitHub <noreply@github.com>2020-08-05 19:02:24 +0530
commit14c44bf584cb7d13ada46b264419efca923ed65c (patch)
treeb22c8a9a7be7934927ed20fa9b2060c877953082
parent12a83e5c36b82e42dda6a58248e2879fde9c3ac8 (diff)
Remove GitAsyncTask and replace with non-blocking coroutines (#865)
Co-authored-by: Fabian Henneke <fabian@henneke.me>
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/Application.kt6
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt41
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.kt1
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt1
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt12
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt27
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt25
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt90
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt53
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/ErrorMessages.kt75
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt (renamed from app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt)186
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt6
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitOperationActivity.kt10
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt6
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt52
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt50
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt69
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt67
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/operation/BreakOutOfDetached.kt52
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/operation/CloneOperation.kt29
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/operation/CredentialFinder.kt94
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/operation/GitOperation.kt (renamed from app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt)129
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/operation/PullOperation.kt28
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/operation/PushOperation.kt29
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/operation/ResetToRemoteOperation.kt30
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/operation/SyncOperation.kt30
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt50
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt1
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/Result.kt16
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/UriTotpFinder.kt1
-rw-r--r--app/src/main/res/values/strings.xml12
32 files changed, 631 insertions, 649 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/Application.kt b/app/src/main/java/com/zeapo/pwdstore/Application.kt
index 3ccf37fe..611ccfe0 100644
--- a/app/src/main/java/com/zeapo/pwdstore/Application.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/Application.kt
@@ -23,6 +23,7 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
override fun onCreate() {
super.onCreate()
+ instance = this
prefs = PreferenceManager.getDefaultSharedPreferences(this)
if (BuildConfig.ENABLE_DEBUG_FEATURES || prefs?.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false) ==
true) {
@@ -52,4 +53,9 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
else -> MODE_NIGHT_AUTO_BATTERY
})
}
+
+ companion object {
+
+ lateinit var instance: Application
+ }
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
index 1cd7c1ea..355d4578 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
@@ -18,8 +18,6 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo
import androidx.appcompat.view.ActionMode
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.fragment.app.setFragmentResultListener
-import androidx.lifecycle.observe
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
index 57e636d1..a4ada3c5 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
@@ -34,7 +34,6 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.observe
import androidx.preference.PreferenceManager
import com.github.ajalt.timberkt.d
import com.github.ajalt.timberkt.e
@@ -581,7 +580,12 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
intent.putExtra("REPO_PATH", getRepositoryDirectory(applicationContext).absolutePath)
registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
- commitChange(resources.getString(R.string.git_commit_add_text, result.data?.extras?.getString("LONG_NAME")))
+ lifecycleScope.launch {
+ commitChange(
+ resources.getString(R.string.git_commit_add_text, result.data?.extras?.getString("LONG_NAME")),
+ finishActivityOnEnd = false,
+ )
+ }
refreshPasswordList()
}
}.launch(intent)
@@ -618,11 +622,15 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
selectedItems.map { item -> item.file.deleteRecursively() }
refreshPasswordList()
AutofillMatcher.updateMatches(applicationContext, delete = filesToDelete)
- commitChange(resources.getString(R.string.git_commit_remove_text,
- selectedItems.joinToString(separator = ", ") { item ->
- item.file.toRelativeString(getRepositoryDirectory(this))
- }
- ))
+ val fmt = selectedItems.joinToString(separator = ", ") { item ->
+ item.file.toRelativeString(getRepositoryDirectory(this@PasswordStore))
+ }
+ lifecycleScope.launch {
+ commitChange(
+ resources.getString(R.string.git_commit_remove_text, fmt),
+ finishActivityOnEnd = false,
+ )
+ }
}
.setNegativeButton(resources.getString(R.string.dialog_no), null)
.show()
@@ -688,14 +696,20 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename)
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
withContext(Dispatchers.Main) {
- commitChange(resources.getString(R.string.git_commit_move_text, sourceLongName, destinationLongName))
+ commitChange(
+ resources.getString(R.string.git_commit_move_text, sourceLongName, destinationLongName),
+ finishActivityOnEnd = false,
+ )
}
}
else -> {
+ val repoDir = getRepositoryDirectory(applicationContext).absolutePath
+ val relativePath = getRelativePath("${target.absolutePath}/", repoDir)
withContext(Dispatchers.Main) {
- commitChange(resources.getString(R.string.git_commit_move_multiple_text,
- getRelativePath("${target.absolutePath}/", getRepositoryDirectory(applicationContext).absolutePath)
- ))
+ commitChange(
+ resources.getString(R.string.git_commit_move_multiple_text, relativePath),
+ finishActivityOnEnd = false,
+ )
}
}
}
@@ -746,7 +760,10 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) {
else -> lifecycleScope.launch(Dispatchers.IO) {
moveFile(oldCategory.file, newCategory)
withContext(Dispatchers.Main) {
- commitChange(resources.getString(R.string.git_commit_move_text, oldCategory.name, newCategory.name))
+ commitChange(
+ resources.getString(R.string.git_commit_move_text, oldCategory.name, newCategory.name),
+ finishActivityOnEnd = false,
+ )
}
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.kt b/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.kt
index 2fd8c94c..7bc32211 100644
--- a/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.kt
@@ -10,7 +10,6 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.observe
import androidx.recyclerview.widget.LinearLayoutManager
import com.zeapo.pwdstore.databinding.PasswordRecyclerViewBinding
import com.zeapo.pwdstore.ui.adapters.PasswordItemRecyclerAdapter
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt
index a1d2fa59..7e29b061 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt
@@ -22,7 +22,6 @@ import androidx.core.text.buildSpannedString
import androidx.core.text.underline
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.observe
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.ajalt.timberkt.e
import com.zeapo.pwdstore.FilterMode
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt
index 4661716f..745d2d1e 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt
@@ -15,6 +15,7 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
+import androidx.lifecycle.lifecycleScope
import com.github.ajalt.timberkt.e
import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.autofill.oreo.AutofillAction
@@ -27,6 +28,7 @@ import com.zeapo.pwdstore.crypto.PasswordCreationActivity
import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.commitChange
import java.io.File
+import kotlinx.coroutines.launch
@RequiresApi(Build.VERSION_CODES.O)
class AutofillSaveActivity : AppCompatActivity() {
@@ -144,10 +146,12 @@ class AutofillSaveActivity : AppCompatActivity() {
}
// PasswordCreationActivity delegates committing the added file to PasswordStore. Since
// PasswordStore is not involved in an AutofillScenario, we have to commit the file ourselves.
- commitChange(
- getString(R.string.git_commit_add_text, longName),
- finishWithResultOnEnd = resultIntent
- )
+ lifecycleScope.launch {
+ commitChange(
+ getString(R.string.git_commit_add_text, longName),
+ finishWithResultOnEnd = resultIntent
+ )
+ }
// GitAsyncTask will finish the activity for us.
}
}.launch(saveIntent)
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt
index a05da554..d46f0a4e 100644
--- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt
@@ -329,12 +329,14 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
gpgIdentifierFile.writeText(keyIds.joinToString("\n"))
val repo = PasswordRepository.getRepository(null)
if (repo != null) {
- commitChange(
- getString(
- R.string.git_commit_gpg_id,
- getLongName(gpgIdentifierFile.parentFile!!.absolutePath, repoPath, gpgIdentifierFile.name)
+ lifecycleScope.launch {
+ commitChange(
+ getString(
+ R.string.git_commit_gpg_id,
+ getLongName(gpgIdentifierFile.parentFile!!.absolutePath, repoPath, gpgIdentifierFile.name)
+ )
)
- )
+ }
}
encrypt(data)
}
@@ -422,7 +424,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
AutofillPreferences.directoryStructure(applicationContext)
val entry = PasswordEntry(content)
returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password)
- val username = entry.username ?: directoryStructure.getUsernameFor(file)
+ val username = entry.username
+ ?: directoryStructure.getUsernameFor(file)
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
}
@@ -430,12 +433,14 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
if (repo != null) {
val status = Git(repo).status().call()
if (status.modified.isNotEmpty()) {
- commitChange(
- getString(
- R.string.git_commit_edit_text,
- getLongName(fullPath, repoPath, editName)
+ lifecycleScope.launch {
+ commitChange(
+ getString(
+ R.string.git_commit_edit_text,
+ getLongName(fullPath, repoPath, editName)
+ )
)
- )
+ }
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt
index 7a818bcf..706592fe 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt
@@ -12,6 +12,7 @@ import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.core.text.isDigitsOnly
+import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.github.ajalt.timberkt.Timber.tag
import com.github.ajalt.timberkt.e
@@ -20,11 +21,19 @@ import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.git.config.ConnectionMode
import com.zeapo.pwdstore.git.config.Protocol
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
+import com.zeapo.pwdstore.git.operation.BreakOutOfDetached
+import com.zeapo.pwdstore.git.operation.CloneOperation
+import com.zeapo.pwdstore.git.operation.GitOperation
+import com.zeapo.pwdstore.git.operation.PullOperation
+import com.zeapo.pwdstore.git.operation.PushOperation
+import com.zeapo.pwdstore.git.operation.ResetToRemoteOperation
+import com.zeapo.pwdstore.git.operation.SyncOperation
import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PreferenceKeys
import com.zeapo.pwdstore.utils.getEncryptedPrefs
import java.io.File
import java.net.URI
+import kotlinx.coroutines.launch
/**
* Abstract AppCompatActivity that holds some information that is commonly shared across git-related
@@ -166,7 +175,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
*
* @param operation The type of git operation to launch
*/
- fun launchGitOperation(operation: Int) {
+ suspend fun launchGitOperation(operation: Int) {
if (url == null) {
setResult(RESULT_CANCELED)
finish()
@@ -190,12 +199,12 @@ abstract class BaseGitActivity : AppCompatActivity() {
val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory(this))
val op = when (operation) {
- REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> CloneOperation(localDir, this).setCommand(url!!)
- REQUEST_PULL -> PullOperation(localDir, this).setCommand()
- REQUEST_PUSH -> PushOperation(localDir, this).setCommand()
- REQUEST_SYNC -> SyncOperation(localDir, this).setCommands()
- BREAK_OUT_OF_DETACHED -> BreakOutOfDetached(localDir, this).setCommands()
- REQUEST_RESET -> ResetToRemoteOperation(localDir, this).setCommands()
+ REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> CloneOperation(localDir, url!!, this)
+ REQUEST_PULL -> PullOperation(localDir, this)
+ REQUEST_PUSH -> PushOperation(localDir, this)
+ REQUEST_SYNC -> SyncOperation(localDir, this)
+ BREAK_OUT_OF_DETACHED -> BreakOutOfDetached(localDir, this)
+ REQUEST_RESET -> ResetToRemoteOperation(localDir, this)
SshApiSessionFactory.POST_SIGNATURE -> return
else -> {
tag(TAG).e { "Operation not recognized : $operation" }
@@ -239,7 +248,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
if (identityBuilder != null) {
identityBuilder!!.consume(data)
}
- launchGitOperation(requestCode)
+ lifecycleScope.launch { launchGitOperation(requestCode) }
}
super.onActivityResult(requestCode, resultCode, data)
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt b/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt
deleted file mode 100644
index 149cabd8..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.git
-
-import androidx.appcompat.app.AppCompatActivity
-import androidx.preference.PreferenceManager
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.zeapo.pwdstore.R
-import com.zeapo.pwdstore.utils.PreferenceKeys
-import java.io.File
-import org.eclipse.jgit.api.Git
-import org.eclipse.jgit.api.GitCommand
-import org.eclipse.jgit.api.PushCommand
-import org.eclipse.jgit.api.RebaseCommand
-
-class BreakOutOfDetached(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
-
- private lateinit var commands: List<GitCommand<out Any>>
- private val gitBranch = PreferenceManager
- .getDefaultSharedPreferences(callingActivity.applicationContext)
- .getString(PreferenceKeys.GIT_BRANCH_NAME, "master")
-
- /**
- * Sets the command
- *
- * @return the current object
- */
- fun setCommands(): BreakOutOfDetached {
- val git = Git(repository)
- val branchName = "conflicting-$gitBranch-${System.currentTimeMillis()}"
-
- this.commands = listOf(
- // abort the rebase
- git.rebase().setOperation(RebaseCommand.Operation.ABORT),
- // git checkout -b conflict-branch
- git.checkout().setCreateBranch(true).setName(branchName),
- // push the changes
- git.push().setRemote("origin"),
- // switch back to ${gitBranch}
- git.checkout().setName(gitBranch)
- )
- return this
- }
-
- override fun execute() {
- val git = Git(repository)
- if (!git.repository.repositoryState.isRebasing) {
- MaterialAlertDialogBuilder(callingActivity)
- .setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title))
- .setMessage("The repository is not rebasing, no need to push to another branch")
- .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
- callingActivity.finish()
- }.show()
- return
- }
-
- if (this.provider != null) {
- // set the credentials for push command
- this.commands.forEach { cmd ->
- if (cmd is PushCommand) {
- cmd.setCredentialsProvider(this.provider)
- }
- }
- }
- GitAsyncTask(callingActivity, this, null)
- .execute(*this.commands.toTypedArray())
- }
-
- override fun onError(err: Exception) {
- MaterialAlertDialogBuilder(callingActivity)
- .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
- .setMessage("Error occurred when checking out another branch operation ${err.message}")
- .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
- callingActivity.finish()
- }.show()
- }
-
- override fun onSuccess() {
- MaterialAlertDialogBuilder(callingActivity)
- .setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title))
- .setMessage("There was a conflict when trying to rebase. " +
- "Your local $gitBranch branch was pushed to another branch named conflicting-$gitBranch-....\n" +
- "Use this branch to resolve conflict on your computer")
- .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
- callingActivity.finish()
- }.show()
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt
deleted file mode 100644
index 8817967b..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.git
-
-import android.content.Intent
-import androidx.appcompat.app.AppCompatActivity
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.zeapo.pwdstore.R
-import java.io.File
-import org.eclipse.jgit.api.CloneCommand
-import org.eclipse.jgit.api.Git
-
-/**
- * Creates a new clone operation
- *
- * @param fileDir the git working tree directory
- * @param callingActivity the calling activity
- */
-class CloneOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
-
- /**
- * Sets the command using the repository uri
- *
- * @param uri the uri of the repository
- * @return the current object
- */
- fun setCommand(uri: String): CloneOperation {
- this.command = Git.cloneRepository()
- .setCloneAllBranches(true)
- .setDirectory(repository?.workTree)
- .setURI(uri)
- return this
- }
-
- override fun execute() {
- (this.command as? CloneCommand)?.setCredentialsProvider(this.provider)
- GitAsyncTask(callingActivity, this, Intent()).execute(this.command)
- }
-
- override fun onError(err: Exception) {
- super.onError(err)
- MaterialAlertDialogBuilder(callingActivity)
- .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
- .setMessage("Error occurred during the clone operation, " +
- callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
- err.message +
- "\nPlease check the FAQ for possible reasons why this error might occur.")
- .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
- .show()
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/ErrorMessages.kt b/app/src/main/java/com/zeapo/pwdstore/git/ErrorMessages.kt
new file mode 100644
index 00000000..dfc786b9
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/ErrorMessages.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.git
+
+import android.os.RemoteException
+import com.zeapo.pwdstore.Application
+import com.zeapo.pwdstore.R
+
+/**
+ * Supertype for all Git-related [Exception]s that can be thrown by [GitCommandExecutor.execute].
+ */
+sealed class GitException(message: String? = null) : Exception(message) {
+
+ /**
+ * Encapsulates possible errors from a [org.eclipse.jgit.api.PullCommand].
+ */
+ class PullException(val reason: Reason) : GitException() {
+
+ enum class Reason {
+ REBASE_FAILED,
+ }
+ }
+
+ /**
+ * Encapsulates possible errors from a [org.eclipse.jgit.api.PushCommand].
+ */
+ class PushException(val reason: Reason, vararg val fmt: String) : GitException() {
+ enum class Reason {
+ NON_FAST_FORWARD,
+ REMOTE_REJECTED,
+ GENERIC,
+ }
+ }
+}
+
+object ErrorMessages {
+
+ private val PULL_REASON_MAP = mapOf(
+ GitException.PullException.Reason.REBASE_FAILED to R.string.git_pull_fail_error,
+ )
+
+ private val PUSH_REASON_MAP = mapOf(
+ GitException.PushException.Reason.NON_FAST_FORWARD to R.string.git_push_nff_error,
+ GitException.PushException.Reason.REMOTE_REJECTED to R.string.git_push_other_error,
+ GitException.PushException.Reason.GENERIC to R.string.git_push_generic_error,
+ )
+
+ operator fun get(throwable: Throwable?): String {
+ val resources = Application.instance.resources
+ if (throwable == null) return resources.getString(R.string.git_unknown_error)
+ return when (val rootCause = rootCause(throwable)) {
+ is GitException.PullException -> {
+ resources.getString(PULL_REASON_MAP.getValue(rootCause.reason))
+ }
+ is GitException.PushException -> {
+ resources.getString(PUSH_REASON_MAP.getValue(rootCause.reason), *rootCause.fmt)
+ }
+ else -> throwable.message ?: resources.getString(R.string.git_unknown_error)
+ }
+ }
+
+ private fun rootCause(throwable: Throwable): Throwable {
+ var cause = throwable
+ while (cause.cause != null) {
+ if (cause is GitException) break
+ val nextCause = cause.cause!!
+ if (nextCause is RemoteException) break
+ cause = nextCause
+ }
+ return cause
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt
index b76c98f6..11b06b8e 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt
@@ -2,23 +2,27 @@
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
+
package com.zeapo.pwdstore.git
-import android.app.ProgressDialog
-import android.content.Context
+import android.app.Activity
import android.content.Intent
-import android.os.AsyncTask
-import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.FragmentActivity
import com.github.ajalt.timberkt.e
+import com.google.android.material.snackbar.Snackbar
import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.git.GitException.PullException
+import com.zeapo.pwdstore.git.GitException.PushException
import com.zeapo.pwdstore.git.config.SshjSessionFactory
-import java.io.IOException
-import java.lang.ref.WeakReference
+import com.zeapo.pwdstore.git.operation.GitOperation
+import com.zeapo.pwdstore.utils.Result
+import com.zeapo.pwdstore.utils.snackbar
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import net.schmizz.sshj.common.DisconnectReason
import net.schmizz.sshj.common.SSHException
import net.schmizz.sshj.userauth.UserAuthException
import org.eclipse.jgit.api.CommitCommand
-import org.eclipse.jgit.api.GitCommand
import org.eclipse.jgit.api.PullCommand
import org.eclipse.jgit.api.PushCommand
import org.eclipse.jgit.api.RebaseResult
@@ -26,109 +30,111 @@ import org.eclipse.jgit.api.StatusCommand
import org.eclipse.jgit.transport.RemoteRefUpdate
import org.eclipse.jgit.transport.SshSessionFactory
-
-class GitAsyncTask(
- activity: AppCompatActivity,
+class GitCommandExecutor(
+ private val activity: FragmentActivity,
private val operation: GitOperation,
- private val finishWithResultOnEnd: Intent?,
- private val silentlyExecute: Boolean = false
-) : AsyncTask<GitCommand<*>, Int, GitAsyncTask.Result>() {
-
- private val activityWeakReference: WeakReference<AppCompatActivity> = WeakReference(activity)
- private val activity: AppCompatActivity?
- get() = activityWeakReference.get()
- private val context: Context = activity.applicationContext
- private val dialog = ProgressDialog(activity)
-
- sealed class Result {
- object Ok : Result()
- data class Err(val err: Exception) : Result()
- }
+ private val finishWithResultOnEnd: Intent? = Intent(),
+ private val finishActivityOnEnd: Boolean = true,
+) {
- override fun onPreExecute() {
- if (silentlyExecute) return
- dialog.run {
- setMessage(activity!!.resources.getString(R.string.running_dialog_text))
- setCancelable(false)
- show()
- }
- }
-
- override fun doInBackground(vararg commands: GitCommand<*>): Result? {
- var nbChanges: Int? = null
- for (command in commands) {
+ suspend fun execute() {
+ operation.setCredentialProvider()
+ val snackbar = activity.snackbar(
+ message = activity.resources.getString(R.string.git_operation_running),
+ length = Snackbar.LENGTH_INDEFINITE,
+ )
+ var nbChanges = 0
+ var operationResult: Result = Result.Ok
+ for (command in operation.commands) {
try {
when (command) {
is StatusCommand -> {
// in case we have changes, we want to keep track of it
- val status = command.call()
+ val status = withContext(Dispatchers.IO) {
+ command.call()
+ }
nbChanges = status.changed.size + status.missing.size
}
is CommitCommand -> {
// the previous status will eventually be used to avoid a commit
- if (nbChanges == null || nbChanges > 0) command.call()
+ withContext(Dispatchers.IO) {
+ if (nbChanges > 0) command.call()
+ }
}
is PullCommand -> {
- val result = command.call()
+ val result = withContext(Dispatchers.IO) {
+ command.call()
+ }
val rr = result.rebaseResult
if (rr.status === RebaseResult.Status.STOPPED) {
- return Result.Err(IOException(context.getString(R.string
- .git_pull_fail_error)))
+ operationResult = Result.Err(PullException(PullException.Reason.REBASE_FAILED))
}
}
is PushCommand -> {
- for (result in command.call()) {
+ val results = withContext(Dispatchers.IO) {
+ command.call()
+ }
+ for (result in results) {
// Code imported (modified) from Gerrit PushOp, license Apache v2
for (rru in result.remoteUpdates) {
val error = when (rru.status) {
- RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD ->
- context.getString(R.string.git_push_nff_error)
+ RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD -> {
+ PushException(PushException.Reason.NON_FAST_FORWARD)
+ }
RemoteRefUpdate.Status.REJECTED_NODELETE,
RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
RemoteRefUpdate.Status.NON_EXISTING,
- RemoteRefUpdate.Status.NOT_ATTEMPTED
- ->
- (activity!!.getString(R.string.git_push_generic_error) + rru.status.name)
+ RemoteRefUpdate.Status.NOT_ATTEMPTED,
+ -> {
+ PushException(PushException.Reason.GENERIC, rru.status.name)
+ }
RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> {
- if
- ("non-fast-forward" == rru.message) {
- context.getString(R.string.git_push_other_error)
+ if ("non-fast-forward" == rru.message) {
+ PushException(PushException.Reason.REMOTE_REJECTED)
} else {
- (context.getString(R.string.git_push_generic_error)
- + rru.message)
+ PushException(PushException.Reason.GENERIC, rru.message)
}
}
else -> null
}
- if (error != null)
- Result.Err(IOException(error))
+ if (error != null) {
+ operationResult = Result.Err(error)
+ }
}
}
}
else -> {
- command.call()
+ withContext(Dispatchers.IO) {
+ command.call()
+ }
}
}
} catch (e: Exception) {
- return Result.Err(e)
+ operationResult = Result.Err(e)
}
}
- return Result.Ok
- }
-
- private fun rootCauseException(e: Exception): Exception {
- var rootCause = e
- // JGit's TransportException hides the more helpful SSHJ exceptions.
- // Also, SSHJ's UserAuthException about exhausting available authentication methods hides
- // more useful exceptions.
- while ((rootCause is org.eclipse.jgit.errors.TransportException ||
- rootCause is org.eclipse.jgit.api.errors.TransportException ||
- (rootCause is UserAuthException &&
- rootCause.message == "Exhausted available authentication methods"))) {
- rootCause = rootCause.cause as? Exception ?: break
+ when (operationResult) {
+ is Result.Err -> {
+ activity.setResult(Activity.RESULT_CANCELED)
+ if (isExplicitlyUserInitiatedError(operationResult.err)) {
+ // Currently, this is only executed when the user cancels a password prompt
+ // during authentication.
+ if (finishActivityOnEnd) activity.finish()
+ } else {
+ e(operationResult.err)
+ operation.onError(rootCauseException(operationResult.err))
+ }
+ }
+ is Result.Ok -> {
+ operation.onSuccess()
+ activity.setResult(Activity.RESULT_OK, finishWithResultOnEnd)
+ if (finishActivityOnEnd) activity.finish()
+ }
}
- return rootCause
+ snackbar.dismiss()
+ (SshSessionFactory.getInstance() as? SshjSessionFactory)?.clearCredentials()
+ SshSessionFactory.setInstance(null)
}
private fun isExplicitlyUserInitiatedError(e: Exception): Boolean {
@@ -142,35 +148,17 @@ class GitAsyncTask(
return false
}
- override fun onPostExecute(maybeResult: Result?) {
- if (!silentlyExecute) dialog.dismiss()
- when (val result = maybeResult ?: Result.Err(IOException("Unexpected error"))) {
- is Result.Err -> {
- if (isExplicitlyUserInitiatedError(result.err)) {
- // Currently, this is only executed when the user cancels a password prompt
- // during authentication.
- if (finishWithResultOnEnd != null) {
- activity?.setResult(AppCompatActivity.RESULT_CANCELED)
- activity?.finish()
- }
- } else {
- e(result.err)
- operation.onError(rootCauseException(result.err))
- if (finishWithResultOnEnd != null) {
- activity?.setResult(AppCompatActivity.RESULT_CANCELED)
- }
- }
- }
- is Result.Ok -> {
- operation.onSuccess()
- if (finishWithResultOnEnd != null) {
- activity?.setResult(AppCompatActivity.RESULT_OK, finishWithResultOnEnd)
- activity?.finish()
- }
- }
+ private fun rootCauseException(e: Exception): Exception {
+ var rootCause = e
+ // JGit's TransportException hides the more helpful SSHJ exceptions.
+ // Also, SSHJ's UserAuthException about exhausting available authentication methods hides
+ // more useful exceptions.
+ while ((rootCause is org.eclipse.jgit.errors.TransportException ||
+ rootCause is org.eclipse.jgit.api.errors.TransportException ||
+ (rootCause is UserAuthException &&
+ rootCause.message == "Exhausted available authentication methods"))) {
+ rootCause = rootCause.cause as? Exception ?: break
}
- (SshSessionFactory.getInstance() as? SshjSessionFactory)?.clearCredentials()
- SshSessionFactory.setInstance(null)
+ return rootCause
}
-
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt
index 0498f6ed..35a3c648 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitConfigActivity.kt
@@ -9,6 +9,7 @@ import android.os.Handler
import android.util.Patterns
import androidx.core.content.edit
import androidx.core.os.postDelayed
+import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.zeapo.pwdstore.R
@@ -16,6 +17,7 @@ import com.zeapo.pwdstore.databinding.ActivityGitConfigBinding
import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PreferenceKeys
import com.zeapo.pwdstore.utils.viewBinding
+import kotlinx.coroutines.launch
import org.eclipse.jgit.lib.Constants
class GitConfigActivity : BaseGitActivity() {
@@ -47,8 +49,8 @@ class GitConfigActivity : BaseGitActivity() {
} catch (ignored: Exception) {
}
}
- binding.gitAbortRebase.setOnClickListener { launchGitOperation(BREAK_OUT_OF_DETACHED) }
- binding.gitResetToRemote.setOnClickListener { launchGitOperation(REQUEST_RESET) }
+ binding.gitAbortRebase.setOnClickListener { lifecycleScope.launch { launchGitOperation(BREAK_OUT_OF_DETACHED) } }
+ binding.gitResetToRemote.setOnClickListener { lifecycleScope.launch { launchGitOperation(REQUEST_RESET) } }
binding.saveButton.setOnClickListener {
val email = binding.gitUserEmail.text.toString().trim()
val name = binding.gitUserName.text.toString().trim()
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitOperationActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitOperationActivity.kt
index c3a46fd7..121a3402 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitOperationActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitOperationActivity.kt
@@ -8,19 +8,21 @@ import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
+import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.UserPreference
import com.zeapo.pwdstore.utils.PasswordRepository
+import kotlinx.coroutines.launch
open class GitOperationActivity : BaseGitActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
when (intent.extras?.getInt(REQUEST_ARG_OP)) {
- REQUEST_PULL -> syncRepository(REQUEST_PULL)
- REQUEST_PUSH -> syncRepository(REQUEST_PUSH)
- REQUEST_SYNC -> syncRepository(REQUEST_SYNC)
+ REQUEST_PULL -> lifecycleScope.launch { syncRepository(REQUEST_PULL) }
+ REQUEST_PUSH -> lifecycleScope.launch { syncRepository(REQUEST_PUSH) }
+ REQUEST_SYNC -> lifecycleScope.launch { syncRepository(REQUEST_SYNC) }
else -> {
setResult(RESULT_CANCELED)
finish()
@@ -54,7 +56,7 @@ open class GitOperationActivity : BaseGitActivity() {
*
* @param operation the operation to execute can be REQUEST_PULL or REQUEST_PUSH
*/
- private fun syncRepository(operation: Int) {
+ private suspend fun syncRepository(operation: Int) {
if (serverUser.isEmpty() || serverHostname.isEmpty() || url.isNullOrEmpty())
MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.set_information_dialog_text))
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt
index 6c87e0f6..4830b2c8 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitServerConfigActivity.kt
@@ -10,6 +10,7 @@ import android.view.View
import androidx.core.content.edit
import androidx.core.os.postDelayed
import androidx.core.widget.doOnTextChanged
+import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.zeapo.pwdstore.R
@@ -20,6 +21,7 @@ import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PreferenceKeys
import com.zeapo.pwdstore.utils.viewBinding
import java.io.IOException
+import kotlinx.coroutines.launch
/**
* Activity that encompasses both the initial clone as well as editing the server config for future
@@ -171,7 +173,7 @@ class GitServerConfigActivity : BaseGitActivity() {
.setPositiveButton(R.string.dialog_delete) { dialog, _ ->
try {
localDir.deleteRecursively()
- launchGitOperation(REQUEST_CLONE)
+ lifecycleScope.launch { launchGitOperation(REQUEST_CLONE) }
} catch (e: IOException) {
// TODO Handle the exception correctly if we are unable to delete the directory...
e.printStackTrace()
@@ -201,7 +203,7 @@ class GitServerConfigActivity : BaseGitActivity() {
e.printStackTrace()
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
}
- launchGitOperation(REQUEST_CLONE)
+ lifecycleScope.launch { launchGitOperation(REQUEST_CLONE) }
}
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt
deleted file mode 100644
index 0e36f46a..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.git
-
-import android.content.Intent
-import androidx.appcompat.app.AppCompatActivity
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.zeapo.pwdstore.R
-import java.io.File
-import org.eclipse.jgit.api.Git
-import org.eclipse.jgit.api.PullCommand
-
-/**
- * Creates a new git operation
- *
- * @param fileDir the git working tree directory
- * @param callingActivity the calling activity
- */
-class PullOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
-
- /**
- * Sets the command
- *
- * @return the current object
- */
- fun setCommand(): PullOperation {
- this.command = Git(repository)
- .pull()
- .setRebase(true)
- .setRemote("origin")
- return this
- }
-
- override fun execute() {
- (this.command as? PullCommand)?.setCredentialsProvider(this.provider)
- GitAsyncTask(callingActivity, this, Intent()).execute(this.command)
- }
-
- override fun onError(err: Exception) {
- super.onError(err)
- MaterialAlertDialogBuilder(callingActivity)
- .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
- .setMessage("Error occurred during the pull operation, " +
- callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
- err.message +
- "\nPlease check the FAQ for possible reasons why this error might occur.")
- .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
- .show()
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt
deleted file mode 100644
index 1d58b255..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.git
-
-import android.content.Intent
-import androidx.appcompat.app.AppCompatActivity
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.zeapo.pwdstore.R
-import java.io.File
-import org.eclipse.jgit.api.Git
-import org.eclipse.jgit.api.PushCommand
-
-/**
- * Creates a new git operation
- *
- * @param fileDir the git working tree directory
- * @param callingActivity the calling activity
- */
-class PushOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
-
- /**
- * Sets the command
- *
- * @return the current object
- */
- fun setCommand(): PushOperation {
- this.command = Git(repository)
- .push()
- .setPushAll()
- .setRemote("origin")
- return this
- }
-
- override fun execute() {
- (this.command as? PushCommand)?.setCredentialsProvider(this.provider)
- GitAsyncTask(callingActivity, this, Intent()).execute(this.command)
- }
-
- override fun onError(err: Exception) {
- // TODO handle the "Nothing to push" case
- super.onError(err)
- MaterialAlertDialogBuilder(callingActivity)
- .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
- .setMessage(callingActivity.getString(R.string.jgit_error_push_dialog_text) + err.message)
- .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
- .show()
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt
deleted file mode 100644
index 60a9fbf3..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.git
-
-import android.content.Intent
-import androidx.appcompat.app.AppCompatActivity
-import androidx.preference.PreferenceManager
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.zeapo.pwdstore.R
-import com.zeapo.pwdstore.utils.PreferenceKeys
-import java.io.File
-import org.eclipse.jgit.api.Git
-import org.eclipse.jgit.api.GitCommand
-import org.eclipse.jgit.api.ResetCommand
-import org.eclipse.jgit.api.TransportCommand
-
-/**
- * Creates a new git operation
- *
- * @param fileDir the git working tree directory
- * @param callingActivity the calling activity
- */
-class ResetToRemoteOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
-
- private lateinit var commands: List<GitCommand<out Any>>
-
- /**
- * Sets the command
- *
- * @return the current object
- */
- fun setCommands(): ResetToRemoteOperation {
- val remoteBranch = PreferenceManager
- .getDefaultSharedPreferences(callingActivity.applicationContext)
- .getString(PreferenceKeys.GIT_BRANCH_NAME, "master")
- val git = Git(repository)
- val cmds = arrayListOf(
- git.add().addFilepattern("."),
- git.fetch().setRemote("origin"),
- git.reset().setRef("origin/$remoteBranch").setMode(ResetCommand.ResetType.HARD)
- )
- if (git.branchList().call().none { it.name == remoteBranch }) {
- cmds.add(
- git.branchCreate().setName(remoteBranch).setForce(true)
- )
- }
- commands = cmds
- return this
- }
-
- override fun execute() {
- commands.filterIsInstance<TransportCommand<*, *>>().map { it.setCredentialsProvider(provider) }
- GitAsyncTask(callingActivity, this, Intent()).execute(*commands.toTypedArray())
- }
-
- override fun onError(err: Exception) {
- super.onError(err)
- MaterialAlertDialogBuilder(callingActivity)
- .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
- .setMessage("Error occurred during the sync operation, " +
- "\nPlease check the FAQ for possible reasons why this error might occur." +
- callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
- err)
- .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
- .show()
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt
deleted file mode 100644
index 386bdf1e..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.git
-
-import android.content.Intent
-import androidx.appcompat.app.AppCompatActivity
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.zeapo.pwdstore.R
-import java.io.File
-import org.eclipse.jgit.api.AddCommand
-import org.eclipse.jgit.api.CommitCommand
-import org.eclipse.jgit.api.Git
-import org.eclipse.jgit.api.PullCommand
-import org.eclipse.jgit.api.PushCommand
-import org.eclipse.jgit.api.StatusCommand
-
-/**
- * Creates a new git operation
- *
- * @param fileDir the git working tree directory
- * @param callingActivity the calling activity
- */
-class SyncOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
-
- private var addCommand: AddCommand? = null
- private var statusCommand: StatusCommand? = null
- private var commitCommand: CommitCommand? = null
- private var pullCommand: PullCommand? = null
- private var pushCommand: PushCommand? = null
-
- /**
- * Sets the command
- *
- * @return the current object
- */
- fun setCommands(): SyncOperation {
- val git = Git(repository)
- this.addCommand = git.add().addFilepattern(".")
- this.statusCommand = git.status()
- this.commitCommand = git.commit().setAll(true).setMessage("[Android Password Store] Sync")
- this.pullCommand = git.pull().setRebase(true).setRemote("origin")
- this.pushCommand = git.push().setPushAll().setRemote("origin")
- return this
- }
-
- override fun execute() {
- if (this.provider != null) {
- this.pullCommand?.setCredentialsProvider(this.provider)
- this.pushCommand?.setCredentialsProvider(this.provider)
- }
- GitAsyncTask(callingActivity, this, Intent()).execute(this.addCommand, this.statusCommand, this.commitCommand, this.pullCommand, this.pushCommand)
- }
-
- override fun onError(err: Exception) {
- super.onError(err)
- MaterialAlertDialogBuilder(callingActivity)
- .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
- .setMessage("Error occurred during the sync operation, " +
- "\nPlease check the FAQ for possible reasons why this error might occur." +
- callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
- err)
- .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
- .show()
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/operation/BreakOutOfDetached.kt b/app/src/main/java/com/zeapo/pwdstore/git/operation/BreakOutOfDetached.kt
new file mode 100644
index 00000000..b46b66cf
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/operation/BreakOutOfDetached.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore.git.operation
+
+import androidx.appcompat.app.AppCompatActivity
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.git.GitCommandExecutor
+import java.io.File
+import org.eclipse.jgit.api.RebaseCommand
+
+class BreakOutOfDetached(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
+
+ private val branchName = "conflicting-$remoteBranch-${System.currentTimeMillis()}"
+
+ override val commands = arrayOf(
+ // abort the rebase
+ git.rebase().setOperation(RebaseCommand.Operation.ABORT),
+ // git checkout -b conflict-branch
+ git.checkout().setCreateBranch(true).setName(branchName),
+ // push the changes
+ git.push().setRemote("origin"),
+ // switch back to ${gitBranch}
+ git.checkout().setName(remoteBranch),
+ )
+
+ override suspend fun execute() {
+ if (!git.repository.repositoryState.isRebasing) {
+ MaterialAlertDialogBuilder(callingActivity)
+ .setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title))
+ .setMessage("The repository is not rebasing, no need to push to another branch")
+ .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
+ callingActivity.finish()
+ }.show()
+ return
+ }
+ GitCommandExecutor(callingActivity, this).execute()
+ }
+
+ override fun onSuccess() {
+ MaterialAlertDialogBuilder(callingActivity)
+ .setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title))
+ .setMessage("There was a conflict when trying to rebase. " +
+ "Your local $remoteBranch branch was pushed to another branch named conflicting-$remoteBranch-....\n" +
+ "Use this branch to resolve conflict on your computer")
+ .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
+ callingActivity.finish()
+ }.show()
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/operation/CloneOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/operation/CloneOperation.kt
new file mode 100644
index 00000000..8c516eac
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/operation/CloneOperation.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore.git.operation
+
+import androidx.appcompat.app.AppCompatActivity
+import com.zeapo.pwdstore.git.GitCommandExecutor
+import java.io.File
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.api.GitCommand
+
+/**
+ * Creates a new clone operation
+ *
+ * @param fileDir the git working tree directory
+ * @param uri URL to clone the repository from
+ * @param callingActivity the calling activity
+ */
+class CloneOperation(fileDir: File, uri: String, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
+
+ override val commands: Array<GitCommand<out Any>> = arrayOf(
+ Git.cloneRepository().setBranch(remoteBranch).setDirectory(repository?.workTree).setURI(uri),
+ )
+
+ override suspend fun execute() {
+ GitCommandExecutor(callingActivity, this).execute()
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/operation/CredentialFinder.kt b/app/src/main/java/com/zeapo/pwdstore/git/operation/CredentialFinder.kt
new file mode 100644
index 00000000..423ceb80
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/operation/CredentialFinder.kt
@@ -0,0 +1,94 @@
+package com.zeapo.pwdstore.git.operation
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import androidx.annotation.StringRes
+import androidx.core.content.edit
+import androidx.fragment.app.FragmentActivity
+import com.google.android.material.checkbox.MaterialCheckBox
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.textfield.TextInputEditText
+import com.google.android.material.textfield.TextInputLayout
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.git.config.ConnectionMode
+import com.zeapo.pwdstore.git.config.InteractivePasswordFinder
+import com.zeapo.pwdstore.utils.PreferenceKeys
+import com.zeapo.pwdstore.utils.getEncryptedPrefs
+import com.zeapo.pwdstore.utils.requestInputFocusOnView
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
+
+class CredentialFinder(
+ val callingActivity: FragmentActivity,
+ val connectionMode: ConnectionMode
+) : InteractivePasswordFinder() {
+
+ override fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) {
+ val gitOperationPrefs = callingActivity.getEncryptedPrefs("git_operation")
+ val credentialPref: String
+ @StringRes val messageRes: Int
+ @StringRes val hintRes: Int
+ @StringRes val rememberRes: Int
+ @StringRes val errorRes: Int
+ when (connectionMode) {
+ ConnectionMode.SshKey -> {
+ credentialPref = PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE
+ messageRes = R.string.passphrase_dialog_text
+ hintRes = R.string.ssh_keygen_passphrase
+ rememberRes = R.string.git_operation_remember_passphrase
+ errorRes = R.string.git_operation_wrong_passphrase
+ }
+ ConnectionMode.Password -> {
+ // Could be either an SSH or an HTTPS password
+ credentialPref = PreferenceKeys.HTTPS_PASSWORD
+ messageRes = R.string.password_dialog_text
+ hintRes = R.string.git_operation_hint_password
+ rememberRes = R.string.git_operation_remember_password
+ errorRes = R.string.git_operation_wrong_password
+ }
+ else -> throw IllegalStateException("Only SshKey and Password connection mode ask for passwords")
+ }
+ val storedCredential = gitOperationPrefs.getString(credentialPref, null)
+ if (isRetry)
+ gitOperationPrefs.edit { remove(credentialPref) }
+ if (storedCredential == null) {
+ val layoutInflater = LayoutInflater.from(callingActivity)
+
+ @SuppressLint("InflateParams")
+ val dialogView = layoutInflater.inflate(R.layout.git_credential_layout, null)
+ val credentialLayout = dialogView.findViewById<TextInputLayout>(R.id.git_auth_passphrase_layout)
+ val editCredential = dialogView.findViewById<TextInputEditText>(R.id.git_auth_credential)
+ editCredential.setHint(hintRes)
+ val rememberCredential = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_credential)
+ rememberCredential.setText(rememberRes)
+ if (isRetry)
+ credentialLayout.error = callingActivity.resources.getString(errorRes)
+ MaterialAlertDialogBuilder(callingActivity).run {
+ setTitle(R.string.passphrase_dialog_title)
+ setMessage(messageRes)
+ setView(dialogView)
+ setPositiveButton(R.string.dialog_ok) { _, _ ->
+ val credential = editCredential.text.toString()
+ if (rememberCredential.isChecked) {
+ gitOperationPrefs.edit {
+ putString(credentialPref, credential)
+ }
+ }
+ cont.resume(credential)
+ }
+ setNegativeButton(R.string.dialog_cancel) { _, _ ->
+ cont.resume(null)
+ }
+ setOnCancelListener {
+ cont.resume(null)
+ }
+ create()
+ }.run {
+ requestInputFocusOnView<TextInputEditText>(R.id.git_auth_credential)
+ show()
+ }
+ } else {
+ cont.resume(storedCredential)
+ }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/operation/GitOperation.kt
index 90f72b7c..45fefd64 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/operation/GitOperation.kt
@@ -2,21 +2,18 @@
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
-package com.zeapo.pwdstore.git
+package com.zeapo.pwdstore.git.operation
-import android.annotation.SuppressLint
import android.content.Intent
-import android.view.LayoutInflater
-import androidx.annotation.StringRes
-import androidx.appcompat.app.AppCompatActivity
+import androidx.annotation.CallSuper
import androidx.core.content.edit
+import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager
-import com.google.android.material.checkbox.MaterialCheckBox
+import com.github.ajalt.timberkt.Timber.d
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.textfield.TextInputEditText
-import com.google.android.material.textfield.TextInputLayout
import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.UserPreference
+import com.zeapo.pwdstore.git.ErrorMessages
import com.zeapo.pwdstore.git.config.ConnectionMode
import com.zeapo.pwdstore.git.config.InteractivePasswordFinder
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
@@ -25,108 +22,34 @@ import com.zeapo.pwdstore.git.config.SshjSessionFactory
import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PreferenceKeys
import com.zeapo.pwdstore.utils.getEncryptedPrefs
-import com.zeapo.pwdstore.utils.requestInputFocusOnView
import java.io.File
-import kotlin.coroutines.Continuation
-import kotlin.coroutines.resume
import net.schmizz.sshj.userauth.password.PasswordFinder
+import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.GitCommand
+import org.eclipse.jgit.api.TransportCommand
import org.eclipse.jgit.errors.UnsupportedCredentialItem
-import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.transport.CredentialItem
import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.SshSessionFactory
import org.eclipse.jgit.transport.URIish
-
-private class GitOperationCredentialFinder(
- val callingActivity: AppCompatActivity,
- val connectionMode: ConnectionMode
-) : InteractivePasswordFinder() {
-
- override fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) {
- val gitOperationPrefs = callingActivity.getEncryptedPrefs("git_operation")
- val credentialPref: String
- @StringRes val messageRes: Int
- @StringRes val hintRes: Int
- @StringRes val rememberRes: Int
- @StringRes val errorRes: Int
- when (connectionMode) {
- ConnectionMode.SshKey -> {
- credentialPref = PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE
- messageRes = R.string.passphrase_dialog_text
- hintRes = R.string.ssh_keygen_passphrase
- rememberRes = R.string.git_operation_remember_passphrase
- errorRes = R.string.git_operation_wrong_passphrase
- }
- ConnectionMode.Password -> {
- // Could be either an SSH or an HTTPS password
- credentialPref = PreferenceKeys.HTTPS_PASSWORD
- messageRes = R.string.password_dialog_text
- hintRes = R.string.git_operation_hint_password
- rememberRes = R.string.git_operation_remember_password
- errorRes = R.string.git_operation_wrong_password
- }
- else -> throw IllegalStateException("Only SshKey and Password connection mode ask for passwords")
- }
- val storedCredential = gitOperationPrefs.getString(credentialPref, null)
- if (isRetry)
- gitOperationPrefs.edit { remove(credentialPref) }
- if (storedCredential == null) {
- val layoutInflater = LayoutInflater.from(callingActivity)
-
- @SuppressLint("InflateParams")
- val dialogView = layoutInflater.inflate(R.layout.git_credential_layout, null)
- val credentialLayout = dialogView.findViewById<TextInputLayout>(R.id.git_auth_passphrase_layout)
- val editCredential = dialogView.findViewById<TextInputEditText>(R.id.git_auth_credential)
- editCredential.setHint(hintRes)
- val rememberCredential = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_credential)
- rememberCredential.setText(rememberRes)
- if (isRetry)
- credentialLayout.error = callingActivity.resources.getString(errorRes)
- MaterialAlertDialogBuilder(callingActivity).run {
- setTitle(R.string.passphrase_dialog_title)
- setMessage(messageRes)
- setView(dialogView)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
- val credential = editCredential.text.toString()
- if (rememberCredential.isChecked) {
- gitOperationPrefs.edit {
- putString(credentialPref, credential)
- }
- }
- cont.resume(credential)
- }
- setNegativeButton(R.string.dialog_cancel) { _, _ ->
- cont.resume(null)
- }
- setOnCancelListener {
- cont.resume(null)
- }
- create()
- }.run {
- requestInputFocusOnView<TextInputEditText>(R.id.git_auth_credential)
- show()
- }
- } else {
- cont.resume(storedCredential)
- }
- }
-}
-
/**
* Creates a new git operation
*
* @param gitDir the git working tree directory
* @param callingActivity the calling activity
*/
-abstract class GitOperation(gitDir: File, internal val callingActivity: AppCompatActivity) {
+abstract class GitOperation(gitDir: File, internal val callingActivity: FragmentActivity) {
- protected val repository: Repository? = PasswordRepository.getRepository(gitDir)
- internal var provider: CredentialsProvider? = null
- internal var command: GitCommand<*>? = null
+ abstract val commands: Array<GitCommand<out Any>>
+ private var provider: CredentialsProvider? = null
private val sshKeyFile = callingActivity.filesDir.resolve(".ssh_key")
private val hostKeyFile = callingActivity.filesDir.resolve(".host_key")
+ protected val repository = PasswordRepository.getRepository(gitDir)
+ protected val git = Git(repository)
+ protected val remoteBranch = PreferenceManager
+ .getDefaultSharedPreferences(callingActivity.applicationContext)
+ .getString(PreferenceKeys.GIT_BRANCH_NAME, "master")
private class PasswordFinderCredentialsProvider(private val username: String, private val passwordFinder: PasswordFinder) : CredentialsProvider() {
@@ -181,12 +104,18 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: AppCompa
}
}
+ fun setCredentialProvider() {
+ provider?.let { credentialsProvider ->
+ commands.filterIsInstance<TransportCommand<*, *>>().forEach { it.setCredentialsProvider(credentialsProvider) }
+ }
+ }
+
/**
* Executes the GitCommand in an async task
*/
- abstract fun execute()
+ abstract suspend fun execute()
- fun executeAfterAuthentication(
+ suspend fun executeAfterAuthentication(
connectionMode: ConnectionMode,
username: String,
identity: SshApiSessionFactory.ApiIdentity?
@@ -207,12 +136,12 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: AppCompa
callingActivity.finish()
}.show()
} else {
- withPublicKeyAuthentication(username, GitOperationCredentialFinder(callingActivity,
+ withPublicKeyAuthentication(username, CredentialFinder(callingActivity,
connectionMode)).execute()
}
ConnectionMode.OpenKeychain -> withOpenKeychainAuthentication(username, identity).execute()
ConnectionMode.Password -> withPasswordAuthentication(
- username, GitOperationCredentialFinder(callingActivity, connectionMode)).execute()
+ username, CredentialFinder(callingActivity, connectionMode)).execute()
ConnectionMode.None -> execute()
}
}
@@ -220,6 +149,7 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: AppCompa
/**
* Action to execute on error
*/
+ @CallSuper
open fun onError(err: Exception) {
// Clear various auth related fields on failure
when (SshSessionFactory.getInstance()) {
@@ -236,6 +166,13 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: AppCompa
}
}
}
+ d(err)
+ MaterialAlertDialogBuilder(callingActivity)
+ .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
+ .setMessage(ErrorMessages[err])
+ .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
+ callingActivity.finish()
+ }.show()
}
/**
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/operation/PullOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/operation/PullOperation.kt
new file mode 100644
index 00000000..d31b2aa4
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/operation/PullOperation.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore.git.operation
+
+import androidx.appcompat.app.AppCompatActivity
+import com.zeapo.pwdstore.git.GitCommandExecutor
+import java.io.File
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.api.GitCommand
+
+/**
+ * Creates a new git operation
+ *
+ * @param fileDir the git working tree directory
+ * @param callingActivity the calling activity
+ */
+class PullOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
+
+ override val commands: Array<GitCommand<out Any>> = arrayOf(
+ Git(repository).pull().setRebase(true).setRemote("origin"),
+ )
+
+ override suspend fun execute() {
+ GitCommandExecutor(callingActivity, this).execute()
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/operation/PushOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/operation/PushOperation.kt
new file mode 100644
index 00000000..33b20a06
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/operation/PushOperation.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore.git.operation
+
+import androidx.appcompat.app.AppCompatActivity
+import com.zeapo.pwdstore.git.GitCommandExecutor
+import java.io.File
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.api.GitCommand
+
+/**
+ * Creates a new git operation
+ *
+ * @param fileDir the git working tree directory
+ * @param callingActivity the calling activity
+ */
+class PushOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
+
+ override val commands: Array<GitCommand<out Any>> = arrayOf(
+ Git(repository).push().setPushAll().setRemote("origin"),
+ )
+
+ override suspend fun execute() {
+ setCredentialProvider()
+ GitCommandExecutor(callingActivity, this).execute()
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/operation/ResetToRemoteOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/operation/ResetToRemoteOperation.kt
new file mode 100644
index 00000000..f0aee212
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/operation/ResetToRemoteOperation.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore.git.operation
+
+import androidx.appcompat.app.AppCompatActivity
+import com.zeapo.pwdstore.git.GitCommandExecutor
+import java.io.File
+import org.eclipse.jgit.api.ResetCommand
+
+/**
+ * Creates a new git operation
+ *
+ * @param fileDir the git working tree directory
+ * @param callingActivity the calling activity
+ */
+class ResetToRemoteOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
+
+ override val commands = arrayOf(
+ git.add().addFilepattern("."),
+ git.fetch().setRemote("origin"),
+ git.reset().setRef("origin/$remoteBranch").setMode(ResetCommand.ResetType.HARD),
+ git.branchCreate().setName(remoteBranch).setForce(true),
+ )
+
+ override suspend fun execute() {
+ GitCommandExecutor(callingActivity, this).execute()
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/operation/SyncOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/operation/SyncOperation.kt
new file mode 100644
index 00000000..6edf2994
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/operation/SyncOperation.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore.git.operation
+
+import androidx.appcompat.app.AppCompatActivity
+import com.zeapo.pwdstore.git.GitCommandExecutor
+import java.io.File
+
+/**
+ * Creates a new git operation
+ *
+ * @param fileDir the git working tree directory
+ * @param callingActivity the calling activity
+ */
+class SyncOperation(fileDir: File, callingActivity: AppCompatActivity) : GitOperation(fileDir, callingActivity) {
+
+ override val commands = arrayOf(
+ git.add().addFilepattern("."),
+ git.status(),
+ git.commit().setAll(true).setMessage("[Android Password Store] Sync"),
+ git.pull().setRebase(true).setRemote("origin"),
+ git.push().setPushAll().setRemote("origin"),
+ )
+
+ override suspend fun execute() {
+ GitCommandExecutor(callingActivity, this).execute()
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
index b274b47d..6ce45fe5 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
@@ -14,20 +14,18 @@ import android.view.View
import android.view.autofill.AutofillManager
import android.view.inputmethod.InputMethodManager
import androidx.annotation.IdRes
-import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
+import androidx.fragment.app.FragmentActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.github.ajalt.timberkt.d
import com.google.android.material.snackbar.Snackbar
-import com.zeapo.pwdstore.git.GitAsyncTask
-import com.zeapo.pwdstore.git.GitOperation
+import com.zeapo.pwdstore.git.GitCommandExecutor
+import com.zeapo.pwdstore.git.operation.GitOperation
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirectory
import java.io.File
-import org.eclipse.jgit.api.Git
const val OPENPGP_PROVIDER = "org.sufficientlysecure.keychain"
@@ -51,12 +49,14 @@ fun CharArray.clear() {
val Context.clipboard get() = getSystemService<ClipboardManager>()
-fun AppCompatActivity.snackbar(
+fun FragmentActivity.snackbar(
view: View = findViewById(android.R.id.content),
message: String,
- length: Int = Snackbar.LENGTH_SHORT
-) {
- Snackbar.make(view, message, length).show()
+ length: Int = Snackbar.LENGTH_SHORT,
+): Snackbar {
+ val snackbar = Snackbar.make(view, message, length)
+ snackbar.show()
+ return snackbar
}
fun File.listFilesRecursively() = walkTopDown().filter { !it.isDirectory }.toList()
@@ -97,24 +97,33 @@ fun Context.getEncryptedPrefs(fileName: String): SharedPreferences {
)
}
-@MainThread
-fun AppCompatActivity.commitChange(message: String, finishWithResultOnEnd: Intent? = null) {
+suspend fun FragmentActivity.commitChange(
+ message: String,
+ finishWithResultOnEnd: Intent? = null,
+ finishActivityOnEnd: Boolean = true,
+) {
if (!PasswordRepository.isGitRepo()) {
if (finishWithResultOnEnd != null) {
- setResult(AppCompatActivity.RESULT_OK, finishWithResultOnEnd)
+ setResult(FragmentActivity.RESULT_OK, finishWithResultOnEnd)
finish()
}
return
}
object : GitOperation(getRepositoryDirectory(this@commitChange), this@commitChange) {
- override fun execute() {
+ override val commands = arrayOf(
+ git.add().addFilepattern("."),
+ git.status(),
+ git.commit().setAll(true).setMessage(message),
+ )
+
+ override suspend fun execute() {
d { "Comitting with message: '$message'" }
- val git = Git(repository)
- val task = GitAsyncTask(this@commitChange, this, finishWithResultOnEnd, silentlyExecute = true)
- task.execute(
- git.add().addFilepattern("."),
- git.commit().setAll(true).setMessage(message)
- )
+ GitCommandExecutor(
+ this@commitChange,
+ this,
+ finishWithResultOnEnd,
+ finishActivityOnEnd,
+ ).execute()
}
}.execute()
}
@@ -124,7 +133,6 @@ fun AppCompatActivity.commitChange(message: String, finishWithResultOnEnd: Inten
* view whose id is [id]. Solution based on a StackOverflow
* answer: https://stackoverflow.com/a/13056259/297261
*/
-@MainThread
fun <T : View> AlertDialog.requestInputFocusOnView(@IdRes id: Int) {
setOnShowListener {
findViewById<T>(id)?.apply {
@@ -143,6 +151,6 @@ val Context.autofillManager: AutofillManager?
@RequiresApi(Build.VERSION_CODES.O)
get() = getSystemService()
-fun AppCompatActivity.isInsideRepository(file: File): Boolean {
+fun FragmentActivity.isInsideRepository(file: File): Boolean {
return file.canonicalPath.contains(getRepositoryDirectory(this).canonicalPath)
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt b/app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt
index 35eb7ae3..55d654e0 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt
@@ -13,7 +13,6 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.observe
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Result.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Result.kt
new file mode 100644
index 00000000..d152cba6
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/Result.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package com.zeapo.pwdstore.utils
+
+/**
+ * Emulates the Rust Result enum but without returning a value in the [Ok] case.
+ * https://doc.rust-lang.org/std/result/enum.Result.html
+ */
+sealed class Result {
+
+ object Ok : Result()
+ data class Err(val err: Exception) : Result()
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/UriTotpFinder.kt b/app/src/main/java/com/zeapo/pwdstore/utils/UriTotpFinder.kt
index aab72d38..23101a13 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/UriTotpFinder.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/UriTotpFinder.kt
@@ -57,6 +57,7 @@ class UriTotpFinder : TotpFinder {
}
companion object {
+
val TOTP_FIELDS = arrayOf(
"otpauth://totp",
"totp:"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9aaebf98..b5f1192d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -276,10 +276,6 @@
<string name="autofill_ins_1_hint">Screenshot of accessibility services</string>
<string name="autofill_ins_2_hint">Screenshot of toggle in accessibility services</string>
<string name="autofill_ins_3_hint">Screenshot of autofill service in action</string>
- <string name="git_pull_fail_error">Pull has failed, you\'re in a detached head. Using "settings > git utils", save your changes to the remote in a new branch and resolve the conflict on your computer.</string>
- <string name="git_push_nff_error">Push was rejected by remote, run pull before pushing again. You can use Synchronize rather than pull/push as it implements both</string>
- <string name="git_push_generic_error">Push was rejected by remote, reason:</string>
- <string name="git_push_other_error">Remote rejected non-fast-forward push. Check receive.denyNonFastForwards variable in config file of destination repository.</string>
<string name="jgit_error_push_dialog_text">Error occurred during the push operation:</string>
<string name="clear_saved_passphrase_ssh">Clear saved passphrase for local SSH key</string>
<string name="clear_saved_passphrase_https">Clear saved HTTPS password</string>
@@ -371,4 +367,12 @@
<string name="short_key_ids_unsupported">A key ID in .gpg-id is too short, please use either long key IDs (16 characters) or fingerprints (40 characters)</string>
<string name="invalid_filename_text">File name must not contain \'/\', set directory above</string>
<string name="directory_hint">Directory</string>
+
+ <!-- GitException messages -->
+ <string name="git_unknown_error">Unknown error</string>
+ <string name="git_pull_fail_error">Pull has failed, you\'re in a detached head. Using "settings > git utils", save your changes to the remote in a new branch and resolve the conflict on your computer.</string>
+ <string name="git_push_nff_error">Push was rejected by remote, run pull before pushing again. You can use Synchronize rather than pull/push as it implements both</string>
+ <string name="git_push_generic_error">Push was rejected by remote, reason: %1$s</string>
+ <string name="git_push_other_error">Remote rejected non-fast-forward push. Check receive.denyNonFastForwards variable in config file of destination repository.</string>
+ <string name="git_operation_running">Running git operation…</string>
</resources>