Skip to content

Commit 4dd80b6

Browse files
Merge pull request #17394 from nextcloud/local-file-list-adapter-kt
convert local file list adapter to Kotlin
2 parents 4c2f99b + b5ce3be commit 4dd80b6

16 files changed

Lines changed: 884 additions & 816 deletions

app/src/androidTest/java/com/nextcloud/utils/FileHelperTest.kt

Lines changed: 61 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package com.nextcloud.utils
88

99
import junit.framework.TestCase.assertEquals
1010
import junit.framework.TestCase.assertTrue
11+
import kotlinx.coroutines.runBlocking
1112
import org.junit.After
1213
import org.junit.Before
1314
import org.junit.Test
@@ -29,176 +30,131 @@ class FileHelperTest {
2930
testDirectory.deleteRecursively()
3031
}
3132

33+
private fun collectPages(directory: File?, pageSize: Int, fetchFolders: Boolean): List<List<File>> {
34+
val pages = mutableListOf<List<File>>()
35+
36+
runBlocking {
37+
FileHelper.forEachDirectoryPage(directory, pageSize, fetchFolders) { page ->
38+
pages.add(page)
39+
}
40+
}
41+
42+
return pages
43+
}
44+
3245
@Test
33-
fun testListDirectoryEntriesWhenGivenNullDirectoryShouldReturnEmptyList() {
34-
val result = FileHelper.listDirectoryEntries(null, 0, 10, false)
35-
assertTrue(result.isEmpty())
46+
fun testForEachDirectoryPageWhenGivenNullDirectoryShouldReportNoPage() {
47+
assertTrue(collectPages(null, 10, false).isEmpty())
3648
}
3749

3850
@Test
39-
fun testListDirectoryEntriesWhenGivenNonExistentDirectoryShouldReturnEmptyList() {
51+
fun testForEachDirectoryPageWhenGivenNonExistentDirectoryShouldReportNoPage() {
4052
val nonExistent = File(testDirectory, "does_not_exist")
41-
val result = FileHelper.listDirectoryEntries(nonExistent, 0, 10, false)
42-
assertTrue(result.isEmpty())
53+
54+
assertTrue(collectPages(nonExistent, 10, false).isEmpty())
4355
}
4456

4557
@Test
46-
fun testListDirectoryEntriesWhenGivenFileInsteadOfDirectoryShouldReturnEmptyList() {
58+
fun testForEachDirectoryPageWhenGivenFileInsteadOfDirectoryShouldReportNoPage() {
4759
val file = File(testDirectory, "test.txt")
4860
file.createNewFile()
49-
val result = FileHelper.listDirectoryEntries(file, 0, 10, false)
50-
assertTrue(result.isEmpty())
61+
62+
assertTrue(collectPages(file, 10, false).isEmpty())
5163
}
5264

5365
@Test
54-
fun testListDirectoryEntriesWhenGivenEmptyDirectoryShouldReturnEmptyList() {
55-
val result = FileHelper.listDirectoryEntries(testDirectory, 0, 10, false)
56-
assertTrue(result.isEmpty())
66+
fun testForEachDirectoryPageWhenGivenEmptyDirectoryShouldReportNoPage() {
67+
assertTrue(collectPages(testDirectory, 10, false).isEmpty())
5768
}
5869

5970
@Test
60-
fun testListDirectoryEntriesWhenFetchingFoldersShouldReturnOnlyFolders() {
71+
fun testForEachDirectoryPageWhenPageSizeIsZeroShouldReportNoPage() {
72+
for (i in 1..5) File(testDirectory, "file$i.txt").createNewFile()
73+
74+
assertTrue(collectPages(testDirectory, 0, false).isEmpty())
75+
}
76+
77+
@Test
78+
fun testForEachDirectoryPageWhenFetchingFoldersShouldReportOnlyFolders() {
6179
File(testDirectory, "folder1").mkdir()
6280
File(testDirectory, "folder2").mkdir()
6381
File(testDirectory, "file1.txt").createNewFile()
6482
File(testDirectory, "file2.txt").createNewFile()
6583

66-
val result = FileHelper.listDirectoryEntries(testDirectory, 0, 10, true)
84+
val entries = collectPages(testDirectory, 10, true).flatten()
6785

68-
assertEquals(2, result.size)
69-
assertTrue(result.all { it.isDirectory })
86+
assertEquals(2, entries.size)
87+
assertTrue(entries.all { it.isDirectory })
7088
}
7189

7290
@Test
73-
fun testListDirectoryEntriesWhenFetchingFilesShouldReturnOnlyFiles() {
91+
fun testForEachDirectoryPageWhenFetchingFilesShouldReportOnlyFiles() {
7492
File(testDirectory, "folder1").mkdir()
7593
File(testDirectory, "folder2").mkdir()
7694
File(testDirectory, "file1.txt").createNewFile()
7795
File(testDirectory, "file2.txt").createNewFile()
7896

79-
val result = FileHelper.listDirectoryEntries(testDirectory, 0, 10, false)
97+
val entries = collectPages(testDirectory, 10, false).flatten()
8098

81-
assertEquals(2, result.size)
82-
assertTrue(result.all { it.isFile })
99+
assertEquals(2, entries.size)
100+
assertTrue(entries.all { it.isFile })
83101
}
84102

85103
@Test
86-
fun testListDirectoryEntriesWhenStartIndexProvidedShouldSkipCorrectNumberOfItems() {
87-
for (i in 1..5) File(testDirectory, "file$i.txt").createNewFile()
88-
val result = FileHelper.listDirectoryEntries(testDirectory, 2, 10, false)
89-
assertEquals(3, result.size)
90-
}
104+
fun testForEachDirectoryPageWhenGivenOnlyFoldersAndFetchingFilesShouldReportNoPage() {
105+
for (i in 1..5) File(testDirectory, "folder$i").mkdir()
91106

92-
@Test
93-
fun testListDirectoryEntriesWhenMaxItemsProvidedShouldLimitResults() {
94-
for (i in 1..10) File(testDirectory, "file$i.txt").createNewFile()
95-
val result = FileHelper.listDirectoryEntries(testDirectory, 0, 5, false)
96-
assertEquals(5, result.size)
107+
assertTrue(collectPages(testDirectory, 10, false).isEmpty())
97108
}
98109

99110
@Test
100-
fun testListDirectoryEntriesWhenGivenStartIndexAndMaxItemsShouldReturnCorrectSubset() {
101-
for (i in 1..10) File(testDirectory, "file$i.txt").createNewFile()
102-
val result = FileHelper.listDirectoryEntries(testDirectory, 3, 4, false)
103-
assertEquals(4, result.size)
104-
}
111+
fun testForEachDirectoryPageWhenGivenOnlyFilesAndFetchingFoldersShouldReportNoPage() {
112+
for (i in 1..5) File(testDirectory, "file$i.txt").createNewFile()
105113

106-
@Test
107-
fun testListDirectoryEntriesWhenStartIndexBeyondAvailableShouldReturnEmptyList() {
108-
for (i in 1..3) File(testDirectory, "file$i.txt").createNewFile()
109-
val result = FileHelper.listDirectoryEntries(testDirectory, 10, 5, false)
110-
assertTrue(result.isEmpty())
114+
assertTrue(collectPages(testDirectory, 10, true).isEmpty())
111115
}
112116

113117
@Test
114-
fun testListDirectoryEntriesWhenMaxItemsBeyondAvailableShouldReturnAllItems() {
118+
fun testForEachDirectoryPageWhenPageSizeExceedsContentShouldReportSinglePage() {
115119
for (i in 1..3) File(testDirectory, "file$i.txt").createNewFile()
116-
val result = FileHelper.listDirectoryEntries(testDirectory, 0, 100, false)
117-
assertEquals(3, result.size)
118-
}
119120

120-
@Test
121-
fun testListDirectoryEntriesWhenFetchingFoldersWithOffsetShouldSkipCorrectly() {
122-
for (i in 1..5) File(testDirectory, "folder$i").mkdir()
123-
for (i in 1..3) File(testDirectory, "file$i.txt").createNewFile()
121+
val pages = collectPages(testDirectory, 100, false)
124122

125-
val result = FileHelper.listDirectoryEntries(testDirectory, 2, 10, true)
126-
127-
assertEquals(3, result.size)
128-
assertTrue(result.all { it.isDirectory })
123+
assertEquals(1, pages.size)
124+
assertEquals(3, pages.first().size)
129125
}
130126

131127
@Test
132-
fun testListDirectoryEntriesWhenFetchingFilesWithOffsetShouldSkipCorrectly() {
133-
for (i in 1..3) File(testDirectory, "folder$i").mkdir()
134-
for (i in 1..5) File(testDirectory, "file$i.txt").createNewFile()
128+
fun testForEachDirectoryPageWhenPaginatingFoldersShouldFillEveryPageButTheLast() {
129+
for (i in 1..10) File(testDirectory, "folder$i").mkdir()
135130

136-
val result = FileHelper.listDirectoryEntries(testDirectory, 2, 10, false)
131+
val pages = collectPages(testDirectory, 3, true)
137132

138-
assertEquals(3, result.size)
139-
assertTrue(result.all { it.isFile })
133+
assertEquals(listOf(3, 3, 3, 1), pages.map { it.size })
140134
}
141135

142136
@Test
143-
fun testListDirectoryEntriesWhenGivenOnlyFoldersAndFetchingFilesShouldReturnEmptyList() {
144-
for (i in 1..5) File(testDirectory, "folder$i").mkdir()
145-
val result = FileHelper.listDirectoryEntries(testDirectory, 0, 10, false)
146-
assertTrue(result.isEmpty())
147-
}
137+
fun testForEachDirectoryPageWhenContentIsAMultipleOfPageSizeShouldNotReportAnEmptyPage() {
138+
for (i in 1..9) File(testDirectory, "file$i.txt").createNewFile()
148139

149-
@Test
150-
fun testListDirectoryEntriesWhenGivenOnlyFilesAndFetchingFoldersShouldReturnEmptyList() {
151-
for (i in 1..5) File(testDirectory, "file$i.txt").createNewFile()
152-
val result = FileHelper.listDirectoryEntries(testDirectory, 0, 10, true)
153-
assertTrue(result.isEmpty())
154-
}
140+
val pages = collectPages(testDirectory, 3, false)
155141

156-
@Test
157-
fun testListDirectoryEntriesWhenMaxItemsIsZeroShouldReturnEmptyList() {
158-
for (i in 1..5) File(testDirectory, "file$i.txt").createNewFile()
159-
val result = FileHelper.listDirectoryEntries(testDirectory, 0, 0, false)
160-
assertTrue(result.isEmpty())
142+
assertEquals(listOf(3, 3, 3), pages.map { it.size })
161143
}
162144

163145
@Test
164-
fun testListDirectoryEntriesWhenGivenMixedContentShouldFilterCorrectly() {
146+
fun testForEachDirectoryPageWhenGivenMixedContentShouldReportEveryEntryExactlyOnce() {
165147
for (i in 1..3) File(testDirectory, "folder$i").mkdir()
166148
for (i in 1..7) File(testDirectory, "file$i.txt").createNewFile()
167149

168-
val folders = FileHelper.listDirectoryEntries(testDirectory, 0, 10, true)
169-
val files = FileHelper.listDirectoryEntries(testDirectory, 0, 10, false)
150+
val folders = collectPages(testDirectory, 2, true).flatten()
151+
val files = collectPages(testDirectory, 2, false).flatten()
170152

171153
assertEquals(3, folders.size)
172154
assertEquals(7, files.size)
155+
assertEquals(folders.size, folders.distinct().size)
156+
assertEquals(files.size, files.distinct().size)
173157
assertTrue(folders.all { it.isDirectory })
174158
assertTrue(files.all { it.isFile })
175159
}
176-
177-
@Test
178-
fun testListDirectoryEntriesWhenPaginatingFoldersShouldWorkCorrectly() {
179-
for (i in 1..10) File(testDirectory, "folder$i").mkdir()
180-
181-
val page1 = FileHelper.listDirectoryEntries(testDirectory, 0, 3, true)
182-
val page2 = FileHelper.listDirectoryEntries(testDirectory, 3, 3, true)
183-
val page3 = FileHelper.listDirectoryEntries(testDirectory, 6, 3, true)
184-
val page4 = FileHelper.listDirectoryEntries(testDirectory, 9, 3, true)
185-
186-
assertEquals(3, page1.size)
187-
assertEquals(3, page2.size)
188-
assertEquals(3, page3.size)
189-
assertEquals(1, page4.size)
190-
}
191-
192-
@Test
193-
fun testListDirectoryEntriesWhenPaginatingFilesShouldWorkCorrectly() {
194-
for (i in 1..10) File(testDirectory, "file$i.txt").createNewFile()
195-
196-
val page1 = FileHelper.listDirectoryEntries(testDirectory, 0, 4, false)
197-
val page2 = FileHelper.listDirectoryEntries(testDirectory, 4, 4, false)
198-
val page3 = FileHelper.listDirectoryEntries(testDirectory, 8, 4, false)
199-
200-
assertEquals(4, page1.size)
201-
assertEquals(4, page2.size)
202-
assertEquals(2, page3.size)
203-
}
204160
}

app/src/main/java/com/nextcloud/utils/FileHelper.kt

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,58 @@
88
package com.nextcloud.utils
99

1010
import com.owncloud.android.lib.common.utils.Log_OC
11+
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.withContext
1113
import java.io.File
1214
import java.io.IOException
15+
import java.nio.file.DirectoryIteratorException
1316
import java.nio.file.Files
1417
import java.nio.file.Path
15-
import java.util.stream.Collectors
1618
import kotlin.io.path.pathString
1719

1820
@Suppress("NestedBlockDepth")
1921
object FileHelper {
2022
private const val TAG = "FileHelper"
2123

22-
fun listDirectoryEntries(directory: File?, startIndex: Int, maxItems: Int, fetchFolders: Boolean): List<File> {
23-
if (directory == null || !directory.exists() || !directory.isDirectory) return emptyList()
24+
suspend fun forEachDirectoryPage(
25+
directory: File?,
26+
pageSize: Int,
27+
fetchFolders: Boolean,
28+
onPage: suspend (List<File>) -> Unit
29+
) {
30+
if (directory == null || !directory.isDirectory || pageSize <= 0) {
31+
return
32+
}
33+
34+
try {
35+
withContext(Dispatchers.IO) {
36+
Files.newDirectoryStream(directory.toPath())
37+
}.use { entries ->
38+
var page = ArrayList<File>(pageSize)
39+
40+
for (entry in entries) {
41+
val file = entry.toFile()
42+
if (file.isDirectory != fetchFolders) {
43+
continue
44+
}
45+
46+
page.add(file)
47+
if (page.size < pageSize) {
48+
continue
49+
}
50+
51+
onPage(page)
52+
page = ArrayList(pageSize)
53+
}
2454

25-
return try {
26-
Files.list(directory.toPath())
27-
.map { it.toFile() }
28-
.filter { file -> if (fetchFolders) file.isDirectory else !file.isDirectory }
29-
.skip(startIndex.toLong())
30-
.limit(maxItems.toLong())
31-
.collect(Collectors.toList())
55+
if (page.isNotEmpty()) {
56+
onPage(page)
57+
}
58+
}
3259
} catch (e: IOException) {
33-
Log_OC.d(TAG, "listDirectoryEntries: $e")
34-
emptyList()
60+
Log_OC.d(TAG, "forEachDirectoryPage failed: $e")
61+
} catch (e: DirectoryIteratorException) {
62+
Log_OC.d(TAG, "forEachDirectoryPage failed: $e")
3563
}
3664
}
3765

0 commit comments

Comments
 (0)