fix(app-phone): звук при watch (диагностика+подстраховка mirrorByItem) + качка не воскресает после удаления
- onWatch: явные логи в каждой точке отказа (watch result / audio source / уже играет / не скачано / mediaDir пуст), свежий mirrorByItem если files не догрузился, звук стартует с позиции очков при уже идущем видео - resolveAudioSource → resolveAudioSourceDetailed (AudioSourceResult с причиной null) - DownloadManager: поколения generations — delete() инвалидирует старую корутину (не воскрешает качку), ensureActive перед .part/rename, jobs.remove(itemId, job) - Тесты 62/0 (в т.ч. поколения — поймали null != 0L) Инфраструктурный блокер (не код): SeaweedFS на Server3 76.66 в IO-шторме (load ~184, wa 20%, чужой yt-dlp на хосте) — большие GET с s3.binom.pw висят/рвутся с ~14:00; качка на очки FAILED по этой причине.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package pw.binom.viewmate.glasses
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class GlassesDownloadManagerTest {
|
||||
|
||||
private fun tempDir(): File = Files.createTempDirectory("glasses-dm-test").toFile()
|
||||
|
||||
private fun newBaseDir(): File {
|
||||
val base = tempDir()
|
||||
base.deleteOnExit()
|
||||
return base
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isFullyOnDiskRequiresAllFilesNoParts() {
|
||||
val dir = newBaseDir()
|
||||
val names = listOf("video.mkv", "audio-0.ogg", "audio-1.ogg")
|
||||
|
||||
assertFalse(isFullyOnDisk(dir, names), "пустой каталог — не полный")
|
||||
|
||||
File(dir, "video.mkv").writeBytes(ByteArray(10))
|
||||
assertFalse(isFullyOnDisk(dir, names), "только видео — не полный")
|
||||
|
||||
File(dir, "audio-0.ogg").writeBytes(ByteArray(5))
|
||||
assertFalse(isFullyOnDisk(dir, names), "не все аудио — не полный")
|
||||
|
||||
File(dir, "audio-1.ogg").writeBytes(ByteArray(5))
|
||||
assertTrue(isFullyOnDisk(dir, names), "все файлы — полный")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isFullyOnDiskRejectsPartFiles() {
|
||||
val dir = newBaseDir()
|
||||
File(dir, "video.mkv").writeBytes(ByteArray(10))
|
||||
File(dir, "audio-0.ogg").writeBytes(ByteArray(5))
|
||||
File(dir, "audio-0.ogg.part").writeBytes(ByteArray(2))
|
||||
|
||||
assertFalse(isFullyOnDisk(dir, listOf("video.mkv", "audio-0.ogg")), ".part не даёт полного")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isFullyOnDiskEmptyListIsFalse() {
|
||||
assertFalse(isFullyOnDisk(newBaseDir(), emptyList()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun partNameAppendsSuffix() {
|
||||
assertEquals("video.mkv.part", partName("video.mkv"))
|
||||
assertEquals("audio-0.ogg.part", partName("audio-0.ogg"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun requestForAddsRangeFromExistingPart() {
|
||||
val noResume = glassesRequestFor("https://s3/mirror/video.mkv?sig", 0)
|
||||
assertNull(noResume.header("Range"), "без .part Range-заголовка быть не должно")
|
||||
|
||||
val resume = glassesRequestFor("https://s3/mirror/video.mkv?sig", 1_863_946_240)
|
||||
assertEquals("bytes=1863946240-", resume.header("Range"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filePercentClampsToRange() {
|
||||
assertEquals(0, filePercent(0, 0))
|
||||
assertEquals(50, filePercent(500, 1000))
|
||||
assertEquals(100, filePercent(1000, 1000))
|
||||
assertEquals(99, filePercent(999, 1000))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun overallPercentSumsAcrossFiles() {
|
||||
val done = mapOf("video.mkv" to 100L, "audio-0.ogg" to 25L, "audio-1.ogg" to 0L)
|
||||
val total = mapOf("video.mkv" to 100L, "audio-0.ogg" to 50L, "audio-1.ogg" to 50L)
|
||||
assertEquals(62, overallPercent(done, total)) // 125/200
|
||||
}
|
||||
|
||||
@Test
|
||||
fun overallPercentZeroWithoutTotals() {
|
||||
assertEquals(0, overallPercent(mapOf("video.mkv" to 5L), emptyMap()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stateStoreWritesAndReads() {
|
||||
val file = File(tempDir(), "status.json")
|
||||
val store = GlassesStateStore(file)
|
||||
val state = GlassesState(
|
||||
expected = mapOf("i1" to listOf("video.mkv", "audio-0.ogg")),
|
||||
downloaded = listOf("i1"),
|
||||
)
|
||||
|
||||
store.write(state)
|
||||
val read = store.read()
|
||||
|
||||
assertEquals(state, read)
|
||||
assertTrue(file.exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stateStoreMissingReadsEmpty() {
|
||||
val store = GlassesStateStore(File(tempDir(), "status.json"))
|
||||
assertEquals(GlassesState(), store.read())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun localVideoPathNullWhenNotDownloaded() {
|
||||
val base = newBaseDir()
|
||||
val dir = File(base, "i1").apply { mkdirs() }
|
||||
File(dir, "video.mkv").writeBytes(ByteArray(10))
|
||||
|
||||
// без ожидаемого списка файлов isFullyDownloaded = false
|
||||
assertFalse(isFullyOnDisk(dir, listOf("video.mkv", "audio-0.ogg")))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user