app: refactor GitSettings and ProxyUtils and inject them using hilt

Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>
This commit is contained in:
Aditya Wasan 2021-05-22 17:25:43 +05:30 committed by Harsh Shandilya
parent 47099c723b
commit c3f8de99be
No known key found for this signature in database
GPG key ID: 366D7BBAD1031E80
13 changed files with 140 additions and 91 deletions

View file

@ -16,18 +16,24 @@ import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import com.github.ajalt.timberkt.Timber.DebugTree import com.github.ajalt.timberkt.Timber.DebugTree
import com.github.ajalt.timberkt.Timber.plant import com.github.ajalt.timberkt.Timber.plant
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import dev.msfjarvis.aps.injection.context.FilesDirPath
import dev.msfjarvis.aps.injection.prefs.SettingsPreferences
import dev.msfjarvis.aps.util.extensions.getString import dev.msfjarvis.aps.util.extensions.getString
import dev.msfjarvis.aps.util.extensions.sharedPrefs
import dev.msfjarvis.aps.util.git.sshj.setUpBouncyCastleForSshj import dev.msfjarvis.aps.util.git.sshj.setUpBouncyCastleForSshj
import dev.msfjarvis.aps.util.proxy.ProxyUtils import dev.msfjarvis.aps.util.proxy.ProxyUtils
import dev.msfjarvis.aps.util.settings.GitSettings
import dev.msfjarvis.aps.util.settings.PreferenceKeys import dev.msfjarvis.aps.util.settings.PreferenceKeys
import dev.msfjarvis.aps.util.settings.runMigrations import dev.msfjarvis.aps.util.settings.runMigrations
import javax.inject.Inject
@Suppress("Unused") @Suppress("Unused")
@HiltAndroidApp @HiltAndroidApp
class Application : android.app.Application(), SharedPreferences.OnSharedPreferenceChangeListener { class Application : android.app.Application(), SharedPreferences.OnSharedPreferenceChangeListener {
private val prefs by lazy { sharedPrefs } @Inject @SettingsPreferences lateinit var prefs: SharedPreferences
@Inject @FilesDirPath lateinit var filesDirPath: String
@Inject lateinit var proxyUtils: ProxyUtils
@Inject lateinit var gitSettings: GitSettings
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -42,8 +48,8 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
prefs.registerOnSharedPreferenceChangeListener(this) prefs.registerOnSharedPreferenceChangeListener(this)
setNightMode() setNightMode()
setUpBouncyCastleForSshj() setUpBouncyCastleForSshj()
runMigrations(applicationContext) runMigrations(filesDirPath, prefs, gitSettings)
ProxyUtils.setDefaultProxy() proxyUtils.setDefaultProxy()
} }
override fun onTerminate() { override fun onTerminate() {

View file

@ -12,6 +12,7 @@ import com.github.michaelbull.result.Result
import com.github.michaelbull.result.andThen import com.github.michaelbull.result.andThen
import com.github.michaelbull.result.mapError import com.github.michaelbull.result.mapError
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.R import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.util.extensions.getEncryptedGitPrefs import dev.msfjarvis.aps.util.extensions.getEncryptedGitPrefs
import dev.msfjarvis.aps.util.extensions.sharedPrefs import dev.msfjarvis.aps.util.extensions.sharedPrefs
@ -25,6 +26,7 @@ import dev.msfjarvis.aps.util.git.operation.SyncOperation
import dev.msfjarvis.aps.util.git.sshj.ContinuationContainerActivity import dev.msfjarvis.aps.util.git.sshj.ContinuationContainerActivity
import dev.msfjarvis.aps.util.settings.GitSettings import dev.msfjarvis.aps.util.settings.GitSettings
import dev.msfjarvis.aps.util.settings.PreferenceKeys import dev.msfjarvis.aps.util.settings.PreferenceKeys
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.schmizz.sshj.common.DisconnectReason import net.schmizz.sshj.common.DisconnectReason
@ -36,6 +38,7 @@ import net.schmizz.sshj.userauth.UserAuthException
* Abstract [AppCompatActivity] that holds some information that is commonly shared across * Abstract [AppCompatActivity] that holds some information that is commonly shared across
* git-related tasks and makes sense to be held here. * git-related tasks and makes sense to be held here.
*/ */
@AndroidEntryPoint
abstract class BaseGitActivity : ContinuationContainerActivity() { abstract class BaseGitActivity : ContinuationContainerActivity() {
/** Enum of possible Git operations than can be run through [launchGitOperation]. */ /** Enum of possible Git operations than can be run through [launchGitOperation]. */
@ -48,29 +51,31 @@ abstract class BaseGitActivity : ContinuationContainerActivity() {
SYNC, SYNC,
} }
@Inject lateinit var gitSettings: GitSettings
/** /**
* Attempt to launch the requested Git operation. * Attempt to launch the requested Git operation.
* @param operation The type of git operation to launch * @param operation The type of git operation to launch
*/ */
suspend fun launchGitOperation(operation: GitOp): Result<Unit, Throwable> { suspend fun launchGitOperation(operation: GitOp): Result<Unit, Throwable> {
if (GitSettings.url == null) { if (gitSettings.url == null) {
return Err(IllegalStateException("Git url is not set!")) return Err(IllegalStateException("Git url is not set!"))
} }
if (operation == GitOp.SYNC && !GitSettings.useMultiplexing) { if (operation == GitOp.SYNC && !gitSettings.useMultiplexing) {
// If the server does not support multiple SSH channels per connection, we cannot run // If the server does not support multiple SSH channels per connection, we cannot run
// a sync operation without reconnecting and thus break sync into its two parts. // a sync operation without reconnecting and thus break sync into its two parts.
return launchGitOperation(GitOp.PULL).andThen { launchGitOperation(GitOp.PUSH) } return launchGitOperation(GitOp.PULL).andThen { launchGitOperation(GitOp.PUSH) }
} }
val op = val op =
when (operation) { when (operation) {
GitOp.CLONE -> CloneOperation(this, GitSettings.url!!) GitOp.CLONE -> CloneOperation(this, gitSettings.url!!)
GitOp.PULL -> PullOperation(this, GitSettings.rebaseOnPull) GitOp.PULL -> PullOperation(this, gitSettings.rebaseOnPull)
GitOp.PUSH -> PushOperation(this) GitOp.PUSH -> PushOperation(this)
GitOp.SYNC -> SyncOperation(this, GitSettings.rebaseOnPull) GitOp.SYNC -> SyncOperation(this, gitSettings.rebaseOnPull)
GitOp.BREAK_OUT_OF_DETACHED -> BreakOutOfDetached(this) GitOp.BREAK_OUT_OF_DETACHED -> BreakOutOfDetached(this)
GitOp.RESET -> ResetToRemoteOperation(this) GitOp.RESET -> ResetToRemoteOperation(this)
} }
return op.executeAfterAuthentication(GitSettings.authMode).mapError(::transformGitError) return op.executeAfterAuthentication(gitSettings.authMode).mapError(::transformGitError)
} }
fun finishOnSuccessHandler(@Suppress("UNUSED_PARAMETER") nothing: Unit) { fun finishOnSuccessHandler(@Suppress("UNUSED_PARAMETER") nothing: Unit) {
@ -105,7 +110,7 @@ abstract class BaseGitActivity : ContinuationContainerActivity() {
val err = rootCauseException(throwable) val err = rootCauseException(throwable)
return when { return when {
err.message?.contains("cannot open additional channels") == true -> { err.message?.contains("cannot open additional channels") == true -> {
GitSettings.useMultiplexing = false gitSettings.useMultiplexing = false
SSHException( SSHException(
DisconnectReason.TOO_MANY_CONNECTIONS, DisconnectReason.TOO_MANY_CONNECTIONS,
"The server does not support multiple Git operations per SSH session. Please try again, a slower fallback mode will be used." "The server does not support multiple Git operations per SSH session. Please try again, a slower fallback mode will be used."

View file

@ -25,7 +25,6 @@ import dev.msfjarvis.aps.databinding.ActivityGitConfigBinding
import dev.msfjarvis.aps.ui.git.base.BaseGitActivity import dev.msfjarvis.aps.ui.git.base.BaseGitActivity
import dev.msfjarvis.aps.ui.git.log.GitLogActivity import dev.msfjarvis.aps.ui.git.log.GitLogActivity
import dev.msfjarvis.aps.util.extensions.viewBinding import dev.msfjarvis.aps.util.extensions.viewBinding
import dev.msfjarvis.aps.util.settings.GitSettings
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.Repository import org.eclipse.jgit.lib.Repository
@ -40,9 +39,9 @@ class GitConfigActivity : BaseGitActivity() {
setContentView(binding.root) setContentView(binding.root)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (GitSettings.authorName.isEmpty()) binding.gitUserName.requestFocus() if (gitSettings.authorName.isEmpty()) binding.gitUserName.requestFocus()
else binding.gitUserName.setText(GitSettings.authorName) else binding.gitUserName.setText(gitSettings.authorName)
binding.gitUserEmail.setText(GitSettings.authorEmail) binding.gitUserEmail.setText(gitSettings.authorEmail)
setupTools() setupTools()
binding.saveButton.setOnClickListener { binding.saveButton.setOnClickListener {
val email = binding.gitUserEmail.text.toString().trim() val email = binding.gitUserEmail.text.toString().trim()
@ -53,8 +52,8 @@ class GitConfigActivity : BaseGitActivity() {
.setPositiveButton(getString(R.string.dialog_ok), null) .setPositiveButton(getString(R.string.dialog_ok), null)
.show() .show()
} else { } else {
GitSettings.authorEmail = email gitSettings.authorEmail = email
GitSettings.authorName = name gitSettings.authorName = name
Snackbar.make( Snackbar.make(
binding.root, binding.root,
getString(R.string.git_server_config_save_success), getString(R.string.git_server_config_save_success),
@ -102,8 +101,8 @@ class GitConfigActivity : BaseGitActivity() {
setMessage( setMessage(
resources.getString( resources.getString(
R.string.git_break_out_of_detached_success, R.string.git_break_out_of_detached_success,
GitSettings.branch, gitSettings.branch,
"conflicting-${GitSettings.branch}-...", "conflicting-${gitSettings.branch}-...",
) )
) )
setOnDismissListener { finish() } setOnDismissListener { finish() }

View file

@ -54,7 +54,7 @@ class GitServerConfigActivity : BaseGitActivity() {
setContentView(binding.root) setContentView(binding.root)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
newAuthMode = GitSettings.authMode newAuthMode = gitSettings.authMode
binding.authModeGroup.apply { binding.authModeGroup.apply {
when (newAuthMode) { when (newAuthMode) {
@ -74,21 +74,21 @@ class GitServerConfigActivity : BaseGitActivity() {
} }
binding.serverUrl.setText( binding.serverUrl.setText(
GitSettings.url.also { gitSettings.url.also {
if (it.isNullOrEmpty()) return@also if (it.isNullOrEmpty()) return@also
setAuthModes(it.startsWith("http://") || it.startsWith("https://")) setAuthModes(it.startsWith("http://") || it.startsWith("https://"))
} }
) )
binding.serverBranch.setText(GitSettings.branch) binding.serverBranch.setText(gitSettings.branch)
binding.serverUrl.doOnTextChanged { text, _, _, _ -> binding.serverUrl.doOnTextChanged { text, _, _, _ ->
if (text.isNullOrEmpty()) return@doOnTextChanged if (text.isNullOrEmpty()) return@doOnTextChanged
setAuthModes(text.startsWith("http://") || text.startsWith("https://")) setAuthModes(text.startsWith("http://") || text.startsWith("https://"))
} }
binding.clearHostKeyButton.isVisible = GitSettings.hasSavedHostKey() binding.clearHostKeyButton.isVisible = gitSettings.hasSavedHostKey()
binding.clearHostKeyButton.setOnClickListener { binding.clearHostKeyButton.setOnClickListener {
GitSettings.clearSavedHostKey() gitSettings.clearSavedHostKey()
Snackbar.make( Snackbar.make(
binding.root, binding.root,
getString(R.string.clear_saved_host_key_success), getString(R.string.clear_saved_host_key_success),
@ -135,7 +135,7 @@ class GitServerConfigActivity : BaseGitActivity() {
return@setOnClickListener return@setOnClickListener
} }
when (val updateResult = when (val updateResult =
GitSettings.updateConnectionSettingsIfValid( gitSettings.updateConnectionSettingsIfValid(
newAuthMode = newAuthMode, newAuthMode = newAuthMode,
newUrl = binding.serverUrl.text.toString().trim(), newUrl = binding.serverUrl.text.toString().trim(),
newBranch = binding.serverBranch.text.toString().trim() newBranch = binding.serverBranch.text.toString().trim()

View file

@ -53,6 +53,7 @@ import me.zhanghai.android.fastscroll.FastScrollerBuilder
@AndroidEntryPoint @AndroidEntryPoint
class PasswordFragment : Fragment(R.layout.password_recycler_view) { class PasswordFragment : Fragment(R.layout.password_recycler_view) {
@Inject lateinit var gitSettings: GitSettings
@Inject lateinit var shortcutHandler: ShortcutHandler @Inject lateinit var shortcutHandler: ShortcutHandler
private lateinit var recyclerAdapter: PasswordItemRecyclerAdapter private lateinit var recyclerAdapter: PasswordItemRecyclerAdapter
private lateinit var listener: OnFragmentInteractionListener private lateinit var listener: OnFragmentInteractionListener
@ -111,7 +112,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
// When authentication is set to AuthMode.None then the only git operation we can // When authentication is set to AuthMode.None then the only git operation we can
// run is a pull, so automatically fallback to that. // run is a pull, so automatically fallback to that.
val operationId = val operationId =
when (GitSettings.authMode) { when (gitSettings.authMode) {
AuthMode.None -> BaseGitActivity.GitOp.PULL AuthMode.None -> BaseGitActivity.GitOp.PULL
else -> BaseGitActivity.GitOp.SYNC else -> BaseGitActivity.GitOp.SYNC
} }

View file

@ -243,7 +243,7 @@ class PasswordStore : BaseGitActivity() {
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val menuRes = val menuRes =
when { when {
GitSettings.authMode == AuthMode.None -> R.menu.main_menu_no_auth gitSettings.authMode == AuthMode.None -> R.menu.main_menu_no_auth
PasswordRepository.isGitRepo() -> R.menu.main_menu_git PasswordRepository.isGitRepo() -> R.menu.main_menu_git
else -> R.menu.main_menu_non_git else -> R.menu.main_menu_non_git
} }

View file

@ -13,6 +13,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.R import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.databinding.ActivityProxySelectorBinding import dev.msfjarvis.aps.databinding.ActivityProxySelectorBinding
import dev.msfjarvis.aps.util.extensions.getEncryptedProxyPrefs import dev.msfjarvis.aps.util.extensions.getEncryptedProxyPrefs
@ -21,12 +22,17 @@ import dev.msfjarvis.aps.util.extensions.viewBinding
import dev.msfjarvis.aps.util.proxy.ProxyUtils import dev.msfjarvis.aps.util.proxy.ProxyUtils
import dev.msfjarvis.aps.util.settings.GitSettings import dev.msfjarvis.aps.util.settings.GitSettings
import dev.msfjarvis.aps.util.settings.PreferenceKeys import dev.msfjarvis.aps.util.settings.PreferenceKeys
import javax.inject.Inject
private val IP_ADDRESS_REGEX = Patterns.IP_ADDRESS.toRegex() private val IP_ADDRESS_REGEX = Patterns.IP_ADDRESS.toRegex()
private val WEB_ADDRESS_REGEX = Patterns.WEB_URL.toRegex() private val WEB_ADDRESS_REGEX = Patterns.WEB_URL.toRegex()
@AndroidEntryPoint
class ProxySelectorActivity : AppCompatActivity() { class ProxySelectorActivity : AppCompatActivity() {
@Inject lateinit var gitSettings: GitSettings
@Inject lateinit var proxyUtils: ProxyUtils
private val binding by viewBinding(ActivityProxySelectorBinding::inflate) private val binding by viewBinding(ActivityProxySelectorBinding::inflate)
private val proxyPrefs by lazy(LazyThreadSafetyMode.NONE) { private val proxyPrefs by lazy(LazyThreadSafetyMode.NONE) {
applicationContext.getEncryptedProxyPrefs() applicationContext.getEncryptedProxyPrefs()
@ -59,19 +65,19 @@ class ProxySelectorActivity : AppCompatActivity() {
private fun saveSettings() { private fun saveSettings() {
proxyPrefs.edit { proxyPrefs.edit {
binding.proxyHost.text?.toString()?.takeIf { it.isNotEmpty() }.let { binding.proxyHost.text?.toString()?.takeIf { it.isNotEmpty() }.let {
GitSettings.proxyHost = it gitSettings.proxyHost = it
} }
binding.proxyUser.text?.toString()?.takeIf { it.isNotEmpty() }.let { binding.proxyUser.text?.toString()?.takeIf { it.isNotEmpty() }.let {
GitSettings.proxyUsername = it gitSettings.proxyUsername = it
} }
binding.proxyPort.text?.toString()?.takeIf { it.isNotEmpty() }?.let { binding.proxyPort.text?.toString()?.takeIf { it.isNotEmpty() }?.let {
GitSettings.proxyPort = it.toInt() gitSettings.proxyPort = it.toInt()
} }
binding.proxyPassword.text?.toString()?.takeIf { it.isNotEmpty() }.let { binding.proxyPassword.text?.toString()?.takeIf { it.isNotEmpty() }.let {
GitSettings.proxyPassword = it gitSettings.proxyPassword = it
} }
} }
ProxyUtils.setDefaultProxy() proxyUtils.setDefaultProxy()
Handler(Looper.getMainLooper()).postDelayed(500) { finish() } Handler(Looper.getMainLooper()).postDelayed(500) { finish() }
} }
} }

View file

@ -6,6 +6,7 @@
package dev.msfjarvis.aps.ui.settings package dev.msfjarvis.aps.ui.settings
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.os.Build import android.os.Build
import androidx.core.content.edit import androidx.core.content.edit
@ -14,6 +15,10 @@ import androidx.fragment.app.FragmentActivity
import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import de.Maxr1998.modernpreferences.Preference import de.Maxr1998.modernpreferences.Preference
import de.Maxr1998.modernpreferences.PreferenceScreen import de.Maxr1998.modernpreferences.PreferenceScreen
import de.Maxr1998.modernpreferences.helpers.checkBox import de.Maxr1998.modernpreferences.helpers.checkBox
@ -22,13 +27,13 @@ import de.Maxr1998.modernpreferences.helpers.onClick
import de.Maxr1998.modernpreferences.helpers.pref import de.Maxr1998.modernpreferences.helpers.pref
import dev.msfjarvis.aps.R import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.data.repo.PasswordRepository import dev.msfjarvis.aps.data.repo.PasswordRepository
import dev.msfjarvis.aps.injection.prefs.GitPreferences
import dev.msfjarvis.aps.ui.git.config.GitConfigActivity import dev.msfjarvis.aps.ui.git.config.GitConfigActivity
import dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity import dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity
import dev.msfjarvis.aps.ui.proxy.ProxySelectorActivity import dev.msfjarvis.aps.ui.proxy.ProxySelectorActivity
import dev.msfjarvis.aps.ui.sshkeygen.ShowSshKeyFragment import dev.msfjarvis.aps.ui.sshkeygen.ShowSshKeyFragment
import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity
import dev.msfjarvis.aps.ui.sshkeygen.SshKeyImportActivity import dev.msfjarvis.aps.ui.sshkeygen.SshKeyImportActivity
import dev.msfjarvis.aps.util.extensions.getEncryptedGitPrefs
import dev.msfjarvis.aps.util.extensions.getString import dev.msfjarvis.aps.util.extensions.getString
import dev.msfjarvis.aps.util.extensions.sharedPrefs import dev.msfjarvis.aps.util.extensions.sharedPrefs
import dev.msfjarvis.aps.util.extensions.snackbar import dev.msfjarvis.aps.util.extensions.snackbar
@ -37,9 +42,10 @@ import dev.msfjarvis.aps.util.settings.PreferenceKeys
class RepositorySettings(private val activity: FragmentActivity) : SettingsProvider { class RepositorySettings(private val activity: FragmentActivity) : SettingsProvider {
private val encryptedPreferences by lazy(LazyThreadSafetyMode.NONE) { private val hiltEntryPoint = EntryPointAccessors.fromApplication(activity.applicationContext, RepositorySettingsEntryPoint::class.java)
activity.getEncryptedGitPrefs() private val encryptedPreferences = hiltEntryPoint.encryptedPreferences()
} private val gitSettings = hiltEntryPoint.gitSettings()
private fun <T : FragmentActivity> launchActivity(clazz: Class<T>) { private fun <T : FragmentActivity> launchActivity(clazz: Class<T>) {
activity.startActivity(Intent(activity, clazz)) activity.startActivity(Intent(activity, clazz))
@ -74,7 +80,7 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi
} }
pref(PreferenceKeys.PROXY_SETTINGS) { pref(PreferenceKeys.PROXY_SETTINGS) {
titleRes = R.string.pref_edit_proxy_settings titleRes = R.string.pref_edit_proxy_settings
visible = GitSettings.url?.startsWith("https") == true && PasswordRepository.isGitRepo() visible = gitSettings.url?.startsWith("https") == true && PasswordRepository.isGitRepo()
onClick { onClick {
launchActivity(ProxySelectorActivity::class.java) launchActivity(ProxySelectorActivity::class.java)
true true
@ -206,4 +212,11 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi
} }
} }
} }
@EntryPoint
@InstallIn(SingletonComponent::class)
interface RepositorySettingsEntryPoint {
fun gitSettings(): GitSettings
@GitPreferences fun encryptedPreferences(): SharedPreferences
}
} }

View file

@ -10,6 +10,10 @@ import androidx.fragment.app.FragmentActivity
import com.github.michaelbull.result.Result import com.github.michaelbull.result.Result
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import dev.msfjarvis.aps.R import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.util.extensions.snackbar import dev.msfjarvis.aps.util.extensions.snackbar
import dev.msfjarvis.aps.util.git.GitException.PullException import dev.msfjarvis.aps.util.git.GitException.PullException
@ -30,6 +34,9 @@ class GitCommandExecutor(
private val operation: GitOperation, private val operation: GitOperation,
) { ) {
private val hiltEntryPoint = EntryPointAccessors.fromApplication(activity.applicationContext, GitCommandExecutorEntryPoint::class.java)
private val gitSettings = hiltEntryPoint.gitSettings()
suspend fun execute(): Result<Unit, Throwable> { suspend fun execute(): Result<Unit, Throwable> {
val snackbar = val snackbar =
activity.snackbar( activity.snackbar(
@ -49,8 +56,8 @@ class GitCommandExecutor(
// the previous status will eventually be used to avoid a commit // the previous status will eventually be used to avoid a commit
if (nbChanges > 0) { if (nbChanges > 0) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val name = GitSettings.authorName.ifEmpty { "root" } val name = gitSettings.authorName.ifEmpty { "root" }
val email = GitSettings.authorEmail.ifEmpty { "localhost" } val email = gitSettings.authorEmail.ifEmpty { "localhost" }
val identity = PersonIdent(name, email) val identity = PersonIdent(name, email)
command.setAuthor(identity).setCommitter(identity).call() command.setAuthor(identity).setCommitter(identity).call()
} }
@ -111,4 +118,10 @@ class GitCommandExecutor(
} }
.also { snackbar.dismiss() } .also { snackbar.dismiss() }
} }
@EntryPoint
@InstallIn(SingletonComponent::class)
interface GitCommandExecutorEntryPoint {
fun gitSettings(): GitSettings
}
} }

View file

@ -14,6 +14,11 @@ import com.github.michaelbull.result.Result
import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import dev.msfjarvis.aps.R import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.data.repo.PasswordRepository import dev.msfjarvis.aps.data.repo.PasswordRepository
import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity import dev.msfjarvis.aps.ui.sshkeygen.SshKeyGenActivity
@ -26,6 +31,8 @@ import dev.msfjarvis.aps.util.git.sshj.SshKey
import dev.msfjarvis.aps.util.git.sshj.SshjSessionFactory import dev.msfjarvis.aps.util.git.sshj.SshjSessionFactory
import dev.msfjarvis.aps.util.settings.AuthMode import dev.msfjarvis.aps.util.settings.AuthMode
import dev.msfjarvis.aps.util.settings.GitSettings import dev.msfjarvis.aps.util.settings.GitSettings
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -53,10 +60,11 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
abstract val commands: Array<GitCommand<out Any>> abstract val commands: Array<GitCommand<out Any>>
private val hostKeyFile = callingActivity.filesDir.resolve(".host_key") private val hostKeyFile = callingActivity.filesDir.resolve(".host_key")
private var sshSessionFactory: SshjSessionFactory? = null private var sshSessionFactory: SshjSessionFactory? = null
private val hiltEntryPoint = EntryPointAccessors.fromApplication(callingActivity.applicationContext, GitOperationEntryPoint::class.java)
protected val repository = PasswordRepository.getRepository(null)!! protected val repository = PasswordRepository.getRepository(null)!!
protected val git = Git(repository) protected val git = Git(repository)
protected val remoteBranch = GitSettings.branch protected val remoteBranch = hiltEntryPoint.gitSettings().branch
private val authActivity private val authActivity
get() = callingActivity as ContinuationContainerActivity get() = callingActivity as ContinuationContainerActivity
@ -220,4 +228,11 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
/** Timeout in seconds before [TransportCommand] will abort a stalled IO operation. */ /** Timeout in seconds before [TransportCommand] will abort a stalled IO operation. */
private const val CONNECT_TIMEOUT = 10 private const val CONNECT_TIMEOUT = 10
} }
// Using @EntryPoint seems to be our best option here, changing this to constructor injection would require a larger refactor.
@EntryPoint
@InstallIn(SingletonComponent::class)
interface GitOperationEntryPoint {
fun gitSettings(): GitSettings
}
} }

View file

@ -14,20 +14,20 @@ import java.net.Proxy
import java.net.ProxySelector import java.net.ProxySelector
import java.net.SocketAddress import java.net.SocketAddress
import java.net.URI import java.net.URI
import javax.inject.Inject
import javax.inject.Singleton
/** Utility class for [Proxy] handling. */ /** Utility class for [Proxy] handling. */
object ProxyUtils { @Singleton
class ProxyUtils @Inject constructor(private val gitSettings: GitSettings) {
private const val HTTP_PROXY_USER_PROPERTY = "http.proxyUser"
private const val HTTP_PROXY_PASSWORD_PROPERTY = "http.proxyPassword"
/** Set the default [Proxy] and [Authenticator] for the app based on user provided settings. */ /** Set the default [Proxy] and [Authenticator] for the app based on user provided settings. */
fun setDefaultProxy() { fun setDefaultProxy() {
ProxySelector.setDefault( ProxySelector.setDefault(
object : ProxySelector() { object : ProxySelector() {
override fun select(uri: URI?): MutableList<Proxy> { override fun select(uri: URI?): MutableList<Proxy> {
val host = GitSettings.proxyHost val host = gitSettings.proxyHost
val port = GitSettings.proxyPort val port = gitSettings.proxyPort
return if (host == null || port == -1) { return if (host == null || port == -1) {
mutableListOf(Proxy.NO_PROXY) mutableListOf(Proxy.NO_PROXY)
} else { } else {
@ -42,8 +42,8 @@ object ProxyUtils {
} }
} }
) )
val user = GitSettings.proxyUsername ?: "" val user = gitSettings.proxyUsername ?: ""
val password = GitSettings.proxyPassword ?: "" val password = gitSettings.proxyPassword ?: ""
if (user.isEmpty() || password.isEmpty()) { if (user.isEmpty() || password.isEmpty()) {
System.clearProperty(HTTP_PROXY_USER_PROPERTY) System.clearProperty(HTTP_PROXY_USER_PROPERTY)
System.clearProperty(HTTP_PROXY_PASSWORD_PROPERTY) System.clearProperty(HTTP_PROXY_PASSWORD_PROPERTY)
@ -63,4 +63,9 @@ object ProxyUtils {
} }
) )
} }
companion object {
private const val HTTP_PROXY_USER_PROPERTY = "http.proxyUser"
private const val HTTP_PROXY_PASSWORD_PROPERTY = "http.proxyPassword"
}
} }

View file

@ -4,16 +4,19 @@
*/ */
package dev.msfjarvis.aps.util.settings package dev.msfjarvis.aps.util.settings
import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import dev.msfjarvis.aps.Application
import dev.msfjarvis.aps.data.repo.PasswordRepository import dev.msfjarvis.aps.data.repo.PasswordRepository
import dev.msfjarvis.aps.util.extensions.getEncryptedGitPrefs import dev.msfjarvis.aps.injection.context.FilesDirPath
import dev.msfjarvis.aps.util.extensions.getEncryptedProxyPrefs import dev.msfjarvis.aps.injection.prefs.GitPreferences
import dev.msfjarvis.aps.injection.prefs.ProxyPreferences
import dev.msfjarvis.aps.injection.prefs.SettingsPreferences
import dev.msfjarvis.aps.util.extensions.getString import dev.msfjarvis.aps.util.extensions.getString
import dev.msfjarvis.aps.util.extensions.sharedPrefs
import java.io.File import java.io.File
import javax.inject.Inject
import javax.inject.Singleton
import org.eclipse.jgit.transport.URIish import org.eclipse.jgit.transport.URIish
enum class Protocol(val pref: String) { enum class Protocol(val pref: String) {
@ -48,29 +51,22 @@ enum class AuthMode(val pref: String) {
} }
} }
object GitSettings { @Singleton
class GitSettings @Inject constructor(
@SettingsPreferences private val settings: SharedPreferences,
@GitPreferences private val encryptedSettings: SharedPreferences,
@ProxyPreferences private val proxySettings: SharedPreferences,
@FilesDirPath private val filesDirPath: String,
) {
private const val DEFAULT_BRANCH = "master"
private val settings by lazy(LazyThreadSafetyMode.PUBLICATION) {
Application.instance.sharedPrefs
}
private val encryptedSettings by lazy(LazyThreadSafetyMode.PUBLICATION) {
Application.instance.getEncryptedGitPrefs()
}
private val proxySettings by lazy(LazyThreadSafetyMode.PUBLICATION) {
Application.instance.getEncryptedProxyPrefs()
}
private val hostKeyPath by lazy(LazyThreadSafetyMode.NONE) { private val hostKeyPath by lazy(LazyThreadSafetyMode.NONE) {
"${Application.instance.filesDir}/.host_key" "$filesDirPath/.host_key"
} }
var authMode var authMode
get() = AuthMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH)) get() = AuthMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH))
private set(value) { private set(value) {
settings.edit { putString(PreferenceKeys.GIT_REMOTE_AUTH, value.pref) } settings.edit { putString(PreferenceKeys.GIT_REMOTE_AUTH, value.pref) }
} }
var url var url
get() = settings.getString(PreferenceKeys.GIT_REMOTE_URL) get() = settings.getString(PreferenceKeys.GIT_REMOTE_URL)
private set(value) { private set(value) {
@ -84,55 +80,46 @@ object GitSettings {
encryptedSettings.edit { remove(PreferenceKeys.HTTPS_PASSWORD) } encryptedSettings.edit { remove(PreferenceKeys.HTTPS_PASSWORD) }
clearSavedHostKey() clearSavedHostKey()
} }
var authorName var authorName
get() = settings.getString(PreferenceKeys.GIT_CONFIG_AUTHOR_NAME) ?: "" get() = settings.getString(PreferenceKeys.GIT_CONFIG_AUTHOR_NAME) ?: ""
set(value) { set(value) {
settings.edit { putString(PreferenceKeys.GIT_CONFIG_AUTHOR_NAME, value) } settings.edit { putString(PreferenceKeys.GIT_CONFIG_AUTHOR_NAME, value) }
} }
var authorEmail var authorEmail
get() = settings.getString(PreferenceKeys.GIT_CONFIG_AUTHOR_EMAIL) ?: "" get() = settings.getString(PreferenceKeys.GIT_CONFIG_AUTHOR_EMAIL) ?: ""
set(value) { set(value) {
settings.edit { putString(PreferenceKeys.GIT_CONFIG_AUTHOR_EMAIL, value) } settings.edit { putString(PreferenceKeys.GIT_CONFIG_AUTHOR_EMAIL, value) }
} }
var branch var branch
get() = settings.getString(PreferenceKeys.GIT_BRANCH_NAME) ?: DEFAULT_BRANCH get() = settings.getString(PreferenceKeys.GIT_BRANCH_NAME) ?: DEFAULT_BRANCH
private set(value) { private set(value) {
settings.edit { putString(PreferenceKeys.GIT_BRANCH_NAME, value) } settings.edit { putString(PreferenceKeys.GIT_BRANCH_NAME, value) }
} }
var useMultiplexing var useMultiplexing
get() = settings.getBoolean(PreferenceKeys.GIT_REMOTE_USE_MULTIPLEXING, true) get() = settings.getBoolean(PreferenceKeys.GIT_REMOTE_USE_MULTIPLEXING, true)
set(value) { set(value) {
settings.edit { putBoolean(PreferenceKeys.GIT_REMOTE_USE_MULTIPLEXING, value) } settings.edit { putBoolean(PreferenceKeys.GIT_REMOTE_USE_MULTIPLEXING, value) }
} }
var proxyHost var proxyHost
get() = proxySettings.getString(PreferenceKeys.PROXY_HOST) get() = proxySettings.getString(PreferenceKeys.PROXY_HOST)
set(value) { set(value) {
proxySettings.edit { putString(PreferenceKeys.PROXY_HOST, value) } proxySettings.edit { putString(PreferenceKeys.PROXY_HOST, value) }
} }
var proxyPort var proxyPort
get() = proxySettings.getInt(PreferenceKeys.PROXY_PORT, -1) get() = proxySettings.getInt(PreferenceKeys.PROXY_PORT, -1)
set(value) { set(value) {
proxySettings.edit { putInt(PreferenceKeys.PROXY_PORT, value) } proxySettings.edit { putInt(PreferenceKeys.PROXY_PORT, value) }
} }
var proxyUsername var proxyUsername
get() = settings.getString(PreferenceKeys.PROXY_USERNAME) get() = settings.getString(PreferenceKeys.PROXY_USERNAME)
set(value) { set(value) {
proxySettings.edit { putString(PreferenceKeys.PROXY_USERNAME, value) } proxySettings.edit { putString(PreferenceKeys.PROXY_USERNAME, value) }
} }
var proxyPassword var proxyPassword
get() = proxySettings.getString(PreferenceKeys.PROXY_PASSWORD) get() = proxySettings.getString(PreferenceKeys.PROXY_PASSWORD)
set(value) { set(value) {
proxySettings.edit { putString(PreferenceKeys.PROXY_PASSWORD, value) } proxySettings.edit { putString(PreferenceKeys.PROXY_PASSWORD, value) }
} }
var rebaseOnPull var rebaseOnPull
get() = settings.getBoolean(PreferenceKeys.REBASE_ON_PULL, true) get() = settings.getBoolean(PreferenceKeys.REBASE_ON_PULL, true)
set(value) { set(value) {
@ -141,8 +128,7 @@ object GitSettings {
sealed class UpdateConnectionSettingsResult { sealed class UpdateConnectionSettingsResult {
class MissingUsername(val newProtocol: Protocol) : UpdateConnectionSettingsResult() class MissingUsername(val newProtocol: Protocol) : UpdateConnectionSettingsResult()
class AuthModeMismatch(val newProtocol: Protocol, val validModes: List<AuthMode>) : class AuthModeMismatch(val newProtocol: Protocol, val validModes: List<AuthMode>) : UpdateConnectionSettingsResult()
UpdateConnectionSettingsResult()
object Valid : UpdateConnectionSettingsResult() object Valid : UpdateConnectionSettingsResult()
object FailedToParseUrl : UpdateConnectionSettingsResult() object FailedToParseUrl : UpdateConnectionSettingsResult()
} }
@ -164,7 +150,6 @@ object GitSettings {
} }
if (newAuthMode != AuthMode.None && parsedUrl.user.isNullOrBlank()) if (newAuthMode != AuthMode.None && parsedUrl.user.isNullOrBlank())
return UpdateConnectionSettingsResult.MissingUsername(newProtocol) return UpdateConnectionSettingsResult.MissingUsername(newProtocol)
val validHttpsAuth = listOf(AuthMode.None, AuthMode.Password) val validHttpsAuth = listOf(AuthMode.None, AuthMode.Password)
val validSshAuth = listOf(AuthMode.OpenKeychain, AuthMode.Password, AuthMode.SshKey) val validSshAuth = listOf(AuthMode.OpenKeychain, AuthMode.Password, AuthMode.SshKey)
when { when {
@ -189,4 +174,8 @@ object GitSettings {
/** Returns true if a host key was previously saved */ /** Returns true if a host key was previously saved */
fun hasSavedHostKey(): Boolean = File(hostKeyPath).exists() fun hasSavedHostKey(): Boolean = File(hostKeyPath).exists()
companion object {
private const val DEFAULT_BRANCH = "master"
}
} }

View file

@ -6,7 +6,6 @@
package dev.msfjarvis.aps.util.settings package dev.msfjarvis.aps.util.settings
import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import com.github.ajalt.timberkt.e import com.github.ajalt.timberkt.e
@ -14,20 +13,18 @@ import com.github.ajalt.timberkt.i
import com.github.michaelbull.result.get import com.github.michaelbull.result.get
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import dev.msfjarvis.aps.util.extensions.getString import dev.msfjarvis.aps.util.extensions.getString
import dev.msfjarvis.aps.util.extensions.sharedPrefs
import dev.msfjarvis.aps.util.git.sshj.SshKey import dev.msfjarvis.aps.util.git.sshj.SshKey
import java.io.File import java.io.File
import java.net.URI import java.net.URI
fun runMigrations(context: Context) { fun runMigrations(filesDirPath: String, sharedPrefs: SharedPreferences, gitSettings: GitSettings) {
val sharedPrefs = context.sharedPrefs migrateToGitUrlBasedConfig(sharedPrefs, gitSettings)
migrateToGitUrlBasedConfig(sharedPrefs)
migrateToHideAll(sharedPrefs) migrateToHideAll(sharedPrefs)
migrateToSshKey(context, sharedPrefs) migrateToSshKey(filesDirPath, sharedPrefs)
migrateToClipboardHistory(sharedPrefs) migrateToClipboardHistory(sharedPrefs)
} }
private fun migrateToGitUrlBasedConfig(sharedPrefs: SharedPreferences) { private fun migrateToGitUrlBasedConfig(sharedPrefs: SharedPreferences, gitSettings: GitSettings) {
val serverHostname = sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_SERVER) ?: return val serverHostname = sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_SERVER) ?: return
i { "Migrating to URL-based Git config" } i { "Migrating to URL-based Git config" }
val serverPort = sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_PORT) ?: "" val serverPort = sharedPrefs.getString(PreferenceKeys.GIT_REMOTE_PORT) ?: ""
@ -76,10 +73,10 @@ private fun migrateToGitUrlBasedConfig(sharedPrefs: SharedPreferences) {
remove(PreferenceKeys.GIT_REMOTE_PROTOCOL) remove(PreferenceKeys.GIT_REMOTE_PROTOCOL)
} }
if (url == null || if (url == null ||
GitSettings.updateConnectionSettingsIfValid( gitSettings.updateConnectionSettingsIfValid(
newAuthMode = GitSettings.authMode, newAuthMode = gitSettings.authMode,
newUrl = url, newUrl = url,
newBranch = GitSettings.branch newBranch = gitSettings.branch
) != GitSettings.UpdateConnectionSettingsResult.Valid ) != GitSettings.UpdateConnectionSettingsResult.Valid
) { ) {
e { "Failed to migrate to URL-based Git config, generated URL is invalid" } e { "Failed to migrate to URL-based Git config, generated URL is invalid" }
@ -95,8 +92,8 @@ private fun migrateToHideAll(sharedPrefs: SharedPreferences) {
} }
} }
private fun migrateToSshKey(context: Context, sharedPrefs: SharedPreferences) { private fun migrateToSshKey(filesDirPath: String, sharedPrefs: SharedPreferences) {
val privateKeyFile = File(context.filesDir, ".ssh_key") val privateKeyFile = File(filesDirPath, ".ssh_key")
if (sharedPrefs.contains(PreferenceKeys.USE_GENERATED_KEY) && if (sharedPrefs.contains(PreferenceKeys.USE_GENERATED_KEY) &&
!SshKey.exists && !SshKey.exists &&
privateKeyFile.exists() privateKeyFile.exists()