From 26d2da923b445b35fd8d0e4d53a7251915223e2b Mon Sep 17 00:00:00 2001 From: Jannik Date: Sat, 17 Jul 2021 13:15:14 +0200 Subject: [PATCH] use Gson in TMDBApiController, adapt tmdb types to api documentation * use gson fromJson() to parse tmdb response * adapt tmd types to documentation (nullable/non nullable) --- .../activity/main/fragments/MediaFragment.kt | 8 +- .../main/viewmodel/MediaFragmentViewModel.kt | 5 +- .../org/mosad/teapod/util/MetaDBController.kt | 49 ++++++- .../teapod/util/tmdb/TMDBApiController.kt | 138 +++++++++--------- .../mosad/teapod/util/tmdb/TMDBDataTypes.kt | 74 ++++++++-- 5 files changed, 176 insertions(+), 98 deletions(-) diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt index 3d9e13e..6aa1742 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt @@ -86,9 +86,9 @@ class MediaFragment(private val mediaId: Int) : Fragment() { */ private fun updateGUI() = with(model) { // generic gui - val backdropUrl = tmdbResult.backdropPath?.let { TMDBApiController.imageUrl + it } + val backdropUrl = tmdbResult?.backdropPath?.let { TMDBApiController.imageUrl + it } ?: media.info.posterUrl - val posterUrl = tmdbResult.posterPath?.let { TMDBApiController.imageUrl + it } + val posterUrl = tmdbResult?.posterPath?.let { TMDBApiController.imageUrl + it } ?: media.info.posterUrl // load poster and backdrop @@ -138,9 +138,9 @@ class MediaFragment(private val mediaId: Int) : Fragment() { fragments.add(MediaFragmentEpisodes()) pagerAdapter.notifyDataSetChanged() } else if (media.type == MediaType.MOVIE) { - val tmdbMovie = (tmdbResult as Movie) + val tmdbMovie = (tmdbResult as Movie?) - if (tmdbMovie.runtime != null) { + if (tmdbMovie?.runtime != null) { binding.textEpisodesOrRuntime.text = resources.getQuantityString( R.plurals.text_runtime, tmdbMovie.runtime, diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt index e897f68..207218b 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt @@ -6,7 +6,6 @@ import androidx.lifecycle.AndroidViewModel import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.util.* import org.mosad.teapod.util.DataTypes.MediaType -import org.mosad.teapod.util.tmdb.Movie import org.mosad.teapod.util.tmdb.TMDBApiController import org.mosad.teapod.util.tmdb.TMDBResult import org.mosad.teapod.util.tmdb.TVSeason @@ -21,7 +20,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic internal set var nextEpisode = Episode() internal set - lateinit var tmdbResult: TMDBResult // TODO rename + var tmdbResult: TMDBResult? = null // TODO rename internal set var tmdbTVSeason: TVSeason? =null internal set @@ -56,7 +55,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic tmdbResult = when (media.type) { MediaType.MOVIE -> tmdbApiController.getMovieDetails(tmdbId) MediaType.TVSHOW -> tmdbApiController.getTVShowDetails(tmdbId) - else -> Movie(-1) + else -> null } println(tmdbResult) // TODO diff --git a/app/src/main/java/org/mosad/teapod/util/MetaDBController.kt b/app/src/main/java/org/mosad/teapod/util/MetaDBController.kt index 84564e0..9a13e43 100644 --- a/app/src/main/java/org/mosad/teapod/util/MetaDBController.kt +++ b/app/src/main/java/org/mosad/teapod/util/MetaDBController.kt @@ -1,5 +1,28 @@ +/** + * Teapod + * + * Copyright 2020-2021 + * + * 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.util +import android.util.Log import com.google.gson.Gson import com.google.gson.annotations.SerializedName import kotlinx.coroutines.* @@ -25,20 +48,30 @@ class MetaDBController { } } + /** + * Get the meta data for a movie from MetaDB + * @param aodId The AoD id of the media + * @return A meta movie object, or null if not found + */ suspend fun getMovieMetadata(aodId: Int): MovieMeta? { return metaCacheList.firstOrNull { it.aodId == aodId - } as MovieMeta? ?: getMovieMetadata2(aodId) + } as MovieMeta? ?: getMovieMetadataFromDB(aodId) } + /** + * Get the meta data for a tv show from MetaDB + * @param aodId The AoD id of the media + * @return A meta tv show object, or null if not found + */ suspend fun getTVShowMetadata(aodId: Int): TVShowMeta? { return metaCacheList.firstOrNull { it.aodId == aodId - } as TVShowMeta? ?: getTVShowMetadata2(aodId) + } as TVShowMeta? ?: getTVShowMetadataFromDB(aodId) } @Suppress("BlockingMethodInNonBlockingContext") - private suspend fun getMovieMetadata2(aodId: Int): MovieMeta? = withContext(Dispatchers.IO) { + private suspend fun getMovieMetadataFromDB(aodId: Int): MovieMeta? = withContext(Dispatchers.IO) { val url = URL("$repoUrl/movie/$aodId/media.json") return@withContext try { val json = url.readText() @@ -47,12 +80,13 @@ class MetaDBController { meta } catch (ex: FileNotFoundException) { + Log.w(javaClass.name, "Waring: The requested file was not found. Requested ID: $aodId", ex) null } } @Suppress("BlockingMethodInNonBlockingContext") - private suspend fun getTVShowMetadata2(aodId: Int): TVShowMeta? = withContext(Dispatchers.IO) { + private suspend fun getTVShowMetadataFromDB(aodId: Int): TVShowMeta? = withContext(Dispatchers.IO) { val url = URL("$repoUrl/tv/$aodId/media.json") return@withContext try { val json = url.readText() @@ -61,23 +95,26 @@ class MetaDBController { meta } catch (ex: FileNotFoundException) { + Log.w(javaClass.name, "Waring: The requested file was not found. Requested ID: $aodId", ex) null } } } -// TODO move data classes +// class representing the media list json object data class MediaList( val media: List ) +// abstract class used for meta data objects (tv, movie) abstract class Meta { abstract val id: Int abstract val aodId: Int abstract val tmdbId: Int } +// class representing the movie json object data class MovieMeta( override val id: Int, @SerializedName("aod_id") @@ -86,6 +123,7 @@ data class MovieMeta( override val tmdbId: Int ): Meta() +// class representing the tv show json object data class TVShowMeta( override val id: Int, @SerializedName("aod_id") @@ -100,6 +138,7 @@ data class TVShowMeta( val episodes: List ): Meta() +// class used in TVShowMeta, part of the tv show json object data class EpisodeMeta( val id: Int, @SerializedName("aod_media_id") diff --git a/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt b/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt index e5c4348..067a496 100644 --- a/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt +++ b/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt @@ -1,6 +1,29 @@ +/** + * Teapod + * + * Copyright 2020-2021 + * + * 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.util.tmdb import android.util.Log +import com.google.gson.Gson import com.google.gson.JsonParser import kotlinx.coroutines.* import org.mosad.teapod.util.DataTypes.MediaType @@ -8,7 +31,13 @@ import java.io.FileNotFoundException import java.net.URL import java.net.URLEncoder -// TODO use Klaxon? +/** + * Controller for tmdb api integration. + * Data types are in TMDBDataTypes. For the type definitions see: + * https://developers.themoviedb.org/3/getting-started/introduction + * + * TODO evaluate Klaxon + */ class TMDBApiController { private val apiUrl = "https://api.themoviedb.org/3" @@ -25,6 +54,12 @@ class TMDBApiController { } @Suppress("BlockingMethodInNonBlockingContext") + /** + * Search for a media(movie or tv show) in tmdb + * @param query The query text + * @param type The media type (movie or tv show) + * @return The media tmdb id, or -1 if not found + */ suspend fun search(query: String, type: MediaType): Int = withContext(Dispatchers.IO) { val searchUrl = when (type) { MediaType.MOVIE -> searchMovieUrl @@ -45,93 +80,56 @@ class TMDBApiController { } @Suppress("BlockingMethodInNonBlockingContext") - suspend fun getMovieDetails(movieId: Int): Movie = withContext(Dispatchers.IO) { + /** + * Get details for a movie from tmdb + * @param movieId The tmdb ID of the movie + * @return A tmdb movie object, or null if not found + */ + suspend fun getMovieDetails(movieId: Int): Movie? = withContext(Dispatchers.IO) { val url = URL("$detailsMovieUrl/$movieId?api_key=$apiKey&language=$language") - val response = try { - JsonParser.parseString(url.readText()).asJsonObject - } catch (ex: FileNotFoundException) { - Log.w(javaClass.name, "The resource you requested could not be found") - return@withContext Movie(-1) - } - return@withContext try { - Movie( - id = response.get("id").asInt, - name = response.get("title")?.asString, - overview = response.get("overview")?.asString, - posterPath = response.get("poster_path")?.asString, - backdropPath = response.get("backdrop_path")?.asString, - releaseDate = response.get("release_date")?.asString, - runtime = response.get("runtime")?.asInt - ) - } catch (ex: Exception) { - Log.w(javaClass.name, "Error", ex) - Movie(-1) + val json = url.readText() + Gson().fromJson(json, Movie::class.java) + } catch (ex: FileNotFoundException) { + Log.w(javaClass.name, "Waring: The requested media was not found. Requested ID: $movieId", ex) + null } } @Suppress("BlockingMethodInNonBlockingContext") - suspend fun getTVShowDetails(tvId: Int): TVShow = withContext(Dispatchers.IO) { + /** + * Get details for a tv show from tmdb + * @param tvId The tmdb ID of the tv show + * @return A tmdb tv show object, or null if not found + */ + suspend fun getTVShowDetails(tvId: Int): TVShow? = withContext(Dispatchers.IO) { val url = URL("$detailsTVUrl/$tvId?api_key=$apiKey&language=$language") - val response = try { - JsonParser.parseString(url.readText()).asJsonObject - } catch (ex: FileNotFoundException) { - Log.w(javaClass.name, "The resource you requested could not be found") - return@withContext TVShow(-1) - } - return@withContext try { - TVShow( - id = response.get("id").asInt, - name = response.get("name")?.asString, - overview = response.get("overview")?.asString, - posterPath = response.get("poster_path")?.asString, - backdropPath = response.get("backdrop_path")?.asString, - firstAirDate = response.get("first_air_date")?.asString, - status = response.get("status")?.asString - ) - } catch (ex: Exception) { - Log.w(javaClass.name, "Error", ex) - TVShow(-1) + val json = url.readText() + Gson().fromJson(json, TVShow::class.java) + } catch (ex: FileNotFoundException) { + Log.w(javaClass.name, "Waring: The requested media was not found. Requested ID: $tvId", ex) + null } } @Suppress("BlockingMethodInNonBlockingContext") + /** + * Get details for a tv show season from tmdb + * @param tvId The tmdb ID of the tv show + * @param seasonNumber The tmdb season number + * @return A tmdb tv season object, or null if not found + */ suspend fun getTVSeasonDetails(tvId: Int, seasonNumber: Int): TVSeason? = withContext(Dispatchers.IO) { val url = URL("$detailsTVUrl/$tvId/season/$seasonNumber?api_key=$apiKey&language=$language") - val response = try { - JsonParser.parseString(url.readText()).asJsonObject - } catch (ex: FileNotFoundException) { - Log.w(javaClass.name, "The resource you requested could not be found") - return@withContext null - } - // println(response) - return@withContext try { - val episodes = response.get("episodes").asJsonArray.map { - TVEpisode( - id = it.asJsonObject.get("id").asInt, - name = it.asJsonObject.get("name")?.asString ?: "", - overview = it.asJsonObject.get("overview")?.asString ?: "", - airDate = it.asJsonObject.get("air_date")?.asString ?: "", - episodeNumber = it.asJsonObject.get("episode_number")?.asInt ?: -1 - ) - } - - TVSeason( - id = response.get("id").asInt, - name = response.asJsonObject.get("name")?.asString ?: "", - overview = response.asJsonObject.get("overview")?.asString ?: "", - posterPath = response.asJsonObject.get("poster_path")?.asString ?: "", - airDate = response.asJsonObject.get("air_date")?.asString ?: "", - episodes = episodes, - seasonNumber = response.get("season_number")?.asInt ?: -1 - ) - } catch (ex: Exception) { - Log.w(javaClass.name, "Error", ex) + val json = url.readText() + Gson().fromJson(json, TVSeason::class.java) + } catch (ex: FileNotFoundException) { + Log.w(javaClass.name, "Waring: The requested media was not found. Requested ID: $tvId, Season: $seasonNumber", ex) null } } diff --git a/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBDataTypes.kt b/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBDataTypes.kt index 08fdc70..5a474c7 100644 --- a/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBDataTypes.kt +++ b/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBDataTypes.kt @@ -1,32 +1,69 @@ +/** + * Teapod + * + * Copyright 2020-2021 + * + * 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.util.tmdb +import com.google.gson.annotations.SerializedName + +/** + * These data classes represent the tmdb api json objects. + * Fields which are nullable in the tmdb api are also nullable here. + */ + abstract class TMDBResult{ abstract val id: Int - abstract val name: String? - abstract val overview: String? + abstract val name: String + abstract val overview: String? // for movies tmdb return string or null abstract val posterPath: String? abstract val backdropPath: String? } data class Movie( override val id: Int, - override val name: String? = null, - override val overview: String? = null, - override val posterPath: String? = null, - override val backdropPath: String? = null, - val releaseDate: String? = null, - val runtime: Int? = null + override val name: String, + override val overview: String?, + @SerializedName("poster_path") + override val posterPath: String?, + @SerializedName("backdrop_path") + override val backdropPath: String?, + @SerializedName("release_date") + val releaseDate: String, + @SerializedName("runtime") + val runtime: Int?, // TODO generes ): TMDBResult() data class TVShow( override val id: Int, - override val name: String? = null, - override val overview: String? = null, - override val posterPath: String? = null, - override val backdropPath: String? = null, - val firstAirDate: String? = null, - val status: String? = null, + override val name: String, + override val overview: String, + @SerializedName("poster_path") + override val posterPath: String?, + @SerializedName("backdrop_path") + override val backdropPath: String?, + @SerializedName("first_air_date") + val firstAirDate: String, + @SerializedName("status") + val status: String, // TODO generes ): TMDBResult() @@ -34,17 +71,22 @@ data class TVSeason( val id: Int, val name: String, val overview: String, - val posterPath: String, + @SerializedName("poster_path") + val posterPath: String?, + @SerializedName("air_date") val airDate: String, + @SerializedName("episodes") val episodes: List, + @SerializedName("season_number") val seasonNumber: Int ) -// TODO decide whether to use nullable or not data class TVEpisode( val id: Int, val name: String, val overview: String, + @SerializedName("air_date") val airDate: String, + @SerializedName("episode_number") val episodeNumber: Int ) \ No newline at end of file