1package main
  2
  3import (
  4	"encoding/gob"
  5	"log"
  6	"os"
  7	"sync"
  8	"time"
  9)
 10
 11type CommitStat struct {
 12	Additions int
 13	Deletions int
 14}
 15
 16type RepoMetadata struct {
 17	Branches     []string
 18	Tags         []string
 19	ReadmeName   string
 20	LicenseName  string
 21	TotalCommits int
 22	Version      int
 23}
 24
 25const CurrentMetadataVersion = 1
 26
 27type LangCacheKey struct {
 28	RepoName   string
 29	CommitHash string
 30}
 31
 32var (
 33	commitStatsCache  sync.Map // string -> CommitStat
 34	repoMetadataCache sync.Map // string -> RepoMetadata
 35	langCache         sync.Map // LangCacheKey -> []LanguageStat
 36	saveChan          = make(chan struct{}, 1)
 37)
 38
 39const cacheFile = "bbgit.cache"
 40
 41func NotifySave() {
 42	select {
 43	case saveChan <- struct{}{}:
 44	default:
 45	}
 46}
 47
 48func StartSaver() {
 49	go func() {
 50		for range saveChan {
 51			// Throttle saves to once per second
 52			time.Sleep(1 * time.Second)
 53			// Drain any additional signals during sleep
 54			for len(saveChan) > 0 {
 55				<-saveChan
 56			}
 57			if err := SaveCache(); err != nil {
 58				log.Printf("Error saving cache: %v", err)
 59			}
 60		}
 61	}()
 62}
 63
 64type cacheContainer struct {
 65	CommitStats  map[string]CommitStat
 66	RepoMetadata map[string]RepoMetadata
 67	LangStats    map[LangCacheKey][]LanguageStat
 68}
 69
 70func SaveCache() error {
 71	container := cacheContainer{
 72		CommitStats:  make(map[string]CommitStat),
 73		RepoMetadata: make(map[string]RepoMetadata),
 74		LangStats:    make(map[LangCacheKey][]LanguageStat),
 75	}
 76
 77	commitStatsCache.Range(func(k, v interface{}) bool {
 78		container.CommitStats[k.(string)] = v.(CommitStat)
 79		return true
 80	})
 81
 82	repoMetadataCache.Range(func(k, v interface{}) bool {
 83		container.RepoMetadata[k.(string)] = v.(RepoMetadata)
 84		return true
 85	})
 86
 87	langCache.Range(func(k, v interface{}) bool {
 88		container.LangStats[k.(LangCacheKey)] = v.([]LanguageStat)
 89		return true
 90	})
 91
 92	f, err := os.Create(cacheFile)
 93	if err != nil {
 94		return err
 95	}
 96	defer f.Close()
 97
 98	if err := gob.NewEncoder(f).Encode(container); err != nil {
 99		return err
100	}
101
102	log.Printf("OPS Cache saved to %s", cacheFile)
103	return nil
104}
105
106func LoadCache() {
107	f, err := os.Open(cacheFile)
108	if err != nil {
109		if !os.IsNotExist(err) {
110			log.Printf("Error opening cache file: %v", err)
111		}
112		return
113	}
114	defer f.Close()
115
116	var container cacheContainer
117	if err := gob.NewDecoder(f).Decode(&container); err != nil {
118		log.Printf("Error decoding cache file: %v", err)
119		return
120	}
121
122	for k, v := range container.CommitStats {
123		commitStatsCache.Store(k, v)
124	}
125	for k, v := range container.RepoMetadata {
126		repoMetadataCache.Store(k, v)
127	}
128	for k, v := range container.LangStats {
129		langCache.Store(k, v)
130	}
131
132	log.Printf("Loaded cache from %s", cacheFile)
133}