0.2.58 - Requests module
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// What a request is doing, as a slug the television renders.
|
||||
//
|
||||
// These are the states Radarr and Sonarr can actually answer for. Neither has an approval
|
||||
// workflow — an added movie is simply added — so there is deliberately no "approved" or
|
||||
// "declined" here: inventing one would put a word on a card that nothing behind it can ever
|
||||
// change. The client renders an unknown slug in its neutral treatment (see requestStatusTone
|
||||
// on the television), so if a household ever puts an approval layer in front of the *arrs,
|
||||
// those states can be added here and reach existing builds without an app release.
|
||||
const (
|
||||
// The household has it. Either Emby has imported it or the *arr reports a file.
|
||||
RequestStatusAvailable = "available"
|
||||
// Accepted and being worked on: released, monitored, no file yet.
|
||||
RequestStatusProcessing = "processing"
|
||||
// Accepted, but there is nothing to fetch yet — unreleased, or still only in cinemas.
|
||||
RequestStatusPending = "pending"
|
||||
// Recorded by Memby, but the *arr could not be asked. The honest fallback: we know
|
||||
// somebody asked and nothing more.
|
||||
RequestStatusRequested = "requested"
|
||||
// Recorded by Memby and the *arr no longer has it, so somebody removed it downstream.
|
||||
RequestStatusUnavailable = "unavailable"
|
||||
// Search only: nothing has it and nobody has asked, so the button does something.
|
||||
RequestStatusRequestable = "requestable"
|
||||
)
|
||||
|
||||
// lookupStatusFor answers what a search result is, which is a different question from what
|
||||
// a stored request is doing: a candidate nothing tracks is the ordinary case here — it is
|
||||
// the whole point of searching — where in a request list it would mean somebody had removed
|
||||
// it. So the two rules are separate rather than one rule with a flag, and only this one can
|
||||
// return "requestable".
|
||||
//
|
||||
// Being this viewer's own ask outranks the household merely having added it: "Requested"
|
||||
// tells them they already did this, which is the thing they most need to know before
|
||||
// pressing a button a second time. It does not outrank availability — a title that is in
|
||||
// the library is watchable now, and that is better news.
|
||||
func lookupStatusFor(subject RequestSubject, mine bool) string {
|
||||
switch {
|
||||
case subject.InLibrary || subject.HasFile:
|
||||
return RequestStatusAvailable
|
||||
case mine:
|
||||
return RequestStatusRequested
|
||||
case !subject.Tracked:
|
||||
return RequestStatusRequestable
|
||||
case !subject.Released:
|
||||
return RequestStatusPending
|
||||
default:
|
||||
return RequestStatusProcessing
|
||||
}
|
||||
}
|
||||
|
||||
// RequestSubject is what the *arr and the library between them know about one title, in
|
||||
// the narrow shape requestStatusFor needs. Keeping it free of Radarr and Sonarr types is
|
||||
// what lets one rule answer for both catalogues and be tested without either.
|
||||
type RequestSubject struct {
|
||||
// Tracked is whether the *arr still holds the title at all.
|
||||
Tracked bool
|
||||
// HasFile is the *arr's own answer about media on disk. Sonarr's series list does not
|
||||
// report this, so a series leaves it false and leans on InLibrary.
|
||||
HasFile bool
|
||||
// InLibrary is whether Emby has imported it, matched on the catalogue's provider id.
|
||||
InLibrary bool
|
||||
// Released is whether there is anything to fetch yet. A film not yet on digital and a
|
||||
// series whose first episode has not aired are both false.
|
||||
Released bool
|
||||
}
|
||||
|
||||
// requestStatusFor is the whole rule, and it is ordered by how much each signal is worth.
|
||||
//
|
||||
// Availability wins over everything: a title the household can watch is available whether
|
||||
// or not the *arr still tracks it, whether or not it was ever released on the date anybody
|
||||
// recorded. Only then does absence from the *arr mean removal — checked before the release
|
||||
// state, because an untracked title's release date says nothing about a request nobody is
|
||||
// working on any more.
|
||||
func requestStatusFor(subject RequestSubject) string {
|
||||
switch {
|
||||
case subject.InLibrary || subject.HasFile:
|
||||
return RequestStatusAvailable
|
||||
case !subject.Tracked:
|
||||
return RequestStatusUnavailable
|
||||
case !subject.Released:
|
||||
return RequestStatusPending
|
||||
default:
|
||||
return RequestStatusProcessing
|
||||
}
|
||||
}
|
||||
|
||||
// movieReleased reads Radarr's own status word rather than comparing dates.
|
||||
//
|
||||
// Radarr already decides this, applying the household's minimum-availability setting, and
|
||||
// a date comparison here would disagree with it for exactly the titles that are marginal —
|
||||
// which are the ones somebody is watching their request page for. "announced" and
|
||||
// "incinemas" are the two that mean there is nothing to fetch; anything else, including a
|
||||
// word this build has never seen, is treated as released so a new Radarr vocabulary
|
||||
// degrades to "processing" rather than parking a request on "pending" for ever.
|
||||
func movieReleased(status string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(status)) {
|
||||
case "announced", "incinemas":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// seriesReleased asks whether any of the show exists yet.
|
||||
//
|
||||
// Sonarr's series list carries no per-episode file information, so the question it can
|
||||
// answer is narrower than Radarr's: a show whose only airing is in the future has nothing
|
||||
// to fetch. "upcoming" is Sonarr's own word for that; a next-airing date in the future with
|
||||
// no library presence is the same thing said with a timestamp, which is what a show added
|
||||
// before its premiere looks like.
|
||||
func seriesReleased(status string, nextAiring *time.Time, now time.Time) bool {
|
||||
if strings.EqualFold(strings.TrimSpace(status), "upcoming") {
|
||||
return false
|
||||
}
|
||||
if nextAiring != nil && nextAiring.After(now) &&
|
||||
strings.EqualFold(strings.TrimSpace(status), "") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// requestStatusLabel is the wording the card shows, sent from here rather than derived on
|
||||
// the television — the MembyAirLabel precedent. A build that predates a state renders the
|
||||
// label it was handed instead of falling back to a slug.
|
||||
func requestStatusLabel(status string) string {
|
||||
switch status {
|
||||
case RequestStatusAvailable:
|
||||
return "Available"
|
||||
case RequestStatusProcessing:
|
||||
return "Processing"
|
||||
case RequestStatusPending:
|
||||
return "Pending"
|
||||
case RequestStatusUnavailable:
|
||||
return "Unavailable"
|
||||
case RequestStatusRequestable:
|
||||
return "Request"
|
||||
default:
|
||||
return "Requested"
|
||||
}
|
||||
}
|
||||
|
||||
// requestStatusDetail is the quiet second line: what the state means for the viewer, in
|
||||
// plain language, rather than a repeat of the word above it.
|
||||
func requestStatusDetail(status, mediaType string) string {
|
||||
thing := "film"
|
||||
if mediaType == "series" {
|
||||
thing = "series"
|
||||
}
|
||||
switch status {
|
||||
case RequestStatusAvailable:
|
||||
return "Ready to watch now"
|
||||
case RequestStatusProcessing:
|
||||
return "Searching for a copy"
|
||||
case RequestStatusPending:
|
||||
if thing == "series" {
|
||||
return "Waiting for it to air"
|
||||
}
|
||||
return "Waiting for release"
|
||||
case RequestStatusUnavailable:
|
||||
return "No longer being tracked"
|
||||
default:
|
||||
return "Waiting on the " + thing + " service"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user