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

Commit 445905b

Browse files
committed
refactor: migrate to NIO
1 parent 5eeb255 commit 445905b

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
@@ -36,8 +36,11 @@ import com.github.michaelbull.result.onSuccess
3636
import com.github.michaelbull.result.runCatching
3737
import dagger.hilt.android.AndroidEntryPoint
3838
import java.io.ByteArrayOutputStream
39-
import java.io.File
39+
import java.nio.file.Path
40+
import java.nio.file.Paths
4041
import javax.inject.Inject
42+
import kotlin.io.path.absolutePathString
43+
import kotlin.io.path.readBytes
4144
import kotlinx.coroutines.launch
4245
import kotlinx.coroutines.withContext
4346
import logcat.LogPriority.ERROR
@@ -55,12 +58,14 @@ class AutofillDecryptActivity : BasePGPActivity() {
5558
override fun onStart() {
5659
super.onStart()
5760
val filePath =
58-
intent?.getStringExtra(EXTRA_FILE_PATH)
59-
?: run {
60-
logcat(ERROR) { "AutofillDecryptActivity started without EXTRA_FILE_PATH" }
61-
finish()
62-
return
63-
}
61+
Paths.get(
62+
intent?.getStringExtra(EXTRA_FILE_PATH)
63+
?: run {
64+
logcat(ERROR) { "AutofillDecryptActivity started without EXTRA_FILE_PATH" }
65+
finish()
66+
return
67+
}
68+
)
6469
val clientState =
6570
intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE)
6671
?: run {
@@ -89,14 +94,17 @@ class AutofillDecryptActivity : BasePGPActivity() {
8994
}
9095

9196
private fun decrypt(
92-
filePath: String,
97+
filePath: Path,
9398
clientState: Bundle,
9499
action: AutofillAction,
95100
authResult: Result,
96101
) {
97102
val gpgIdentifiers =
98103
getPGPIdentifiers(
99-
getParentPath(filePath, PasswordRepository.getRepositoryDirectory().toString())
104+
getParentPath(
105+
filePath.absolutePathString(),
106+
PasswordRepository.getRepositoryDirectory().toString(),
107+
)
100108
) ?: return
101109
lifecycleScope.launch(dispatcherProvider.main()) {
102110
when (authResult) {
@@ -116,13 +124,7 @@ class AutofillDecryptActivity : BasePGPActivity() {
116124
gpgIdentifiers.first(),
117125
)
118126
if (cachedPassphrase != null) {
119-
decryptWithPassphrase(
120-
File(filePath),
121-
gpgIdentifiers,
122-
clientState,
123-
action,
124-
cachedPassphrase,
125-
)
127+
decryptWithPassphrase(filePath, gpgIdentifiers, clientState, action, cachedPassphrase)
126128
} else {
127129
askPassphrase(filePath, gpgIdentifiers, clientState, action)
128130
}
@@ -132,13 +134,13 @@ class AutofillDecryptActivity : BasePGPActivity() {
132134
}
133135

134136
private suspend fun askPassphrase(
135-
filePath: String,
137+
filePath: Path,
136138
identifiers: List<PGPIdentifier>,
137139
clientState: Bundle,
138140
action: AutofillAction,
139141
) {
140142
if (!repository.isPasswordProtected(identifiers)) {
141-
decryptWithPassphrase(File(filePath), identifiers, clientState, action, password = "")
143+
decryptWithPassphrase(filePath, identifiers, clientState, action, password = "")
142144
return
143145
}
144146
val dialog = PasswordDialog()
@@ -147,14 +149,14 @@ class AutofillDecryptActivity : BasePGPActivity() {
147149
if (key == PasswordDialog.PASSWORD_RESULT_KEY) {
148150
val value = bundle.getString(PasswordDialog.PASSWORD_RESULT_KEY)!!
149151
lifecycleScope.launch(dispatcherProvider.main()) {
150-
decryptWithPassphrase(File(filePath), identifiers, clientState, action, value)
152+
decryptWithPassphrase(filePath, identifiers, clientState, action, value)
151153
}
152154
}
153155
}
154156
}
155157

156158
private suspend fun decryptWithPassphrase(
157-
filePath: File,
159+
filePath: Path,
158160
identifiers: List<PGPIdentifier>,
159161
clientState: Bundle,
160162
action: AutofillAction,
@@ -182,7 +184,7 @@ class AutofillDecryptActivity : BasePGPActivity() {
182184
}
183185

184186
private suspend fun decryptCredential(
185-
file: File,
187+
file: Path,
186188
password: String,
187189
identifiers: List<PGPIdentifier>,
188190
): Credentials? {
@@ -225,19 +227,19 @@ class AutofillDecryptActivity : BasePGPActivity() {
225227

226228
private var decryptFileRequestCode = 1
227229

228-
fun makeDecryptFileIntent(file: File, forwardedExtras: Bundle, context: Context): Intent {
230+
fun makeDecryptFileIntent(file: Path, forwardedExtras: Bundle, context: Context): Intent {
229231
return Intent(context, AutofillDecryptActivity::class.java).apply {
230232
putExtras(forwardedExtras)
231233
putExtra(EXTRA_SEARCH_ACTION, true)
232-
putExtra(EXTRA_FILE_PATH, file.absolutePath)
234+
putExtra(EXTRA_FILE_PATH, file.absolutePathString())
233235
}
234236
}
235237

236-
fun makeDecryptFileIntentSender(file: File, context: Context): IntentSender {
238+
fun makeDecryptFileIntentSender(file: Path, context: Context): IntentSender {
237239
val intent =
238240
Intent(context, AutofillDecryptActivity::class.java).apply {
239241
putExtra(EXTRA_SEARCH_ACTION, false)
240-
putExtra(EXTRA_FILE_PATH, file.absolutePath)
242+
putExtra(EXTRA_FILE_PATH, file.absolutePathString())
241243
}
242244
return PendingIntent.getActivity(
243245
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)