perf(error): better deal with deferred errors

This commit is contained in:
Antoine Aflalo
2024-09-09 14:28:24 -04:00
parent a80997835a
commit 5b183cca29
4 changed files with 130 additions and 52 deletions

View File

@@ -4,6 +4,7 @@ import (
"archive/zip" "archive/zip"
"fmt" "fmt"
"github.com/belphemur/CBZOptimizer/manga" "github.com/belphemur/CBZOptimizer/manga"
"github.com/belphemur/CBZOptimizer/utils/errs"
"os" "os"
"time" "time"
) )
@@ -14,14 +15,14 @@ func WriteChapterToCBZ(chapter *manga.Chapter, outputFilePath string) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to create .cbz file: %w", err) return fmt.Errorf("failed to create .cbz file: %w", err)
} }
defer zipFile.Close() defer errs.Capture(&err, zipFile.Close, "failed to close .cbz file")
// Create a new ZIP writer // Create a new ZIP writer
zipWriter := zip.NewWriter(zipFile) zipWriter := zip.NewWriter(zipFile)
if err != nil { if err != nil {
return err return err
} }
defer zipWriter.Close() defer errs.Capture(&err, zipWriter.Close, "failed to close .cbz writer")
// Write each page to the ZIP archive // Write each page to the ZIP archive
for _, page := range chapter.Pages { for _, page := range chapter.Pages {

View File

@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"github.com/araddon/dateparse" "github.com/araddon/dateparse"
"github.com/belphemur/CBZOptimizer/manga" "github.com/belphemur/CBZOptimizer/manga"
"github.com/belphemur/CBZOptimizer/utils/errs"
"io" "io"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -18,7 +19,7 @@ func LoadChapter(filePath string) (*manga.Chapter, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open .cbz file: %w", err) return nil, fmt.Errorf("failed to open .cbz file: %w", err)
} }
defer r.Close() defer errs.Capture(&err, r.Close, "failed to close opened .cbz file")
chapter := &manga.Chapter{ chapter := &manga.Chapter{
FilePath: filePath, FilePath: filePath,
@@ -39,12 +40,15 @@ func LoadChapter(filePath string) (*manga.Chapter, error) {
if f.FileInfo().IsDir() { if f.FileInfo().IsDir() {
continue continue
} }
err := func() error {
// Open the file inside the zip // Open the file inside the zip
rc, err := f.Open() rc, err := f.Open()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open file inside .cbz: %w", err) return fmt.Errorf("failed to open file inside .cbz: %w", err)
} }
defer errs.Capture(&err, rc.Close, "failed to close file inside .cbz")
// Determine the file extension // Determine the file extension
ext := strings.ToLower(filepath.Ext(f.Name)) ext := strings.ToLower(filepath.Ext(f.Name))
@@ -52,23 +56,20 @@ func LoadChapter(filePath string) (*manga.Chapter, error) {
// Read the ComicInfo.xml file content // Read the ComicInfo.xml file content
xmlContent, err := io.ReadAll(rc) xmlContent, err := io.ReadAll(rc)
if err != nil { if err != nil {
rc.Close() return fmt.Errorf("failed to read ComicInfo.xml content: %w", err)
return nil, fmt.Errorf("failed to read ComicInfo.xml content: %w", err)
} }
chapter.ComicInfoXml = string(xmlContent) chapter.ComicInfoXml = string(xmlContent)
} else if !chapter.IsConverted && ext == ".txt" && strings.ToLower(filepath.Base(f.Name)) == "converted.txt" { } else if !chapter.IsConverted && ext == ".txt" && strings.ToLower(filepath.Base(f.Name)) == "converted.txt" {
textContent, err := io.ReadAll(rc) textContent, err := io.ReadAll(rc)
if err != nil { if err != nil {
rc.Close() return fmt.Errorf("failed to read Converted.xml content: %w", err)
return nil, fmt.Errorf("failed to read Converted.xml content: %w", err)
} }
scanner := bufio.NewScanner(bytes.NewReader(textContent)) scanner := bufio.NewScanner(bytes.NewReader(textContent))
if scanner.Scan() { if scanner.Scan() {
convertedTime := scanner.Text() convertedTime := scanner.Text()
chapter.ConvertedTime, err = dateparse.ParseAny(convertedTime) chapter.ConvertedTime, err = dateparse.ParseAny(convertedTime)
if err != nil { if err != nil {
rc.Close() return fmt.Errorf("failed to parse converted time: %w", err)
return nil, fmt.Errorf("failed to parse converted time: %w", err)
} }
chapter.IsConverted = true chapter.IsConverted = true
} }
@@ -77,8 +78,7 @@ func LoadChapter(filePath string) (*manga.Chapter, error) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
_, err = io.Copy(buf, rc) _, err = io.Copy(buf, rc)
if err != nil { if err != nil {
rc.Close() return fmt.Errorf("failed to read file contents: %w", err)
return nil, fmt.Errorf("failed to read file contents: %w", err)
} }
// Create a new Page object // Create a new Page object
@@ -93,8 +93,11 @@ func LoadChapter(filePath string) (*manga.Chapter, error) {
// Add the page to the chapter // Add the page to the chapter
chapter.Pages = append(chapter.Pages, page) chapter.Pages = append(chapter.Pages, page)
} }
rc.Close() return nil
}()
if err != nil {
return nil, err
}
} }
return chapter, nil return chapter, nil

View File

@@ -0,0 +1,16 @@
package errs
import (
"errors"
"fmt"
)
// Capture runs errFunc and assigns the error, if any, to *errPtr. Preserves the
// original error by wrapping with errors.Join if the errFunc err is non-nil.
func Capture(errPtr *error, errFunc func() error, msg string) {
err := errFunc()
if err == nil {
return
}
*errPtr = errors.Join(*errPtr, fmt.Errorf("%s: %w", msg, err))
}

View File

@@ -0,0 +1,58 @@
package errs
import (
"errors"
"fmt"
"testing"
)
func TestCapture(t *testing.T) {
tests := []struct {
name string
initial error
errFunc func() error
msg string
expected string
}{
{
name: "No error from errFunc",
initial: nil,
errFunc: func() error { return nil },
msg: "test message",
expected: "",
},
{
name: "Error from errFunc with no initial error",
initial: nil,
errFunc: func() error { return errors.New("error from func") },
msg: "test message",
expected: "test message: error from func",
},
{
name: "Error from errFunc with initial error",
initial: errors.New("initial error"),
errFunc: func() error { return errors.New("error from func") },
msg: "test message",
expected: "initial error\ntest message: error from func",
},
{
name: "Error from errFunc with initial wrapped error",
initial: fmt.Errorf("wrapped error: %w", errors.New("initial error")),
errFunc: func() error { return errors.New("error from func") },
msg: "test message",
expected: "wrapped error: initial error\ntest message: error from func",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var err error = tt.initial
Capture(&err, tt.errFunc, tt.msg)
if err != nil && err.Error() != tt.expected {
t.Errorf("expected %q, got %q", tt.expected, err.Error())
} else if err == nil && tt.expected != "" {
t.Errorf("expected %q, got nil", tt.expected)
}
})
}
}