Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions _codeql_detected_source_root
67 changes: 49 additions & 18 deletions src/core/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
#include <ftxui/component/event.hpp>
#include <ftxui/component/screen_interactive.hpp> // for ScreenInteractive
#include <ftxui/screen/color.hpp>
#include <future>
#include <iostream>
#include <map>
#include <sched.h>
#include <unordered_set>

#ifdef WITH_MPRIS
#include <sdbus-c++/IConnection.h>
Expand All @@ -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<std::string> track_strings;
std::vector<std::string> home_track_strings;
Expand Down Expand Up @@ -126,19 +133,41 @@ std::vector<std::string> 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<std::future<std::vector<Track>>> 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) {
Expand All @@ -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<Track> unique_recently_played;
std::unordered_set<std::string> 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<Track>(
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());
}
Expand Down
85 changes: 63 additions & 22 deletions src/services/lastfm/lastfm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,59 @@
#include <string>
#include <vector>
#include <regex>
#include <memory>
#include "../../common/Track.h"
#include <mpv/client.h>

// 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, decltype(&curl_easy_cleanup)> 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);
Expand All @@ -17,54 +65,47 @@ class Lastfm {
// Function to extract tracks from HTML
std::vector<Track> extractTracks(const std::string& html) {
std::vector<Track> 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];
track.name = match[2];
track.artist = match[3];
track.id = track.name;
track.source = "lastfm";
tracks.push_back(track);
tracks.push_back(std::move(track));
}

return tracks;
}

// Main function to fetch tracks
std::vector<Track> 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<Track> 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;
Expand Down
91 changes: 62 additions & 29 deletions src/services/saavn/saavn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,50 @@
#include <rapidjson/document.h>
#include <string>
#include <vector>
#include <memory>

// Performance tuning constants
namespace {
constexpr size_t INITIAL_BUFFER_SIZE = 8192;
}

class Saavn {
private:
// Reusable CURL handle for better performance
std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> 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) {
Expand Down Expand Up @@ -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.";
}
Expand All @@ -119,14 +151,16 @@ class Saavn {


std::vector<Track> 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);
Expand All @@ -139,21 +173,20 @@ class Saavn {
}

std::vector<Track> 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);
}
Expand Down