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
This commit is contained in:
@@ -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 {
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user