2022-04-04 14:21:43 +02:00
|
|
|
package entity
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
"github.com/photoprism/photoprism/pkg/clean"
|
2022-10-13 22:11:02 +02:00
|
|
|
"github.com/photoprism/photoprism/pkg/list"
|
2022-04-04 14:21:43 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Strings is a simple string map that should not be accessed by multiple goroutines.
|
|
|
|
type Strings map[string]string
|
|
|
|
|
2022-10-13 22:11:02 +02:00
|
|
|
type MultiStrings map[string][]string
|
|
|
|
|
2022-04-04 14:21:43 +02:00
|
|
|
// StringMap is a string (reverse) lookup map that can be accessed by multiple goroutines.
|
|
|
|
type StringMap struct {
|
|
|
|
sync.RWMutex
|
|
|
|
m Strings
|
2022-10-13 22:11:02 +02:00
|
|
|
r MultiStrings
|
2022-04-04 14:21:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewStringMap creates a new string (reverse) lookup map.
|
|
|
|
func NewStringMap(s Strings) *StringMap {
|
|
|
|
if s == nil {
|
2022-10-13 22:11:02 +02:00
|
|
|
return &StringMap{m: make(Strings, 64), r: make(MultiStrings, 64)}
|
2022-04-04 14:21:43 +02:00
|
|
|
} else {
|
2022-10-13 22:11:02 +02:00
|
|
|
m := &StringMap{m: s, r: make(MultiStrings, len(s))}
|
2022-04-04 14:21:43 +02:00
|
|
|
|
|
|
|
for k := range s {
|
2022-10-13 22:11:02 +02:00
|
|
|
m.r[strings.ToLower(s[k])] = []string{k}
|
2022-04-04 14:21:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-13 22:11:02 +02:00
|
|
|
// Get returns the matching value or an empty string if it was not found.
|
2022-04-04 14:21:43 +02:00
|
|
|
func (s *StringMap) Get(key string) string {
|
|
|
|
if key == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
s.RLock()
|
|
|
|
defer s.RUnlock()
|
|
|
|
|
|
|
|
return s.m[key]
|
|
|
|
}
|
|
|
|
|
2022-10-13 22:11:02 +02:00
|
|
|
// Has checks whether a value has been set for the specified key.
|
|
|
|
func (s *StringMap) Has(key string) bool {
|
|
|
|
if key == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
s.RLock()
|
|
|
|
defer s.RUnlock()
|
|
|
|
|
|
|
|
_, ok := s.m[key]
|
|
|
|
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// Missing checks if the key is unknown.
|
|
|
|
func (s *StringMap) Missing(key string) bool {
|
|
|
|
return !s.Has(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Key returns the last added key that matches the specified value.
|
2022-04-04 14:21:43 +02:00
|
|
|
func (s *StringMap) Key(val string) string {
|
2022-10-13 22:11:02 +02:00
|
|
|
keys := s.Keys(val)
|
|
|
|
|
|
|
|
if l := len(keys); l > 0 {
|
|
|
|
return keys[l-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keys returns all keys that match the specified value.
|
|
|
|
func (s *StringMap) Keys(val string) []string {
|
2022-04-04 14:21:43 +02:00
|
|
|
if val == "" {
|
2022-10-13 22:11:02 +02:00
|
|
|
return []string{}
|
2022-04-04 14:21:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
s.RLock()
|
|
|
|
defer s.RUnlock()
|
|
|
|
|
|
|
|
return s.r[strings.ToLower(val)]
|
|
|
|
}
|
|
|
|
|
2022-10-13 22:11:02 +02:00
|
|
|
// HasValue checks if the specified value exists for any key.
|
|
|
|
func (s *StringMap) HasValue(val string) bool {
|
|
|
|
if val == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
s.RLock()
|
|
|
|
defer s.RUnlock()
|
|
|
|
|
|
|
|
_, ok := s.r[strings.ToLower(val)]
|
|
|
|
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2022-04-04 14:21:43 +02:00
|
|
|
// Log returns a string sanitized for logging and using the key as fallback value.
|
|
|
|
func (s *StringMap) Log(key string) (val string) {
|
2022-04-13 01:59:32 +02:00
|
|
|
if key == "" {
|
|
|
|
return "<unknown>"
|
|
|
|
}
|
|
|
|
|
2022-04-04 14:21:43 +02:00
|
|
|
if val = s.Get(key); val != "" {
|
2022-04-15 09:42:07 +02:00
|
|
|
return clean.Log(val)
|
2022-04-04 14:21:43 +02:00
|
|
|
} else {
|
2022-04-15 09:42:07 +02:00
|
|
|
return clean.Log(key)
|
2022-04-04 14:21:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unchanged verifies if the key/value pair is unchanged.
|
|
|
|
func (s *StringMap) Unchanged(key string, val string) bool {
|
|
|
|
if key == "" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
s.RLock()
|
|
|
|
defer s.RUnlock()
|
|
|
|
|
2022-10-13 22:11:02 +02:00
|
|
|
return s.m[key] == val && list.Contains(s.r[strings.ToLower(val)], key)
|
2022-04-04 14:21:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set adds a string to the map.
|
|
|
|
func (s *StringMap) Set(key string, val string) {
|
|
|
|
if s.Unchanged(key, val) {
|
|
|
|
return
|
|
|
|
} else if val == "" {
|
|
|
|
s.Unset(key)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
|
|
|
|
|
|
|
s.m[key] = val
|
2022-10-13 22:11:02 +02:00
|
|
|
|
|
|
|
// Update reverse lookup map.
|
|
|
|
s.r[strings.ToLower(val)] = list.Add(s.r[strings.ToLower(val)], key)
|
2022-04-04 14:21:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unset removes a string from the map.
|
|
|
|
func (s *StringMap) Unset(key string) {
|
|
|
|
if key == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
|
|
|
|
2022-10-13 22:11:02 +02:00
|
|
|
// Update reverse lookup map.
|
|
|
|
if v := strings.ToLower(s.m[key]); v != "" {
|
|
|
|
if keys := list.Remove(s.r[v], key); len(keys) == 0 {
|
|
|
|
delete(s.r, v)
|
|
|
|
} else {
|
|
|
|
s.r[v] = keys
|
|
|
|
}
|
2022-04-04 14:21:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
delete(s.m, key)
|
|
|
|
}
|