- Rename ebook_scanner_library_type_test.go -> media_scanner_library_type_test.go - Rename ebook_scanner_comic_test.go -> media_scanner_comic_test.go - Rename ebook_scanner_hash_test.go -> media_scanner_hash_test.go - Update test function names (TestEbookScanner* -> TestMediaScanner*) - Update ExampleEbookScanner_calculateFileSHA256 -> ExampleMediaScanner_calculateFileSHA256 - Update all EbookScanner references to MediaScanner in tests
612 lines
14 KiB
Go
612 lines
14 KiB
Go
package services
|
|
|
|
import (
|
|
"archive/tar"
|
|
"archive/zip"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestExtractZipMetadata tests .cbz (ZIP) metadata extraction
|
|
func TestExtractZipMetadata(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
cbzFile := filepath.Join(tmpDir, "test.cbz")
|
|
|
|
// Create a test ZIP archive with ComicInfo.xml and cover image
|
|
if err := createTestCBZ(cbzFile); err != nil {
|
|
t.Fatalf("Failed to create test CBZ: %v", err)
|
|
}
|
|
|
|
// Test metadata extraction
|
|
comicInfo, coverImage, err := extractZipMetadata(cbzFile)
|
|
if err != nil {
|
|
t.Fatalf("extractZipMetadata failed: %v", err)
|
|
}
|
|
|
|
// Verify metadata
|
|
if comicInfo == nil {
|
|
t.Fatal("comicInfo is nil")
|
|
}
|
|
|
|
if comicInfo.Title != "Test Comic Title" {
|
|
t.Errorf("Expected title 'Test Comic Title', got '%s'", comicInfo.Title)
|
|
}
|
|
|
|
if comicInfo.Series != "Test Series" {
|
|
t.Errorf("Expected series 'Test Series', got '%s'", comicInfo.Series)
|
|
}
|
|
|
|
if comicInfo.Number != 1 {
|
|
t.Errorf("Expected issue number 1, got %d", comicInfo.Number)
|
|
}
|
|
|
|
if comicInfo.Publisher != "Test Publisher" {
|
|
t.Errorf("Expected publisher 'Test Publisher', got '%s'", comicInfo.Publisher)
|
|
}
|
|
|
|
if comicInfo.Writer != "Test Writer" {
|
|
t.Errorf("Expected writer 'Test Writer', got '%s'", comicInfo.Writer)
|
|
}
|
|
|
|
// Verify cover image
|
|
if len(coverImage) == 0 {
|
|
t.Error("Cover image not extracted")
|
|
// Debug: Check if the file was created properly
|
|
t.Logf("Debug: Checking created CBZ file")
|
|
r, err := zip.OpenReader(cbzFile)
|
|
if err != nil {
|
|
t.Logf("Failed to open CBZ for debug: %v", err)
|
|
return
|
|
}
|
|
defer r.Close()
|
|
for _, f := range r.File {
|
|
t.Logf("File in CBZ: %s (isImage: %v)", f.Name, isImageFile(f.Name))
|
|
}
|
|
} else {
|
|
// Verify it's a valid image
|
|
_, format, err := image.Decode(bytes.NewReader(coverImage))
|
|
if err != nil {
|
|
t.Errorf("Extracted cover is not a valid image: %v", err)
|
|
}
|
|
t.Logf("Cover image format: %s, size: %d bytes", format, len(coverImage))
|
|
}
|
|
|
|
t.Logf("ZIP metadata extraction test passed: title=%s, series=%s, issue=%d",
|
|
comicInfo.Title, comicInfo.Series, comicInfo.Number)
|
|
}
|
|
|
|
// TestExtractZipMetadataWithoutComicInfo tests .cbz without ComicInfo.xml
|
|
func TestExtractZipMetadataWithoutComicInfo(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
cbzFile := filepath.Join(tmpDir, "test_no_metadata.cbz")
|
|
|
|
// Create a ZIP without ComicInfo.xml
|
|
if err := createTestCBZWithoutMetadata(cbzFile); err != nil {
|
|
t.Fatalf("Failed to create test CBZ: %v", err)
|
|
}
|
|
|
|
// Test metadata extraction
|
|
comicInfo, coverImage, err := extractZipMetadata(cbzFile)
|
|
if err != nil {
|
|
t.Fatalf("extractZipMetadata failed: %v", err)
|
|
}
|
|
|
|
// Should fall back to filename
|
|
if comicInfo.Title != "test_no_metadata" {
|
|
t.Errorf("Expected title 'test_no_metadata', got '%s'", comicInfo.Title)
|
|
}
|
|
|
|
// Should still extract cover
|
|
if len(coverImage) == 0 {
|
|
t.Error("Cover image not extracted")
|
|
}
|
|
|
|
t.Logf("ZIP without ComicInfo.xml test passed: title=%s (fallback)", comicInfo.Title)
|
|
}
|
|
|
|
// TestExtractRarMetadata tests .cbr (RAR) metadata extraction
|
|
func TestExtractRarMetadata(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping RAR test in short mode (requires test archive)")
|
|
}
|
|
|
|
// Note: Creating RAR archives requires additional libraries
|
|
// This test will use a pre-created test file if available
|
|
// For now, we'll test with a fallback
|
|
|
|
tmpDir := t.TempDir()
|
|
cbrFile := filepath.Join(tmpDir, "test.cbr")
|
|
|
|
// Create a mock RAR file (not a real RAR, just for error handling test)
|
|
if err := os.WriteFile(cbrFile, []byte("not a real rar"), 0644); err != nil {
|
|
t.Fatalf("Failed to create mock RAR: %v", err)
|
|
}
|
|
|
|
// Test error handling
|
|
_, _, err := extractRarMetadata(cbrFile)
|
|
if err == nil {
|
|
t.Error("Expected error for invalid RAR file, got nil")
|
|
}
|
|
|
|
t.Logf("RAR error handling test passed")
|
|
}
|
|
|
|
// TestExtract7ZipMetadata tests .cb7 (7-Zip) metadata extraction
|
|
func TestExtract7ZipMetadata(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping 7-Zip test in short mode (requires test archive)")
|
|
}
|
|
|
|
tmpDir := t.TempDir()
|
|
cb7File := filepath.Join(tmpDir, "test.cb7")
|
|
|
|
// Create a mock 7-Zip file for error handling test
|
|
if err := os.WriteFile(cb7File, []byte("not a real 7z"), 0644); err != nil {
|
|
t.Fatalf("Failed to create mock 7z: %v", err)
|
|
}
|
|
|
|
// Test error handling
|
|
_, _, err := extract7ZipMetadata(cb7File)
|
|
if err == nil {
|
|
t.Error("Expected error for invalid 7-Zip file, got nil")
|
|
}
|
|
|
|
t.Logf("7-Zip error handling test passed")
|
|
}
|
|
|
|
// TestExtractTarMetadata tests .cbt (TAR) metadata extraction
|
|
func TestExtractTarMetadata(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
cbtFile := filepath.Join(tmpDir, "test.cbt")
|
|
|
|
// Create a test TAR archive with ComicInfo.xml and cover image
|
|
if err := createTestCBT(cbtFile); err != nil {
|
|
t.Fatalf("Failed to create test CBT: %v", err)
|
|
}
|
|
|
|
// Test metadata extraction
|
|
comicInfo, coverImage, err := extractTarMetadata(cbtFile)
|
|
if err != nil {
|
|
t.Fatalf("extractTarMetadata failed: %v", err)
|
|
}
|
|
|
|
// Verify metadata
|
|
if comicInfo == nil {
|
|
t.Fatal("comicInfo is nil")
|
|
}
|
|
|
|
if comicInfo.Title != "Test Tar Comic" {
|
|
t.Errorf("Expected title 'Test Tar Comic', got '%s'", comicInfo.Title)
|
|
}
|
|
|
|
// Verify cover image
|
|
if len(coverImage) == 0 {
|
|
t.Error("Cover image not extracted")
|
|
}
|
|
|
|
t.Logf("TAR metadata extraction test passed: title=%s", comicInfo.Title)
|
|
}
|
|
|
|
// TestExtractTarGzMetadata tests .tar.gz metadata extraction
|
|
func TestExtractTarGzMetadata(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
targzFile := filepath.Join(tmpDir, "test.tar.gz")
|
|
|
|
// Create a test TAR.GZ archive
|
|
if err := createTestTarGz(targzFile); err != nil {
|
|
t.Fatalf("Failed to create test TAR.GZ: %v", err)
|
|
}
|
|
|
|
// Test metadata extraction
|
|
comicInfo, coverImage, err := extractTarMetadata(targzFile)
|
|
if err != nil {
|
|
t.Fatalf("extractTarMetadata failed for tar.gz: %v", err)
|
|
}
|
|
|
|
// Verify metadata
|
|
if comicInfo == nil {
|
|
t.Fatal("comicInfo is nil")
|
|
}
|
|
|
|
if comicInfo.Title != "Test Tar Gz Comic" {
|
|
t.Errorf("Expected title 'Test Tar Gz Comic', got '%s'", comicInfo.Title)
|
|
}
|
|
|
|
// Verify cover image
|
|
if len(coverImage) == 0 {
|
|
t.Error("Cover image not extracted")
|
|
}
|
|
|
|
t.Logf("TAR.GZ metadata extraction test passed: title=%s", comicInfo.Title)
|
|
}
|
|
|
|
// TestExtractComicMetadata tests the router function
|
|
func TestExtractComicMetadata(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
fileExt string
|
|
createFunc func(string) error
|
|
shouldFail bool
|
|
expectedTitle string
|
|
}{
|
|
{
|
|
name: "CBZ file",
|
|
fileExt: ".cbz",
|
|
createFunc: func(path string) error { return createTestCBZ(path) },
|
|
shouldFail: false,
|
|
expectedTitle: "Test Comic Title",
|
|
},
|
|
{
|
|
name: "CBT file",
|
|
fileExt: ".cbt",
|
|
createFunc: func(path string) error { return createTestCBT(path) },
|
|
shouldFail: false,
|
|
expectedTitle: "Test Tar Comic",
|
|
},
|
|
{
|
|
name: "Unsupported format",
|
|
fileExt: ".xyz",
|
|
createFunc: func(path string) error { return os.WriteFile(path, []byte("test"), 0644) },
|
|
shouldFail: true,
|
|
expectedTitle: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
testFile := filepath.Join(tmpDir, "test"+tt.fileExt)
|
|
|
|
if err := tt.createFunc(testFile); err != nil {
|
|
t.Fatalf("Failed to create test file: %v", err)
|
|
}
|
|
|
|
comicInfo, coverImage, err := extractComicMetadata(testFile)
|
|
|
|
if tt.shouldFail {
|
|
if err == nil {
|
|
t.Error("Expected error, got nil")
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("extractComicMetadata failed: %v", err)
|
|
}
|
|
|
|
if comicInfo == nil {
|
|
t.Fatal("comicInfo is nil")
|
|
}
|
|
|
|
if comicInfo.Title != tt.expectedTitle {
|
|
t.Errorf("Expected title '%s', got '%s'", tt.expectedTitle, comicInfo.Title)
|
|
}
|
|
|
|
if len(coverImage) == 0 {
|
|
t.Error("Cover image not extracted")
|
|
}
|
|
|
|
t.Logf("Test passed: title=%s, cover=%d bytes", comicInfo.Title, len(coverImage))
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsImageFile tests image file detection
|
|
func TestIsImageFile(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
filename string
|
|
expected bool
|
|
}{
|
|
{"JPG file", "cover.jpg", true},
|
|
{"JPEG file", "cover.jpeg", true},
|
|
{"PNG file", "cover.png", true},
|
|
{"GIF file", "cover.gif", true},
|
|
{"Uppercase JPG", "cover.JPG", true},
|
|
{"Mixed case", "cover.JpG", true},
|
|
{"XML file", "ComicInfo.xml", false},
|
|
{"Text file", "readme.txt", false},
|
|
{"No extension", "cover", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := isImageFile(tt.filename)
|
|
if result != tt.expected {
|
|
t.Errorf("Expected %v for %s, got %v", tt.expected, tt.filename, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkExtractZipMetadata benchmarks ZIP extraction
|
|
func BenchmarkExtractZipMetadata(b *testing.B) {
|
|
tmpDir := b.TempDir()
|
|
cbzFile := filepath.Join(tmpDir, "bench.cbz")
|
|
|
|
if err := createTestCBZ(cbzFile); err != nil {
|
|
b.Fatalf("Failed to create test CBZ: %v", err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _, _ = extractZipMetadata(cbzFile)
|
|
}
|
|
}
|
|
|
|
// Helper functions to create test archives
|
|
|
|
// createTestCBZ creates a test ZIP archive with ComicInfo.xml and cover image
|
|
func createTestCBZ(path string) error {
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
zipWriter := zip.NewWriter(file)
|
|
defer zipWriter.Close()
|
|
|
|
// Add ComicInfo.xml
|
|
comicInfo := ComicInfo{
|
|
Title: "Test Comic Title",
|
|
Series: "Test Series",
|
|
Number: 1,
|
|
Volume: 1,
|
|
Publisher: "Test Publisher",
|
|
Year: 2024,
|
|
Month: 1,
|
|
Day: 1,
|
|
Writer: "Test Writer",
|
|
Penciller: "Test Penciller",
|
|
Inker: "Test Inker",
|
|
Genre: "Test Genre",
|
|
}
|
|
|
|
xmlData, err := xml.Marshal(comicInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
writer, err := zipWriter.Create("ComicInfo.xml")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := writer.Write(xmlData); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a simple valid PNG image (1x1 red pixel) using image/png package
|
|
img := image.NewRGBA(image.Rect(0, 0, 1, 1))
|
|
img.Set(0, 0, color.RGBA{255, 0, 0, 255})
|
|
|
|
var pngBuf bytes.Buffer
|
|
if err := png.Encode(&pngBuf, img); err != nil {
|
|
return err
|
|
}
|
|
coverPNG := pngBuf.Bytes()
|
|
|
|
// Write cover with .jpg extension (even though it's PNG data, for testing extension handling)
|
|
writer, err = zipWriter.Create("cover.jpg")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := writer.Write(coverPNG); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Add a page image
|
|
writer, err = zipWriter.Create("page001.jpg")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := writer.Write(coverPNG); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createTestCBZWithoutMetadata creates a ZIP without ComicInfo.xml
|
|
func createTestCBZWithoutMetadata(path string) error {
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
zipWriter := zip.NewWriter(file)
|
|
defer zipWriter.Close()
|
|
|
|
// Create a simple valid PNG image
|
|
img := image.NewRGBA(image.Rect(0, 0, 1, 1))
|
|
img.Set(0, 0, color.RGBA{0, 0, 255, 255})
|
|
|
|
var pngBuf bytes.Buffer
|
|
if err := png.Encode(&pngBuf, img); err != nil {
|
|
return err
|
|
}
|
|
coverPNG := pngBuf.Bytes()
|
|
|
|
writer, err := zipWriter.Create("cover.jpg")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := writer.Write(coverPNG); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createTestCBT creates a test TAR archive
|
|
func createTestCBT(path string) error {
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
tarWriter := tar.NewWriter(file)
|
|
defer tarWriter.Close()
|
|
|
|
// Add ComicInfo.xml
|
|
comicInfo := ComicInfo{
|
|
Title: "Test Tar Comic",
|
|
Series: "Test Tar Series",
|
|
Number: 5,
|
|
Publisher: "Test Publisher",
|
|
}
|
|
|
|
xmlData, err := xml.Marshal(comicInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
header := &tar.Header{
|
|
Name: "ComicInfo.xml",
|
|
Mode: 0644,
|
|
Size: int64(len(xmlData)),
|
|
ModTime: time.Now(),
|
|
}
|
|
|
|
if err := tarWriter.WriteHeader(header); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := tarWriter.Write(xmlData); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a simple valid PNG image
|
|
img := image.NewRGBA(image.Rect(0, 0, 1, 1))
|
|
img.Set(0, 0, color.RGBA{0, 255, 0, 255})
|
|
|
|
var pngBuf bytes.Buffer
|
|
if err := png.Encode(&pngBuf, img); err != nil {
|
|
return err
|
|
}
|
|
coverPNG := pngBuf.Bytes()
|
|
|
|
header = &tar.Header{
|
|
Name: "cover.jpg",
|
|
Mode: 0644,
|
|
Size: int64(len(coverPNG)),
|
|
ModTime: time.Now(),
|
|
}
|
|
|
|
if err := tarWriter.WriteHeader(header); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := tarWriter.Write(coverPNG); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createTestTarGz creates a test TAR.GZ archive
|
|
func createTestTarGz(path string) error {
|
|
// First create the TAR content in memory
|
|
var tarBuffer bytes.Buffer
|
|
tarWriter := tar.NewWriter(&tarBuffer)
|
|
|
|
comicInfo := ComicInfo{
|
|
Title: "Test Tar Gz Comic",
|
|
Series: "Test Tar Gz Series",
|
|
Number: 10,
|
|
}
|
|
|
|
xmlData, err := xml.Marshal(comicInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
header := &tar.Header{
|
|
Name: "ComicInfo.xml",
|
|
Mode: 0644,
|
|
Size: int64(len(xmlData)),
|
|
ModTime: time.Now(),
|
|
}
|
|
|
|
if err := tarWriter.WriteHeader(header); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := tarWriter.Write(xmlData); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a simple valid PNG image
|
|
img := image.NewRGBA(image.Rect(0, 0, 1, 1))
|
|
img.Set(0, 0, color.RGBA{255, 255, 0, 255})
|
|
|
|
var pngBuf bytes.Buffer
|
|
if err := png.Encode(&pngBuf, img); err != nil {
|
|
return err
|
|
}
|
|
coverPNG := pngBuf.Bytes()
|
|
|
|
header = &tar.Header{
|
|
Name: "cover.jpg",
|
|
Mode: 0644,
|
|
Size: int64(len(coverPNG)),
|
|
ModTime: time.Now(),
|
|
}
|
|
|
|
if err := tarWriter.WriteHeader(header); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := tarWriter.Write(coverPNG); err != nil {
|
|
return err
|
|
}
|
|
|
|
tarWriter.Close()
|
|
|
|
// Now compress the TAR with gzip
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
gzWriter := gzip.NewWriter(file)
|
|
defer gzWriter.Close()
|
|
|
|
if _, err := gzWriter.Write(tarBuffer.Bytes()); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Example usage
|
|
func Example_extractComicMetadata() {
|
|
// Extract metadata from a comic archive
|
|
comicInfo, coverImage, err := extractComicMetadata("/path/to/comic.cbz")
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("Title: %s\n", comicInfo.Title)
|
|
fmt.Printf("Series: %s\n", comicInfo.Series)
|
|
fmt.Printf("Issue: %d\n", comicInfo.Number)
|
|
fmt.Printf("Cover size: %d bytes\n", len(coverImage))
|
|
}
|