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
123 changes: 25 additions & 98 deletions app/src/main/java/com/battlelancer/seriesguide/sync/TraktMovieSync.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2017-2024 Uwe Trottmann
// SPDX-FileCopyrightText: Copyright © 2017 Uwe Trottmann <uwe@uwetrottmann.com>

package com.battlelancer.seriesguide.sync

Expand All @@ -9,20 +9,20 @@ import com.battlelancer.seriesguide.movies.database.SgMovieFlags
import com.battlelancer.seriesguide.movies.tools.MovieTools
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Movies
import com.battlelancer.seriesguide.provider.SgRoomDatabase
import com.battlelancer.seriesguide.traktapi.SgTrakt
import com.battlelancer.seriesguide.traktapi.TraktSettings
import com.battlelancer.seriesguide.traktapi.TraktTools4
import com.battlelancer.seriesguide.traktapi.TraktTools4.TraktNonNullResponse.Success
import com.battlelancer.seriesguide.util.DBUtils
import com.battlelancer.seriesguide.util.Errors
import com.uwetrottmann.trakt5.entities.BaseMovie
import com.uwetrottmann.trakt5.entities.LastActivityMore
import com.uwetrottmann.trakt5.entities.MovieIds
import com.uwetrottmann.trakt5.entities.SyncItems
import com.uwetrottmann.trakt5.entities.SyncMovie
import com.uwetrottmann.trakt5.entities.SyncResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import retrofit2.Response
import timber.log.Timber
import kotlin.collections.set

