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

Migrate file system operations to NIO #3076

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.name
import kotlin.io.path.nameWithoutExtension

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.absolutePathString().replace(rootDir.absolutePathString(), "").replace(file.name, "")

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 @@ -10,6 +10,7 @@ import android.view.MotionEvent
import android.view.View
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.view.isVisible
import androidx.recyclerview.selection.ItemDetailsLookup
import androidx.recyclerview.selection.Selection
import androidx.recyclerview.widget.RecyclerView
Expand All @@ -18,6 +19,11 @@ 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.ExperimentalPathApi
import kotlin.io.path.PathWalkOption
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
import kotlin.io.path.walk
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext

Expand Down Expand Up @@ -49,6 +55,7 @@ open class PasswordItemRecyclerAdapter(
return super.onSelectionChanged(listener) as PasswordItemRecyclerAdapter
}

@OptIn(ExperimentalPathApi::class)
class PasswordItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {

private val name: AppCompatTextView = itemView.findViewById(R.id.label)
Expand All @@ -71,9 +78,13 @@ 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
.walk(PathWalkOption.INCLUDE_DIRECTORIES)
.filter { it.isDirectory() || it.extension == "gpg" }
.toSet()
.size
}
childCount.visibility = if (count > 0) View.VISIBLE else View.GONE
childCount.isVisible = count > 0
childCount.text = "$count"
} else {
childCount.visibility = View.GONE
Expand Down
Loading