From fb7231248c8efb0133d31b6d0f387f0b7b2ce80c Mon Sep 17 00:00:00 2001 From: Simon Martens Date: Tue, 30 Sep 2025 10:15:32 +0200 Subject: [PATCH] BUGFIX: gitignore pics --- .gitignore | 2 +- providers/pictures/pictures.go | 279 +++++++++++++++++++++++++++++++++ 2 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 providers/pictures/pictures.go diff --git a/.gitignore b/.gitignore index dffcf06..596c85d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ cache/ cache_geo/ cache_gnd/ data_bilder/ -pictures/ +/pictures/ config.json out.log kgpz_web diff --git a/providers/pictures/pictures.go b/providers/pictures/pictures.go new file mode 100644 index 0000000..cea834c --- /dev/null +++ b/providers/pictures/pictures.go @@ -0,0 +1,279 @@ +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 +} \ No newline at end of file