Skip to content

Commit

Permalink
Show notes: handle Trakt note deletion returning null (void) response
Browse files Browse the repository at this point in the history
  • Loading branch information
UweTrottmann committed Nov 21, 2024
1 parent 112b908 commit c71d39a
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import com.battlelancer.seriesguide.shows.database.SgShow2
import com.battlelancer.seriesguide.sync.HexagonShowSync
import com.battlelancer.seriesguide.traktapi.TraktCredentials
import com.battlelancer.seriesguide.traktapi.TraktTools2
import com.battlelancer.seriesguide.traktapi.TraktTools2.TraktErrorResponse
import com.battlelancer.seriesguide.traktapi.TraktTools2.TraktNonNullResponse
import com.battlelancer.seriesguide.traktapi.TraktTools2.TraktResponse
import com.uwetrottmann.androidutils.AndroidUtils
import com.uwetrottmann.seriesguide.backend.shows.model.SgCloudShow
import dagger.Lazy
Expand Down Expand Up @@ -525,44 +528,44 @@ class ShowTools2 @Inject constructor(
if (noteTraktId == null) return@withContext null
val response = TraktTools2.deleteNote(trakt, noteTraktId)
return@withContext when (response) {
is TraktTools2.TraktResponse.Success -> {
is TraktResponse.Success -> {
StoreUserNoteResult("", null) // Remove text and Trakt ID
}

is TraktTools2.TraktResponse.IsUnauthorized -> {
is TraktErrorResponse.IsUnauthorized -> {
TraktCredentials.get(context).setCredentialsInvalid()
null // Abort
}

is TraktTools2.TraktResponse.IsNotVip -> {
is TraktErrorResponse.IsNotVip -> {
Timber.d("storeUserNote: user is not Trakt VIP, can not delete at Trakt")
result // Store as is
}

is TraktTools2.TraktResponse.Error -> null // Abort
is TraktErrorResponse.Other -> null // Abort
}
} else {
// Add or update note
val response = TraktTools2.saveNoteForShow(trakt.notes(), showTmdbId, noteText)
return@withContext when (response) {
is TraktTools2.TraktResponse.Success -> {
is TraktNonNullResponse.Success -> {
// Store ID and note text from Trakt
// (which may shorten or otherwise modify it).
val storedText = response.data.notes ?: ""
StoreUserNoteResult(storedText, response.data.id)
}

is TraktTools2.TraktResponse.IsUnauthorized -> {
is TraktErrorResponse.IsUnauthorized -> {
TraktCredentials.get(context).setCredentialsInvalid()
null
}

is TraktTools2.TraktResponse.IsNotVip -> {
is TraktErrorResponse.IsNotVip -> {
Timber.d("storeUserNote: user is not Trakt VIP, can not upload to Trakt")
result // Store as is
}

is TraktTools2.TraktResponse.Error -> null // Abort
is TraktErrorResponse.Other -> null // Abort
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import com.battlelancer.seriesguide.shows.database.SgShow2Helper
import com.battlelancer.seriesguide.traktapi.SgTrakt
import com.battlelancer.seriesguide.traktapi.TraktSettings
import com.battlelancer.seriesguide.traktapi.TraktTools2
import com.battlelancer.seriesguide.traktapi.TraktTools2.TraktErrorResponse
import com.battlelancer.seriesguide.traktapi.TraktTools2.TraktNonNullResponse
import com.battlelancer.seriesguide.util.Errors
import com.battlelancer.seriesguide.util.TimeTools
import com.uwetrottmann.trakt5.TraktV2
Expand Down Expand Up @@ -182,10 +184,10 @@ class TraktNotesSync(
val response = TraktTools2
.saveNoteForShow(traktNotes, showTmdbId, noteText)
when (response) {
is TraktTools2.TraktResponse.Success -> response.data
// Note: if failing due to not VIP, downloaded notes before, which would
// have required VIP; so assume it expired when getting until this point.
is TraktTools2.TraktResponse.IsNotVip -> {
is TraktNonNullResponse.Success -> response.data
is TraktErrorResponse.IsNotVip -> {
// Note: if failing due to not VIP, downloaded notes before, which would
// have required VIP; so assume it expired when getting until this point.
Timber.e("uploadNotesForShows: user is no longer VIP")
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,23 @@ import retrofit2.awaitResponse

object TraktTools2 {

sealed class TraktResponse<T> {
data class Success<T>(val data: T) : TraktResponse<T>()
class IsNotVip<T> : TraktResponse<T>()
class IsUnauthorized<T> : TraktResponse<T>()
class Error<T> : TraktResponse<T>()
sealed interface TraktResponse<T> {
data class Success<T>(
/**
* If T is [Void] this is always `null`.
*/
val data: T?
) : TraktResponse<T>
}

sealed interface TraktNonNullResponse<T> {
data class Success<T>(val data: T) : TraktNonNullResponse<T>
}

sealed interface TraktErrorResponse {
class IsNotVip<T> : TraktResponse<T>, TraktNonNullResponse<T>
class IsUnauthorized<T> : TraktResponse<T>, TraktNonNullResponse<T>
class Other<T> : TraktResponse<T>, TraktNonNullResponse<T>
}

/**
Expand All @@ -47,9 +59,9 @@ object TraktTools2 {
traktNotes: Notes,
showTmdbId: Int,
noteText: String
): TraktResponse<Note> {
): TraktNonNullResponse<Note> {
// Note: calling the add endpoint for an existing note will update it
return awaitTraktCall(
return awaitTraktCallNonNull(
traktNotes.addNote(
AddNoteRequest(
Show().apply {
Expand All @@ -68,25 +80,58 @@ object TraktTools2 {
return awaitTraktCall(trakt.notes().deleteNote(noteId), "delete note")
}

private suspend fun <T> awaitTraktCall(call: Call<T>, action: String): TraktResponse<T> {
try {
val response = call.awaitResponse()
if (response.isSuccessful) {
val body = response.body()
if (body != null) {
return TraktResponse.Success(body)
private suspend fun <T> awaitTraktCall(
call: Call<T>,
action: String,
logErrorOnNullBody: Boolean = false
): TraktResponse<T> {
val response = try {
call.awaitResponse()
} catch (e: Exception) {
Errors.logAndReport(action, e)
return TraktErrorResponse.Other()
}

if (!response.isSuccessful) {
return when {
TraktV2.isNotVip(response) -> TraktErrorResponse.IsNotVip()
TraktV2.isUnauthorized(response) -> TraktErrorResponse.IsUnauthorized()
else -> {
Errors.logAndReport(action, response)
TraktErrorResponse.Other()
}
}
}

val body = response.body()

if (logErrorOnNullBody && body == null) {
Errors.logAndReport(action, response, "body is null")
}

return TraktResponse.Success(body)
}

/**
* Like [awaitTraktCall], but ensures the response is not null.
*/
private suspend fun <T> awaitTraktCallNonNull(
call: Call<T>,
action: String
): TraktNonNullResponse<T> {
return when (val response = awaitTraktCall(call, action, logErrorOnNullBody = true)) {
is TraktErrorResponse.Other -> response
is TraktErrorResponse.IsNotVip -> response
is TraktErrorResponse.IsUnauthorized -> response
is TraktResponse.Success -> {
val data = response.data
if (data == null) {
TraktErrorResponse.Other()
} else {
Errors.logAndReport(action, response, "body is null")
TraktNonNullResponse.Success(data)
}
} else {
if (TraktV2.isNotVip(response)) return TraktResponse.IsNotVip()
if (TraktV2.isUnauthorized(response)) return TraktResponse.IsUnauthorized()
Errors.logAndReport(action, response)
}
} catch (e: Exception) {
Errors.logAndReport(action, e)
}
return TraktResponse.Error()
}

/**
Expand Down

0 comments on commit c71d39a

Please sign in to comment.