Browse Source

Use ktor instead of fuel for http requests [Part 2/2]

* update preferred locale in preferences, is is the actual locale implementation
* update token handling for crunchy (country via token)
* update TMDBApiController to use ktor
* add parsable dates to NoneTMDBTVShow and NoneTMDBMovie
pull/49/head
Jannik 4 months ago
parent
commit
75204e522d
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
  1. 7
      app/build.gradle
  2. 210
      app/src/main/java/org/mosad/teapod/parser/crunchyroll/Crunchyroll.kt
  3. 35
      app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt
  4. 16
      app/src/main/java/org/mosad/teapod/preferences/Preferences.kt
  5. 9
      app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AccountFragment.kt
  6. 2
      app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt
  7. 2
      app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt
  8. 91
      app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt
  9. 4
      app/src/main/java/org/mosad/teapod/util/tmdb/TMDBDataTypes.kt
  10. 1
      app/src/main/res/values/strings.xml

7
app/build.gradle

@ -14,7 +14,7 @@ android {
minSdkVersion 23
targetSdkVersion 30
versionCode 4200 //00.04.200
versionName "1.0.0-alpha4"
versionName "1.0.0-alpha5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime()
@ -72,11 +72,6 @@ dependencies {
implementation 'com.afollestad.material-dialogs:core:3.3.0'
implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0'
implementation 'com.github.kittinunf.fuel:fuel:2.3.1'
implementation 'com.github.kittinunf.fuel:fuel-android:2.3.1'
implementation 'com.github.kittinunf.fuel:fuel-json:2.3.1'
// TODO replace fuel with ktor
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-client-android:$ktor_version"
implementation "io.ktor:ktor-client-serialization:$ktor_version"

210
app/src/main/java/org/mosad/teapod/parser/crunchyroll/Crunchyroll.kt

@ -1,16 +1,34 @@
/**
* Teapod
*
* Copyright 2020-2022 <seil0@mosad.xyz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
package org.mosad.teapod.parser.crunchyroll
import android.util.Log
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.FuelError
import com.github.kittinunf.fuel.core.Parameters
import com.github.kittinunf.fuel.json.FuelJson
import com.github.kittinunf.fuel.json.responseJson
import com.github.kittinunf.result.Result
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.*
@ -35,8 +53,7 @@ object Crunchyroll {
}
private const val baseUrl = "https://beta-api.crunchyroll.com"
private var accessToken = ""
private var tokenType = ""
private lateinit var token: Token
private var tokenValidUntil: Long = 0
private var accountID = ""
@ -45,10 +62,6 @@ object Crunchyroll {
private var signature = ""
private var keyPairID = ""
// TODO temp helper vary
private var locale: String = Preferences.preferredLocal.toLanguageTag()
private var country: String = Preferences.preferredLocal.country
private val browsingCache = arrayListOf<Item>()
/**
@ -61,39 +74,24 @@ object Crunchyroll {
*/
fun login(username: String, password: String): Boolean = runBlocking {
val tokenEndpoint = "/auth/v1/token"
val formData = listOf(
"username" to username,
"password" to password,
"grant_type" to "password",
"scope" to "offline_access"
)
val formData = Parameters.build {
append("username", username)
append("password", password)
append("grant_type", "password")
append("scope", "offline_access")
}
var success: Boolean // is false
var success = false// is false
withContext(Dispatchers.IO) {
val (request, response, result) = Fuel.post("$baseUrl$tokenEndpoint", parameters = formData)
.header("Content-Type", "application/x-www-form-urlencoded")
.appendHeader(
"Authorization",
"Basic "
)
.responseJson()
// TODO fix JSONException: No value for
result.component1()?.obj()?.let {
accessToken = it.get("access_token").toString()
tokenType = it.get("token_type").toString()
// token will be invalid 1 sec
val expiresIn = (it.get("expires_in").toString().toLong() - 1)
tokenValidUntil = System.currentTimeMillis() + (expiresIn * 1000)
// TODO handle exceptions
val response: HttpResponse = client.submitForm("$baseUrl$tokenEndpoint", formParameters = formData) {
header("Authorization", "Basic ")
}
token = response.receive()
tokenValidUntil = System.currentTimeMillis() + (token.expiresIn * 1000)
// println("request: $request")
// println("response: $response")
// println("response: $result")
Log.i(TAG, "login complete with code ${response.statusCode}")
success = (response.statusCode == 200)
Log.i(TAG, "login complete with code ${response.status}")
success = (response.status == HttpStatusCode.OK)
}
return@runBlocking success
@ -110,22 +108,22 @@ object Crunchyroll {
private suspend inline fun <reified T> request(
url: String,
httpMethod: HttpMethod,
params: Parameters = listOf(),
bodyA: Any = Any()
params: List<Pair<String, Any?>> = listOf(),
bodyObject: Any = Any()
): T = coroutineScope {
if (System.currentTimeMillis() > tokenValidUntil) refreshToken()
return@coroutineScope (Dispatchers.IO) {
val response: T = client.request(url) {
method = httpMethod
body = bodyA
header("Authorization", "$tokenType $accessToken")
header("Authorization", "${token.tokenType} ${token.accessToken}")
params.forEach {
parameter(it.first, it.second)
}
// for json body set content type
if (bodyA is JsonObject) {
// for json set body and content type
if (bodyObject is JsonObject) {
body = bodyObject
contentType(ContentType.Application.Json)
}
}
@ -136,88 +134,45 @@ object Crunchyroll {
private suspend inline fun <reified T> requestGet(
endpoint: String,
params: Parameters = listOf(),
params: List<Pair<String, Any?>> = listOf(),
url: String = ""
): T = coroutineScope {
): T {
val path = url.ifEmpty { "$baseUrl$endpoint" }
if (System.currentTimeMillis() > tokenValidUntil) refreshToken()
return@coroutineScope (Dispatchers.IO) {
client.request(path) {
method = HttpMethod.Get
header("Authorization", "$tokenType $accessToken")
params.forEach {
parameter(it.first, it.second)
}
} as T
}
return request(path, HttpMethod.Get, params)
}
private suspend fun requestPost(
endpoint: String,
params: Parameters = listOf(),
params: List<Pair<String, Any?>> = listOf(),
bodyObject: JsonObject
) = coroutineScope {
) {
val path = "$baseUrl$endpoint"
if (System.currentTimeMillis() > tokenValidUntil) refreshToken()
withContext(Dispatchers.IO) {
val response: HttpResponse = client.request(path) {
method = HttpMethod.Post
body = bodyObject
header("Authorization", "$tokenType $accessToken")
contentType(ContentType.Application.Json)
params.forEach {
parameter(it.first, it.second)
}
}
Log.i(TAG, "Response: $response")
}
val response: HttpResponse = request(path, HttpMethod.Post, params, bodyObject)
Log.i(TAG, "Response: $response")
}
private suspend fun requestPatch(
endpoint: String,
params: Parameters = listOf(),
params: List<Pair<String, Any?>> = listOf(),
bodyObject: JsonObject
) = coroutineScope {
) {
val path = "$baseUrl$endpoint"
if (System.currentTimeMillis() > tokenValidUntil) refreshToken()
withContext(Dispatchers.IO) {
val response: HttpResponse = client.request(path) {
method = HttpMethod.Patch
body = bodyObject
header("Authorization", "$tokenType $accessToken")
contentType(ContentType.Application.Json)
params.forEach {
parameter(it.first, it.second)
}
}
Log.i(TAG, "Response: $response")
}
val response: HttpResponse = request(path, HttpMethod.Patch, params, bodyObject)
Log.i(TAG, "Response: $response")
}
private suspend fun requestDelete(
endpoint: String,
params: Parameters = listOf(),
params: List<Pair<String, Any?>> = listOf(),
url: String = ""
) = coroutineScope {
val path = url.ifEmpty { "$baseUrl$endpoint" }
if (System.currentTimeMillis() > tokenValidUntil) refreshToken()
withContext(Dispatchers.IO) {
val response: HttpResponse = client.request(path) {
method = HttpMethod.Delete
header("Authorization", "$tokenType $accessToken")
params.forEach {
parameter(it.first, it.second)
}
}
Log.i(TAG, "Response : $response")
}
val response: HttpResponse = request(path, HttpMethod.Delete, params)
Log.i(TAG, "Response: $response")
}
/**
@ -282,7 +237,7 @@ object Crunchyroll {
): BrowseResult {
val browseEndpoint = "/content/v1/browse"
val noneOptParams = listOf(
"locale" to locale,
"locale" to Preferences.preferredLocale.toLanguageTag(),
"sort_by" to sortBy.str,
"start" to start,
"n" to n
@ -314,7 +269,12 @@ object Crunchyroll {
*/
suspend fun search(query: String, n: Int = 10): SearchResult {
val searchEndpoint = "/content/v1/search"
val parameters = listOf("q" to query, "n" to n, "locale" to locale, "type" to "series")
val parameters = listOf(
"locale" to Preferences.preferredLocale.toLanguageTag(),
"q" to query,
"n" to n,
"type" to "series"
)
// TODO episodes have thumbnails as image, and not poster_tall/poster_tall,
// to work around this, for now only tv shows are supported
@ -337,7 +297,7 @@ object Crunchyroll {
suspend fun objects(objects: List<String>): Collection<Item> {
val episodesEndpoint = "/cms/v2/DE/M3/crunchyroll/objects/${objects.joinToString(",")}"
val parameters = listOf(
"locale" to locale,
"locale" to Preferences.preferredLocale.toLanguageTag(),
"Signature" to signature,
"Policy" to policy,
"Key-Pair-Id" to keyPairID
@ -357,7 +317,7 @@ object Crunchyroll {
@Suppress("unused")
suspend fun seasonList(): DiscSeasonList {
val seasonListEndpoint = "/content/v1/season_list"
val parameters = listOf("locale" to locale)
val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag())
return try {
requestGet(seasonListEndpoint, parameters)
@ -375,9 +335,9 @@ object Crunchyroll {
* series id == crunchyroll id?
*/
suspend fun series(seriesId: String): Series {
val seriesEndpoint = "/cms/v2/$country/M3/crunchyroll/series/$seriesId"
val seriesEndpoint = "/cms/v2/${token.country}/M3/crunchyroll/series/$seriesId"
val parameters = listOf(
"locale" to locale,
"locale" to Preferences.preferredLocale.toLanguageTag(),
"Signature" to signature,
"Policy" to policy,
"Key-Pair-Id" to keyPairID
@ -398,7 +358,7 @@ object Crunchyroll {
val upNextSeriesEndpoint = "/content/v1/up_next_series"
val parameters = listOf(
"series_id" to seriesId,
"locale" to locale
"locale" to Preferences.preferredLocale.toLanguageTag()
)
return try {
@ -410,10 +370,10 @@ object Crunchyroll {
}
suspend fun seasons(seriesId: String): Seasons {
val seasonsEndpoint = "/cms/v2/$country/M3/crunchyroll/seasons"
val seasonsEndpoint = "/cms/v2/${token.country}/M3/crunchyroll/seasons"
val parameters = listOf(
"series_id" to seriesId,
"locale" to locale,
"locale" to Preferences.preferredLocale.toLanguageTag(),
"Signature" to signature,
"Policy" to policy,
"Key-Pair-Id" to keyPairID
@ -428,10 +388,10 @@ object Crunchyroll {
}
suspend fun episodes(seasonId: String): Episodes {
val episodesEndpoint = "/cms/v2/$country/M3/crunchyroll/episodes"
val episodesEndpoint = "/cms/v2/${token.country}/M3/crunchyroll/episodes"
val parameters = listOf(
"season_id" to seasonId,
"locale" to locale,
"locale" to Preferences.preferredLocale.toLanguageTag(),
"Signature" to signature,
"Policy" to policy,
"Key-Pair-Id" to keyPairID
@ -466,7 +426,7 @@ object Crunchyroll {
*/
suspend fun isWatchlist(seriesId: String): Boolean {
val watchlistSeriesEndpoint = "/content/v1/watchlist/$accountID/$seriesId"
val parameters = listOf("locale" to locale)
val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag())
return try {
(requestGet(watchlistSeriesEndpoint, parameters) as JsonObject)
@ -484,7 +444,7 @@ object Crunchyroll {
*/
suspend fun postWatchlist(seriesId: String) {
val watchlistPostEndpoint = "/content/v1/watchlist/$accountID"
val parameters = listOf("locale" to locale)
val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag())
val json = buildJsonObject {
put("content_id", seriesId)
@ -500,7 +460,7 @@ object Crunchyroll {
*/
suspend fun deleteWatchlist(seriesId: String) {
val watchlistDeleteEndpoint = "/content/v1/watchlist/$accountID/$seriesId"
val parameters = listOf("locale" to locale)
val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag())
requestDelete(watchlistDeleteEndpoint, parameters)
}
@ -515,7 +475,7 @@ object Crunchyroll {
*/
suspend fun playheads(episodeIDs: List<String>): PlayheadsMap {
val playheadsEndpoint = "/content/v1/playheads/$accountID/${episodeIDs.joinToString(",")}"
val parameters = listOf("locale" to locale)
val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag())
return try {
requestGet(playheadsEndpoint, parameters)
@ -527,7 +487,7 @@ object Crunchyroll {
suspend fun postPlayheads(episodeId: String, playhead: Int) {
val playheadsEndpoint = "/content/v1/playheads/$accountID"
val parameters = listOf("locale" to locale)
val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag())
val json = buildJsonObject {
put("content_id", episodeId)
@ -549,7 +509,10 @@ object Crunchyroll {
*/
suspend fun watchlist(n: Int = 20): Watchlist {
val watchlistEndpoint = "/content/v1/$accountID/watchlist"
val parameters = listOf("locale" to locale, "n" to n)
val parameters = listOf(
"locale" to Preferences.preferredLocale.toLanguageTag(),
"n" to n
)
val list: ContinueWatchingList = try {
requestGet(watchlistEndpoint, parameters)
@ -570,7 +533,10 @@ object Crunchyroll {
*/
suspend fun upNextAccount(n: Int = 20): ContinueWatchingList {
val watchlistEndpoint = "/content/v1/$accountID/up_next_account"
val parameters = listOf("locale" to locale, "n" to n)
val parameters = listOf(
"locale" to Preferences.preferredLocale.toLanguageTag(),
"n" to n
)
return try {
requestGet(watchlistEndpoint, parameters)

35
app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt

@ -1,3 +1,25 @@
/**
* Teapod
*
* Copyright 2020-2022 <seil0@mosad.xyz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
package org.mosad.teapod.parser.crunchyroll
import kotlinx.serialization.SerialName
@ -29,8 +51,19 @@ enum class SortBy(val str: String) {
}
/**
* index, account. This must pe present for the app to work!
* token, index, account. This must pe present for the app to work!
*/
@Serializable
data class Token(
@SerialName("access_token") val accessToken: String,
@SerialName("refresh_token") val refreshToken: String,
@SerialName("expires_in") val expiresIn: Int,
@SerialName("token_type") val tokenType: String,
@SerialName("scope") val scope: String,
@SerialName("country") val country: String,
@SerialName("account_id") val accountId: String,
)
@Serializable
data class Index(
@SerialName("cms") val cms: CMS,

16
app/src/main/java/org/mosad/teapod/preferences/Preferences.kt

@ -10,7 +10,7 @@ object Preferences {
var preferSecondary = false
internal set
var preferredLocal = Locale.GERMANY
var preferredLocale: Locale = Locale.forLanguageTag("en-US") // TODO this should be saved (potential offline usage) but fetched on start
internal set
var autoplay = true
internal set
@ -35,6 +35,15 @@ object Preferences {
this.preferSecondary = preferSecondary
}
fun savePreferredLocal(context: Context, preferredLocale: Locale) {
with(getSharedPref(context).edit()) {
putString(context.getString(R.string.save_key_preferred_local), preferredLocale.toLanguageTag())
apply()
}
this.preferredLocale = preferredLocale
}
fun saveAutoplay(context: Context, autoplay: Boolean) {
with(getSharedPref(context).edit()) {
putBoolean(context.getString(R.string.save_key_autoplay), autoplay)
@ -71,6 +80,11 @@ object Preferences {
preferSecondary = sharedPref.getBoolean(
context.getString(R.string.save_key_prefer_secondary), false
)
preferredLocale = Locale.forLanguageTag(
sharedPref.getString(
context.getString(R.string.save_key_preferred_local), "en-US"
) ?: "en-US"
)
autoplay = sharedPref.getBoolean(
context.getString(R.string.save_key_autoplay), true
)

9
app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AccountFragment.kt

@ -180,18 +180,21 @@ class AccountFragment : Fragment() {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.settings_content_language)
.setSingleChoiceItems(items, initialSelection){ dialog, which ->
updatePrefContentLanguage(supportedLocals[which].toLanguageTag())
updatePrefContentLanguage(supportedLocals[which])
dialog.dismiss()
}
.show()
}
@kotlinx.coroutines.ExperimentalCoroutinesApi
private fun updatePrefContentLanguage(languageTag: String) {
private fun updatePrefContentLanguage(preferredLocale: Locale) {
lifecycleScope.launch {
Crunchyroll.postPrefSubLanguage(languageTag)
Crunchyroll.postPrefSubLanguage(preferredLocale.toLanguageTag())
}.invokeOnCompletion {
// update the local preferred content language
Preferences.savePreferredLocal(requireContext(), preferredLocale)
// update profile since the language selection might have changed
profile = lifecycleScope.async { Crunchyroll.profile() }
profile.invokeOnCompletion {

2
app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt

@ -62,7 +62,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
println(upNextSeries)
// load the preferred season (preferred language, language per season, not per stream)
currentSeasonCrunchy = seasonsCrunchy.getPreferredSeason(Preferences.preferredLocal)
currentSeasonCrunchy = seasonsCrunchy.getPreferredSeason(Preferences.preferredLocale)
// load episodes and metaDB in parallel (tmdb needs mediaType, which is set via episodes)
listOf(

2
app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt

@ -83,7 +83,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
var currentPlayback = NonePlayback
// current playback settings
var currentLanguage: Locale = Preferences.preferredLocal
var currentLanguage: Locale = Preferences.preferredLocale
internal set
init {

91
app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt

@ -22,15 +22,17 @@
package org.mosad.teapod.util.tmdb
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.FuelError
import com.github.kittinunf.fuel.core.Parameters
import com.github.kittinunf.fuel.json.FuelJson
import com.github.kittinunf.fuel.json.responseJson
import com.github.kittinunf.result.Result
import kotlinx.coroutines.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromString
import android.util.Log
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.invoke
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import org.mosad.teapod.util.concatenate
@ -41,8 +43,14 @@ import org.mosad.teapod.util.concatenate
*
*/
class TMDBApiController {
private val classTag = javaClass.name
private val json = Json { ignoreUnknownKeys = true }
private val client = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer(json)
}
}
private val apiUrl = "https://api.themoviedb.org/3"
private val apiKey = "de959cf9c07a08b5ca7cb51cda9a40c2"
@ -52,19 +60,22 @@ class TMDBApiController {
const val imageUrl = "https://image.tmdb.org/t/p/w500"
}
private suspend fun request(
private suspend inline fun <reified T> request(
endpoint: String,
parameters: Parameters = emptyList()
): Result<FuelJson, FuelError> = coroutineScope {
parameters: List<Pair<String, Any?>> = emptyList()
): T = coroutineScope {
val path = "$apiUrl$endpoint"
val params = concatenate(listOf("api_key" to apiKey, "language" to language), parameters)
// TODO handle FileNotFoundException
return@coroutineScope (Dispatchers.IO) {
val (_, _, result) = Fuel.get(path, params)
.responseJson()
val response: HttpResponse = client.get(path) {
params.forEach {
parameter(it.first, it.second)
}
}
result
response.receive<T>()
}
}
@ -78,10 +89,12 @@ class TMDBApiController {
val searchEndpoint = "/search/multi"
val parameters = listOf("query" to query, "include_adult" to false)
val result = request(searchEndpoint, parameters)
return result.component1()?.obj()?.let {
json.decodeFromString(it.toString())
} ?: NoneTMDBSearchMovie
return try {
request(searchEndpoint, parameters)
}catch (ex: SerializationException) {
Log.e(classTag, "SerializationException in searchMovie(), with query = $query.", ex)
NoneTMDBSearchMovie
}
}
/**
@ -94,10 +107,12 @@ class TMDBApiController {
val searchEndpoint = "/search/tv"
val parameters = listOf("query" to query, "include_adult" to false)
val result = request(searchEndpoint, parameters)
return result.component1()?.obj()?.let {
json.decodeFromString(it.toString())
} ?: NoneTMDBSearchTVShow
return try {
request(searchEndpoint, parameters)
}catch (ex: SerializationException) {
Log.e(classTag, "SerializationException in searchTVShow(), with query = $query.", ex)
NoneTMDBSearchTVShow
}
}
/**
@ -109,10 +124,12 @@ class TMDBApiController {
val movieEndpoint = "/movie/$movieId"
// TODO is FileNotFoundException handling needed?
val result = request(movieEndpoint)
return result.component1()?.obj()?.let {
json.decodeFromString(it.toString())
} ?: NoneTMDBMovie
return try {
request(movieEndpoint)
}catch (ex: SerializationException) {
Log.e(classTag, "SerializationException in getMovieDetails(), with movieId = $movieId.", ex)
NoneTMDBMovie
}
}
/**
@ -124,10 +141,12 @@ class TMDBApiController {
val tvShowEndpoint = "/tv/$tvId"
// TODO is FileNotFoundException handling needed?
val result = request(tvShowEndpoint)
return result.component1()?.obj()?.let {
json.decodeFromString(it.toString())
} ?: NoneTMDBTVShow
return try {
request(tvShowEndpoint)
}catch (ex: SerializationException) {
Log.e(classTag, "SerializationException in getTVShowDetails(), with tvId = $tvId.", ex)
NoneTMDBTVShow
}
}
@Suppress("unused")
@ -141,10 +160,12 @@ class TMDBApiController {
val tvShowSeasonEndpoint = "/tv/$tvId/season/$seasonNumber"
// TODO is FileNotFoundException handling needed?
val result = request(tvShowSeasonEndpoint)
return result.component1()?.obj()?.let {
json.decodeFromString(it.toString())
} ?: NoneTMDBTVSeason
return try {
request(tvShowSeasonEndpoint)
}catch (ex: SerializationException) {
Log.e(classTag, "SerializationException in getTVSeasonDetails(), with tvId = $tvId, seasonNumber = $seasonNumber.", ex)
NoneTMDBTVSeason
}
}
}

4
app/src/main/java/org/mosad/teapod/util/tmdb/TMDBDataTypes.kt

@ -110,8 +110,8 @@ data class TMDBTVShow(
// use null for nullable types, the gui needs to handle/implement a fallback for null values
val NoneTMDB = TMDBBase(0, "", "", null, null)
val NoneTMDBMovie = TMDBMovie(0, "", "", null, null, "", null, "")
val NoneTMDBTVShow = TMDBTVShow(0, "", "", null, null, "", "", "")
val NoneTMDBMovie = TMDBMovie(0, "", "", null, null, "1970-01-01", null, "")
val NoneTMDBTVShow = TMDBTVShow(0, "", "", null, null, "1970-01-01", "1970-01-01", "")
@Serializable
data class TMDBTVSeason(

1
app/src/main/res/values/strings.xml

@ -132,6 +132,7 @@
<string name="save_key_user_login" translatable="false">org.mosad.teapod.user_login</string>
<string name="save_key_user_password" translatable="false">org.mosad.teapod.user_password</string>
<string name="save_key_prefer_secondary" translatable="false">org.mosad.teapod.prefer_secondary</string>
<string name="save_key_preferred_local" translatable="false">org.mosad.teapod.preferred_local</string>
<string name="save_key_autoplay" translatable="false">org.mosad.teapod.autoplay</string>
<string name="save_key_dev_settings" translatable="false">org.mosad.teapod.dev.settings</string>
<string name="save_key_theme" translatable="false">org.mosad.teapod.theme</string>

Loading…
Cancel
Save

Du besuchst diese Seite mit einem veralteten IPv4-Internetzugang. Möglicherweise treten in Zukunft Probleme mit der Erreichbarkeit und Performance auf. Bitte frage deinen Internetanbieter oder Netzwerkadministrator nach IPv6-Unterstützung.
You are visiting this site with an outdated IPv4 internet access. You may experience problems with accessibility and performance in the future. Please ask your ISP or network administrator for IPv6 support.
Weitere Infos | More Information
Klicke zum schließen | Click to close