Convert PasswordRepository to an object

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
Harsh Shandilya 2020-10-19 00:00:52 +05:30
parent 95d53e495c
commit 30c8c27770
No known key found for this signature in database
GPG key ID: 366D7BBAD1031E80
4 changed files with 192 additions and 200 deletions

View file

@ -50,9 +50,6 @@ import com.zeapo.pwdstore.ui.dialogs.FolderCreationDialogFragment
import com.zeapo.pwdstore.ui.onboarding.activity.OnboardingActivity import com.zeapo.pwdstore.ui.onboarding.activity.OnboardingActivity
import com.zeapo.pwdstore.utils.PasswordItem import com.zeapo.pwdstore.utils.PasswordItem
import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepository
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirectory
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.initialize
import com.zeapo.pwdstore.utils.PreferenceKeys import com.zeapo.pwdstore.utils.PreferenceKeys
import com.zeapo.pwdstore.utils.base64 import com.zeapo.pwdstore.utils.base64
import com.zeapo.pwdstore.utils.commitChange import com.zeapo.pwdstore.utils.commitChange
@ -101,7 +98,7 @@ class PasswordStore : BaseGitActivity() {
val intentData = result.data ?: return@registerForActivityResult val intentData = result.data ?: return@registerForActivityResult
val filesToMove = requireNotNull(intentData.getStringArrayExtra("Files")) val filesToMove = requireNotNull(intentData.getStringArrayExtra("Files"))
val target = File(requireNotNull(intentData.getStringExtra("SELECTED_FOLDER_PATH"))) val target = File(requireNotNull(intentData.getStringExtra("SELECTED_FOLDER_PATH")))
val repositoryPath = getRepositoryDirectory().absolutePath val repositoryPath = PasswordRepository.getRepositoryDirectory().absolutePath
if (!target.isDirectory) { if (!target.isDirectory) {
e { "Tried moving passwords to a non-existing folder." } e { "Tried moving passwords to a non-existing folder." }
return@registerForActivityResult return@registerForActivityResult
@ -158,7 +155,7 @@ class PasswordStore : BaseGitActivity() {
} }
} }
else -> { else -> {
val repoDir = getRepositoryDirectory().absolutePath val repoDir = PasswordRepository.getRepositoryDirectory().absolutePath
val relativePath = getRelativePath("${target.absolutePath}/", repoDir) val relativePath = getRelativePath("${target.absolutePath}/", repoDir)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
commitChange( commitChange(
@ -204,7 +201,7 @@ class PasswordStore : BaseGitActivity() {
setContentView(R.layout.activity_pwdstore) setContentView(R.layout.activity_pwdstore)
model.currentDir.observe(this) { dir -> model.currentDir.observe(this) { dir ->
val basePath = getRepositoryDirectory().absoluteFile val basePath = PasswordRepository.getRepositoryDirectory().absoluteFile
supportActionBar!!.apply { supportActionBar!!.apply {
if (dir != basePath) if (dir != basePath)
title = dir.name title = dir.name
@ -384,11 +381,11 @@ class PasswordStore : BaseGitActivity() {
} }
private fun checkLocalRepository() { private fun checkLocalRepository() {
val repo = initialize() val repo = PasswordRepository.initialize()
if (repo == null) { if (repo == null) {
directorySelectAction.launch(UserPreference.createDirectorySelectionIntent(this)) directorySelectAction.launch(UserPreference.createDirectorySelectionIntent(this))
} else { } else {
checkLocalRepository(getRepositoryDirectory()) checkLocalRepository(PasswordRepository.getRepositoryDirectory())
} }
} }
@ -400,7 +397,7 @@ class PasswordStore : BaseGitActivity() {
settings.getBoolean(PreferenceKeys.REPO_CHANGED, false)) { settings.getBoolean(PreferenceKeys.REPO_CHANGED, false)) {
settings.edit { putBoolean(PreferenceKeys.REPO_CHANGED, false) } settings.edit { putBoolean(PreferenceKeys.REPO_CHANGED, false) }
val args = Bundle() val args = Bundle()
args.putString(REQUEST_ARG_PATH, getRepositoryDirectory().absolutePath) args.putString(REQUEST_ARG_PATH, PasswordRepository.getRepositoryDirectory().absolutePath)
// if the activity was started from the autofill settings, the // if the activity was started from the autofill settings, the
// intent is to match a clicked pwd with app. pass this to fragment // intent is to match a clicked pwd with app. pass this to fragment
@ -426,8 +423,8 @@ class PasswordStore : BaseGitActivity() {
} }
private fun getLastChangedTimestamp(fullPath: String): Long { private fun getLastChangedTimestamp(fullPath: String): Long {
val repoPath = getRepositoryDirectory() val repoPath = PasswordRepository.getRepositoryDirectory()
val repository = getRepository(repoPath) val repository = PasswordRepository.getRepository(repoPath)
if (repository == null) { if (repository == null) {
d { "getLastChangedTimestamp: No git repository" } d { "getLastChangedTimestamp: No git repository" }
return File(fullPath).lastModified() return File(fullPath).lastModified()
@ -450,7 +447,7 @@ class PasswordStore : BaseGitActivity() {
for (intent in arrayOf(decryptIntent, authDecryptIntent)) { for (intent in arrayOf(decryptIntent, authDecryptIntent)) {
intent.putExtra("NAME", item.toString()) intent.putExtra("NAME", item.toString())
intent.putExtra("FILE_PATH", item.file.absolutePath) intent.putExtra("FILE_PATH", item.file.absolutePath)
intent.putExtra("REPO_PATH", getRepositoryDirectory().absolutePath) intent.putExtra("REPO_PATH", PasswordRepository.getRepositoryDirectory().absolutePath)
intent.putExtra("LAST_CHANGED_TIMESTAMP", getLastChangedTimestamp(item.file.absolutePath)) intent.putExtra("LAST_CHANGED_TIMESTAMP", getLastChangedTimestamp(item.file.absolutePath))
} }
// Needs an action to be a shortcut intent // Needs an action to be a shortcut intent
@ -494,7 +491,7 @@ class PasswordStore : BaseGitActivity() {
i { "Adding file to : ${currentDir.absolutePath}" } i { "Adding file to : ${currentDir.absolutePath}" }
val intent = Intent(this, PasswordCreationActivity::class.java) val intent = Intent(this, PasswordCreationActivity::class.java)
intent.putExtra("FILE_PATH", currentDir.absolutePath) intent.putExtra("FILE_PATH", currentDir.absolutePath)
intent.putExtra("REPO_PATH", getRepositoryDirectory().absolutePath) intent.putExtra("REPO_PATH", PasswordRepository.getRepositoryDirectory().absolutePath)
listRefreshAction.launch(intent) listRefreshAction.launch(intent)
} }
@ -530,7 +527,7 @@ class PasswordStore : BaseGitActivity() {
refreshPasswordList() refreshPasswordList()
AutofillMatcher.updateMatches(applicationContext, delete = filesToDelete) AutofillMatcher.updateMatches(applicationContext, delete = filesToDelete)
val fmt = selectedItems.joinToString(separator = ", ") { item -> val fmt = selectedItems.joinToString(separator = ", ") { item ->
item.file.toRelativeString(getRepositoryDirectory()) item.file.toRelativeString(PasswordRepository.getRepositoryDirectory())
} }
lifecycleScope.launch { lifecycleScope.launch {
commitChange( commitChange(
@ -644,7 +641,7 @@ class PasswordStore : BaseGitActivity() {
} }
private val currentDir: File private val currentDir: File
get() = getPasswordFragment()?.currentDir ?: getRepositoryDirectory() get() = getPasswordFragment()?.currentDir ?: PasswordRepository.getRepositoryDirectory()
private suspend fun moveFile(source: File, destinationFile: File) { private suspend fun moveFile(source: File, destinationFile: File) {
val sourceDestinationMap = if (source.isDirectory) { val sourceDestinationMap = if (source.isDirectory) {
@ -674,7 +671,7 @@ class PasswordStore : BaseGitActivity() {
fun matchPasswordWithApp(item: PasswordItem) { fun matchPasswordWithApp(item: PasswordItem) {
val path = item.file val path = item.file
.absolutePath .absolutePath
.replace(getRepositoryDirectory().toString() + "/", "") .replace(PasswordRepository.getRepositoryDirectory().toString() + "/", "")
.replace(".gpg", "") .replace(".gpg", "")
val data = Intent() val data = Intent()
data.putExtra("path", path) data.putExtra("path", path)

View file

@ -22,7 +22,6 @@ import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.crypto.BasePgpActivity import com.zeapo.pwdstore.crypto.BasePgpActivity
import com.zeapo.pwdstore.crypto.GetKeyIdsActivity import com.zeapo.pwdstore.crypto.GetKeyIdsActivity
import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirectory
import com.zeapo.pwdstore.utils.commitChange import com.zeapo.pwdstore.utils.commitChange
import com.zeapo.pwdstore.utils.requestInputFocusOnView import com.zeapo.pwdstore.utils.requestInputFocusOnView
import java.io.File import java.io.File
@ -41,7 +40,7 @@ class FolderCreationDialogFragment : DialogFragment() {
val repo = PasswordRepository.getRepository(null) val repo = PasswordRepository.getRepository(null)
if (repo != null) { if (repo != null) {
lifecycleScope.launch { lifecycleScope.launch {
val repoPath = getRepositoryDirectory().absolutePath val repoPath = PasswordRepository.getRepositoryDirectory().absolutePath
requireActivity().commitChange( requireActivity().commitChange(
getString( getString(
R.string.git_commit_gpg_id, R.string.git_commit_gpg_id,

View file

@ -6,7 +6,6 @@ package com.zeapo.pwdstore.utils
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 com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirectory
import java.io.File import java.io.File
import java.util.Date import java.util.Date
import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.ObjectId
@ -50,10 +49,10 @@ fun File.contains(other: File): Boolean {
/** /**
* Checks if this [File] is in the password repository directory as given * Checks if this [File] is in the password repository directory as given
* by [getRepositoryDirectory] * by [PasswordRepository.getRepositoryDirectory]
*/ */
fun File.isInsideRepository(): Boolean { fun File.isInsideRepository(): Boolean {
return canonicalPath.contains(getRepositoryDirectory().canonicalPath) return canonicalPath.contains(PasswordRepository.getRepositoryDirectory().canonicalPath)
} }
/** /**

View file

@ -7,9 +7,9 @@ package com.zeapo.pwdstore.utils
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.edit import androidx.core.content.edit
import com.github.michaelbull.result.runCatching
import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching
import com.zeapo.pwdstore.Application import com.zeapo.pwdstore.Application
import java.io.File import java.io.File
import java.io.FileFilter import java.io.FileFilter
@ -24,7 +24,7 @@ import org.eclipse.jgit.transport.URIish
import org.eclipse.jgit.util.FS import org.eclipse.jgit.util.FS
import org.eclipse.jgit.util.FS_POSIX_Java6 import org.eclipse.jgit.util.FS_POSIX_Java6
open class PasswordRepository protected constructor() { object PasswordRepository {
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
private class FS_POSIX_Java6_with_optional_symlinks : FS_POSIX_Java6() { private class FS_POSIX_Java6_with_optional_symlinks : FS_POSIX_Java6() {
@ -51,191 +51,188 @@ open class PasswordRepository protected constructor() {
} }
} }
companion object { private var repository: Repository? = null
private val settings by lazy(LazyThreadSafetyMode.NONE) { Application.instance.sharedPrefs }
private val filesDir
get() = Application.instance.filesDir
private var repository: Repository? = null /**
private val settings by lazy(LazyThreadSafetyMode.NONE) { Application.instance.sharedPrefs } * Returns the git repository
private val filesDir *
get() = Application.instance.filesDir * @param localDir needed only on the creation
* @return the git repository
/** */
* Returns the git repository @JvmStatic
* fun getRepository(localDir: File?): Repository? {
* @param localDir needed only on the creation if (repository == null && localDir != null) {
* @return the git repository val builder = FileRepositoryBuilder()
*/ repository = runCatching {
@JvmStatic builder.run {
fun getRepository(localDir: File?): Repository? { gitDir = localDir
if (repository == null && localDir != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val builder = FileRepositoryBuilder() fs = Java7FSFactory().detect(null)
repository = runCatching {
builder.run {
gitDir = localDir
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
fs = Java7FSFactory().detect(null)
}
readEnvironment()
}.build()
}.getOrElse { e ->
e.printStackTrace()
null
}
}
return repository
}
@JvmStatic
val isInitialized: Boolean
get() = repository != null
@JvmStatic
fun isGitRepo(): Boolean {
if (repository != null) {
return repository!!.objectDatabase.exists()
}
return false
}
@JvmStatic
@Throws(Exception::class)
fun createRepository(localDir: File) {
localDir.delete()
Git.init().setDirectory(localDir).call()
getRepository(localDir)
}
// TODO add multiple remotes support for pull/push
@JvmStatic
fun addRemote(name: String, url: String, replace: Boolean = false) {
val storedConfig = repository!!.config
val remotes = storedConfig.getSubsections("remote")
if (!remotes.contains(name)) {
runCatching {
val uri = URIish(url)
val refSpec = RefSpec("+refs/head/*:refs/remotes/$name/*")
val remoteConfig = RemoteConfig(storedConfig, name)
remoteConfig.addFetchRefSpec(refSpec)
remoteConfig.addPushRefSpec(refSpec)
remoteConfig.addURI(uri)
remoteConfig.addPushURI(uri)
remoteConfig.update(storedConfig)
storedConfig.save()
}.onFailure { e ->
e.printStackTrace()
}
} else if (replace) {
runCatching {
val uri = URIish(url)
val remoteConfig = RemoteConfig(storedConfig, name)
// remove the first and eventually the only uri
if (remoteConfig.urIs.size > 0) {
remoteConfig.removeURI(remoteConfig.urIs[0])
}
if (remoteConfig.pushURIs.size > 0) {
remoteConfig.removePushURI(remoteConfig.pushURIs[0])
} }
readEnvironment()
}.build()
}.getOrElse { e ->
e.printStackTrace()
null
}
}
return repository
}
remoteConfig.addURI(uri) @JvmStatic
remoteConfig.addPushURI(uri) val isInitialized: Boolean
get() = repository != null
remoteConfig.update(storedConfig) @JvmStatic
fun isGitRepo(): Boolean {
if (repository != null) {
return repository!!.objectDatabase.exists()
}
return false
}
storedConfig.save() @JvmStatic
}.onFailure { e -> @Throws(Exception::class)
e.printStackTrace() fun createRepository(localDir: File) {
localDir.delete()
Git.init().setDirectory(localDir).call()
getRepository(localDir)
}
// TODO add multiple remotes support for pull/push
@JvmStatic
fun addRemote(name: String, url: String, replace: Boolean = false) {
val storedConfig = repository!!.config
val remotes = storedConfig.getSubsections("remote")
if (!remotes.contains(name)) {
runCatching {
val uri = URIish(url)
val refSpec = RefSpec("+refs/head/*:refs/remotes/$name/*")
val remoteConfig = RemoteConfig(storedConfig, name)
remoteConfig.addFetchRefSpec(refSpec)
remoteConfig.addPushRefSpec(refSpec)
remoteConfig.addURI(uri)
remoteConfig.addPushURI(uri)
remoteConfig.update(storedConfig)
storedConfig.save()
}.onFailure { e ->
e.printStackTrace()
}
} else if (replace) {
runCatching {
val uri = URIish(url)
val remoteConfig = RemoteConfig(storedConfig, name)
// remove the first and eventually the only uri
if (remoteConfig.urIs.size > 0) {
remoteConfig.removeURI(remoteConfig.urIs[0])
} }
} if (remoteConfig.pushURIs.size > 0) {
} remoteConfig.removePushURI(remoteConfig.pushURIs[0])
@JvmStatic
fun closeRepository() {
if (repository != null) repository!!.close()
repository = null
}
@JvmStatic
fun getRepositoryDirectory(): File {
return if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)) {
val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
if (externalRepo != null)
File(externalRepo)
else
File(filesDir.toString(), "/store")
} else {
File(filesDir.toString(), "/store")
}
}
@JvmStatic
fun initialize(): Repository? {
val dir = getRepositoryDirectory()
// uninitialize the repo if the dir does not exist or is absolutely empty
settings.edit {
if (!dir.exists() || !dir.isDirectory || requireNotNull(dir.listFiles()).isEmpty()) {
putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)
} else {
putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true)
} }
remoteConfig.addURI(uri)
remoteConfig.addPushURI(uri)
remoteConfig.update(storedConfig)
storedConfig.save()
}.onFailure { e ->
e.printStackTrace()
} }
// create the repository static variable in PasswordRepository
return getRepository(File(dir.absolutePath + "/.git"))
}
/**
* Gets the .gpg files in a directory
*
* @param path the directory path
* @return the list of gpg files in that directory
*/
@JvmStatic
fun getFilesList(path: File?): ArrayList<File> {
if (path == null || !path.exists()) return ArrayList()
val directories = (path.listFiles(FileFilter { pathname -> pathname.isDirectory })
?: emptyArray()).toList()
val files = (path.listFiles(FileFilter { pathname -> pathname.extension == "gpg" })
?: emptyArray()).toList()
val items = ArrayList<File>()
items.addAll(directories)
items.addAll(files)
return items
}
/**
* Gets the passwords (PasswordItem) in a directory
*
* @param path the directory path
* @return a list of password items
*/
@JvmStatic
fun getPasswords(path: File, rootDir: File, sortOrder: PasswordSortOrder): ArrayList<PasswordItem> {
// We need to recover the passwords then parse the files
val passList = getFilesList(path).also { it.sortBy { f -> f.name } }
val passwordList = ArrayList<PasswordItem>()
val showHidden = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false)
if (passList.size == 0) return passwordList
if (!showHidden) {
passList.filter { !it.isHidden }.toCollection(passList.apply { clear() })
}
passList.forEach { file ->
passwordList.add(if (file.isFile) {
PasswordItem.newPassword(file.name, file, rootDir)
} else {
PasswordItem.newCategory(file.name, file, rootDir)
})
}
passwordList.sortWith(sortOrder.comparator)
return passwordList
} }
} }
@JvmStatic
fun closeRepository() {
if (repository != null) repository!!.close()
repository = null
}
@JvmStatic
fun getRepositoryDirectory(): File {
return if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)) {
val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
if (externalRepo != null)
File(externalRepo)
else
File(filesDir.toString(), "/store")
} else {
File(filesDir.toString(), "/store")
}
}
@JvmStatic
fun initialize(): Repository? {
val dir = getRepositoryDirectory()
// uninitialize the repo if the dir does not exist or is absolutely empty
settings.edit {
if (!dir.exists() || !dir.isDirectory || requireNotNull(dir.listFiles()).isEmpty()) {
putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)
} else {
putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true)
}
}
// create the repository static variable in PasswordRepository
return getRepository(File(dir.absolutePath + "/.git"))
}
/**
* Gets the .gpg files in a directory
*
* @param path the directory path
* @return the list of gpg files in that directory
*/
@JvmStatic
fun getFilesList(path: File?): ArrayList<File> {
if (path == null || !path.exists()) return ArrayList()
val directories = (path.listFiles(FileFilter { pathname -> pathname.isDirectory })
?: emptyArray()).toList()
val files = (path.listFiles(FileFilter { pathname -> pathname.extension == "gpg" })
?: emptyArray()).toList()
val items = ArrayList<File>()
items.addAll(directories)
items.addAll(files)
return items
}
/**
* Gets the passwords (PasswordItem) in a directory
*
* @param path the directory path
* @return a list of password items
*/
@JvmStatic
fun getPasswords(path: File, rootDir: File, sortOrder: PasswordSortOrder): ArrayList<PasswordItem> {
// We need to recover the passwords then parse the files
val passList = getFilesList(path).also { it.sortBy { f -> f.name } }
val passwordList = ArrayList<PasswordItem>()
val showHidden = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false)
if (passList.size == 0) return passwordList
if (!showHidden) {
passList.filter { !it.isHidden }.toCollection(passList.apply { clear() })
}
passList.forEach { file ->
passwordList.add(if (file.isFile) {
PasswordItem.newPassword(file.name, file, rootDir)
} else {
PasswordItem.newCategory(file.name, file, rootDir)
})
}
passwordList.sortWith(sortOrder.comparator)
return passwordList
}
} }