mirror of
https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
synced 2025-10-28 16:45:32 +00:00
437 lines
13 KiB
Go
437 lines
13 KiB
Go
package viewmodels
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging"
|
|
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/pictures"
|
|
"github.com/Theodor-Springmann-Stiftung/kgpz_web/xmlmodels"
|
|
)
|
|
|
|
type PiecePageEntry struct {
|
|
PageNumber int
|
|
IssueYear int
|
|
IssueNumber int
|
|
ImagePath string // Full-quality image path (prefers WebP over JPEG)
|
|
PreviewPath string // Compressed WebP path for layout views
|
|
JpegPath string // JPEG path for download button
|
|
IsContinuation bool
|
|
IssueContext string // "1764 Nr. 37" for display
|
|
Available bool
|
|
OtherPieces []IndividualPieceByIssue // Other pieces on the same page
|
|
PartNumber int // Which part of the piece (1, 2, 3, etc.)
|
|
}
|
|
|
|
type PieceImages struct {
|
|
AllPages []IssuePage // Sequential list of all pages across issues
|
|
HasImages bool
|
|
}
|
|
|
|
type PieceVM struct {
|
|
xmlmodels.Piece
|
|
AllIssueRefs []xmlmodels.IssueRef // All issues containing this piece
|
|
AllPages []PiecePageEntry // Flattened chronological page list
|
|
ContinuousPages IndividualPiecesByPage // For template compatibility
|
|
Images PieceImages // All page images across issues
|
|
TotalPageCount int
|
|
Title string // Extracted piece title
|
|
MainCategory string // Primary category
|
|
IssueContexts []string // List of issue contexts for display
|
|
}
|
|
|
|
func NewPieceView(piece xmlmodels.Piece, lib *xmlmodels.Library, pics *pictures.PicturesProvider) (*PieceVM, error) {
|
|
pvm := &PieceVM{
|
|
Piece: piece,
|
|
AllIssueRefs: piece.IssueRefs,
|
|
AllPages: []PiecePageEntry{},
|
|
IssueContexts: []string{},
|
|
}
|
|
|
|
// DEBUG: Log piece details
|
|
logging.Debug(fmt.Sprintf("PieceView: Creating view for piece ID=%s", piece.Identifier.ID))
|
|
logging.Debug(fmt.Sprintf("PieceView: Piece has %d IssueRefs", len(piece.IssueRefs)))
|
|
for i, ref := range piece.IssueRefs {
|
|
logging.Debug(fmt.Sprintf("PieceView: IssueRef[%d]: Year=%d, Nr=%d, Von=%d, Bis=%d, Beilage=%d", i, ref.When.Year, ref.Nr, ref.Von, ref.Bis, ref.Beilage))
|
|
}
|
|
|
|
// Extract title from piece
|
|
if len(piece.Title) > 0 {
|
|
pvm.Title = piece.Title[0]
|
|
}
|
|
|
|
// Extract main category
|
|
if len(piece.CategoryRefs) > 0 {
|
|
pvm.MainCategory = piece.CategoryRefs[0].Ref
|
|
}
|
|
|
|
// Sort issue refs chronologically
|
|
sort.Slice(pvm.AllIssueRefs, func(i, j int) bool {
|
|
refA := pvm.AllIssueRefs[i]
|
|
refB := pvm.AllIssueRefs[j]
|
|
|
|
if refA.When.Year != refB.When.Year {
|
|
return refA.When.Year < refB.When.Year
|
|
}
|
|
return refA.Nr < refB.Nr
|
|
})
|
|
|
|
// Process each issue reference
|
|
for partIndex, issueRef := range pvm.AllIssueRefs {
|
|
issueContext := fmt.Sprintf("%d Nr. %d", issueRef.When.Year, issueRef.Nr)
|
|
pvm.IssueContexts = append(pvm.IssueContexts, issueContext)
|
|
|
|
// Add pages for this issue reference
|
|
// Handle case where Bis=0 (should be treated as single page at Von)
|
|
bis := issueRef.Bis
|
|
if bis == 0 {
|
|
bis = issueRef.Von
|
|
}
|
|
|
|
for pageNum := issueRef.Von; pageNum <= bis; pageNum++ {
|
|
pageEntry := PiecePageEntry{
|
|
PageNumber: pageNum,
|
|
IssueYear: issueRef.When.Year,
|
|
IssueNumber: issueRef.Nr,
|
|
IsContinuation: pageNum > issueRef.Von || partIndex > 0,
|
|
IssueContext: issueContext,
|
|
Available: true, // Will be updated when we load images
|
|
OtherPieces: []IndividualPieceByIssue{}, // Will be populated later
|
|
PartNumber: partIndex + 1, // Part number (1-based)
|
|
}
|
|
|
|
// Get actual image path, preview path, and JPEG path from provider
|
|
pageEntry.ImagePath, pageEntry.PreviewPath, pageEntry.JpegPath = getImagePathsFromProvider(pics, issueRef.When.Year, issueRef.Nr, pageNum, issueRef.Beilage > 0)
|
|
|
|
pvm.AllPages = append(pvm.AllPages, pageEntry)
|
|
}
|
|
}
|
|
|
|
pvm.TotalPageCount = len(pvm.AllPages)
|
|
|
|
// DEBUG: Log final counts
|
|
logging.Debug(fmt.Sprintf("PieceView: Final counts - %d issue contexts, %d total pages", len(pvm.IssueContexts), pvm.TotalPageCount))
|
|
logging.Debug(fmt.Sprintf("PieceView: Issue contexts: %v", pvm.IssueContexts))
|
|
|
|
// Load images and update availability
|
|
if err := pvm.loadImages(pics); err != nil {
|
|
return nil, fmt.Errorf("failed to load images: %w", err)
|
|
}
|
|
|
|
// Create template-compatible structure
|
|
if err := pvm.createContinuousPages(lib); err != nil {
|
|
return nil, fmt.Errorf("failed to create continuous pages: %w", err)
|
|
}
|
|
|
|
// Populate other pieces on each page
|
|
if err := pvm.populateOtherPieces(lib); err != nil {
|
|
return nil, fmt.Errorf("failed to populate other pieces: %w", err)
|
|
}
|
|
|
|
return pvm, nil
|
|
}
|
|
|
|
// loadImages loads and validates all page images for the piece
|
|
func (pvm *PieceVM) loadImages(pics *pictures.PicturesProvider) error {
|
|
issuePages := []IssuePage{}
|
|
hasAnyImages := false
|
|
|
|
for i, pageEntry := range pvm.AllPages {
|
|
// Create IssuePage for template compatibility
|
|
issuePage := IssuePage{
|
|
PageNumber: pageEntry.PageNumber,
|
|
ImagePath: pageEntry.ImagePath,
|
|
PreviewPath: pageEntry.PreviewPath,
|
|
JpegPath: pageEntry.JpegPath,
|
|
Available: true, // Assume available for now
|
|
PageIcon: "single", // Simplified icon for piece view
|
|
}
|
|
|
|
// Check if image actually exists using the provider
|
|
if _, exists := pics.GetByYearPage(pageEntry.IssueYear, pageEntry.PageNumber); exists {
|
|
hasAnyImages = true
|
|
} else {
|
|
issuePage.Available = false
|
|
pvm.AllPages[i].Available = false
|
|
}
|
|
|
|
issuePages = append(issuePages, issuePage)
|
|
}
|
|
|
|
pvm.Images = PieceImages{
|
|
AllPages: issuePages,
|
|
HasImages: hasAnyImages,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createContinuousPages creates the template-compatible IndividualPiecesByPage structure
|
|
func (pvm *PieceVM) createContinuousPages(lib *xmlmodels.Library) error {
|
|
individual := IndividualPiecesByPage{
|
|
Items: make(map[int][]IndividualPieceByIssue),
|
|
Pages: []int{},
|
|
}
|
|
|
|
// Create a virtual piece entry for each page
|
|
for _, pageEntry := range pvm.AllPages {
|
|
// Create IssueRef for this specific page
|
|
issueRef := xmlmodels.IssueRef{
|
|
Nr: pageEntry.IssueNumber,
|
|
Von: pageEntry.PageNumber,
|
|
Bis: pageEntry.PageNumber,
|
|
}
|
|
issueRef.When.Year = pageEntry.IssueYear
|
|
|
|
// Create PieceByIssue
|
|
pieceByIssue := PieceByIssue{
|
|
Piece: pvm.Piece,
|
|
Reference: issueRef,
|
|
IsContinuation: pageEntry.IsContinuation,
|
|
}
|
|
|
|
// Create IndividualPieceByIssue
|
|
individualPiece := IndividualPieceByIssue{
|
|
PieceByIssue: pieceByIssue,
|
|
IssueRefs: pvm.AllIssueRefs,
|
|
PageIcon: "single", // Simplified icon for piece view
|
|
}
|
|
|
|
// Add to the page map
|
|
if individual.Items[pageEntry.PageNumber] == nil {
|
|
individual.Items[pageEntry.PageNumber] = []IndividualPieceByIssue{}
|
|
individual.Pages = append(individual.Pages, pageEntry.PageNumber)
|
|
}
|
|
individual.Items[pageEntry.PageNumber] = append(individual.Items[pageEntry.PageNumber], individualPiece)
|
|
}
|
|
|
|
// Sort pages
|
|
sort.Ints(individual.Pages)
|
|
|
|
pvm.ContinuousPages = individual
|
|
return nil
|
|
}
|
|
|
|
// getImagePathsFromProvider gets image paths: primary (WebP preferred), preview (compressed), and JPEG, handling both regular and Beilage pages
|
|
func getImagePathsFromProvider(pics *pictures.PicturesProvider, year, issue, page int, isBeilage bool) (string, string, string) {
|
|
// Get image file from provider
|
|
imageFile, exists := pics.GetByYearIssuePage(year, issue, page, isBeilage)
|
|
if !exists {
|
|
// Fallback for regular pages
|
|
if !isBeilage {
|
|
fallbackPath := fmt.Sprintf("/static/pictures/%d/seite_%d.jpg", year, page)
|
|
return fallbackPath, fallbackPath, fallbackPath
|
|
}
|
|
// No file found for beilage
|
|
return "", "", ""
|
|
}
|
|
|
|
// Return paths from found image file
|
|
previewPath := imageFile.PreviewPath
|
|
if previewPath == "" {
|
|
previewPath = imageFile.Path // Fallback to original if no preview
|
|
}
|
|
jpegPath := imageFile.JpegPath
|
|
if jpegPath == "" {
|
|
jpegPath = imageFile.Path // Fallback to primary if no separate JPEG
|
|
}
|
|
return imageFile.Path, previewPath, jpegPath
|
|
}
|
|
|
|
// populateOtherPieces finds and populates other pieces that appear on the same pages as this piece
|
|
func (pvm *PieceVM) populateOtherPieces(lib *xmlmodels.Library) error {
|
|
logging.Debug(fmt.Sprintf("Starting populateOtherPieces for piece %s", pvm.Piece.Identifier.ID))
|
|
for i, pageEntry := range pvm.AllPages {
|
|
logging.Debug(fmt.Sprintf("Processing page %d from issue %d/%d", pageEntry.PageNumber, pageEntry.IssueYear, pageEntry.IssueNumber))
|
|
|
|
// Find the issue this page belongs to
|
|
var issue *xmlmodels.Issue
|
|
lib.Issues.Lock()
|
|
for _, iss := range lib.Issues.Array {
|
|
if iss.Datum.When.Year == pageEntry.IssueYear && iss.Number.No == pageEntry.IssueNumber {
|
|
issue = &iss
|
|
break
|
|
}
|
|
}
|
|
lib.Issues.Unlock()
|
|
|
|
if issue == nil {
|
|
logging.Debug(fmt.Sprintf("Issue not found for %d/%d", pageEntry.IssueYear, pageEntry.IssueNumber))
|
|
continue
|
|
}
|
|
|
|
// Get all pieces for this issue using the same approach as the ausgabe view
|
|
piecesForIssue, _, err := PiecesForIssue(lib, *issue)
|
|
if err != nil {
|
|
logging.Error(err, fmt.Sprintf("Error getting pieces for issue %d/%d", pageEntry.IssueYear, pageEntry.IssueNumber))
|
|
continue
|
|
}
|
|
logging.Debug(fmt.Sprintf("Found %d total pieces for issue %d/%d", len(piecesForIssue.Pages), pageEntry.IssueYear, pageEntry.IssueNumber))
|
|
logging.Debug(fmt.Sprintf("PiecesForIssue.Pages = %v", piecesForIssue.Pages))
|
|
|
|
// Create IndividualPiecesByPage using the same function as ausgabe view
|
|
individualPieces := CreateIndividualPagesWithMetadata(piecesForIssue, lib)
|
|
logging.Debug(fmt.Sprintf("CreateIndividualPagesWithMetadata created %d pages with pieces", len(individualPieces.Pages)))
|
|
logging.Debug(fmt.Sprintf("Pages with pieces: %v", individualPieces.Pages))
|
|
|
|
// DEBUG: Show what pages are available in the map
|
|
if pageEntry.PageNumber == 113 {
|
|
logging.Debug(fmt.Sprintf("Available pages in individualPieces.Items: %v", individualPieces.Pages))
|
|
for pageNum := range individualPieces.Items {
|
|
logging.Debug(fmt.Sprintf("Page %d has %d pieces", pageNum, len(individualPieces.Items[pageNum])))
|
|
}
|
|
}
|
|
|
|
// Get pieces that appear on this specific page
|
|
if individualPiecesOnPage, exists := individualPieces.Items[pageEntry.PageNumber]; exists {
|
|
logging.Debug(fmt.Sprintf("Found %d pieces on page %d", len(individualPiecesOnPage), pageEntry.PageNumber))
|
|
otherPieces := []IndividualPieceByIssue{}
|
|
|
|
for _, individualPiece := range individualPiecesOnPage {
|
|
// Skip the current piece itself using comprehensive comparison
|
|
if IsSamePiece(individualPiece.PieceByIssue.Piece, pvm.Piece) {
|
|
logging.Debug("Skipping current piece (comprehensive field match)")
|
|
continue
|
|
}
|
|
|
|
// Debug piece information
|
|
pieceTitle := "no title"
|
|
if len(individualPiece.PieceByIssue.Piece.Title) > 0 {
|
|
pieceTitle = individualPiece.PieceByIssue.Piece.Title[0]
|
|
} else if len(individualPiece.PieceByIssue.Piece.Incipit) > 0 {
|
|
pieceTitle = individualPiece.PieceByIssue.Piece.Incipit[0]
|
|
}
|
|
|
|
logging.Debug(fmt.Sprintf("Adding other piece title='%s'", pieceTitle))
|
|
otherPieces = append(otherPieces, individualPiece)
|
|
}
|
|
|
|
logging.Debug(fmt.Sprintf("Found %d other pieces on page %d", len(otherPieces), pageEntry.PageNumber))
|
|
pvm.AllPages[i].OtherPieces = otherPieces
|
|
} else {
|
|
logging.Debug(fmt.Sprintf("No pieces found on page %d", pageEntry.PageNumber))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsSamePiece compares two pieces using comprehensive field-by-field heuristics
|
|
// This is a universal method that can be used anywhere in the codebase
|
|
func IsSamePiece(piece1, piece2 xmlmodels.Piece) bool {
|
|
// 1. Compare titles (all variants)
|
|
if !equalStringSlices(piece1.Title, piece2.Title) {
|
|
return false
|
|
}
|
|
|
|
// 2. Compare incipits (all variants)
|
|
if !equalStringSlices(piece1.Incipit, piece2.Incipit) {
|
|
return false
|
|
}
|
|
|
|
// 3. Compare issue references (must have identical coverage)
|
|
if !equalIssueRefs(piece1.IssueRefs, piece2.IssueRefs) {
|
|
return false
|
|
}
|
|
|
|
// 4. Compare category references
|
|
if !equalCategoryRefs(piece1.CategoryRefs, piece2.CategoryRefs) {
|
|
return false
|
|
}
|
|
|
|
// 5. Compare agent references (authors, etc.)
|
|
if !equalAgentRefs(piece1.AgentRefs, piece2.AgentRefs) {
|
|
return false
|
|
}
|
|
|
|
// 6. Compare work references
|
|
if !equalWorkRefs(piece1.WorkRefs, piece2.WorkRefs) {
|
|
return false
|
|
}
|
|
|
|
// 7. Compare place references
|
|
if !equalPlaceRefs(piece1.PlaceRefs, piece2.PlaceRefs) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Helper functions for comparing slices and references
|
|
|
|
func equalStringSlices(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func equalIssueRefs(a, b []xmlmodels.IssueRef) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i].When.Year != b[i].When.Year ||
|
|
a[i].Nr != b[i].Nr ||
|
|
a[i].Von != b[i].Von ||
|
|
a[i].Bis != b[i].Bis {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func equalCategoryRefs(a, b []xmlmodels.CategoryRef) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i].Ref != b[i].Ref {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func equalAgentRefs(a, b []xmlmodels.AgentRef) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i].Ref != b[i].Ref || a[i].Category != b[i].Category {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func equalWorkRefs(a, b []xmlmodels.WorkRef) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i].Ref != b[i].Ref {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func equalPlaceRefs(a, b []xmlmodels.PlaceRef) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i].Ref != b[i].Ref {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|