Skip to content

Commit 6c8afb7

Browse files
authored
Merge pull request #26 from ItzNotABug/projections
Feat: custom projections support
2 parents 019254d + 59949da commit 6c8afb7

15 files changed

Lines changed: 477 additions & 43 deletions

File tree

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ android {
3636

3737
dependencies {
3838
implementation project(":dfc")
39-
//implementation "com.lazygeniouz:dfc:1.1"
39+
// implementation "com.lazygeniouz:dfc:1.3"
4040

4141
implementation "androidx.appcompat:appcompat:1.7.1"
4242
implementation "androidx.activity:activity-ktx:1.12.3"

app/src/main/java/com/lazygeniouz/filecompat/example/MainActivity.kt

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package com.lazygeniouz.filecompat.example
22

33
import android.annotation.SuppressLint
4+
import android.app.AlertDialog
45
import android.content.Intent
56
import android.os.Build.VERSION.SDK_INT
67
import android.os.Bundle
78
import android.os.storage.StorageManager
9+
import android.view.Menu
10+
import android.view.MenuItem
811
import android.widget.Button
912
import android.widget.ProgressBar
1013
import android.widget.TextView
@@ -22,10 +25,40 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
2225

2326
private lateinit var buttonDir: Button
2427
private lateinit var buttonFile: Button
28+
private lateinit var buttonProjections: Button
2529

2630
private lateinit var textView: TextView
2731
private lateinit var progress: ProgressBar
2832

33+
private var selectedFileCount = 250
34+
35+
private val testFileGenerationLauncher =
36+
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
37+
if (result.resultCode == RESULT_OK) {
38+
val documentUri = result.data?.data
39+
if (documentUri != null) {
40+
textView.text = getString(R.string.creating_files)
41+
42+
lifecycleScope.launch {
43+
progress.isVisible = true
44+
buttonDir.isVisible = false
45+
buttonFile.isVisible = false
46+
buttonProjections.isVisible = false
47+
48+
val generationResult = TestFileGenerator.generateTestFiles(
49+
this@MainActivity, documentUri, selectedFileCount
50+
)
51+
52+
progress.isVisible = false
53+
buttonDir.isVisible = true
54+
buttonFile.isVisible = true
55+
buttonProjections.isVisible = true
56+
textView.text = generationResult
57+
}
58+
}
59+
}
60+
}
61+
2962
private val folderResultLauncher =
3063
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
3164
if (result.resultCode == RESULT_OK) {
@@ -37,6 +70,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
3770
progress.isVisible = true
3871
buttonDir.isVisible = false
3972
buttonFile.isVisible = false
73+
buttonProjections.isVisible = false
4074
val performanceResult = withContext(Dispatchers.IO) {
4175
Performance.calculateDirectoryPerformance(
4276
this@MainActivity, documentUri
@@ -46,6 +80,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
4680
progress.isVisible = false
4781
buttonDir.isVisible = true
4882
buttonFile.isVisible = true
83+
buttonProjections.isVisible = true
4984
textView.text = performanceResult
5085
}
5186
}
@@ -73,11 +108,41 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
73108
}
74109
}
75110

111+
private val projectionResultLauncher =
112+
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
113+
if (result.resultCode == RESULT_OK) {
114+
val documentUri = result.data?.data
115+
if (documentUri != null) {
116+
textView.text = ""
117+
118+
lifecycleScope.launch {
119+
progress.isVisible = true
120+
buttonDir.isVisible = false
121+
buttonFile.isVisible = false
122+
buttonProjections.isVisible = false
123+
124+
val performanceResult = withContext(Dispatchers.IO) {
125+
Performance.calculateProjectionPerformance(
126+
this@MainActivity, documentUri
127+
)
128+
}
129+
130+
progress.isVisible = false
131+
buttonDir.isVisible = true
132+
buttonFile.isVisible = true
133+
buttonProjections.isVisible = true
134+
textView.text = performanceResult
135+
}
136+
}
137+
}
138+
}
139+
76140
override fun onCreate(savedInstanceState: Bundle?) {
77141
super.onCreate(savedInstanceState)
78142

79143
buttonDir = findViewById(R.id.buttonDir)
80144
buttonFile = findViewById(R.id.buttonFile)
145+
buttonProjections = findViewById(R.id.buttonProjections)
81146
textView = findViewById(R.id.fileNames)
82147
progress = findViewById(R.id.progress)
83148

@@ -90,6 +155,39 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
90155
buttonFile.setOnClickListener {
91156
fileResultLauncher.launch(getStorageIntent(true))
92157
}
158+
159+
buttonProjections.setOnClickListener {
160+
projectionResultLauncher.launch(getStorageIntent())
161+
}
162+
}
163+
164+
override fun onCreateOptionsMenu(menu: Menu): Boolean {
165+
menuInflater.inflate(R.menu.main_menu, menu)
166+
return true
167+
}
168+
169+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
170+
return when (item.itemId) {
171+
R.id.menu_add_test_files -> {
172+
showFileCountDialog()
173+
true
174+
}
175+
176+
else -> super.onOptionsItemSelected(item)
177+
}
178+
}
179+
180+
private fun showFileCountDialog() {
181+
val options = arrayOf("250 files", "500 files", "1000 files")
182+
val counts = arrayOf(250, 500, 1000)
183+
184+
AlertDialog.Builder(this)
185+
.setTitle(R.string.select_file_count)
186+
.setItems(options) { _, which ->
187+
selectedFileCount = counts[which]
188+
testFileGenerationLauncher.launch(getStorageIntent())
189+
}
190+
.show()
93191
}
94192

