Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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)

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