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  }