package main import ( "fmt" "html/template" "log" "math/rand" "net/http" "sync" "time" ) var ( searchEngines []searchEngine searchEngineLock sync.Mutex ) type searchEngine struct { Name string Func func(string, string, string, int) ([]TextSearchResult, error) Weight int } func init() { searchEngines = []searchEngine{ {Name: "Google", Func: PerformGoogleTextSearch, Weight: 1}, {Name: "LibreX", Func: PerformLibreXTextSearch, Weight: 2}, // {Name: "DuckDuckGo", Func: PerformDuckDuckGoTextSearch, Weight: 3}, // DuckDuckGo timeouts too fast and search results are trash // {Name: "SearXNG", Func: PerformSearXNGTextSearch, Weight: 2}, // Uncomment when implemented } rand.Seed(time.Now().UnixNano()) } func HandleTextSearch(w http.ResponseWriter, query, safe, lang string, page int) { startTime := time.Now() cacheKey := CacheKey{Query: query, Page: page, Safe: safe == "true", Lang: lang, Type: "text"} combinedResults := getTextResultsFromCacheOrFetch(cacheKey, query, safe, lang, page) hasPrevPage := page > 1 hasNextPage := len(combinedResults) > 0 displayResults(w, combinedResults, query, lang, time.Since(startTime).Seconds(), page, hasPrevPage, hasNextPage) // Prefetch next and previous pages go prefetchPage(query, safe, lang, page+1) if hasPrevPage { go prefetchPage(query, safe, lang, page-1) } } func getTextResultsFromCacheOrFetch(cacheKey CacheKey, query, safe, lang string, page int) []TextSearchResult { cacheChan := make(chan []SearchResult) var combinedResults []TextSearchResult go func() { results, exists := resultsCache.Get(cacheKey) if exists { log.Println("Cache hit") cacheChan <- results } else { log.Println("Cache miss") cacheChan <- nil } }() select { case results := <-cacheChan: if results == nil { combinedResults = fetchTextResults(query, safe, lang, page) if len(combinedResults) > 0 { resultsCache.Set(cacheKey, convertToSearchResults(combinedResults)) } } else { textResults, _, _ := convertToSpecificResults(results) combinedResults = textResults } case <-time.After(2 * time.Second): log.Println("Cache check timeout") combinedResults = fetchTextResults(query, safe, lang, page) if len(combinedResults) > 0 { resultsCache.Set(cacheKey, convertToSearchResults(combinedResults)) } } return combinedResults } func prefetchPage(query, safe, lang string, page int) { cacheKey := CacheKey{Query: query, Page: page, Safe: safe == "true", Lang: lang, Type: "text"} if _, exists := resultsCache.Get(cacheKey); !exists { log.Printf("Page %d not cached, caching now...", page) pageResults := fetchTextResults(query, safe, lang, page) if len(pageResults) > 0 { resultsCache.Set(cacheKey, convertToSearchResults(pageResults)) } } else { log.Printf("Page %d already cached", page) } } func fetchTextResults(query, safe, lang string, page int) []TextSearchResult { var results []TextSearchResult var err error for attempts := 0; attempts < len(searchEngines); attempts++ { engine := selectSearchEngine() log.Printf("Using search engine: %s", engine.Name) results, err = engine.Func(query, safe, lang, page) if err != nil { log.Printf("Error performing search with %s: %v", engine.Name, err) continue } if len(results) > 0 { break } } return results } func selectSearchEngine() searchEngine { searchEngineLock.Lock() defer searchEngineLock.Unlock() totalWeight := 0 for _, engine := range searchEngines { totalWeight += engine.Weight } randValue := rand.Intn(totalWeight) for _, engine := range searchEngines { if randValue < engine.Weight { // Adjust weights for load balancing for i := range searchEngines { if searchEngines[i].Name == engine.Name { searchEngines[i].Weight = max(1, searchEngines[i].Weight-1) } else { searchEngines[i].Weight++ } } return engine } randValue -= engine.Weight } return searchEngines[0] // fallback to the first engine } func displayResults(w http.ResponseWriter, results []TextSearchResult, query, lang string, elapsed float64, page int, hasPrevPage, hasNextPage bool) { log.Printf("Displaying results for page %d", page) log.Printf("Total results: %d", len(results)) log.Printf("Has previous page: %t, Has next page: %t", hasPrevPage, hasNextPage) tmpl, err := template.New("text.html").Funcs(template.FuncMap{ "sub": func(a, b int) int { return a - b }, "add": func(a, b int) int { return a + b }, }).ParseFiles("templates/text.html") if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } data := struct { Results []TextSearchResult Query string Fetched string Page int HasPrevPage bool HasNextPage bool LanguageOptions []LanguageOption CurrentLang string }{ Results: results, Query: query, Fetched: fmt.Sprintf("%.2f seconds", elapsed), Page: page, HasPrevPage: hasPrevPage, HasNextPage: hasNextPage, LanguageOptions: languageOptions, CurrentLang: lang, } err = tmpl.Execute(w, data) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }