Skip to content

Commit

Permalink
FileUtils: refactoring and unit tests (#388)
Browse files Browse the repository at this point in the history
  • Loading branch information
hieuwu authored Oct 14, 2023
1 parent da66315 commit 8d6b29f
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 87 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
*.tab
*.tab.values.at
misc.xml
/htmlReport
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dev.arkbuilders.navigator.data.utils

import java.nio.file.Path

enum class Sorting {
DEFAULT, NAME, SIZE, LAST_MODIFIED, TYPE
}
interface DevicePathsExtractor {
fun listDevices(): List<Path>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.arkbuilders.navigator.data.utils

import dev.arkbuilders.navigator.presentation.App
import java.nio.file.Path
import javax.inject.Inject

class DevicePathsExtractorImpl @Inject constructor(
private val appInstance: App
) : DevicePathsExtractor {

override fun listDevices(): List<Path> =
appInstance
.getExternalFilesDirs(null)
.toList()
.filterNotNull()
.filter { it.exists() }
.map {
it.toPath().toRealPath()
.takeWhile { part ->
part != ANDROID_DIRECTORY
}
.fold(ROOT_PATH) { parent, child ->
parent.resolve(child)
}
}
}

This file was deleted.

58 changes: 58 additions & 0 deletions app/src/main/java/dev/arkbuilders/navigator/data/utils/PathExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package dev.arkbuilders.navigator.data.utils

import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.exists
import kotlin.io.path.extension
import kotlin.io.path.nameWithoutExtension
import kotlin.io.path.notExists

val ROOT_PATH: Path = Paths.get("/")

val ANDROID_DIRECTORY: Path = Paths.get("Android")

fun Path.findNotExistCopyName(name: Path): Path {
val originalNamePath = this.resolve(name.fileName)
if (originalNamePath.notExists())
return originalNamePath

var filesCounter = 1

val formatNameWithCounter =
"${name.nameWithoutExtension}_$filesCounter.${name.extension}"

var newPath = this.resolve(formatNameWithCounter)

while (newPath.exists()) {
newPath = this.resolve(formatNameWithCounter)
filesCounter++
}
return newPath
}

fun findLongestCommonPrefix(paths: List<Path>): Path {
if (paths.isEmpty()) {
throw IllegalArgumentException(
"Can't search for common prefix among empty collection"
)
}

if (paths.size == 1) {
return paths.first()
}

return tailrec(ROOT_PATH, paths).first
}

private fun tailrec(prefix: Path, paths: List<Path>): Pair<Path, List<Path>> {
val grouped = paths.groupBy { it.getName(0) }
if (grouped.size > 1) {
return prefix to paths
}

val resolvedPrefix = prefix.resolve(grouped.keys.first())
val shortened = grouped.values.first()
.map { resolvedPrefix.relativize(it) }

return tailrec(resolvedPrefix, shortened)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import dev.arkbuilders.navigator.presentation.App
import dev.arkbuilders.navigator.presentation.dialog.ExplainPermsDialog
import dev.arkbuilders.navigator.presentation.dialog.RootPickerDialogFragment
import dev.arkbuilders.navigator.presentation.dialog.edittags.EditTagsDialogPresenter
import dev.arkbuilders.navigator.presentation.dialog.rootsscan.RootsScanDialogPresenter
import dev.arkbuilders.navigator.presentation.dialog.sort.SortDialogPresenter
import dev.arkbuilders.navigator.presentation.dialog.tagssort.TagsSortDialogFragment
import dev.arkbuilders.navigator.presentation.screen.folders.FoldersFragment
Expand Down Expand Up @@ -55,6 +56,7 @@ interface AppComponent {
fun inject(tagsSortDialogFragment: TagsSortDialogFragment)
fun inject(rootPickerDialogFragment: RootPickerDialogFragment)
fun inject(explainPermsDialog: ExplainPermsDialog)
fun inject(rootsScanDialogPresenter: RootsScanDialogPresenter)

@Component.Factory
interface Factory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import dagger.Module
import dagger.Provides
import dev.arkbuilders.navigator.data.preferences.Preferences
import dev.arkbuilders.navigator.data.preferences.PreferencesImpl
import dev.arkbuilders.navigator.data.utils.DevicePathsExtractor
import dev.arkbuilders.navigator.data.utils.DevicePathsExtractorImpl
import dev.arkbuilders.navigator.presentation.App
import dev.arkbuilders.navigator.presentation.utils.StringProvider
import javax.inject.Singleton

Expand All @@ -21,4 +24,9 @@ class AppModule {
@Singleton
fun provideUserPreferences(ctx: Context): Preferences =
PreferencesImpl(ctx)

@Provides
@Singleton
fun provideDevicePathsExtractor(application: App): DevicePathsExtractor =
DevicePathsExtractorImpl(application)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import moxy.MvpBottomSheetDialogFragment
import moxy.ktx.moxyPresenter
import dev.arkbuilders.navigator.R
import dev.arkbuilders.navigator.databinding.DialogRootsScanBinding
import dev.arkbuilders.navigator.presentation.App
import dev.arkbuilders.navigator.presentation.utils.toast
import java.nio.file.Path

class RootsScanDialogFragment : MvpBottomSheetDialogFragment(), RootsScanView {
private lateinit var binding: DialogRootsScanBinding
private val presenter by moxyPresenter {
RootsScanDialogPresenter()
RootsScanDialogPresenter().apply {
App.instance.appComponent.inject(this)
}
}

override fun onCreateView(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.arkbuilders.navigator.presentation.dialog.rootsscan

import android.util.Log
import dev.arkbuilders.navigator.data.utils.DevicePathsExtractor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
Expand All @@ -9,16 +10,19 @@ import moxy.MvpPresenter
import moxy.presenterScope
import space.taran.arklib.arkFolder
import dev.arkbuilders.navigator.data.utils.LogTags
import dev.arkbuilders.navigator.data.utils.listDevices
import java.nio.file.Path
import java.util.LinkedList
import java.util.Queue
import javax.inject.Inject
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import kotlin.io.path.listDirectoryEntries

class RootsScanDialogPresenter : MvpPresenter<RootsScanView>() {

@Inject
lateinit var devicePathsExtractor: DevicePathsExtractor

private val roots = mutableListOf<Path>()
private val queue: Queue<Path> = LinkedList()

Expand All @@ -37,7 +41,7 @@ class RootsScanDialogPresenter : MvpPresenter<RootsScanView>() {
}

private fun scan() = presenterScope.launch(Dispatchers.IO) {
queue.addAll(listDevices())
queue.addAll(devicePathsExtractor.listDevices())

while (queue.isNotEmpty()) {
ensureActive()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import dagger.assisted.AssistedInject
import dev.arkbuilders.navigator.data.PermissionsHelper
import dev.arkbuilders.navigator.data.preferences.PreferenceKey
import dev.arkbuilders.navigator.data.preferences.Preferences
import dev.arkbuilders.navigator.data.utils.DevicePathsExtractor
import dev.arkbuilders.navigator.data.utils.LogTags
import dev.arkbuilders.navigator.data.utils.listDevices
import org.orbitmvi.orbit.Container
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.syntax.simple.intent
Expand Down Expand Up @@ -50,6 +50,7 @@ class FoldersViewModel(
private val resourcesIndexRepo: ResourceIndexRepo,
private val preferences: Preferences,
private val permsHelper: PermissionsHelper,
private val devicePathsExtractor: DevicePathsExtractor
) : ViewModel(), ContainerHost<FoldersState, FoldersSideEffect> {

override val container: Container<FoldersState, FoldersSideEffect> = container(
Expand Down Expand Up @@ -77,7 +78,7 @@ class FoldersViewModel(
)
}
val folders = foldersRepo.provideWithMissing()
devices = listDevices()
devices = devicePathsExtractor.listDevices()

postSideEffect(FoldersSideEffect.ToastFailedPaths(folders.failed))

Expand Down Expand Up @@ -276,14 +277,16 @@ class FoldersViewModelFactory @AssistedInject constructor(
private val resourcesIndexRepo: ResourceIndexRepo,
private val preferences: Preferences,
private val permsHelper: PermissionsHelper,
private val devicePathsExtractor: DevicePathsExtractor
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return FoldersViewModel(
rescanRoots,
foldersRepo,
resourcesIndexRepo,
preferences,
permsHelper
permsHelper,
devicePathsExtractor
) as T
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package dev.arkbuilders.navigator.data.utils

import dev.arkbuilders.navigator.presentation.App
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.name

@RunWith(MockitoJUnitRunner::class)
class DevicePathsExtractorTest {

private val mockedApplication = mockk<App>()

private lateinit var testee: DevicePathsExtractor

@BeforeEach
fun setUp() {
testee = DevicePathsExtractorImpl(
appInstance = mockedApplication
)
}

@Test
fun givenApplicationAvailable_whenGetExternalFileDirs_thenReturnCorrectResult() {
val mockedFile = mockk<File>()
val files = listOf(mockedFile)
every { mockedApplication.getExternalFilesDirs(null) } returns files.toTypedArray()
every { mockedFile.exists() } returns true

val mockedPath = mockk<Path>()
every { mockedFile.toPath() } returns mockedPath
every { mockedPath.toRealPath() } returns mockedPath

val directoryIterator: MutableIterator<Path> = arrayListOf(
Paths.get("PATH")
).iterator()
every { mockedPath.iterator() } returns directoryIterator

val result = testee.listDevices()

verify { mockedApplication.getExternalFilesDirs(null) }
assertEquals(1, result.size)
assertEquals("PATH", result[0].name)
}
}
Loading

0 comments on commit 8d6b29f

Please sign in to comment.