284 lines
7.2 KiB
Go
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
|
|
}
|