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() { for 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 }