Commit 599618dc authored by Geoffrey Métais's avatar Geoffrey Métais

Refactor BrowserProvider with CoroutineScope

parent fdcfa84a
......@@ -27,10 +27,11 @@ import android.os.Handler
import android.os.HandlerThread
import android.os.Process
import android.support.v4.util.SimpleArrayMap
import android.util.Log
import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.android.HandlerContext
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.android.Main
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.channels.actor
import kotlinx.coroutines.experimental.channels.mapTo
import org.videolan.libvlc.Media
import org.videolan.libvlc.util.MediaBrowser
......@@ -46,11 +47,9 @@ import java.util.*
const val TAG = "VLC/BrowserProvider"
abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<MediaLibraryItem>, val url: String?, private val showHiddenFiles: Boolean) : EventListener {
abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<MediaLibraryItem>, val url: String?, private val showHiddenFiles: Boolean) : EventListener, CoroutineScope {
init {
fetch()
}
override val coroutineContext = Dispatchers.Main.immediate
protected var mediabrowser: MediaBrowser? = null
......@@ -61,21 +60,24 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
val descriptionUpdate = MutableLiveData<Pair<Int, String>>()
internal val medialibrary = Medialibrary.getInstance()
init {
fetch()
}
protected open fun initBrowser() {
if (mediabrowser == null) mediabrowser = MediaBrowser(VLCInstance.get(), this, browserHandler)
}
open fun fetch() {
val prefetchList by lazy(LazyThreadSafetyMode.NONE) { BrowserProvider.prefetchLists[url] }
val list by lazy(LazyThreadSafetyMode.NONE) { prefetchLists[url] }
when {
url === null -> launch(UI) {
url === null -> launch(Dispatchers.Main) {
browseRoot()
job?.join()
parseSubDirectories()
}
prefetchList?.isEmpty() == false -> launch(UI) {
dataset.value = prefetchList
BrowserProvider.prefetchLists.remove(url)
list?.isEmpty() == false -> launch(Dispatchers.Main) {
dataset.value = list
prefetchLists.remove(url)
parseSubDirectories()
}
else -> browse(url)
......@@ -83,9 +85,13 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
}
protected open fun browse(url: String? = null) {
browserActor.offer(Browse(url))
}
private fun browseImpl(url: String? = null) {
browserChannel = Channel(Channel.UNLIMITED)
requestBrowsing(url)
job = launch(UI.immediate) {
job = launch {
for (media in browserChannel) addMedia(findMedia(media))
if (dataset.value.isNotEmpty()) parseSubDirectories()
else dataset.clear() // send observable event when folder is empty
......@@ -94,67 +100,72 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
protected open fun addMedia(media: MediaLibraryItem) = dataset.add(media)
open fun refresh(): Boolean {
open fun refresh() : Boolean {
if (url === null) return false
browserActor.offer(Refresh)
return true
}
internal open fun parseSubDirectories() {
browserActor.offer(ParseSubDirectories)
}
open fun refreshImpl() {
browserChannel = Channel(Channel.UNLIMITED)
requestBrowsing(url)
job = launch(UI.immediate) {
job = launch {
dataset.value = browserChannel.mapTo(mutableListOf()) { findMedia(it) }
parseSubDirectories()
}
return true
}
internal open suspend fun parseSubDirectories() {
private suspend fun parseSubDirectoriesImpl() {
if (dataset.value.isEmpty()) return
val currentMediaList = dataset.value.toList()
launch(browserContext, parent = job) {
val directories: MutableList<MediaWrapper> = ArrayList()
val files: MutableList<MediaWrapper> = ArrayList()
foldersContentMap.clear()
initBrowser()
var currentParsedPosition = -1
loop@ while (++currentParsedPosition < currentMediaList.size) {
if (!isActive) {
browserChannel.close()
return@launch
}
//skip media that are not browsable
val item = currentMediaList[currentParsedPosition]
val current = when {
item.itemType == MediaLibraryItem.TYPE_MEDIA -> {
val mw = item as MediaWrapper
if (mw.type != MediaWrapper.TYPE_DIR && mw.type != MediaWrapper.TYPE_PLAYLIST) continue@loop
if (mw.uri.scheme == "otg" || mw.uri.scheme == "content") continue@loop
mw
}
item.itemType == MediaLibraryItem.TYPE_STORAGE -> MediaWrapper((item as Storage).uri).apply { type = MediaWrapper.TYPE_DIR }
else -> continue@loop
}
// request parsing
browserChannel = Channel(Channel.UNLIMITED)
mediabrowser?.browse(current.uri, 0)
// retrieve subitems
for (media in browserChannel) {
val type = media.type
val mw = findMedia(media)
if (type == Media.Type.Directory) directories.add(mw)
else if (type == Media.Type.File) files.add(mw)
val currentMediaList = withContext(Dispatchers.Main) { dataset.value.toList() }
val directories: MutableList<MediaWrapper> = ArrayList()
val files: MutableList<MediaWrapper> = ArrayList()
foldersContentMap.clear()
initBrowser()
var currentParsedPosition = -1
loop@ while (++currentParsedPosition < currentMediaList.size) {
if (!isActive) {
browserChannel.close()
return
}
//skip media that are not browsable
val item = currentMediaList[currentParsedPosition]
val current = when {
item.itemType == MediaLibraryItem.TYPE_MEDIA -> {
val mw = item as MediaWrapper
if (mw.type != MediaWrapper.TYPE_DIR && mw.type != MediaWrapper.TYPE_PLAYLIST) continue@loop
if (mw.uri.scheme == "otg" || mw.uri.scheme == "content") continue@loop
mw
}
// all subitems are in
val holderText = getDescription(directories.size, files.size)
if (holderText != "") {
val position = currentParsedPosition
launch(UI) {
item.description = holderText
descriptionUpdate.value = Pair(position, holderText)
}
directories.addAll(files)
foldersContentMap.put(item, directories.toMutableList())
item.itemType == MediaLibraryItem.TYPE_STORAGE -> MediaWrapper((item as Storage).uri).apply { type = MediaWrapper.TYPE_DIR }
else -> continue@loop
}
// request parsing
browserChannel = Channel(Channel.UNLIMITED)
mediabrowser?.browse(current.uri, 0)
// retrieve subitems
for (media in browserChannel) {
val type = media.type
val mw = findMedia(media)
if (type == Media.Type.Directory) directories.add(mw)
else if (type == Media.Type.File) files.add(mw)
}
// all subitems are in
getDescription(directories.size, files.size).takeIf { it.isNotEmpty() }?.let {
val position = currentParsedPosition
launch {
item.description = it
descriptionUpdate.value = Pair(position, it)
}
directories.clear()
files.clear()
directories.addAll(files)
foldersContentMap.put(item, directories.toMutableList())
}
directories.clear()
files.clear()
}
}
......@@ -183,7 +194,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
return mw
}
abstract fun browseRoot()
abstract suspend fun browseRoot()
open fun getFlags() : Int {
var flags = MediaBrowser.Flag.Interact or MediaBrowser.Flag.NoSlavesAutodetect
......@@ -191,7 +202,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
return flags
}
private fun requestBrowsing(url: String?) = launch(browserContext) {
private fun requestBrowsing(url: String?) = launch(Dispatchers.IO) {
initBrowser()
mediabrowser?.let {
if (url != null) it.browse(Uri.parse(url), getFlags())
......@@ -201,8 +212,9 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
fun stop() = job?.cancel()
open fun release() = launch(BrowserProvider.browserContext) {
open fun release() = launch(Dispatchers.IO) {
if (this@BrowserProvider::browserChannel.isInitialized) browserChannel.close()
job?.cancelAndJoin()
mediabrowser?.let {
it.release()
mediabrowser = null
......@@ -213,6 +225,12 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
fun isFolderEmpty(mw: MediaWrapper) = foldersContentMap[mw]?.isEmpty() ?: true
private val browserActor = actor<BrowserAction>(Dispatchers.IO, Channel.UNLIMITED) { for (action in channel) when (action) {
is Browse -> browseImpl(action.url)
is Refresh -> refreshImpl()
is ParseSubDirectories -> parseSubDirectoriesImpl()
} }
companion object {
private val browserHandler by lazy {
val handlerThread = HandlerThread("vlc-mProvider", Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE)
......@@ -220,6 +238,10 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
Handler(handlerThread.looper)
}
private val prefetchLists = mutableMapOf<String, MutableList<MediaLibraryItem>>()
private val browserContext by lazy { HandlerContext(browserHandler, "provider-context") }
}
}
\ No newline at end of file
}
private sealed class BrowserAction
private class Browse(val url: String?) : BrowserAction()
private object Refresh : BrowserAction()
private object ParseSubDirectories : BrowserAction()
\ No newline at end of file
......@@ -90,40 +90,38 @@ open class FileBrowserProvider(
showFavorites = url == null && !filePicker && this !is StorageProvider
}
override fun browseRoot() {
launch(UI.immediate, parent = job) {
val internalmemoryTitle = context.getString(R.string.internal_memory)
val browserStorage = context.getString(R.string.browser_storages)
val storages = DirectoryRepository.getInstance(context).getMediaDirectories()
val devices = mutableListOf<MediaLibraryItem>()
if (!filePicker) devices.add(DummyItem(browserStorage))
for (mediaDirLocation in storages) {
if (!File(mediaDirLocation).exists()) continue
val directory = MediaWrapper(AndroidUtil.PathToUri(mediaDirLocation))
directory.type = MediaWrapper.TYPE_DIR
if (TextUtils.equals(AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY, mediaDirLocation)) {
directory.setDisplayTitle(internalmemoryTitle)
storagePosition = devices.size
} else {
val deviceName = FileUtils.getStorageTag(directory.title)
if (deviceName != null) directory.setDisplayTitle(deviceName)
directory.addStateFlags(MediaLibraryItem.FLAG_STORAGE)
}
devices.add(directory)
override suspend fun browseRoot() {
val internalmemoryTitle = context.getString(R.string.internal_memory)
val browserStorage = context.getString(R.string.browser_storages)
val storages = DirectoryRepository.getInstance(context).getMediaDirectories()
val devices = mutableListOf<MediaLibraryItem>()
if (!filePicker) devices.add(DummyItem(browserStorage))
for (mediaDirLocation in storages) {
if (!File(mediaDirLocation).exists()) continue
val directory = MediaWrapper(AndroidUtil.PathToUri(mediaDirLocation))
directory.type = MediaWrapper.TYPE_DIR
if (TextUtils.equals(AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY, mediaDirLocation)) {
directory.setDisplayTitle(internalmemoryTitle)
storagePosition = devices.size
} else {
val deviceName = FileUtils.getStorageTag(directory.title)
if (deviceName != null) directory.setDisplayTitle(deviceName)
directory.addStateFlags(MediaLibraryItem.FLAG_STORAGE)
}
if (AndroidUtil.isLolliPopOrLater && !ExternalMonitor.devices.value.isEmpty()) {
val otg = MediaWrapper(Uri.parse("otg://")).apply {
title = "OTG Device"
type = MediaWrapper.TYPE_DIR
}
otgPosition = devices.size
devices.add(otg)
devices.add(directory)
}
if (AndroidUtil.isLolliPopOrLater && !ExternalMonitor.devices.value.isEmpty()) {
val otg = MediaWrapper(Uri.parse("otg://")).apply {
title = "OTG Device"
type = MediaWrapper.TYPE_DIR
}
dataset.value = devices
// observe devices & favorites
ExternalMonitor.devices.observeForever(this@FileBrowserProvider)
if (showFavorites) favorites?.observeForever(favoritesObserver)
otgPosition = devices.size
devices.add(otg)
}
dataset.value = devices
// observe devices & favorites
ExternalMonitor.devices.observeForever(this@FileBrowserProvider)
if (showFavorites) favorites?.observeForever(favoritesObserver)
}
......
......@@ -42,5 +42,5 @@ class FilePickerProvider(context: Context, dataset: LiveDataset<MediaLibraryItem
if (media is MediaWrapper && (media.type == MediaWrapper.TYPE_SUBTITLE || media.type == MediaWrapper.TYPE_DIR)) super.addMedia(media)
}
override suspend fun parseSubDirectories() {}
override fun parseSubDirectories() {}
}
\ No newline at end of file
......@@ -23,6 +23,8 @@ package org.videolan.vlc.providers
import android.arch.lifecycle.Observer
import android.content.Context
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import org.videolan.medialibrary.media.DummyItem
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.medialibrary.media.MediaWrapper
......@@ -40,23 +42,23 @@ class NetworkProvider(context: Context, dataset: LiveDataset<MediaLibraryItem>,
favorites?.observeForever(this)
}
override fun browseRoot() {
override suspend fun browseRoot() {
if (ExternalMonitor.allowLan()) browse()
}
override fun fetch() {}
override fun refresh(): Boolean {
if (url == null) {
return if (url == null) {
dataset.value = mutableListOf<MediaLibraryItem>().apply {
getFavoritesList(favorites?.value)?.let { addAll(it) }
}
browseRoot()
launch { browseRoot() }
true
} else super.refresh()
return true
}
override suspend fun parseSubDirectories() {
override fun parseSubDirectories() {
if (url != null) super.parseSubDirectories()
}
......
......@@ -38,29 +38,27 @@ import java.util.*
class StorageProvider(context: Context, dataset: LiveDataset<MediaLibraryItem>, url: String?, showHiddenFiles: Boolean): FileBrowserProvider(context, dataset, url, false, showHiddenFiles) {
override fun browseRoot() {
launch(UI.immediate, parent = job) {
val storages = DirectoryRepository.getInstance(context).getMediaDirectories()
val customDirectories = DirectoryRepository.getInstance(context).getCustomDirectories()
var storage: Storage
val storagesList = ArrayList<MediaLibraryItem>()
override suspend fun browseRoot() {
val storages = DirectoryRepository.getInstance(context).getMediaDirectories()
val customDirectories = DirectoryRepository.getInstance(context).getCustomDirectories()
var storage: Storage
val storagesList = ArrayList<MediaLibraryItem>()
for (mediaDirLocation in storages) {
if (TextUtils.isEmpty(mediaDirLocation)) continue
storage = Storage(Uri.fromFile(File(mediaDirLocation)))
if (TextUtils.equals(AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY, mediaDirLocation))
storage.name = context.getString(R.string.internal_memory)
storagesList.add(storage)
}
customLoop@ for (customDir in customDirectories) {
for (mediaDirLocation in storages) {
if (TextUtils.isEmpty(mediaDirLocation)) continue
storage = Storage(Uri.fromFile(File(mediaDirLocation)))
if (TextUtils.equals(AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY, mediaDirLocation))
storage.name = context.getString(R.string.internal_memory)
storagesList.add(storage)
}
customLoop@ for (customDir in customDirectories) {
for (mediaDirLocation in storages) {
if (TextUtils.isEmpty(mediaDirLocation)) continue
if (customDir.path.startsWith(mediaDirLocation)) continue@customLoop
}
storage = Storage(Uri.parse(customDir.path))
storagesList.add(storage)
if (customDir.path.startsWith(mediaDirLocation)) continue@customLoop
}
dataset.value = storagesList
storage = Storage(Uri.parse(customDir.path))
storagesList.add(storage)
}
dataset.value = storagesList
}
override fun addMedia(media: MediaLibraryItem) {
......
......@@ -51,7 +51,7 @@ open class BrowserModel(context: Context, val url: String?, type: Int, showHidde
override fun refresh() = provider.refresh()
fun browserRoot() = provider.browseRoot()
fun browserRoot() = launch { provider.browseRoot() }
@MainThread
override fun sort(sort: Int) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment