diff options
| -rw-r--r-- | cmd/yoga/main_test.go | 2 | ||||
| -rw-r--r-- | internal/app/messages.go | 2 | ||||
| -rw-r--r-- | internal/app/model.go | 117 | ||||
| -rw-r--r-- | internal/app/model_durations.go | 88 | ||||
| -rw-r--r-- | internal/app/model_keys.go | 2 | ||||
| -rw-r--r-- | internal/app/model_test.go | 2 | ||||
| -rw-r--r-- | internal/app/style.go | 1 | ||||
| -rw-r--r-- | internal/meta/version.go | 2 |
8 files changed, 124 insertions, 92 deletions
diff --git a/cmd/yoga/main_test.go b/cmd/yoga/main_test.go index d06ff33..4062b58 100644 --- a/cmd/yoga/main_test.go +++ b/cmd/yoga/main_test.go @@ -7,7 +7,7 @@ import ( "path/filepath" "testing" - "yoga/internal/app" + "codeberg.org/snonux/yoga/internal/app" ) func TestRunPrintsVersion(t *testing.T) { diff --git a/internal/app/messages.go b/internal/app/messages.go index 6c571f7..3847750 100644 --- a/internal/app/messages.go +++ b/internal/app/messages.go @@ -33,3 +33,5 @@ type tagsSavedMsg struct { tags []string err error } + +type reindexVideosMsg struct{} diff --git a/internal/app/model.go b/internal/app/model.go index 76ab358..9361e81 100644 --- a/internal/app/model.go +++ b/internal/app/model.go @@ -3,6 +3,7 @@ package app import ( "fmt" "path/filepath" + "runtime" "strings" "github.com/charmbracelet/bubbles/table" @@ -168,6 +169,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.handleVideosLoaded(typed) case playVideoMsg: return m.handlePlayVideo(typed), nil + case reindexVideosMsg: + return m.handleReindexVideos(typed) case tagsSavedMsg: return m.handleTagsSaved(typed) case tea.WindowSizeMsg: @@ -193,7 +196,7 @@ func (m model) View() string { func (m model) renderBody() string { helpLines := []string{ - "↑/↓ navigate • enter play • s sort • / filter • c crop • t edit tags • q quit", + "↑/↓ navigate • enter play • s sort • / filter • c crop • t edit tags • i re-index • q quit", } info := statusStyle.Render(m.statusText()) progressLine := m.renderProgressLine() @@ -327,6 +330,118 @@ func (m model) handlePlayVideo(msg playVideoMsg) model { return m } +func (m model) handleReindexVideos(msg reindexVideosMsg) (tea.Model, tea.Cmd) { + m.statusMessage = "Re-indexing videos..." + return m, loadVideosCmd(m.root, m.cachePath, m.progress) +} + +func (m model) handleVideosLoaded(msg videosLoadedMsg) (tea.Model, tea.Cmd) { + m.loading = false + if msg.err != nil { + m.err = msg.err + m.statusMessage = fmt.Sprintf("error: %v", msg.err) + } + + if len(m.videos) == 0 { + m.videos = msg.videos + } else { + existingVideos := make(map[string]int) + for i, v := range m.videos { + existingVideos[v.Path] = i + } + + for _, newVideo := range msg.videos { + if i, ok := existingVideos[newVideo.Path]; ok { + m.videos[i] = newVideo + } else { + m.videos = append(m.videos, newVideo) + } + } + } + + m.cache = msg.cache + m.pendingDurations = msg.pending + m.durationTotal = len(msg.pending) + m.durationDone = 0 + m.applyFiltersAndSort() + m.updateStatusAfterLoad(msg) + m.durationInFlight = 0 + if len(msg.pending) == 0 { + return m, nil + } + cmd := m.startDurationWorkers() + return m, cmd +} + +func (m *model) updateStatusAfterLoad(msg videosLoadedMsg) { + if len(m.filtered) == 0 { + m.baseStatus = "No videos found" + m.statusMessage = m.baseStatus + return + } + status := "" + if len(msg.pending) > 0 { + status = fmt.Sprintf("Loaded %d videos, probing durations...", len(m.filtered)) + if msg.cacheErr != nil { + status = fmt.Sprintf("Loaded %d videos (cache warning: %v), probing durations...", len(m.filtered), msg.cacheErr) + } + } else { + status = fmt.Sprintf("Loaded %d videos", len(m.filtered)) + if msg.cacheErr != nil { + status = fmt.Sprintf("Loaded %d videos (cache warning: %v)", len(m.filtered), msg.cacheErr) + } + } + if msg.tagErr != nil { + status = fmt.Sprintf("%s (tag warning: %v)", status, msg.tagErr) + } + m.baseStatus = status + m.statusMessage = status +} + +func (m *model) startDurationWorkers() tea.Cmd { + if len(m.pendingDurations) == 0 { + return nil + } + workers := runtime.NumCPU() + if workers < 1 { + workers = 1 + } + if workers > 6 { + workers = 6 + } + if workers > len(m.pendingDurations) { + workers = len(m.pendingDurations) + } + cmds := make([]tea.Cmd, 0, workers) + for i := 0; i < workers; i++ { + cmd := m.dequeueDurationCmd() + if cmd != nil { + cmds = append(cmds, cmd) + } + } + if len(cmds) == 0 { + return nil + } + return tea.Batch(cmds...) +} + +func (m *model) dequeueDurationCmd() tea.Cmd { + if len(m.pendingDurations) == 0 { + return nil + } + path := m.pendingDurations[0] + m.pendingDurations = m.pendingDurations[1:] + m.durationInFlight++ + return probeDurationsCmd(path, m.cache) +} + +func (m model) activeCrop() string { + if m.cropEnabled && m.cropValue != "" { + return m.cropValue + } + return "" +} + func (m model) handleProgressUpdate(msg progressUpdateMsg) (tea.Model, tea.Cmd) { if !m.loading { return m, nil diff --git a/internal/app/model_durations.go b/internal/app/model_durations.go index 95fd6c7..d8d767f 100644 --- a/internal/app/model_durations.go +++ b/internal/app/model_durations.go @@ -3,7 +3,6 @@ package app import ( "fmt" "path/filepath" - "runtime" "time" tea "github.com/charmbracelet/bubbletea" @@ -95,92 +94,5 @@ func (m *model) resetDurationState() { m.durationInFlight = 0 } -func (m *model) dequeueDurationCmd() tea.Cmd { - if len(m.pendingDurations) == 0 { - return nil - } - path := m.pendingDurations[0] - m.pendingDurations = m.pendingDurations[1:] - m.durationInFlight++ - return probeDurationsCmd(path, m.cache) -} -func (m *model) startDurationWorkers() tea.Cmd { - if len(m.pendingDurations) == 0 { - return nil - } - workers := runtime.NumCPU() - if workers < 1 { - workers = 1 - } - if workers > 6 { - workers = 6 - } - if workers > len(m.pendingDurations) { - workers = len(m.pendingDurations) - } - cmds := make([]tea.Cmd, 0, workers) - for i := 0; i < workers; i++ { - cmd := m.dequeueDurationCmd() - if cmd != nil { - cmds = append(cmds, cmd) - } - } - if len(cmds) == 0 { - return nil - } - return tea.Batch(cmds...) -} - -func (m model) activeCrop() string { - if m.cropEnabled && m.cropValue != "" { - return m.cropValue - } - return "" -} - -func (m model) handleVideosLoaded(msg videosLoadedMsg) (tea.Model, tea.Cmd) { - m.loading = false - if msg.err != nil { - m.err = msg.err - m.statusMessage = fmt.Sprintf("error: %v", msg.err) - } - m.videos = msg.videos - m.cache = msg.cache - m.pendingDurations = msg.pending - m.durationTotal = len(msg.pending) - m.durationDone = 0 - m.applyFiltersAndSort() - m.updateStatusAfterLoad(msg) - m.durationInFlight = 0 - if len(msg.pending) == 0 { - return m, nil - } - cmd := m.startDurationWorkers() - return m, cmd -} -func (m *model) updateStatusAfterLoad(msg videosLoadedMsg) { - if len(m.filtered) == 0 { - m.baseStatus = "No videos found" - m.statusMessage = m.baseStatus - return - } - status := "" - if len(msg.pending) > 0 { - status = fmt.Sprintf("Loaded %d videos, probing durations...", len(m.filtered)) - if msg.cacheErr != nil { - status = fmt.Sprintf("Loaded %d videos (cache warning: %v), probing durations...", len(m.filtered), msg.cacheErr) - } - } else { - status = fmt.Sprintf("Loaded %d videos", len(m.filtered)) - if msg.cacheErr != nil { - status = fmt.Sprintf("Loaded %d videos (cache warning: %v)", len(m.filtered), msg.cacheErr) - } - } - if msg.tagErr != nil { - status = fmt.Sprintf("%s (tag warning: %v)", status, msg.tagErr) - } - m.baseStatus = status - m.statusMessage = status -} diff --git a/internal/app/model_keys.go b/internal/app/model_keys.go index ee15d5d..0e786d4 100644 --- a/internal/app/model_keys.go +++ b/internal/app/model_keys.go @@ -94,6 +94,8 @@ func (m model) handleTableKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m.showHelpBar() case "r": return m.resetFilterState() + case "i": + return m, func() tea.Msg { return reindexVideosMsg{} } default: return m.updateTable(msg) } diff --git a/internal/app/model_test.go b/internal/app/model_test.go index 6d1053b..4cb9bcf 100644 --- a/internal/app/model_test.go +++ b/internal/app/model_test.go @@ -583,7 +583,7 @@ func TestToggleHelpKeys(t *testing.T) { loaded := videosLoadedMsg{videos: []video{vid}, cache: newDurationCache(filepath.Join(root, "cache.json"))} modelAny, _ := m.handleVideosLoaded(loaded) m = modelAny.(model) - helpLine := "↑/↓ navigate • enter play • s sort • / filter • c crop • t edit tags • q quit" + helpLine := "↑/↓ navigate • enter play • s sort • / filter • c crop • t edit tags • i re-index • q quit" if view := m.View(); !strings.Contains(view, helpLine) { t.Fatalf("expected help line visible: %s", view) } diff --git a/internal/app/style.go b/internal/app/style.go index de26b8a..452eaa6 100644 --- a/internal/app/style.go +++ b/internal/app/style.go @@ -10,6 +10,7 @@ var ( ".avi": {}, ".wmv": {}, ".m4v": {}, + ".webm": {}, } tableStyle = lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("63")).Padding(0, 1) headerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("99")).Bold(true) diff --git a/internal/meta/version.go b/internal/meta/version.go index fcd820f..8b3a685 100644 --- a/internal/meta/version.go +++ b/internal/meta/version.go @@ -1,3 +1,3 @@ package meta -const Version = "v0.2.5" +const Version = "v0.3.0" |