95193
private fun getStorageIntent(single: Boolean = false): Intent {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.lazygeniouz.filecompat.example
2+
3+
import android.content.Context
4+
import android.net.Uri
5+
import com.lazygeniouz.dfc.file.DocumentFileCompat
6+
import kotlinx.coroutines.Dispatchers
7+
import kotlinx.coroutines.async
8+
import kotlinx.coroutines.awaitAll
9+
import kotlinx.coroutines.coroutineScope
10+
import kotlinx.coroutines.sync.Semaphore
11+
import kotlinx.coroutines.sync.withPermit
12+
import java.util.Random
13+
import java.util.concurrent.atomic.AtomicInteger
14+
15+
object TestFileGenerator {
16+
17+
private val extensions = listOf("txt", "pdf", "jpg", "png", "mp4", "mp3", "doc", "zip", "apk")
18+
private val random = Random()
19+
20+
suspend fun generateTestFiles(context: Context, directoryUri: Uri, fileCount: Int): String {
21+
return try {
22+
val directory = DocumentFileCompat.fromTreeUri(context, directoryUri)
23+
?: return "Failed to access directory"
24+
25+
val startTime = System.currentTimeMillis()
26+
val successCount = AtomicInteger(0)
27+
val failCount = AtomicInteger(0)
28+
29+
val semaphore = Semaphore(10)
30+
31+
coroutineScope {
32+
val jobs = (1..fileCount).map { index ->
33+
async(Dispatchers.IO) {
34+
semaphore.withPermit {
35+
val fileName = "test_file_$index"
36+
val extension = extensions.random()
37+
val sizeInKb = random.nextInt(100) + 1 // 1KB to 100KB
38+
39+
val file = directory.createFile(
40+
"application/octet-stream",
41+
"$fileName.$extension"
42+
)
43+
if (file != null) {
44+
val success = writeRandomData(context, file.uri, sizeInKb)
45+
if (success) successCount.incrementAndGet() else failCount.incrementAndGet()
46+
} else {
47+
failCount.incrementAndGet()
48+
}
49+
}
50+
}
51+
}
52+
53+
jobs.awaitAll()
54+
}
55+
56+
val elapsedTime = (System.currentTimeMillis() - startTime) / 1000.0
57+
58+
buildString {
59+
append("Test Files Generation Complete!\n\n")
60+
append("Total: $fileCount files\n")
61+
append("Success: ${successCount.get()}\n")
62+
append("Failed: ${failCount.get()}\n")
63+
append("Time: ${elapsedTime}s\n\n")
64+
append("Files have random sizes between 1KB-100KB\n")
65+
append("Extensions: ${extensions.joinToString(", ")}")
66+
}
67+
} catch (e: Exception) {
68+
"Error generating files: ${e.message}"
69+
}
70+
}
71+
72+
/**
73+
* Write random data to a file to achieve desired size.
74+
*/
75+
private fun writeRandomData(context: Context, fileUri: Uri, sizeInKb: Int): Boolean {
76+
return try {
77+
context.contentResolver.openOutputStream(fileUri)?.use { output ->
78+
val bufferSize = 8192 // 8KB buffer
79+
val buffer = ByteArray(bufferSize)
80+
val totalBytes = sizeInKb * 1024
81+
var written = 0
82+
83+
while (written < totalBytes) {
84+
random.nextBytes(buffer)
85+
val toWrite = minOf(bufferSize, totalBytes - written)
86+
output.write(buffer, 0, toWrite)
87+
written += toWrite
88+
}
89+
output.flush()
90+
}
91+
true
92+
} catch (e: Exception) {
93+
false
94+
}
95+
}
96+
}

app/src/main/java/com/lazygeniouz/filecompat/example/performance/DirectoryPerformance.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ object DirectoryPerformance {
2424
results += "Fetching only Uris will always be faster (after File)" + "\nBut try fetching the Documents' Names.\n\n"
2525
results += calculateDocumentFileCompatPerformanceWithName(context, uri) + "\n"
2626
results += calculateDocumentFilePerformanceOnlyUri(context, uri) + "\n"
27-
results += calculateDocumentFilePerformanceWithName(context, uri)
27+
results += calculateDocumentFilePerformanceWithName(context, uri) + "\n\n"
28+
29+
results += "=".repeat(48).plus("\n\n")
30+
results += calculateCountVsListSize(context, uri)
2831
return results
2932
}
3033

@@ -141,4 +144,25 @@ object DirectoryPerformance {
141144
return ("DFC Performance (With Names) = ${time}s")
142145
}
143146
}
147+
148+
private fun calculateCountVsListSize(context: Context, uri: Uri): String {
149+
val documentFile = DocumentFileCompat.fromTreeUri(context, uri)
150+
?: return "Failed to access directory"
151+
152+
val dfListSizeTime = measureTimeSeconds {
153+
DocumentFile.fromTreeUri(context, uri)?.listFiles()?.size
154+
}
155+
156+
val dfcCountTime = measureTimeSeconds {
157+
documentFile.count()
158+
}
159+
160+
val dfcListSizeTime = measureTimeSeconds {
161+
documentFile.listFiles().size
162+
}
163+
164+
return "DocumentFile.listFiles().size = ${dfListSizeTime}s\n" +
165+
"DFC count() Performance = ${dfcCountTime}s\n" +
166+
"DFC listFiles().size Performance = ${dfcListSizeTime}s"
167+
}
144168
}

app/src/main/java/com/lazygeniouz/filecompat/example/performance/Performance.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ object Performance {
1616
return FilesPerformance.calculateFileSidePerformance(context, uri)
1717
}
1818

19+
fun calculateProjectionPerformance(context: Context, uri: Uri): String {
20+
return ProjectionPerformance.calculateProjectionPerformance(context, uri)
21+
}
22+
1923
fun getUsablePath(uri: Uri): String {
2024
val path = uri.path!!
2125
return when {

0 commit comments

Comments
 (0)