Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

Commit

Permalink
refactor: migrate to NIO
Browse files Browse the repository at this point in the history
  • Loading branch information
msfjarvis committed Jun 17, 2024
1 parent 67084fc commit b6c5107
Show file tree
Hide file tree
Showing 34 changed files with 402 additions and 337 deletions.
40 changes: 23 additions & 17 deletions app/src/main/java/app/passwordstore/data/password/PasswordItem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,25 @@ import android.content.Intent
import app.passwordstore.data.repo.PasswordRepository
import app.passwordstore.ui.crypto.BasePGPActivity
import app.passwordstore.ui.main.LaunchActivity
import java.io.File
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.nameWithoutExtension
import kotlin.io.path.pathString
import kotlin.io.path.relativeTo

data class PasswordItem(
val name: String,
val parent: PasswordItem? = null,
val type: Char,
val file: File,
val rootDir: File,
val file: Path,
val rootDir: Path,
) : Comparable<PasswordItem> {

val fullPathToParent = file.absolutePath.replace(rootDir.absolutePath, "").replace(file.name, "")
val name = file.nameWithoutExtension

val longName = BasePGPActivity.getLongName(fullPathToParent, rootDir.absolutePath, toString())
val fullPathToParent = file.relativeTo(rootDir).parent.pathString

val longName =
BasePGPActivity.getLongName(fullPathToParent, rootDir.absolutePathString(), toString())

override fun equals(other: Any?): Boolean {
return (other is PasswordItem) && (other.file == file)
Expand All @@ -32,7 +38,7 @@ data class PasswordItem(
}

override fun toString(): String {
return name.replace("\\.gpg$".toRegex(), "")
return name
}

override fun hashCode(): Int {
Expand All @@ -43,8 +49,8 @@ data class PasswordItem(
fun createAuthEnabledIntent(context: Context): Intent {
val intent = Intent(context, LaunchActivity::class.java)
intent.putExtra("NAME", toString())
intent.putExtra("FILE_PATH", file.absolutePath)
intent.putExtra("REPO_PATH", PasswordRepository.getRepositoryDirectory().absolutePath)
intent.putExtra("FILE_PATH", file.absolutePathString())
intent.putExtra("REPO_PATH", PasswordRepository.getRepositoryDirectory().absolutePathString())
intent.action = LaunchActivity.ACTION_DECRYPT_PASS
return intent
}
Expand All @@ -55,23 +61,23 @@ data class PasswordItem(
const val TYPE_PASSWORD = 'p'

@JvmStatic
fun newCategory(name: String, file: File, parent: PasswordItem, rootDir: File): PasswordItem {
return PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir)
fun newCategory(path: Path, parent: PasswordItem, rootDir: Path): PasswordItem {
return PasswordItem(parent, TYPE_CATEGORY, path, rootDir)
}

@JvmStatic
fun newCategory(name: String, file: File, rootDir: File): PasswordItem {
return PasswordItem(name, null, TYPE_CATEGORY, file, rootDir)
fun newCategory(path: Path, rootDir: Path): PasswordItem {
return PasswordItem(null, TYPE_CATEGORY, path, rootDir)
}

@JvmStatic
fun newPassword(name: String, file: File, parent: PasswordItem, rootDir: File): PasswordItem {
return PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir)
fun newPassword(path: Path, parent: PasswordItem, rootDir: Path): PasswordItem {
return PasswordItem(parent, TYPE_PASSWORD, path, rootDir)
}

@JvmStatic
fun newPassword(name: String, file: File, rootDir: File): PasswordItem {
return PasswordItem(name, null, TYPE_PASSWORD, file, rootDir)
fun newPassword(path: Path, rootDir: Path): PasswordItem {
return PasswordItem(null, TYPE_PASSWORD, path, rootDir)
}
}
}
81 changes: 19 additions & 62 deletions app/src/main/java/app/passwordstore/data/repo/PasswordRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ package app.passwordstore.data.repo

import androidx.core.content.edit
import app.passwordstore.Application
import app.passwordstore.data.password.PasswordItem
import app.passwordstore.util.extensions.sharedPrefs
import app.passwordstore.util.extensions.unsafeLazy
import app.passwordstore.util.settings.PasswordSortOrder
import app.passwordstore.util.settings.PreferenceKeys
import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching
import java.io.File
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.PathWalkOption
import kotlin.io.path.deleteRecursively
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import kotlin.io.path.walk
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.Repository
Expand All @@ -23,12 +27,13 @@ import org.eclipse.jgit.transport.RefSpec
import org.eclipse.jgit.transport.RemoteConfig
import org.eclipse.jgit.transport.URIish

@OptIn(ExperimentalPathApi::class)
object PasswordRepository {

var repository: Repository? = null
private val settings by unsafeLazy { Application.instance.sharedPrefs }
private val filesDir
get() = Application.instance.filesDir
get() = Application.instance.filesDir.toPath()

val isInitialized: Boolean
get() = repository != null
Expand All @@ -41,19 +46,20 @@ object PasswordRepository {
* Takes in a [repositoryDir] to initialize a Git repository with, and assigns it to [repository]
* as static state.
*/
private fun initializeRepository(repositoryDir: File) {
private fun initializeRepository(repositoryDir: Path) {
val builder = FileRepositoryBuilder()
repository =
runCatching { builder.setGitDir(repositoryDir).build() }
runCatching { builder.setGitDir(repositoryDir.toFile()).build() }
.getOrElse { e ->
e.printStackTrace()
null
}
}

fun createRepository(repositoryDir: File) {
repositoryDir.delete()
repository = Git.init().setDirectory(repositoryDir).call().repository
@OptIn(ExperimentalPathApi::class)
fun createRepository(repositoryDir: Path) {
repositoryDir.deleteRecursively()
repository = Git.init().setDirectory(repositoryDir.toFile()).call().repository
}

// TODO add multiple remotes support for pull/push
Expand Down Expand Up @@ -106,8 +112,8 @@ object PasswordRepository {
repository = null
}

fun getRepositoryDirectory(): File {
return File(filesDir.toString(), "/store")
fun getRepositoryDirectory(): Path {
return filesDir.resolve("store")
}

fun initialize(): Repository? {
Expand All @@ -116,8 +122,8 @@ object PasswordRepository {
settings.edit {
if (
!dir.exists() ||
!dir.isDirectory ||
requireNotNull(dir.listFiles()) { "Failed to list files in ${dir.path}" }.isEmpty()
!dir.isDirectory() ||
dir.walk(PathWalkOption.INCLUDE_DIRECTORIES).toList().isEmpty()
) {
putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)
} else {
Expand All @@ -141,53 +147,4 @@ object PasswordRepository {
null
}
}

/**
* Gets the .gpg files in a directory
*
* @param path the directory path
* @return the list of gpg files in that directory
*/
private fun getFilesList(path: File): ArrayList<File> {
if (!path.exists()) return ArrayList()
val files =
(path.listFiles { file -> file.isDirectory || file.extension == "gpg" } ?: emptyArray())
.toList()
val items = ArrayList<File>()
items.addAll(files)
return items
}

/**
* Gets the passwords (PasswordItem) in a directory
*
* @param path the directory path
* @return a list of password items
*/
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import app.passwordstore.data.password.PasswordItem
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.viewmodel.SearchableRepositoryAdapter
import app.passwordstore.util.viewmodel.stableId
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
import kotlin.io.path.listDirectoryEntries
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext

Expand Down Expand Up @@ -71,7 +74,10 @@ open class PasswordItemRecyclerAdapter(
folderIndicator.visibility = View.VISIBLE
val count =
withContext(dispatcherProvider.io()) {
item.file.listFiles { path -> path.isDirectory || path.extension == "gpg" }?.size ?: 0
item.file
.listDirectoryEntries()
.filter { it.isDirectory() || it.extension == "gpg" }
.size
}
childCount.visibility = if (count > 0) View.VISIBLE else View.GONE
childCount.text = "$count"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ import com.github.michaelbull.result.onSuccess
import com.github.michaelbull.result.runCatching
import dagger.hilt.android.AndroidEntryPoint
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import javax.inject.Inject
import kotlin.io.path.absolutePathString
import kotlin.io.path.readBytes
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import logcat.LogPriority.ERROR
Expand All @@ -55,12 +58,14 @@ class AutofillDecryptActivity : BasePGPActivity() {
override fun onStart() {
super.onStart()
val filePath =
intent?.getStringExtra(EXTRA_FILE_PATH)
?: run {
logcat(ERROR) { "AutofillDecryptActivity started without EXTRA_FILE_PATH" }
finish()
return
}
Paths.get(
intent?.getStringExtra(EXTRA_FILE_PATH)
?: run {
logcat(ERROR) { "AutofillDecryptActivity started without EXTRA_FILE_PATH" }
finish()
return
}
)
val clientState =
intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE)
?: run {
Expand Down Expand Up @@ -89,14 +94,17 @@ class AutofillDecryptActivity : BasePGPActivity() {
}

private fun decrypt(
filePath: String,
filePath: Path,
clientState: Bundle,
action: AutofillAction,
authResult: Result,
) {
val gpgIdentifiers =
getPGPIdentifiers(
getParentPath(filePath, PasswordRepository.getRepositoryDirectory().toString())
getParentPath(
filePath.absolutePathString(),
PasswordRepository.getRepositoryDirectory().toString(),
)
) ?: return
lifecycleScope.launch(dispatcherProvider.main()) {
when (authResult) {
Expand All @@ -116,13 +124,7 @@ class AutofillDecryptActivity : BasePGPActivity() {
gpgIdentifiers.first(),
)
if (cachedPassphrase != null) {
decryptWithPassphrase(
File(filePath),
gpgIdentifiers,
clientState,
action,
cachedPassphrase,
)
decryptWithPassphrase(filePath, gpgIdentifiers, clientState, action, cachedPassphrase)
} else {
askPassphrase(filePath, gpgIdentifiers, clientState, action)
}
Expand All @@ -132,13 +134,13 @@ class AutofillDecryptActivity : BasePGPActivity() {
}

private suspend fun askPassphrase(
filePath: String,
filePath: Path,
identifiers: List<PGPIdentifier>,
clientState: Bundle,
action: AutofillAction,
) {
if (!repository.isPasswordProtected(identifiers)) {
decryptWithPassphrase(File(filePath), identifiers, clientState, action, password = "")
decryptWithPassphrase(filePath, identifiers, clientState, action, password = "")
return
}
val dialog = PasswordDialog()
Expand All @@ -147,14 +149,14 @@ class AutofillDecryptActivity : BasePGPActivity() {
if (key == PasswordDialog.PASSWORD_RESULT_KEY) {
val value = bundle.getString(PasswordDialog.PASSWORD_RESULT_KEY)!!
lifecycleScope.launch(dispatcherProvider.main()) {
decryptWithPassphrase(File(filePath), identifiers, clientState, action, value)
decryptWithPassphrase(filePath, identifiers, clientState, action, value)
}
}
}
}

private suspend fun decryptWithPassphrase(
filePath: File,
filePath: Path,
identifiers: List<PGPIdentifier>,
clientState: Bundle,
action: AutofillAction,
Expand Down Expand Up @@ -182,7 +184,7 @@ class AutofillDecryptActivity : BasePGPActivity() {
}

private suspend fun decryptCredential(
file: File,
file: Path,
password: String,
identifiers: List<PGPIdentifier>,
): Credentials? {
Expand Down Expand Up @@ -225,19 +227,19 @@ class AutofillDecryptActivity : BasePGPActivity() {

private var decryptFileRequestCode = 1

fun makeDecryptFileIntent(file: File, forwardedExtras: Bundle, context: Context): Intent {
fun makeDecryptFileIntent(file: Path, forwardedExtras: Bundle, context: Context): Intent {
return Intent(context, AutofillDecryptActivity::class.java).apply {
putExtras(forwardedExtras)
putExtra(EXTRA_SEARCH_ACTION, true)
putExtra(EXTRA_FILE_PATH, file.absolutePath)
putExtra(EXTRA_FILE_PATH, file.absolutePathString())
}
}

fun makeDecryptFileIntentSender(file: File, context: Context): IntentSender {
fun makeDecryptFileIntentSender(file: Path, context: Context): IntentSender {
val intent =
Intent(context, AutofillDecryptActivity::class.java).apply {
putExtra(EXTRA_SEARCH_ACTION, false)
putExtra(EXTRA_FILE_PATH, file.absolutePath)
putExtra(EXTRA_FILE_PATH, file.absolutePathString())
}
return PendingIntent.getActivity(
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import app.passwordstore.util.viewmodel.SearchableRepositoryViewModel
import com.github.androidpasswordstore.autofillparser.FormOrigin
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.flow.collect
import kotlin.io.path.relativeTo
import kotlinx.coroutines.launch
import logcat.LogPriority.ERROR
import logcat.logcat
Expand Down
Loading

0 comments on commit b6c5107

Please sign in to comment.