Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ android {

dependencies {
implementation project(":dfc")
//implementation "com.lazygeniouz:dfc:1.1"
// implementation "com.lazygeniouz:dfc:1.3"

implementation "androidx.appcompat:appcompat:1.7.1"
implementation "androidx.activity:activity-ktx:1.12.3"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.lazygeniouz.filecompat.example

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Intent
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.os.storage.StorageManager
import android.view.Menu
import android.view.MenuItem
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
Expand All @@ -22,10 +25,40 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {

private lateinit var buttonDir: Button
private lateinit var buttonFile: Button
private lateinit var buttonProjections: Button

private lateinit var textView: TextView
private lateinit var progress: ProgressBar

private var selectedFileCount = 250

private val testFileGenerationLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val documentUri = result.data?.data
if (documentUri != null) {
textView.text = getString(R.string.creating_files)

lifecycleScope.launch {
progress.isVisible = true
buttonDir.isVisible = false
buttonFile.isVisible = false
buttonProjections.isVisible = false

val generationResult = TestFileGenerator.generateTestFiles(
this@MainActivity, documentUri, selectedFileCount
)

progress.isVisible = false
buttonDir.isVisible = true
buttonFile.isVisible = true
buttonProjections.isVisible = true
textView.text = generationResult
}
}
}
}

private val folderResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
Expand All @@ -37,6 +70,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
progress.isVisible = true
buttonDir.isVisible = false
buttonFile.isVisible = false
buttonProjections.isVisible = false
val performanceResult = withContext(Dispatchers.IO) {
Performance.calculateDirectoryPerformance(
this@MainActivity, documentUri
Expand All @@ -46,6 +80,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
progress.isVisible = false
buttonDir.isVisible = true
buttonFile.isVisible = true
buttonProjections.isVisible = true
textView.text = performanceResult
}
}
Expand Down Expand Up @@ -73,11 +108,41 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
}
}

private val projectionResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val documentUri = result.data?.data
if (documentUri != null) {
textView.text = ""

lifecycleScope.launch {
progress.isVisible = true
buttonDir.isVisible = false
buttonFile.isVisible = false
buttonProjections.isVisible = false

val performanceResult = withContext(Dispatchers.IO) {
Performance.calculateProjectionPerformance(
this@MainActivity, documentUri
)
}

progress.isVisible = false
buttonDir.isVisible = true
buttonFile.isVisible = true
buttonProjections.isVisible = true
textView.text = performanceResult
}
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

buttonDir = findViewById(R.id.buttonDir)
buttonFile = findViewById(R.id.buttonFile)
buttonProjections = findViewById(R.id.buttonProjections)
textView = findViewById(R.id.fileNames)
progress = findViewById(R.id.progress)

Expand All @@ -90,6 +155,39 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
buttonFile.setOnClickListener {
fileResultLauncher.launch(getStorageIntent(true))
}

buttonProjections.setOnClickListener {
projectionResultLauncher.launch(getStorageIntent())
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_add_test_files -> {
showFileCountDialog()
true
}

else -> super.onOptionsItemSelected(item)
}
}

private fun showFileCountDialog() {
val options = arrayOf("250 files", "500 files", "1000 files")
val counts = arrayOf(250, 500, 1000)
Comment thread
ItzNotABug marked this conversation as resolved.

AlertDialog.Builder(this)
.setTitle(R.string.select_file_count)
.setItems(options) { _, which ->
selectedFileCount = counts[which]
testFileGenerationLauncher.launch(getStorageIntent())
}
.show()
}

