Files
chronological/internal/storage/photos.go
2025-12-10 11:41:18 +01:00

168 lines
3.7 KiB
Go

package storage
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"luxick/chronological/internal/models"
)
// PhotoStore handles scanning and serving photos from the filesystem.
type PhotoStore struct {
rootFolder string
}
// NewPhotoStore creates a new photo store with the given root folder.
func NewPhotoStore(rootFolder string) *PhotoStore {
return &PhotoStore{
rootFolder: rootFolder,
}
}
// Supported image extensions
var supportedExtensions = map[string]bool{
".jpg": true,
".jpeg": true,
".png": true,
".gif": true,
".webp": true,
}
// Filename pattern: YYYY-MM-DD_description.ext
var filenamePattern = regexp.MustCompile(`^(\d{4})-(\d{2})-(\d{2}) (.+)\.(\w+)$`)
// LoadAllPhotos scans and loads all photos from the configured directory.
func (s *PhotoStore) LoadAllPhotos() ([]*models.Photo, error) {
if s.rootFolder == "" {
return nil, nil
}
var photos []*models.Photo
// Walk through year folders
entries, err := os.ReadDir(s.rootFolder)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to read photo root folder: %w", err)
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
yearFolder := entry.Name()
yearPath := filepath.Join(s.rootFolder, yearFolder)
yearPhotos, err := s.loadPhotosFromYear(yearFolder, yearPath)
if err != nil {
continue // Skip problematic folders
}
photos = append(photos, yearPhotos...)
}
return photos, nil
}
// LoadPhotosForMonth loads photos for a specific month.
func (s *PhotoStore) LoadPhotosForMonth(year int, month time.Month) ([]*models.Photo, error) {
allPhotos, err := s.LoadAllPhotos()
if err != nil {
return nil, err
}
var monthPhotos []*models.Photo
for _, photo := range allPhotos {
if photo.Date.Year() == year && photo.Date.Month() == month {
monthPhotos = append(monthPhotos, photo)
}
}
return monthPhotos, nil
}
// LoadPhotosForDate loads photos for a specific date.
func (s *PhotoStore) LoadPhotosForDate(date time.Time) ([]*models.Photo, error) {
allPhotos, err := s.LoadAllPhotos()
if err != nil {
return nil, err
}
targetDate := date.Truncate(24 * time.Hour)
var datePhotos []*models.Photo
for _, photo := range allPhotos {
if photo.Date.Truncate(24 * time.Hour).Equal(targetDate) {
datePhotos = append(datePhotos, photo)
}
}
return datePhotos, nil
}
// GetPhotoPath returns the full filesystem path for a photo.
func (s *PhotoStore) GetPhotoPath(year, filename string) string {
return filepath.Join(s.rootFolder, year, filename)
}
func (s *PhotoStore) loadPhotosFromYear(yearFolder, yearPath string) ([]*models.Photo, error) {
var photos []*models.Photo
entries, err := os.ReadDir(yearPath)
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
filename := entry.Name()
ext := strings.ToLower(filepath.Ext(filename))
if !supportedExtensions[ext] {
continue
}
photo := parsePhotoFilename(yearFolder, filename, filepath.Join(yearPath, filename))
if photo != nil {
photos = append(photos, photo)
}
}
return photos, nil
}
func parsePhotoFilename(yearFolder, filename, fullPath string) *models.Photo {
matches := filenamePattern.FindStringSubmatch(filename)
if matches == nil {
return nil
}
dateStr := fmt.Sprintf("%s-%s-%s", matches[1], matches[2], matches[3])
date, err := time.Parse("2006-01-02", dateStr)
if err != nil {
return nil
}
description := matches[4]
// Convert underscores and camelCase to spaces for display
description = strings.ReplaceAll(description, "_", " ")
return &models.Photo{
Date: date,
Year: yearFolder,
Filename: filename,
Description: description,
FullPath: fullPath,
}
}