Skip to content

Commit fa4d006

Browse files
committed
Merge branch 'trakt-api-updates' into dev
2 parents 8603acc + d6e1274 commit fa4d006

File tree

11 files changed

+239
-179
lines changed

11 files changed

+239
-179
lines changed

app/src/main/java/com/battlelancer/seriesguide/jobs/BaseNetworkJob.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: Apache-2.0
2-
// Copyright 2017-2025 Uwe Trottmann
2+
// SPDX-FileCopyrightText: Copyright © 2017 Uwe Trottmann <uwe@uwetrottmann.com>
33

44
package com.battlelancer.seriesguide.jobs
55

@@ -125,8 +125,8 @@ abstract class BaseNetworkJob(
125125
SgTrakt.checkForTraktError(trakt, response)
126126
)
127127
val resultCode = when {
128-
SgTrakt.isRateLimitExceeded(response) || SgTrakt.isServerError(response) -> ERROR_TRAKT_SERVER
129-
SgTrakt.isAccountLimitExceeded(response) -> ERROR_TRAKT_ACCOUNT_LIMIT_EXCEEDED
128+
TraktV2.isRateLimitExceeded(response) || TraktV2.isServerError(response) -> ERROR_TRAKT_SERVER
129+
TraktV2.isAccountLimitExceeded(response) -> ERROR_TRAKT_ACCOUNT_LIMIT_EXCEEDED
130130
TraktV2.isAccountLocked(response) -> ERROR_TRAKT_ACCOUNT_LOCKED
131131
else -> ERROR_TRAKT_CLIENT
132132
}

app/src/main/java/com/battlelancer/seriesguide/shows/search/discover/TraktAddLoader.kt

Lines changed: 22 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: Apache-2.0
2-
// Copyright 2015-2024 Uwe Trottmann
2+
// SPDX-FileCopyrightText: Copyright © 2015 Uwe Trottmann <uwe@uwetrottmann.com>
33

44
package com.battlelancer.seriesguide.shows.search.discover
55

@@ -8,14 +8,13 @@ import androidx.annotation.StringRes
88
import com.battlelancer.seriesguide.R
99
import com.battlelancer.seriesguide.SgApp
1010
import com.battlelancer.seriesguide.shows.ShowsSettings
11-
import com.battlelancer.seriesguide.traktapi.SgTrakt
12-
import com.battlelancer.seriesguide.util.Errors
11+
import com.battlelancer.seriesguide.traktapi.TraktTools4
12+
import com.battlelancer.seriesguide.traktapi.TraktTools4.TraktNonNullResponse.Success
1313
import com.uwetrottmann.androidutils.AndroidUtils
1414
import com.uwetrottmann.androidutils.GenericSimpleLoader
1515
import com.uwetrottmann.trakt5.TraktV2
16-
import com.uwetrottmann.trakt5.entities.BaseShow
17-
import com.uwetrottmann.trakt5.enums.Extended
18-
import retrofit2.Response
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.runBlocking
1918
import java.util.LinkedList
2019

