diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 0000000..945c9b4 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/src/core/main.cpp b/src/core/main.cpp index 4fe4ba1..924544f 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -19,9 +19,11 @@ #include #include // for ScreenInteractive #include +#include #include #include #include +#include #ifdef WITH_MPRIS #include @@ -40,6 +42,11 @@ #include "../ai/command_handler.hpp" #include "../ai/mcp_server.hpp" +// Performance tuning constants +namespace { + constexpr size_t MAX_RECENT_TRACKS = 10; +} + // Data in string to render in UI std::vector track_strings; std::vector home_track_strings; @@ -126,19 +133,41 @@ std::vector get_track_ascii_art(const Track &track) { } auto searchQuery(const std::string &query) { - track_data_saavn = saavn.fetch_tracks(query); + // Launch all API calls in parallel for better performance + std::vector>> futures; + futures.reserve(3); + + // Capture query by value to ensure thread safety + futures.push_back(std::async(std::launch::async, [query, &saavn]() { + return saavn.fetch_tracks(query); + })); + futures.push_back(std::async(std::launch::async, [query, &lastfm]() { + return lastfm.fetch_tracks(query); + })); + futures.push_back(std::async(std::launch::async, [query, &soundcloud]() { + return soundcloud.fetch_tracks(query); + })); + + // Collect results + track_data_saavn = futures[0].get(); track_data = track_data_saavn; - track_data_lastfm = lastfm.fetch_tracks(query); - track_data_soundcloud = soundcloud.fetch_tracks(query); - for (const auto &track : track_data_soundcloud) { - track_data.push_back(track); + track_data_lastfm = futures[1].get(); + track_data_soundcloud = futures[2].get(); + + // Pre-allocate space to avoid multiple reallocations + track_data.reserve(track_data.size() + track_data_soundcloud.size() + track_data_lastfm.size()); + + // Use move semantics for better performance + for (auto &track : track_data_soundcloud) { + track_data.push_back(std::move(track)); } - for (const auto &track : track_data_lastfm) { - track_data.push_back(track); + for (auto &track : track_data_lastfm) { + track_data.push_back(std::move(track)); } home_track_data = track_data; track_strings.clear(); + track_strings.reserve(track_data.size()); // Pre-allocate // Convert tracks to display strings for menu UI for (const auto &track : track_data) { @@ -153,29 +182,31 @@ auto fetch_recent() { recently_played_strings.clear(); track_data.clear(); - // Limit recently played to last 10 unique tracks + // Limit recently played to last MAX_RECENT_TRACKS unique tracks + // Use unordered_set for O(1) lookups instead of O(n) find_if std::vector unique_recently_played; + std::unordered_set seen_tracks; + unique_recently_played.reserve(MAX_RECENT_TRACKS); // Pre-allocate + for (const auto &track : recently_played) { + std::string track_str = track.to_string(); // Call once instead of in loop + // Check if this exact track is not already in unique list - auto it = std::find_if(unique_recently_played.begin(), - unique_recently_played.end(), - [&track](const Track &existing) { - return existing.to_string() == track.to_string(); - }); - - if (it == unique_recently_played.end()) { + if (seen_tracks.find(track_str) == seen_tracks.end()) { + seen_tracks.insert(track_str); unique_recently_played.push_back(track); } } - // Keep only the last 10 tracks - if (unique_recently_played.size() > 10) { + // Keep only the last MAX_RECENT_TRACKS tracks + if (unique_recently_played.size() > MAX_RECENT_TRACKS) { unique_recently_played = std::vector( - unique_recently_played.end() - 10, unique_recently_played.end()); + unique_recently_played.end() - MAX_RECENT_TRACKS, unique_recently_played.end()); } // Populate track_data and recently_played_strings track_data = unique_recently_played; + recently_played_strings.reserve(track_data.size()); // Pre-allocate for (const auto &recent : track_data) { recently_played_strings.push_back(recent.to_string()); } diff --git a/src/services/lastfm/lastfm.cpp b/src/services/lastfm/lastfm.cpp index e69754f..6f8730d 100644 --- a/src/services/lastfm/lastfm.cpp +++ b/src/services/lastfm/lastfm.cpp @@ -3,11 +3,59 @@ #include #include #include +#include #include "../../common/Track.h" #include +// Performance tuning constants +namespace { + constexpr size_t INITIAL_BUFFER_SIZE = 16384; + constexpr size_t EXPECTED_TRACK_COUNT = 9; +} + class Lastfm { + private: + // Reusable CURL handle for better performance + std::unique_ptr curl_handle{nullptr, curl_easy_cleanup}; + struct curl_slist *headers = nullptr; + + // Cached regex pattern for better performance + static const std::regex& get_track_pattern() { + static const std::regex pattern("chartlist-play-button[^>]*href=\"([^\"]+)\"[^>]*data-track-name=\"([^\"]+)\"[^>]*data-artist-name=\"([^\"]+)\""); + return pattern; + } + + void init_curl() { + if (!curl_handle) { + curl_handle.reset(curl_easy_init()); + if (curl_handle) { + // Set up headers once + headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml"); + headers = curl_slist_append(headers, "Accept-Language: en-US,en;q=0.9"); + + // Enable connection reuse and keep-alive + curl_easy_setopt(curl_handle.get(), CURLOPT_TCP_KEEPALIVE, 1L); + curl_easy_setopt(curl_handle.get(), CURLOPT_TCP_KEEPIDLE, 120L); + curl_easy_setopt(curl_handle.get(), CURLOPT_TCP_KEEPINTVL, 60L); + curl_easy_setopt(curl_handle.get(), CURLOPT_USERAGENT, "Mozilla/5.0"); + curl_easy_setopt(curl_handle.get(), CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl_handle.get(), CURLOPT_HTTPHEADER, headers); + } + } + } + public: + Lastfm() { + init_curl(); + } + + ~Lastfm() { + if (headers) { + curl_slist_free_all(headers); + headers = nullptr; + } + } + // Callback for CURL static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) { userp->append((char*)contents, size * nmemb); @@ -17,12 +65,13 @@ class Lastfm { // Function to extract tracks from HTML std::vector extractTracks(const std::string& html) { std::vector tracks; - std::regex pattern("chartlist-play-button[^>]*href=\"([^\"]+)\"[^>]*data-track-name=\"([^\"]+)\"[^>]*data-artist-name=\"([^\"]+)\""); - + tracks.reserve(EXPECTED_TRACK_COUNT); // Pre-allocate expected size + + const auto& pattern = get_track_pattern(); auto matches_begin = std::sregex_iterator(html.begin(), html.end(), pattern); auto matches_end = std::sregex_iterator(); - for (std::sregex_iterator i = matches_begin; i != matches_end && tracks.size() < 9; ++i) { + for (std::sregex_iterator i = matches_begin; i != matches_end && tracks.size() < EXPECTED_TRACK_COUNT; ++i) { std::smatch match = *i; Track track; track.url = match[1]; @@ -30,7 +79,7 @@ class Lastfm { track.artist = match[3]; track.id = track.name; track.source = "lastfm"; - tracks.push_back(track); + tracks.push_back(std::move(track)); } return tracks; @@ -38,33 +87,25 @@ class Lastfm { // Main function to fetch tracks std::vector fetch_tracks(const std::string& search_query) { - CURL* curl = curl_easy_init(); + init_curl(); std::string readBuffer; + readBuffer.reserve(INITIAL_BUFFER_SIZE); // Pre-allocate reasonable buffer size std::vector tracks; - if(curl) { - std::string url = "https://www.last.fm/search?q="; - url += curl_easy_escape(curl, search_query.c_str(), search_query.length()); - - struct curl_slist *headers = NULL; - headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml"); - headers = curl_slist_append(headers, "Accept-Language: en-US,en;q=0.9"); + if(curl_handle) { + char* escaped = curl_easy_escape(curl_handle.get(), search_query.c_str(), search_query.length()); + std::string url = "https://www.last.fm/search?q=" + std::string(escaped); + curl_free(escaped); - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0"); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl_handle.get(), CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl_handle.get(), CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl_handle.get(), CURLOPT_WRITEDATA, &readBuffer); - CURLcode res = curl_easy_perform(curl); + CURLcode res = curl_easy_perform(curl_handle.get()); if(res == CURLE_OK) { tracks = extractTracks(readBuffer); } - - curl_easy_cleanup(curl); - curl_slist_free_all(headers); } return tracks; diff --git a/src/services/saavn/saavn.cpp b/src/services/saavn/saavn.cpp index 23e3cb8..cff7a63 100644 --- a/src/services/saavn/saavn.cpp +++ b/src/services/saavn/saavn.cpp @@ -5,9 +5,50 @@ #include #include #include +#include + +// Performance tuning constants +namespace { + constexpr size_t INITIAL_BUFFER_SIZE = 8192; +} class Saavn { + private: + // Reusable CURL handle for better performance + std::unique_ptr curl_handle{nullptr, curl_easy_cleanup}; + struct curl_slist *headers = nullptr; + + void init_curl() { + if (!curl_handle) { + curl_handle.reset(curl_easy_init()); + if (curl_handle) { + // Set up headers once + headers = curl_slist_append(headers, "Accept: text/html,application/xhtml+xml,application/xml"); + headers = curl_slist_append(headers, "Accept-Language: en-US,en;q=0.9"); + + // Enable connection reuse and keep-alive + curl_easy_setopt(curl_handle.get(), CURLOPT_TCP_KEEPALIVE, 1L); + curl_easy_setopt(curl_handle.get(), CURLOPT_TCP_KEEPIDLE, 120L); + curl_easy_setopt(curl_handle.get(), CURLOPT_TCP_KEEPINTVL, 60L); + curl_easy_setopt(curl_handle.get(), CURLOPT_USERAGENT, "Mozilla/5.0"); + curl_easy_setopt(curl_handle.get(), CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl_handle.get(), CURLOPT_HTTPHEADER, headers); + } + } + } + public: + Saavn() { + init_curl(); + } + + ~Saavn() { + if (headers) { + curl_slist_free_all(headers); + headers = nullptr; + } + } + // Callback for CURL static size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::string *userp) { @@ -88,29 +129,20 @@ class Saavn { } std::string make_request(const std::string &url) { - CURL *curl = curl_easy_init(); + init_curl(); std::string readBuffer; + readBuffer.reserve(INITIAL_BUFFER_SIZE); // Pre-allocate reasonable buffer size - if (curl) { - struct curl_slist *headers = NULL; - headers = curl_slist_append( - headers, "Accept: text/html,application/xhtml+xml,application/xml"); - headers = curl_slist_append(headers, "Accept-Language: en-US,en;q=0.9"); - - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0"); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + if (curl_handle) { + curl_easy_setopt(curl_handle.get(), CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl_handle.get(), CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl_handle.get(), CURLOPT_WRITEDATA, &readBuffer); - CURLcode res = curl_easy_perform(curl); + CURLcode res = curl_easy_perform(curl_handle.get()); if (res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); readBuffer = "Error"; } - curl_easy_cleanup(curl); - curl_slist_free_all(headers); } else { readBuffer = "Error: Failed to initialize CURL."; } @@ -119,14 +151,16 @@ class Saavn { std::vector fetch_tracks(const std::string &search_query) { - CURL *curl = curl_easy_init(); - if (!curl) { + init_curl(); + if (!curl_handle) { throw std::runtime_error("CURL initialization failed."); } - std::string url = "https://www.jiosaavn.com/api.php?p=1&q=" + - std::string(curl_easy_escape(curl, search_query.c_str(), search_query.length())) + + + // Use the reusable handle for URL escaping + char* escaped = curl_easy_escape(curl_handle.get(), search_query.c_str(), search_query.length()); + std::string url = "https://www.jiosaavn.com/api.php?p=1&q=" + std::string(escaped) + "&_format=json&_marker=0&api_version=4&ctx=web6dot0&n=20&__call=search.getResults"; - curl_easy_cleanup(curl); + curl_free(escaped); std::string readBuffer = make_request(url); return extractTracks(readBuffer); @@ -139,21 +173,20 @@ class Saavn { } std::vector fetch_next_tracks(std::string id) { - CURL *curl = curl_easy_init(); - if (!curl) { + init_curl(); + if (!curl_handle) { throw std::runtime_error("CURL initialization failed."); } - std::string url = "https://www.jiosaavn.com/api.php?__call=reco.getreco&api_version=4&_format=json&_marker=0&ctx=web6dot0&pid=" + std::string(curl_easy_escape(curl, id.c_str(), id.length())); - // std::cout << url << std::endl; - curl_easy_cleanup(curl); + // Use the reusable handle for URL escaping + char* escaped = curl_easy_escape(curl_handle.get(), id.c_str(), id.length()); + std::string url = "https://www.jiosaavn.com/api.php?__call=reco.getreco&api_version=4&_format=json&_marker=0&ctx=web6dot0&pid=" + std::string(escaped); + curl_free(escaped); + std::string readBuffer = make_request(url); - // std::cout << readBuffer << readBuffer.size()<< std::endl; if(readBuffer.size() == 2) { - // std::cout << "in fi"; std::string url = "https://www.jiosaavn.com/api.php?__call=content.getTrending&api_version=4&_format=json&_marker=0&ctx=web6dot0&entity_type=song&entity_language=english"; - curl_easy_cleanup(curl); std::string readBuffer = make_request(url); return extractTrendingTracks(readBuffer); }