diff --git a/cbz/cbz_creator.go b/cbz/cbz_creator.go index 7df9434..1deaaef 100644 --- a/cbz/cbz_creator.go +++ b/cbz/cbz_creator.go @@ -4,6 +4,7 @@ import ( "archive/zip" "fmt" "github.com/belphemur/CBZOptimizer/manga" + "github.com/belphemur/CBZOptimizer/utils/errs" "os" "time" ) @@ -14,14 +15,14 @@ func WriteChapterToCBZ(chapter *manga.Chapter, outputFilePath string) error { if err != nil { 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 zipWriter := zip.NewWriter(zipFile) if err != nil { return err } - defer zipWriter.Close() + defer errs.Capture(&err, zipWriter.Close, "failed to close .cbz writer") // Write each page to the ZIP archive for _, page := range chapter.Pages { diff --git a/cbz/cbz_loader.go b/cbz/cbz_loader.go index b2ded20..159aa81 100644 --- a/cbz/cbz_loader.go +++ b/cbz/cbz_loader.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/araddon/dateparse" "github.com/belphemur/CBZOptimizer/manga" + "github.com/belphemur/CBZOptimizer/utils/errs" "io" "path/filepath" "strings" @@ -18,7 +19,7 @@ func LoadChapter(filePath string) (*manga.Chapter, error) { if err != nil { 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{ FilePath: filePath, @@ -39,62 +40,64 @@ func LoadChapter(filePath string) (*manga.Chapter, error) { if f.FileInfo().IsDir() { continue } - // Open the file inside the zip - rc, err := f.Open() - if err != nil { - return nil, fmt.Errorf("failed to open file inside .cbz: %w", err) - } - - // Determine the file extension - ext := strings.ToLower(filepath.Ext(f.Name)) - - if ext == ".xml" && strings.ToLower(filepath.Base(f.Name)) == "comicinfo.xml" { - // Read the ComicInfo.xml file content - xmlContent, err := io.ReadAll(rc) - if err != nil { - rc.Close() - return nil, fmt.Errorf("failed to read ComicInfo.xml content: %w", err) - } - chapter.ComicInfoXml = string(xmlContent) - } else if !chapter.IsConverted && ext == ".txt" && strings.ToLower(filepath.Base(f.Name)) == "converted.txt" { - textContent, err := io.ReadAll(rc) + err := func() error { + // Open the file inside the zip + rc, err := f.Open() if err != nil { - rc.Close() - return nil, fmt.Errorf("failed to read Converted.xml content: %w", err) + return fmt.Errorf("failed to open file inside .cbz: %w", err) } - scanner := bufio.NewScanner(bytes.NewReader(textContent)) - if scanner.Scan() { - convertedTime := scanner.Text() - chapter.ConvertedTime, err = dateparse.ParseAny(convertedTime) + + defer errs.Capture(&err, rc.Close, "failed to close file inside .cbz") + + // Determine the file extension + ext := strings.ToLower(filepath.Ext(f.Name)) + + if ext == ".xml" && strings.ToLower(filepath.Base(f.Name)) == "comicinfo.xml" { + // Read the ComicInfo.xml file content + xmlContent, err := io.ReadAll(rc) if err != nil { - rc.Close() - return nil, fmt.Errorf("failed to parse converted time: %w", err) + return fmt.Errorf("failed to read ComicInfo.xml content: %w", err) + } + chapter.ComicInfoXml = string(xmlContent) + } else if !chapter.IsConverted && ext == ".txt" && strings.ToLower(filepath.Base(f.Name)) == "converted.txt" { + textContent, err := io.ReadAll(rc) + if err != nil { + return fmt.Errorf("failed to read Converted.xml content: %w", err) + } + scanner := bufio.NewScanner(bytes.NewReader(textContent)) + if scanner.Scan() { + convertedTime := scanner.Text() + chapter.ConvertedTime, err = dateparse.ParseAny(convertedTime) + if err != nil { + return fmt.Errorf("failed to parse converted time: %w", err) + } + chapter.IsConverted = true + } + } else { + // Read the file contents for page + buf := new(bytes.Buffer) + _, err = io.Copy(buf, rc) + if err != nil { + return fmt.Errorf("failed to read file contents: %w", err) } - chapter.IsConverted = true - } - } else { - // Read the file contents for page - buf := new(bytes.Buffer) - _, err = io.Copy(buf, rc) - if err != nil { - rc.Close() - return nil, fmt.Errorf("failed to read file contents: %w", err) - } - // Create a new Page object - page := &manga.Page{ - Index: uint16(len(chapter.Pages)), // Simple index based on order - Extension: ext, - Size: uint64(buf.Len()), - Contents: buf, - IsSplitted: false, - } + // Create a new Page object + page := &manga.Page{ + Index: uint16(len(chapter.Pages)), // Simple index based on order + Extension: ext, + Size: uint64(buf.Len()), + Contents: buf, + IsSplitted: false, + } - // Add the page to the chapter - chapter.Pages = append(chapter.Pages, page) + // Add the page to the chapter + chapter.Pages = append(chapter.Pages, page) + } + return nil + }() + if err != nil { + return nil, err } - rc.Close() - } return chapter, nil diff --git a/cbz/cbz_loader_test.go b/cbz/cbz_loader_test.go index f49452b..f820c18 100644 --- a/cbz/cbz_loader_test.go +++ b/cbz/cbz_loader_test.go @@ -24,7 +24,7 @@ func TestLoadChapter(t *testing.T) { }, { name: "Converted Chapter", - filePath: "../testdata/Chapter 1_converted.cbz", + filePath: "../testdata/Chapter 10_converted.cbz", expectedPages: 107, expectedSeries: "Boundless Necromancer", expectedConversion: true, diff --git a/testdata/Chapter 1_converted.cbz b/testdata/Chapter 10_converted.cbz similarity index 100% rename from testdata/Chapter 1_converted.cbz rename to testdata/Chapter 10_converted.cbz diff --git a/utils/errs/errors_defer.go b/utils/errs/errors_defer.go new file mode 100644 index 0000000..050bf6d --- /dev/null +++ b/utils/errs/errors_defer.go @@ -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)) +} diff --git a/utils/errs/errors_defer_test.go b/utils/errs/errors_defer_test.go new file mode 100644 index 0000000..d4b2d83 --- /dev/null +++ b/utils/errs/errors_defer_test.go @@ -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) + } + }) + } +}