/**
* Syncs movie collection and watchlist and watched movies with the connected Trakt profile.
Expand Down Expand Up @@ -74,9 +74,15 @@ class TraktMovieSync(
}

// Download Trakt state.
val collection = downloadCollection() ?: return false
val watchlist = downloadWatchlist() ?: return false
val watchedWithPlays = downloadWatched() ?: return false
val collection = runBlocking(Dispatchers.Default) {
downloadCollection()
} ?: return false
val watchlist = runBlocking(Dispatchers.Default) {
downloadWatchlist()
} ?: return false
val watchedWithPlays = runBlocking(Dispatchers.Default) {
downloadWatched()
} ?: return false

// Loop through local movies to build updates.
val localMovies: List<SgMovieFlags> = try {
Expand Down Expand Up @@ -214,100 +220,27 @@ class TraktMovieSync(
return addingSuccessful
}

private fun downloadCollection(): MutableSet<Int>? {
return try {
val response = traktSync.sync
.collectionMovies(null)
.execute()
val collection = verifyListResponse(
response,
"null collection response", ACTION_GET_COLLECTION
)
toTmdbIdSet(collection)
} catch (e: Exception) {
Errors.logAndReport(ACTION_GET_COLLECTION, e)
null
private suspend fun downloadCollection(): MutableSet<Int>? {
return when (val response = TraktTools4.getCollectedMoviesByTmdbId(traktSync.sync)) {
is Success -> response.data
else -> null
}
}

private fun downloadWatchlist(): MutableSet<Int>? {
return try {
val response = traktSync.sync
.watchlistMovies(null)
.execute()
val watchlist = verifyListResponse(
response,
"null watchlist response", ACTION_GET_WATCHLIST
)
toTmdbIdSet(watchlist)
} catch (e: Exception) {
Errors.logAndReport(ACTION_GET_WATCHLIST, e)
null
private suspend fun downloadWatchlist(): MutableSet<Int>? {
return when (val response = TraktTools4.getMoviesOnWatchlistByTmdbId(traktSync.sync)) {
is Success -> response.data
else -> null
}
}

private fun downloadWatched(): MutableMap<Int, Int>? {
return try {
val response = traktSync.sync
.watchedMovies(null)
.execute()
val watched = verifyListResponse(
response,
"null watched response", ACTION_GET_WATCHED
)
mapTmdbIdToPlays(watched)
} catch (e: Exception) {
Errors.logAndReport(ACTION_GET_WATCHED, e)
null
}
}

private fun verifyListResponse(
response: Response<List<BaseMovie>>,
nullResponse: String,
action: String
): List<BaseMovie>? {
return if (response.isSuccessful) {
val movies = response.body()
if (movies == null) {
Timber.e(nullResponse)
}
movies
} else {
if (SgTrakt.isUnauthorized(context, response)) {
return null
}
Errors.logAndReport(action, response)
null
private suspend fun downloadWatched(): MutableMap<Int, Int>? {
return when (val response = TraktTools4.getWatchedMoviesByTmdbId(traktSync.sync)) {
is Success -> response.data
else -> null
}
}

private fun toTmdbIdSet(movies: List<BaseMovie>?): MutableSet<Int>? {
if (movies == null) {
return null
}
val tmdbIdSet: MutableSet<Int> = HashSet()
for (movie in movies) {
val tmdbId = movie.movie?.ids?.tmdb
?: continue // skip invalid values
tmdbIdSet.add(tmdbId)
}
return tmdbIdSet
}

private fun mapTmdbIdToPlays(movies: List<BaseMovie>?): MutableMap<Int, Int>? {
if (movies == null) {
return null
}
val map: MutableMap<Int, Int> = HashMap()
for (movie in movies) {
val tmdbId = movie.movie?.ids?.tmdb
?: continue // skip invalid values
map[tmdbId] = movie.plays
}
return map
}

/**
* Uploads the given movies to the appropriate list(s)/history on Trakt.
*/
Expand Down Expand Up @@ -368,10 +301,4 @@ class TraktMovieSync(

private fun convertToSyncMovieList(movieTmdbIds: Set<Int>): List<SyncMovie> =
movieTmdbIds.map { SyncMovie().id(MovieIds.tmdb(it)) }

companion object {
private const val ACTION_GET_COLLECTION = "get movie collection"
private const val ACTION_GET_WATCHLIST = "get movie watchlist"
private const val ACTION_GET_WATCHED = "get watched movies"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.battlelancer.seriesguide.traktapi.TraktTools4.awaitTraktCall
import com.battlelancer.seriesguide.util.Errors
import com.uwetrottmann.trakt5.TraktV2
import com.uwetrottmann.trakt5.entities.AddNoteRequest
import com.uwetrottmann.trakt5.entities.BaseMovie
import com.uwetrottmann.trakt5.entities.BaseShow
import com.uwetrottmann.trakt5.entities.Note
import com.uwetrottmann.trakt5.entities.Show
Expand All @@ -26,6 +27,9 @@ import timber.log.Timber
*/
object TraktTools4 {

// 1000 is the maximum limit according to https://github.com/trakt/trakt-api/discussions/681
private const val COLLECTION_MAX_LIMIT = 1000

sealed interface TraktResponse<T> {
data class Success<T>(
/**
Expand Down Expand Up @@ -81,8 +85,7 @@ object TraktTools4 {
action = "get collected shows",
reportIsNotVip = true // Should work even if not VIP
) { page ->
// 1000 is the maximum limit according to https://github.com/trakt/trakt-api/discussions/681
traktSync.collectionShows(page, 1000, null)
traktSync.collectionShows(page, COLLECTION_MAX_LIMIT, null)
}
}

Expand Down Expand Up @@ -160,6 +163,60 @@ object TraktTools4 {
)
}

suspend fun getWatchedMoviesByTmdbId(
traktSync: Sync
): TraktNonNullResponse<MutableMap<Int, Int>> {
val response = awaitTraktCallNonNull(
traktSync.watchedMovies(null),
"get watched movies",
reportIsNotVip = true // Should work even if not VIP
)
return mapResponseData(response) { mapMoviesToTmdbIdWithPlays(it) }
}

private fun mapMoviesToTmdbIdWithPlays(traktMovies: List<BaseMovie>): MutableMap<Int, Int> {
val map: MutableMap<Int, Int> = HashMap(traktMovies.size)
for (movie in traktMovies) {
val tmdbId = movie.movie?.ids?.tmdb
?: continue // skip invalid values
map[tmdbId] = movie.plays
}
return map
}

suspend fun getCollectedMoviesByTmdbId(
traktSync: Sync
): TraktNonNullResponse<MutableSet<Int>> {
val response = fetchAllPages(
action = "get collected movies",
reportIsNotVip = true // Should work even if not VIP
) { page ->
traktSync.collectionMovies(page, COLLECTION_MAX_LIMIT, null)
}
return mapResponseData(response) { mapMoviesToTmdbIdSet(it) }
}

suspend fun getMoviesOnWatchlistByTmdbId(
traktSync: Sync
): TraktNonNullResponse<MutableSet<Int>> {
val response = awaitTraktCallNonNull(
traktSync.watchlistMovies(null),
"get movie watchlist",
reportIsNotVip = true // Should work even if not VIP
)
return mapResponseData(response) { mapMoviesToTmdbIdSet(it) }
}

private fun mapMoviesToTmdbIdSet(traktMovies: List<BaseMovie>): MutableSet<Int> {
val tmdbIdSet: MutableSet<Int> = HashSet(traktMovies.size)
for (movie in traktMovies) {
val tmdbId = movie.movie?.ids?.tmdb
?: continue // skip invalid values
tmdbIdSet.add(tmdbId)
}
return tmdbIdSet
}

/**
* Adds or updates the note for the given show.
*
Expand Down