205 lines
5.1 KiB
Go
205 lines
5.1 KiB
Go
package useCases
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/contenttype"
|
|
"RenewCMS/internal/domain/entry"
|
|
"RenewCMS/internal/domain/image"
|
|
"cmp"
|
|
"errors"
|
|
"maps"
|
|
"slices"
|
|
)
|
|
|
|
type EntryMedia struct {
|
|
ID uint32
|
|
Missing bool
|
|
Filename string
|
|
Width *int
|
|
Height *int
|
|
Size int64
|
|
ThumbnailURL string
|
|
}
|
|
|
|
type mediaResolver struct {
|
|
images image.Repository
|
|
cache map[uint32]EntryMedia
|
|
}
|
|
|
|
func newMediaResolver(images image.Repository) *mediaResolver {
|
|
return &mediaResolver{images: images, cache: map[uint32]EntryMedia{}}
|
|
}
|
|
|
|
func (r *mediaResolver) resolve(owner contenttype.ContentType, record entry.Entry) (map[string]EntryMedia, error) {
|
|
resolved := map[string]EntryMedia{}
|
|
for _, ref := range contenttype.MediaRefs(owner.Fields, record.Data) {
|
|
media, err := r.lookup(ref.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resolved[ref.Field.Key] = media
|
|
}
|
|
return resolved, nil
|
|
}
|
|
|
|
func (r *mediaResolver) lookup(id uint32) (EntryMedia, error) {
|
|
if cached, found := r.cache[id]; found {
|
|
return cached, nil
|
|
}
|
|
found, err := r.images.Get(id)
|
|
if errors.Is(err, image.ErrImageNotFound) {
|
|
r.cache[id] = EntryMedia{ID: id, Missing: true}
|
|
return r.cache[id], nil
|
|
}
|
|
if err != nil {
|
|
return EntryMedia{}, err
|
|
}
|
|
media := EntryMedia{ID: found.ID, Filename: found.Filename, Width: knownDimension(found.Width), Height: knownDimension(found.Height), Size: found.Size, ThumbnailURL: found.Path}
|
|
r.cache[id] = media
|
|
return media, nil
|
|
}
|
|
|
|
func knownDimension(value int) *int {
|
|
if value <= 0 {
|
|
return nil
|
|
}
|
|
return &value
|
|
}
|
|
|
|
type mediaEntry struct {
|
|
owner contenttype.ContentType
|
|
record entry.Entry
|
|
}
|
|
|
|
func (m mediaEntry) keysPointingAt(ids map[uint32]bool) []string {
|
|
keys := []string{}
|
|
for _, ref := range contenttype.MediaRefs(m.owner.Fields, m.record.Data) {
|
|
if ids[ref.ID] {
|
|
keys = append(keys, ref.Field.Key)
|
|
}
|
|
}
|
|
return keys
|
|
}
|
|
|
|
func compareMediaEntries(a, b mediaEntry) int {
|
|
return cmp.Or(cmp.Compare(a.owner.Name, b.owner.Name), cmp.Compare(a.owner.ID, b.owner.ID), cmp.Compare(a.record.ID, b.record.ID))
|
|
}
|
|
|
|
type entryMediaIndex struct {
|
|
byImage map[uint32][]mediaEntry
|
|
}
|
|
|
|
func newEntryMediaIndex(found []mediaEntry) *entryMediaIndex {
|
|
index := &entryMediaIndex{byImage: map[uint32][]mediaEntry{}}
|
|
for _, candidate := range found {
|
|
distinct := map[uint32]bool{}
|
|
for _, ref := range contenttype.MediaRefs(candidate.owner.Fields, candidate.record.Data) {
|
|
distinct[ref.ID] = true
|
|
}
|
|
for id := range distinct {
|
|
index.byImage[id] = append(index.byImage[id], candidate)
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
func (i *entryMediaIndex) usageCounts() map[uint32]int {
|
|
counts := make(map[uint32]int, len(i.byImage))
|
|
for id, found := range i.byImage {
|
|
counts[id] = len(found)
|
|
}
|
|
return counts
|
|
}
|
|
|
|
func (i *entryMediaIndex) referencing(imageIDs []uint32) []mediaEntry {
|
|
type entryKey struct{ typeID, entryID uint32 }
|
|
seen := map[entryKey]bool{}
|
|
var found []mediaEntry
|
|
for _, id := range imageIDs {
|
|
for _, candidate := range i.byImage[id] {
|
|
key := entryKey{candidate.owner.ID, candidate.record.ID}
|
|
if !seen[key] {
|
|
seen[key] = true
|
|
found = append(found, candidate)
|
|
}
|
|
}
|
|
}
|
|
slices.SortFunc(found, compareMediaEntries)
|
|
return found
|
|
}
|
|
|
|
type entryMediaScanner struct {
|
|
contentTypes contenttype.Repository
|
|
entries entry.Repository
|
|
}
|
|
|
|
func newEntryMediaScanner(contentTypes contenttype.Repository, entries entry.Repository) *entryMediaScanner {
|
|
return &entryMediaScanner{contentTypes: contentTypes, entries: entries}
|
|
}
|
|
|
|
func (s *entryMediaScanner) index() (*entryMediaIndex, error) {
|
|
types, err := s.contentTypes.List()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var found []mediaEntry
|
|
for _, owner := range types {
|
|
if len(contenttype.MediaFields(owner.Fields)) == 0 {
|
|
continue
|
|
}
|
|
page, err := s.entries.ListPage(entry.Filters{ContentTypeID: owner.ID, Unpaged: true, Sort: entry.DefaultSort()})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, record := range page.Items {
|
|
found = append(found, mediaEntry{owner: owner, record: record})
|
|
}
|
|
}
|
|
return newEntryMediaIndex(found), nil
|
|
}
|
|
|
|
func (s *entryMediaScanner) usageCounts() (map[uint32]int, error) {
|
|
index, err := s.index()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return index.usageCounts(), nil
|
|
}
|
|
|
|
func (s *entryMediaScanner) referencing(imageID uint32) ([]mediaEntry, error) {
|
|
index, err := s.index()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return index.referencing([]uint32{imageID}), nil
|
|
}
|
|
|
|
func repointEntries(entries entry.Repository, candidates []mediaEntry, copyIDs []uint32, keptID uint32) (int, error) {
|
|
copies := map[uint32]bool{}
|
|
for _, id := range copyIDs {
|
|
copies[id] = true
|
|
}
|
|
updated := 0
|
|
for _, candidate := range candidates {
|
|
current, err := entries.Get(candidate.owner.ID, candidate.record.ID)
|
|
if errors.Is(err, entry.ErrEntryNotFound) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return updated, err
|
|
}
|
|
keys := mediaEntry{owner: candidate.owner, record: current}.keysPointingAt(copies)
|
|
if len(keys) == 0 {
|
|
continue
|
|
}
|
|
current.Data = maps.Clone(current.Data)
|
|
for _, key := range keys {
|
|
current.Data[key] = float64(keptID)
|
|
}
|
|
if _, err := entries.Update(current); err != nil {
|
|
return updated, err
|
|
}
|
|
updated++
|
|
}
|
|
return updated, nil
|
|
}
|