music: fix device files uris
This commit is contained in:
parent
ec19808cf1
commit
cc9bb167c4
1 changed files with 48 additions and 40 deletions
|
@ -42,57 +42,65 @@ class DeviceFilesImpl
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
@Inject private val documentPathFactory: DocumentPathFactory
|
private val documentPathFactory: DocumentPathFactory
|
||||||
) : DeviceFiles {
|
) : DeviceFiles {
|
||||||
private val contentResolver = context.contentResolverSafe
|
private val contentResolver = context.contentResolverSafe
|
||||||
|
|
||||||
override fun explore(uris: Flow<Uri>): Flow<DeviceFile> =
|
override fun explore(uris: Flow<Uri>): Flow<DeviceFile> =
|
||||||
uris.flatMapMerge { rootUri -> exploreImpl(contentResolver, rootUri,
|
uris.flatMapMerge { rootUri ->
|
||||||
requireNotNull(documentPathFactory.unpackDocumentTreeUri(rootUri))) }
|
Timber.d("$rootUri")
|
||||||
|
exploreImpl(
|
||||||
|
contentResolver,
|
||||||
|
rootUri,
|
||||||
|
DocumentsContract.getTreeDocumentId(rootUri),
|
||||||
|
requireNotNull(documentPathFactory.unpackDocumentTreeUri(rootUri)))
|
||||||
|
}
|
||||||
|
|
||||||
private fun exploreImpl(
|
private fun exploreImpl(
|
||||||
contentResolver: ContentResolver,
|
contentResolver: ContentResolver,
|
||||||
uri: Uri,
|
rootUri: Uri,
|
||||||
|
treeDocumentId: String,
|
||||||
relativePath: Path
|
relativePath: Path
|
||||||
): Flow<DeviceFile> = flow {
|
): Flow<DeviceFile> = flow {
|
||||||
contentResolver.useQuery(uri, PROJECTION) { cursor ->
|
contentResolver.useQuery(
|
||||||
val childUriIndex =
|
DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, treeDocumentId),
|
||||||
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
|
PROJECTION) { cursor ->
|
||||||
val displayNameIndex =
|
val childUriIndex =
|
||||||
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
|
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
|
||||||
val mimeTypeIndex =
|
val displayNameIndex =
|
||||||
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_MIME_TYPE)
|
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
|
||||||
val sizeIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_SIZE)
|
val mimeTypeIndex =
|
||||||
val lastModifiedIndex =
|
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_MIME_TYPE)
|
||||||
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_LAST_MODIFIED)
|
val sizeIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_SIZE)
|
||||||
// Recurse into sub-directories as another flow
|
val lastModifiedIndex =
|
||||||
val recursions = mutableListOf<Flow<DeviceFile>>()
|
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_LAST_MODIFIED)
|
||||||
while (cursor.moveToNext()) {
|
val recursive = mutableListOf<Flow<DeviceFile>>()
|
||||||
val childId = cursor.getString(childUriIndex)
|
while (cursor.moveToNext()) {
|
||||||
val childUri = DocumentsContract.buildDocumentUriUsingTree(uri, childId)
|
val childId = cursor.getString(childUriIndex)
|
||||||
val displayName = cursor.getString(displayNameIndex)
|
val displayName = cursor.getString(displayNameIndex)
|
||||||
val path = relativePath.file(displayName)
|
val newPath = relativePath.file(displayName)
|
||||||
val mimeType = cursor.getString(mimeTypeIndex)
|
val mimeType = cursor.getString(mimeTypeIndex)
|
||||||
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
|
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
|
||||||
// This does NOT block the current coroutine. Instead, we will
|
// This does NOT block the current coroutine. Instead, we will
|
||||||
// evaluate this flow in parallel later to maximize throughput.
|
// evaluate this flow in parallel later to maximize throughput.
|
||||||
recursions.add(exploreImpl(contentResolver, childUri, path))
|
recursive.add(exploreImpl(contentResolver, rootUri, childId, newPath))
|
||||||
} else {
|
} else if (mimeType.startsWith("audio/") && mimeType != "audio/x-mpegurl") {
|
||||||
// Immediately emit all files given that it's just an O(1) op.
|
// Immediately emit all files given that it's just an O(1) op.
|
||||||
// This also just makes sure the outer flow has a reason to exist
|
// This also just makes sure the outer flow has a reason to exist
|
||||||
// rather than just being a glorified async.
|
// rather than just being a glorified async.
|
||||||
val lastModified = cursor.getLong(lastModifiedIndex)
|
val lastModified = cursor.getLong(lastModifiedIndex)
|
||||||
val size = cursor.getLong(sizeIndex)
|
val size = cursor.getLong(sizeIndex)
|
||||||
emit(DeviceFile(childUri, mimeType, path, size, lastModified))
|
emit(
|
||||||
|
DeviceFile(
|
||||||
|
DocumentsContract.buildDocumentUriUsingTree(rootUri, childId),
|
||||||
|
mimeType,
|
||||||
|
newPath,
|
||||||
|
size,
|
||||||
|
lastModified))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
emitAll(recursive.asFlow().flattenMerge())
|
||||||
}
|
}
|
||||||
// Hypothetically, we could just emitAll as we recurse into a new directory,
|
|
||||||
// but this will block the flow and force the tree search to be sequential.
|
|
||||||
// Instead, try to leverage flow parallelism and do all recursive calls in parallel.
|
|
||||||
// Kotlin coroutines can handle doing possibly thousands of parallel calls, it'll
|
|
||||||
// be fine. I hope.
|
|
||||||
emitAll(recursions.asFlow().flattenMerge())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
Loading…
Reference in a new issue