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

Commit 36b45f9

Browse files
committed
refactor: migrate to NIO
1 parent 7010ee8 commit 36b45f9

34 files changed

+402
-337
lines changed

app/src/main/java/app/passwordstore/data/password/PasswordItem.kt

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,25 @@ import android.content.Intent
99
import app.passwordstore.data.repo.PasswordRepository
1010
import app.passwordstore.ui.crypto.BasePGPActivity
1111
import app.passwordstore.ui.main.LaunchActivity
12-
import java.io.File
12+
import java.nio.file.Path
13+
import kotlin.io.path.absolutePathString
14+
import kotlin.io.path.nameWithoutExtension
15+
import kotlin.io.path.pathString
16+
import kotlin.io.path.relativeTo
1317

1418
data class PasswordItem(
15-
val name: String,
1619
val parent: PasswordItem? = null,
1720
val type: Char,
18-
val file: File,
19-
val rootDir: File,
21+
val file: Path,
22+
val rootDir: Path,
2023
) : Comparable<PasswordItem> {
2124

22-
val fullPathToParent = file.absolutePath.replace(rootDir.absolutePath, "").replace(file.name, "")
25+
val name = file.nameWithoutExtension
2326

24-
val longName = BasePGPActivity.getLongName(fullPathToParent, rootDir.absolutePath, toString())
27+
val fullPathToParent = file.relativeTo(rootDir).parent.pathString
28+
29+
val longName =
30+
BasePGPActivity.getLongName(fullPathToParent, rootDir.absolutePathString(), toString())
2531

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

3440
override fun toString(): String {
35-
return name.replace("\\.gpg$".toRegex(), "")
41+
return name
3642
}
3743

3844
override fun hashCode(): Int {
@@ -43,8 +49,8 @@ data class PasswordItem(
4349
fun createAuthEnabledIntent(context: Context): Intent {
4450
val intent = Intent(context, LaunchActivity::class.java)
4551
intent.putExtra("NAME", toString())
46-
intent.putExtra("FILE_PATH", file.absolutePath)
47-
intent.putExtra("REPO_PATH", PasswordRepository.getRepositoryDirectory().absolutePath)
52+
intent.putExtra("FILE_PATH", file.absolutePathString())
53+
intent.putExtra("REPO_PATH", PasswordRepository.getRepositoryDirectory().absolutePathString())
4854
intent.action = LaunchActivity.ACTION_DECRYPT_PASS
4955
return intent
5056
}
@@ -55,23 +61,23 @@ data class PasswordItem(
5561
const val TYPE_PASSWORD = 'p'
5662

5763
@JvmStatic
58-
fun newCategory(name: String, file: File, parent: PasswordItem, rootDir: File): PasswordItem {
59-
return PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir)
64+
fun newCategory(path: Path, parent: PasswordItem, rootDir: Path): PasswordItem {
65+
return PasswordItem(parent, TYPE_CATEGORY, path, rootDir)
6066
}
6167

6268
@JvmStatic
63-
fun newCategory(name: String, file: File, rootDir: File): PasswordItem {
64-
return PasswordItem(name, null, TYPE_CATEGORY, file, rootDir)
69+
fun newCategory(path: Path, rootDir: Path): PasswordItem {
70+
return PasswordItem(null, TYPE_CATEGORY, path, rootDir)
6571
}
6672

6773
@JvmStatic
68-
fun newPassword(name: String, file: File, parent: PasswordItem, rootDir: File): PasswordItem {
69-
return PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir)
74+
fun newPassword(path: Path, parent: PasswordItem, rootDir: Path): PasswordItem {
75+
return PasswordItem(parent, TYPE_PASSWORD, path, rootDir)
7076
}
7177

7278
@JvmStatic
73-
fun newPassword(name: String, file: File, rootDir: File): PasswordItem {
74-
return PasswordItem(name, null, TYPE_PASSWORD, file, rootDir)
79+
fun newPassword(path: Path, rootDir: Path): PasswordItem {
80+
return PasswordItem(null, TYPE_PASSWORD, path, rootDir)
7581
}
7682
}
7783
}

app/src/main/java/app/passwordstore/data/repo/PasswordRepository.kt

Lines changed: 19 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ package app.passwordstore.data.repo
66

77
import androidx.core.content.edit
88
import app.passwordstore.Application
9-
import app.passwordstore.data.password.PasswordItem
109
import app.passwordstore.util.extensions.sharedPrefs
1110
import app.passwordstore.util.extensions.unsafeLazy
12-
import app.passwordstore.util.settings.PasswordSortOrder
1311
import app.passwordstore.util.settings.PreferenceKeys
1412
import com.github.michaelbull.result.getOrElse
1513
import com.github.michaelbull.result.onFailure
1614
import com.github.michaelbull.result.runCatching
17-
import java.io.File
15+
import java.nio.file.Path
16+
import kotlin.io.path.ExperimentalPathApi
17+
import kotlin.io.path.PathWalkOption
18+
import kotlin.io.path.deleteRecursively
19+
import kotlin.io.path.exists
20+
import kotlin.io.path.isDirectory
21+
import kotlin.io.path.walk
1822
import org.eclipse.jgit.api.Git
1923
import org.eclipse.jgit.lib.Constants
2024
import org.eclipse.jgit.lib.Repository
@@ -23,12 +27,13 @@ import org.eclipse.jgit.transport.RefSpec
2327
import org.eclipse.jgit.transport.RemoteConfig
2428
import org.eclipse.jgit.transport.URIish
2529

30+
@OptIn(ExperimentalPathApi::class)
2631
object PasswordRepository {
2732

2833
var repository: Repository? = null
2934
private val settings by unsafeLazy { Application.instance.sharedPrefs }
3035
private val filesDir
31-
get() = Application.instance.filesDir
36+
get() = Application.instance.filesDir.toPath()
3237

3338
val isInitialized: Boolean
3439
get() = repository != null
@@ -41,19 +46,20 @@ object PasswordRepository {
4146
* Takes in a [repositoryDir] to initialize a Git repository with, and assigns it to [repository]
4247
* as static state.
4348
*/
44-
private fun initializeRepository(repositoryDir: File) {
49+
private fun initializeRepository(repositoryDir: Path) {
4550
val builder = FileRepositoryBuilder()
4651
repository =
47-
runCatching { builder.setGitDir(repositoryDir).build() }
52+
runCatching { builder.setGitDir(repositoryDir.toFile()).build() }
4853
.getOrElse { e ->
4954
e.printStackTrace()
5055
null
5156
}
5257
}
5358

54-
fun createRepository(repositoryDir: File) {
55-
repositoryDir.delete()
56-
repository = Git.init().setDirectory(repositoryDir).call().repository
59+
@OptIn(ExperimentalPathApi::class)
60+
fun createRepository(repositoryDir: Path) {
61+
repositoryDir.deleteRecursively()
62+
repository = Git.init().setDirectory(repositoryDir.toFile()).call().repository
5763
}
5864

5965
// TODO add multiple remotes support for pull/push
@@ -106,8 +112,8 @@ object PasswordRepository {
106112
repository = null
107113
}
108114

109-
fun getRepositoryDirectory(): File {
110-
return File(filesDir.toString(), "/store")
115+
fun getRepositoryDirectory(): Path {
116+
return filesDir.resolve("store")
111117
}
112118

113119
fun initialize(): Repository? {
@@ -116,8 +122,8 @@ object PasswordRepository {
116122
settings.edit {
117123
if (
118124
!dir.exists() ||
119-
!dir.isDirectory ||
120-
requireNotNull(dir.listFiles()) { "Failed to list files in ${dir.path}" }.isEmpty()
125+
!dir.isDirectory() ||
126+
dir.walk(PathWalkOption.INCLUDE_DIRECTORIES).toList().isEmpty()
121127
) {
122128
putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)
123129
} else {
@@ -141,53 +147,4 @@ object PasswordRepository {
141147
null
142148
}
143149
}
144-
145-
/**
146-
* Gets the .gpg files in a directory
147-
*
148-
* @param path the directory path
149-
* @return the list of gpg files in that directory
150-
*/
151-
private fun getFilesList(path: File): ArrayList<File> {
152-
if (!path.exists()) return ArrayList()
153-
val files =
154-
(path.listFiles { file -> file.isDirectory || file.extension == "gpg" } ?: emptyArray())
155-
.toList()
156-
val items = ArrayList<File>()
157-
items.addAll(files)
158-
return items
159-
}
160-
161-
/**
162-
* Gets the passwords (PasswordItem) in a directory
163-
*
164-
* @param path the directory path
165-
* @return a list of password items
166-
*/
167-
fun getPasswords(
168-
path: File,
169-
rootDir: File,
170-
sortOrder: PasswordSortOrder,
171-
): ArrayList<PasswordItem> {
172-
// We need to recover the passwords then parse the files
173-
val passList = getFilesList(path).also { it.sortBy { f -> f.name } }
174-
val passwordList = ArrayList<PasswordItem>()
175-
val showHidden = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false)
176-
177-
if (passList.size == 0) return passwordList
178-
if (!showHidden) {
179-
passList.filter { !it.isHidden }.toCollection(passList.apply { clear() })
180-
}
181-
passList.forEach { file ->
182-
passwordList.add(
183-
if (file.isFile) {
184-
PasswordItem.newPassword(file.name, file, rootDir)
185-
} else {
186-
PasswordItem.newCategory(file.name, file, rootDir)
187-
}
188-
)
189-
}
190-
passwordList.sortWith(sortOrder.comparator)
191-
return passwordList
192-
}
193150
}

app/src/main/java/app/passwordstore/ui/adapters/PasswordItemRecyclerAdapter.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import app.passwordstore.data.password.PasswordItem
1818
import app.passwordstore.util.coroutines.DispatcherProvider
1919
import app.passwordstore.util.viewmodel.SearchableRepositoryAdapter
2020
import app.passwordstore.util.viewmodel.stableId
21+
import kotlin.io.path.extension
22+
import kotlin.io.path.isDirectory
23+
import kotlin.io.path.listDirectoryEntries
2124
import kotlinx.coroutines.CoroutineScope
2225
import kotlinx.coroutines.withContext
2326

@@ -71,7 +74,10 @@ open class PasswordItemRecyclerAdapter(
7174
folderIndicator.visibility = View.VISIBLE
7275
val count =
7376
withContext(dispatcherProvider.io()) {
74-
item.file.listFiles { path -> path.isDirectory || path.extension == "gpg" }?.size ?: 0
77+
item.file
78+
.listDirectoryEntries()
79+
.filter { it.isDirectory() || it.extension == "gpg" }
80+
.size
7581
}
7682
childCount.visibility = if (count > 0) View.VISIBLE else View.GONE
7783
childCount.text = "$count"

app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ import com.github.michaelbull.result.onSuccess
3939
import com.github.michaelbull.result.runCatching
4040
import dagger.hilt.android.AndroidEntryPoint
4141
import java.io.ByteArrayOutputStream
42-
import java.io.File
42+
import java.nio.file.Path
43+
import java.nio.file.Paths
4344
import javax.inject.Inject
45+
import kotlin.io.path.absolutePathString
46+
import kotlin.io.path.readBytes
4447
import kotlinx.coroutines.launch
4548
import kotlinx.coroutines.withContext
4649
import logcat.LogPriority.ERROR
@@ -59,12 +62,14 @@ class AutofillDecryptActivity : BasePGPActivity() {
5962
override fun onStart() {
6063
super.onStart()
6164
val filePath =
62-
intent?.getStringExtra(EXTRA_FILE_PATH)
63-
?: run {
64-
logcat(ERROR) { "AutofillDecryptActivity started without EXTRA_FILE_PATH" }
65-
finish()
66-
return
67-
}
65+
Paths.get(
66+
intent?.getStringExtra(EXTRA_FILE_PATH)
67+
?: run {
68+
logcat(ERROR) { "AutofillDecryptActivity started without EXTRA_FILE_PATH" }
69+
finish()
70+
return
71+
}
72+
)
6873
val clientState =
6974
intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE)
7075
?: run {
@@ -93,14 +98,17 @@ class AutofillDecryptActivity : BasePGPActivity() {
9398
}
9499

95100
private fun decrypt(
96-
filePath: String,
101+
filePath: Path,
97102
clientState: Bundle,
98103
action: AutofillAction,
99104
authResult: BiometricResult,
100105
) {
101106
val gpgIdentifiers =
102107
getPGPIdentifiers(
103-
getParentPath(filePath, PasswordRepository.getRepositoryDirectory().toString())
108+
getParentPath(
109+
filePath.absolutePathString(),
110+
PasswordRepository.getRepositoryDirectory().toString(),
111+
)
104112
) ?: return
105113
lifecycleScope.launch(dispatcherProvider.main()) {
106114
when (authResult) {
@@ -126,13 +134,7 @@ class AutofillDecryptActivity : BasePGPActivity() {
126134
gpgIdentifiers.first(),
127135
)
128136
if (cachedPassphrase != null) {
129-
decryptWithPassphrase(
130-
File(filePath),
131-
gpgIdentifiers,
132-
clientState,
133-
action,
134-
cachedPassphrase,
135-
)
137+
decryptWithPassphrase(filePath, gpgIdentifiers, clientState, action, cachedPassphrase)
136138
} else {
137139
askPassphrase(filePath, gpgIdentifiers, clientState, action)
138140
}
@@ -142,13 +144,13 @@ class AutofillDecryptActivity : BasePGPActivity() {
142144
}
143145

144146
private suspend fun askPassphrase(
145-
filePath: String,
147+
filePath: Path,
146148
identifiers: List<PGPIdentifier>,
147149
clientState: Bundle,
148150
action: AutofillAction,
149151
) {
150152
if (!repository.isPasswordProtected(identifiers)) {
151-
decryptWithPassphrase(File(filePath), identifiers, clientState, action, password = "")
153+
decryptWithPassphrase(filePath, identifiers, clientState, action, password = "")
152154
return
153155
}
154156
val dialog =
@@ -162,14 +164,14 @@ class AutofillDecryptActivity : BasePGPActivity() {
162164
val value = bundle.getString(PasswordDialog.PASSWORD_PHRASE_KEY)!!
163165
clearCache = bundle.getBoolean(PasswordDialog.PASSWORD_CLEAR_KEY)
164166
lifecycleScope.launch(dispatcherProvider.main()) {
165-
decryptWithPassphrase(File(filePath), identifiers, clientState, action, value)
167+
decryptWithPassphrase(filePath, identifiers, clientState, action, value)
166168
}
167169
}
168170
}
169171
}
170172

171173
private suspend fun decryptWithPassphrase(
172-
filePath: File,
174+
filePath: Path,
173175
identifiers: List<PGPIdentifier>,
174176
clientState: Bundle,
175177
action: AutofillAction,
@@ -199,7 +201,7 @@ class AutofillDecryptActivity : BasePGPActivity() {
199201
}
200202

201203
private suspend fun decryptCredential(
202-
file: File,
204+
file: Path,
203205
password: String,
204206
identifiers: List<PGPIdentifier>,
205207
): Credentials? {
@@ -240,19 +242,19 @@ class AutofillDecryptActivity : BasePGPActivity() {
240242

241243
private var decryptFileRequestCode = 1
242244

243-
fun makeDecryptFileIntent(file: File, forwardedExtras: Bundle, context: Context): Intent {
245+
fun makeDecryptFileIntent(file: Path, forwardedExtras: Bundle, context: Context): Intent {
244246
return Intent(context, AutofillDecryptActivity::class.java).apply {
245247
putExtras(forwardedExtras)
246248
putExtra(EXTRA_SEARCH_ACTION, true)
247-
putExtra(EXTRA_FILE_PATH, file.absolutePath)
249+
putExtra(EXTRA_FILE_PATH, file.absolutePathString())
248250
}
249251
}
250252

251-
fun makeDecryptFileIntentSender(file: File, context: Context): IntentSender {
253+
fun makeDecryptFileIntentSender(file: Path, context: Context): IntentSender {
252254
val intent =
253255
Intent(context, AutofillDecryptActivity::class.java).apply {
254256
putExtra(EXTRA_SEARCH_ACTION, false)
255-
putExtra(EXTRA_FILE_PATH, file.absolutePath)
257+
putExtra(EXTRA_FILE_PATH, file.absolutePathString())
256258
}
257259
return PendingIntent.getActivity(
258260
context,

app/src/main/java/app/passwordstore/ui/autofill/AutofillFilterView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import app.passwordstore.util.viewmodel.SearchableRepositoryViewModel
3838
import com.github.androidpasswordstore.autofillparser.FormOrigin
3939
import dagger.hilt.android.AndroidEntryPoint
4040
import javax.inject.Inject
41-
import kotlinx.coroutines.flow.collect
41+
import kotlin.io.path.relativeTo
4242
import kotlinx.coroutines.launch
4343
import logcat.LogPriority.ERROR
4444
import logcat.logcat

0 commit comments

Comments
 (0)