1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
package state
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
)
// State represents the persistent state of gitsyncer
type State struct {
LastBatchRun time.Time `json:"lastBatchRun"`
// Per-repo sync tracking for default daily sync limits and optional throttling
LastRepoSync map[string]time.Time `json:"lastRepoSync,omitempty"`
NextRepoSyncAllowed map[string]time.Time `json:"nextRepoSyncAllowed,omitempty"`
}
// Manager handles state persistence
type Manager struct {
filePath string
}
// NewManager creates a new state manager
func NewManager(workDir string) *Manager {
return &Manager{
filePath: filepath.Join(workDir, ".gitsyncer-state.json"),
}
}
// Load reads the state from disk
func (m *Manager) Load() (*State, error) {
data, err := os.ReadFile(m.filePath)
if err != nil {
if os.IsNotExist(err) {
// Return empty state if file doesn't exist
return &State{}, nil
}
return nil, fmt.Errorf("failed to read state file: %w", err)
}
var state State
if err := json.Unmarshal(data, &state); err != nil {
return nil, fmt.Errorf("failed to parse state file: %w", err)
}
return &state, nil
}
// Save writes the state to disk
func (m *Manager) Save(state *State) error {
data, err := json.MarshalIndent(state, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal state: %w", err)
}
// Ensure directory exists
dir := filepath.Dir(m.filePath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create state directory: %w", err)
}
// Write state file
if err := os.WriteFile(m.filePath, data, 0644); err != nil {
return fmt.Errorf("failed to write state file: %w", err)
}
return nil
}
// HasRunWithinWeek checks if the last batch run was within the past week
func (s *State) HasRunWithinWeek() bool {
if s.LastBatchRun.IsZero() {
return false
}
return time.Since(s.LastBatchRun) < 7*24*time.Hour
}
// UpdateBatchRunTime updates the last batch run timestamp to now
func (s *State) UpdateBatchRunTime() {
s.LastBatchRun = time.Now()
}
// EnsureRepoMaps initializes per-repo maps if needed
func (s *State) EnsureRepoMaps() {
if s.LastRepoSync == nil {
s.LastRepoSync = make(map[string]time.Time)
}
if s.NextRepoSyncAllowed == nil {
s.NextRepoSyncAllowed = make(map[string]time.Time)
}
}
// GetLastRepoSync returns the last sync time for a repo
func (s *State) GetLastRepoSync(repoName string) time.Time {
if s == nil || s.LastRepoSync == nil {
return time.Time{}
}
return s.LastRepoSync[repoName]
}
// GetNextRepoSyncAllowed returns the next allowed sync time for a repo
func (s *State) GetNextRepoSyncAllowed(repoName string) time.Time {
if s == nil || s.NextRepoSyncAllowed == nil {
return time.Time{}
}
return s.NextRepoSyncAllowed[repoName]
}
// SetRepoSync updates the last sync time and next allowed sync time for a repo
func (s *State) SetRepoSync(repoName string, lastSync time.Time, nextAllowed time.Time) {
if s == nil {
return
}
s.EnsureRepoMaps()
s.LastRepoSync[repoName] = lastSync
s.NextRepoSyncAllowed[repoName] = nextAllowed
}
// SetLastRepoSync updates only the last sync time for a repo.
func (s *State) SetLastRepoSync(repoName string, lastSync time.Time) {
if s == nil {
return
}
s.EnsureRepoMaps()
s.LastRepoSync[repoName] = lastSync
}
// SetNextRepoSyncAllowed updates only the next allowed sync time for a repo
func (s *State) SetNextRepoSyncAllowed(repoName string, nextAllowed time.Time) {
if s == nil {
return
}
s.EnsureRepoMaps()
s.NextRepoSyncAllowed[repoName] = nextAllowed
}
// ClearNextRepoSyncAllowed removes the next allowed sync time for a repo.
func (s *State) ClearNextRepoSyncAllowed(repoName string) {
if s == nil || s.NextRepoSyncAllowed == nil {
return
}
delete(s.NextRepoSyncAllowed, repoName)
}
|