repo_description: An organizer for your porn, written in Go.  Documentation:  https://docs.stashapp.cc
repo_topics: go golang graphql hacktoberfest json media organize organizer porn video
code: package api
  
  import (
  	"context"
  	"errors"
  	"net/http"
  	"strconv"
  
  	"github.com/go-chi/chi/v5"
  
  	"github.com/stashapp/stash/internal/manager/config"
  	"github.com/stashapp/stash/internal/static"
  	"github.com/stashapp/stash/pkg/image"
  	"github.com/stashapp/stash/pkg/logger"
  	"github.com/stashapp/stash/pkg/models"
  	"github.com/stashapp/stash/pkg/utils"
  )
  
  type GalleryFinder interface {
  	models.GalleryGetter
  	FindByChecksum(ctx context.Context, checksum string) ([]*models.Gallery, error)
  }
  
  type GalleryImageFinder interface {
  	FindByGalleryIDIndex(ctx context.Context, galleryID int, index uint) (*models.Image, error)
  	image.Queryer
  	image.CoverQueryer
  }
  
  type galleryRoutes struct {
  	routes
  	imageRoutes   imageRoutes
  	galleryFinder GalleryFinder
  	imageFinder   GalleryImageFinder
  	fileGetter    models.FileGetter
  }
  
  func (rs galleryRoutes) Routes() chi.Router {
  	r := chi.NewRouter()
  
  	r.Route("/{galleryId}", func(r chi.Router) {
  		r.Use(rs.GalleryCtx)
  
  		r.Get("/cover", rs.Cover)
  		r.Get("/preview/{imageIndex}", rs.Preview)
  	})
  
  	return r
  }
  
  func (rs galleryRoutes) Cover(w http.ResponseWriter, r *http.Request) {
  	g := r.Context().Value(galleryKey).(*models.Gallery)
  
  	var i *models.Image
  	_ = rs.withReadTxn(r, func(ctx context.Context) error {
  		// Find cover image first
  		i, _ = image.FindGalleryCover(ctx, rs.imageFinder, g.ID, config.GetInstance().GetGalleryCoverRegex())
  		if i == nil {
  			return nil
  		}
  
  		// serveThumbnail needs files populated
  		if err := i.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
  			if !errors.Is(err, context.Canceled) {
  				logger.Errorf("error loading primary file for image %d: %v", i.ID, err)
  			}
  			// set image to nil so that it doesn't try to use the primary file
  			i = nil
  		}
  
  		return nil
  	})
  
  	if i == nil {
  		// fallback to default image
  		image := static.ReadAll(static.DefaultGalleryImage)
  		utils.ServeImage(w, r, image)
  		return
  	}
  
  	rs.imageRoutes.serveThumbnail(w, r, i, &g.UpdatedAt)
  }
  
  func (rs galleryRoutes) Preview(w http.ResponseWriter, r *http.Request) {
  	g := r.Context().Value(galleryKey).(*models.Gallery)
  	indexQueryParam := chi.URLParam(r, "imageIndex")
  	var i *models.Image
  
  	index, err := strconv.Atoi(indexQueryParam)
  	if err != nil || index < 0 {
  		http.Error(w, "bad index", 400)
  		return
  	}
  
  	_ = rs.withReadTxn(r, func(ctx context.Context) error {
  		qb := rs.imageFinder
  		i, _ = qb.FindByGalleryIDIndex(ctx, g.ID, uint(index))
  		if i == nil {
  			return nil
  		}
  		// TODO - handle errors?
  
  		// serveThumbnail needs files populated
  		if err := i.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
  			if !errors.Is(err, context.Canceled) {
  				logger.Errorf("error loading primary file for image %d: %v", i.ID, err)
  			}
  			// set image to nil so that it doesn't try to use the primary file
  			i = nil
  		}
  
  		return nil
  	})
  	if i == nil {
  		http.Error(w, http.StatusText(404), 404)
  		return
  	}
  
  	rs.imageRoutes.serveThumbnail(w, r, i, nil)
  }
  
  func (rs galleryRoutes) GalleryCtx(next http.Handler) http.Handler {
  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  		galleryIdentifierQueryParam := chi.URLParam(r, "galleryId")
  		galleryID, _ := strconv.Atoi(galleryIdentifierQueryParam)
  
  		var gallery *models.Gallery
  		_ = rs.withReadTxn(r, func(ctx context.Context) error {
  			qb := rs.galleryFinder
  			if galleryID == 0 {
  				galleries, _ := qb.FindByChecksum(ctx, galleryIdentifierQueryParam)
  				if len(galleries) > 0 {
  					gallery = galleries[0]
  				}
  			} else {
  				gallery, _ = qb.Find(ctx, galleryID)
  			}
  
  			if gallery != nil {
  				if err := gallery.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
  					if !errors.Is(err, context.Canceled) {
  						logger.Errorf("error loading primary file for gallery %d: %v", galleryID, err)
  					}
  					// set image to nil so that it doesn't try to use the primary file
  					gallery = nil
  				}
  			}
  
  			return nil
  		})
  		if gallery == nil {
  			http.Error(w, http.StatusText(404), 404)
  			return
  		}
  
  		ctx := context.WithValue(r.Context(), galleryKey, gallery)
  		next.ServeHTTP(w, r.WithContext(ctx))
  	})
  }
  