mirror of
https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
synced 2025-10-29 17:15:31 +00:00
279 lines
7.4 KiB
Go
279 lines
7.4 KiB
Go
package pictures
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// ImageFile represents a single image file with its metadata
|
|
type ImageFile struct {
|
|
Year int
|
|
Issue int
|
|
Page int
|
|
IsBeilage bool
|
|
BeilageNo int
|
|
Filename string
|
|
Path string // Primary path (prefers WebP over JPEG)
|
|
PreviewPath string // Path to compressed WebP version for layout views
|
|
JpegPath string // Path to JPEG version (for download button)
|
|
}
|
|
|
|
// imageRegistry holds all image files organized by different keys for fast lookup
|
|
type imageRegistry struct {
|
|
Files []ImageFile
|
|
ByYearIssue map[string][]ImageFile // "year-issue" -> files
|
|
ByYearPage map[string]ImageFile // "year-page" -> file (only for non-beilage)
|
|
}
|
|
|
|
// PicturesProvider manages all newspaper picture images with thread-safe access
|
|
type PicturesProvider struct {
|
|
mu sync.RWMutex
|
|
registry *imageRegistry
|
|
}
|
|
|
|
// NewPicturesProvider creates a new PicturesProvider
|
|
func NewPicturesProvider() *PicturesProvider {
|
|
return &PicturesProvider{
|
|
registry: nil,
|
|
}
|
|
}
|
|
|
|
// Scan scans the pictures directory and builds the image registry
|
|
func (p *PicturesProvider) Scan(path string) error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
registry := &imageRegistry{
|
|
Files: make([]ImageFile, 0),
|
|
ByYearIssue: make(map[string][]ImageFile),
|
|
ByYearPage: make(map[string]ImageFile),
|
|
}
|
|
|
|
// Temporary map to collect all files by their base name (year-issue-page)
|
|
tempFiles := make(map[string]*ImageFile)
|
|
|
|
err := filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
filename := info.Name()
|
|
filenamelower := strings.ToLower(filename)
|
|
|
|
// Only process .jpg and .webp files (but skip preview files)
|
|
var nameWithoutExt string
|
|
var isWebP bool
|
|
|
|
if strings.HasSuffix(filenamelower, ".jpg") {
|
|
nameWithoutExt = strings.TrimSuffix(filename, ".jpg")
|
|
isWebP = false
|
|
} else if strings.HasSuffix(filenamelower, ".webp") && !strings.HasSuffix(filenamelower, "-preview.webp") {
|
|
nameWithoutExt = strings.TrimSuffix(filename, ".webp")
|
|
isWebP = true
|
|
} else {
|
|
return nil // Skip non-image files and preview files
|
|
}
|
|
|
|
parts := strings.Split(nameWithoutExt, "-")
|
|
|
|
// Need at least 3 parts: year-issue-page
|
|
if len(parts) != 3 {
|
|
return nil
|
|
}
|
|
|
|
// Parse year
|
|
year, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Check if second part ends with 'b' (beilage)
|
|
issueStr := strings.TrimSpace(parts[1])
|
|
isBeilage := strings.HasSuffix(issueStr, "b")
|
|
|
|
if isBeilage {
|
|
issueStr = strings.TrimSuffix(issueStr, "b")
|
|
}
|
|
|
|
// Parse issue number
|
|
issue, err := strconv.Atoi(issueStr)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Parse page number
|
|
page, err := strconv.Atoi(strings.TrimSpace(parts[2]))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Create unique key for this image (handles both regular and beilage)
|
|
var uniqueKey string
|
|
if isBeilage {
|
|
uniqueKey = fmt.Sprintf("%d-%db-%d", year, issue, page)
|
|
} else {
|
|
uniqueKey = fmt.Sprintf("%d-%d-%d", year, issue, page)
|
|
}
|
|
|
|
// Get or create the ImageFile entry
|
|
imageFile, exists := tempFiles[uniqueKey]
|
|
if !exists {
|
|
imageFile = &ImageFile{
|
|
Year: year,
|
|
Issue: issue,
|
|
Page: page,
|
|
IsBeilage: isBeilage,
|
|
BeilageNo: 1, // Default beilage number
|
|
}
|
|
tempFiles[uniqueKey] = imageFile
|
|
}
|
|
|
|
// Set paths based on file type
|
|
currentPath := fmt.Sprintf("/static/pictures/%s", filePath[len(path)+1:]) // Remove path prefix
|
|
if isWebP {
|
|
// WebP is the primary path for single page viewer
|
|
imageFile.Path = currentPath
|
|
imageFile.Filename = filename
|
|
} else {
|
|
// JPEG is the fallback path for download
|
|
imageFile.JpegPath = currentPath
|
|
// If no WebP path is set yet, use JPEG as primary
|
|
if imageFile.Path == "" {
|
|
imageFile.Path = currentPath
|
|
imageFile.Filename = filename
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Second pass: set PreviewPath for each ImageFile by checking for preview files
|
|
for _, imageFile := range tempFiles {
|
|
// Extract the base name from the filename to preserve original format
|
|
baseNameWithExt := imageFile.Filename
|
|
var baseName string
|
|
|
|
// Remove extension to get base name
|
|
if strings.HasSuffix(strings.ToLower(baseNameWithExt), ".webp") {
|
|
baseName = strings.TrimSuffix(baseNameWithExt, ".webp")
|
|
} else if strings.HasSuffix(strings.ToLower(baseNameWithExt), ".jpg") {
|
|
baseName = strings.TrimSuffix(baseNameWithExt, ".jpg")
|
|
} else {
|
|
baseName = baseNameWithExt
|
|
}
|
|
|
|
// Generate preview filename using the original base name format
|
|
previewFilename := baseName + "-preview.webp"
|
|
|
|
// Check if preview file exists
|
|
previewFullPath := filepath.Join(path, fmt.Sprintf("%d", imageFile.Year), previewFilename)
|
|
if _, err := os.Stat(previewFullPath); err == nil {
|
|
imageFile.PreviewPath = fmt.Sprintf("/static/pictures/%d/%s", imageFile.Year, previewFilename)
|
|
}
|
|
}
|
|
|
|
// Convert temp map to final registry structures
|
|
for _, imageFile := range tempFiles {
|
|
registry.Files = append(registry.Files, *imageFile)
|
|
|
|
yearIssueKey := fmt.Sprintf("%d-%d", imageFile.Year, imageFile.Issue)
|
|
registry.ByYearIssue[yearIssueKey] = append(registry.ByYearIssue[yearIssueKey], *imageFile)
|
|
|
|
if !imageFile.IsBeilage {
|
|
yearPageKey := fmt.Sprintf("%d-%d", imageFile.Year, imageFile.Page)
|
|
registry.ByYearPage[yearPageKey] = *imageFile
|
|
}
|
|
}
|
|
|
|
p.registry = registry
|
|
return nil
|
|
}
|
|
|
|
// GetByYearIssuePage returns an image file for a specific year, issue, and page
|
|
// For beilage pages, isBeilage should be true
|
|
func (p *PicturesProvider) GetByYearIssuePage(year, issue, page int, isBeilage bool) (*ImageFile, bool) {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
if p.registry == nil {
|
|
return nil, false
|
|
}
|
|
|
|
// For regular pages, use year-page lookup
|
|
if !isBeilage {
|
|
key := fmt.Sprintf("%d-%d", year, page)
|
|
if imageFile, exists := p.registry.ByYearPage[key]; exists {
|
|
return &imageFile, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// For beilage pages, search through all files for this year-issue
|
|
yearIssueKey := fmt.Sprintf("%d-%d", year, issue)
|
|
if issueFiles, exists := p.registry.ByYearIssue[yearIssueKey]; exists {
|
|
for _, file := range issueFiles {
|
|
if file.IsBeilage && file.Page == page {
|
|
return &file, true
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// GetByYearIssue returns all image files for a specific year and issue
|
|
func (p *PicturesProvider) GetByYearIssue(year, issue int) []ImageFile {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
if p.registry == nil {
|
|
return nil
|
|
}
|
|
|
|
yearIssueKey := fmt.Sprintf("%d-%d", year, issue)
|
|
if files, exists := p.registry.ByYearIssue[yearIssueKey]; exists {
|
|
// Return a copy to prevent external modification
|
|
result := make([]ImageFile, len(files))
|
|
copy(result, files)
|
|
return result
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetByYearPage returns an image file for a specific year and page (non-beilage only)
|
|
func (p *PicturesProvider) GetByYearPage(year, page int) (*ImageFile, bool) {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
if p.registry == nil {
|
|
return nil, false
|
|
}
|
|
|
|
key := fmt.Sprintf("%d-%d", year, page)
|
|
if imageFile, exists := p.registry.ByYearPage[key]; exists {
|
|
return &imageFile, true
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// HasImages returns true if the registry has been initialized and contains images
|
|
func (p *PicturesProvider) HasImages() bool {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
return p.registry != nil && len(p.registry.Files) > 0
|
|
} |