private fun getStorageIntent(single: Boolean = false): Intent {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.lazygeniouz.filecompat.example

import android.content.Context
import android.net.Uri
import com.lazygeniouz.dfc.file.DocumentFileCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.util.Random
import java.util.concurrent.atomic.AtomicInteger

object TestFileGenerator {

private val extensions = listOf("txt", "pdf", "jpg", "png", "mp4", "mp3", "doc", "zip", "apk")
private val random = Random()

suspend fun generateTestFiles(context: Context, directoryUri: Uri, fileCount: Int): String {
return try {
val directory = DocumentFileCompat.fromTreeUri(context, directoryUri)
?: return "Failed to access directory"

val startTime = System.currentTimeMillis()
val successCount = AtomicInteger(0)
val failCount = AtomicInteger(0)

val semaphore = Semaphore(10)

coroutineScope {
val jobs = (1..fileCount).map { index ->
async(Dispatchers.IO) {
semaphore.withPermit {
val fileName = "test_file_$index"
val extension = extensions.random()
val sizeInKb = random.nextInt(100) + 1 // 1KB to 100KB

val file = directory.createFile(
"application/octet-stream",
"$fileName.$extension"
)
if (file != null) {
val success = writeRandomData(context, file.uri, sizeInKb)
if (success) successCount.incrementAndGet() else failCount.incrementAndGet()
} else {
failCount.incrementAndGet()
}
}
}
}

jobs.awaitAll()
}

val elapsedTime = (System.currentTimeMillis() - startTime) / 1000.0

buildString {
append("Test Files Generation Complete!\n\n")
append("Total: $fileCount files\n")
append("Success: ${successCount.get()}\n")
append("Failed: ${failCount.get()}\n")
append("Time: ${elapsedTime}s\n\n")
append("Files have random sizes between 1KB-100KB\n")
append("Extensions: ${extensions.joinToString(", ")}")
}
} catch (e: Exception) {
"Error generating files: ${e.message}"
}
}

/**
* Write random data to a file to achieve desired size.
*/
private fun writeRandomData(context: Context, fileUri: Uri, sizeInKb: Int): Boolean {
return try {
context.contentResolver.openOutputStream(fileUri)?.use { output ->
val bufferSize = 8192 // 8KB buffer
val buffer = ByteArray(bufferSize)
val totalBytes = sizeInKb * 1024
var written = 0

while (written < totalBytes) {
random.nextBytes(buffer)
val toWrite = minOf(bufferSize, totalBytes - written)
output.write(buffer, 0, toWrite)
written += toWrite
}
output.flush()
}
true
} catch (e: Exception) {
false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ object DirectoryPerformance {
results += "Fetching only Uris will always be faster (after File)" + "\nBut try fetching the Documents' Names.\n\n"
results += calculateDocumentFileCompatPerformanceWithName(context, uri) + "\n"
results += calculateDocumentFilePerformanceOnlyUri(context, uri) + "\n"
results += calculateDocumentFilePerformanceWithName(context, uri)
results += calculateDocumentFilePerformanceWithName(context, uri) + "\n\n"

results += "=".repeat(48).plus("\n\n")
results += calculateCountVsListSize(context, uri)
return results
}

Expand Down Expand Up @@ -141,4 +144,25 @@ object DirectoryPerformance {
return ("DFC Performance (With Names) = ${time}s")
}
}

private fun calculateCountVsListSize(context: Context, uri: Uri): String {
val documentFile = DocumentFileCompat.fromTreeUri(context, uri)
?: return "Failed to access directory"

val dfListSizeTime = measureTimeSeconds {
DocumentFile.fromTreeUri(context, uri)?.listFiles()?.size
}

val dfcCountTime = measureTimeSeconds {
documentFile.count()
}

val dfcListSizeTime = measureTimeSeconds {
documentFile.listFiles().size
}

return "DocumentFile.listFiles().size = ${dfListSizeTime}s\n" +
"DFC count() Performance = ${dfcCountTime}s\n" +
"DFC listFiles().size Performance = ${dfcListSizeTime}s"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ object Performance {
return FilesPerformance.calculateFileSidePerformance(context, uri)
}

fun calculateProjectionPerformance(context: Context, uri: Uri): String {
return ProjectionPerformance.calculateProjectionPerformance(context, uri)
}

fun getUsablePath(uri: Uri): String {
val path = uri.path!!
return when {
Expand Down
Loading