package main import ( "fmt" "log" "sync" "time" "github.com/shirou/gopsutil/mem" ) var ( resultsCache = NewResultsCache(6 * time.Hour) // Cache with 6-hour expiration maxMemoryUsage = 90.0 // Maximum memory usage in % ) // 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 { Thumbnail string Title string Media string Width int Height int Source string ThumbProxy string } 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"` } // 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 } // NewResultsCache creates a new ResultsCache with a specified expiration duration. func NewResultsCache(expiration time.Duration) *ResultsCache { return &ResultsCache{ results: make(map[string]CachedItem), expiration: expiration, } } // 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) > rc.expiration { delete(rc.results, 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) } func (rc *ResultsCache) checkAndCleanCache() { if rc.memoryUsage() > maxMemoryUsage { rc.cleanOldestItems() } } func (rc *ResultsCache) memoryUsage() float64 { v, err := mem.VirtualMemory() if err != nil { log.Printf("Failed to get memory info: %v", err) return 0 } return v.UsedPercent } func (rc *ResultsCache) cleanOldestItems() { rc.mu.Lock() defer rc.mu.Unlock() for rc.memoryUsage() > maxMemoryUsage { 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) log.Printf("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 } return nil } func convertToSpecificResults(results []SearchResult) ([]TextSearchResult, []TorrentResult, []ImageSearchResult) { var textResults []TextSearchResult var torrentResults []TorrentResult var imageResults []ImageSearchResult 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) } } return textResults, torrentResults, imageResults }