]> git.lizzy.rs Git - go-anidb.git/blobdiff - misc/episode.go
anidb: Correct cache key for LID.MyListEntry
[go-anidb.git] / misc / episode.go
index 1103952d745488ec3111320e2c1b5f8d494581be..920ae93c8481a6cfea1f37be1031e4c0b30d4f2c 100644 (file)
@@ -4,16 +4,24 @@ import (
        "fmt"
        "math"
        "strconv"
+       "strings"
 )
 
 type EpisodeContainer interface {
        // Returns true if this EpisodeContainer is equivalent or a superset of the given EpisodeContainer
        ContainsEpisodes(EpisodeContainer) bool
+       // Returns a channel meant for iterating with for/range.
+       // Sends all contained episodes in order.
+       Episodes() chan Episode
 }
 
 type Formatter interface {
        // Returns a string where the number portion is 0-padded to fit 'width' digits
        Format(width int) string
+
+       // Returns a string where the number portion is 0-padded to be the same length
+       // as max.
+       FormatLog(max int) string
 }
 
 type EpisodeType int
@@ -68,10 +76,17 @@ func (et EpisodeType) String() string {
 type Episode struct {
        Type   EpisodeType
        Number int
+       Part   int
+       Parts  int
+}
+
+// returns how many digits are needed to represent this int
+func scale(i int) int {
+       return 1 + int(math.Floor(math.Log10(float64(i))))
 }
 
 // Converts the Episode into AniDB API episode format.
-func (ep *Episode) String() string {
+func (ep Episode) String() string {
        return ep.Format(1)
 }
 
@@ -80,7 +95,16 @@ func (ep *Episode) scale() int {
        if ep == nil {
                return 1
        }
-       return 1 + int(math.Floor(math.Log10(float64(ep.Number))))
+       return scale(ep.Number)
+}
+
+func (ep *Episode) Episodes() chan Episode {
+       ch := make(chan Episode, 1)
+       if ep != nil {
+               ch <- *ep
+       }
+       close(ch)
+       return ch
 }
 
 // Returns true if ec is an Episode and is identical to this episode,
@@ -89,7 +113,14 @@ func (ep *Episode) scale() int {
 func (ep *Episode) ContainsEpisodes(ec EpisodeContainer) bool {
        switch e := ec.(type) {
        case *Episode:
-               return ep != nil && ep.Type == e.Type && ep.Number == ep.Number
+               if ep == nil {
+                       return false
+               }
+               basic := ep.Type == e.Type && ep.Number == e.Number
+               if ep.Part < 0 { // a whole episode contains any partial episodes
+                       return basic
+               }
+               return basic && ep.Part == e.Part
        case *EpisodeRange:
        case *EpisodeList:
                return EpisodeList{&EpisodeRange{Type: ep.Type, Start: ep, End: ep}}.ContainsEpisodes(ep)
@@ -98,19 +129,72 @@ func (ep *Episode) ContainsEpisodes(ec EpisodeContainer) bool {
        return false
 }
 
-func (ep *Episode) Format(width int) string {
-       return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d", ep.Type, ep.Number)
+func (ep Episode) Format(width int) string {
+       if ep.Part < 0 { // whole episode
+               return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d", ep.Type, ep.Number)
+       }
+       if ep.Parts != 0 { // part X of Y
+               frac := float64(ep.Number) + float64(ep.Part)/float64(ep.Parts)
+
+               return fmt.Sprintf("%s%0"+strconv.Itoa(width)+".2f", ep.Type, frac)
+       }
+       // part N
+       return fmt.Sprintf("%s%0"+strconv.Itoa(width)+"d.%d", ep.Type, ep.Number, ep.Part)
+}
+
+func (ep *Episode) FormatLog(max int) string {
+       return ep.Format(scale(max))
+}
+
+func (ep *Episode) IncPart() {
+       if ep.Parts > 0 && ep.Part == ep.Parts-1 {
+               ep.IncNumber()
+       } else {
+               ep.Part++
+       }
+}
+
+func (ep *Episode) IncNumber() {
+       ep.Part = -1
+       ep.Parts = 0
+       ep.Number++
+}
+
+func (ep *Episode) DecPart() {
+       if ep.Part > 0 {
+               ep.Part--
+       } else {
+               ep.DecNumber()
+       }
+}
+
+func (ep *Episode) DecNumber() {
+       ep.Part = -1
+       ep.Parts = 0
+       ep.Number--
 }
 
 // Parses a string in the usual AniDB API episode format and converts into
 // an Episode.
 func ParseEpisode(s string) *Episode {
+       p := int64(-1)
+
+       parts := strings.Split(s, ".")
+       switch len(parts) {
+       case 1: // no worries
+       case 2:
+               s = parts[0]
+               p, _ = strconv.ParseInt(parts[1], 10, 32)
+       default: // too many dots
+               return nil
+       }
+
        if no, err := strconv.ParseInt(s, 10, 32); err == nil {
-               return &Episode{Type: EpisodeTypeRegular, Number: int(no)}
+               return &Episode{Type: EpisodeTypeRegular, Number: int(no), Part: int(p)}
        } else if len(s) < 1 {
                // s too short
        } else if no, err = strconv.ParseInt(s[1:], 10, 30); err == nil {
-               return &Episode{Type: parseEpisodeType(s[:1]), Number: int(no)}
+               return &Episode{Type: parseEpisodeType(s[:1]), Number: int(no), Part: int(p)}
        }
        return nil
 }