func pollMonth(env task.Env, month string, cc bool) (pt []*task.PolledTask, err error) { c := env.HTTPClient() cursor := "" n := 0 var ( metawg sync.WaitGroup // for meta or metaerr to be loaded meta monthMeta metaerr error ) metawg.Add(1) go func() { defer metawg.Done() meta, metaerr = getMonthMeta(env, month) }() // Opens issue to rietveld, even if they're logically closed // (R=closed) to us. var rietOpen []*Review for { query := monthQuery if cc { query = monthQueryCC } url := urlWithParams(query, map[string]string{ "DATE_AFTER": month + "-01 00:00:00", "DATE_BEFORE": monthAfter(month) + "-01 00:00:00", "CURSOR": cursor, "LIMIT": fmt.Sprint(itemsPerPage), }) n++ env.Logf("Fetching codereview page %d: %s", n, url) res, err := c.Get(url) if err != nil { env.Logf("Error fetching %s: %v", url, err) return nil, err } var reviews []*Review reviews, cursor, err = ParseReviews(res.Body) res.Body.Close() if err != nil { env.Logf("ParseReviews error: %v", err) return nil, err } env.Logf("got %d reviews, cursor is %q", len(reviews), cursor) for _, r := range reviews { if r.Issue == 0 { env.Logf("bogus issue %+v", r) continue } rietOpen = append(rietOpen, r) } if cursor == "" || len(reviews) < itemsPerPage { break } } metawg.Wait() if metaerr != nil { env.Logf("Error loading month meta for %q: %v", month, metaerr) return nil, metaerr } // For each issue that Rietveld believes to be open, see if we // have the latest comments for it and then see if it's // actually logically still open, based on its comments. var updates []chan issueMeta for _, r := range rietOpen { im := meta[r.Issue] if im.policyVersion != policyVersion || im.lastModified != r.Modified { env.Logf("Need to summarize issue %d", r.Issue) ch := make(chan issueMeta, 1) updates = append(updates, ch) go func(issueId int) { ch <- summarizeIssue(env, issueId) }(r.Issue) } else { env.Logf("Issue %d is unmodified.", r.Issue) } } var updateErr error var errCount int for _, ch := range updates { im := <-ch if im.err != nil { errCount++ if updateErr == nil { updateErr = im.err } } else { meta[im.issue] = im } } if updateErr != nil { // Save what we've got so far. env.Logf("Errors re-fetching month %q: %d/%d fetches failed. Saving %d good issue summaries.", month, errCount, len(updates), len(meta)) env.SetMeta(monthMetaPrefix+month, meta.serialize()) return nil, updateErr } if len(updates) > 0 { if err := env.SetMeta(monthMetaPrefix+month, meta.serialize()); err != nil { env.Logf("Error writing month meta for %q: %v", month, err) return nil, err } } for _, r := range rietOpen { im := meta[r.Issue] if im.reviewer == "close" { continue } ownerHint := im.reviewer if ownerHint == "" { ownerHint = r.Reviewer() } t := &task.PolledTask{ ID: fmt.Sprint(r.Issue), Title: r.Desc, OwnerHint: ownerHint, } pt = append(pt, t) } env.Logf("codereview: for month %q, returning %d open tasks", month, len(pt)) return pt, nil }