github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fs/bwtimetable.go (about) 1 package fs 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "time" 8 9 "github.com/pkg/errors" 10 ) 11 12 // BwTimeSlot represents a bandwidth configuration at a point in time. 13 type BwTimeSlot struct { 14 DayOfTheWeek int 15 HHMM int 16 Bandwidth SizeSuffix 17 } 18 19 // BwTimetable contains all configured time slots. 20 type BwTimetable []BwTimeSlot 21 22 // String returns a printable representation of BwTimetable. 23 func (x BwTimetable) String() string { 24 ret := []string{} 25 for _, ts := range x { 26 ret = append(ret, fmt.Sprintf("%s-%04.4d,%s", time.Weekday(ts.DayOfTheWeek), ts.HHMM, ts.Bandwidth.String())) 27 } 28 return strings.Join(ret, " ") 29 } 30 31 // Basic hour format checking 32 func validateHour(HHMM string) error { 33 if len(HHMM) != 5 { 34 return errors.Errorf("invalid time specification (hh:mm): %q", HHMM) 35 } 36 hh, err := strconv.Atoi(HHMM[0:2]) 37 if err != nil { 38 return errors.Errorf("invalid hour in time specification %q: %v", HHMM, err) 39 } 40 if hh < 0 || hh > 23 { 41 return errors.Errorf("invalid hour (must be between 00 and 23): %q", hh) 42 } 43 mm, err := strconv.Atoi(HHMM[3:]) 44 if err != nil { 45 return errors.Errorf("invalid minute in time specification: %q: %v", HHMM, err) 46 } 47 if mm < 0 || mm > 59 { 48 return errors.Errorf("invalid minute (must be between 00 and 59): %q", hh) 49 } 50 return nil 51 } 52 53 // Basic weekday format checking 54 func parseWeekday(dayOfWeek string) (int, error) { 55 dayOfWeek = strings.ToLower(dayOfWeek) 56 if dayOfWeek == "sun" || dayOfWeek == "sunday" { 57 return 0, nil 58 } 59 if dayOfWeek == "mon" || dayOfWeek == "monday" { 60 return 1, nil 61 } 62 if dayOfWeek == "tue" || dayOfWeek == "tuesday" { 63 return 2, nil 64 } 65 if dayOfWeek == "wed" || dayOfWeek == "wednesday" { 66 return 3, nil 67 } 68 if dayOfWeek == "thu" || dayOfWeek == "thursday" { 69 return 4, nil 70 } 71 if dayOfWeek == "fri" || dayOfWeek == "friday" { 72 return 5, nil 73 } 74 if dayOfWeek == "sat" || dayOfWeek == "saturday" { 75 return 6, nil 76 } 77 return 0, errors.Errorf("invalid weekday: %q", dayOfWeek) 78 } 79 80 // Set the bandwidth timetable. 81 func (x *BwTimetable) Set(s string) error { 82 // The timetable is formatted as: 83 // "dayOfWeek-hh:mm,bandwidth dayOfWeek-hh:mm,banwidth..." ex: "Mon-10:00,10G Mon-11:30,1G Tue-18:00,off" 84 // If only a single bandwidth identifier is provided, we assume constant bandwidth. 85 86 if len(s) == 0 { 87 return errors.New("empty string") 88 } 89 // Single value without time specification. 90 if !strings.Contains(s, " ") && !strings.Contains(s, ",") { 91 ts := BwTimeSlot{} 92 if err := ts.Bandwidth.Set(s); err != nil { 93 return err 94 } 95 ts.DayOfTheWeek = 0 96 ts.HHMM = 0 97 *x = BwTimetable{ts} 98 return nil 99 } 100 101 for _, tok := range strings.Split(s, " ") { 102 tv := strings.Split(tok, ",") 103 104 // Format must be dayOfWeek-HH:MM,BW 105 if len(tv) != 2 { 106 return errors.Errorf("invalid time/bandwidth specification: %q", tok) 107 } 108 109 weekday := 0 110 HHMM := "" 111 if !strings.Contains(tv[0], "-") { 112 HHMM = tv[0] 113 if err := validateHour(HHMM); err != nil { 114 return err 115 } 116 for i := 0; i < 7; i++ { 117 hh, _ := strconv.Atoi(HHMM[0:2]) 118 mm, _ := strconv.Atoi(HHMM[3:]) 119 ts := BwTimeSlot{ 120 DayOfTheWeek: i, 121 HHMM: (hh * 100) + mm, 122 } 123 if err := ts.Bandwidth.Set(tv[1]); err != nil { 124 return err 125 } 126 *x = append(*x, ts) 127 } 128 } else { 129 timespec := strings.Split(tv[0], "-") 130 if len(timespec) != 2 { 131 return errors.Errorf("invalid time specification: %q", tv[0]) 132 } 133 var err error 134 weekday, err = parseWeekday(timespec[0]) 135 if err != nil { 136 return err 137 } 138 HHMM = timespec[1] 139 if err := validateHour(HHMM); err != nil { 140 return err 141 } 142 143 hh, _ := strconv.Atoi(HHMM[0:2]) 144 mm, _ := strconv.Atoi(HHMM[3:]) 145 ts := BwTimeSlot{ 146 DayOfTheWeek: weekday, 147 HHMM: (hh * 100) + mm, 148 } 149 // Bandwidth limit for this time slot. 150 if err := ts.Bandwidth.Set(tv[1]); err != nil { 151 return err 152 } 153 *x = append(*x, ts) 154 } 155 } 156 return nil 157 } 158 159 // Difference in minutes between lateDayOfWeekHHMM and earlyDayOfWeekHHMM 160 func timeDiff(lateDayOfWeekHHMM int, earlyDayOfWeekHHMM int) int { 161 162 lateTimeMinutes := (lateDayOfWeekHHMM / 10000) * 24 * 60 163 lateTimeMinutes += ((lateDayOfWeekHHMM / 100) % 100) * 60 164 lateTimeMinutes += lateDayOfWeekHHMM % 100 165 166 earlyTimeMinutes := (earlyDayOfWeekHHMM / 10000) * 24 * 60 167 earlyTimeMinutes += ((earlyDayOfWeekHHMM / 100) % 100) * 60 168 earlyTimeMinutes += earlyDayOfWeekHHMM % 100 169 170 return lateTimeMinutes - earlyTimeMinutes 171 } 172 173 // LimitAt returns a BwTimeSlot for the time requested. 174 func (x BwTimetable) LimitAt(tt time.Time) BwTimeSlot { 175 // If the timetable is empty, we return an unlimited BwTimeSlot starting at Sunday midnight. 176 if len(x) == 0 { 177 return BwTimeSlot{DayOfTheWeek: 0, HHMM: 0, Bandwidth: -1} 178 } 179 180 dayOfWeekHHMM := int(tt.Weekday())*10000 + tt.Hour()*100 + tt.Minute() 181 182 // By default, we return the last element in the timetable. This 183 // satisfies two conditions: 1) If there's only one element it 184 // will always be selected, and 2) The last element of the table 185 // will "wrap around" until overridden by an earlier time slot. 186 // there's only one time slot in the timetable. 187 ret := x[len(x)-1] 188 mindif := 0 189 first := true 190 191 // Look for most recent time slot. 192 for _, ts := range x { 193 // Ignore the past 194 if dayOfWeekHHMM < (ts.DayOfTheWeek*10000)+ts.HHMM { 195 continue 196 } 197 dif := timeDiff(dayOfWeekHHMM, (ts.DayOfTheWeek*10000)+ts.HHMM) 198 if first { 199 mindif = dif 200 first = false 201 } 202 if dif <= mindif { 203 mindif = dif 204 ret = ts 205 } 206 } 207 208 return ret 209 } 210 211 // Type of the value 212 func (x BwTimetable) Type() string { 213 return "BwTimetable" 214 }