package main import ( "context" "fmt" "log" "sort" "teafolio/gitea" "time" ) // rearrangeOrder applies rearrangements from the OverrideOrder configuration. func (this *Application) rearrangeOrder(repos []gitea.Repo) ([]gitea.Repo, error) { if len(this.cfg.OverrideOrder) == 0 { return repos, nil // nothing to do } // Collect pre-existing positions for repos reposByName := make(map[string]gitea.Repo, len(repos)) for _, repo := range repos { reposByName[repo.Name] = repo } // Sort target insertion positions by lowest-first type insertionPosition struct { pos int repoName string } insertAt := make([]insertionPosition, 0, len(this.cfg.OverrideOrder)) for rn, rpos := range this.cfg.OverrideOrder { insertAt = append(insertAt, insertionPosition{len(repos) - rpos - 1, rn}) } sort.Slice(insertAt, func(i, j int) bool { return insertAt[i].pos < insertAt[j].pos }) // Walking-insertion loop ret := make([]gitea.Repo, 0, len(repos)) nextRepo := 0 nextOverride := 0 for { if nextOverride < len(insertAt) && insertAt[nextOverride].pos == len(ret) { // We have an override to insert at this position ret = append(ret, reposByName[insertAt[nextOverride].repoName]) nextOverride++ } else if nextRepo < len(repos) { // There are still other repos to insert generally if _, ok := this.cfg.OverrideOrder[repos[nextRepo].Name]; ok { // This repo has an overridden position. Don't insert it generally here nextRepo++ } else { // This repo does not have an overriden position. Here is fine ret = append(ret, repos[nextRepo]) nextRepo++ } } else { // There are no more overrides to insert and there are no remaining // non-override repos // That means we're done break } } if len(ret) != len(repos) { return nil, fmt.Errorf("Failed to apply OverrideOrder (got %d of %d repositories)", len(ret), len(repos)) } return ret, nil } func (this *Application) sync(ctx context.Context) (bool, error) { // List repositories on Gitea repos, err := this.gitea.Repos(ctx) if err != nil { return false, err } // Compare this list of repositories to our existing one // If the repository is new, or if it's update-time has changed since we last // saw it, then re-refresh its real git commit timestamps // Otherwise copy them from the previous version this.reposMut.RLock() // readonly anyChanges := false if len(repos) != len(this.reposCache) { anyChanges = true } for i, rr := range repos { if idx, ok := this.reposCacheByName[rr.Name]; ok && this.reposCache[idx].GiteaUpdated == rr.GiteaUpdated { // Already exists in cache with same Gitea update time // Copy timestamps repos[i] = this.reposCache[idx] } else { // New repo, or Gitea has updated timestamp anyChanges = true // Refresh timestamps err := this.gitea.PopulateCommitInfo(ctx, &rr) if err != nil { log.Printf("loading branches for '%s': %s", rr.Name, err) rr.NewestCommit = rr.GiteaUpdated // best guess } // Refresh topics err = this.gitea.PopulateTopics(ctx, &rr) if err != nil { log.Printf("loading topics for '%s': %s", rr.Name, err) } err = this.gitea.PopulateImages(ctx, &rr) if err != nil { log.Printf("loading images for '%s': %s", rr.Name, err) } // Save repos[i] = rr } } this.reposMut.RUnlock() // if !anyChanges { return false, nil // nothing to do } // We have a final updated repos array // Sort repos once alphabetically, to get alphabetical indexes... sort.Slice(repos, func(i, j int) bool { return repos[i].Name < repos[j].Name }) alphabeticalOrderIndexes := make(map[string]int, len(repos)) for idx, repo := range repos { alphabeticalOrderIndexes[repo.Name] = idx } // But then make sure the final sort is by most-recently-created sort.Slice(repos, func(i, j int) bool { return repos[i].GiteaCreated.After(repos[j].GiteaCreated) }) reordered, err := this.rearrangeOrder(repos) if err != nil { log.Printf("Reorder failure: %s", err.Error()) } else { repos = reordered } reposCacheByName := make(map[string]int, len(repos)) for idx, repo := range repos { reposCacheByName[repo.Name] = idx } // Commit our changes for the other threads to look at this.reposMut.Lock() this.reposCache = repos this.reposCacheByName = reposCacheByName this.reposAlphabeticalOrder = alphabeticalOrderIndexes this.reposMut.Unlock() // Done return true, nil } func (this *Application) syncWorker(ctx context.Context) { t := time.NewTicker(30 * time.Minute) defer t.Stop() for { anyChanges, err := this.sync(ctx) if err != nil { // log and continue log.Printf("Refreshing repositories: %s", err.Error()) } if anyChanges { log.Printf("Repositories updated") } select { case <-t.C: continue case <-ctx.Done(): return } } }