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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ fun String.isDefaultCloudDriveWebDavEndpoint(): Boolean =
URI(DEFAULT_CLOUD_DRIVE_ENDPOINT_URL).port
}.getOrDefault(false)

fun webDavDirectoryWarmupChain(parentPath: String): List<String> =
buildList {
add("")
var current = ""
parentPath.replace('\\', '/').trim('/').split('/')
.filter(String::isNotBlank)
.forEach { segment ->
current += "/$segment"
add(current)
}
}

@Serializable
data class CloudDriveAutomationConfig(
val endpointUrl: String = DEFAULT_CLOUD_DRIVE_ENDPOINT_URL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ class CloudDriveDirectoryConventionsTest {
assertEquals("/CloudRoot/Anime", normalizeCloudDriveDirectoryPath("/CloudRoot/Anime/"))
}

@Test
fun `builds WebDAV warmup chain from root to parent`() {
assertEquals(listOf(""), webDavDirectoryWarmupChain(""))
assertEquals(
listOf("", "/115open", "/115open/影音", "/115open/影音/动漫"),
webDavDirectoryWarmupChain("/115open/影音/动漫/"),
)
}

@Test
fun `scopes navigation requests to token root`() {
assertEquals("/CloudRoot", scopedCloudDriveDirectoryPath("/", "/CloudRoot"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.miruplay.tv.model.connectionPassword
import com.miruplay.tv.model.connectionUsername
import com.miruplay.tv.model.isDefaultCloudDriveWebDavEndpoint
import com.miruplay.tv.model.remoteUrl
import com.miruplay.tv.model.webDavDirectoryWarmupChain
import com.miruplay.tv.repository.MediaSourceRepository
import java.util.Base64
import javax.inject.Inject
Expand All @@ -27,23 +28,27 @@ class PlaybackHttpRequestResolver @Inject constructor(
if (remoteUrl.isBlank()) return PlaybackHttpRequestConfig.Empty

if (remoteUrl.isDefaultCloudDriveWebDavEndpoint()) {
warmWebDavParentDirectory(mediaSource, source.uri, remoteUrl)
warmWebDavParentDirectories(mediaSource, source.uri, remoteUrl)
}
return PlaybackHttpRequestConfig(
baseUrl = remoteUrl,
headers = mapOf(AUTHORIZATION_HEADER to mediaSource.playbackAuthorizationHeader()),
)
}

private suspend fun warmWebDavParentDirectory(
private suspend fun warmWebDavParentDirectories(
source: MediaSourceInfo,
uri: String,
remoteUrl: String,
) {
val parentPath = webDavParentDirectoryForPlayback(uri, remoteUrl) ?: return
val directories = webDavParentDirectoryForPlayback(uri, remoteUrl)
?.let(::webDavDirectoryWarmupChain)
?: return
val mediaSource = mediaSourceFactory?.create(source)?.getOrNull() ?: return
try {
runCatching { mediaSource.listFiles(parentPath) }
directories.forEach { directory ->
runCatching { mediaSource.listFiles(directory) }
}
} finally {
runCatching { mediaSource.close() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.miruplay.tv.model.MediaSourceType
import com.miruplay.tv.model.PlaybackSource
import com.miruplay.tv.repository.MediaSourceRepository
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.coVerifySequence
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
Expand Down Expand Up @@ -71,10 +71,15 @@ class PlaybackHttpRequestResolverTest {
),
)

coVerify(exactly = 1) {
coVerifySequence {
webDavSource.listFiles("")
webDavSource.listFiles("/115open")
webDavSource.listFiles("/115open/影音")
webDavSource.listFiles("/115open/影音/动漫")
webDavSource.listFiles("/115open/影音/动漫/從 0 位居民開始的邊境領主大人")
webDavSource.listFiles("/115open/影音/动漫/從 0 位居民開始的邊境領主大人/Season 1")
webDavSource.close()
}
coVerify(exactly = 1) { webDavSource.close() }
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.miruplay.tv.model.MediaSourceType
import com.miruplay.tv.model.Episode
import com.miruplay.tv.model.isDefaultCloudDriveWebDavEndpoint
import com.miruplay.tv.model.remoteUrl
import com.miruplay.tv.model.webDavDirectoryWarmupChain
import com.miruplay.tv.repository.MediaExtraKind
import com.miruplay.tv.repository.MediaIndexEntry
import com.miruplay.tv.repository.MediaIndexRepository
Expand Down Expand Up @@ -95,8 +96,8 @@ class MlipLibraryIndexImporter @Inject constructor(
}
}

val warmCloudDriveArtworkParents =
source.remoteUrl().orEmpty().isDefaultCloudDriveWebDavEndpoint()
val warmedCloudDriveArtworkDirectories =
if (source.remoteUrl().orEmpty().isDefaultCloudDriveWebDavEndpoint()) mutableSetOf<String>() else null
var artworkCachedCount = 0
for (series in snapshot.series) {
val seriesFiles = mediaFiles.filter { it.seriesId == series.id }
Expand Down Expand Up @@ -137,7 +138,7 @@ class MlipLibraryIndexImporter @Inject constructor(
posterCacheDirectory = posterCacheDirectory,
sourceId = source.id,
seriesUuid = series.uuid,
warmParentDirectory = warmCloudDriveArtworkParents,
warmedDirectories = warmedCloudDriveArtworkDirectories,
)
}
?.also { artworkCachedCount += 1 }
Expand Down Expand Up @@ -570,7 +571,7 @@ class MlipLibraryIndexImporter @Inject constructor(
posterCacheDirectory: File?,
sourceId: Long,
seriesUuid: String,
warmParentDirectory: Boolean,
warmedDirectories: MutableSet<String>?,
): String? {
val cacheRoot = posterCacheDirectory ?: return null
val outputDir = File(cacheRoot, "mlip/$sourceId").apply { mkdirs() }
Expand All @@ -580,10 +581,10 @@ class MlipLibraryIndexImporter @Inject constructor(
?: "jpg"
val output = File(outputDir, "${stableHash("$seriesUuid:$artworkPath")}.$extension")
if (output.length() > 0L) return output.absolutePath
if (warmParentDirectory) {
artworkPath.substringBeforeLast('/', "")
.takeIf(String::isNotBlank)
?.let { parent -> runCatching { mediaSource.listFiles(parent) } }
if (warmedDirectories != null) {
webDavDirectoryWarmupChain(artworkPath.substringBeforeLast('/', ""))
.filter(warmedDirectories::add)
.forEach { directory -> runCatching { mediaSource.listFiles(directory) } }
}
val stream = mediaSource.openStream(artworkPath).getOrNull() ?: return null
return runCatching {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ class MlipLibraryIndexImporterTest {

assertTrue(result is Result.Success)
assertEquals(1, (result as Result.Success).data.artworkCachedCount)
assertEquals(listOf("/Series"), mediaSource.listedPaths)
assertEquals(listOf("", "/Series"), mediaSource.listedPaths)
assertNotNull(metadataRepository.anime.single().posterLocalPath)
} finally {
databaseFile.delete()
Expand Down
Loading