feat: app-host → телефон-эмулятор (Jellyfin + mirror + стейт + команды)
- Config.kt: env > application.properties > дефолты (креды не в коде) - PhoneState.kt: единый стейт — mode, currentItem, audioIndex, mirrorStatus, activeSessionId, сессии (эмуляция), downloaded - PhoneActions.kt: catalog (с бейджами зеркал), open, watch (PlayVideo на очки), makeMirror, mirrorStatus, download/delete, sessions/selectSession, status - Main.kt: новые консольные команды (catalog/open/watch/mirror/dl/rm/sessions/session/status) - GlassesHub: Welcome берёт mode/sessionId из стейта, glasses_off → пауза - JellyfinClient.item() — элемент по id (для open/watch) - 58 тестов, 130 всего, все зелёные; живые проверки: catalog (реальный Jellyfin), watch (PlayVideo с s3-url), status (стейт обновляется)
This commit is contained in:
@@ -5,6 +5,7 @@ import io.ktor.client.call.body
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.http.HttpStatusCode
|
||||
|
||||
/**
|
||||
* HTTP-клиент Jellyfin (каталог).
|
||||
@@ -37,6 +38,17 @@ class JellyfinClient(
|
||||
recursive = false,
|
||||
)
|
||||
|
||||
/** Один элемент по id; null если не найден (404). */
|
||||
suspend fun item(userId: String, itemId: String): JellyfinItem? {
|
||||
val response = http.get("$baseUrl/Users/$userId/Items/$itemId") {
|
||||
header("X-Emby-Token", apiKey)
|
||||
parameter("Fields", "PrimaryImageAspectRatio,Overview")
|
||||
}
|
||||
if (response.status == HttpStatusCode.NotFound) return null
|
||||
response.ensureSuccess("jellyfin.item")
|
||||
return response.body()
|
||||
}
|
||||
|
||||
/** Поиск по всей библиотеке. */
|
||||
suspend fun search(userId: String, query: String): List<JellyfinItem> = requestItems(
|
||||
userId = userId,
|
||||
|
||||
@@ -109,6 +109,41 @@ class JellyfinClientTest {
|
||||
assertEquals("Movie", items[0].Type)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun itemParsesSingleItemAndSendsToken() = runTest {
|
||||
var capturedPath: String? = null
|
||||
var capturedToken: String? = null
|
||||
val jf = JellyfinClient(
|
||||
baseUrl = "https://jellyfin.binom.pw",
|
||||
apiKey = apiKey,
|
||||
http = client { request ->
|
||||
capturedPath = request.url.encodedPath
|
||||
capturedToken = request.headers["X-Emby-Token"]
|
||||
jsonResponse("""{"Id":"m1","Name":"Interstellar","Type":"Movie","Overview":"Описание"}""")
|
||||
},
|
||||
)
|
||||
|
||||
val item = jf.item(userId = "u1", itemId = "m1")
|
||||
|
||||
assertEquals("/Users/u1/Items/m1", capturedPath)
|
||||
assertEquals(apiKey, capturedToken)
|
||||
kotlin.test.assertNotNull(item)
|
||||
assertEquals("m1", item.Id)
|
||||
assertEquals("Interstellar", item.Name)
|
||||
assertEquals("Описание", item.Overview)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun itemReturnsNullOnNotFound() = runTest {
|
||||
val jf = JellyfinClient(
|
||||
baseUrl = "https://jellyfin.binom.pw",
|
||||
apiKey = apiKey,
|
||||
http = client { respond("", HttpStatusCode.NotFound) },
|
||||
)
|
||||
|
||||
kotlin.test.assertNull(jf.item(userId = "u1", itemId = "missing"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchSendsSearchTerm() = runTest {
|
||||
var capturedSearchTerm: String? = null
|
||||
|
||||
Reference in New Issue
Block a user