diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-08 09:48:42 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-08 09:48:42 +0300 |
| commit | 246881b00c3af3834ad612a64422dff265027234 (patch) | |
| tree | 9c03af3299bb4cdd4048e66c334c97704bda5a0e /internal/processor | |
| parent | f9c7585730d2d119939a6e1e503684ca6ff2e086 (diff) | |
fix(processor): propagate errors from attribution and sidecar writes
Return errors from saveAudioAttribution (including audio_metadata.txt),
image prompt persistence (callback + saveImagePrompt), and
ProcessWordWithTranslationAndType (card type, translation, phonetic)
instead of only logging warnings so callers see partial failures.
Made-with: Cursor
Diffstat (limited to 'internal/processor')
| -rw-r--r-- | internal/processor/audio_coordinator.go | 6 | ||||
| -rw-r--r-- | internal/processor/image_downloader.go | 31 | ||||
| -rw-r--r-- | internal/processor/processor.go | 9 |
3 files changed, 29 insertions, 17 deletions
diff --git a/internal/processor/audio_coordinator.go b/internal/processor/audio_coordinator.go index 97f233e..441ee51 100644 --- a/internal/processor/audio_coordinator.go +++ b/internal/processor/audio_coordinator.go @@ -264,7 +264,7 @@ func (p *Processor) generateAudioWithVoiceAndFilenameInDir(ctx context.Context, // Write attribution and metadata sidecars next to the audio file. if err := p.saveAudioAttribution(word, outputFile, providerConfig); err != nil { - fmt.Printf(" Warning: Failed to save audio attribution: %v\n", err) + return fmt.Errorf("failed to save audio attribution: %w", err) } return nil @@ -351,12 +351,12 @@ func (p *Processor) saveAudioAttribution(word, audioFile string, config *audio.C return fmt.Errorf("failed to write audio attribution file: %w", err) } - // Also save metadata for GUI display (non-fatal on failure). + // Also save metadata for GUI display. wordDir := filepath.Dir(audioFile) metadataFile := filepath.Join(wordDir, "audio_metadata.txt") metadata := p.buildAudioMetadata(config, audioFile) if err := os.WriteFile(metadataFile, []byte(metadata), 0644); err != nil { - fmt.Printf("Warning: Failed to save audio metadata: %v\n", err) + return fmt.Errorf("failed to save audio metadata: %w", err) } return nil diff --git a/internal/processor/image_downloader.go b/internal/processor/image_downloader.go index 74aa58a..0fe9229 100644 --- a/internal/processor/image_downloader.go +++ b/internal/processor/image_downloader.go @@ -8,6 +8,7 @@ package processor import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -44,17 +45,24 @@ func (p *Processor) downloadImagesWithTranslation(ctx context.Context, word, tra // Register a prompt callback so the AI-generated prompt is persisted // to disk before the download completes (used by the GUI and for debugging). - p.registerPromptCallback(searcher, wordDir) + var promptSaveErr error + p.registerPromptCallback(searcher, wordDir, &promptSaveErr) _, path, err := downloader.DownloadBestMatchWithOptions(ctx, searchOpts) if err != nil { - return err + return errors.Join(err, promptSaveErr) } fmt.Printf(" Downloaded: %s\n", path) + if promptSaveErr != nil { + return promptSaveErr + } + // Persist the final prompt used by the searcher (some providers set it // only after the search call; this handles that case as a fallback). - p.saveImagePrompt(wordDir, searcher) + if err := p.saveImagePrompt(wordDir, searcher); err != nil { + return err + } return nil } @@ -63,14 +71,18 @@ func (p *Processor) downloadImagesWithTranslation(ctx context.Context, word, tra // callback fires during the Search call so the prompt is captured even if the // subsequent download fails. All searchers returned by newImageSearcher // implement image.PromptAwareClient, so no type-assertion is needed. -func (p *Processor) registerPromptCallback(searcher image.PromptAwareClient, wordDir string) { +// promptErr accumulates write failures so downloadImagesWithTranslation can +// return them to the caller instead of only logging. +func (p *Processor) registerPromptCallback(searcher image.PromptAwareClient, wordDir string, promptErr *error) { promptFile := filepath.Join(wordDir, "image_prompt.txt") searcher.SetPromptCallback(func(prompt string) { if prompt == "" { return } if err := os.WriteFile(promptFile, []byte(prompt), 0644); err != nil { - fmt.Printf(" Warning: Failed to save image prompt: %v\n", err) + if promptErr != nil { + *promptErr = errors.Join(*promptErr, fmt.Errorf("failed to save image prompt: %w", err)) + } } }) } @@ -79,25 +91,26 @@ func (p *Processor) registerPromptCallback(searcher image.PromptAwareClient, wor // GetLastPrompt. This acts as a fallback when the prompt is not available via // the callback during the search call itself. The local promptGetter interface // is intentionally narrow: not all PromptAwareClients expose GetLastPrompt. -func (p *Processor) saveImagePrompt(wordDir string, searcher image.PromptAwareClient) { +func (p *Processor) saveImagePrompt(wordDir string, searcher image.PromptAwareClient) error { type promptGetter interface { GetLastPrompt() string } promptSource, ok := searcher.(promptGetter) if !ok { - return + return nil } usedPrompt := promptSource.GetLastPrompt() if usedPrompt == "" { - return + return nil } promptFile := filepath.Join(wordDir, "image_prompt.txt") if err := os.WriteFile(promptFile, []byte(usedPrompt), 0644); err != nil { - fmt.Printf(" Warning: Failed to save image prompt: %v\n", err) + return fmt.Errorf("failed to save image prompt: %w", err) } + return nil } // newImageSearcher creates the appropriate PromptAwareClient based on the diff --git a/internal/processor/processor.go b/internal/processor/processor.go index 323df23..67dc897 100644 --- a/internal/processor/processor.go +++ b/internal/processor/processor.go @@ -253,19 +253,18 @@ func (p *Processor) ProcessWordWithTranslationAndType(ctx context.Context, word, wordDir := p.findOrCreateWordDirectory(word) if err := internal.SaveCardType(wordDir, cardType); err != nil { - fmt.Printf(" Warning: Failed to save card type: %v\n", err) + return fmt.Errorf("failed to save card type: %w", err) } if err := p.saveTranslationIfNeeded(word, translationText, wordDir); err != nil { - fmt.Printf(" Warning: Failed to save translation: %v\n", err) + return fmt.Errorf("failed to save translation: %w", err) } fmt.Printf(" Fetching phonetic information...\n") if err := p.phoneticFetcher.FetchAndSave(word, wordDir); err != nil { - fmt.Printf(" Warning: Failed to fetch phonetic info: %v\n", err) - } else { - fmt.Printf(" Saved phonetic information\n") + return fmt.Errorf("failed to fetch phonetic info: %w", err) } + fmt.Printf(" Saved phonetic information\n") if !p.flags.SkipAudio { fmt.Printf(" Generating audio...\n") |
