168 lines
3.7 KiB
Go
168 lines
3.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/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,
|
|
}
|
|
}
|