// music.go - Central music search handler package main import ( "fmt" "net/http" "sync" "time" ) type MusicSearchEngine struct { Name string Func func(query string, page int) ([]MusicResult, error) } var ( musicSearchEngines []MusicSearchEngine cacheMutex = &sync.Mutex{} ) var allMusicSearchEngines = []MusicSearchEngine{ {Name: "SoundCloud", Func: SearchSoundCloud}, {Name: "YouTube", Func: SearchMusicViaPiped}, {Name: "Bandcamp", Func: SearchBandcamp}, //{Name: "Spotify", Func: SearchSpotify}, } func initMusicEngines() { // Initialize with all engines if no specific config musicSearchEngines = allMusicSearchEngines } func handleMusicSearch(w http.ResponseWriter, settings UserSettings, query string, page int) { start := time.Now() cacheKey := CacheKey{ Query: query, Page: page, Type: "music", Lang: settings.SearchLanguage, Safe: settings.SafeSearch == "active", } var results []MusicResult if cached, found := resultsCache.Get(cacheKey); found { if musicResults, ok := convertCacheToMusicResults(cached); ok { results = musicResults } } if len(results) == 0 { results = fetchMusicResults(query, page) if len(results) > 0 { resultsCache.Set(cacheKey, convertMusicResultsToCache(results)) } } go prefetchMusicPages(query, page) elapsed := time.Since(start) // Calculate duration fetched := fmt.Sprintf("%.2f %s", elapsed.Seconds(), Translate("seconds")) data := map[string]interface{}{ "Results": results, "Query": query, "Page": page, "HasPrevPage": page > 1, "HasNextPage": len(results) >= 10, // Default page size "MusicServices": getMusicServiceNames(), "CurrentService": "all", // Default service "Theme": settings.Theme, "IsThemeDark": settings.IsThemeDark, "Trans": Translate, "Fetched": fetched, } renderTemplate(w, "music.html", data) } // Helper to get music service names func getMusicServiceNames() []string { names := make([]string, len(allMusicSearchEngines)) for i, engine := range allMusicSearchEngines { names[i] = engine.Name } return names } func convertMusicResultsToCache(results []MusicResult) []SearchResult { cacheResults := make([]SearchResult, len(results)) for i, r := range results { cacheResults[i] = r } return cacheResults } func convertCacheToMusicResults(cached []SearchResult) ([]MusicResult, bool) { results := make([]MusicResult, 0, len(cached)) for _, item := range cached { if musicResult, ok := item.(MusicResult); ok { results = append(results, musicResult) } else { return nil, false } } return results, true } func fetchMusicResults(query string, page int) []MusicResult { var results []MusicResult resultsChan := make(chan []MusicResult, len(musicSearchEngines)) var wg sync.WaitGroup for _, engine := range musicSearchEngines { wg.Add(1) go func(e MusicSearchEngine) { defer wg.Done() res, err := e.Func(query, page) if err == nil && len(res) > 0 { resultsChan <- res } }(engine) } go func() { wg.Wait() close(resultsChan) }() for res := range resultsChan { results = append(results, res...) if len(results) >= 50 { // Default max results break } } return deduplicateResults(results) } func prefetchMusicPages(query string, currentPage int) { for _, page := range []int{currentPage - 1, currentPage + 1} { if page < 1 { continue } cacheKey := CacheKey{ Query: query, Page: page, Type: "music", } if _, found := resultsCache.Get(cacheKey); !found { go fetchMusicResults(query, page) } } } func deduplicateResults(results []MusicResult) []MusicResult { seen := make(map[string]bool) var unique []MusicResult for _, res := range results { if !seen[res.URL] { seen[res.URL] = true unique = append(unique, res) } } return unique } // func generatePlayerHTML(result MusicResult) template.HTML { // if result.IframeSrc != "" { // return template.HTML(fmt.Sprintf( // ``, // result.IframeSrc, // )) // } // return template.HTML("") // }