From 0e813f14ce36a9c19150f736d67e2fa2a5b21147 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 8 Feb 2026 14:29:49 -0500 Subject: [PATCH] refactor(services): rename EbookScanner to MediaScanner - Rename EbookScanner struct to MediaScanner - Rename EbookMetadata struct to MediaMetadata - Rename NewEbookScanner to NewMediaScanner - Rename processEbookFile to processMediaFile - Rename updateEbook to updateMediaItem - Rename getEbookByFilePath to getMediaItemByFilePath - Remove unused isEbookFile method - Update all method receivers - Update variable names (ebookFiles -> mediaFiles, existingEbook -> existingItem) - Update print statements to use 'media' terminology - Update worker.go to use NewMediaScanner - File renamed: ebook_scanner.go -> media_scanner.go --- .../{ebook_scanner.go => media_scanner.go} | 115 ++++++++---------- internal/services/worker.go | 2 +- 2 files changed, 55 insertions(+), 62 deletions(-) rename internal/services/{ebook_scanner.go => media_scanner.go} (90%) diff --git a/internal/services/ebook_scanner.go b/internal/services/media_scanner.go similarity index 90% rename from internal/services/ebook_scanner.go rename to internal/services/media_scanner.go index 9197220..3a6ef7f 100644 --- a/internal/services/ebook_scanner.go +++ b/internal/services/media_scanner.go @@ -33,7 +33,8 @@ import ( "github.com/nwaples/rardecode" ) -type EbookMetadata struct { +// MediaMetadata contains extracted metadata for media files (ebooks, comics, manga) +type MediaMetadata struct { Title string Author string Description string @@ -66,7 +67,8 @@ type FormatInfo struct { MimeType string } -type EbookScanner struct { +// MediaScanner scans library folders for media files (ebooks, comics, manga) +type MediaScanner struct { db *database.Queries watcher *fsnotify.Watcher folders []string @@ -75,13 +77,14 @@ type EbookScanner struct { libraryTypes map[string][]string } -func NewEbookScanner(db *database.Queries) *EbookScanner { +// NewMediaScanner creates a new media scanner instance +func NewMediaScanner(db *database.Queries) *MediaScanner { watcher, err := fsnotify.NewWatcher() if err != nil { panic(fmt.Sprintf("Failed to create file watcher: %v", err)) } - return &EbookScanner{ + return &MediaScanner{ db: db, watcher: watcher, folders: []string{}, @@ -91,11 +94,11 @@ func NewEbookScanner(db *database.Queries) *EbookScanner { } } -func (s *EbookScanner) SetAdminID(adminID pgtype.UUID) { +func (s *MediaScanner) SetAdminID(adminID pgtype.UUID) { s.adminID = adminID } -func (s *EbookScanner) SetFolders(folders []string) error { +func (s *MediaScanner) SetFolders(folders []string) error { s.folders = folders // Remove old watch if exists @@ -145,7 +148,7 @@ func (s *EbookScanner) SetFolders(folders []string) error { return nil } -func (s *EbookScanner) ScanFolders(ctx context.Context) error { +func (s *MediaScanner) ScanFolders(ctx context.Context) error { if len(s.folders) == 0 { return fmt.Errorf("no folders set") } @@ -153,7 +156,7 @@ func (s *EbookScanner) ScanFolders(ctx context.Context) error { fmt.Printf("Starting scan of %d folders: %v\n", len(s.folders), s.folders) totalFiles := 0 - ebookFiles := 0 + mediaFiles := 0 for _, folder := range s.folders { fmt.Printf("Scanning folder: %s\n", folder) @@ -182,12 +185,12 @@ func (s *EbookScanner) ScanFolders(ctx context.Context) error { // Check if file should be scanned based on library type if s.isScannableFile(path) { - ebookFiles++ - fmt.Printf("Found ebook file: %s\n", path) - if err := s.processEbookFile(ctx, path); err != nil { - fmt.Printf("Error processing ebook %s: %v\n", path, err) + mediaFiles++ + fmt.Printf("Found media file: %s\n", path) + if err := s.processMediaFile(ctx, path); err != nil { + fmt.Printf("Error processing media file %s: %v\n", path, err) } else { - fmt.Printf("Successfully processed ebook: %s\n", path) + fmt.Printf("Successfully processed media file: %s\n", path) } } @@ -198,21 +201,11 @@ func (s *EbookScanner) ScanFolders(ctx context.Context) error { } } - fmt.Printf("Scan completed: %d total files scanned, %d ebook files found\n", totalFiles, ebookFiles) + fmt.Printf("Scan completed: %d total files scanned, %d media files found\n", totalFiles, mediaFiles) return nil } -func (s *EbookScanner) isEbookFile(path string) bool { - ext := strings.ToLower(filepath.Ext(path)) - switch ext { - case ".epub", ".pdf", ".mobi", ".azw3", ".fb2", ".txt": - return true - default: - return false - } -} - -func (s *EbookScanner) isScannableFile(path string) bool { +func (s *MediaScanner) isScannableFile(path string) bool { ext := strings.ToLower(filepath.Ext(path)) // Find which folder this file belongs to @@ -248,8 +241,8 @@ func (s *EbookScanner) isScannableFile(path string) bool { } // extractFolderStructureMetadata extracts metadata from folder paths, prioritizing Calibre structure -func (s *EbookScanner) extractFolderStructureMetadata(path, rootFolder string) *EbookMetadata { - metadata := &EbookMetadata{} +func (s *MediaScanner) extractFolderStructureMetadata(path, rootFolder string) *MediaMetadata { + metadata := &MediaMetadata{} // Get the relative path from root folder relPath, err := filepath.Rel(rootFolder, path) @@ -308,8 +301,8 @@ func (s *EbookScanner) extractFolderStructureMetadata(path, rootFolder string) * return metadata } -func (s *EbookScanner) processEbookFile(ctx context.Context, path string) error { - fmt.Printf("Processing ebook file: %s\n", path) +func (s *MediaScanner) processMediaFile(ctx context.Context, path string) error { + fmt.Printf("Processing media file: %s\n", path) // Get file info info, err := os.Stat(path) @@ -320,29 +313,29 @@ func (s *EbookScanner) processEbookFile(ctx context.Context, path string) error fmt.Printf("File info for %s: size=%d\n", path, info.Size()) - // Check if ebook already exists in database - existingEbook, err := s.getEbookByFilePath(ctx, path) + // Check if media item already exists in database + existingItem, err := s.getMediaItemByFilePath(ctx, path) if err == nil { - fmt.Printf("Ebook already exists in database: %s (size: %d vs %d)\n", path, existingEbook.FileSize.Int64, info.Size()) - // Ebook exists, check if file has changed (by size) - if existingEbook.FileSize.Int64 != info.Size() { - fmt.Printf("File size changed, updating ebook: %s\n", path) - return s.updateEbook(ctx, existingEbook.ID, path, info) + fmt.Printf("Media item already exists in database: %s (size: %d vs %d)\n", path, existingItem.FileSize.Int64, info.Size()) + // Media item exists, check if file has changed (by size) + if existingItem.FileSize.Int64 != info.Size() { + fmt.Printf("File size changed, updating media item: %s\n", path) + return s.updateMediaItem(ctx, existingItem.ID, path, info) } - fmt.Printf("Ebook already exists with same size, skipping: %s\n", path) + fmt.Printf("Media item already exists with same size, skipping: %s\n", path) return nil // Skip if already exists and size matches } else if err != pgx.ErrNoRows { - fmt.Printf("Database error checking ebook existence: %v\n", err) + fmt.Printf("Database error checking media item existence: %v\n", err) // Some other error occurred - return fmt.Errorf("failed to check if ebook exists: %v", err) + return fmt.Errorf("failed to check if media item exists: %v", err) } - fmt.Printf("Ebook does not exist in database, creating new entry: %s\n", path) + fmt.Printf("Media item does not exist in database, creating new entry: %s\n", path) // Extract metadata from file first metadata, err := s.extractMetadata(path) if err != nil { fmt.Printf("Warning: failed to extract metadata from %s: %v\n", path, err) - metadata = &EbookMetadata{} + metadata = &MediaMetadata{} } // Extract hash information (Phase 2) @@ -518,7 +511,7 @@ func (s *EbookScanner) processEbookFile(ctx context.Context, path string) error return err } -func (s *EbookScanner) extractMetadata(path string) (*EbookMetadata, error) { +func (s *MediaScanner) extractMetadata(path string) (*MediaMetadata, error) { ext := strings.ToLower(filepath.Ext(path)) switch ext { @@ -528,19 +521,19 @@ func (s *EbookScanner) extractMetadata(path string) (*EbookMetadata, error) { return s.extractPDFMetadata(path) default: // For other formats, return basic metadata - return &EbookMetadata{ + return &MediaMetadata{ Title: strings.TrimSuffix(filepath.Base(path), ext), }, nil } } -func (s *EbookScanner) extractEPUBMetadata(path string) (*EbookMetadata, error) { +func (s *MediaScanner) extractEPUBMetadata(path string) (*MediaMetadata, error) { book, err := epub.ReadBook(path) if err != nil { return nil, fmt.Errorf("failed to open EPUB: %v", err) } - metadata := &EbookMetadata{} + metadata := &MediaMetadata{} // Title if title, err := book.Title(); err == nil && title != "" { @@ -620,12 +613,12 @@ func (s *EbookScanner) extractEPUBMetadata(path string) (*EbookMetadata, error) return metadata, nil } -func (s *EbookScanner) extractPDFMetadata(path string) (*EbookMetadata, error) { +func (s *MediaScanner) extractPDFMetadata(path string) (*MediaMetadata, error) { // For now, return basic metadata since PDF extraction requires additional libraries // In a future enhancement, you could use libraries like github.com/ledongthuc/pdf filename := strings.TrimSuffix(filepath.Base(path), ".pdf") - return &EbookMetadata{ + return &MediaMetadata{ Title: filename, }, nil } @@ -941,16 +934,16 @@ func isImageFile(filename string) bool { return ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" } -func (s *EbookScanner) updateEbook(ctx context.Context, ebookID pgtype.UUID, filePath string, info os.FileInfo) error { +func (s *MediaScanner) updateMediaItem(ctx context.Context, mediaItemID pgtype.UUID, filePath string, info os.FileInfo) error { // Update disabled - scanner creates items but doesn't update return nil } -func (s *EbookScanner) getEbookByFilePath(ctx context.Context, filePath string) (database.MediaItems, error) { +func (s *MediaScanner) getMediaItemByFilePath(ctx context.Context, filePath string) (database.MediaItems, error) { return s.db.GetMediaItemByFilePath(ctx, filePath) } -func (s *EbookScanner) getMimeType(path string) string { +func (s *MediaScanner) getMimeType(path string) string { ext := strings.ToLower(filepath.Ext(path)) switch ext { case ".epub": @@ -978,7 +971,7 @@ func (s *EbookScanner) getMimeType(path string) string { } } -func (s *EbookScanner) WatchChanges(ctx context.Context) { +func (s *MediaScanner) WatchChanges(ctx context.Context) { go func() { for { select { @@ -1002,9 +995,9 @@ func (s *EbookScanner) WatchChanges(ctx context.Context) { // Handle file modifications and creations if (event.Has(fsnotify.Create) || event.Has(fsnotify.Write)) && s.isScannableFile(event.Name) { - fmt.Printf("New/modified ebook detected: %s\n", event.Name) - if err := s.processEbookFile(ctx, event.Name); err != nil { - fmt.Printf("Error processing modified ebook %s: %v\n", event.Name, err) + fmt.Printf("New/modified media file detected: %s\n", event.Name) + if err := s.processMediaFile(ctx, event.Name); err != nil { + fmt.Printf("Error processing modified media file %s: %v\n", event.Name, err) } } @@ -1020,7 +1013,7 @@ func (s *EbookScanner) WatchChanges(ctx context.Context) { }() } -func (s *EbookScanner) Close() error { +func (s *MediaScanner) Close() error { if s.watcher != nil { return s.watcher.Close() } @@ -1032,7 +1025,7 @@ func (s *EbookScanner) Close() error { // ============================================ // calculateFileSHA256 calculates SHA-256 hash using streaming to avoid loading entire file into memory -func (s *EbookScanner) calculateFileSHA256(filePath string) (string, error) { +func (s *MediaScanner) calculateFileSHA256(filePath string) (string, error) { file, err := os.Open(filePath) if err != nil { return "", fmt.Errorf("failed to open file: %v", err) @@ -1063,7 +1056,7 @@ type OPFMetadata struct { } // extractOPFIdentifiers extracts identifiers from EPUB OPF file -func (s *EbookScanner) extractOPFIdentifiers(epubPath string) (opfIdentifier, opfUUID string, confidence string, err error) { +func (s *MediaScanner) extractOPFIdentifiers(epubPath string) (opfIdentifier, opfUUID string, confidence string, err error) { book, err := epub.ReadBook(epubPath) if err != nil { return "", "", "", fmt.Errorf("failed to open EPUB: %v", err) @@ -1118,7 +1111,7 @@ func isValidUUID(idStr string) bool { } // extractISBNFromIdentifier extracts ISBN from identifier string -func (s *EbookScanner) extractISBNFromIdentifier(id string) string { +func (s *MediaScanner) extractISBNFromIdentifier(id string) string { id = strings.TrimSpace(id) // Remove "isbn:" prefix if present @@ -1138,7 +1131,7 @@ func (s *EbookScanner) extractISBNFromIdentifier(id string) string { } // determineHashConfidence determines confidence level based on available identifiers -func (s *EbookScanner) determineHashConfidence(uuid, identifier string) string { +func (s *MediaScanner) determineHashConfidence(uuid, identifier string) string { if uuid != "" && isValidUUID(uuid) { return "high" } @@ -1149,7 +1142,7 @@ func (s *EbookScanner) determineHashConfidence(uuid, identifier string) string { } // detectFormatType detects the format type based on file extension and content -func (s *EbookScanner) detectFormatType(filePath string) string { +func (s *MediaScanner) detectFormatType(filePath string) string { base := strings.ToLower(filepath.Base(filePath)) // Check for compound extensions first (like .kepub.epub) @@ -1182,7 +1175,7 @@ func (s *EbookScanner) detectFormatType(filePath string) string { } // extractHashInfo calculates hash and extracts OPF identifiers for a file -func (s *EbookScanner) extractHashInfo(filePath string) (*HashInfo, *FormatInfo, error) { +func (s *MediaScanner) extractHashInfo(filePath string) (*HashInfo, *FormatInfo, error) { // Calculate SHA-256 fileSHA256, err := s.calculateFileSHA256(filePath) if err != nil { diff --git a/internal/services/worker.go b/internal/services/worker.go index d4829c2..4f14222 100644 --- a/internal/services/worker.go +++ b/internal/services/worker.go @@ -170,7 +170,7 @@ func (w *Worker) processScanJob(job *Job) (interface{}, error) { return nil, fmt.Errorf("database queries required") } - scanner := NewEbookScanner(db) + scanner := NewMediaScanner(db) if err := scanner.SetFolders(folders); err != nil { return nil, err