{"action":"created","comment":{"url":"https://api.github.com/repos/ncw/rclone/pulls/comments/94396294","pull_request_review_id":14920938,"id":94396294,"diff_hunk":"@@ -191,6 +200,128 @@ func (x *SizeSuffix) Type() string {\n // Check it satisfies the interface\n var _ pflag.Value = (*SizeSuffix)(nil)\n \n+// String returns a printable representation of BwTimetable.\n+func (x *BwTimetable) String() string {\n+\tret := []string{}\n+\tfor _, ts := range *x {\n+\t\tret = append(ret, fmt.Sprintf(\"%04.4d,%s\", ts.hhmm, ts.bandwidth.String()))\n+\t}\n+\treturn strings.Join(ret, \" \")\n+}\n+\n+// Set the bandwidth timetable.\n+func (x *BwTimetable) Set(s string) error {\n+\t// The timetable is formatted as:\n+\t// \"hh:mm,bandwidth hh:mm,banwidth...\" ex: \"10:00,10G 11:30,1G 18:00,off\"\n+\t// If only a single bandwidth identifier is provided, we assume constant bandwidth.\n+\n+\tif len(s) == 0 {\n+\t\treturn errors.New(\"empty string\")\n+\t}\n+\t// Single value without time specification.\n+\tif !strings.Contains(s, \" \") && !strings.Contains(s, \",\") {\n+\t\tts := BwTimeSlot{}\n+\t\tif err := ts.bandwidth.Set(s); err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\tts.hhmm = 0\n+\t\t*x = BwTimetable{ts}\n+\t\treturn nil\n+\t}\n+\n+\tfor _, tok := range strings.Split(s, \" \") {\n+\t\ttv := strings.Split(tok, \",\")\n+\n+\t\t// Format must be HH:MM,BW\n+\t\tif len(tv) != 2 {\n+\t\t\treturn errors.Errorf(\"invalid time/bandwidth specification: %q\", tok)\n+\t\t}\n+\n+\t\t// Basic timespec sanity checking\n+\t\thhmm := tv[0]\n+\t\tif len(hhmm) != 5 {\n+\t\t\treturn errors.Errorf(\"invalid time specification (hh:mm): %q\", hhmm)\n+\t\t}\n+\t\thh, err := strconv.Atoi(hhmm[0:2])\n+\t\tif err != nil {\n+\t\t\treturn errors.Errorf(\"invalid hour in time specification %q: %v\", hhmm, err)\n+\t\t}\n+\t\tif hh < 0 || hh > 23 {\n+\t\t\treturn errors.Errorf(\"invalid hour (must be between 00 and 23): %q\", hh)\n+\t\t}\n+\t\tmm, err := strconv.Atoi(hhmm[3:])\n+\t\tif err != nil {\n+\t\t\treturn errors.Errorf(\"invalid minute in time specification: %q: %v\", hhmm, err)\n+\t\t}\n+\t\tif mm < 0 || mm > 59 {\n+\t\t\treturn errors.Errorf(\"invalid minute (must be between 00 and 59): %q\", hh)\n+\t\t}\n+\n+\t\tts := BwTimeSlot{\n+\t\t\thhmm: int64((hh * 100) + mm),\n+\t\t}\n+\t\t// Bandwidth limit for this time slot.\n+\t\tif err := ts.bandwidth.Set(tv[1]); err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\t*x = append(*x, ts)\n+\t}\n+\treturn nil\n+}\n+\n+// LimitAt returns a BwTimeSlot for the time requested.\n+func (x *BwTimetable) LimitAt(tt time.Time) (BwTimeSlot, error) {\n+\t// If the timetable is empty, we return an unlimited BwTimeSlot starting at midnight.\n+\tif len(*x) == 0 {\n+\t\treturn BwTimeSlot{hhmm: 0, bandwidth: -1}, nil\n+\t}\n+\n+\tt := tt.Format(\"1504\")\n+\n+\thhmm, err := strconv.ParseInt(t, 10, 0)\n+\tif err != nil {\n+\t\treturn BwTimeSlot{}, err\n+\t}\n+\n+\t// By default, we return the last element in the timetable. This\n+\t// satisfies two conditions: 1) If there's only one element it\n+\t// will always be selected, and 2) The last element of the table\n+\t// will \"wrap around\" until overriden by an earlier time slot.\n+\t// there's only one time slot in the timetable.\n+\tret := (*x)[len(*x)-1]\n+\n+\tvar mindif int64\n+\n+\tfirst := true\n+\n+\t// Look for most recent time slot.\n+\tfor _, ts := range *x {\n+\t\t// Ignore the past\n+\t\tif hhmm < ts.hhmm {\n+\t\t\tcontinue\n+\t\t}\n+\t\tdif := ((hhmm / 100 * 60) + (hhmm % 100)) - ((ts.hhmm / 100 * 60) + (ts.hhmm % 100))","path":"fs/config.go","position":138,"original_position":138,"commit_id":"bd17f833285f7b0c73c84f0a248269d9b9896a2e","original_commit_id":"bd17f833285f7b0c73c84f0a248269d9b9896a2e","user":{"login":"ncw","id":536803,"avatar_url":"https://avatars.githubusercontent.com/u/536803?v=3","gravatar_id":"","url":"https://api.github.com/users/ncw","html_url":"https://github.com/ncw","followers_url":"https://api.github.com/users/ncw/followers","following_url":"https://api.github.com/users/ncw/following{/other_user}","gists_url":"https://api.github.com/users/ncw/gists{/gist_id}","starred_url":"https://api.github.com/users/ncw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ncw/subscriptions","organizations_url":"https://api.github.com/users/ncw/orgs","repos_url":"https://api.github.com/users/ncw/repos","events_url":"https://api.github.com/users/ncw/events{/privacy}","received_events_url":"https://api.github.com/users/ncw/received_events","type":"User","site_admin":false},"body":"This is a little untidy...  Makes me think that you should have stored the hhmm and ts.hhmm in minutes from midnight?  Not sure it is worth changing though.","created_at":"2017-01-03T12:18:50Z","updated_at":"2017-01-03T12:47:01Z","html_url":"https://github.com/ncw/rclone/pull/982#discussion_r94396294","pull_request_url":"https://api.github.com/repos/ncw/rclone/pulls/982","_links":{"self":{"href":"https://api.github.com/repos/ncw/rclone/pulls/comments/94396294"},"html":{"href":"https://github.com/ncw/rclone/pull/982#discussion_r94396294"},"pull_request":{"href":"https://api.github.com/repos/ncw/rclone/pulls/982"}}},"pull_request":{"url":"https://api.github.com/repos/ncw/rclone/pulls/982","id":99905692,"html_url":"https://github.com/ncw/rclone/pull/982","diff_url":"https://github.com/ncw/rclone/pull/982.diff","patch_url":"https://github.com/ncw/rclone/pull/982.patch","issue_url":"https://api.github.com/repos/ncw/rclone/issues/982","number":982,"state":"open","locked":false,"title":"Add time-based bandwidth limits.","user":{"login":"marcopaganini","id":7425028,"avatar_url":"https://avatars.githubusercontent.com/u/7425028?v=3","gravatar_id":"","url":"https://api.github.com/users/marcopaganini","html_url":"https://github.com/marcopaganini","followers_url":"https://api.github.com/users/marcopaganini/followers","following_url":"https://api.github.com/users/marcopaganini/following{/other_user}","gists_url":"https://api.github.com/users/marcopaganini/gists{/gist_id}","starred_url":"https://api.github.com/users/marcopaganini/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marcopaganini/subscriptions","organizations_url":"https://api.github.com/users/marcopaganini/orgs","repos_url":"https://api.github.com/users/marcopaganini/repos","events_url":"https://api.github.com/users/marcopaganini/events{/privacy}","received_events_url":"https://api.github.com/users/marcopaganini/received_events","type":"User","site_admin":false},"body":"- Change the --bwlimit command line parameter to accept both a limit (as\r\n  before) or a full timetable (formatted as \"hh:mm,limit\r\n  hh:mm,limit...\")\r\n- The timetable is checked once a minute by a ticker function. A new\r\n  tokenBucket is created every time a bandwidth change is necessary.\r\n- This change is compatible with the SIGUSR2 change to toggle bandwidth\r\n  limits.","created_at":"2017-01-03T02:58:59Z","updated_at":"2017-01-03T12:47:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6b8316e9ff0f3a38c334b2ac0b1dd36d873f34c4","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/ncw/rclone/pulls/982/commits","review_comments_url":"https://api.github.com/repos/ncw/rclone/pulls/982/comments","review_comment_url":"https://api.github.com/repos/ncw/rclone/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ncw/rclone/issues/982/comments","statuses_url":"https://api.github.com/repos/ncw/rclone/statuses/bd17f833285f7b0c73c84f0a248269d9b9896a2e","head":{"label":"marcopaganini:bwlimit","ref":"bwlimit","sha":"bd17f833285f7b0c73c84f0a248269d9b9896a2e","user":{"login":"marcopaganini","id":7425028,"avatar_url":"https://avatars.githubusercontent.com/u/7425028?v=3","gravatar_id":"","url":"https://api.github.com/users/marcopaganini","html_url":"https://github.com/marcopaganini","followers_url":"https://api.github.com/users/marcopaganini/followers","following_url":"https://api.github.com/users/marcopaganini/following{/other_user}","gists_url":"https://api.github.com/users/marcopaganini/gists{/gist_id}","starred_url":"https://api.github.com/users/marcopaganini/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marcopaganini/subscriptions","organizations_url":"https://api.github.com/users/marcopaganini/orgs","repos_url":"https://api.github.com/users/marcopaganini/repos","events_url":"https://api.github.com/users/marcopaganini/events{/privacy}","received_events_url":"https://api.github.com/users/marcopaganini/received_events","type":"User","site_admin":false},"repo":{"id":46640528,"name":"rclone","full_name":"marcopaganini/rclone","owner":{"login":"marcopaganini","id":7425028,"avatar_url":"https://avatars.githubusercontent.com/u/7425028?v=3","gravatar_id":"","url":"https://api.github.com/users/marcopaganini","html_url":"https://github.com/marcopaganini","followers_url":"https://api.github.com/users/marcopaganini/followers","following_url":"https://api.github.com/users/marcopaganini/following{/other_user}","gists_url":"https://api.github.com/users/marcopaganini/gists{/gist_id}","starred_url":"https://api.github.com/users/marcopaganini/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marcopaganini/subscriptions","organizations_url":"https://api.github.com/users/marcopaganini/orgs","repos_url":"https://api.github.com/users/marcopaganini/repos","events_url":"https://api.github.com/users/marcopaganini/events{/privacy}","received_events_url":"https://api.github.com/users/marcopaganini/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/marcopaganini/rclone","description":"Sync files to and from Google Drive, S3, Swift, Cloudfiles, Dropbox and Google Cloud Storage","fork":true,"url":"https://api.github.com/repos/marcopaganini/rclone","forks_url":"https://api.github.com/repos/marcopaganini/rclone/forks","keys_url":"https://api.github.com/repos/marcopaganini/rclone/keys{/key_id}","collaborators_url":"https://api.github.com/repos/marcopaganini/rclone/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/marcopaganini/rclone/teams","hooks_url":"https://api.github.com/repos/marcopaganini/rclone/hooks","issue_events_url":"https://api.github.com/repos/marcopaganini/rclone/issues/events{/number}","events_url":"https://api.github.com/repos/marcopaganini/rclone/events","assignees_url":"https://api.github.com/repos/marcopaganini/rclone/assignees{/user}","branches_url":"https://api.github.com/repos/marcopaganini/rclone/branches{/branch}","tags_url":"https://api.github.com/repos/marcopaganini/rclone/tags","blobs_url":"https://api.github.com/repos/marcopaganini/rclone/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/marcopaganini/rclone/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/marcopaganini/rclone/git/refs{/sha}","trees_url":"https://api.github.com/repos/marcopaganini/rclone/git/trees{/sha}","statuses_url":"https://api.github.com/repos/marcopaganini/rclone/statuses/{sha}","languages_url":"https://api.github.com/repos/marcopaganini/rclone/languages","stargazers_url":"https://api.github.com/repos/marcopaganini/rclone/stargazers","contributors_url":"https://api.github.com/repos/marcopaganini/rclone/contributors","subscribers_url":"https://api.github.com/repos/marcopaganini/rclone/subscribers","subscription_url":"https://api.github.com/repos/marcopaganini/rclone/subscription","commits_url":"https://api.github.com/repos/marcopaganini/rclone/commits{/sha}","git_commits_url":"https://api.github.com/repos/marcopaganini/rclone/git/commits{/sha}","comments_url":"https://api.github.com/repos/marcopaganini/rclone/comments{/number}","issue_comment_url":"https://api.github.com/repos/marcopaganini/rclone/issues/comments{/number}","contents_url":"https://api.github.com/repos/marcopaganini/rclone/contents/{+path}","compare_url":"https://api.github.com/repos/marcopaganini/rclone/compare/{base}...{head}","merges_url":"https://api.github.com/repos/marcopaganini/rclone/merges","archive_url":"https://api.github.com/repos/marcopaganini/rclone/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/marcopaganini/rclone/downloads","issues_url":"https://api.github.com/repos/marcopaganini/rclone/issues{/number}","pulls_url":"https://api.github.com/repos/marcopaganini/rclone/pulls{/number}","milestones_url":"https://api.github.com/repos/marcopaganini/rclone/milestones{/number}","notifications_url":"https://api.github.com/repos/marcopaganini/rclone/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/marcopaganini/rclone/labels{/name}","releases_url":"https://api.github.com/repos/marcopaganini/rclone/releases{/id}","deployments_url":"https://api.github.com/repos/marcopaganini/rclone/deployments","created_at":"2015-11-22T00:48:29Z","updated_at":"2016-10-22T07:56:23Z","pushed_at":"2017-01-03T03:55:11Z","git_url":"git://github.com/marcopaganini/rclone.git","ssh_url":"git@github.com:marcopaganini/rclone.git","clone_url":"https://github.com/marcopaganini/rclone.git","svn_url":"https://github.com/marcopaganini/rclone","homepage":"http://rclone.org","size":16437,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"ncw:master","ref":"master","sha":"1cad759306ff117cfb9bd9339db543b91e0738e9","user":{"login":"ncw","id":536803,"avatar_url":"https://avatars.githubusercontent.com/u/536803?v=3","gravatar_id":"","url":"https://api.github.com/users/ncw","html_url":"https://github.com/ncw","followers_url":"https://api.github.com/users/ncw/followers","following_url":"https://api.github.com/users/ncw/following{/other_user}","gists_url":"https://api.github.com/users/ncw/gists{/gist_id}","starred_url":"https://api.github.com/users/ncw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ncw/subscriptions","organizations_url":"https://api.github.com/users/ncw/orgs","repos_url":"https://api.github.com/users/ncw/repos","events_url":"https://api.github.com/users/ncw/events{/privacy}","received_events_url":"https://api.github.com/users/ncw/received_events","type":"User","site_admin":false},"repo":{"id":17803236,"name":"rclone","full_name":"ncw/rclone","owner":{"login":"ncw","id":536803,"avatar_url":"https://avatars.githubusercontent.com/u/536803?v=3","gravatar_id":"","url":"https://api.github.com/users/ncw","html_url":"https://github.com/ncw","followers_url":"https://api.github.com/users/ncw/followers","following_url":"https://api.github.com/users/ncw/following{/other_user}","gists_url":"https://api.github.com/users/ncw/gists{/gist_id}","starred_url":"https://api.github.com/users/ncw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ncw/subscriptions","organizations_url":"https://api.github.com/users/ncw/orgs","repos_url":"https://api.github.com/users/ncw/repos","events_url":"https://api.github.com/users/ncw/events{/privacy}","received_events_url":"https://api.github.com/users/ncw/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/ncw/rclone","description":"\"rsync for cloud storage\" - Google Drive, Amazon Drive, S3, Dropbox, Backblaze B2, One Drive, Swift, Hubic, Cloudfiles, Google Cloud Storage, Yandex Files","fork":false,"url":"https://api.github.com/repos/ncw/rclone","forks_url":"https://api.github.com/repos/ncw/rclone/forks","keys_url":"https://api.github.com/repos/ncw/rclone/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ncw/rclone/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ncw/rclone/teams","hooks_url":"https://api.github.com/repos/ncw/rclone/hooks","issue_events_url":"https://api.github.com/repos/ncw/rclone/issues/events{/number}","events_url":"https://api.github.com/repos/ncw/rclone/events","assignees_url":"https://api.github.com/repos/ncw/rclone/assignees{/user}","branches_url":"https://api.github.com/repos/ncw/rclone/branches{/branch}","tags_url":"https://api.github.com/repos/ncw/rclone/tags","blobs_url":"https://api.github.com/repos/ncw/rclone/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ncw/rclone/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ncw/rclone/git/refs{/sha}","trees_url":"https://api.github.com/repos/ncw/rclone/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ncw/rclone/statuses/{sha}","languages_url":"https://api.github.com/repos/ncw/rclone/languages","stargazers_url":"https://api.github.com/repos/ncw/rclone/stargazers","contributors_url":"https://api.github.com/repos/ncw/rclone/contributors","subscribers_url":"https://api.github.com/repos/ncw/rclone/subscribers","subscription_url":"https://api.github.com/repos/ncw/rclone/subscription","commits_url":"https://api.github.com/repos/ncw/rclone/commits{/sha}","git_commits_url":"https://api.github.com/repos/ncw/rclone/git/commits{/sha}","comments_url":"https://api.github.com/repos/ncw/rclone/comments{/number}","issue_comment_url":"https://api.github.com/repos/ncw/rclone/issues/comments{/number}","contents_url":"https://api.github.com/repos/ncw/rclone/contents/{+path}","compare_url":"https://api.github.com/repos/ncw/rclone/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ncw/rclone/merges","archive_url":"https://api.github.com/repos/ncw/rclone/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ncw/rclone/downloads","issues_url":"https://api.github.com/repos/ncw/rclone/issues{/number}","pulls_url":"https://api.github.com/repos/ncw/rclone/pulls{/number}","milestones_url":"https://api.github.com/repos/ncw/rclone/milestones{/number}","notifications_url":"https://api.github.com/repos/ncw/rclone/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ncw/rclone/labels{/name}","releases_url":"https://api.github.com/repos/ncw/rclone/releases{/id}","deployments_url":"https://api.github.com/repos/ncw/rclone/deployments","created_at":"2014-03-16T16:19:57Z","updated_at":"2017-01-03T11:42:42Z","pushed_at":"2017-01-03T10:36:10Z","git_url":"git://github.com/ncw/rclone.git","ssh_url":"git@github.com:ncw/rclone.git","clone_url":"https://github.com/ncw/rclone.git","svn_url":"https://github.com/ncw/rclone","homepage":"http://rclone.org","size":29169,"stargazers_count":4832,"watchers_count":4832,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":250,"mirror_url":null,"open_issues_count":280,"forks":250,"open_issues":280,"watchers":4832,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ncw/rclone/pulls/982"},"html":{"href":"https://github.com/ncw/rclone/pull/982"},"issue":{"href":"https://api.github.com/repos/ncw/rclone/issues/982"},"comments":{"href":"https://api.github.com/repos/ncw/rclone/issues/982/comments"},"review_comments":{"href":"https://api.github.com/repos/ncw/rclone/pulls/982/comments"},"review_comment":{"href":"https://api.github.com/repos/ncw/rclone/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ncw/rclone/pulls/982/commits"},"statuses":{"href":"https://api.github.com/repos/ncw/rclone/statuses/bd17f833285f7b0c73c84f0a248269d9b9896a2e"}}}}
{"actor":{"display_login":"ncw"}}