2120
/**
@@ -48,46 +47,28 @@ class TraktAddLoader(
4847
private val trakt: TraktV2 = SgApp.getServicesComponent(context).trakt()
4948

5049
override fun loadInBackground(): Result {
51-
var shows: List<BaseShow> = emptyList()
52-
var action: String? = null
53-
try {
54-
val response: Response<List<BaseShow>>
50+
val response = runBlocking(Dispatchers.Default) {
51+
val traktSync = trakt.sync()
5552
when (type) {
56-
Type.WATCHED -> {
57-
action = "load watched shows"
58-
response = trakt.sync().watchedShows(Extended.NOSEASONS).execute()
59-
}
60-
61-
Type.COLLECTION -> {
62-
action = "load show collection"
63-
response = trakt.sync().collectionShows(null).execute()
64-
}
53+
Type.WATCHED -> TraktTools4.getWatchedShows(traktSync, noSeasons = true)
54+
Type.COLLECTION -> TraktTools4.getCollectedShows(traktSync)
55+
Type.WATCHLIST -> TraktTools4.getShowsOnWatchlist(traktSync)
56+
}
57+
}
6558

66-
Type.WATCHLIST -> {
67-
action = "load show watchlist"
68-
response = trakt.sync().watchlistShows(Extended.FULL).execute()
69-
}
59+
val shows = when (response) {
60+
is Success -> response.data
61+
is TraktTools4.TraktErrorResponse.IsUnauthorized -> {
62+
return buildResultFailure(R.string.trakt_error_credentials)
7063
}
7164

72-
if (response.isSuccessful) {
73-
val body = response.body()
74-
if (body != null) {
75-
shows = body
65+
else -> {
66+
// Wait to check for network until here to allow hitting the response cache
67+
return if (AndroidUtils.isNetworkConnected(context)) {
68+
buildResultGenericFailure()
69+
} else {
70+
buildResultFailure(R.string.offline)
7671
}
77-
} else {
78-
if (SgTrakt.isUnauthorized(context, response)) {
79-
return buildResultFailure(R.string.trakt_error_credentials)
80-
}
81-
Errors.logAndReport(action, response)
82-
return buildResultGenericFailure()
83-
}
84-
} catch (e: Exception) {
85-
Errors.logAndReport(action!!, e)
86-
// only check for network here to allow hitting the response cache
87-
return if (AndroidUtils.isNetworkConnected(context)) {
88-
buildResultGenericFailure()
89-
} else {
90-
buildResultFailure(R.string.offline)
9172
}
9273
}
9374

app/src/main/java/com/battlelancer/seriesguide/shows/tools/AddShowTask.kt

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: Apache-2.0
2-
// Copyright 2011-2025 Uwe Trottmann
2+
// SPDX-FileCopyrightText: Copyright © 2011 Uwe Trottmann <uwe@uwetrottmann.com>
33

44
package com.battlelancer.seriesguide.shows.tools
55

@@ -11,13 +11,13 @@ import com.battlelancer.seriesguide.SgApp
1111
import com.battlelancer.seriesguide.backend.settings.HexagonSettings
1212
import com.battlelancer.seriesguide.provider.SeriesGuideDatabase
1313
import com.battlelancer.seriesguide.provider.SgRoomDatabase
14-
import com.battlelancer.seriesguide.shows.tools.AddShowTask.OnShowAddedEvent
1514
import com.battlelancer.seriesguide.shows.tools.AddUpdateShowTools.ShowResult
1615
import com.battlelancer.seriesguide.sync.HexagonEpisodeSync
1716
import com.battlelancer.seriesguide.traktapi.TraktCredentials
1817
import com.battlelancer.seriesguide.traktapi.TraktSettings
19-
import com.battlelancer.seriesguide.traktapi.TraktTools2
20-
import com.battlelancer.seriesguide.traktapi.TraktTools2.ServiceResult
18+
import com.battlelancer.seriesguide.traktapi.TraktTools4
19+
import com.battlelancer.seriesguide.traktapi.TraktTools4.TraktErrorResponse.IsUnauthorized
20+
import com.battlelancer.seriesguide.traktapi.TraktTools4.TraktNonNullResponse.Success
2121
import com.battlelancer.seriesguide.util.Errors
2222
import com.uwetrottmann.androidutils.AndroidUtils
2323
import com.uwetrottmann.trakt5.entities.BaseShow
@@ -114,7 +114,7 @@ class AddShowTask(
114114
addQueue.addAll(shows)
115115
}
116116

117-
fun run() {
117+
suspend fun run() {
118118
Timber.d("Starting to add shows...")
119119

120120
val firstShow = addQueue.peek()
@@ -298,15 +298,30 @@ class AddShowTask(
298298
publishProgress(result, 0, "")
299299
}
300300

301-
private fun getTraktShows(isCollectionNotWatched: Boolean): Map<Int, BaseShow>? {
302-
val result: Pair<Map<Int, BaseShow>?, ServiceResult> =
303-
TraktTools2.getCollectedOrWatchedShows(isCollectionNotWatched, context)
304-
if (result.second == ServiceResult.AUTH_ERROR) {
305-
publishProgress(RESULT_TRAKT_AUTH_ERROR)
306-
} else if (result.second == ServiceResult.API_ERROR) {
307-
publishProgress(RESULT_TRAKT_API_ERROR)
301+
private suspend fun getTraktShows(isCollectionNotWatched: Boolean): Map<Int, BaseShow>? {
302+
val traktSync = SgApp.getServicesComponent(context).traktSync()!!
303+
304+
val response =
305+
if (isCollectionNotWatched) {
306+
TraktTools4.getCollectedShowsByTmdbId(traktSync)
307+
} else {
308+
TraktTools4.getWatchedShowsByTmdbId(traktSync)
309+
}
310+
311+
when (response) {
312+
is Success -> {
313+
return response.data
314+
}
315+
316+
is IsUnauthorized -> {
317+
publishProgress(RESULT_TRAKT_AUTH_ERROR)
318+
}
319+
320+
else -> {
321+
publishProgress(RESULT_TRAKT_API_ERROR)
322+
}
308323
}
309-
return result.first
324+
return null
310325
}
311326

312327
companion object {

app/src/main/java/com/battlelancer/seriesguide/sync/TraktEpisodeSync.kt

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: Apache-2.0
2-
// Copyright 2017-2024 Uwe Trottmann
2+
// SPDX-FileCopyrightText: Copyright © 2017 Uwe Trottmann <uwe@uwetrottmann.com>
33

44
package com.battlelancer.seriesguide.sync
55

@@ -12,10 +12,10 @@ import com.battlelancer.seriesguide.shows.database.SgEpisode2ForSync
1212
import com.battlelancer.seriesguide.shows.database.SgEpisode2WatchedUpdate
1313
import com.battlelancer.seriesguide.shows.episodes.EpisodeFlags
1414
import com.battlelancer.seriesguide.shows.episodes.EpisodeTools
15-
import com.battlelancer.seriesguide.traktapi.SgTrakt
1615
import com.battlelancer.seriesguide.traktapi.TraktSettings
1716
import com.battlelancer.seriesguide.traktapi.TraktTools
18-
import com.battlelancer.seriesguide.traktapi.TraktTools2
17+
import com.battlelancer.seriesguide.traktapi.TraktTools4
18+
import com.battlelancer.seriesguide.traktapi.TraktTools4.TraktNonNullResponse.Success
1919
import com.battlelancer.seriesguide.util.Errors
2020
import com.battlelancer.seriesguide.util.TimeTools
2121
import com.uwetrottmann.trakt5.entities.BaseSeason
@@ -25,6 +25,8 @@ import com.uwetrottmann.trakt5.entities.SyncEpisode
2525
import com.uwetrottmann.trakt5.entities.SyncItems
2626
import com.uwetrottmann.trakt5.entities.SyncSeason
2727
import com.uwetrottmann.trakt5.entities.SyncShow
28+
import kotlinx.coroutines.Dispatchers
29+
import kotlinx.coroutines.runBlocking
2830
import org.threeten.bp.OffsetDateTime
2931
import timber.log.Timber
3032

@@ -63,7 +65,11 @@ class TraktEpisodeSync(
6365
* Trakt. If an episode has multiple plays, uploads it multiple times.
6466
* If false, sets episodes that are not watched on Trakt but watched locally
6567
* (and only those, e.g. no skipped episodes) as not watched.
68+
*
69+
* Note: this uses [runBlocking], so if the calling thread is interrupted this will throw
70+
* [InterruptedException].
6671
*/
72+
@Throws(InterruptedException::class)
6773
fun syncWatched(
6874
tmdbIdsToShowIds: Map<Int, Long>,
6975
watchedAt: OffsetDateTime?,
@@ -75,21 +81,11 @@ class TraktEpisodeSync(
7581
}
7682
val lastWatchedAt = TraktSettings.getLastEpisodesWatchedAt(context)
7783
if (isInitialSync || TimeTools.isAfterMillis(watchedAt, lastWatchedAt)) {
78-
val watchedShowsTrakt = try {
79-
val response = traktSync!!.sync
80-
.watchedShows(null)
81-
.execute()
82-
if (!response.isSuccessful) {
83-
if (SgTrakt.isUnauthorized(context, response)) {
84-
return false
85-
}
86-
Errors.logAndReport("get watched shows", response)
87-
return false
84+
val watchedShowsTrakt = runBlocking(Dispatchers.Default) {
85+
when (val response = TraktTools4.getWatchedShowsByTmdbId(traktSync!!.sync)) {
86+
is Success -> response.data
87+
else -> null
8888
}
89-
response.body()
90-
} catch (e: Exception) {
91-
Errors.logAndReport("get watched shows", e)
92-
return false
9389
} ?: return false
9490

9591
// apply database updates, if initial sync upload diff
@@ -124,7 +120,11 @@ class TraktEpisodeSync(
124120
* Trakt.
125121
* If false, sets episodes that are not collected on Trakt but collected locally
126122
* as not collected.
123+
*
124+
* Note: this uses [runBlocking], so if the calling thread is interrupted this will throw
125+
* [InterruptedException].
127126
*/
127+
@Throws(InterruptedException::class)
128128
fun syncCollected(
129129
tmdbIdsToShowIds: Map<Int, Long>,
130130
collectedAt: OffsetDateTime?,
@@ -136,21 +136,12 @@ class TraktEpisodeSync(
136136
}
137137
val lastCollectedAt = TraktSettings.getLastEpisodesCollectedAt(context)
138138
if (isInitialSync || TimeTools.isAfterMillis(collectedAt, lastCollectedAt)) {
139-
val collectedShowsTrakt = try {
140-
val response = traktSync!!.sync
141-
.collectionShows(null)
142-
.execute()
143-
if (!response.isSuccessful) {
144-
if (SgTrakt.isUnauthorized(context, response)) {
145-
return false
146-
}
147-
Errors.logAndReport("get collected shows", response)
148-
return false
139+
140+
val collectedShowsTrakt = runBlocking(Dispatchers.Default) {
141+
when (val response = TraktTools4.getCollectedShowsByTmdbId(traktSync!!.sync)) {
142+
is Success -> response.data
143+
else -> null
149144
}
150-
response.body()
151-
} catch (e: Exception) {
152-
Errors.logAndReport("get collected shows", e)
153-
return false
154145
} ?: return false
155146

156147
// apply database updates, if initial sync upload diff
@@ -183,13 +174,11 @@ class TraktEpisodeSync(
183174
}
184175

185176
private fun processTraktShows(
186-
remoteShows: List<BaseShow>,
177+
tmdbIdsToTraktShow: Map<Int, BaseShow>,
187178
tmdbIdsToShowIds: Map<Int, Long>,
188179
flag: Flag,
189180
isInitialSync: Boolean
190181
): Boolean {
191-
val tmdbIdsToTraktShow = TraktTools2.mapByTmdbId(remoteShows)
192-
193182
var uploadedShowsCount = 0
194183
val showIdsToLastWatched: MutableMap<Long, Long> = HashMap()
195184
val showsToClear = ArrayList<Long>()

app/src/main/java/com/battlelancer/seriesguide/sync/TraktSync.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: Apache-2.0
2-
// Copyright 2017-2025 Uwe Trottmann
2+
// SPDX-FileCopyrightText: Copyright © 2017 Uwe Trottmann <uwe@uwetrottmann.com>
33

44
package com.battlelancer.seriesguide.sync
55

@@ -49,7 +49,8 @@ class TraktSync(
4949
* To not conflict with Hexagon sync, can turn on [onlyRatings] so only
5050
* ratings are synced.
5151
*
52-
* Note: this calls [TraktNotesSync.syncForShows] which may throw [InterruptedException].
52+
* Note: this calls [syncEpisodes] and [TraktNotesSync.syncForShows] which may throw
53+
* [InterruptedException].
5354
*/
5455
@Throws(InterruptedException::class)
5556
fun sync(onlyRatings: Boolean): SgSyncAdapter.UpdateResult {
@@ -136,8 +137,12 @@ class TraktSync(
136137
/**
137138
* Downloads and uploads episode watched and collected flags.
138139
*
139-
* Do **NOT** call if there are no local shows to avoid unnecessary work.
140+
* Do **NOT** call if there are no local shows to avoid unnecessary work.
141+
*
142+
* Note: this calls [TraktEpisodeSync.syncWatched] and [TraktEpisodeSync.syncCollected] which
143+
* may throw [InterruptedException].
140144
*/
145+
@Throws(InterruptedException::class)
141146
private fun syncEpisodes(
142147
tmdbIdsToShowIds: Map<Int, Long>,
143148
lastActivity: LastActivityMore
@@ -170,7 +175,7 @@ class TraktSync(
170175
fun <T> handleUnsuccessfulResponse(response: Response<T>, action: String) {
171176
if (SgTrakt.isUnauthorized(context, response)) {
172177
return // Do not report auth errors.
173-
} else if (SgTrakt.isAccountLimitExceeded(response)) {
178+
} else if (TraktV2.isAccountLimitExceeded(response)) {
174179
// Currently should only occur on initial sync when uploading items to watchlist or
175180
// collection (notes upload has its own error handling).
176181
progress.setImportantErrorIfNone(context.getString(R.string.trakt_error_limit_exceeded_upload))

app/src/main/java/com/battlelancer/seriesguide/traktapi/SgTrakt.kt

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: Apache-2.0
2-
// Copyright 2014-2025 Uwe Trottmann
2+
// SPDX-FileCopyrightText: Copyright © 2014 Uwe Trottmann <uwe@uwetrottmann.com>
33

44
package com.battlelancer.seriesguide.traktapi
55

@@ -90,30 +90,6 @@ class SgTrakt(
9090
}
9191
}
9292

93-
/**
94-
* Returns if the response code is 420, which indicates an account limit would be exceeded.
95-
* These limits [can be higher for VIP users](https://trakt.docs.apiary.io/#introduction/vip-methods).
96-
*/
97-
fun isAccountLimitExceeded(response: Response<*>): Boolean {
98-
return response.code() == 420
99-
}
100-
101-
/**
102-
* Returns if the response code is 429, which indicates the rate limit was exceeded.
103-
*
104-
* [Trakt rate limiting info](https://trakt.docs.apiary.io/#introduction/rate-limiting)
105-
*/
106-
fun isRateLimitExceeded(response: Response<*>): Boolean {
107-
return response.code() == 429
108-
}
109-
110-
/**
111-
* Returns if the response code is 500 or greater, which indicates a server error.
112-
*/
113-
fun isServerError(response: Response<*>): Boolean {
114-
return response.code() >= 500
115-
}
116-
11793
fun checkForTraktError(trakt: TraktV2, response: Response<*>): String? {
11894
val error = trakt.checkForTraktError(response)
11995
return if (error?.message != null) {

0 commit comments

Comments
 (0)