Skip to content

Commit b94da4e

Browse files
authored
Merge pull request #2 from ryo246912/refactor/api-access
feat: APIアクセス頻度制御機能の実装
2 parents 9770e2f + c84afac commit b94da4e

File tree

1 file changed

+146
-4
lines changed

1 file changed

+146
-4
lines changed

internal/tui/app.go

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package tui
33
import (
44
"fmt"
55
"strings"
6+
"sync"
7+
"time"
68

79
"github.com/charmbracelet/bubbles/help"
810
"github.com/charmbracelet/bubbles/key"
@@ -25,6 +27,68 @@ const (
2527
WorkflowRunLogsView
2628
)
2729

30+
// JobsCacheEntry represents a cached job entry with timestamp
31+
type JobsCacheEntry struct {
32+
Jobs []models.Job
33+
Timestamp time.Time
34+
}
35+
36+
// JobsCache represents the jobs cache with TTL
37+
type JobsCache struct {
38+
mu sync.RWMutex
39+
entries map[int64]JobsCacheEntry
40+
ttl time.Duration
41+
}
42+
43+
// NewJobsCache creates a new jobs cache
44+
func NewJobsCache(ttl time.Duration) *JobsCache {
45+
return &JobsCache{
46+
entries: make(map[int64]JobsCacheEntry),
47+
ttl: ttl,
48+
}
49+
}
50+
51+
// Get retrieves jobs from cache if not expired
52+
func (c *JobsCache) Get(runID int64) ([]models.Job, bool) {
53+
c.mu.RLock()
54+
defer c.mu.RUnlock()
55+
56+
entry, exists := c.entries[runID]
57+
if !exists {
58+
return nil, false
59+
}
60+
61+
if time.Since(entry.Timestamp) > c.ttl {
62+
return nil, false
63+
}
64+
65+
return entry.Jobs, true
66+
}
67+
68+
// Set stores jobs in cache with current timestamp
69+
func (c *JobsCache) Set(runID int64, jobs []models.Job) {
70+
c.mu.Lock()
71+
defer c.mu.Unlock()
72+
73+
c.entries[runID] = JobsCacheEntry{
74+
Jobs: jobs,
75+
Timestamp: time.Now(),
76+
}
77+
}
78+
79+
// Cleanup removes expired entries
80+
func (c *JobsCache) Cleanup() {
81+
c.mu.Lock()
82+
defer c.mu.Unlock()
83+
84+
now := time.Now()
85+
for runID, entry := range c.entries {
86+
if now.Sub(entry.Timestamp) > c.ttl {
87+
delete(c.entries, runID)
88+
}
89+
}
90+
}
91+
2892
// App represents the main application state
2993
type App struct {
3094
client *github.Client
@@ -75,6 +139,12 @@ type App struct {
75139
allRunsPage int
76140
allRunsPerPage int
77141
allRunsTotal int
142+
143+
// Cache and debounce
144+
jobsCache *JobsCache
145+
debounceTimer *time.Timer
146+
pendingRunID int64
147+
debounceMutex sync.Mutex
78148
}
79149

80150
// NewApp creates a new TUI application
@@ -127,11 +197,21 @@ func NewApp(client *github.Client, owner, repo string) *App {
127197
workflowsPerPage: 100,
128198
allRunsPage: 1,
129199
allRunsPerPage: 100,
200+
jobsCache: NewJobsCache(10 * time.Minute),
130201
}
131202
}
132203

133204
// Init initializes the application
134205
func (a *App) Init() tea.Cmd {
206+
// Start periodic cache cleanup
207+
go func() {
208+
ticker := time.NewTicker(5 * time.Minute)
209+
defer ticker.Stop()
210+
for range ticker.C {
211+
a.jobsCache.Cleanup()
212+
}
213+
}()
214+
135215
return tea.Batch(
136216
a.loadAllRunsPaginated(),
137217
tea.EnterAltScreen,
@@ -457,11 +537,11 @@ func (a *App) updateLists(msg tea.Msg) (tea.Model, tea.Cmd) {
457537
a.allRunsList, cmd = a.allRunsList.Update(msg)
458538
cmds = append(cmds, cmd)
459539

460-
// If selection changed, load jobs for the new selection
540+
// If selection changed, load jobs for the new selection with debounce
461541
if a.allRunsList.Index() != oldIndex && len(a.allRuns) > 0 {
462542
if a.allRunsList.Index() < len(a.allRuns) {
463543
selectedRun := a.allRuns[a.allRunsList.Index()]
464-
cmds = append(cmds, a.loadWorkflowRunJobs(selectedRun.ID))
544+
a.scheduleJobsLoad(selectedRun.ID)
465545
}
466546
}
467547
case WorkflowListView:
@@ -472,11 +552,11 @@ func (a *App) updateLists(msg tea.Msg) (tea.Model, tea.Cmd) {
472552
a.runsList, cmd = a.runsList.Update(msg)
473553
cmds = append(cmds, cmd)
474554

475-
// If selection changed, load jobs for the new selection
555+
// If selection changed, load jobs for the new selection with debounce
476556
if a.runsList.Index() != oldIndex && len(a.workflowRuns) > 0 {
477557
if a.runsList.Index() < len(a.workflowRuns) {
478558
selectedRun := a.workflowRuns[a.runsList.Index()]
479-
cmds = append(cmds, a.loadWorkflowRunJobs(selectedRun.ID))
559+
a.scheduleJobsLoad(selectedRun.ID)
480560
}
481561
}
482562
}
@@ -914,12 +994,74 @@ func (a *App) loadWorkflowRunLogs(runID int64) tea.Cmd {
914994
})
915995
}
916996

997+
// scheduleJobsLoad schedules a debounced jobs load
998+
func (a *App) scheduleJobsLoad(runID int64) {
999+
a.debounceMutex.Lock()
1000+
defer a.debounceMutex.Unlock()
1001+
1002+
// キャッシュから取得を試行
1003+
if jobs, found := a.jobsCache.Get(runID); found {
1004+
a.currentJobs = jobs
1005+
return
1006+
}
1007+
1008+
// Cancel existing timer
1009+
if a.debounceTimer != nil {
1010+
a.debounceTimer.Stop()
1011+
}
1012+
1013+
a.pendingRunID = runID
1014+
1015+
// Set new timer
1016+
a.debounceTimer = time.AfterFunc(400*time.Millisecond, func() {
1017+
// Execute the API call after debounce period
1018+
a.executeJobsLoad(runID)
1019+
})
1020+
}
1021+
1022+
// executeJobsLoad executes the actual jobs load
1023+
func (a *App) executeJobsLoad(runID int64) {
1024+
a.debounceMutex.Lock()
1025+
defer a.debounceMutex.Unlock()
1026+
1027+
// Check if this is still the pending request
1028+
if a.pendingRunID != runID {
1029+
return
1030+
}
1031+
1032+
// キャッシュから再度確認(並行処理対策)
1033+
if jobs, found := a.jobsCache.Get(runID); found {
1034+
a.currentJobs = jobs
1035+
return
1036+
}
1037+
1038+
// API呼び出し実行
1039+
go func() {
1040+
jobs, err := a.client.GetWorkflowRunJobs(a.owner, a.repo, runID)
1041+
if err == nil {
1042+
// キャッシュに保存
1043+
a.jobsCache.Set(runID, jobs)
1044+
a.currentJobs = jobs
1045+
}
1046+
}()
1047+
}
1048+
9171049
func (a *App) loadWorkflowRunJobs(runID int64) tea.Cmd {
9181050
return tea.Cmd(func() tea.Msg {
1051+
// キャッシュから取得を試行
1052+
if jobs, found := a.jobsCache.Get(runID); found {
1053+
return jobsLoadedMsg{jobs: jobs}
1054+
}
1055+
1056+
// キャッシュにない場合のみAPI呼び出し
9191057
jobs, err := a.client.GetWorkflowRunJobs(a.owner, a.repo, runID)
9201058
if err != nil {
9211059
return errorMsg{err: err}
9221060
}
1061+
1062+
// キャッシュに保存
1063+
a.jobsCache.Set(runID, jobs)
1064+
9231065
return jobsLoadedMsg{jobs: jobs}
9241066
})
9251067
}

0 commit comments

Comments
 (0)