Search/cache.go
partisan 7886a3e60f
Some checks failed
Run Integration Tests / test (push) Failing after 29s
fixed stupidity
2024-12-27 08:09:18 +01:00

284 lines
7.2 KiB
Go

package main
import (
"fmt"
"sync"
"time"
"github.com/shirou/gopsutil/mem"
)
// SearchResult is a generic interface for all types of search results.
type SearchResult interface{}
// Define various search result types implementing SearchResult interface
type TextSearchResult struct {
URL string
Header string
Description string
Source string
}
type ImageSearchResult struct {
ID string
Title string
Full string // Full-size image URL
Thumb string // Thumbnail image URL
ProxyFull string // Proxied full-size image URL
ProxyThumb string // Proxied thumbnail image URL (from cache)
Source string // Source webpage URL
Width int
Height int
}
type VideoResult struct {
Href string
Title string
Date string
Views string
Creator string
Publisher string
Image string
Duration string
}
type TorrentResult struct {
URL string
Seeders int
Leechers int
Magnet string
Views int
Size string
Title string
Error string
}
type ForumSearchResult struct {
URL string `json:"url"`
Header string `json:"header"`
Description string `json:"description"`
PublishedDate time.Time `json:"publishedDate"`
ImgSrc string `json:"imgSrc,omitempty"`
ThumbnailSrc string `json:"thumbnailSrc,omitempty"`
}
// GeocodeCachedItem represents a geocoding result stored in the cache.
type GeocodeCachedItem struct {
Latitude string
Longitude string
Found bool
StoredTime time.Time
}
// GeocodeCache is a thread-safe map for caching geocoding results.
type GeocodeCache struct {
mu sync.Mutex
results map[string]GeocodeCachedItem
expiration time.Duration
}
var geocodeCache *GeocodeCache
// CacheKey represents the key used to store search results in the cache.
type CacheKey struct {
Query string
Page int
Safe bool
Lang string
Type string
}
// CachedItem represents an item stored in the cache with an expiration time.
type CachedItem struct {
Results []SearchResult
StoredTime time.Time
}
// ResultsCache is a thread-safe map for caching search results by composite keys.
type ResultsCache struct {
mu sync.Mutex
results map[string]CachedItem
expiration time.Duration
}
var resultsCache *ResultsCache
// NewResultsCache creates a new ResultsCache with a specified expiration duration.
func NewResultsCache() *ResultsCache {
printDebug("Initializing results cache with expiration: %s and max usage: %d bytes", config.RamCache.Duration, config.RamCache.MaxUsageBytes)
return &ResultsCache{
results: make(map[string]CachedItem),
expiration: config.RamCache.Duration,
}
}
// NewGeocodeCache creates a new GeocodeCache with a specified expiration duration.
func NewGeocodeCache() *GeocodeCache {
printDebug("Initializing geocode cache with expiration: %s", config.RamCache.Duration)
return &GeocodeCache{
results: make(map[string]GeocodeCachedItem),
expiration: config.RamCache.Duration,
}
}
// Get retrieves the results for a given key from the cache.
func (rc *ResultsCache) Get(key CacheKey) ([]SearchResult, bool) {
rc.mu.Lock()
defer rc.mu.Unlock()
item, exists := rc.results[rc.keyToString(key)]
if !exists {
return nil, false
}
// Check if the item has expired
if time.Since(item.StoredTime) > config.RamCache.Duration {
delete(rc.results, rc.keyToString(key))
printDebug("Cache expired for key: %s", rc.keyToString(key))
return nil, false
}
return item.Results, true
}
// Set stores the results for a given key in the cache.
func (rc *ResultsCache) Set(key CacheKey, results []SearchResult) {
rc.mu.Lock()
defer rc.mu.Unlock()
if _, exists := rc.results[rc.keyToString(key)]; !exists {
rc.results[rc.keyToString(key)] = CachedItem{
Results: results,
StoredTime: time.Now(),
}
go rc.checkAndCleanCache()
}
}
// keyToString converts a CacheKey to a string representation.
func (rc *ResultsCache) keyToString(key CacheKey) string {
return fmt.Sprintf("%s|%d|%t|%s|%s", key.Query, key.Page, key.Safe, key.Lang, key.Type)
}
// checkAndCleanCache removes items if memory usage exceeds the limit.
func (rc *ResultsCache) checkAndCleanCache() {
if rc.currentMemoryUsage() > config.RamCache.MaxUsageBytes {
rc.cleanOldestItems()
}
}
// currentMemoryUsage calculates the current memory usage in bytes.
func (rc *ResultsCache) currentMemoryUsage() uint64 {
v, err := mem.VirtualMemory()
if err != nil {
printErr("Failed to get memory info: %v", err)
return 0
}
return v.Used // Used memory in bytes
}
// Get retrieves the geocoding result for a given query from the cache.
func (gc *GeocodeCache) Get(query string) (latitude, longitude string, found bool, exists bool) {
gc.mu.Lock()
defer gc.mu.Unlock()
item, exists := gc.results[query]
if !exists {
return "", "", false, false
}
// Check if the item has expired
if time.Since(item.StoredTime) > gc.expiration {
delete(gc.results, query)
printDebug("Geocode cache expired for query: %s", query)
return "", "", false, false
}
return item.Latitude, item.Longitude, item.Found, true
}
func (gc *GeocodeCache) Set(query, latitude, longitude string, found bool) {
gc.mu.Lock()
defer gc.mu.Unlock()
gc.results[query] = GeocodeCachedItem{
Latitude: latitude,
Longitude: longitude,
Found: found,
StoredTime: time.Now(),
}
}
func (rc *ResultsCache) cleanOldestItems() {
rc.mu.Lock()
defer rc.mu.Unlock()
for rc.currentMemoryUsage() > config.RamCache.MaxUsageBytes {
var oldestKey string
var oldestTime time.Time = time.Now()
for key, item := range rc.results {
if item.StoredTime.Before(oldestTime) {
oldestTime = item.StoredTime
oldestKey = key
}
}
if oldestKey != "" {
delete(rc.results, oldestKey)
printDebug("Removed oldest cache item: %s", oldestKey)
} else {
break
}
}
}
func convertToSearchResults(results interface{}) []SearchResult {
switch res := results.(type) {
case []TextSearchResult:
genericResults := make([]SearchResult, len(res))
for i, r := range res {
genericResults[i] = r
}
return genericResults
case []TorrentResult:
genericResults := make([]SearchResult, len(res))
for i, r := range res {
genericResults[i] = r
}
return genericResults
case []ImageSearchResult:
genericResults := make([]SearchResult, len(res))
for i, r := range res {
genericResults[i] = r
}
return genericResults
case []ForumSearchResult:
genericResults := make([]SearchResult, len(res))
for i, r := range res {
genericResults[i] = r
}
return genericResults
}
return nil
}
func convertToSpecificResults(results []SearchResult) ([]TextSearchResult, []TorrentResult, []ImageSearchResult, []ForumSearchResult) {
var textResults []TextSearchResult
var torrentResults []TorrentResult
var imageResults []ImageSearchResult
var forumResults []ForumSearchResult
for _, r := range results {
switch res := r.(type) {
case TextSearchResult:
textResults = append(textResults, res)
case TorrentResult:
torrentResults = append(torrentResults, res)
case ImageSearchResult:
imageResults = append(imageResults, res)
case ForumSearchResult:
forumResults = append(forumResults, res)
}
}
return textResults, torrentResults, imageResults, forumResults
}