Skip to content

Commit b060155

Browse files
authored
Merge pull request #4050 from seelchen/bugfix/missing-glob-support
Add missing glob support
2 parents 12d4990 + 58a39d9 commit b060155

File tree

17 files changed

+2013
-853
lines changed

17 files changed

+2013
-853
lines changed

app/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ dependencies {
151151
testImplementation "org.jsoup:jsoup:$jsoupVersion"
152152
testImplementation "androidx.room:room-migration:$roomVersion"
153153
testImplementation "io.mockk:mockk:$mockkVersion"
154+
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutineTestVersion"
155+
testImplementation "androidx.arch.core:core-testing:$androidXArchCoreTestVersion"
154156
kaptTest "com.google.auto.service:auto-service:1.0-rc4"
155157

156158
androidTestImplementation "junit:junit:$junitVersion"//tests the app logic
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (C) 2014-2024 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>,
3+
* Emmanuel Messulam<emmanuelbendavid@gmail.com>, Raymond Lai <airwave209gt at gmail.com> and Contributors.
4+
*
5+
* This file is part of Amaze File Manager.
6+
*
7+
* Amaze File Manager is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem
22+
23+
import android.content.Context
24+
import com.amaze.filemanager.filesystem.HybridFileParcelable
25+
import com.amaze.filemanager.filesystem.root.ListFilesCommand.listFiles
26+
27+
class BasicSearch(
28+
query: String,
29+
path: String,
30+
searchParameters: SearchParameters,
31+
context: Context
32+
) : FileSearch(query, path, searchParameters) {
33+
private val applicationContext = context.applicationContext
34+
35+
override suspend fun search(filter: SearchFilter) {
36+
listFiles(
37+
path,
38+
SearchParameter.ROOT in searchParameters,
39+
SearchParameter.SHOW_HIDDEN_FILES in searchParameters,
40+
{ }
41+
) { hybridFileParcelable: HybridFileParcelable ->
42+
if (SearchParameter.SHOW_HIDDEN_FILES in searchParameters ||
43+
!hybridFileParcelable.isHidden
44+
) {
45+
val resultRange =
46+
filter.searchFilter(hybridFileParcelable.getName(applicationContext))
47+
if (resultRange != null) {
48+
publishProgress(hybridFileParcelable, resultRange)
49+
}
50+
}
51+
}
52+
}
53+
}

app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/searchfilesystem/DeepSearch.kt

Lines changed: 12 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -21,69 +21,36 @@
2121
package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem
2222

2323
import android.content.Context
24-
import androidx.lifecycle.MutableLiveData
2524
import com.amaze.filemanager.fileoperations.filesystem.OpenMode
2625
import com.amaze.filemanager.filesystem.HybridFile
27-
import com.amaze.filemanager.filesystem.HybridFileParcelable
2826
import kotlinx.coroutines.isActive
2927
import org.slf4j.LoggerFactory
30-
import java.util.Locale
31-
import java.util.regex.Pattern
3228
import kotlin.coroutines.coroutineContext
3329

3430
class DeepSearch(
31+
query: String,
32+
path: String,
33+
searchParameters: SearchParameters,
3534
context: Context,
36-
private val query: String,
37-
private val path: String,
38-
private val openMode: OpenMode,
39-
private val searchParameters: SearchParameters
40-
) {
35+
private val openMode: OpenMode
36+
) : FileSearch(query, path, searchParameters) {
4137
private val LOG = LoggerFactory.getLogger(DeepSearch::class.java)
4238

43-
private val hybridFileParcelables: ArrayList<HybridFileParcelable> = ArrayList()
44-
val mutableLiveData: MutableLiveData<ArrayList<HybridFileParcelable>> = MutableLiveData(
45-
hybridFileParcelables
46-
)
47-
4839
private val applicationContext: Context
4940

5041
init {
5142
applicationContext = context.applicationContext
5243
}
5344

54-
/**
55-
* Search for files, whose names match [query], starting from [path] and add them to
56-
* [mutableLiveData].
57-
*/
58-
suspend fun search() {
59-
val file = HybridFile(openMode, path)
60-
if (file.isSmb) return
61-
62-
// level 1
63-
// if regex or not
64-
if (SearchParameter.REGEX !in searchParameters) {
65-
search(file, query)
66-
} else {
67-
// compile the regular expression in the input
68-
val pattern = Pattern.compile(
69-
bashRegexToJava(query),
70-
Pattern.CASE_INSENSITIVE
71-
)
72-
// level 2
73-
if (SearchParameter.REGEX_MATCHES !in searchParameters) {
74-
searchRegExFind(file, pattern)
75-
} else {
76-
searchRegExMatch(file, pattern)
77-
}
78-
}
79-
}
80-
8145
/**
8246
* Search for occurrences of a given text in file names and publish the result
8347
*
8448
* @param directory the current path
8549
*/
86-
private suspend fun search(directory: HybridFile, filter: SearchFilter) {
50+
override suspend fun search(filter: SearchFilter) {
51+
val directory = HybridFile(openMode, path)
52+
if (directory.isSmb) return
53+
8754
if (directory.isDirectory(applicationContext)) {
8855
// you have permission to read this directory
8956
val worklist = ArrayDeque<HybridFile>()
@@ -95,8 +62,9 @@ class DeepSearch(
9562
SearchParameter.ROOT in searchParameters
9663
) { file ->
9764
if (!file.isHidden || SearchParameter.SHOW_HIDDEN_FILES in searchParameters) {
98-
if (filter.searchFilter(file.getName(applicationContext))) {
99-
publishProgress(file)
65+
val resultRange = filter.searchFilter(file.getName(applicationContext))
66+
if (resultRange != null) {
67+
publishProgress(file, resultRange)
10068
}
10169
if (file.isDirectory(applicationContext)) {
10270
worklist.add(file)
@@ -108,68 +76,4 @@ class DeepSearch(
10876
LOG.warn("Cannot search " + directory.path + ": Permission Denied")
10977
}
11078
}
111-
112-
private fun publishProgress(file: HybridFileParcelable) {
113-
hybridFileParcelables.add(file)
114-
mutableLiveData.postValue(hybridFileParcelables)
115-
}
116-
117-
/**
118-
* Recursively search for occurrences of a given text in file names and publish the result
119-
*
120-
* @param file the current path
121-
* @param query the searched text
122-
*/
123-
private suspend fun search(file: HybridFile, query: String) {
124-
search(file) { fileName: String ->
125-
fileName.lowercase(Locale.getDefault()).contains(
126-
query.lowercase(
127-
Locale.getDefault()
128-
)
129-
)
130-
}
131-
}
132-
133-
/**
134-
* Recursively find a java regex pattern [Pattern] in the file names and publish the result
135-
*
136-
* @param file the current file
137-
* @param pattern the compiled java regex
138-
*/
139-
private suspend fun searchRegExFind(file: HybridFile, pattern: Pattern) {
140-
search(file) { fileName: String -> pattern.matcher(fileName).find() }
141-
}
142-
143-
/**
144-
* Recursively match a java regex pattern [Pattern] with the file names and publish the
145-
* result
146-
*
147-
* @param file the current file
148-
* @param pattern the compiled java regex
149-
*/
150-
private suspend fun searchRegExMatch(file: HybridFile, pattern: Pattern) {
151-
search(file) { fileName: String -> pattern.matcher(fileName).matches() }
152-
}
153-
154-
/**
155-
* method converts bash style regular expression to java. See [Pattern]
156-
*
157-
* @return converted string
158-
*/
159-
private fun bashRegexToJava(originalString: String): String {
160-
val stringBuilder = StringBuilder()
161-
for (i in originalString.indices) {
162-
when (originalString[i].toString() + "") {
163-
"*" -> stringBuilder.append("\\w*")
164-
"?" -> stringBuilder.append("\\w")
165-
else -> stringBuilder.append(originalString[i])
166-
}
167-
}
168-
return stringBuilder.toString()
169-
}
170-
171-
fun interface SearchFilter {
172-
/** Returns if the file with the given [fileName] fulfills some predicate */
173-
fun searchFilter(fileName: String): Boolean
174-
}
17579
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright (C) 2014-2024 Arpit Khurana <arpitkh96@gmail.com>, Vishal Nehra <vishalmeham2@gmail.com>,
3+
* Emmanuel Messulam<emmanuelbendavid@gmail.com>, Raymond Lai <airwave209gt at gmail.com> and Contributors.
4+
*
5+
* This file is part of Amaze File Manager.
6+
*
7+
* Amaze File Manager is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.amaze.filemanager.asynchronous.asynctasks.searchfilesystem
22+
23+
import androidx.lifecycle.LiveData
24+
import androidx.lifecycle.MutableLiveData
25+
import com.amaze.filemanager.filesystem.HybridFileParcelable
26+
import java.util.Locale
27+
import java.util.regex.Pattern
28+
29+
abstract class FileSearch(
30+
protected val query: String,
31+
protected val path: String,
32+
protected val searchParameters: SearchParameters
33+
) {
34+
private val mutableFoundFilesLiveData: MutableLiveData<List<SearchResult>> =
35+
MutableLiveData()
36+
val foundFilesLiveData: LiveData<List<SearchResult>> = mutableFoundFilesLiveData
37+
private val foundFilesList: MutableList<SearchResult> = mutableListOf()
38+
39+
/**
40+
* Search for files, whose names match [query], starting from [path] and add them to
41+
* [foundFilesLiveData]
42+
*/
43+
suspend fun search() {
44+
if (SearchParameter.REGEX !in searchParameters) {
45+
// regex not turned on so we use simpleFilter
46+
this.search(simpleFilter(query))
47+
} else {
48+
if (SearchParameter.REGEX_MATCHES !in searchParameters) {
49+
// only regex turned on so we use regexFilter
50+
this.search(regexFilter(query))
51+
} else {
52+
// regex turned on and names must match pattern so use regexMatchFilter
53+
this.search(regexMatchFilter(query))
54+
}
55+
}
56+
}
57+
58+
/**
59+
* Search for files, whose names fulfill [filter], starting from [path] and add them to
60+
* [foundFilesLiveData].
61+
*/
62+
protected abstract suspend fun search(filter: SearchFilter)
63+
64+
/**
65+
* Add [file] to list of found files and post it to [foundFilesLiveData]
66+
*/
67+
protected fun publishProgress(
68+
file: HybridFileParcelable,
69+
matchRange: MatchRange
70+
) {
71+
foundFilesList.add(SearchResult(file, matchRange))
72+
mutableFoundFilesLiveData.postValue(foundFilesList)
73+
}
74+
75+
private fun simpleFilter(query: String): SearchFilter =
76+
SearchFilter { fileName ->
77+
// check case-insensitively if query is contained in fileName
78+
val start = fileName.lowercase(Locale.getDefault()).indexOf(
79+
query.lowercase(
80+
Locale.getDefault()
81+
)
82+
)
83+
if (start >= 0) {
84+
start until start + query.length
85+
} else {
86+
null
87+
}
88+
}
89+
90+
private fun regexFilter(query: String): SearchFilter {
91+
val pattern = regexPattern(query)
92+
return SearchFilter { fileName ->
93+
// check case-insensitively if the pattern compiled from query can be found in fileName
94+
val matcher = pattern.matcher(fileName)
95+
if (matcher.find()) {
96+
matcher.start() until matcher.end()
97+
} else {
98+
null
99+
}
100+
}
101+
}
102+
103+
private fun regexMatchFilter(query: String): SearchFilter {
104+
val pattern = regexPattern(query)
105+
return SearchFilter { fileName ->
106+
// check case-insensitively if the pattern compiled from query matches fileName
107+
if (pattern.matcher(fileName).matches()) {
108+
fileName.indices
109+
} else {
110+
null
111+
}
112+
}
113+
}
114+
115+
private fun regexPattern(query: String): Pattern =
116+
// compiles the given query into a Pattern
117+
Pattern.compile(
118+
bashRegexToJava(query),
119+
Pattern.CASE_INSENSITIVE
120+
)
121+
122+
/**
123+
* method converts bash style regular expression to java. See [Pattern]
124+
*
125+
* @return converted string
126+
*/
127+
private fun bashRegexToJava(originalString: String): String {
128+
val stringBuilder = StringBuilder()
129+
for (i in originalString.indices) {
130+
when (originalString[i].toString() + "") {
131+
"*" -> stringBuilder.append("\\w*")
132+
"?" -> stringBuilder.append("\\w")
133+
else -> stringBuilder.append(originalString[i])
134+
}
135+
}
136+
return stringBuilder.toString()
137+
}
138+
139+
fun interface SearchFilter {
140+
/**
141+
* If the file with the given [fileName] fulfills some predicate, returns the part that fulfills the predicate.
142+
* Otherwise returns null.
143+
*/
144+
fun searchFilter(fileName: String): MatchRange?
145+
}
146+
}

0 commit comments

Comments
 (0)