summaryrefslogtreecommitdiff
path: root/internal/state/state.go
blob: 4cae156c36fededd7ac9d2bdad4a2ca9cdfebf6f (plain)
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) < 17*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)
}