summaryrefslogtreecommitdiff
path: root/internal/processor
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-08 09:48:42 +0300
committerPaul Buetow <paul@buetow.org>2026-04-08 09:48:42 +0300
commit246881b00c3af3834ad612a64422dff265027234 (patch)
tree9c03af3299bb4cdd4048e66c334c97704bda5a0e /internal/processor
parentf9c7585730d2d119939a6e1e503684ca6ff2e086 (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.go6
-rw-r--r--internal/processor/image_downloader.go31
-rw-r--r--internal/processor/processor.go9
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")