mirror of
https://github.com/Goldenkrew3000/OSSP_OpenSource.git
synced 2025-12-19 00:04:44 +10:00
Adding base source
This commit is contained in:
20
src/build.sh
Executable file
20
src/build.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
egcc main.c \
|
||||||
|
configHandler.c \
|
||||||
|
libopensubsonic/httpclient.c \
|
||||||
|
external/cJSON.c \
|
||||||
|
external/md5.c \
|
||||||
|
libopensubsonic/utils.c \
|
||||||
|
libopensubsonic/logger.c \
|
||||||
|
libopensubsonic/crypto.c \
|
||||||
|
libopensubsonic/endpoint_ping.c \
|
||||||
|
libopensubsonic/endpoint_getAlbum.c \
|
||||||
|
libopensubsonic/endpoint_getSong.c \
|
||||||
|
libopensubsonic/endpoint_getPlaylists.c \
|
||||||
|
libopensubsonic/endpoint_getPlaylist.c \
|
||||||
|
libopensubsonic/endpoint_getArtists.c \
|
||||||
|
libopensubsonic/endpoint_getArtist.c \
|
||||||
|
libopensubsonic/endpoint_getLyricsBySongId.c \
|
||||||
|
libopensubsonic/endpoint_getAlbumList.c \
|
||||||
|
libopensubsonic/endpoint_getStarred.c \
|
||||||
|
libopensubsonic/endpoint_scrobble.c \
|
||||||
|
-o main -I/usr/local/include -L/usr/local/lib -lcurl
|
||||||
77
src/config.json
Normal file
77
src/config.json
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"opensubsonic_server": {
|
||||||
|
"protocol": "https",
|
||||||
|
"server": "server.com",
|
||||||
|
"username": "",
|
||||||
|
"password": ""
|
||||||
|
},
|
||||||
|
"scrobbler": {
|
||||||
|
"listenbrainz_enable": true,
|
||||||
|
"listenbrainz_token": "",
|
||||||
|
"lastfm_enable": true,
|
||||||
|
"lastfm_username": "",
|
||||||
|
"lastfm_password": "",
|
||||||
|
"lastfm_api_key": "",
|
||||||
|
"lastfm_api_secret": "",
|
||||||
|
"lastfm_session_key": ""
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"equalizer": {
|
||||||
|
"enable": true,
|
||||||
|
"followPitch": true,
|
||||||
|
"graph": [
|
||||||
|
{
|
||||||
|
"bandwidth": 0.5,
|
||||||
|
"frequency": 22,
|
||||||
|
"gain": 17,
|
||||||
|
"bypass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bandwidth": 0.5,
|
||||||
|
"frequency": 28,
|
||||||
|
"gain": 17,
|
||||||
|
"bypass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bandwidth": 0.5,
|
||||||
|
"frequency": 35,
|
||||||
|
"gain": 8.6,
|
||||||
|
"bypass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bandwidth": 0.5,
|
||||||
|
"frequency": 43,
|
||||||
|
"gain": 5.4,
|
||||||
|
"bypass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bandwidth": 0.5,
|
||||||
|
"frequency": 53,
|
||||||
|
"gain": 12.3,
|
||||||
|
"bypass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bandwidth": 0.5,
|
||||||
|
"frequency": 66,
|
||||||
|
"gain": 5.1,
|
||||||
|
"bypass": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bandwidth": 0.5,
|
||||||
|
"frequency": 82,
|
||||||
|
"gain": 0,
|
||||||
|
"bypass": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pitch": {
|
||||||
|
"enable": true,
|
||||||
|
"cents": -270.0,
|
||||||
|
"rate": 1.0
|
||||||
|
},
|
||||||
|
"reverb": {
|
||||||
|
"enable": true,
|
||||||
|
"wetDryMix": 15.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
356
src/configHandler.c
Normal file
356
src/configHandler.c
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include "configHandler.h"
|
||||||
|
#include "external/cJSON.h"
|
||||||
|
#include "libopensubsonic/logger.h"
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#include "OSSP_Bridge.h"
|
||||||
|
#endif // defined(__APPLE__) && defined(__MACH__)
|
||||||
|
|
||||||
|
static int rc = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read a predefined config file into the configuration struct
|
||||||
|
* Returns 0 on success, 1 on failure
|
||||||
|
*/
|
||||||
|
int configHandler_Read(configHandler_config_t** configObj) {
|
||||||
|
// Allocate config object on heap
|
||||||
|
*configObj = malloc(sizeof(configHandler_config_t));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
(*configObj)->opensubsonic_protocol = NULL;
|
||||||
|
(*configObj)->opensubsonic_server = NULL;
|
||||||
|
(*configObj)->opensubsonic_username = NULL;
|
||||||
|
(*configObj)->opensubsonic_password = NULL;
|
||||||
|
(*configObj)->internal_opensubsonic_version = NULL;
|
||||||
|
(*configObj)->internal_opensubsonic_clientName = NULL;
|
||||||
|
(*configObj)->internal_opensubsonic_loginSalt = NULL;
|
||||||
|
(*configObj)->internal_opensubsonic_loginToken = NULL;
|
||||||
|
(*configObj)->listenbrainz_enable = false;
|
||||||
|
(*configObj)->listenbrainz_token = NULL;
|
||||||
|
(*configObj)->lastfm_enable = false;
|
||||||
|
(*configObj)->lastfm_username = NULL;
|
||||||
|
(*configObj)->lastfm_password = NULL;
|
||||||
|
(*configObj)->lastfm_api_key = NULL;
|
||||||
|
(*configObj)->lastfm_api_secret = NULL;
|
||||||
|
(*configObj)->lastfm_api_session_key = NULL;
|
||||||
|
(*configObj)->audio_equalizer_enable = false;
|
||||||
|
(*configObj)->audio_equalizer_followPitch = false;
|
||||||
|
(*configObj)->audio_equalizer_graphCount = 0;
|
||||||
|
(*configObj)->audio_equalizer_graph = NULL;
|
||||||
|
(*configObj)->audio_pitch_enable = false;
|
||||||
|
(*configObj)->audio_pitch_cents = 0.00;
|
||||||
|
(*configObj)->audio_pitch_rate = 0.00;
|
||||||
|
(*configObj)->audio_reverb_enable = false;
|
||||||
|
(*configObj)->audio_reverb_wetDryMix = 0.00;
|
||||||
|
|
||||||
|
// Set internal configuration values
|
||||||
|
(*configObj)->internal_opensubsonic_version = strdup("1.8.0");
|
||||||
|
(*configObj)->internal_opensubsonic_clientName = strdup("Hojuix_OSSP");
|
||||||
|
|
||||||
|
// Form the path to the config JSON
|
||||||
|
char* config_path = NULL;
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
// NOTE: This is a relatively hacky way of fetching the iOS container path without diving into the hell that is ObjC
|
||||||
|
char* root_path = getenv("HOME");
|
||||||
|
rc = asprintf(&config_path, "%s/Documents/config.json", root_path);
|
||||||
|
#if DEBUG
|
||||||
|
printf("iOS Container Path: %s\n", config_path);
|
||||||
|
#endif // DEBUG
|
||||||
|
#else
|
||||||
|
rc = asprintf(&config_path, "config.json");
|
||||||
|
#endif // defined(__APPLE__) && defined(__MACH__)
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed (Could not generate config path).");
|
||||||
|
free(config_path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read config file
|
||||||
|
FILE* config_fd = NULL;
|
||||||
|
char* config_buf = NULL;
|
||||||
|
long config_fsize = 0;
|
||||||
|
long config_fread_rc = 0; // Needs to be separate from 'rc' as fread() returns bytes read
|
||||||
|
|
||||||
|
// Check if the config file exists, and fetch it's size
|
||||||
|
struct stat st;
|
||||||
|
if (stat(config_path, &st) == 0) {
|
||||||
|
config_fsize = st.st_size;
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "stat() failed (Config file does not exist).");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually open and read in the contents of the config file
|
||||||
|
config_fd = fopen(config_path, "rb");
|
||||||
|
if (config_fd == NULL) {
|
||||||
|
logger_log_error(__func__, "fopen() failed (Could not open config file).");
|
||||||
|
free(config_path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
free(config_path);
|
||||||
|
|
||||||
|
config_buf = (char*)malloc(config_fsize + 1);
|
||||||
|
if (config_buf == NULL) {
|
||||||
|
logger_log_error(__func__, "malloc() failed (Could not allocate enough memory for the config file).");
|
||||||
|
fclose(config_fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
config_fread_rc = fread(config_buf, 1, config_fsize, config_fd);
|
||||||
|
if (config_fread_rc != config_fsize) {
|
||||||
|
logger_log_error(__func__, "fread() failed (Could not read the config file).");
|
||||||
|
fclose(config_fd);
|
||||||
|
free(config_buf);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null terminate the buffer
|
||||||
|
config_buf[config_fsize] = '\0';
|
||||||
|
fclose(config_fd);
|
||||||
|
|
||||||
|
// Parse config JSON
|
||||||
|
cJSON* root = cJSON_Parse(config_buf);
|
||||||
|
free(config_buf);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "cJSON_Parse() failed (Could not parse the configuration JSON).");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from opensubsonic_server
|
||||||
|
// TODO - Use the new OSS_P*oj functions?
|
||||||
|
cJSON* opensubsonic_server_root = cJSON_GetObjectItemCaseSensitive(root, "opensubsonic_server");
|
||||||
|
if (opensubsonic_server_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - opensubsonic_server does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* opensubsonic_server_protocol = cJSON_GetObjectItemCaseSensitive(opensubsonic_server_root, "protocol");
|
||||||
|
if (cJSON_IsString(opensubsonic_server_protocol) && opensubsonic_server_protocol->valuestring != NULL) {
|
||||||
|
(*configObj)->opensubsonic_protocol = strdup(opensubsonic_server_protocol->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* opensubsonic_server_server = cJSON_GetObjectItemCaseSensitive(opensubsonic_server_root, "server");
|
||||||
|
if (cJSON_IsString(opensubsonic_server_server) && opensubsonic_server_server->valuestring != NULL) {
|
||||||
|
(*configObj)->opensubsonic_server = strdup(opensubsonic_server_server->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* opensubsonic_server_username = cJSON_GetObjectItemCaseSensitive(opensubsonic_server_root, "username");
|
||||||
|
if (cJSON_IsString(opensubsonic_server_username) && opensubsonic_server_username->valuestring != NULL) {
|
||||||
|
(*configObj)->opensubsonic_username = strdup(opensubsonic_server_username->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* opensubsonic_server_password = cJSON_GetObjectItemCaseSensitive(opensubsonic_server_root, "password");
|
||||||
|
if (cJSON_IsString(opensubsonic_server_password) && opensubsonic_server_password->valuestring != NULL) {
|
||||||
|
(*configObj)->opensubsonic_password = strdup(opensubsonic_server_password->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from scrobbler
|
||||||
|
cJSON* scrobbler_root = cJSON_GetObjectItemCaseSensitive(root, "scrobbler");
|
||||||
|
if (scrobbler_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - scrobbler does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* scrobbler_listenbrainz_enable = cJSON_GetObjectItemCaseSensitive(scrobbler_root, "listenbrainz_enable");
|
||||||
|
if (cJSON_IsBool(scrobbler_listenbrainz_enable)) {
|
||||||
|
if (cJSON_IsTrue(scrobbler_listenbrainz_enable)) {
|
||||||
|
(*configObj)->listenbrainz_enable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* scrobbler_listenbrainz_token = cJSON_GetObjectItemCaseSensitive(scrobbler_root, "listenbrainz_token");
|
||||||
|
if (cJSON_IsString(scrobbler_listenbrainz_token) && scrobbler_listenbrainz_token->valuestring != NULL) {
|
||||||
|
(*configObj)->listenbrainz_token = strdup(scrobbler_listenbrainz_token->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* scrobbler_lastfm_enable = cJSON_GetObjectItemCaseSensitive(scrobbler_root, "lastfm_enable");
|
||||||
|
if (cJSON_IsBool(scrobbler_lastfm_enable)) {
|
||||||
|
if (cJSON_IsTrue(scrobbler_lastfm_enable)) {
|
||||||
|
(*configObj)->lastfm_enable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* scrobbler_lastfm_username = cJSON_GetObjectItemCaseSensitive(scrobbler_root, "lastfm_username");
|
||||||
|
if (cJSON_IsString(scrobbler_lastfm_username) && scrobbler_lastfm_username->valuestring != NULL) {
|
||||||
|
(*configObj)->lastfm_username = strdup(scrobbler_lastfm_username->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* scrobbler_lastfm_password = cJSON_GetObjectItemCaseSensitive(scrobbler_root, "lastfm_password");
|
||||||
|
if (cJSON_IsString(scrobbler_lastfm_password) && scrobbler_lastfm_password->valuestring != NULL) {
|
||||||
|
(*configObj)->lastfm_password = strdup(scrobbler_lastfm_password->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* scrobbler_lastfm_api_key = cJSON_GetObjectItemCaseSensitive(scrobbler_root, "lastfm_api_key");
|
||||||
|
if (cJSON_IsString(scrobbler_lastfm_api_key) && scrobbler_lastfm_api_key->valuestring != NULL) {
|
||||||
|
(*configObj)->lastfm_api_key = strdup(scrobbler_lastfm_api_key->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* scrobbler_lastfm_api_secret = cJSON_GetObjectItemCaseSensitive(scrobbler_root, "lastfm_api_secret");
|
||||||
|
if (cJSON_IsString(scrobbler_lastfm_api_secret) && scrobbler_lastfm_api_secret->valuestring != NULL) {
|
||||||
|
(*configObj)->lastfm_api_secret = strdup(scrobbler_lastfm_api_secret->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* scrobbler_lastfm_api_session_key = cJSON_GetObjectItemCaseSensitive(scrobbler_root, "lastfm_session_key");
|
||||||
|
if (cJSON_IsString(scrobbler_lastfm_api_session_key) && scrobbler_lastfm_api_session_key->valuestring != NULL) {
|
||||||
|
(*configObj)->lastfm_api_session_key = strdup(scrobbler_lastfm_api_session_key->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from audio
|
||||||
|
cJSON* audio_root = cJSON_GetObjectItemCaseSensitive(root, "audio");
|
||||||
|
if (audio_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - audio does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from equalizer
|
||||||
|
cJSON* equalizer_root = cJSON_GetObjectItemCaseSensitive(audio_root, "equalizer");
|
||||||
|
if (equalizer_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - equalizer does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_equalizer_enable = cJSON_GetObjectItemCaseSensitive(equalizer_root, "enable");
|
||||||
|
if (cJSON_IsBool(audio_equalizer_enable)) {
|
||||||
|
if (cJSON_IsTrue(audio_equalizer_enable)) {
|
||||||
|
(*configObj)->audio_equalizer_enable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_equalizer_followPitch = cJSON_GetObjectItemCaseSensitive(equalizer_root, "followPitch");
|
||||||
|
if (cJSON_IsBool(audio_equalizer_followPitch)) {
|
||||||
|
if (cJSON_IsTrue(audio_equalizer_followPitch)) {
|
||||||
|
(*configObj)->audio_equalizer_followPitch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the equalizer graph array, and allocate memory for it
|
||||||
|
cJSON* equalizer_graph_array = cJSON_GetObjectItemCaseSensitive(equalizer_root, "graph");
|
||||||
|
if (equalizer_graph_array == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - graph does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*configObj)->audio_equalizer_graphCount = cJSON_GetArraySize(equalizer_graph_array);
|
||||||
|
(*configObj)->audio_equalizer_graph = (configHandler_eqGraph_t*)malloc((*configObj)->audio_equalizer_graphCount * sizeof(configHandler_eqGraph_t));
|
||||||
|
if ((*configObj)->audio_equalizer_graph == NULL) {
|
||||||
|
logger_log_error(__func__, "malloc() failed.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize more struct variables
|
||||||
|
for (size_t i = 0; i < (*configObj)->audio_equalizer_graphCount; i++) {
|
||||||
|
(*configObj)->audio_equalizer_graph[i].bandwidth = 0.00;
|
||||||
|
(*configObj)->audio_equalizer_graph[i].frequency = 0;
|
||||||
|
(*configObj)->audio_equalizer_graph[i].gain = 0.00;
|
||||||
|
(*configObj)->audio_equalizer_graph[i].bypass = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < (*configObj)->audio_equalizer_graphCount; i++) {
|
||||||
|
cJSON* array_equalizer_graph_root = cJSON_GetArrayItem(equalizer_graph_array, (int)i);
|
||||||
|
if (array_equalizer_graph_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - Could not fetch graph index %d.", i);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_equalizer_graph_bandwidth = cJSON_GetObjectItemCaseSensitive(array_equalizer_graph_root, "bandwidth");
|
||||||
|
if (cJSON_IsNumber(audio_equalizer_graph_bandwidth)) {
|
||||||
|
(*configObj)->audio_equalizer_graph[i].bandwidth = audio_equalizer_graph_bandwidth->valuedouble;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_equalizer_graph_frequency = cJSON_GetObjectItemCaseSensitive(array_equalizer_graph_root, "frequency");
|
||||||
|
if (cJSON_IsNumber(audio_equalizer_graph_frequency)) {
|
||||||
|
(*configObj)->audio_equalizer_graph[i].frequency = audio_equalizer_graph_frequency->valueint;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_equalizer_graph_gain = cJSON_GetObjectItemCaseSensitive(array_equalizer_graph_root, "gain");
|
||||||
|
if (cJSON_IsNumber(audio_equalizer_graph_gain)) {
|
||||||
|
(*configObj)->audio_equalizer_graph[i].gain = audio_equalizer_graph_gain->valuedouble;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_equalizer_graph_bypass = cJSON_GetObjectItemCaseSensitive(array_equalizer_graph_root, "bypass");
|
||||||
|
if (cJSON_IsBool(audio_equalizer_graph_bypass)) {
|
||||||
|
if (cJSON_IsTrue(audio_equalizer_graph_bypass)) {
|
||||||
|
(*configObj)->audio_equalizer_graph[i].bypass = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from pitch
|
||||||
|
cJSON* pitch_root = cJSON_GetObjectItemCaseSensitive(audio_root, "pitch");
|
||||||
|
if (pitch_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - pitch does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_pitch_enable = cJSON_GetObjectItemCaseSensitive(pitch_root, "enable");
|
||||||
|
if (cJSON_IsBool(audio_pitch_enable)) {
|
||||||
|
if (cJSON_IsTrue(audio_pitch_enable)) {
|
||||||
|
(*configObj)->audio_pitch_enable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_pitch_cents = cJSON_GetObjectItemCaseSensitive(pitch_root, "cents");
|
||||||
|
if (cJSON_IsNumber(audio_pitch_cents)) {
|
||||||
|
(*configObj)->audio_pitch_cents = audio_pitch_cents->valuedouble;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_pitch_rate = cJSON_GetObjectItemCaseSensitive(pitch_root, "rate");
|
||||||
|
if (cJSON_IsNumber(audio_pitch_rate)) {
|
||||||
|
(*configObj)->audio_pitch_rate = audio_pitch_rate->valuedouble;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from reverb
|
||||||
|
cJSON* reverb_root = cJSON_GetObjectItemCaseSensitive(audio_root, "reverb");
|
||||||
|
if (reverb_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - reverb does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_reverb_enable = cJSON_GetObjectItemCaseSensitive(reverb_root, "enable");
|
||||||
|
if (cJSON_IsBool(audio_reverb_enable)) {
|
||||||
|
if (cJSON_IsTrue(audio_reverb_enable)) {
|
||||||
|
(*configObj)->audio_reverb_enable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* audio_reverb_wetDryMix = cJSON_GetObjectItemCaseSensitive(reverb_root, "wetDryMix");
|
||||||
|
if (cJSON_IsNumber(audio_reverb_wetDryMix)) {
|
||||||
|
(*configObj)->audio_reverb_wetDryMix = audio_reverb_wetDryMix->valuedouble;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
logger_log_general(__func__, "Successfully read configuration file.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void configHandler_Free(configHandler_config_t** configObj) {
|
||||||
|
if ((*configObj)->opensubsonic_protocol != NULL) { free((*configObj)->opensubsonic_protocol); }
|
||||||
|
if ((*configObj)->opensubsonic_server != NULL) { free((*configObj)->opensubsonic_server); }
|
||||||
|
if ((*configObj)->opensubsonic_username != NULL) { free((*configObj)->opensubsonic_username); }
|
||||||
|
if ((*configObj)->opensubsonic_password != NULL) { free((*configObj)->opensubsonic_password); }
|
||||||
|
if ((*configObj)->internal_opensubsonic_version != NULL) { free((*configObj)->internal_opensubsonic_version); }
|
||||||
|
if ((*configObj)->internal_opensubsonic_clientName != NULL) { free((*configObj)->internal_opensubsonic_clientName); }
|
||||||
|
if ((*configObj)->internal_opensubsonic_loginSalt != NULL) { free((*configObj)->internal_opensubsonic_loginSalt); }
|
||||||
|
if ((*configObj)->internal_opensubsonic_loginToken != NULL) { free((*configObj)->internal_opensubsonic_loginToken); }
|
||||||
|
if ((*configObj)->listenbrainz_token != NULL) { free((*configObj)->listenbrainz_token); }
|
||||||
|
if ((*configObj)->lastfm_username != NULL) { free((*configObj)->lastfm_username); }
|
||||||
|
if ((*configObj)->lastfm_password != NULL) { free((*configObj)->lastfm_password); }
|
||||||
|
if ((*configObj)->lastfm_api_key != NULL) { free((*configObj)->lastfm_api_key); }
|
||||||
|
if ((*configObj)->lastfm_api_secret != NULL) { free((*configObj)->lastfm_api_secret); }
|
||||||
|
if ((*configObj)->lastfm_api_session_key != NULL) { free((*configObj)->lastfm_api_session_key); }
|
||||||
|
if ((*configObj)->audio_equalizer_graph != NULL) { free((*configObj)->audio_equalizer_graph); }
|
||||||
|
if (*configObj != NULL) { free(*configObj); }
|
||||||
|
}
|
||||||
48
src/configHandler.h
Normal file
48
src/configHandler.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#ifndef _CONFIG_HANDLER_H
|
||||||
|
#define _CONFIG_HANDLER_H
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
double bandwidth;
|
||||||
|
int frequency; // Frequency in Hz
|
||||||
|
double gain; // Gain in db
|
||||||
|
bool bypass; // Ignore entry
|
||||||
|
} configHandler_eqGraph_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// Opensubsonic Settings
|
||||||
|
char* opensubsonic_protocol; // http / https
|
||||||
|
char* opensubsonic_server; // address:port
|
||||||
|
char* opensubsonic_username;
|
||||||
|
char* opensubsonic_password;
|
||||||
|
char* internal_opensubsonic_version; // (Internal) Opensubsonic API Version
|
||||||
|
char* internal_opensubsonic_clientName; // (Internal) Opensubsonic Client Name
|
||||||
|
char* internal_opensubsonic_loginSalt; // (Internal) Opensubsonic Login Salt
|
||||||
|
char* internal_opensubsonic_loginToken; // (Internal) Opensubsonic Login Token
|
||||||
|
|
||||||
|
// Scrobbler Settings
|
||||||
|
bool listenbrainz_enable; // Enable ListenBrainz Scrobbling
|
||||||
|
char* listenbrainz_token; // ListenBrainz Token
|
||||||
|
bool lastfm_enable; // Enable LastFM Scrobbling
|
||||||
|
char* lastfm_username; // LastFM Username
|
||||||
|
char* lastfm_password; // LastFM Password
|
||||||
|
char* lastfm_api_key; // LastFM API Key
|
||||||
|
char* lastfm_api_secret; // LastFM API Secret
|
||||||
|
char* lastfm_api_session_key; // LastFM API Session Key (Generated from authorization endpoint)
|
||||||
|
|
||||||
|
// Audio Settings
|
||||||
|
bool audio_equalizer_enable;
|
||||||
|
bool audio_equalizer_followPitch; // Have equalizer align to pitch adjustment
|
||||||
|
int audio_equalizer_graphCount;
|
||||||
|
configHandler_eqGraph_t* audio_equalizer_graph;
|
||||||
|
bool audio_pitch_enable;
|
||||||
|
double audio_pitch_cents;
|
||||||
|
double audio_pitch_rate;
|
||||||
|
bool audio_reverb_enable;
|
||||||
|
double audio_reverb_wetDryMix; // Reverb Wet/Dry Mix Percent
|
||||||
|
} configHandler_config_t;
|
||||||
|
|
||||||
|
int configHandler_Read(configHandler_config_t** config);
|
||||||
|
void configHandler_Free(configHandler_config_t** config);
|
||||||
|
|
||||||
|
#endif
|
||||||
270
src/dscrdrpc.c
Normal file
270
src/dscrdrpc.c
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
/*
|
||||||
|
* NOTE: The private keys in this file are PURELY for FORMATTING DEMONSTRATION. They are NOT in active use
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include "external/cJSON.h"
|
||||||
|
#include "external/libcurl_uriescape.h"
|
||||||
|
#include "dscrdrpc.h"
|
||||||
|
|
||||||
|
#include "DarwinHttpClient.h"
|
||||||
|
|
||||||
|
void dscrdrpc_struct_init(dscrdrpc_data** dscrdrpc_struct) {
|
||||||
|
(*dscrdrpc_struct) = (dscrdrpc_data*)malloc(sizeof(dscrdrpc_data));
|
||||||
|
(*dscrdrpc_struct)->requestType = 0;
|
||||||
|
(*dscrdrpc_struct)->trackingUuid = NULL;
|
||||||
|
(*dscrdrpc_struct)->songLength = 0;
|
||||||
|
(*dscrdrpc_struct)->songTitle = NULL;
|
||||||
|
(*dscrdrpc_struct)->songArtist = NULL;
|
||||||
|
(*dscrdrpc_struct)->coverArtUrl = NULL;
|
||||||
|
(*dscrdrpc_struct)->deviceInfo = NULL;
|
||||||
|
(*dscrdrpc_struct)->checksum = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dscrdrpc_struct_deinit(dscrdrpc_data** dscrdrpc_struct) {
|
||||||
|
if ((*dscrdrpc_struct)->trackingUuid != NULL) { free((*dscrdrpc_struct)->trackingUuid); }
|
||||||
|
if ((*dscrdrpc_struct)->songTitle != NULL) { free((*dscrdrpc_struct)->songTitle); }
|
||||||
|
if ((*dscrdrpc_struct)->songArtist != NULL) { free((*dscrdrpc_struct)->songArtist); }
|
||||||
|
if ((*dscrdrpc_struct)->coverArtUrl != NULL) { free((*dscrdrpc_struct)->coverArtUrl); }
|
||||||
|
if ((*dscrdrpc_struct)->deviceInfo != NULL) { free((*dscrdrpc_struct)->deviceInfo); }
|
||||||
|
if ((*dscrdrpc_struct)->checksum != NULL) { free((*dscrdrpc_struct)->checksum); }
|
||||||
|
if (*dscrdrpc_struct != NULL) { free(*dscrdrpc_struct); }
|
||||||
|
}
|
||||||
|
|
||||||
|
int dscrdrpc_uuidv4(char** uuidv4) {
|
||||||
|
// Generate non-compliant UUIDv4 string
|
||||||
|
uint8_t uuidv4_bytes[16];
|
||||||
|
static int rc = 0;
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
uuidv4_bytes[i] = arc4random() & 0xFF;
|
||||||
|
}
|
||||||
|
rc = asprintf(uuidv4, "%.2x%.2x%.2x%.2x-%.2x%.2x-%.2x%.2x-%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x",
|
||||||
|
uuidv4_bytes[0], uuidv4_bytes[1], uuidv4_bytes[2], uuidv4_bytes[3], uuidv4_bytes[4],
|
||||||
|
uuidv4_bytes[5], uuidv4_bytes[6], uuidv4_bytes[7], uuidv4_bytes[8], uuidv4_bytes[9],
|
||||||
|
uuidv4_bytes[10], uuidv4_bytes[11], uuidv4_bytes[12], uuidv4_bytes[13], uuidv4_bytes[14],
|
||||||
|
uuidv4_bytes[15]);
|
||||||
|
if (rc == -1) {
|
||||||
|
printf("asprintf() failed.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dscrdrpc_crc32(char* inputString, char** crc32Output) {
|
||||||
|
static int rc = 0;
|
||||||
|
uLong crc = crc32(0, (void*)inputString, (uInt)strlen(inputString));
|
||||||
|
rc = asprintf(crc32Output, "%lx", crc);
|
||||||
|
if (rc == -1) {
|
||||||
|
printf("asprintf() failed.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dscrdrpc_form_innerJSON(dscrdrpc_data** dscrdrpc_struct, char** jsonOutput) {
|
||||||
|
cJSON* internal_root = cJSON_CreateObject();
|
||||||
|
cJSON* internal_requestType = cJSON_CreateNumber((*dscrdrpc_struct)->requestType);
|
||||||
|
cJSON* internal_trackingUuid = cJSON_CreateString((*dscrdrpc_struct)->trackingUuid);
|
||||||
|
cJSON* internal_songLength = cJSON_CreateNumber((*dscrdrpc_struct)->songLength);
|
||||||
|
cJSON* internal_songTitle = cJSON_CreateString((*dscrdrpc_struct)->songTitle);
|
||||||
|
cJSON* internal_songArtist = cJSON_CreateString((*dscrdrpc_struct)->songArtist);
|
||||||
|
cJSON* internal_coverArtUrl = cJSON_CreateString((*dscrdrpc_struct)->coverArtUrl);
|
||||||
|
cJSON* internal_deviceInfo = cJSON_CreateString((*dscrdrpc_struct)->deviceInfo);
|
||||||
|
cJSON* internal_checksum = cJSON_CreateString((*dscrdrpc_struct)->checksum);
|
||||||
|
cJSON_AddItemToObject(internal_root, "requestType", internal_requestType);
|
||||||
|
cJSON_AddItemToObject(internal_root, "trackingUuid", internal_trackingUuid);
|
||||||
|
cJSON_AddItemToObject(internal_root, "songLength", internal_songLength);
|
||||||
|
cJSON_AddItemToObject(internal_root, "songTitle", internal_songTitle);
|
||||||
|
cJSON_AddItemToObject(internal_root, "songArtist", internal_songArtist);
|
||||||
|
cJSON_AddItemToObject(internal_root, "coverArtUrl", internal_coverArtUrl);
|
||||||
|
cJSON_AddItemToObject(internal_root, "deviceInfo", internal_deviceInfo);
|
||||||
|
cJSON_AddItemToObject(internal_root, "checksum", internal_checksum);
|
||||||
|
*jsonOutput = cJSON_PrintUnformatted(internal_root);
|
||||||
|
cJSON_Delete(internal_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dscrdrpc_aes_gcm_encrypt(unsigned char* key, unsigned char* iv, unsigned char* ct, unsigned char* tag,
|
||||||
|
char* plaintext, int length) {
|
||||||
|
// Initialize OpenSSL
|
||||||
|
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
|
||||||
|
|
||||||
|
EVP_EncryptInit(ctx, EVP_aes_256_gcm(), NULL, NULL);
|
||||||
|
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL); // 96 bit IV
|
||||||
|
EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv);
|
||||||
|
|
||||||
|
// Encrypt the plaintext
|
||||||
|
int len;
|
||||||
|
EVP_EncryptUpdate(ctx, ct, &len, (const unsigned char*)plaintext, length);
|
||||||
|
EVP_EncryptFinal(ctx, ct + len, &len);
|
||||||
|
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
EVP_CIPHER_CTX_free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
EVP_PKEY* dscrdrpc_rsa_read_pubkey(char* pubkey) {
|
||||||
|
BIO* bio = BIO_new_mem_buf(pubkey, -1); // -1 causes strlen to be called
|
||||||
|
if (!bio) {
|
||||||
|
printf("BIO_new_mem_buf() failed.\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
|
||||||
|
BIO_free(bio);
|
||||||
|
if (!pkey) {
|
||||||
|
printf("PEM_read_bio_RSA_PUBKEY() failed.\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return pkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dscrdrpc_rsa_oaep_encrypt(EVP_PKEY* pkey, char* plaintext, int length, char** ct) {
|
||||||
|
// Initialize OpenSSL
|
||||||
|
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL);
|
||||||
|
EVP_PKEY_encrypt_init(ctx);
|
||||||
|
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
|
||||||
|
EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256());
|
||||||
|
EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256());
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
size_t len;
|
||||||
|
EVP_PKEY_encrypt(ctx, NULL, &len, (const unsigned char*)plaintext, length);
|
||||||
|
*ct = malloc(len);
|
||||||
|
EVP_PKEY_encrypt(ctx, (unsigned char*)*ct, &len, (const unsigned char*)plaintext, length);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
EVP_PKEY_CTX_free(ctx);
|
||||||
|
return (int)len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dscrdrpc_form_outerJSON(char* iv, char* tag, char* ct, char* checksum, char** jsonOutput) {
|
||||||
|
cJSON* outer_root = cJSON_CreateObject();
|
||||||
|
cJSON* outer_iv = cJSON_CreateString(iv);
|
||||||
|
cJSON* outer_tag = cJSON_CreateString(tag);
|
||||||
|
cJSON* outer_ct = cJSON_CreateString(ct);
|
||||||
|
cJSON* outer_checksum = cJSON_CreateString(checksum);
|
||||||
|
cJSON_AddItemToObject(outer_root, "i", outer_iv);
|
||||||
|
cJSON_AddItemToObject(outer_root, "t", outer_tag);
|
||||||
|
cJSON_AddItemToObject(outer_root, "d", outer_ct);
|
||||||
|
cJSON_AddItemToObject(outer_root, "c", outer_checksum);
|
||||||
|
*jsonOutput = cJSON_PrintUnformatted(outer_root);
|
||||||
|
cJSON_Delete(outer_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
int dscrdrpc_encrypt(dscrdrpc_data** dscrdrpc_struct) {
|
||||||
|
static int rc = 0;
|
||||||
|
|
||||||
|
// Generate UUIDv4 (Non compliant but does not matter)
|
||||||
|
dscrdrpc_uuidv4(&(*dscrdrpc_struct)->trackingUuid);
|
||||||
|
|
||||||
|
// Form string to make checksum
|
||||||
|
char* innerContentChecksumStr = NULL;
|
||||||
|
rc = asprintf(&innerContentChecksumStr, "%d%s%s%ld%s%s%s",
|
||||||
|
(*dscrdrpc_struct)->requestType, (*dscrdrpc_struct)->songTitle, (*dscrdrpc_struct)->songArtist,
|
||||||
|
(*dscrdrpc_struct)->songLength, (*dscrdrpc_struct)->coverArtUrl, (*dscrdrpc_struct)->deviceInfo,
|
||||||
|
(*dscrdrpc_struct)->trackingUuid);
|
||||||
|
if (rc == -1) {
|
||||||
|
printf("asprintf() failed.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create CRC32 checksum of contents
|
||||||
|
dscrdrpc_crc32(innerContentChecksumStr, &(*dscrdrpc_struct)->checksum);
|
||||||
|
free(innerContentChecksumStr);
|
||||||
|
|
||||||
|
// Form inner JSON
|
||||||
|
char* innerJSON = NULL;
|
||||||
|
dscrdrpc_form_innerJSON(dscrdrpc_struct, &innerJSON);
|
||||||
|
int innerJSON_length = (int)strlen(innerJSON);
|
||||||
|
|
||||||
|
// Encrypt inner JSON with AES GCM
|
||||||
|
unsigned char aes_key[32] = {
|
||||||
|
0x43, 0x63, 0x50, 0x5d, 0x31, 0x23, 0x46, 0x51,
|
||||||
|
0x74, 0x50, 0x70, 0x55, 0x4d, 0x60, 0x46, 0x69,
|
||||||
|
0x39, 0x46, 0x52, 0x6e, 0x5f, 0x5e, 0x2f, 0x50,
|
||||||
|
0x45, 0x27, 0x30, 0x55, 0x39, 0x68, 0x78, 0x43
|
||||||
|
};
|
||||||
|
unsigned char* aes_iv = calloc(12, sizeof(char));
|
||||||
|
RAND_bytes(aes_iv, 12); // TODO replace with arc4random
|
||||||
|
unsigned char* ciphertext = calloc(innerJSON_length, sizeof(char));
|
||||||
|
unsigned char* aes_tag = calloc(16, sizeof(char));
|
||||||
|
dscrdrpc_aes_gcm_encrypt(aes_key, aes_iv, ciphertext, aes_tag, innerJSON, innerJSON_length);
|
||||||
|
free(innerJSON);
|
||||||
|
|
||||||
|
printf("Key: "); for (int i = 0; i < 32; i++) { printf("%.2x", aes_key[i]); } printf("\n");
|
||||||
|
printf("IV: "); for (int i = 0; i < 12; i++) { printf("%.2x", aes_iv[i]); } printf("\n");
|
||||||
|
printf("Tag: "); for (int i = 0; i < 16; i++) { printf("%.2x", aes_tag[i]); } printf("\n");
|
||||||
|
printf("Ciphertext: "); for (int i = 0; i < innerJSON_length; i++) { printf("%.2x", ciphertext[i]); } printf("\n");
|
||||||
|
|
||||||
|
// RSA encrypt the AES IV and Tag
|
||||||
|
char* rsa_pubkey_text = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoeZn81EnEGom5eWOpFvB\nZw3X9gDBtjzix69qhWDHfq2oh/b0tynIe2PV7G6ELr2StwQOzcVa4cx3HkQ4vmE8\nZxdQ3Ru+HN6EjGonXrfgN7J6+MUDcEE0wOglAkoIGyrDhuxlMrUFKwiTVMdPqxQy\ngOWbOvnJu0q5x/7TFbJGgkZwdRYHgFXW/1lTEzBfZZu0cCa0V7YQ0prCxtbGP5CD\nXaDND65a/rk5t4Jp+3nOQDUtd6tqZ9Rp/mOBRQm8bj4nhw03UsbRhV+vHx34rtPl\nw46JSUfEuGXiNNaluXbPxWPNAf6Lj36UJH01tFVnJrPtv6T5mQCygL22OgkbSsAN\n3QIDAQAB\n-----END PUBLIC KEY-----";
|
||||||
|
EVP_PKEY* rsa_pubkey = dscrdrpc_rsa_read_pubkey(rsa_pubkey_text);
|
||||||
|
|
||||||
|
char* ret_rsa_iv = NULL;
|
||||||
|
char* ret_rsa_tag = NULL;
|
||||||
|
int ret_rsa_iv_len = dscrdrpc_rsa_oaep_encrypt(rsa_pubkey, (char*)aes_iv, 12, &ret_rsa_iv);
|
||||||
|
int ret_rsa_tag_len = dscrdrpc_rsa_oaep_encrypt(rsa_pubkey, (char*)aes_tag, 16, &ret_rsa_tag);
|
||||||
|
free(aes_iv);
|
||||||
|
free(aes_tag);
|
||||||
|
|
||||||
|
char* escaped_iv = lcue_uriescape(ret_rsa_iv, ret_rsa_iv_len);
|
||||||
|
char* escaped_tag = lcue_uriescape(ret_rsa_tag, ret_rsa_tag_len);
|
||||||
|
char* escaped_ciphertext = lcue_uriescape((char*)ciphertext, innerJSON_length);
|
||||||
|
char* escaped_aes_key = lcue_uriescape((char*)aes_key, sizeof(aes_key));
|
||||||
|
|
||||||
|
free(ciphertext);
|
||||||
|
free(ret_rsa_iv);
|
||||||
|
free(ret_rsa_tag);
|
||||||
|
EVP_PKEY_free(rsa_pubkey);
|
||||||
|
|
||||||
|
// Form string to make checksum
|
||||||
|
char* outerContentChecksumStr = NULL;
|
||||||
|
rc = asprintf(&outerContentChecksumStr, "%s%s%s%s",
|
||||||
|
escaped_ciphertext, escaped_iv, escaped_tag, escaped_aes_key);
|
||||||
|
if (rc == -1) {
|
||||||
|
printf("asprintf() failed.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create CRC32 checksum of contents
|
||||||
|
char* outerContentChecksum = NULL;
|
||||||
|
dscrdrpc_crc32(outerContentChecksumStr, &outerContentChecksum);
|
||||||
|
free(outerContentChecksumStr);
|
||||||
|
|
||||||
|
// Form Outer JSON
|
||||||
|
char* finalPayload = NULL;
|
||||||
|
dscrdrpc_form_outerJSON(escaped_iv, escaped_tag, escaped_ciphertext, outerContentChecksum, &finalPayload);
|
||||||
|
|
||||||
|
free(escaped_iv);
|
||||||
|
free(escaped_tag);
|
||||||
|
free(escaped_ciphertext);
|
||||||
|
free(escaped_aes_key);
|
||||||
|
free(outerContentChecksum);
|
||||||
|
|
||||||
|
printf("Final payload: %s\n", finalPayload);
|
||||||
|
|
||||||
|
opensubsonic_httpClientRequest_t* httpReq;
|
||||||
|
opensubsonic_httpClient_prepareRequest(&httpReq);
|
||||||
|
httpReq->method = HTTP_METHOD_POST;
|
||||||
|
httpReq->requestUrl = strdup("http://192.168.5.140:20000/updrp");
|
||||||
|
httpReq->isBodyRequired = true;
|
||||||
|
httpReq->requestBody = strdup(finalPayload);
|
||||||
|
opensubsonic_httpClient_request(&httpReq);
|
||||||
|
XNU_HttpRequest(&httpReq);
|
||||||
|
|
||||||
|
printf("%s\n", httpReq->responseMsg);
|
||||||
|
|
||||||
|
free(finalPayload);
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
34
src/dscrdrpc.h
Normal file
34
src/dscrdrpc.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef _DSCRDRPC_H
|
||||||
|
#define _DSCRDRPC_H
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
|
#define DSCRDRPC_REQTYPE_PLAYING 1
|
||||||
|
#define DSCRDRPC_REQTYPE_PAUSED 2
|
||||||
|
#define DSCRDRPC_REQTYPE_UNPAUSED 3
|
||||||
|
#define DSCRDRPC_REQTYPE_CLEAR 4
|
||||||
|
#define DSCRDRPC_REQTYPE_KEEPALIVE 5
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int requestType;
|
||||||
|
char* trackingUuid;
|
||||||
|
long songLength;
|
||||||
|
char* songTitle;
|
||||||
|
char* songArtist;
|
||||||
|
char* coverArtUrl;
|
||||||
|
char* deviceInfo;
|
||||||
|
char* checksum;
|
||||||
|
} dscrdrpc_data;
|
||||||
|
|
||||||
|
void dscrdrpc_struct_init(dscrdrpc_data** dscrdrpc_struct);
|
||||||
|
void dscrdrpc_struct_deinit(dscrdrpc_data** dscrdrpc_struct);
|
||||||
|
int dscrdrpc_uuidv4(char** uuidv4);
|
||||||
|
int dscrdrpc_crc32(char* inputString, char** crc32Output);
|
||||||
|
void dscrdrpc_form_innerJSON(dscrdrpc_data** dscrdrpc_struct, char** jsonOutput);
|
||||||
|
void dscrdrpc_aes_gcm_encrypt(unsigned char* key, unsigned char* iv, unsigned char* ct, unsigned char* tag, char* plaintext, int length);
|
||||||
|
EVP_PKEY* dscrdrpc_rsa_read_pubkey(char* pubkey);
|
||||||
|
int dscrdrpc_rsa_oaep_encrypt(EVP_PKEY* pkey, char* plaintext, int length, char** ct);
|
||||||
|
void dscrdrpc_form_outerJSON(char* iv, char* tag, char* ct, char* checksum, char** jsonOutput);
|
||||||
|
|
||||||
|
int dscrdrpc_encrypt(dscrdrpc_data** dscrdrpc_struct);
|
||||||
|
|
||||||
|
#endif
|
||||||
3143
src/external/cJSON.c
vendored
Normal file
3143
src/external/cJSON.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
300
src/external/cJSON.h
vendored
Normal file
300
src/external/cJSON.h
vendored
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef cJSON__h
|
||||||
|
#define cJSON__h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
|
||||||
|
#define __WINDOWS__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
|
||||||
|
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
|
||||||
|
|
||||||
|
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
|
||||||
|
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
|
||||||
|
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
|
||||||
|
|
||||||
|
For *nix builds that support visibility attribute, you can define similar behavior by
|
||||||
|
|
||||||
|
setting default visibility to hidden by adding
|
||||||
|
-fvisibility=hidden (for gcc)
|
||||||
|
or
|
||||||
|
-xldscope=hidden (for sun cc)
|
||||||
|
to CFLAGS
|
||||||
|
|
||||||
|
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define CJSON_CDECL __cdecl
|
||||||
|
#define CJSON_STDCALL __stdcall
|
||||||
|
|
||||||
|
/* export symbols by default, this is necessary for copy pasting the C and header file */
|
||||||
|
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
|
||||||
|
#define CJSON_EXPORT_SYMBOLS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(CJSON_HIDE_SYMBOLS)
|
||||||
|
#define CJSON_PUBLIC(type) type CJSON_STDCALL
|
||||||
|
#elif defined(CJSON_EXPORT_SYMBOLS)
|
||||||
|
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
|
||||||
|
#elif defined(CJSON_IMPORT_SYMBOLS)
|
||||||
|
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
|
||||||
|
#endif
|
||||||
|
#else /* !__WINDOWS__ */
|
||||||
|
#define CJSON_CDECL
|
||||||
|
#define CJSON_STDCALL
|
||||||
|
|
||||||
|
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
|
||||||
|
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
|
||||||
|
#else
|
||||||
|
#define CJSON_PUBLIC(type) type
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* project version */
|
||||||
|
#define CJSON_VERSION_MAJOR 1
|
||||||
|
#define CJSON_VERSION_MINOR 7
|
||||||
|
#define CJSON_VERSION_PATCH 18
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* cJSON Types: */
|
||||||
|
#define cJSON_Invalid (0)
|
||||||
|
#define cJSON_False (1 << 0)
|
||||||
|
#define cJSON_True (1 << 1)
|
||||||
|
#define cJSON_NULL (1 << 2)
|
||||||
|
#define cJSON_Number (1 << 3)
|
||||||
|
#define cJSON_String (1 << 4)
|
||||||
|
#define cJSON_Array (1 << 5)
|
||||||
|
#define cJSON_Object (1 << 6)
|
||||||
|
#define cJSON_Raw (1 << 7) /* raw json */
|
||||||
|
|
||||||
|
#define cJSON_IsReference 256
|
||||||
|
#define cJSON_StringIsConst 512
|
||||||
|
|
||||||
|
/* The cJSON structure: */
|
||||||
|
typedef struct cJSON
|
||||||
|
{
|
||||||
|
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
|
||||||
|
struct cJSON *next;
|
||||||
|
struct cJSON *prev;
|
||||||
|
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
|
||||||
|
struct cJSON *child;
|
||||||
|
|
||||||
|
/* The type of the item, as above. */
|
||||||
|
int type;
|
||||||
|
|
||||||
|
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
|
||||||
|
char *valuestring;
|
||||||
|
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
|
||||||
|
int valueint;
|
||||||
|
/* The item's number, if type==cJSON_Number */
|
||||||
|
double valuedouble;
|
||||||
|
|
||||||
|
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
|
||||||
|
char *string;
|
||||||
|
} cJSON;
|
||||||
|
|
||||||
|
typedef struct cJSON_Hooks
|
||||||
|
{
|
||||||
|
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
|
||||||
|
void *(CJSON_CDECL *malloc_fn)(size_t sz);
|
||||||
|
void (CJSON_CDECL *free_fn)(void *ptr);
|
||||||
|
} cJSON_Hooks;
|
||||||
|
|
||||||
|
typedef int cJSON_bool;
|
||||||
|
|
||||||
|
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
|
||||||
|
* This is to prevent stack overflows. */
|
||||||
|
#ifndef CJSON_NESTING_LIMIT
|
||||||
|
#define CJSON_NESTING_LIMIT 1000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* returns the version of cJSON as a string */
|
||||||
|
CJSON_PUBLIC(const char*) cJSON_Version(void);
|
||||||
|
|
||||||
|
/* Supply malloc, realloc and free functions to cJSON */
|
||||||
|
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
|
||||||
|
|
||||||
|
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
|
||||||
|
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
|
||||||
|
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
|
||||||
|
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||||
|
|
||||||
|
/* Render a cJSON entity to text for transfer/storage. */
|
||||||
|
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
|
||||||
|
/* Render a cJSON entity to text for transfer/storage without any formatting. */
|
||||||
|
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
|
||||||
|
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
|
||||||
|
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
|
||||||
|
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
|
||||||
|
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
|
||||||
|
/* Delete a cJSON entity and all subentities. */
|
||||||
|
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
|
||||||
|
|
||||||
|
/* Returns the number of items in an array (or object). */
|
||||||
|
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
|
||||||
|
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
|
||||||
|
/* Get item "string" from object. Case insensitive. */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
|
||||||
|
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
|
||||||
|
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
|
||||||
|
|
||||||
|
/* Check item type and return its value */
|
||||||
|
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
|
||||||
|
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
|
||||||
|
|
||||||
|
/* These functions check the type of an item */
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
|
||||||
|
|
||||||
|
/* These calls create a cJSON item of the appropriate type. */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
|
||||||
|
/* raw json */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
|
||||||
|
|
||||||
|
/* Create a string where valuestring references a string so
|
||||||
|
* it will not be freed by cJSON_Delete */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
|
||||||
|
/* Create an object/array that only references it's elements so
|
||||||
|
* they will not be freed by cJSON_Delete */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
|
||||||
|
|
||||||
|
/* These utilities create an Array of count items.
|
||||||
|
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
|
||||||
|
|
||||||
|
/* Append item to the specified array/object. */
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
|
||||||
|
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
|
||||||
|
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
|
||||||
|
* writing to `item->string` */
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
|
||||||
|
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
|
||||||
|
|
||||||
|
/* Remove/Detach items from Arrays/Objects. */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
|
||||||
|
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||||
|
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
|
||||||
|
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||||
|
|
||||||
|
/* Update array items. */
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
|
||||||
|
|
||||||
|
/* Duplicate a cJSON item */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
|
||||||
|
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
|
||||||
|
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
|
||||||
|
* The item->next and ->prev pointers are always zero on return from Duplicate. */
|
||||||
|
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
|
||||||
|
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
|
||||||
|
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
|
||||||
|
|
||||||
|
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
|
||||||
|
* The input pointer json cannot point to a read-only address area, such as a string constant,
|
||||||
|
* but should point to a readable and writable address area. */
|
||||||
|
CJSON_PUBLIC(void) cJSON_Minify(char *json);
|
||||||
|
|
||||||
|
/* Helper functions for creating and adding items to an object at the same time.
|
||||||
|
* They return the added item or NULL on failure. */
|
||||||
|
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
|
||||||
|
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
|
||||||
|
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
|
||||||
|
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
|
||||||
|
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
|
||||||
|
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
|
||||||
|
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
|
||||||
|
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
|
||||||
|
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
|
||||||
|
|
||||||
|
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
|
||||||
|
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
|
||||||
|
/* helper for the cJSON_SetNumberValue macro */
|
||||||
|
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
|
||||||
|
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
|
||||||
|
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
|
||||||
|
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
|
||||||
|
|
||||||
|
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
|
||||||
|
#define cJSON_SetBoolValue(object, boolValue) ( \
|
||||||
|
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
|
||||||
|
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
|
||||||
|
cJSON_Invalid\
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Macro for iterating over an array or object */
|
||||||
|
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
|
||||||
|
|
||||||
|
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
|
||||||
|
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
|
||||||
|
CJSON_PUBLIC(void) cJSON_free(void *object);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
1481
src/external/cJSON_Utils.c
vendored
Normal file
1481
src/external/cJSON_Utils.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
88
src/external/cJSON_Utils.h
vendored
Normal file
88
src/external/cJSON_Utils.h
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef cJSON_Utils__h
|
||||||
|
#define cJSON_Utils__h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "cJSON.h"
|
||||||
|
|
||||||
|
/* Implement RFC6901 (https://tools.ietf.org/html/rfc6901) JSON Pointer spec. */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON * const object, const char *pointer);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointerCaseSensitive(cJSON * const object, const char *pointer);
|
||||||
|
|
||||||
|
/* Implement RFC6902 (https://tools.ietf.org/html/rfc6902) JSON Patch spec. */
|
||||||
|
/* NOTE: This modifies objects in 'from' and 'to' by sorting the elements by their key */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatches(cJSON * const from, cJSON * const to);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatchesCaseSensitive(cJSON * const from, cJSON * const to);
|
||||||
|
/* Utility for generating patch array entries. */
|
||||||
|
CJSON_PUBLIC(void) cJSONUtils_AddPatchToArray(cJSON * const array, const char * const operation, const char * const path, const cJSON * const value);
|
||||||
|
/* Returns 0 for success. */
|
||||||
|
CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON * const object, const cJSON * const patches);
|
||||||
|
CJSON_PUBLIC(int) cJSONUtils_ApplyPatchesCaseSensitive(cJSON * const object, const cJSON * const patches);
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Note that ApplyPatches is NOT atomic on failure. To implement an atomic ApplyPatches, use:
|
||||||
|
//int cJSONUtils_AtomicApplyPatches(cJSON **object, cJSON *patches)
|
||||||
|
//{
|
||||||
|
// cJSON *modme = cJSON_Duplicate(*object, 1);
|
||||||
|
// int error = cJSONUtils_ApplyPatches(modme, patches);
|
||||||
|
// if (!error)
|
||||||
|
// {
|
||||||
|
// cJSON_Delete(*object);
|
||||||
|
// *object = modme;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// cJSON_Delete(modme);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return error;
|
||||||
|
//}
|
||||||
|
// Code not added to library since this strategy is a LOT slower.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Implement RFC7386 (https://tools.ietf.org/html/rfc7396) JSON Merge Patch spec. */
|
||||||
|
/* target will be modified by patch. return value is new ptr for target. */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatch(cJSON *target, const cJSON * const patch);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatchCaseSensitive(cJSON *target, const cJSON * const patch);
|
||||||
|
/* generates a patch to move from -> to */
|
||||||
|
/* NOTE: This modifies objects in 'from' and 'to' by sorting the elements by their key */
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON * const from, cJSON * const to);
|
||||||
|
CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatchCaseSensitive(cJSON * const from, cJSON * const to);
|
||||||
|
|
||||||
|
/* Given a root object and a target object, construct a pointer from one to the other. */
|
||||||
|
CJSON_PUBLIC(char *) cJSONUtils_FindPointerFromObjectTo(const cJSON * const object, const cJSON * const target);
|
||||||
|
|
||||||
|
/* Sorts the members of the object into alphabetical order. */
|
||||||
|
CJSON_PUBLIC(void) cJSONUtils_SortObject(cJSON * const object);
|
||||||
|
CJSON_PUBLIC(void) cJSONUtils_SortObjectCaseSensitive(cJSON * const object);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
64
src/external/libcurl_uriescape.c
vendored
Normal file
64
src/external/libcurl_uriescape.c
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Libcurl curl_easy_escape out-of-tree and reimplemented
|
||||||
|
* Based on libcurl 8.15.0
|
||||||
|
* Libcurl Copyright (C) Steve Holme, <steve_holme@hotmail.com>
|
||||||
|
* Written for use in OSSP by Goldenkrew3000, <avery@hojuix.org>
|
||||||
|
* Acronyms are used to avoid conflict when in use in combination with libcurl (lcue -> Libcurl URI Encode)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "libcurl_uriescape.h"
|
||||||
|
|
||||||
|
#define LCUE_ISUPPER(x) (((x) >= 'A') && ((x) <= 'Z'))
|
||||||
|
#define LCUE_ISLOWER(x) (((x) >= 'a') && ((x) <= 'z'))
|
||||||
|
#define LCUE_ISDIGIT(x) (((x) >= '0') && ((x) <= '9'))
|
||||||
|
#define LCUE_ISALNUM(x) (LCUE_ISDIGIT(x) || LCUE_ISLOWER(x) || LCUE_ISUPPER(x))
|
||||||
|
#define LCUE_ISURLPUNTCS(x) (((x) == '-') || ((x) == '.') || ((x) == '_') || ((x) == '~'))
|
||||||
|
#define LCUE_ISUNRESERVED(x) (LCUE_ISALNUM(x) || LCUE_ISURLPUNTCS(x))
|
||||||
|
const unsigned char lcue_udigits[] = "0123456789ABCDEF";
|
||||||
|
const unsigned char lcue_ldigits[] = "0123456789abcdef";
|
||||||
|
|
||||||
|
static void lcue_hexbyte(unsigned char* dest, unsigned char val) {
|
||||||
|
dest[0] = lcue_udigits[val >> 4];
|
||||||
|
dest[1] = lcue_udigits[val & 0x0F];
|
||||||
|
}
|
||||||
|
|
||||||
|
char* lcue_uriescape(char* string, int inlength) {
|
||||||
|
size_t length = 0;
|
||||||
|
char* d = NULL;
|
||||||
|
int d_idx = 0;
|
||||||
|
|
||||||
|
if (!string || (inlength < 0)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = (inlength ? (size_t)inlength : strlen(string));
|
||||||
|
if(!length) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
d = calloc(length * 3 + 1, sizeof(char));
|
||||||
|
if (d == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (length--) {
|
||||||
|
unsigned char in = (unsigned char)*string++;
|
||||||
|
|
||||||
|
if (LCUE_ISUNRESERVED(in)) {
|
||||||
|
d[d_idx] = in;
|
||||||
|
d_idx++;
|
||||||
|
} else {
|
||||||
|
unsigned char out[3] = { '%' };
|
||||||
|
lcue_hexbyte(&out[1], in);
|
||||||
|
d[d_idx] = out[0];
|
||||||
|
d[d_idx + 1] = out[1];
|
||||||
|
d[d_idx + 2] = out[2];
|
||||||
|
d_idx += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
14
src/external/libcurl_uriescape.h
vendored
Normal file
14
src/external/libcurl_uriescape.h
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Libcurl curl_easy_escape out-of-tree and reimplemented
|
||||||
|
* Based on libcurl 8.15.0
|
||||||
|
* Libcurl Copyright (C) Steve Holme, <steve_holme@hotmail.com>
|
||||||
|
* Written for use in OSSP by Goldenkrew3000, <avery@hojuix.org>
|
||||||
|
* Acronyms are used to avoid conflict when in use in combination with libcurl (lsue -> Libcurl URI Encode)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _LIBCURL_URIESCAPE_H
|
||||||
|
#define _LIBCURL_URIESCAPE_H
|
||||||
|
|
||||||
|
char* lcue_uriescape(char* string, int inlength);
|
||||||
|
|
||||||
|
#endif
|
||||||
223
src/external/md5.c
vendored
Normal file
223
src/external/md5.c
vendored
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm
|
||||||
|
* and modified slightly to be functionally identical but condensed into control structures.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "md5.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constants defined by the MD5 algorithm
|
||||||
|
*/
|
||||||
|
#define A 0x67452301
|
||||||
|
#define B 0xefcdab89
|
||||||
|
#define C 0x98badcfe
|
||||||
|
#define D 0x10325476
|
||||||
|
|
||||||
|
static uint32_t S[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
||||||
|
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
||||||
|
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||||||
|
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
|
||||||
|
|
||||||
|
static uint32_t K[] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
|
||||||
|
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
|
||||||
|
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
|
||||||
|
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
|
||||||
|
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
|
||||||
|
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
||||||
|
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
|
||||||
|
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
|
||||||
|
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
|
||||||
|
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
|
||||||
|
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
|
||||||
|
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
||||||
|
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
|
||||||
|
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
|
||||||
|
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
|
||||||
|
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Padding used to make the size (in bits) of the input congruent to 448 mod 512
|
||||||
|
*/
|
||||||
|
static uint8_t PADDING[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bit-manipulation functions defined by the MD5 algorithm
|
||||||
|
*/
|
||||||
|
#define F(X, Y, Z) ((X & Y) | (~X & Z))
|
||||||
|
#define G(X, Y, Z) ((X & Z) | (Y & ~Z))
|
||||||
|
#define H(X, Y, Z) (X ^ Y ^ Z)
|
||||||
|
#define I(X, Y, Z) (Y ^ (X | ~Z))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rotates a 32-bit word left by n bits
|
||||||
|
*/
|
||||||
|
uint32_t rotateLeft(uint32_t x, uint32_t n){
|
||||||
|
return (x << n) | (x >> (32 - n));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize a context
|
||||||
|
*/
|
||||||
|
void md5Init(MD5Context *ctx){
|
||||||
|
ctx->size = (uint64_t)0;
|
||||||
|
|
||||||
|
ctx->buffer[0] = (uint32_t)A;
|
||||||
|
ctx->buffer[1] = (uint32_t)B;
|
||||||
|
ctx->buffer[2] = (uint32_t)C;
|
||||||
|
ctx->buffer[3] = (uint32_t)D;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add some amount of input to the context
|
||||||
|
*
|
||||||
|
* If the input fills out a block of 512 bits, apply the algorithm (md5Step)
|
||||||
|
* and save the result in the buffer. Also updates the overall size.
|
||||||
|
*/
|
||||||
|
void md5Update(MD5Context *ctx, uint8_t *input_buffer, size_t input_len){
|
||||||
|
uint32_t input[16];
|
||||||
|
unsigned int offset = ctx->size % 64;
|
||||||
|
ctx->size += (uint64_t)input_len;
|
||||||
|
|
||||||
|
// Copy each byte in input_buffer into the next space in our context input
|
||||||
|
for(unsigned int i = 0; i < input_len; ++i){
|
||||||
|
ctx->input[offset++] = (uint8_t)*(input_buffer + i);
|
||||||
|
|
||||||
|
// If we've filled our context input, copy it into our local array input
|
||||||
|
// then reset the offset to 0 and fill in a new buffer.
|
||||||
|
// Every time we fill out a chunk, we run it through the algorithm
|
||||||
|
// to enable some back and forth between cpu and i/o
|
||||||
|
if(offset % 64 == 0){
|
||||||
|
for(unsigned int j = 0; j < 16; ++j){
|
||||||
|
// Convert to little-endian
|
||||||
|
// The local variable `input` our 512-bit chunk separated into 32-bit words
|
||||||
|
// we can use in calculations
|
||||||
|
input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 |
|
||||||
|
(uint32_t)(ctx->input[(j * 4) + 2]) << 16 |
|
||||||
|
(uint32_t)(ctx->input[(j * 4) + 1]) << 8 |
|
||||||
|
(uint32_t)(ctx->input[(j * 4)]);
|
||||||
|
}
|
||||||
|
md5Step(ctx->buffer, input);
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pad the current input to get to 448 bytes, append the size in bits to the very end,
|
||||||
|
* and save the result of the final iteration into digest.
|
||||||
|
*/
|
||||||
|
void md5Finalize(MD5Context *ctx){
|
||||||
|
uint32_t input[16];
|
||||||
|
unsigned int offset = ctx->size % 64;
|
||||||
|
unsigned int padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset;
|
||||||
|
|
||||||
|
// Fill in the padding and undo the changes to size that resulted from the update
|
||||||
|
md5Update(ctx, PADDING, padding_length);
|
||||||
|
ctx->size -= (uint64_t)padding_length;
|
||||||
|
|
||||||
|
// Do a final update (internal to this function)
|
||||||
|
// Last two 32-bit words are the two halves of the size (converted from bytes to bits)
|
||||||
|
for(unsigned int j = 0; j < 14; ++j){
|
||||||
|
input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 |
|
||||||
|
(uint32_t)(ctx->input[(j * 4) + 2]) << 16 |
|
||||||
|
(uint32_t)(ctx->input[(j * 4) + 1]) << 8 |
|
||||||
|
(uint32_t)(ctx->input[(j * 4)]);
|
||||||
|
}
|
||||||
|
input[14] = (uint32_t)(ctx->size * 8);
|
||||||
|
input[15] = (uint32_t)((ctx->size * 8) >> 32);
|
||||||
|
|
||||||
|
md5Step(ctx->buffer, input);
|
||||||
|
|
||||||
|
// Move the result into digest (convert from little-endian)
|
||||||
|
for(unsigned int i = 0; i < 4; ++i){
|
||||||
|
ctx->digest[(i * 4) + 0] = (uint8_t)((ctx->buffer[i] & 0x000000FF));
|
||||||
|
ctx->digest[(i * 4) + 1] = (uint8_t)((ctx->buffer[i] & 0x0000FF00) >> 8);
|
||||||
|
ctx->digest[(i * 4) + 2] = (uint8_t)((ctx->buffer[i] & 0x00FF0000) >> 16);
|
||||||
|
ctx->digest[(i * 4) + 3] = (uint8_t)((ctx->buffer[i] & 0xFF000000) >> 24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Step on 512 bits of input with the main MD5 algorithm.
|
||||||
|
*/
|
||||||
|
void md5Step(uint32_t *buffer, uint32_t *input){
|
||||||
|
uint32_t AA = buffer[0];
|
||||||
|
uint32_t BB = buffer[1];
|
||||||
|
uint32_t CC = buffer[2];
|
||||||
|
uint32_t DD = buffer[3];
|
||||||
|
|
||||||
|
uint32_t E;
|
||||||
|
|
||||||
|
unsigned int j;
|
||||||
|
|
||||||
|
for(unsigned int i = 0; i < 64; ++i){
|
||||||
|
switch(i / 16){
|
||||||
|
case 0:
|
||||||
|
E = F(BB, CC, DD);
|
||||||
|
j = i;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
E = G(BB, CC, DD);
|
||||||
|
j = ((i * 5) + 1) % 16;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
E = H(BB, CC, DD);
|
||||||
|
j = ((i * 3) + 5) % 16;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
E = I(BB, CC, DD);
|
||||||
|
j = (i * 7) % 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t temp = DD;
|
||||||
|
DD = CC;
|
||||||
|
CC = BB;
|
||||||
|
BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]);
|
||||||
|
AA = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[0] += AA;
|
||||||
|
buffer[1] += BB;
|
||||||
|
buffer[2] += CC;
|
||||||
|
buffer[3] += DD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Functions that run the algorithm on the provided input and put the digest into result.
|
||||||
|
* result should be able to store 16 bytes.
|
||||||
|
*/
|
||||||
|
void md5String(char *input, uint8_t *result){
|
||||||
|
MD5Context ctx;
|
||||||
|
md5Init(&ctx);
|
||||||
|
md5Update(&ctx, (uint8_t *)input, strlen(input));
|
||||||
|
md5Finalize(&ctx);
|
||||||
|
|
||||||
|
memcpy(result, ctx.digest, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
void md5File(FILE *file, uint8_t *result){
|
||||||
|
char *input_buffer = malloc(1024);
|
||||||
|
size_t input_size = 0;
|
||||||
|
|
||||||
|
MD5Context ctx;
|
||||||
|
md5Init(&ctx);
|
||||||
|
|
||||||
|
while((input_size = fread(input_buffer, 1, 1024, file)) > 0){
|
||||||
|
md5Update(&ctx, (uint8_t *)input_buffer, input_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
md5Finalize(&ctx);
|
||||||
|
|
||||||
|
free(input_buffer);
|
||||||
|
|
||||||
|
memcpy(result, ctx.digest, 16);
|
||||||
|
}
|
||||||
24
src/external/md5.h
vendored
Normal file
24
src/external/md5.h
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef MD5_H
|
||||||
|
#define MD5_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
uint64_t size; // Size of input in bytes
|
||||||
|
uint32_t buffer[4]; // Current accumulation of hash
|
||||||
|
uint8_t input[64]; // Input to be used in the next step
|
||||||
|
uint8_t digest[16]; // Result of algorithm
|
||||||
|
}MD5Context;
|
||||||
|
|
||||||
|
void md5Init(MD5Context *ctx);
|
||||||
|
void md5Update(MD5Context *ctx, uint8_t *input, size_t input_len);
|
||||||
|
void md5Finalize(MD5Context *ctx);
|
||||||
|
void md5Step(uint32_t *buffer, uint32_t *input);
|
||||||
|
|
||||||
|
void md5String(char *input, uint8_t *result);
|
||||||
|
void md5File(FILE *file, uint8_t *result);
|
||||||
|
|
||||||
|
#endif
|
||||||
100
src/libopensubsonic/crypto.c
Normal file
100
src/libopensubsonic/crypto.c
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include "crypto.h"
|
||||||
|
#include "../external/md5.h"
|
||||||
|
#include "../configHandler.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#if __NetBSD__ // Functions for NetBSD to use KERN_ARND
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int rc = 0;
|
||||||
|
extern configHandler_config_t* configObj;
|
||||||
|
|
||||||
|
// Use arc4random() to generate cryptographically secure bytes. Should work on all BSD-style systems
|
||||||
|
void crypto_secure_arc4random_generate(unsigned char* bytes, size_t length) {
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
bytes[i] = arc4random() & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the arandom sysctl on NetBSD to generate cryptographically secure bytes.
|
||||||
|
#if __NetBSD__
|
||||||
|
void crypto_secure_netbsd_arandom_generate(unsigned char* bytes, size_t length) {
|
||||||
|
// Setup the sysctl MIB for kern.arandom
|
||||||
|
int mib[2];
|
||||||
|
mib[0] = CTL_KERN;
|
||||||
|
mib[1] = KERN_ARND;
|
||||||
|
|
||||||
|
// Read random bytes
|
||||||
|
if (sysctl(mib, 2, bytes, &length, NULL, 0) == -1) {
|
||||||
|
logger_log_error(__func__, "sysctl() error.");
|
||||||
|
exit(EXIT_FAILURE); // TODO handle error better
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Generate an Opensubsonic Login Salt
|
||||||
|
void crypto_secure_generate_salt(void) {
|
||||||
|
uint8_t salt_bytes[8];
|
||||||
|
|
||||||
|
// Generate cryptographically secure salt bytes using OS-native functions
|
||||||
|
#if __NetBSD__
|
||||||
|
crypto_secure_netbsd_arandom_generate(salt_bytes, 8);
|
||||||
|
#else
|
||||||
|
crypto_secure_arc4random_generate(salt_bytes, 8);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Convert to a string hex representation
|
||||||
|
char* loginSalt = NULL;
|
||||||
|
rc = asprintf(&loginSalt, "%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||||
|
salt_bytes[0], salt_bytes[1], salt_bytes[2], salt_bytes[3],
|
||||||
|
salt_bytes[4], salt_bytes[5], salt_bytes[6], salt_bytes[7]);
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed.");
|
||||||
|
return; // TODO return proper error
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Fix this hack - Copy login salt to config
|
||||||
|
configObj->internal_opensubsonic_loginSalt = strdup(loginSalt);
|
||||||
|
free(loginSalt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate an MD5 checksum in string hex representation of the account password and salt
|
||||||
|
void crypto_secure_generate_token(void) {
|
||||||
|
uint8_t md5_bytes[16];
|
||||||
|
char* token_plaintext = NULL;
|
||||||
|
|
||||||
|
// Concatenate account password and salt into single string
|
||||||
|
rc = asprintf(&token_plaintext, "%s%s", configObj->opensubsonic_password, configObj->internal_opensubsonic_loginSalt);
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed.");
|
||||||
|
return; // TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate an MD5 checksum of the plaintext token
|
||||||
|
md5String(token_plaintext, md5_bytes);
|
||||||
|
free(token_plaintext);
|
||||||
|
|
||||||
|
// Convert the MD5 checksum bytes into string hex representation
|
||||||
|
char* loginToken = NULL;
|
||||||
|
rc = asprintf(&loginToken, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||||
|
md5_bytes[0], md5_bytes[1], md5_bytes[2], md5_bytes[3],
|
||||||
|
md5_bytes[4], md5_bytes[5], md5_bytes[6], md5_bytes[7],
|
||||||
|
md5_bytes[8], md5_bytes[9], md5_bytes[10], md5_bytes[11],
|
||||||
|
md5_bytes[12], md5_bytes[13], md5_bytes[14], md5_bytes[15]);
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed.");
|
||||||
|
return; // TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Fix this hack - Copy login token to config
|
||||||
|
configObj->internal_opensubsonic_loginToken = strdup(loginToken);
|
||||||
|
free(loginToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_crypto_generateLogin(void) {
|
||||||
|
crypto_secure_generate_salt();
|
||||||
|
crypto_secure_generate_token();
|
||||||
|
}
|
||||||
14
src/libopensubsonic/crypto.h
Normal file
14
src/libopensubsonic/crypto.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef _CRYPTO_H
|
||||||
|
#define _CRYPTO_H
|
||||||
|
|
||||||
|
// OS-native cryptographic generation functions
|
||||||
|
void crypto_secure_arc4random_generate(unsigned char* bytes, size_t length);
|
||||||
|
#if __NetBSD__
|
||||||
|
void crypto_secure_netbsd_arandom_generate(unsigned char* bytes, size_t length);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void crypto_secure_generate_salt(void);
|
||||||
|
void crypto_secure_generate_token(void);
|
||||||
|
void opensubsonic_crypto_generateLogin(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
174
src/libopensubsonic/endpoint_getAlbum.c
Normal file
174
src/libopensubsonic/endpoint_getAlbum.c
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_getAlbum.h"
|
||||||
|
|
||||||
|
// Returns 1 if failure occured, else 0
|
||||||
|
int opensubsonic_getAlbum_parse(char* data, opensubsonic_getAlbum_struct** getAlbumStruct) {
|
||||||
|
// Allocate and initialize
|
||||||
|
(*getAlbumStruct) = (opensubsonic_getAlbum_struct*)malloc(sizeof(opensubsonic_getAlbum_struct));
|
||||||
|
(*getAlbumStruct)->status = NULL;
|
||||||
|
(*getAlbumStruct)->errorCode = 0;
|
||||||
|
(*getAlbumStruct)->errorMessage = NULL;
|
||||||
|
(*getAlbumStruct)->id = NULL;
|
||||||
|
(*getAlbumStruct)->parent = NULL;
|
||||||
|
(*getAlbumStruct)->album = NULL;
|
||||||
|
(*getAlbumStruct)->title = NULL;
|
||||||
|
(*getAlbumStruct)->name = NULL;
|
||||||
|
(*getAlbumStruct)->coverArt = NULL;
|
||||||
|
(*getAlbumStruct)->created = NULL;
|
||||||
|
(*getAlbumStruct)->duration = 0;
|
||||||
|
(*getAlbumStruct)->playCount = 0;
|
||||||
|
(*getAlbumStruct)->artistId = NULL;
|
||||||
|
(*getAlbumStruct)->artist = NULL;
|
||||||
|
(*getAlbumStruct)->year = 0;
|
||||||
|
(*getAlbumStruct)->genre = NULL;
|
||||||
|
(*getAlbumStruct)->songCount = 0;
|
||||||
|
(*getAlbumStruct)->songs = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for error
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->status, subsonic_root, "status");
|
||||||
|
if (strstr((*getAlbumStruct)->status, "ok") == NULL) {
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Pioj(&(*getAlbumStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getAlbumStruct)->errorCode, (*getAlbumStruct)->errorMessage);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process contents
|
||||||
|
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "album");
|
||||||
|
if (album_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - album does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->id, album_root, "id");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->parent, album_root, "parent");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->album, album_root, "album");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->title, album_root, "title");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->name, album_root, "name");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->coverArt, album_root, "coverArt");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->created, album_root, "created");
|
||||||
|
OSS_Ploj(&(*getAlbumStruct)->duration, album_root, "duration");
|
||||||
|
OSS_Pioj(&(*getAlbumStruct)->playCount, album_root, "playCount");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->artistId, album_root, "artistId");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->artist, album_root, "artist");
|
||||||
|
OSS_Pioj(&(*getAlbumStruct)->year, album_root, "year");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->genre, album_root, "genre");
|
||||||
|
|
||||||
|
cJSON* song_root = cJSON_GetObjectItemCaseSensitive(album_root, "song");
|
||||||
|
if (song_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - song does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*getAlbumStruct)->songCount = cJSON_GetArraySize(song_root);
|
||||||
|
|
||||||
|
// Allocate and initialize
|
||||||
|
(*getAlbumStruct)->songs = (opensubsonic_getAlbum_song_struct*)malloc((*getAlbumStruct)->songCount * sizeof(opensubsonic_getAlbum_song_struct));
|
||||||
|
|
||||||
|
for (int i = 0; i < (*getAlbumStruct)->songCount; i++) {
|
||||||
|
(*getAlbumStruct)->songs[i].id = NULL;
|
||||||
|
(*getAlbumStruct)->songs[i].parent = NULL;
|
||||||
|
(*getAlbumStruct)->songs[i].title = NULL;
|
||||||
|
(*getAlbumStruct)->songs[i].albumId = NULL;
|
||||||
|
(*getAlbumStruct)->songs[i].album = NULL;
|
||||||
|
(*getAlbumStruct)->songs[i].artistId = NULL;
|
||||||
|
(*getAlbumStruct)->songs[i].artist = NULL;
|
||||||
|
(*getAlbumStruct)->songs[i].coverArt = NULL;
|
||||||
|
(*getAlbumStruct)->songs[i].duration = 0;
|
||||||
|
(*getAlbumStruct)->songs[i].bitRate = 0;
|
||||||
|
(*getAlbumStruct)->songs[i].bitDepth = 0;
|
||||||
|
(*getAlbumStruct)->songs[i].samplingRate = 0;
|
||||||
|
(*getAlbumStruct)->songs[i].channelCount = 0;
|
||||||
|
(*getAlbumStruct)->songs[i].track = 0;
|
||||||
|
(*getAlbumStruct)->songs[i].year = 0;
|
||||||
|
(*getAlbumStruct)->songs[i].genre = NULL;
|
||||||
|
(*getAlbumStruct)->songs[i].size = 0;
|
||||||
|
(*getAlbumStruct)->songs[i].discNumber = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < (*getAlbumStruct)->songCount; i++) {
|
||||||
|
cJSON* array_song_root = cJSON_GetArrayItem(song_root, i);
|
||||||
|
if (array_song_root != NULL) {
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->songs[i].id, array_song_root, "id");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->songs[i].parent, array_song_root, "parent");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->songs[i].title, array_song_root, "title");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->songs[i].albumId, array_song_root, "albumId");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->songs[i].album, array_song_root, "album");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->songs[i].artistId, array_song_root, "artistId");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->songs[i].artist, array_song_root, "artist");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->songs[i].coverArt, array_song_root, "coverArt");
|
||||||
|
OSS_Ploj(&(*getAlbumStruct)->songs[i].duration, array_song_root, "duration");
|
||||||
|
OSS_Pioj(&(*getAlbumStruct)->songs[i].bitRate, array_song_root, "bitRate");
|
||||||
|
OSS_Pioj(&(*getAlbumStruct)->songs[i].bitDepth, array_song_root, "bitDepth");
|
||||||
|
OSS_Ploj(&(*getAlbumStruct)->songs[i].samplingRate, array_song_root, "samplingRate");
|
||||||
|
OSS_Pioj(&(*getAlbumStruct)->songs[i].channelCount, array_song_root, "channelCount");
|
||||||
|
OSS_Pioj(&(*getAlbumStruct)->songs[i].track, array_song_root, "track");
|
||||||
|
OSS_Pioj(&(*getAlbumStruct)->songs[i].year, array_song_root, "year");
|
||||||
|
OSS_Psoj(&(*getAlbumStruct)->songs[i].genre, array_song_root, "genre");
|
||||||
|
OSS_Ploj(&(*getAlbumStruct)->songs[i].size, array_song_root, "size");
|
||||||
|
OSS_Pioj(&(*getAlbumStruct)->songs[i].discNumber, array_song_root, "discNumber");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_getAlbum_struct_free(opensubsonic_getAlbum_struct** getAlbumStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /getAlbum endpoint heap objects.");
|
||||||
|
if ((*getAlbumStruct)->status != NULL) { free((*getAlbumStruct)->status); }
|
||||||
|
if ((*getAlbumStruct)->errorMessage != NULL) { free((*getAlbumStruct)->errorMessage); }
|
||||||
|
if ((*getAlbumStruct)->id != NULL) { free((*getAlbumStruct)->id); }
|
||||||
|
if ((*getAlbumStruct)->parent != NULL) { free((*getAlbumStruct)->parent); }
|
||||||
|
if ((*getAlbumStruct)->album != NULL) { free((*getAlbumStruct)->album); }
|
||||||
|
if ((*getAlbumStruct)->title != NULL) { free((*getAlbumStruct)->title); }
|
||||||
|
if ((*getAlbumStruct)->name != NULL) { free((*getAlbumStruct)->name); }
|
||||||
|
if ((*getAlbumStruct)->coverArt != NULL) { free((*getAlbumStruct)->coverArt); }
|
||||||
|
if ((*getAlbumStruct)->created != NULL) { free((*getAlbumStruct)->created); }
|
||||||
|
if ((*getAlbumStruct)->artistId != NULL) { free((*getAlbumStruct)->artistId); }
|
||||||
|
if ((*getAlbumStruct)->artist != NULL) { free((*getAlbumStruct)->artist); }
|
||||||
|
if ((*getAlbumStruct)->genre != NULL) { free((*getAlbumStruct)->genre); }
|
||||||
|
for (int i = 0; i < (*getAlbumStruct)->songCount; i++) {
|
||||||
|
if ((*getAlbumStruct)->songs[i].id != NULL) { free((*getAlbumStruct)->songs[i].id); }
|
||||||
|
if ((*getAlbumStruct)->songs[i].parent != NULL) { free((*getAlbumStruct)->songs[i].parent); }
|
||||||
|
if ((*getAlbumStruct)->songs[i].title != NULL) { free((*getAlbumStruct)->songs[i].title); }
|
||||||
|
if ((*getAlbumStruct)->songs[i].albumId != NULL) { free((*getAlbumStruct)->songs[i].albumId); }
|
||||||
|
if ((*getAlbumStruct)->songs[i].album != NULL) { free((*getAlbumStruct)->songs[i].album); }
|
||||||
|
if ((*getAlbumStruct)->songs[i].artistId != NULL) { free((*getAlbumStruct)->songs[i].artistId); }
|
||||||
|
if ((*getAlbumStruct)->songs[i].artist != NULL) { free((*getAlbumStruct)->songs[i].artist); }
|
||||||
|
if ((*getAlbumStruct)->songs[i].coverArt != NULL) { free((*getAlbumStruct)->songs[i].coverArt); }
|
||||||
|
if ((*getAlbumStruct)->songs[i].genre != NULL) { free((*getAlbumStruct)->songs[i].genre); }
|
||||||
|
}
|
||||||
|
if ((*getAlbumStruct)->songs != NULL) { free((*getAlbumStruct)->songs); }
|
||||||
|
if (*getAlbumStruct != NULL) { free(*getAlbumStruct); }
|
||||||
|
}
|
||||||
52
src/libopensubsonic/endpoint_getAlbum.h
Normal file
52
src/libopensubsonic/endpoint_getAlbum.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#ifndef _ENDPOINT_GETALBUM_H
|
||||||
|
#define _ENDPOINT_GETALBUM_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* id;
|
||||||
|
char* parent;
|
||||||
|
char* title;
|
||||||
|
// 'isDir', 'isVideo', 'type' excluded
|
||||||
|
char* albumId;
|
||||||
|
char* album;
|
||||||
|
char* artistId;
|
||||||
|
char* artist;
|
||||||
|
char* coverArt;
|
||||||
|
long duration;
|
||||||
|
int bitRate;
|
||||||
|
int bitDepth;
|
||||||
|
long samplingRate;
|
||||||
|
int channelCount;
|
||||||
|
int track; // Use songCount index instead
|
||||||
|
int year;
|
||||||
|
char* genre;
|
||||||
|
long size;
|
||||||
|
int discNumber;
|
||||||
|
// 'suffix', 'contentType', 'path' excluded
|
||||||
|
} opensubsonic_getAlbum_song_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status;
|
||||||
|
int errorCode;
|
||||||
|
char* errorMessage;
|
||||||
|
char* id;
|
||||||
|
char* parent;
|
||||||
|
char* album;
|
||||||
|
char* title;
|
||||||
|
char* name;
|
||||||
|
// 'isDir' excluded
|
||||||
|
char* coverArt;
|
||||||
|
char* created;
|
||||||
|
long duration;
|
||||||
|
int playCount;
|
||||||
|
char* artistId;
|
||||||
|
char* artist;
|
||||||
|
int year;
|
||||||
|
char* genre;
|
||||||
|
int songCount; // Counted, not retrieved from JSON
|
||||||
|
opensubsonic_getAlbum_song_struct* songs;
|
||||||
|
} opensubsonic_getAlbum_struct;
|
||||||
|
|
||||||
|
int opensubsonic_getAlbum_parse(char* data, opensubsonic_getAlbum_struct** getAlbumStruct);
|
||||||
|
void opensubsonic_getAlbum_struct_free(opensubsonic_getAlbum_struct** getAlbumStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
134
src/libopensubsonic/endpoint_getAlbumList.c
Normal file
134
src/libopensubsonic/endpoint_getAlbumList.c
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_getAlbumList.h"
|
||||||
|
|
||||||
|
// Returns 1 if failure occured, else 0
|
||||||
|
int opensubsonic_getAlbumList_parse(char* data, opensubsonic_getAlbumList_struct** getAlbumListStruct) {
|
||||||
|
// Allocate struct and initialize variables
|
||||||
|
(*getAlbumListStruct) = (opensubsonic_getAlbumList_struct*)malloc(sizeof(opensubsonic_getAlbumList_struct));
|
||||||
|
(*getAlbumListStruct)->status = NULL;
|
||||||
|
(*getAlbumListStruct)->errorCode = 0;
|
||||||
|
(*getAlbumListStruct)->errorMessage = NULL;
|
||||||
|
(*getAlbumListStruct)->albumCount = 0;
|
||||||
|
(*getAlbumListStruct)->albums = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->status, subsonic_root, "status");
|
||||||
|
|
||||||
|
// Check if API has returned an error
|
||||||
|
if (strstr((*getAlbumListStruct)->status, "ok") == NULL) {
|
||||||
|
// API has not returned 'ok' in status, fetch error, and return
|
||||||
|
// Check if an error is present
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
// Error not defined in JSON
|
||||||
|
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Pioj(&(*getAlbumListStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getAlbumListStruct)->errorCode, (*getAlbumListStruct)->errorMessage);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* albumList_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "albumList");
|
||||||
|
if (albumList_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - albumList does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(albumList_root, "album");
|
||||||
|
if (album_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - album does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*getAlbumListStruct)->albumCount = cJSON_GetArraySize(album_root);
|
||||||
|
|
||||||
|
// Allocate and initialize
|
||||||
|
(*getAlbumListStruct)->albums = (opensubsonic_getAlbumList_album_struct*)malloc((*getAlbumListStruct)->albumCount * sizeof(opensubsonic_getAlbumList_album_struct));
|
||||||
|
for (int i = 0; i < (*getAlbumListStruct)->albumCount; i++) {
|
||||||
|
(*getAlbumListStruct)->albums[i].id = NULL;
|
||||||
|
(*getAlbumListStruct)->albums[i].parent = NULL;
|
||||||
|
(*getAlbumListStruct)->albums[i].album = NULL;
|
||||||
|
(*getAlbumListStruct)->albums[i].title = NULL;
|
||||||
|
(*getAlbumListStruct)->albums[i].name = NULL;
|
||||||
|
(*getAlbumListStruct)->albums[i].coverArt = NULL;
|
||||||
|
(*getAlbumListStruct)->albums[i].songCount = 0;
|
||||||
|
(*getAlbumListStruct)->albums[i].created = NULL;
|
||||||
|
(*getAlbumListStruct)->albums[i].duration = 0;
|
||||||
|
(*getAlbumListStruct)->albums[i].playCount = 0;
|
||||||
|
(*getAlbumListStruct)->albums[i].artistId = NULL;
|
||||||
|
(*getAlbumListStruct)->albums[i].artist = NULL;
|
||||||
|
(*getAlbumListStruct)->albums[i].year = 0;
|
||||||
|
(*getAlbumListStruct)->albums[i].genre = NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < (*getAlbumListStruct)->albumCount; i++) {
|
||||||
|
cJSON* array_album_root = cJSON_GetArrayItem(album_root, i);
|
||||||
|
if (array_album_root != NULL) {
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->albums[i].id, array_album_root, "id");
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->albums[i].parent, array_album_root, "parent");
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->albums[i].album, array_album_root, "album");
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->albums[i].title, array_album_root, "title");
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->albums[i].name, array_album_root, "name");
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->albums[i].coverArt, array_album_root, "coverArt");
|
||||||
|
OSS_Pioj(&(*getAlbumListStruct)->albums[i].songCount, array_album_root, "songCount");
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->albums[i].created, array_album_root, "created");
|
||||||
|
OSS_Ploj(&(*getAlbumListStruct)->albums[i].duration, array_album_root, "duration");
|
||||||
|
OSS_Pioj(&(*getAlbumListStruct)->albums[i].playCount, array_album_root, "playCount");
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->albums[i].artistId, array_album_root, "artistId");
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->albums[i].artist, array_album_root, "artist");
|
||||||
|
OSS_Pioj(&(*getAlbumListStruct)->albums[i].year, array_album_root, "year");
|
||||||
|
OSS_Psoj(&(*getAlbumListStruct)->albums[i].genre, array_album_root, "genre");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_getAlbumList_struct_free(opensubsonic_getAlbumList_struct** getAlbumListStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /getAlbumList endpoint heap objects.");
|
||||||
|
if ((*getAlbumListStruct)->status != NULL) { free((*getAlbumListStruct)->status); }
|
||||||
|
if ((*getAlbumListStruct)->errorMessage != NULL) { free((*getAlbumListStruct)->errorMessage); }
|
||||||
|
for (int i = 0; i < (*getAlbumListStruct)->albumCount; i++) {
|
||||||
|
if ((*getAlbumListStruct)->albums[i].id != NULL) { free((*getAlbumListStruct)->albums[i].id); }
|
||||||
|
if ((*getAlbumListStruct)->albums[i].parent != NULL) { free((*getAlbumListStruct)->albums[i].parent); }
|
||||||
|
if ((*getAlbumListStruct)->albums[i].album != NULL) { free((*getAlbumListStruct)->albums[i].album); }
|
||||||
|
if ((*getAlbumListStruct)->albums[i].title != NULL) { free((*getAlbumListStruct)->albums[i].title); }
|
||||||
|
if ((*getAlbumListStruct)->albums[i].name != NULL) { free((*getAlbumListStruct)->albums[i].name); }
|
||||||
|
if ((*getAlbumListStruct)->albums[i].coverArt != NULL) { free((*getAlbumListStruct)->albums[i].coverArt); }
|
||||||
|
if ((*getAlbumListStruct)->albums[i].created != NULL) { free((*getAlbumListStruct)->albums[i].created); }
|
||||||
|
if ((*getAlbumListStruct)->albums[i].artistId != NULL) { free((*getAlbumListStruct)->albums[i].artistId); }
|
||||||
|
if ((*getAlbumListStruct)->albums[i].artist != NULL) { free((*getAlbumListStruct)->albums[i].artist); }
|
||||||
|
if ((*getAlbumListStruct)->albums[i].genre != NULL) { free((*getAlbumListStruct)->albums[i].genre); }
|
||||||
|
}
|
||||||
|
if ((*getAlbumListStruct)->albums != NULL) { free((*getAlbumListStruct)->albums); }
|
||||||
|
if (*getAlbumListStruct != NULL) { free(*getAlbumListStruct); }
|
||||||
|
}
|
||||||
33
src/libopensubsonic/endpoint_getAlbumList.h
Normal file
33
src/libopensubsonic/endpoint_getAlbumList.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef _ENDPOINT_GETALBUMLIST_H
|
||||||
|
#define _ENDPOINT_GETALBUMLIST_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* id;
|
||||||
|
char* parent;
|
||||||
|
char* album;
|
||||||
|
char* title;
|
||||||
|
char* name;
|
||||||
|
// 'isDir' excluded
|
||||||
|
char* coverArt;
|
||||||
|
int songCount; // Only use as a guideline, count manually when possible
|
||||||
|
char* created;
|
||||||
|
long duration;
|
||||||
|
int playCount;
|
||||||
|
char* artistId;
|
||||||
|
char* artist;
|
||||||
|
int year;
|
||||||
|
char* genre;
|
||||||
|
} opensubsonic_getAlbumList_album_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status;
|
||||||
|
int errorCode;
|
||||||
|
char* errorMessage;
|
||||||
|
int albumCount; // Album count (Counted from array, NOT from JSON)
|
||||||
|
opensubsonic_getAlbumList_album_struct* albums;
|
||||||
|
} opensubsonic_getAlbumList_struct;
|
||||||
|
|
||||||
|
int opensubsonic_getAlbumList_parse(char* data, opensubsonic_getAlbumList_struct** getAlbumListStruct);
|
||||||
|
void opensubsonic_getAlbumList_struct_free(opensubsonic_getAlbumList_struct** getAlbumListStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
163
src/libopensubsonic/endpoint_getArtist.c
Normal file
163
src/libopensubsonic/endpoint_getArtist.c
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_getArtist.h"
|
||||||
|
|
||||||
|
// Parse the JSON returned from the /rest/getArtist endpoint
|
||||||
|
// Returns 1 if failure occured, else 0
|
||||||
|
int opensubsonic_getArtist_parse(char* data, opensubsonic_getArtist_struct** getArtistStruct) {
|
||||||
|
// Allocate struct
|
||||||
|
*getArtistStruct = (opensubsonic_getArtist_struct*)malloc(sizeof(opensubsonic_getArtist_struct));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
(*getArtistStruct)->status = NULL;
|
||||||
|
(*getArtistStruct)->errorCode = 0;
|
||||||
|
(*getArtistStruct)->errorMessage = NULL;
|
||||||
|
(*getArtistStruct)->artistId = NULL;
|
||||||
|
(*getArtistStruct)->artistName = NULL;
|
||||||
|
(*getArtistStruct)->coverArt = NULL;
|
||||||
|
(*getArtistStruct)->albumCount = 0;
|
||||||
|
(*getArtistStruct)->userRating = 0;
|
||||||
|
(*getArtistStruct)->artistImageUrl = NULL;
|
||||||
|
(*getArtistStruct)->starred = NULL;
|
||||||
|
(*getArtistStruct)->albums = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->status, subsonic_root, "status");
|
||||||
|
|
||||||
|
// Check if API has returned an error
|
||||||
|
if (strstr((*getArtistStruct)->status, "ok") == NULL) {
|
||||||
|
// API has not returned 'ok' in status, fetch error, and return
|
||||||
|
// Check if an error is present
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
// Error not defined in JSON
|
||||||
|
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Pioj(&(*getArtistStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getArtistStruct)->errorCode, (*getArtistStruct)->errorMessage);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from artist
|
||||||
|
cJSON* artist_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "artist");
|
||||||
|
if (artist_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - artist does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->artistId, artist_root, "id");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->artistName, artist_root, "name");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->coverArt, artist_root, "coverArt");
|
||||||
|
OSS_Pioj(&(*getArtistStruct)->userRating, artist_root, "userRating");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->artistImageUrl, artist_root, "artistImageUrl");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->starred, artist_root, "starred");
|
||||||
|
|
||||||
|
// Make an object from album
|
||||||
|
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(artist_root, "album");
|
||||||
|
if (album_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - album does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the amount of albums
|
||||||
|
(*getArtistStruct)->albumCount = cJSON_GetArraySize(album_root);
|
||||||
|
|
||||||
|
// Allocate memory for albums
|
||||||
|
(*getArtistStruct)->albums = (opensubsonic_getArtist_album_struct*)malloc((*getArtistStruct)->albumCount * sizeof(opensubsonic_getArtist_album_struct));
|
||||||
|
|
||||||
|
// Initialize variables
|
||||||
|
for (int i = 0; i < (*getArtistStruct)->albumCount; i++) {
|
||||||
|
(*getArtistStruct)->albums[i].id = NULL;
|
||||||
|
(*getArtistStruct)->albums[i].parent = NULL;
|
||||||
|
(*getArtistStruct)->albums[i].title = NULL;
|
||||||
|
(*getArtistStruct)->albums[i].name = NULL;
|
||||||
|
(*getArtistStruct)->albums[i].coverArt = NULL;
|
||||||
|
(*getArtistStruct)->albums[i].songCount = 0;
|
||||||
|
(*getArtistStruct)->albums[i].created = NULL;
|
||||||
|
(*getArtistStruct)->albums[i].duration = 0;
|
||||||
|
(*getArtistStruct)->albums[i].playCount = 0;
|
||||||
|
(*getArtistStruct)->albums[i].artistId = NULL;
|
||||||
|
(*getArtistStruct)->albums[i].artist = NULL;
|
||||||
|
(*getArtistStruct)->albums[i].year = 0;
|
||||||
|
(*getArtistStruct)->albums[i].genre = NULL;
|
||||||
|
(*getArtistStruct)->albums[i].userRating = 0;
|
||||||
|
(*getArtistStruct)->albums[i].starred = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the album array
|
||||||
|
for (int i = 0; i < (*getArtistStruct)->albumCount; i++) {
|
||||||
|
cJSON* array_album_root = cJSON_GetArrayItem(album_root, i);
|
||||||
|
if (array_album_root != NULL) {
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->albums[i].id, array_album_root, "id");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->albums[i].parent, array_album_root, "parent");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->albums[i].title, array_album_root, "title");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->albums[i].name, array_album_root, "name");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->albums[i].coverArt, array_album_root, "coverArt");
|
||||||
|
OSS_Pioj(&(*getArtistStruct)->albums[i].songCount, array_album_root, "songCount");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->albums[i].created, array_album_root, "created");
|
||||||
|
OSS_Ploj(&(*getArtistStruct)->albums[i].duration, array_album_root, "duration");
|
||||||
|
OSS_Pioj(&(*getArtistStruct)->albums[i].playCount, array_album_root, "playCount");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->albums[i].artistId, array_album_root, "artistId");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->albums[i].artist, array_album_root, "artist");
|
||||||
|
OSS_Pioj(&(*getArtistStruct)->albums[i].year, array_album_root, "year");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->albums[i].genre, array_album_root, "genre");
|
||||||
|
OSS_Pioj(&(*getArtistStruct)->albums[i].userRating, array_album_root, "userRating");
|
||||||
|
OSS_Psoj(&(*getArtistStruct)->albums[i].starred, array_album_root, "starred");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the dynamically allocated elements of the opensubsonic_getArtist_struct structure and the opensubsonic_getArtist_album_struct array structs.
|
||||||
|
void opensubsonic_getArtist_struct_free(opensubsonic_getArtist_struct** getArtistStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /getArtist endpoint heap objects.");
|
||||||
|
if((*getArtistStruct)->status != NULL) { free((*getArtistStruct)->status); }
|
||||||
|
if((*getArtistStruct)->errorMessage != NULL) { free((*getArtistStruct)->errorMessage); }
|
||||||
|
if((*getArtistStruct)->artistId != NULL) { free((*getArtistStruct)->artistId); }
|
||||||
|
if((*getArtistStruct)->artistName != NULL) { free((*getArtistStruct)->artistName); }
|
||||||
|
if((*getArtistStruct)->coverArt != NULL) { free((*getArtistStruct)->coverArt); }
|
||||||
|
if((*getArtistStruct)->artistImageUrl != NULL) { free((*getArtistStruct)->artistImageUrl); }
|
||||||
|
if((*getArtistStruct)->starred != NULL) { free((*getArtistStruct)->starred); }
|
||||||
|
for (int i = 0; i < (*getArtistStruct)->albumCount; i++) {
|
||||||
|
if((*getArtistStruct)->albums[i].id != NULL) { free((*getArtistStruct)->albums[i].id); }
|
||||||
|
if((*getArtistStruct)->albums[i].parent != NULL) { free((*getArtistStruct)->albums[i].parent); }
|
||||||
|
if((*getArtistStruct)->albums[i].title != NULL) { free((*getArtistStruct)->albums[i].title); }
|
||||||
|
if((*getArtistStruct)->albums[i].name != NULL) { free((*getArtistStruct)->albums[i].name); }
|
||||||
|
if((*getArtistStruct)->albums[i].coverArt != NULL) { free((*getArtistStruct)->albums[i].coverArt); }
|
||||||
|
if((*getArtistStruct)->albums[i].created != NULL) { free((*getArtistStruct)->albums[i].created); }
|
||||||
|
if((*getArtistStruct)->albums[i].artistId != NULL) { free((*getArtistStruct)->albums[i].artistId); }
|
||||||
|
if((*getArtistStruct)->albums[i].artist != NULL) { free((*getArtistStruct)->albums[i].artist); }
|
||||||
|
if((*getArtistStruct)->albums[i].genre != NULL) { free((*getArtistStruct)->albums[i].genre); }
|
||||||
|
if((*getArtistStruct)->albums[i].starred != NULL) { free((*getArtistStruct)->albums[i].starred); }
|
||||||
|
}
|
||||||
|
if((*getArtistStruct)->albums != NULL) { free((*getArtistStruct)->albums); }
|
||||||
|
if(*getArtistStruct != NULL) { free(*getArtistStruct); }
|
||||||
|
}
|
||||||
41
src/libopensubsonic/endpoint_getArtist.h
Normal file
41
src/libopensubsonic/endpoint_getArtist.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef _ENDPOINT_GETARTIST_H
|
||||||
|
#define _ENDPOINT_GETARTIST_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* id; // Album ID
|
||||||
|
char* parent; // Parent ID
|
||||||
|
char* title; // Album Title
|
||||||
|
char* name; // Album Name
|
||||||
|
// 'isDir' excluded
|
||||||
|
char* coverArt; // Cover art ID
|
||||||
|
int songCount;
|
||||||
|
char* created; // Time created
|
||||||
|
long duration; // Duration of the album in seconds
|
||||||
|
int playCount;
|
||||||
|
char* artistId; // Artist ID
|
||||||
|
char* artist; // Artist Name
|
||||||
|
int year; // Year released
|
||||||
|
char* genre;
|
||||||
|
int userRating;
|
||||||
|
char* starred; // Time starred
|
||||||
|
} opensubsonic_getArtist_album_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status;
|
||||||
|
int errorCode;
|
||||||
|
char* errorMessage;
|
||||||
|
char* artistId; // Artist ID
|
||||||
|
char* artistName; // Artist name
|
||||||
|
char* coverArt; // Artist cover art ID
|
||||||
|
int userRating;
|
||||||
|
char* artistImageUrl;
|
||||||
|
char* starred; // Time starred
|
||||||
|
// 'musicBrainzId', 'sorted', 'roles' excluded
|
||||||
|
int albumCount; // Album count (Counted from array, NOT from JSON)
|
||||||
|
opensubsonic_getArtist_album_struct* albums;
|
||||||
|
} opensubsonic_getArtist_struct;
|
||||||
|
|
||||||
|
int opensubsonic_getArtist_parse(char* data, opensubsonic_getArtist_struct** getArtistStruct);
|
||||||
|
void opensubsonic_getArtist_struct_free(opensubsonic_getArtist_struct** getArtistStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
144
src/libopensubsonic/endpoint_getArtists.c
Normal file
144
src/libopensubsonic/endpoint_getArtists.c
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_getArtists.h"
|
||||||
|
|
||||||
|
// Parse the JSON returned from the /rest/getArtists endpoint
|
||||||
|
// Returns 1 if failure occured, else 0
|
||||||
|
int opensubsonic_getArtists_parse(char* data, opensubsonic_getArtists_struct** getArtistsStruct) {
|
||||||
|
// Allocate struct
|
||||||
|
*getArtistsStruct = (opensubsonic_getArtists_struct*)malloc(sizeof(opensubsonic_getArtists_struct));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
(*getArtistsStruct)->status = NULL;
|
||||||
|
(*getArtistsStruct)->errorMessage = NULL;
|
||||||
|
(*getArtistsStruct)->errorCode = 0;
|
||||||
|
(*getArtistsStruct)->artistCount = 0;
|
||||||
|
(*getArtistsStruct)->artists = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getArtistsStruct)->status, subsonic_root, "status");
|
||||||
|
|
||||||
|
// Check if API has returned an error
|
||||||
|
if (strstr((*getArtistsStruct)->status, "ok") == NULL) {
|
||||||
|
// API has not returned 'ok' in status, fetch error, and return
|
||||||
|
// Check if an error is present
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
// Error not defined in JSON
|
||||||
|
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Pioj(&(*getArtistsStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*getArtistsStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getArtistsStruct)->errorCode, (*getArtistsStruct)->errorMessage);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from artists
|
||||||
|
cJSON* artists_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "artists");
|
||||||
|
if (artists_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - artists does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from index
|
||||||
|
cJSON* index_root = cJSON_GetObjectItemCaseSensitive(artists_root, "index");
|
||||||
|
if (index_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - index does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the amount of artists present for malloc (Copied from function below)
|
||||||
|
int totalArtistIndex = 0;
|
||||||
|
for (int i = 0; i < cJSON_GetArraySize(index_root); i++) {
|
||||||
|
cJSON* array_letter_root = cJSON_GetArrayItem(index_root, i);
|
||||||
|
|
||||||
|
cJSON* current_letter_artists_array = cJSON_GetObjectItemCaseSensitive(array_letter_root, "artist");
|
||||||
|
if (current_letter_artists_array != NULL) {
|
||||||
|
// Array of artists starting with the same letter
|
||||||
|
for (int j = 0; j < cJSON_GetArraySize(current_letter_artists_array); j++) {
|
||||||
|
cJSON* current_letter_artist_array_layer_b = cJSON_GetArrayItem(current_letter_artists_array, j);
|
||||||
|
if (current_letter_artist_array_layer_b != NULL) {
|
||||||
|
// Increment total artist index
|
||||||
|
(*getArtistsStruct)->artistCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate memory for opensubsonic_getArtists_artist_struct inside opensubsonic_getArtists_struct (Where the artist data is held)
|
||||||
|
(*getArtistsStruct)->artists = (opensubsonic_getArtists_artist_struct*)malloc((*getArtistsStruct)->artistCount * sizeof(opensubsonic_getArtists_artist_struct));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
for (int i = 0; i < (*getArtistsStruct)->artistCount; i++) {
|
||||||
|
(*getArtistsStruct)->artists[i].name = NULL;
|
||||||
|
(*getArtistsStruct)->artists[i].id = NULL;
|
||||||
|
(*getArtistsStruct)->artists[i].coverArt = NULL;
|
||||||
|
(*getArtistsStruct)->artists[i].albumCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the alphabet array (Each element in this array is the first letter of the artist, organized)
|
||||||
|
int currentArtistIndex = 0;
|
||||||
|
for (int i = 0; i < cJSON_GetArraySize(index_root); i++) {
|
||||||
|
cJSON* array_letter_root = cJSON_GetArrayItem(index_root, i);
|
||||||
|
|
||||||
|
cJSON* current_letter_artists_array = cJSON_GetObjectItemCaseSensitive(array_letter_root, "artist");
|
||||||
|
if (current_letter_artists_array != NULL) {
|
||||||
|
// Array of artists starting with the same letter
|
||||||
|
for (int j = 0; j < cJSON_GetArraySize(current_letter_artists_array); j++) {
|
||||||
|
cJSON* current_letter_artist_array_layer_b = cJSON_GetArrayItem(current_letter_artists_array, j);
|
||||||
|
if (current_letter_artist_array_layer_b != NULL) {
|
||||||
|
// Fetch information
|
||||||
|
OSS_Psoj(&(*getArtistsStruct)->artists[currentArtistIndex].name, current_letter_artist_array_layer_b, "name");
|
||||||
|
OSS_Psoj(&(*getArtistsStruct)->artists[currentArtistIndex].id, current_letter_artist_array_layer_b, "id");
|
||||||
|
OSS_Psoj(&(*getArtistsStruct)->artists[currentArtistIndex].coverArt, current_letter_artist_array_layer_b, "coverArt");
|
||||||
|
OSS_Pioj(&(*getArtistsStruct)->artists[currentArtistIndex].albumCount, current_letter_artist_array_layer_b, "albumCount");
|
||||||
|
|
||||||
|
// Increment current artist index
|
||||||
|
currentArtistIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the opensubsonic_getArtists_struct structure.
|
||||||
|
void opensubsonic_getArtists_struct_free(opensubsonic_getArtists_struct** getArtistsStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /getArtists endpoint heap objects.");
|
||||||
|
if ((*getArtistsStruct)->status != NULL) { free((*getArtistsStruct)->status); }
|
||||||
|
if ((*getArtistsStruct)->errorMessage != NULL) { free((*getArtistsStruct)->errorMessage); }
|
||||||
|
for (size_t i = 0; i < (*getArtistsStruct)->artistCount; i++) {
|
||||||
|
if ((*getArtistsStruct)->artists[i].name != NULL) { free((*getArtistsStruct)->artists[i].name); }
|
||||||
|
if ((*getArtistsStruct)->artists[i].id != NULL) { free((*getArtistsStruct)->artists[i].id); }
|
||||||
|
if ((*getArtistsStruct)->artists[i].coverArt != NULL) { free((*getArtistsStruct)->artists[i].coverArt); }
|
||||||
|
}
|
||||||
|
if ((*getArtistsStruct)->artists != NULL) { free((*getArtistsStruct)->artists); }
|
||||||
|
if (*getArtistsStruct != NULL) { free(*getArtistsStruct); }
|
||||||
|
}
|
||||||
22
src/libopensubsonic/endpoint_getArtists.h
Normal file
22
src/libopensubsonic/endpoint_getArtists.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef _ENDPOINT_GETARTISTS_H
|
||||||
|
#define _ENDPOINT_GETARTISTS_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* id;
|
||||||
|
char* name;
|
||||||
|
char* coverArt;
|
||||||
|
int albumCount;
|
||||||
|
} opensubsonic_getArtists_artist_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status;
|
||||||
|
int errorCode;
|
||||||
|
char* errorMessage;
|
||||||
|
int artistCount;
|
||||||
|
opensubsonic_getArtists_artist_struct* artists;
|
||||||
|
} opensubsonic_getArtists_struct;
|
||||||
|
|
||||||
|
int opensubsonic_getArtists_parse(char* data, opensubsonic_getArtists_struct** getArtistsStruct);
|
||||||
|
void opensubsonic_getArtists_struct_free(opensubsonic_getArtists_struct** getArtistsStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
129
src/libopensubsonic/endpoint_getLyricsBySongId.c
Normal file
129
src/libopensubsonic/endpoint_getLyricsBySongId.c
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_getLyricsBySongId.h"
|
||||||
|
|
||||||
|
// Returns 1 if failure occured, else 0
|
||||||
|
int opensubsonic_getLyricsBySongId_parse(char* data, opensubsonic_getLyricsBySongId_struct** getLyricsBySongIdStruct) {
|
||||||
|
// Allocate struct
|
||||||
|
*getLyricsBySongIdStruct = malloc(sizeof(opensubsonic_getLyricsBySongId_struct));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
(*getLyricsBySongIdStruct)->status = NULL;
|
||||||
|
(*getLyricsBySongIdStruct)->errorCode = 0;
|
||||||
|
(*getLyricsBySongIdStruct)->errorMessage = NULL;
|
||||||
|
(*getLyricsBySongIdStruct)->displayArtist = NULL;
|
||||||
|
(*getLyricsBySongIdStruct)->displayTitle = NULL;
|
||||||
|
(*getLyricsBySongIdStruct)->lyricsAmount = 0;
|
||||||
|
(*getLyricsBySongIdStruct)->lyrics = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getLyricsBySongIdStruct)->status, subsonic_root, "status");
|
||||||
|
|
||||||
|
// Check if API has returned an error
|
||||||
|
if (strstr((*getLyricsBySongIdStruct)->status, "ok") == NULL) {
|
||||||
|
// API has not returned 'ok' in status, fetch error, and return
|
||||||
|
// Check if an error is present
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
// Error not defined in JSON
|
||||||
|
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Pioj(&(*getLyricsBySongIdStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*getLyricsBySongIdStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getLyricsBySongIdStruct)->errorCode, (*getLyricsBySongIdStruct)->errorMessage);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from 'lyricsList'
|
||||||
|
cJSON* lyricsList_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "lyricsList");
|
||||||
|
if (lyricsList_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - lyricsList does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from 'structuredLyrics'
|
||||||
|
cJSON* structuredLyrics_root = cJSON_GetObjectItemCaseSensitive(lyricsList_root, "structuredLyrics");
|
||||||
|
if (structuredLyrics_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - structuredLyrics does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from the first index of 'structuredLyrics'
|
||||||
|
cJSON* structuredLyrics_idx0_root = cJSON_GetArrayItem(structuredLyrics_root, 0);
|
||||||
|
if (structuredLyrics_idx0_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - structuredLyrics (idx 0) does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getLyricsBySongIdStruct)->displayArtist, structuredLyrics_idx0_root, "displayArtist");
|
||||||
|
OSS_Psoj(&(*getLyricsBySongIdStruct)->displayTitle, structuredLyrics_idx0_root, "displayTitle");
|
||||||
|
|
||||||
|
// Make an object from 'line'
|
||||||
|
cJSON* line_root = cJSON_GetObjectItemCaseSensitive(structuredLyrics_idx0_root, "line");
|
||||||
|
if (line_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - line does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count amount of objects in 'line' and allocate memory
|
||||||
|
(*getLyricsBySongIdStruct)->lyricsAmount = cJSON_GetArraySize(line_root);
|
||||||
|
(*getLyricsBySongIdStruct)->lyrics = (opensubsonic_getLyricsBySongId_lyric_struct*)malloc((*getLyricsBySongIdStruct)->lyricsAmount * sizeof(opensubsonic_getLyricsBySongId_lyric_struct));
|
||||||
|
|
||||||
|
// Initialize variables
|
||||||
|
for (int i = 0; i < (*getLyricsBySongIdStruct)->lyricsAmount; i++) {
|
||||||
|
(*getLyricsBySongIdStruct)->lyrics[i].data = NULL;
|
||||||
|
(*getLyricsBySongIdStruct)->lyrics[i].offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract lyrics
|
||||||
|
for (int i = 0; i < (*getLyricsBySongIdStruct)->lyricsAmount; i++) {
|
||||||
|
cJSON* curr_idx_root = cJSON_GetArrayItem(line_root, i);
|
||||||
|
if (curr_idx_root != NULL) {
|
||||||
|
OSS_Psoj(&(*getLyricsBySongIdStruct)->lyrics[i].data, curr_idx_root, "value");
|
||||||
|
OSS_Ploj(&(*getLyricsBySongIdStruct)->lyrics[i].offset, curr_idx_root, "start");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_getLyricsBySongId_struct_free(opensubsonic_getLyricsBySongId_struct** getLyricsBySongIdStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /getLyricsBySongId endpoint heap objects.");
|
||||||
|
if ((*getLyricsBySongIdStruct)->status != NULL) { free((*getLyricsBySongIdStruct)->status); }
|
||||||
|
if ((*getLyricsBySongIdStruct)->errorMessage != NULL) { free((*getLyricsBySongIdStruct)->errorMessage); }
|
||||||
|
if ((*getLyricsBySongIdStruct)->displayArtist != NULL) { free((*getLyricsBySongIdStruct)->displayArtist); }
|
||||||
|
if ((*getLyricsBySongIdStruct)->displayTitle != NULL) { free((*getLyricsBySongIdStruct)->displayTitle); }
|
||||||
|
for (int i = 0; i < (*getLyricsBySongIdStruct)->lyricsAmount; i++) {
|
||||||
|
if ((*getLyricsBySongIdStruct)->lyrics[i].data != NULL) { free((*getLyricsBySongIdStruct)->lyrics[i].data); }
|
||||||
|
}
|
||||||
|
if ((*getLyricsBySongIdStruct)->lyrics != NULL) { free((*getLyricsBySongIdStruct)->lyrics); }
|
||||||
|
if (*getLyricsBySongIdStruct != NULL) { free(*getLyricsBySongIdStruct); }
|
||||||
|
}
|
||||||
22
src/libopensubsonic/endpoint_getLyricsBySongId.h
Normal file
22
src/libopensubsonic/endpoint_getLyricsBySongId.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef _ENDPOINT_GETLYRICSBYSONGID_H
|
||||||
|
#define _ENDPOINT_GETLYRICSBYSONGID_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* data;
|
||||||
|
long offset;
|
||||||
|
} opensubsonic_getLyricsBySongId_lyric_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status;
|
||||||
|
int errorCode;
|
||||||
|
char* errorMessage;
|
||||||
|
char* displayArtist;
|
||||||
|
char* displayTitle;
|
||||||
|
int lyricsAmount;
|
||||||
|
opensubsonic_getLyricsBySongId_lyric_struct* lyrics;
|
||||||
|
} opensubsonic_getLyricsBySongId_struct;
|
||||||
|
|
||||||
|
int opensubsonic_getLyricsBySongId_parse(char* data, opensubsonic_getLyricsBySongId_struct** getLyricsBySongIdStruct);
|
||||||
|
void opensubsonic_getLyricsBySongId_struct_free(opensubsonic_getLyricsBySongId_struct** getLyricsBySongIdStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
166
src/libopensubsonic/endpoint_getPlaylist.c
Normal file
166
src/libopensubsonic/endpoint_getPlaylist.c
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_getPlaylist.h"
|
||||||
|
|
||||||
|
int opensubsonic_getPlaylist_parse(char* data, opensubsonic_getPlaylist_struct** getPlaylistStruct) {
|
||||||
|
// Allocate struct
|
||||||
|
*getPlaylistStruct = (opensubsonic_getPlaylist_struct*)malloc(sizeof(opensubsonic_getPlaylist_struct));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
(*getPlaylistStruct)->status = NULL;
|
||||||
|
(*getPlaylistStruct)->errorCode = 0;
|
||||||
|
(*getPlaylistStruct)->errorMessage = NULL;
|
||||||
|
(*getPlaylistStruct)->id = NULL;
|
||||||
|
(*getPlaylistStruct)->name = NULL;
|
||||||
|
(*getPlaylistStruct)->owner = NULL;
|
||||||
|
(*getPlaylistStruct)->isPublic = false;
|
||||||
|
(*getPlaylistStruct)->created = NULL;
|
||||||
|
(*getPlaylistStruct)->changed = NULL;
|
||||||
|
(*getPlaylistStruct)->songCount = 0;
|
||||||
|
(*getPlaylistStruct)->duration = 0;
|
||||||
|
(*getPlaylistStruct)->songs = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->status, subsonic_root, "status");
|
||||||
|
|
||||||
|
// Check if API has returned an error
|
||||||
|
if (strstr((*getPlaylistStruct)->status, "ok") == NULL) {
|
||||||
|
// API has not returned 'ok' in status, fetch error, and return
|
||||||
|
// Check if an error is present
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
// Error not defined in JSON
|
||||||
|
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Pioj(&(*getPlaylistStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getPlaylistStruct)->errorCode, (*getPlaylistStruct)->errorMessage);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from 'playlist'
|
||||||
|
cJSON* playlist_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "playlist");
|
||||||
|
if (playlist_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - playlist does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->id, playlist_root, "id");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->name, playlist_root, "name");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->owner, playlist_root, "owner");
|
||||||
|
OSS_Pboj(&(*getPlaylistStruct)->isPublic, playlist_root, "public");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->created, playlist_root, "created");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->changed, playlist_root, "changed");
|
||||||
|
OSS_Ploj(&(*getPlaylistStruct)->duration, playlist_root, "duration");
|
||||||
|
|
||||||
|
// Make an object from 'entry'
|
||||||
|
cJSON* entry_root = cJSON_GetObjectItemCaseSensitive(playlist_root, "entry");
|
||||||
|
if (entry_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - entry does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the amount of songs in the playlist, and allocate memory
|
||||||
|
(*getPlaylistStruct)->songCount = cJSON_GetArraySize(entry_root);
|
||||||
|
(*getPlaylistStruct)->songs = (opensubsonic_getPlaylist_songs_struct*)malloc((*getPlaylistStruct)->songCount * sizeof(opensubsonic_getPlaylist_songs_struct));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
for (int i = 0; i < (*getPlaylistStruct)->songCount; i++) {
|
||||||
|
(*getPlaylistStruct)->songs[i].id = NULL;
|
||||||
|
(*getPlaylistStruct)->songs[i].parent = NULL;
|
||||||
|
(*getPlaylistStruct)->songs[i].title = NULL;
|
||||||
|
(*getPlaylistStruct)->songs[i].albumId = NULL;
|
||||||
|
(*getPlaylistStruct)->songs[i].album = NULL;
|
||||||
|
(*getPlaylistStruct)->songs[i].artistId = NULL;
|
||||||
|
(*getPlaylistStruct)->songs[i].artist = NULL;
|
||||||
|
(*getPlaylistStruct)->songs[i].coverArt = NULL;
|
||||||
|
(*getPlaylistStruct)->songs[i].duration = 0;
|
||||||
|
(*getPlaylistStruct)->songs[i].bitRate = 0;
|
||||||
|
(*getPlaylistStruct)->songs[i].bitDepth = 0;
|
||||||
|
(*getPlaylistStruct)->songs[i].samplingRate = 0;
|
||||||
|
(*getPlaylistStruct)->songs[i].channelCount = 0;
|
||||||
|
(*getPlaylistStruct)->songs[i].userRating = 0;
|
||||||
|
(*getPlaylistStruct)->songs[i].track = 0;
|
||||||
|
(*getPlaylistStruct)->songs[i].year = 0;
|
||||||
|
(*getPlaylistStruct)->songs[i].genre = NULL;
|
||||||
|
(*getPlaylistStruct)->songs[i].size = 0;
|
||||||
|
(*getPlaylistStruct)->songs[i].discNumber = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < (*getPlaylistStruct)->songCount; i++) {
|
||||||
|
cJSON* curr_idx_root = cJSON_GetArrayItem(entry_root, i);
|
||||||
|
if (curr_idx_root != NULL) {
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->songs[i].id, curr_idx_root, "id");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->songs[i].parent, curr_idx_root, "parent");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->songs[i].title, curr_idx_root, "title");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->songs[i].albumId, curr_idx_root, "albumId");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->songs[i].album, curr_idx_root, "album");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->songs[i].artistId, curr_idx_root, "artistId");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->songs[i].artist, curr_idx_root, "artist");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->songs[i].coverArt, curr_idx_root, "coverArt");
|
||||||
|
OSS_Ploj(&(*getPlaylistStruct)->songs[i].duration, curr_idx_root, "duration");
|
||||||
|
OSS_Pioj(&(*getPlaylistStruct)->songs[i].bitRate, curr_idx_root, "bitRate");
|
||||||
|
OSS_Pioj(&(*getPlaylistStruct)->songs[i].bitDepth, curr_idx_root, "bitDepth");
|
||||||
|
OSS_Ploj(&(*getPlaylistStruct)->songs[i].samplingRate, curr_idx_root, "samplingRate");
|
||||||
|
OSS_Pioj(&(*getPlaylistStruct)->songs[i].channelCount, curr_idx_root, "channelCount");
|
||||||
|
OSS_Pioj(&(*getPlaylistStruct)->songs[i].userRating, curr_idx_root, "userRating");
|
||||||
|
OSS_Pioj(&(*getPlaylistStruct)->songs[i].track, curr_idx_root, "track");
|
||||||
|
OSS_Pioj(&(*getPlaylistStruct)->songs[i].year, curr_idx_root, "year");
|
||||||
|
OSS_Psoj(&(*getPlaylistStruct)->songs[i].genre, curr_idx_root, "genre");
|
||||||
|
OSS_Ploj(&(*getPlaylistStruct)->songs[i].size, curr_idx_root, "size");
|
||||||
|
OSS_Pioj(&(*getPlaylistStruct)->songs[i].discNumber, curr_idx_root, "discNumber");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_getPlaylist_struct_free(opensubsonic_getPlaylist_struct** getPlaylistStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /getPlaylist endpoint heap objects.");
|
||||||
|
if ((*getPlaylistStruct)->status != NULL) { free((*getPlaylistStruct)->status); }
|
||||||
|
if ((*getPlaylistStruct)->errorMessage != NULL) { free((*getPlaylistStruct)->errorMessage); }
|
||||||
|
if ((*getPlaylistStruct)->id != NULL) { free((*getPlaylistStruct)->id); }
|
||||||
|
if ((*getPlaylistStruct)->name != NULL) { free((*getPlaylistStruct)->name); }
|
||||||
|
if ((*getPlaylistStruct)->owner != NULL) { free((*getPlaylistStruct)->owner); }
|
||||||
|
if ((*getPlaylistStruct)->created != NULL) { free((*getPlaylistStruct)->created); }
|
||||||
|
if ((*getPlaylistStruct)->changed != NULL) { free((*getPlaylistStruct)->changed); }
|
||||||
|
for (int i = 0; i < (*getPlaylistStruct)->songCount; i++) {
|
||||||
|
if((*getPlaylistStruct)->songs[i].id != NULL) { free((*getPlaylistStruct)->songs[i].id); }
|
||||||
|
if((*getPlaylistStruct)->songs[i].parent != NULL) { free((*getPlaylistStruct)->songs[i].parent); }
|
||||||
|
if((*getPlaylistStruct)->songs[i].title != NULL) { free((*getPlaylistStruct)->songs[i].title); }
|
||||||
|
if((*getPlaylistStruct)->songs[i].albumId != NULL) { free((*getPlaylistStruct)->songs[i].albumId); }
|
||||||
|
if((*getPlaylistStruct)->songs[i].album != NULL) { free((*getPlaylistStruct)->songs[i].album); }
|
||||||
|
if((*getPlaylistStruct)->songs[i].artistId != NULL) { free((*getPlaylistStruct)->songs[i].artistId); }
|
||||||
|
if((*getPlaylistStruct)->songs[i].artist != NULL) { free((*getPlaylistStruct)->songs[i].artist); }
|
||||||
|
if((*getPlaylistStruct)->songs[i].coverArt != NULL) { free((*getPlaylistStruct)->songs[i].coverArt); }
|
||||||
|
if((*getPlaylistStruct)->songs[i].genre != NULL) { free((*getPlaylistStruct)->songs[i].genre); }
|
||||||
|
}
|
||||||
|
if ((*getPlaylistStruct)->songs != NULL) { free((*getPlaylistStruct)->songs); }
|
||||||
|
if (*getPlaylistStruct != NULL) { free(*getPlaylistStruct); }
|
||||||
|
}
|
||||||
49
src/libopensubsonic/endpoint_getPlaylist.h
Normal file
49
src/libopensubsonic/endpoint_getPlaylist.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef _ENDPOINT_GETPLAYLIST_H
|
||||||
|
#define _ENDPOINT_GETPLAYLIST_H
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* id;
|
||||||
|
char* parent;
|
||||||
|
char* title;
|
||||||
|
// 'isDir', 'isVideo', and 'type' are not needed
|
||||||
|
char* albumId;
|
||||||
|
char* album; // Album Name
|
||||||
|
char* artistId;
|
||||||
|
char* artist; // Artist Name
|
||||||
|
char* coverArt; // Cover Art ID
|
||||||
|
long duration; // Duration (seconds)
|
||||||
|
// Do not completely rely on the next 4 values if you can detect them manually
|
||||||
|
int bitRate;
|
||||||
|
int bitDepth;
|
||||||
|
long samplingRate;
|
||||||
|
int channelCount;
|
||||||
|
int userRating;
|
||||||
|
// Not sure what 'averageRating' is used for
|
||||||
|
int track;
|
||||||
|
int year; // Year released
|
||||||
|
char* genre;
|
||||||
|
long size; // Size in bytes
|
||||||
|
int discNumber;
|
||||||
|
// 'suffix', 'contentType', and 'path' not needed
|
||||||
|
} opensubsonic_getPlaylist_songs_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status;
|
||||||
|
int errorCode;
|
||||||
|
char* errorMessage;
|
||||||
|
char* id;
|
||||||
|
char* name;
|
||||||
|
char* owner;
|
||||||
|
bool isPublic; // 'public'
|
||||||
|
char* created;
|
||||||
|
char* changed;
|
||||||
|
long duration;
|
||||||
|
int songCount; // Counts are not reliable from navidrome, counted manually
|
||||||
|
opensubsonic_getPlaylist_songs_struct* songs;
|
||||||
|
} opensubsonic_getPlaylist_struct;
|
||||||
|
|
||||||
|
int opensubsonic_getPlaylist_parse(char* data, opensubsonic_getPlaylist_struct** getPlaylistStruct);
|
||||||
|
void opensubsonic_getPlaylist_struct_free(opensubsonic_getPlaylist_struct** getPlaylistStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
121
src/libopensubsonic/endpoint_getPlaylists.c
Normal file
121
src/libopensubsonic/endpoint_getPlaylists.c
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_getPlaylists.h"
|
||||||
|
|
||||||
|
// Parse the JSON returned from the /rest/getPlaylists endpoint
|
||||||
|
// Returns 1 if failure occured, else 0
|
||||||
|
int opensubsonic_getPlaylists_parse(char* data, opensubsonic_getPlaylists_struct** getPlaylistsStruct) {
|
||||||
|
// Allocate struct
|
||||||
|
*getPlaylistsStruct = (opensubsonic_getPlaylists_struct*)malloc(sizeof(opensubsonic_getPlaylists_struct));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
(*getPlaylistsStruct)->status = NULL;
|
||||||
|
(*getPlaylistsStruct)->errorCode = 0;
|
||||||
|
(*getPlaylistsStruct)->errorMessage = NULL;
|
||||||
|
(*getPlaylistsStruct)->playlistCount = 0;
|
||||||
|
(*getPlaylistsStruct)->playlists = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getPlaylistsStruct)->status, subsonic_root, "status");
|
||||||
|
|
||||||
|
// Check if API has returned an error
|
||||||
|
if (strstr((*getPlaylistsStruct)->status, "ok") == NULL) {
|
||||||
|
// API has not returned 'ok' in status, fetch error, and return
|
||||||
|
// Check if an error is present
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
// Error not defined in JSON
|
||||||
|
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Pioj(&(*getPlaylistsStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*getPlaylistsStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getPlaylistsStruct)->errorCode, (*getPlaylistsStruct)->errorMessage);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from playlists
|
||||||
|
cJSON* playlists_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "playlists");
|
||||||
|
if (playlists_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - playlists does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from playlist
|
||||||
|
cJSON* playlist_root = cJSON_GetObjectItemCaseSensitive(playlists_root, "playlist");
|
||||||
|
if (playlist_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - playlist does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the amount of playlists
|
||||||
|
(*getPlaylistsStruct)->playlistCount = cJSON_GetArraySize(playlist_root);
|
||||||
|
|
||||||
|
// Allocate memory for opensubsonic_getPlaylists_playlist_struct inside opensubsonic_getPlaylists_struct
|
||||||
|
(*getPlaylistsStruct)->playlists = (opensubsonic_getPlaylists_playlist_struct*)malloc((*getPlaylistsStruct)->playlistCount * sizeof(opensubsonic_getPlaylists_playlist_struct));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
for (int i = 0; i < (*getPlaylistsStruct)->playlistCount; i++) {
|
||||||
|
(*getPlaylistsStruct)->playlists[i].id = NULL;
|
||||||
|
(*getPlaylistsStruct)->playlists[i].name = NULL;
|
||||||
|
(*getPlaylistsStruct)->playlists[i].songCount = 0;
|
||||||
|
(*getPlaylistsStruct)->playlists[i].duration = 0;
|
||||||
|
(*getPlaylistsStruct)->playlists[i].owner = NULL;
|
||||||
|
(*getPlaylistsStruct)->playlists[i].coverArt = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the data from the playlist array
|
||||||
|
for (int i = 0; i < (*getPlaylistsStruct)->playlistCount; i++) {
|
||||||
|
cJSON* current_index_root = cJSON_GetArrayItem(playlist_root, i);
|
||||||
|
if (current_index_root != NULL) {
|
||||||
|
// Fetch playlist information
|
||||||
|
OSS_Psoj(&(*getPlaylistsStruct)->playlists[i].id, current_index_root, "id");
|
||||||
|
OSS_Psoj(&(*getPlaylistsStruct)->playlists[i].name, current_index_root, "name");
|
||||||
|
OSS_Pioj(&(*getPlaylistsStruct)->playlists[i].songCount, current_index_root, "songCount");
|
||||||
|
OSS_Ploj(&(*getPlaylistsStruct)->playlists[i].duration, current_index_root, "duration");
|
||||||
|
OSS_Psoj(&(*getPlaylistsStruct)->playlists[i].owner, current_index_root, "owner");
|
||||||
|
OSS_Psoj(&(*getPlaylistsStruct)->playlists[i].coverArt, current_index_root, "coverArt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_getPlaylists_struct_free(opensubsonic_getPlaylists_struct** getPlaylistsStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /getPlaylists endpoint heap objects.");
|
||||||
|
if ((*getPlaylistsStruct)->status != NULL) { free((*getPlaylistsStruct)->status); }
|
||||||
|
if ((*getPlaylistsStruct)->errorMessage != NULL) { free((*getPlaylistsStruct)->errorMessage); }
|
||||||
|
for (size_t i = 0; i < (*getPlaylistsStruct)->playlistCount; i++) {
|
||||||
|
if ((*getPlaylistsStruct)->playlists[i].id != NULL) { free((*getPlaylistsStruct)->playlists[i].id); }
|
||||||
|
if ((*getPlaylistsStruct)->playlists[i].name != NULL) { free((*getPlaylistsStruct)->playlists[i].name); }
|
||||||
|
if ((*getPlaylistsStruct)->playlists[i].owner != NULL) { free((*getPlaylistsStruct)->playlists[i].owner); }
|
||||||
|
if ((*getPlaylistsStruct)->playlists[i].coverArt != NULL) { free((*getPlaylistsStruct)->playlists[i].coverArt); }
|
||||||
|
}
|
||||||
|
if ((*getPlaylistsStruct)->playlists != NULL) { free((*getPlaylistsStruct)->playlists); }
|
||||||
|
if (*getPlaylistsStruct != NULL) { free(*getPlaylistsStruct); }
|
||||||
|
}
|
||||||
26
src/libopensubsonic/endpoint_getPlaylists.h
Normal file
26
src/libopensubsonic/endpoint_getPlaylists.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#ifndef _ENDPOINT_GETPLAYLISTS_H
|
||||||
|
#define _ENDPOINT_GETPLAYLISTS_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* id; // Album ID
|
||||||
|
char* name; // Album name
|
||||||
|
int songCount; // Number of songs in the album
|
||||||
|
long duration; // Duration of the album in seconds
|
||||||
|
// 'public' doesn't seem useful here
|
||||||
|
char* owner; // Username of the owner of the album
|
||||||
|
// 'created' and 'changed' do not seem useful here either
|
||||||
|
char* coverArt; // Cover art ID of the album
|
||||||
|
} opensubsonic_getPlaylists_playlist_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status;
|
||||||
|
int errorCode;
|
||||||
|
char* errorMessage;
|
||||||
|
int playlistCount;
|
||||||
|
opensubsonic_getPlaylists_playlist_struct* playlists;
|
||||||
|
} opensubsonic_getPlaylists_struct;
|
||||||
|
|
||||||
|
int opensubsonic_getPlaylists_parse(char* data, opensubsonic_getPlaylists_struct** getPlaylistsStruct);
|
||||||
|
void opensubsonic_getPlaylists_struct_free(opensubsonic_getPlaylists_struct** getPlaylistsStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
137
src/libopensubsonic/endpoint_getSong.c
Normal file
137
src/libopensubsonic/endpoint_getSong.c
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_getSong.h"
|
||||||
|
|
||||||
|
// Parse the JSON returned from the /rest/getSong endpoint
|
||||||
|
// Returns 1 if failure occured, else 0
|
||||||
|
int opensubsonic_getSong_parse(char* data, opensubsonic_getSong_struct** getSongStruct) {
|
||||||
|
// Allocate struct
|
||||||
|
*getSongStruct = (opensubsonic_getSong_struct*)malloc(sizeof(opensubsonic_getSong_struct));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
(*getSongStruct)->status = NULL;
|
||||||
|
(*getSongStruct)->errorCode = 0;
|
||||||
|
(*getSongStruct)->errorMessage = NULL;
|
||||||
|
(*getSongStruct)->id = NULL;
|
||||||
|
(*getSongStruct)->parent = NULL;
|
||||||
|
(*getSongStruct)->title = NULL;
|
||||||
|
(*getSongStruct)->album = NULL;
|
||||||
|
(*getSongStruct)->artist = NULL;
|
||||||
|
(*getSongStruct)->track = 0;
|
||||||
|
(*getSongStruct)->year = 0;
|
||||||
|
(*getSongStruct)->coverArt = NULL;
|
||||||
|
(*getSongStruct)->size = 0;
|
||||||
|
(*getSongStruct)->starred = NULL;
|
||||||
|
(*getSongStruct)->duration = 0;
|
||||||
|
(*getSongStruct)->bitRate = 0;
|
||||||
|
(*getSongStruct)->bitDepth = 0;
|
||||||
|
(*getSongStruct)->samplingRate = 0;
|
||||||
|
(*getSongStruct)->channelCount = 0;
|
||||||
|
(*getSongStruct)->played = NULL;
|
||||||
|
(*getSongStruct)->discNumber = 0;
|
||||||
|
(*getSongStruct)->created = NULL;
|
||||||
|
(*getSongStruct)->albumId = NULL;
|
||||||
|
(*getSongStruct)->artistId = NULL;
|
||||||
|
(*getSongStruct)->displayArtist = NULL;
|
||||||
|
(*getSongStruct)->displayAlbumArtist = NULL;
|
||||||
|
(*getSongStruct)->displayComposer = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*getSongStruct)->status, subsonic_root, "status");
|
||||||
|
|
||||||
|
// Check if API has returned an error
|
||||||
|
if (strstr((*getSongStruct)->status, "ok") == NULL) {
|
||||||
|
// API has not returned 'ok' in status, fetch error, and return
|
||||||
|
// Check if an error is present
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
// Error not defined in JSON
|
||||||
|
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Pioj(&(*getSongStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getSongStruct)->errorCode, (*getSongStruct)->errorMessage);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from song
|
||||||
|
cJSON* song_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "song");
|
||||||
|
if (song_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - song does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch song information
|
||||||
|
OSS_Psoj(&(*getSongStruct)->id, song_root, "id");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->parent, song_root, "parent");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->title, song_root, "title");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->album, song_root, "album");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->artist, song_root, "artist");
|
||||||
|
OSS_Pioj(&(*getSongStruct)->track, song_root, "track");
|
||||||
|
OSS_Pioj(&(*getSongStruct)->year, song_root, "year");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->coverArt, song_root, "coverArt");
|
||||||
|
OSS_Ploj(&(*getSongStruct)->size, song_root, "size");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->starred, song_root, "starred");
|
||||||
|
OSS_Ploj(&(*getSongStruct)->duration, song_root, "duration");
|
||||||
|
OSS_Pioj(&(*getSongStruct)->bitRate, song_root, "bitRate");
|
||||||
|
OSS_Pioj(&(*getSongStruct)->bitDepth, song_root, "bitDepth");
|
||||||
|
OSS_Ploj(&(*getSongStruct)->samplingRate, song_root, "samplingRate");
|
||||||
|
OSS_Pioj(&(*getSongStruct)->channelCount, song_root, "channelCount");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->played, song_root, "played");
|
||||||
|
OSS_Pioj(&(*getSongStruct)->discNumber, song_root, "discNumber");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->created, song_root, "created");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->albumId, song_root, "albumId");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->artistId, song_root, "artistId");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->displayArtist, song_root, "displayArtist");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->displayAlbumArtist, song_root, "displayAlbumArtist");
|
||||||
|
OSS_Psoj(&(*getSongStruct)->displayComposer, song_root, "displayComposer");
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the dynamically allocated elements of the opensubsonic_getSong_struct structure
|
||||||
|
void opensubsonic_getSong_struct_free(opensubsonic_getSong_struct** getSongStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /getSong endpoint heap objects.");
|
||||||
|
if ((*getSongStruct)->status != NULL) { free((*getSongStruct)->status); }
|
||||||
|
if ((*getSongStruct)->errorMessage != NULL) { free((*getSongStruct)->errorMessage); }
|
||||||
|
if ((*getSongStruct)->id != NULL) { free((*getSongStruct)->id); }
|
||||||
|
if ((*getSongStruct)->parent != NULL) { free((*getSongStruct)->parent); }
|
||||||
|
if ((*getSongStruct)->title != NULL) { free((*getSongStruct)->title); }
|
||||||
|
if ((*getSongStruct)->album != NULL) { free((*getSongStruct)->album); }
|
||||||
|
if ((*getSongStruct)->artist != NULL) { free((*getSongStruct)->artist); }
|
||||||
|
if ((*getSongStruct)->coverArt != NULL) { free((*getSongStruct)->coverArt); }
|
||||||
|
if ((*getSongStruct)->starred != NULL) { free((*getSongStruct)->starred); }
|
||||||
|
if ((*getSongStruct)->played != NULL) { free((*getSongStruct)->played); }
|
||||||
|
if ((*getSongStruct)->created != NULL) { free((*getSongStruct)->created); }
|
||||||
|
if ((*getSongStruct)->albumId != NULL) { free((*getSongStruct)->albumId); }
|
||||||
|
if ((*getSongStruct)->artistId != NULL) { free((*getSongStruct)->artistId); }
|
||||||
|
if ((*getSongStruct)->displayArtist != NULL) { free((*getSongStruct)->displayArtist); }
|
||||||
|
if ((*getSongStruct)->displayAlbumArtist != NULL) { free((*getSongStruct)->displayAlbumArtist); }
|
||||||
|
if ((*getSongStruct)->displayComposer != NULL) { free((*getSongStruct)->displayComposer); }
|
||||||
|
if (*getSongStruct != NULL) { free(*getSongStruct); }
|
||||||
|
}
|
||||||
39
src/libopensubsonic/endpoint_getSong.h
Normal file
39
src/libopensubsonic/endpoint_getSong.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#ifndef _ENDPOINT_GETSONG_H
|
||||||
|
#define _ENDPOINT_GETSONG_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status; // Request status
|
||||||
|
int errorCode; // Request error code (0 if none)
|
||||||
|
char* errorMessage; // Request error message (NULL if none)
|
||||||
|
char* id; // Song ID
|
||||||
|
char* parent; // Song parent
|
||||||
|
// 'isDir' excluded
|
||||||
|
char* title; // Song title
|
||||||
|
char* album; // Song album
|
||||||
|
char* artist; // Song artist
|
||||||
|
int track; // Track number
|
||||||
|
int year; // Year released
|
||||||
|
char* coverArt; // Cover art ID
|
||||||
|
long size; // Size of the song in bytes
|
||||||
|
// 'contentType', 'suffix' excluded
|
||||||
|
char* starred; // Time starred
|
||||||
|
long duration; // Duration of the song in seconds
|
||||||
|
int bitRate; // Bitrate of the song
|
||||||
|
int bitDepth; // Bit depth
|
||||||
|
long samplingRate; // Sample rate
|
||||||
|
int channelCount; // Channel count
|
||||||
|
char* played; // Time last played (TODO probably)
|
||||||
|
int discNumber; // Disc number
|
||||||
|
char* created; // Date added (TODO probably)
|
||||||
|
char* albumId; // Album ID
|
||||||
|
char* artistId; // Artist ID
|
||||||
|
// 'type', 'mediaType', 'isVideo', 'bpm', 'comment', 'sortName', 'musicBrainzId', 'genres', 'artists', 'albumArtists', 'contributors', 'moods', 'replayGain' excluded
|
||||||
|
char* displayArtist; // Display Artist
|
||||||
|
char* displayAlbumArtist; // Display Album Artist
|
||||||
|
char* displayComposer; // Display Composer
|
||||||
|
} opensubsonic_getSong_struct;
|
||||||
|
|
||||||
|
int opensubsonic_getSong_parse(char* data, opensubsonic_getSong_struct** getSongStruct);
|
||||||
|
void opensubsonic_getSong_struct_free(opensubsonic_getSong_struct** getSongStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
260
src/libopensubsonic/endpoint_getStarred.c
Normal file
260
src/libopensubsonic/endpoint_getStarred.c
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_getStarred.h"
|
||||||
|
|
||||||
|
// Returns 1 if failure occured, else 0
|
||||||
|
int opensubsonic_getStarred_parse(char* data, opensubsonic_getStarred_struct** getStarredStruct) {
|
||||||
|
// Allocate and initialize
|
||||||
|
*getStarredStruct = (opensubsonic_getStarred_struct*)malloc(sizeof(opensubsonic_getStarred_struct));
|
||||||
|
(*getStarredStruct)->status = NULL;
|
||||||
|
(*getStarredStruct)->errorMessage = NULL;
|
||||||
|
(*getStarredStruct)->errorCode = 0;
|
||||||
|
(*getStarredStruct)->artistCount = -1;
|
||||||
|
(*getStarredStruct)->albumCount = -1;
|
||||||
|
(*getStarredStruct)->songCount = -1;
|
||||||
|
(*getStarredStruct)->artists = NULL;
|
||||||
|
(*getStarredStruct)->albums = NULL;
|
||||||
|
(*getStarredStruct)->songs = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for error
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->status, subsonic_root, "status");
|
||||||
|
if (strstr((*getStarredStruct)->status, "ok") == NULL) {
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getStarredStruct)->errorCode, (*getStarredStruct)->errorMessage);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from starred
|
||||||
|
cJSON* starred_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "starred");
|
||||||
|
if (starred_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - starred does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from artist, album, and song
|
||||||
|
cJSON* artist_root = cJSON_GetObjectItemCaseSensitive(starred_root, "artist");
|
||||||
|
if (artist_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - artist does not exist.");
|
||||||
|
} else {
|
||||||
|
(*getStarredStruct)->artistCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(starred_root, "album");
|
||||||
|
if (album_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - album does not exist.");
|
||||||
|
} else {
|
||||||
|
(*getStarredStruct)->albumCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* song_root = cJSON_GetObjectItemCaseSensitive(starred_root, "song");
|
||||||
|
if (song_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - song does not exist.");
|
||||||
|
} else {
|
||||||
|
(*getStarredStruct)->songCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract starred artists
|
||||||
|
if ((*getStarredStruct)->artistCount != -1) {
|
||||||
|
// Count, allocate, initialize, and extract
|
||||||
|
(*getStarredStruct)->artistCount = cJSON_GetArraySize(artist_root);
|
||||||
|
(*getStarredStruct)->artists = (opensubsonic_getStarred_artist_struct*)malloc((*getStarredStruct)->artistCount * sizeof(opensubsonic_getStarred_artist_struct));
|
||||||
|
|
||||||
|
for (int i = 0; i < (*getStarredStruct)->artistCount; i++) {
|
||||||
|
(*getStarredStruct)->artists[i].id = NULL;
|
||||||
|
(*getStarredStruct)->artists[i].name = NULL;
|
||||||
|
(*getStarredStruct)->artists[i].coverArt = NULL;
|
||||||
|
(*getStarredStruct)->artists[i].starred = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < (*getStarredStruct)->artistCount; i++) {
|
||||||
|
cJSON* array_artist_root = cJSON_GetArrayItem(artist_root, i);
|
||||||
|
if (array_artist_root != NULL) {
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->artists[i].id, array_artist_root, "id");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->artists[i].name, array_artist_root, "name");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->artists[i].coverArt, array_artist_root, "coverArt");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->artists[i].starred, array_artist_root, "starred");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No starred artists
|
||||||
|
(*getStarredStruct)->artistCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract starred albums
|
||||||
|
if ((*getStarredStruct)->albumCount != -1) {
|
||||||
|
// Count, allocate, initialize, and extract
|
||||||
|
(*getStarredStruct)->albumCount = cJSON_GetArraySize(album_root);
|
||||||
|
(*getStarredStruct)->albums = (opensubsonic_getStarred_album_struct*)malloc((*getStarredStruct)->albumCount * sizeof(opensubsonic_getStarred_album_struct));
|
||||||
|
|
||||||
|
for (int i = 0; i < (*getStarredStruct)->albumCount; i++) {
|
||||||
|
(*getStarredStruct)->albums[i].id = NULL;
|
||||||
|
(*getStarredStruct)->albums[i].parent = NULL;
|
||||||
|
(*getStarredStruct)->albums[i].album = NULL;
|
||||||
|
(*getStarredStruct)->albums[i].title = NULL;
|
||||||
|
(*getStarredStruct)->albums[i].name = NULL;
|
||||||
|
(*getStarredStruct)->albums[i].coverArt = NULL;
|
||||||
|
(*getStarredStruct)->albums[i].songCount = 0;
|
||||||
|
(*getStarredStruct)->albums[i].created = NULL;
|
||||||
|
(*getStarredStruct)->albums[i].duration = 0;
|
||||||
|
(*getStarredStruct)->albums[i].playCount = 0;
|
||||||
|
(*getStarredStruct)->albums[i].artistId = NULL;
|
||||||
|
(*getStarredStruct)->albums[i].artist = NULL;
|
||||||
|
(*getStarredStruct)->albums[i].year = 0;
|
||||||
|
(*getStarredStruct)->albums[i].genre = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < (*getStarredStruct)->albumCount; i++) {
|
||||||
|
cJSON* array_album_root = cJSON_GetArrayItem(album_root, i);
|
||||||
|
if (array_album_root != NULL) {
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->albums[i].id, array_album_root, "id");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->albums[i].parent, array_album_root, "parent");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->albums[i].album, array_album_root, "album");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->albums[i].title, array_album_root, "title");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->albums[i].name, array_album_root, "name");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->albums[i].coverArt, array_album_root, "coverArt");
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->albums[i].songCount, array_album_root, "songCount");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->albums[i].created, array_album_root, "created");
|
||||||
|
OSS_Ploj(&(*getStarredStruct)->albums[i].duration, array_album_root, "duration");
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->albums[i].playCount, array_album_root, "playCount");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->albums[i].artistId, array_album_root, "artistId");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->albums[i].artist, array_album_root, "artist");
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->albums[i].year, array_album_root, "year");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->albums[i].genre, array_album_root, "genre");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No starred albums
|
||||||
|
(*getStarredStruct)->albumCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract starred songs
|
||||||
|
if ((*getStarredStruct)->songCount != -1) {
|
||||||
|
// Count, allocate, initialize, and extract
|
||||||
|
(*getStarredStruct)->songCount = cJSON_GetArraySize(song_root);
|
||||||
|
(*getStarredStruct)->songs = (opensubsonic_getStarred_song_struct*)malloc((*getStarredStruct)->songCount * sizeof(opensubsonic_getStarred_song_struct));
|
||||||
|
|
||||||
|
for (int i = 0; i < (*getStarredStruct)->songCount; i++) {
|
||||||
|
(*getStarredStruct)->songs[i].id = NULL;
|
||||||
|
(*getStarredStruct)->songs[i].parent = NULL;
|
||||||
|
(*getStarredStruct)->songs[i].title = NULL;
|
||||||
|
(*getStarredStruct)->songs[i].album = NULL;
|
||||||
|
(*getStarredStruct)->songs[i].artist = NULL;
|
||||||
|
(*getStarredStruct)->songs[i].track = 0;
|
||||||
|
(*getStarredStruct)->songs[i].year = 0;
|
||||||
|
(*getStarredStruct)->songs[i].coverArt = NULL;
|
||||||
|
(*getStarredStruct)->songs[i].size = 0;
|
||||||
|
(*getStarredStruct)->songs[i].starred = NULL;
|
||||||
|
(*getStarredStruct)->songs[i].duration = 0;
|
||||||
|
(*getStarredStruct)->songs[i].bitRate = 0;
|
||||||
|
(*getStarredStruct)->songs[i].bitDepth = 0;
|
||||||
|
(*getStarredStruct)->songs[i].samplingRate = 0;
|
||||||
|
(*getStarredStruct)->songs[i].channelCount = 0;
|
||||||
|
(*getStarredStruct)->songs[i].playCount = 0;
|
||||||
|
(*getStarredStruct)->songs[i].discNumber = 0;
|
||||||
|
(*getStarredStruct)->songs[i].created = NULL;
|
||||||
|
(*getStarredStruct)->songs[i].albumId = NULL;
|
||||||
|
(*getStarredStruct)->songs[i].artistId = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < (*getStarredStruct)->songCount; i++) {
|
||||||
|
cJSON* array_song_root = cJSON_GetArrayItem(song_root, i);
|
||||||
|
if (array_song_root != NULL) {
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->songs[i].id, array_song_root, "id");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->songs[i].parent, array_song_root, "parent");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->songs[i].title, array_song_root, "title");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->songs[i].album, array_song_root, "album");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->songs[i].artist, array_song_root, "artist");
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->songs[i].track, array_song_root, "track");
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->songs[i].year, array_song_root, "year");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->songs[i].coverArt, array_song_root, "coverArt");
|
||||||
|
OSS_Ploj(&(*getStarredStruct)->songs[i].size, array_song_root, "size");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->songs[i].starred, array_song_root, "starred");
|
||||||
|
OSS_Ploj(&(*getStarredStruct)->songs[i].duration, array_song_root, "duration");
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->songs[i].bitRate, array_song_root, "bitRate");
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->songs[i].bitDepth, array_song_root, "bitDepth");
|
||||||
|
OSS_Ploj(&(*getStarredStruct)->songs[i].samplingRate, array_song_root, "samplingRate");
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->songs[i].channelCount, array_song_root, "channelCount");
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->songs[i].playCount, array_song_root, "playCount");
|
||||||
|
OSS_Pioj(&(*getStarredStruct)->songs[i].discNumber, array_song_root, "discNumber");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->songs[i].created, array_song_root, "created");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->songs[i].albumId, array_song_root, "albumId");
|
||||||
|
OSS_Psoj(&(*getStarredStruct)->songs[i].artistId, array_song_root, "artistId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No starred songs
|
||||||
|
(*getStarredStruct)->songCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_getStarred_struct_free(opensubsonic_getStarred_struct** getStarredStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /getStarred endpoint heap objects.");
|
||||||
|
if ((*getStarredStruct)->status != NULL) { free((*getStarredStruct)->status); }
|
||||||
|
if ((*getStarredStruct)->errorMessage != NULL) { free((*getStarredStruct)->errorMessage); }
|
||||||
|
for (int i = 0; i < (*getStarredStruct)->artistCount; i++) {
|
||||||
|
if ((*getStarredStruct)->artists[i].id != NULL) { free((*getStarredStruct)->artists[i].id); }
|
||||||
|
if ((*getStarredStruct)->artists[i].name != NULL) { free((*getStarredStruct)->artists[i].name); }
|
||||||
|
if ((*getStarredStruct)->artists[i].coverArt != NULL) { free((*getStarredStruct)->artists[i].coverArt); }
|
||||||
|
if ((*getStarredStruct)->artists[i].starred != NULL) { free((*getStarredStruct)->artists[i].starred); }
|
||||||
|
}
|
||||||
|
for (int i = 0; i < (*getStarredStruct)->albumCount; i++) {
|
||||||
|
if ((*getStarredStruct)->albums[i].id != NULL) { free((*getStarredStruct)->albums[i].id); }
|
||||||
|
if ((*getStarredStruct)->albums[i].parent != NULL) { free((*getStarredStruct)->albums[i].parent); }
|
||||||
|
if ((*getStarredStruct)->albums[i].album != NULL) { free((*getStarredStruct)->albums[i].album); }
|
||||||
|
if ((*getStarredStruct)->albums[i].title != NULL) { free((*getStarredStruct)->albums[i].title); }
|
||||||
|
if ((*getStarredStruct)->albums[i].name != NULL) { free((*getStarredStruct)->albums[i].name); }
|
||||||
|
if ((*getStarredStruct)->albums[i].coverArt != NULL) { free((*getStarredStruct)->albums[i].coverArt); }
|
||||||
|
if ((*getStarredStruct)->albums[i].created != NULL) { free((*getStarredStruct)->albums[i].created); }
|
||||||
|
if ((*getStarredStruct)->albums[i].artistId != NULL) { free((*getStarredStruct)->albums[i].artistId); }
|
||||||
|
if ((*getStarredStruct)->albums[i].artist != NULL) { free((*getStarredStruct)->albums[i].artist); }
|
||||||
|
if ((*getStarredStruct)->albums[i].genre != NULL) { free((*getStarredStruct)->albums[i].genre); }
|
||||||
|
}
|
||||||
|
for (int i = 0; i < (*getStarredStruct)->songCount; i++) {
|
||||||
|
if ((*getStarredStruct)->songs[i].id != NULL) { free((*getStarredStruct)->songs[i].id); }
|
||||||
|
if ((*getStarredStruct)->songs[i].parent != NULL) { free((*getStarredStruct)->songs[i].parent); }
|
||||||
|
if ((*getStarredStruct)->songs[i].title != NULL) { free((*getStarredStruct)->songs[i].title); }
|
||||||
|
if ((*getStarredStruct)->songs[i].album != NULL) { free((*getStarredStruct)->songs[i].album); }
|
||||||
|
if ((*getStarredStruct)->songs[i].artist != NULL) { free((*getStarredStruct)->songs[i].artist); }
|
||||||
|
if ((*getStarredStruct)->songs[i].coverArt != NULL) { free((*getStarredStruct)->songs[i].coverArt); }
|
||||||
|
if ((*getStarredStruct)->songs[i].starred != NULL) { free((*getStarredStruct)->songs[i].starred); }
|
||||||
|
if ((*getStarredStruct)->songs[i].created != NULL) { free((*getStarredStruct)->songs[i].created); }
|
||||||
|
if ((*getStarredStruct)->songs[i].albumId != NULL) { free((*getStarredStruct)->songs[i].albumId); }
|
||||||
|
if ((*getStarredStruct)->songs[i].artistId != NULL) { free((*getStarredStruct)->songs[i].artistId); }
|
||||||
|
}
|
||||||
|
if ((*getStarredStruct)->artists != NULL) { free((*getStarredStruct)->artists); }
|
||||||
|
if ((*getStarredStruct)->albums != NULL) { free((*getStarredStruct)->albums); }
|
||||||
|
if ((*getStarredStruct)->songs != NULL) { free((*getStarredStruct)->songs); }
|
||||||
|
if (*getStarredStruct != NULL) { free(*getStarredStruct); }
|
||||||
|
}
|
||||||
71
src/libopensubsonic/endpoint_getStarred.h
Normal file
71
src/libopensubsonic/endpoint_getStarred.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#ifndef _ENDPOINT_GETSTARRED_H
|
||||||
|
#define _ENDPOINT_GETSTARRED_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* id;
|
||||||
|
char* name;
|
||||||
|
char* coverArt;
|
||||||
|
char* starred;
|
||||||
|
} opensubsonic_getStarred_artist_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* id;
|
||||||
|
char* parent;
|
||||||
|
char* album;
|
||||||
|
char* title;
|
||||||
|
char* name;
|
||||||
|
// 'isDir' excluded
|
||||||
|
char* coverArt;
|
||||||
|
int songCount; // Do not rely on this
|
||||||
|
char* created;
|
||||||
|
long duration;
|
||||||
|
int playCount;
|
||||||
|
char* artistId;
|
||||||
|
char* artist;
|
||||||
|
int year;
|
||||||
|
char* genre;
|
||||||
|
} opensubsonic_getStarred_album_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* id;
|
||||||
|
char* parent;
|
||||||
|
// 'isDir' excluded
|
||||||
|
char* title;
|
||||||
|
char* album;
|
||||||
|
char* artist;
|
||||||
|
int track;
|
||||||
|
int year;
|
||||||
|
char* coverArt;
|
||||||
|
long size;
|
||||||
|
// 'contentType', 'suffix' excluded
|
||||||
|
char* starred;
|
||||||
|
long duration;
|
||||||
|
int bitRate;
|
||||||
|
int bitDepth;
|
||||||
|
long samplingRate;
|
||||||
|
int channelCount;
|
||||||
|
// 'path' excluded
|
||||||
|
int playCount;
|
||||||
|
int discNumber;
|
||||||
|
char* created;
|
||||||
|
char* albumId;
|
||||||
|
char* artistId;
|
||||||
|
// 'type', 'isVideo' excluded
|
||||||
|
} opensubsonic_getStarred_song_struct;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status;
|
||||||
|
int errorCode;
|
||||||
|
char* errorMessage;
|
||||||
|
int artistCount;
|
||||||
|
int albumCount;
|
||||||
|
int songCount;
|
||||||
|
opensubsonic_getStarred_artist_struct* artists;
|
||||||
|
opensubsonic_getStarred_album_struct* albums;
|
||||||
|
opensubsonic_getStarred_song_struct* songs;
|
||||||
|
} opensubsonic_getStarred_struct;
|
||||||
|
|
||||||
|
int opensubsonic_getStarred_parse(char* data, opensubsonic_getStarred_struct** getStarredStruct);
|
||||||
|
void opensubsonic_getStarred_struct_free(opensubsonic_getStarred_struct** getStarredStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
73
src/libopensubsonic/endpoint_ping.c
Normal file
73
src/libopensubsonic/endpoint_ping.c
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_ping.h"
|
||||||
|
|
||||||
|
// Parse the JSON returned from the /rest/ping endpoint
|
||||||
|
// Returns 1 if failure occured, else 0
|
||||||
|
int opensubsonic_ping_parse(char* data, opensubsonic_ping_struct** pingStruct) {
|
||||||
|
// Allocate on the heap
|
||||||
|
*pingStruct = (opensubsonic_ping_struct*)malloc(sizeof(opensubsonic_ping_struct));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
(*pingStruct)->status = NULL;
|
||||||
|
(*pingStruct)->version = NULL;
|
||||||
|
(*pingStruct)->serverType = NULL;
|
||||||
|
(*pingStruct)->serverVersion = NULL;
|
||||||
|
(*pingStruct)->openSubsonicCapable = false;
|
||||||
|
(*pingStruct)->error = false;
|
||||||
|
(*pingStruct)->errorCode = 0;
|
||||||
|
(*pingStruct)->errorMessage = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Psoj(&(*pingStruct)->status, subsonic_root, "status");
|
||||||
|
OSS_Psoj(&(*pingStruct)->version, subsonic_root, "version");
|
||||||
|
OSS_Psoj(&(*pingStruct)->serverType, subsonic_root, "type");
|
||||||
|
OSS_Psoj(&(*pingStruct)->serverVersion, subsonic_root, "serverVersion");
|
||||||
|
OSS_Pboj(&(*pingStruct)->openSubsonicCapable, subsonic_root, "openSubsonic");
|
||||||
|
|
||||||
|
// Check if an error is present
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
// Error did not occur, return
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
(*pingStruct)->error = true;
|
||||||
|
|
||||||
|
// From this point on, error has occured, capture error information
|
||||||
|
OSS_Pioj(&(*pingStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*pingStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*pingStruct)->errorCode, (*pingStruct)->errorMessage);
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the dynamically allocated elements of the opensubsonic_ping_struct structure
|
||||||
|
void opensubsonic_ping_struct_free(opensubsonic_ping_struct** pingStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /ping endpoint heap objects.");
|
||||||
|
if ((*pingStruct)->status != NULL) { free((*pingStruct)->status); }
|
||||||
|
if ((*pingStruct)->version != NULL) { free((*pingStruct)->version); }
|
||||||
|
if ((*pingStruct)->serverType != NULL) { free((*pingStruct)->serverType); }
|
||||||
|
if ((*pingStruct)->serverVersion != NULL) { free((*pingStruct)->serverVersion); }
|
||||||
|
if ((*pingStruct)->errorMessage != NULL) { free((*pingStruct)->errorMessage); }
|
||||||
|
if (*pingStruct != NULL) { free(*pingStruct); }
|
||||||
|
}
|
||||||
20
src/libopensubsonic/endpoint_ping.h
Normal file
20
src/libopensubsonic/endpoint_ping.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef _ENDPOINT_PING_H
|
||||||
|
#define _ENDPOINT_PING_H
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status;
|
||||||
|
char* version;
|
||||||
|
char* serverType;
|
||||||
|
char* serverVersion;
|
||||||
|
bool openSubsonicCapable;
|
||||||
|
bool error;
|
||||||
|
int errorCode;
|
||||||
|
char* errorMessage;
|
||||||
|
} opensubsonic_ping_struct;
|
||||||
|
|
||||||
|
int opensubsonic_ping_parse(char* data, opensubsonic_ping_struct** pingStruct);
|
||||||
|
void opensubsonic_ping_struct_free(opensubsonic_ping_struct** pingStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
58
src/libopensubsonic/endpoint_scrobble.c
Normal file
58
src/libopensubsonic/endpoint_scrobble.c
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "endpoint_scrobble.h"
|
||||||
|
|
||||||
|
// Returns 1 if failure occured, else 0
|
||||||
|
int opensubsonic_scrobble_parse(char* data, opensubsonic_scrobble_struct** scrobbleStruct) {
|
||||||
|
// Allocate and initialize
|
||||||
|
(*scrobbleStruct) = (opensubsonic_scrobble_struct*)malloc(sizeof(opensubsonic_scrobble_struct));
|
||||||
|
(*scrobbleStruct)->status = NULL;
|
||||||
|
(*scrobbleStruct)->errorCode = 0;
|
||||||
|
(*scrobbleStruct)->errorMessage = NULL;
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
cJSON* root = cJSON_Parse(data);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from subsonic-response
|
||||||
|
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||||
|
if (subsonic_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for error
|
||||||
|
OSS_Psoj(&(*scrobbleStruct)->status, subsonic_root, "status");
|
||||||
|
if (strstr((*scrobbleStruct)->status, "ok") == NULL) {
|
||||||
|
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||||
|
if (subsonic_error == NULL) {
|
||||||
|
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSS_Pioj(&(*scrobbleStruct)->errorCode, subsonic_error, "code");
|
||||||
|
OSS_Psoj(&(*scrobbleStruct)->errorMessage, subsonic_error, "message");
|
||||||
|
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*scrobbleStruct)->errorCode, (*scrobbleStruct)->errorMessage);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_scrobble_struct_free(opensubsonic_scrobble_struct** scrobbleStruct) {
|
||||||
|
logger_log_general(__func__, "Freeing /scrobble endpoint heap objects.");
|
||||||
|
if ((*scrobbleStruct)->status != NULL) { free((*scrobbleStruct)->status); }
|
||||||
|
if ((*scrobbleStruct)->errorMessage != NULL) { free((*scrobbleStruct)->errorMessage); }
|
||||||
|
if (*scrobbleStruct != NULL) { free(*scrobbleStruct); }
|
||||||
|
}
|
||||||
13
src/libopensubsonic/endpoint_scrobble.h
Normal file
13
src/libopensubsonic/endpoint_scrobble.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef _ENDPOINT_SCROBBLE_H
|
||||||
|
#define _ENDPOINT_SCROBBLE_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* status;
|
||||||
|
int errorCode;
|
||||||
|
char* errorMessage;
|
||||||
|
} opensubsonic_scrobble_struct;
|
||||||
|
|
||||||
|
int opensubsonic_scrobble_parse(char* data, opensubsonic_scrobble_struct** scrobbleStruct);
|
||||||
|
void opensubsonic_scrobble_struct_free(opensubsonic_scrobble_struct** scrobbleStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
428
src/libopensubsonic/httpclient.c
Normal file
428
src/libopensubsonic/httpclient.c
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "httpclient.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "../configHandler.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include "endpoint_ping.h"
|
||||||
|
#include "endpoint_getStarred.h"
|
||||||
|
#include "endpoint_getSong.h"
|
||||||
|
#include "endpoint_getPlaylists.h"
|
||||||
|
#include "endpoint_getPlaylist.h"
|
||||||
|
#include "endpoint_getArtists.h"
|
||||||
|
#include "endpoint_getArtist.h"
|
||||||
|
#include "endpoint_getLyricsBySongId.h"
|
||||||
|
#include "endpoint_getAlbumList.h"
|
||||||
|
#include "endpoint_getAlbum.h"
|
||||||
|
#include "endpoint_scrobble.h"
|
||||||
|
|
||||||
|
static int rc = 0;
|
||||||
|
extern configHandler_config_t* configObj;
|
||||||
|
|
||||||
|
void opensubsonic_httpClient_URL_prepare(opensubsonic_httpClient_URL_t** urlObj) {
|
||||||
|
// Initialize struct variables
|
||||||
|
(*urlObj)->endpoint = 0;
|
||||||
|
(*urlObj)->id = NULL;
|
||||||
|
(*urlObj)->type = 0;
|
||||||
|
(*urlObj)->amount = 0;
|
||||||
|
(*urlObj)->submit = false;
|
||||||
|
(*urlObj)->formedUrl = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_httpClient_URL_cleanup(opensubsonic_httpClient_URL_t** urlObj) {
|
||||||
|
if ((*urlObj)->formedUrl != NULL) { free((*urlObj)->formedUrl); }
|
||||||
|
if ((*urlObj)->id != NULL) { free((*urlObj)->id); }
|
||||||
|
if (*urlObj != NULL) { free(*urlObj); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_httpClient_formUrl(opensubsonic_httpClient_URL_t** urlObj) {
|
||||||
|
// TODO fix hack, add error checking,
|
||||||
|
|
||||||
|
char* url = NULL;
|
||||||
|
|
||||||
|
switch ((*urlObj)->endpoint) {
|
||||||
|
case OPENSUBSONIC_ENDPOINT_PING:
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/ping?u=%s&t=%s&s=%s&f=json&v=%s&c=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETSTARRED:
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/getStarred?u=%s&t=%s&s=%s&f=json&v=%s&c=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETSONG:
|
||||||
|
if ((*urlObj)->id == NULL) {
|
||||||
|
logger_log_error(__func__, "(getSong) ID is null.");
|
||||||
|
// TODO handle error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/getSong?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||||
|
(*urlObj)->id);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_STREAM: // Does not have a fetchResponse counterpart
|
||||||
|
if ((*urlObj)->id == NULL) {
|
||||||
|
logger_log_error(__func__, "(stream) ID is null.");
|
||||||
|
// TODO handle error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/stream?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||||
|
(*urlObj)->id);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETCOVERART: // Does not have a fetchResponse counterpart
|
||||||
|
if ((*urlObj)->id == NULL) {
|
||||||
|
logger_log_error(__func__, "(getCoverArt) ID is null.");
|
||||||
|
// TODO handle error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/getCoverArt?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||||
|
(*urlObj)->id);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETALBUM:
|
||||||
|
if ((*urlObj)->id == NULL) {
|
||||||
|
logger_log_error(__func__, "(getAlbum) ID is null.");
|
||||||
|
// TODO handle error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/getAlbum?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||||
|
(*urlObj)->id);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETPLAYLISTS:
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/getPlaylists?u=%s&t=%s&s=%s&f=json&v=%s&c=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETPLAYLIST:
|
||||||
|
if ((*urlObj)->id == NULL) {
|
||||||
|
logger_log_error(__func__, "(getPlaylist) ID is null.");
|
||||||
|
// TODO handle error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/getPlaylist?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||||
|
(*urlObj)->id);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETARTISTS:
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/getArtists?u=%s&t=%s&s=%s&f=json&v=%s&c=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETARTIST:
|
||||||
|
if ((*urlObj)->id == NULL) {
|
||||||
|
logger_log_error(__func__, "(getArtist) ID is null.");
|
||||||
|
// TODO handle error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/getArtist?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||||
|
(*urlObj)->id);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETLYRICSBYSONGID:
|
||||||
|
if ((*urlObj)->id == NULL) {
|
||||||
|
logger_log_error(__func__, "(getArtist) ID is null.");
|
||||||
|
// TODO handle error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/getLyricsBySongId?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||||
|
(*urlObj)->id);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST:
|
||||||
|
if ((*urlObj)->type == 0) {
|
||||||
|
logger_log_error(__func__, "(getAlbumList) Type is 0.");
|
||||||
|
// TODO handle error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((*urlObj)->amount == 0) {
|
||||||
|
logger_log_error(__func__, "(getAlbumList) Amount is 0.");
|
||||||
|
// TODO handle error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* typeString = NULL;
|
||||||
|
switch ((*urlObj)->type) {
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RANDOM:
|
||||||
|
rc = asprintf(&typeString, "random");
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST_NEWEST:
|
||||||
|
rc = asprintf(&typeString, "newest");
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST_HIGHEST:
|
||||||
|
rc = asprintf(&typeString, "highest");
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST_FREQUENT:
|
||||||
|
rc = asprintf(&typeString, "frequent");
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RECENT:
|
||||||
|
rc = asprintf(&typeString, "recent");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger_log_error(__func__, "(getAlbumList) Unknown type requested.");
|
||||||
|
// TODO handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/getAlbumList?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&type=%s&size=%d",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||||
|
typeString, (*urlObj)->amount);
|
||||||
|
free(typeString);
|
||||||
|
break;
|
||||||
|
case OPENSUBSONIC_ENDPOINT_SCROBBLE:
|
||||||
|
if ((*urlObj)->id == NULL) {
|
||||||
|
logger_log_error(__func__, "(scrobble) ID is null.");
|
||||||
|
// TODO handle error
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* submitString = NULL;
|
||||||
|
if ((*urlObj)->submit) {
|
||||||
|
rc = asprintf(&submitString, "true");
|
||||||
|
} else {
|
||||||
|
rc = asprintf(&submitString, "false");
|
||||||
|
}
|
||||||
|
rc = asprintf(&url, "%s://%s/rest/scrobble?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s&submission=%s",
|
||||||
|
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||||
|
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||||
|
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||||
|
(*urlObj)->id, submitString);
|
||||||
|
free(submitString);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger_log_error(__func__, "Unknown endpoint requested.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() error.");
|
||||||
|
// TODO handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK
|
||||||
|
(*urlObj)->formedUrl = strdup(url); free(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_httpClient_fetchResponse(opensubsonic_httpClient_URL_t** urlObj, void** responseObj) {
|
||||||
|
// Make and prepare HTTP object
|
||||||
|
opensubsonic_httpClientRequest_t* httpReq;
|
||||||
|
opensubsonic_httpClient_prepareRequest(&httpReq);
|
||||||
|
httpReq->method = HTTP_METHOD_GET;
|
||||||
|
httpReq->requestUrl = strdup((*urlObj)->formedUrl);
|
||||||
|
opensubsonic_httpClient_request(&httpReq);
|
||||||
|
|
||||||
|
// Cannot use a switch statement here due to maintaining compatibility with < C23
|
||||||
|
if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_PING) {
|
||||||
|
opensubsonic_ping_struct** pingStruct = (opensubsonic_ping_struct**)responseObj;
|
||||||
|
opensubsonic_ping_parse(httpReq->responseMsg, pingStruct);
|
||||||
|
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETSTARRED) {
|
||||||
|
opensubsonic_getStarred_struct** getStarredStruct = (opensubsonic_getStarred_struct**)responseObj;
|
||||||
|
opensubsonic_getStarred_parse(httpReq->responseMsg, getStarredStruct);
|
||||||
|
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETSONG) {
|
||||||
|
opensubsonic_getSong_struct** getSongStruct = (opensubsonic_getSong_struct**)responseObj;
|
||||||
|
opensubsonic_getSong_parse(httpReq->responseMsg, getSongStruct);
|
||||||
|
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETALBUM) {
|
||||||
|
opensubsonic_getAlbum_struct** getAlbumStruct = (opensubsonic_getAlbum_struct**)responseObj;
|
||||||
|
opensubsonic_getAlbum_parse(httpReq->responseMsg, getAlbumStruct);
|
||||||
|
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETPLAYLISTS) {
|
||||||
|
opensubsonic_getPlaylists_struct** getPlaylistsStruct = (opensubsonic_getPlaylists_struct**)responseObj;
|
||||||
|
opensubsonic_getPlaylists_parse(httpReq->responseMsg, getPlaylistsStruct);
|
||||||
|
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETPLAYLIST) {
|
||||||
|
opensubsonic_getPlaylist_struct** getPlaylistStruct = (opensubsonic_getPlaylist_struct**)responseObj;
|
||||||
|
opensubsonic_getPlaylist_parse(httpReq->responseMsg, getPlaylistStruct);
|
||||||
|
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETARTISTS) {
|
||||||
|
opensubsonic_getArtists_struct** getArtistsStruct = (opensubsonic_getArtists_struct**)responseObj;
|
||||||
|
opensubsonic_getArtists_parse(httpReq->responseMsg, getArtistsStruct);
|
||||||
|
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETARTIST) {
|
||||||
|
opensubsonic_getArtist_struct** getArtistStruct = (opensubsonic_getArtist_struct**)responseObj;
|
||||||
|
opensubsonic_getArtist_parse(httpReq->responseMsg, getArtistStruct);
|
||||||
|
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETLYRICSBYSONGID) {
|
||||||
|
opensubsonic_getLyricsBySongId_struct** getLyricsBySongIdStruct = (opensubsonic_getLyricsBySongId_struct**)responseObj;
|
||||||
|
opensubsonic_getLyricsBySongId_parse(httpReq->responseMsg, getLyricsBySongIdStruct);
|
||||||
|
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETALBUMLIST) {
|
||||||
|
opensubsonic_getAlbumList_struct** getAlbumListStruct = (opensubsonic_getAlbumList_struct**)responseObj;
|
||||||
|
opensubsonic_getAlbumList_parse(httpReq->responseMsg, getAlbumListStruct);
|
||||||
|
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_SCROBBLE) {
|
||||||
|
opensubsonic_scrobble_struct** scrobbleStruct = (opensubsonic_scrobble_struct**)responseObj;
|
||||||
|
opensubsonic_scrobble_parse(httpReq->responseMsg, scrobbleStruct);
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Unknown endpoint requested.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup HTTP object
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Contact the /rest/getAlbum endpoint
|
||||||
|
int opensubsonic_getAlbum(const char* protocol_ptr, const char* server_ptr, const char* user_ptr, char* login_token_ptr, char* login_salt_ptr, const char* opensubsonic_version_ptr, const char* client_name_ptr, char* id, char** response) {
|
||||||
|
// Generate full URL, perform HTTP GET, and free the full URL
|
||||||
|
int rc = 0;
|
||||||
|
char* full_url = malloc(256);
|
||||||
|
snprintf(full_url, 256, "%s://%s/rest/getAlbum?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s", protocol_ptr, server_ptr, user_ptr, login_token_ptr, login_salt_ptr, opensubsonic_version_ptr, client_name_ptr, id);
|
||||||
|
//rc = opensubsonic_http_json_get(full_url, response);
|
||||||
|
free(full_url);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO COVER ART - Returns JSON on error.
|
||||||
|
// {"subsonic-response":{"status":"failed","version":"1.16.1","type":"navidrome","serverVersion":"0.53.1-FREEBSD (1ba390a)","openSubsonic":true,"error":{"code":70,"message":"Artwork not found"}}}
|
||||||
|
// Contact the /rest/getCoverArt endpoint (Returns binary data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Functions for preparing / freeing a HTTP Request struct
|
||||||
|
void opensubsonic_httpClient_prepareRequest(opensubsonic_httpClientRequest_t** httpReq) {
|
||||||
|
// Allocate struct
|
||||||
|
*httpReq = (opensubsonic_httpClientRequest_t*)malloc(sizeof(opensubsonic_httpClientRequest_t));
|
||||||
|
|
||||||
|
// Initialize struct variables
|
||||||
|
(*httpReq)->requestUrl = NULL;
|
||||||
|
(*httpReq)->requestBody = NULL;
|
||||||
|
(*httpReq)->method = 0;
|
||||||
|
(*httpReq)->isBodyRequired = false;
|
||||||
|
(*httpReq)->scrobbler = 0;
|
||||||
|
(*httpReq)->responseCode = 0;
|
||||||
|
(*httpReq)->responseMsg = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void opensubsonic_httpClient_cleanup(opensubsonic_httpClientRequest_t** httpReq) {
|
||||||
|
// Free heap-allocated struct variables
|
||||||
|
if ((*httpReq)->requestUrl != NULL) { free((*httpReq)->requestUrl); }
|
||||||
|
if ((*httpReq)->requestBody != NULL) { free((*httpReq)->requestBody); }
|
||||||
|
if ((*httpReq)->responseMsg != NULL) { free((*httpReq)->responseMsg); }
|
||||||
|
|
||||||
|
// Free struct
|
||||||
|
free(*httpReq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform HTTP POST for Scrobbling (This function is a wrapper around OS-specific networking functions)
|
||||||
|
int opensubsonic_httpClient_request(opensubsonic_httpClientRequest_t** httpReq) {
|
||||||
|
logger_log_general(__func__, "Performing HTTP Request.");
|
||||||
|
//#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
//XNU_HttpRequest(httpReq);
|
||||||
|
//#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||||
|
UNIX_HttpRequest(httpReq);
|
||||||
|
//#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct memory {
|
||||||
|
char *data;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t write_to_memory(void *ptr, size_t size, size_t nmemb, void *userdata) {
|
||||||
|
struct memory *mem = (struct memory *)userdata;
|
||||||
|
size_t total_size = size * nmemb;
|
||||||
|
|
||||||
|
mem->data = realloc(mem->data, mem->size + total_size + 1);
|
||||||
|
if (!mem->data) return 0; // Fail on OOM
|
||||||
|
|
||||||
|
memcpy(&(mem->data[mem->size]), ptr, total_size);
|
||||||
|
mem->size += total_size;
|
||||||
|
mem->data[mem->size] = '\0'; // Null-terminate
|
||||||
|
|
||||||
|
return total_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UNIX_HttpRequest(opensubsonic_httpClientRequest_t** httpReq) {
|
||||||
|
CURL* curl_handle = curl_easy_init();
|
||||||
|
struct curl_slist* header_list = NULL;
|
||||||
|
struct memory chunk = {0};
|
||||||
|
long httpCode = 0;
|
||||||
|
|
||||||
|
if (curl_handle) {
|
||||||
|
// Set method
|
||||||
|
if ((*httpReq)->method == HTTP_METHOD_GET) {
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "GET");
|
||||||
|
} else if ((*httpReq)->method == HTTP_METHOD_POST) {
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "POST");
|
||||||
|
if ((*httpReq)->isBodyRequired == false) {
|
||||||
|
header_list = curl_slist_append(header_list, "Content-Length: 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set scrobbler information
|
||||||
|
if ((*httpReq)->scrobbler == SCROBBLER_LISTENBRAINZ && (*httpReq)->method == HTTP_METHOD_POST) {
|
||||||
|
header_list = curl_slist_append(header_list, "Content-Type: application/json");
|
||||||
|
char* authString = NULL;
|
||||||
|
rc = asprintf(&authString, "Authorization: Token %s", configObj->listenbrainz_token);
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() error.");
|
||||||
|
// TODO handle error
|
||||||
|
}
|
||||||
|
printf("CODE: %s\n", authString);
|
||||||
|
header_list = curl_slist_append(header_list, authString); // TODO Check does this copy the string?
|
||||||
|
// TODO free auth string
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*httpReq)->isBodyRequired == true && (*httpReq)->scrobbler == 0) {
|
||||||
|
header_list = curl_slist_append(header_list, "X-Organization: Hojuix");
|
||||||
|
header_list = curl_slist_append(header_list, "X-Application: OSSP");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header_list != NULL) {
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, header_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*httpReq)->isBodyRequired == true) {
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, (*httpReq)->requestBody);
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, (long)strlen((*httpReq)->requestBody));
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "OSSP/1.0 (avery@hojuix.org)");
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_URL, (*httpReq)->requestUrl);
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 50L);
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_TCP_KEEPALIVE,0L);
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_to_memory);
|
||||||
|
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
|
||||||
|
CURLcode res = curl_easy_perform(curl_handle);
|
||||||
|
curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_cleanup(curl_handle);
|
||||||
|
|
||||||
|
(*httpReq)->responseMsg = strdup(chunk.data);
|
||||||
|
(*httpReq)->responseCode = (int)httpCode;
|
||||||
|
free(chunk.data);
|
||||||
|
}
|
||||||
75
src/libopensubsonic/httpclient.h
Normal file
75
src/libopensubsonic/httpclient.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#ifndef _HTTPCLIENT_H
|
||||||
|
#define _HTTPCLIENT_H
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define SCROBBLER_LISTENBRAINZ 101
|
||||||
|
#define SCROBBLER_LASTFM 102
|
||||||
|
#define HTTP_METHOD_GET 201
|
||||||
|
#define HTTP_METHOD_POST 202
|
||||||
|
#define HTTP_CODE_SUCCESS 200
|
||||||
|
#define HTTP_CODE_NOT_AUTHORIZED 403
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_PING 301
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETSTARRED 302
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETSONG 303
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_STREAM 304
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETCOVERART 305
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETALBUM 306
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETPLAYLISTS 307
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETPLAYLIST 308
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETARTISTS 309
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETARTIST 310
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETLYRICSBYSONGID 311
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST 312
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_SCROBBLE 313
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RANDOM 501
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_NEWEST 502
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_HIGHEST 503
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_FREQUENT 504
|
||||||
|
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RECENT 505
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int endpoint; // Endpoint
|
||||||
|
char* id; // ID
|
||||||
|
int type; // Type of request (As used in the /getAlbumList endpoint)
|
||||||
|
int amount; // Amount of items to return (Also as used in the /getAlbumList endpoint)
|
||||||
|
bool submit; // Submit scrobble (used for the /scrobble endpoint)
|
||||||
|
char* formedUrl; // Final URL
|
||||||
|
} opensubsonic_httpClient_URL_t; // Forms authenticated URLs with required parameters
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// Request Information
|
||||||
|
char* requestUrl;
|
||||||
|
char* requestBody;
|
||||||
|
int method;
|
||||||
|
bool isBodyRequired;
|
||||||
|
int scrobbler;
|
||||||
|
|
||||||
|
// Response Information
|
||||||
|
int responseCode;
|
||||||
|
char* responseMsg;
|
||||||
|
} opensubsonic_httpClientRequest_t; // OS-agnostic HTTP interface
|
||||||
|
|
||||||
|
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||||
|
void UNIX_HttpRequest(opensubsonic_httpClientRequest_t** httpReq);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void opensubsonic_httpClient_URL_prepare(opensubsonic_httpClient_URL_t** urlObj);
|
||||||
|
void opensubsonic_httpClient_URL_cleanup(opensubsonic_httpClient_URL_t** urlObj);
|
||||||
|
void opensubsonic_httpClient_formUrl(opensubsonic_httpClient_URL_t** urlObj);
|
||||||
|
void opensubsonic_httpClient_fetchResponse(opensubsonic_httpClient_URL_t** urlObj, void** responseObj);
|
||||||
|
void opensubsonic_httpClient_prepareRequest(opensubsonic_httpClientRequest_t** httpReq);
|
||||||
|
void opensubsonic_httpClient_cleanup(opensubsonic_httpClientRequest_t** httpReq);
|
||||||
|
int opensubsonic_httpClient_request(opensubsonic_httpClientRequest_t** httpReq);
|
||||||
|
|
||||||
|
void UNIX_HttpRequest(opensubsonic_httpClientRequest_t** httpReq);
|
||||||
|
|
||||||
|
// DEPRECATED - TO BE REMOVED SOON - APART OF THE OLD INFRASTRUCTURE
|
||||||
|
typedef struct {
|
||||||
|
char* memory;
|
||||||
|
size_t size;
|
||||||
|
} binary_response_struct;
|
||||||
|
|
||||||
|
int opensubsonic_getAlbum(const char* protocol_ptr, const char* server_ptr, const char* user_ptr, char* login_token_ptr, char* login_salt_ptr, const char* opensubsonic_version_ptr, const char* client_name_ptr, char* id, char** response);
|
||||||
|
|
||||||
|
#endif
|
||||||
74
src/libopensubsonic/logger.c
Normal file
74
src/libopensubsonic/logger.c
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#define LOGGER_COLOR_BLACK "\033[0;30m"
|
||||||
|
#define LOGGER_COLOR_RED "\033[0;31m"
|
||||||
|
#define LOGGER_COLOR_GREEN "\033[0;32m"
|
||||||
|
#define LOGGER_COLOR_YELLOW "\033[0;33m"
|
||||||
|
#define LOGGER_COLOR_BLUE "\033[0;34m"
|
||||||
|
#define LOGGER_COLOR_PURPLE "\033[0;35m"
|
||||||
|
#define LOGGER_COLOR_CYAN "\033[0;36m"
|
||||||
|
#define LOGGER_COLOR_WHITE "\033[0;37m"
|
||||||
|
|
||||||
|
#define LOGGER_MODE_EVERYTHING 1
|
||||||
|
|
||||||
|
void logger_init(int mode) {
|
||||||
|
printf("%s +\n", __func__);
|
||||||
|
|
||||||
|
printf("%s HAHAHA %s HAHAHA\n", LOGGER_COLOR_RED, LOGGER_COLOR_WHITE);
|
||||||
|
|
||||||
|
printf("%s -\n", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log entry into / exit from function
|
||||||
|
// Action 1 = Entry, Action 2 = Exit
|
||||||
|
void logger_log_function(const char* function, int action) {
|
||||||
|
if (action == 1) {
|
||||||
|
printf("%s%s +%s\n", LOGGER_COLOR_YELLOW, function, LOGGER_COLOR_WHITE);
|
||||||
|
} else if (action == 2) {
|
||||||
|
printf("%s%s -%s\n", LOGGER_COLOR_YELLOW, function, LOGGER_COLOR_WHITE);
|
||||||
|
} else {
|
||||||
|
printf("%sERROR: Invalid logger action!%s\n", LOGGER_COLOR_RED, LOGGER_COLOR_WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log error
|
||||||
|
void logger_log_error(const char* function, const char* format, ...) {
|
||||||
|
printf("%s(%s) ERROR - ", LOGGER_COLOR_RED, function);
|
||||||
|
va_list argptr;
|
||||||
|
va_start(argptr, format);
|
||||||
|
vprintf(format, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
printf("%s\n", LOGGER_COLOR_WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log general information
|
||||||
|
void logger_log_general(const char* function, const char* format, ...) {
|
||||||
|
printf("%s(%s) ", LOGGER_COLOR_YELLOW, function);
|
||||||
|
va_list argptr;
|
||||||
|
va_start(argptr, format);
|
||||||
|
vprintf(format, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
printf("%s\n", LOGGER_COLOR_WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log important information
|
||||||
|
void logger_log_important(const char* function, const char* format, ...) {
|
||||||
|
printf("%s(%s) ", LOGGER_COLOR_GREEN, function);
|
||||||
|
va_list argptr;
|
||||||
|
va_start(argptr, format);
|
||||||
|
vprintf(format, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
printf("%s\n", LOGGER_COLOR_WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log title
|
||||||
|
void logger_log_title(const char* function, const char* format, ...) {
|
||||||
|
printf("%s(%s) ", LOGGER_COLOR_PURPLE, function);
|
||||||
|
va_list argptr;
|
||||||
|
va_start(argptr, format);
|
||||||
|
vprintf(format, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
printf("%s\n", LOGGER_COLOR_WHITE);
|
||||||
|
}
|
||||||
11
src/libopensubsonic/logger.h
Normal file
11
src/libopensubsonic/logger.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef _LOGGER_H
|
||||||
|
#define _LOGGER_H
|
||||||
|
|
||||||
|
void logger_init(int mode);
|
||||||
|
void logger_log_function(const char* function, int action);
|
||||||
|
void logger_log_error(const char* function, const char* format, ...);
|
||||||
|
void logger_log_general(const char* function, const char* format, ...);
|
||||||
|
void logger_log_important(const char* function, const char* format, ...);
|
||||||
|
void logger_log_title(const char* function, const char* format, ...);
|
||||||
|
|
||||||
|
#endif
|
||||||
530
src/libopensubsonic/scrobble_lastFm.c
Normal file
530
src/libopensubsonic/scrobble_lastFm.c
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "scrobble_lastFm.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "httpclient.h"
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "../external/md5.h"
|
||||||
|
#include "../external/libcurl_uriescape.h"
|
||||||
|
#include "../configHandler.h"
|
||||||
|
#include "../DarwinHttpClient.h"
|
||||||
|
#include "../configHandler.h"
|
||||||
|
|
||||||
|
const char* lastFmScrobbleURL = "https://ws.audioscrobbler.com/2.0/";
|
||||||
|
static int rc = 0;
|
||||||
|
extern configHandler_config_t* configObj;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Authenticate with LastFM using the method.getMobileSession endpoint
|
||||||
|
* Receives credentials from the config file, and returns the token as a heap allocated char*, or NULL if failure
|
||||||
|
*/
|
||||||
|
char* opensubsonic_authenticate_lastFm(void) {
|
||||||
|
logger_log_general(__func__, "Attempting to authenticate with LastFM.");
|
||||||
|
|
||||||
|
// Check to make sure there are credentials present
|
||||||
|
if (configObj->lastfm_username == NULL || configObj->lastfm_username[0] == '\0' ||
|
||||||
|
configObj->lastfm_password == NULL || configObj->lastfm_password[0] == '\0' ||
|
||||||
|
configObj->lastfm_api_key == NULL || configObj->lastfm_api_key[0] == '\0' ||
|
||||||
|
configObj->lastfm_api_secret == NULL || configObj->lastfm_api_secret[0] == '\0') {
|
||||||
|
logger_log_error(__func__, "LastFM Username/Password/API Key/API Secret is not in the config file.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the signature
|
||||||
|
char* sig_plaintext = NULL;
|
||||||
|
rc = asprintf(&sig_plaintext, "api_key%smethodauth.getMobileSessionpassword%susername%s%s",
|
||||||
|
configObj->lastfm_api_key, configObj->lastfm_password,
|
||||||
|
configObj->lastfm_username, configObj->lastfm_api_secret);
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t sig_md5_bytes[16];
|
||||||
|
char* sig_md5_text = NULL;
|
||||||
|
md5String(sig_plaintext, sig_md5_bytes);
|
||||||
|
free(sig_plaintext);
|
||||||
|
rc = asprintf(&sig_md5_text, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||||
|
sig_md5_bytes[0], sig_md5_bytes[1], sig_md5_bytes[2], sig_md5_bytes[3],
|
||||||
|
sig_md5_bytes[4], sig_md5_bytes[5], sig_md5_bytes[6], sig_md5_bytes[7],
|
||||||
|
sig_md5_bytes[8], sig_md5_bytes[9], sig_md5_bytes[10], sig_md5_bytes[11],
|
||||||
|
sig_md5_bytes[12], sig_md5_bytes[13], sig_md5_bytes[14], sig_md5_bytes[15]);
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the payload
|
||||||
|
char* payload = NULL;
|
||||||
|
rc = asprintf(&payload, "%s?method=auth.getMobileSession&api_key=%s&username=%s&password=%s&api_sig=%s&format=json",
|
||||||
|
lastFmScrobbleURL, configObj->lastfm_api_key, configObj->lastfm_username,
|
||||||
|
configObj->lastfm_password, sig_md5_text);
|
||||||
|
free(sig_md5_text);
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send payload and receive JSON response
|
||||||
|
opensubsonic_httpClientRequest_t* httpReq;
|
||||||
|
opensubsonic_httpClient_prepareRequest(&httpReq);
|
||||||
|
|
||||||
|
httpReq->requestUrl = strdup(payload);
|
||||||
|
free(payload);
|
||||||
|
httpReq->scrobbler = SCROBBLER_LASTFM;
|
||||||
|
httpReq->method = HTTP_METHOD_POST;
|
||||||
|
opensubsonic_httpClient_request(&httpReq);
|
||||||
|
|
||||||
|
if (httpReq->responseCode != HTTP_CODE_SUCCESS && httpReq->responseCode != HTTP_CODE_NOT_AUTHORIZED) {
|
||||||
|
logger_log_error(__func__, "HTTP POST returned invalid code (%d).", httpReq->responseCode);
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse response JSON
|
||||||
|
cJSON* root = cJSON_Parse(httpReq->responseMsg);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpReq->responseCode == HTTP_CODE_SUCCESS) {
|
||||||
|
// Make an object from session
|
||||||
|
cJSON* session_root = cJSON_GetObjectItemCaseSensitive(root, "session");
|
||||||
|
if (session_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - session does not exist.");
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* name = cJSON_GetObjectItemCaseSensitive(session_root, "name");
|
||||||
|
if (cJSON_IsString(name) && name->valuestring != NULL) {
|
||||||
|
printf("Fetched username: %s\n", name->valuestring);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* key = cJSON_GetObjectItemCaseSensitive(session_root, "key");
|
||||||
|
if (cJSON_IsString(key) && key->valuestring != NULL) {
|
||||||
|
printf("Fetched key: %s\n", key->valuestring);
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - key does not exist.");
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
} else if (httpReq->responseCode == HTTP_CODE_NOT_AUTHORIZED) {
|
||||||
|
cJSON* error = cJSON_GetObjectItemCaseSensitive(root, "error");
|
||||||
|
if (cJSON_IsNumber(error)) {
|
||||||
|
int code = error->valueint;
|
||||||
|
switch (code) {
|
||||||
|
case 2:
|
||||||
|
logger_log_error(__func__, "Invalid service (%d).", code);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
logger_log_error(__func__, "Invalid method (%d).", code);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
logger_log_error(__func__, "Auth error (%d).", code);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
logger_log_error(__func__, "Invalid format (%d).", code);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
logger_log_error(__func__, "Invalid parameters (%d).", code);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
logger_log_error(__func__, "Invalid resource (%d).", code);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
logger_log_error(__func__, "Operation failed (%d).", code);
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
logger_log_error(__func__, "Invalid session key (%d).", code);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
logger_log_error(__func__, "Invalid API key (%d).", code);
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
logger_log_error(__func__, "Service offline (%d).", code);
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
logger_log_error(__func__, "Invalid method signature (%d).", code);
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
logger_log_error(__func__, "Temporary error processing request (%d).", code);
|
||||||
|
break;
|
||||||
|
case 26:
|
||||||
|
logger_log_error(__func__, "Suspended API key (%d).", code);
|
||||||
|
break;
|
||||||
|
case 29:
|
||||||
|
logger_log_error(__func__, "Rate limit exceeded (%d).", code);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger_log_error(__func__, "Unknown error (%d).", code);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
|
||||||
|
// TODO fix error codes, do something with the code etc
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sends a scrobble to LastFM
|
||||||
|
* If 'finalize' is true, it sends to the track.scrobble endpoint
|
||||||
|
* If 'finalize' is false, it sends to the track.updateNowPlaying endpoint
|
||||||
|
*/
|
||||||
|
void opensubsonic_scrobble_lastFm(bool finalize, opensubsonic_getSong_struct* songStruct) {
|
||||||
|
if (finalize) {
|
||||||
|
logger_log_general(__func__, "Performing final scrobble to LastFM.");
|
||||||
|
} else {
|
||||||
|
logger_log_general(__func__, "Performing in-progress scrobble to LastFM.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the current UNIX timestamp
|
||||||
|
time_t currentTime;
|
||||||
|
char* currentTime_string;
|
||||||
|
currentTime = time(NULL);
|
||||||
|
rc = asprintf(¤tTime_string, "%ld", currentTime);
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed.");
|
||||||
|
return; // TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the signature
|
||||||
|
char* sig_plaintext = NULL;
|
||||||
|
if (finalize) {
|
||||||
|
rc = asprintf(&sig_plaintext, "album%salbumArtist%sapi_key%sartist%smethodtrack.scrobblesk%stimestamp%strack%s%s",
|
||||||
|
songStruct->album, songStruct->artist, configObj->lastfm_api_key, songStruct->artist,
|
||||||
|
configObj->lastfm_api_session_key, currentTime_string, songStruct->title, configObj->lastfm_api_secret);
|
||||||
|
} else {
|
||||||
|
rc = asprintf(&sig_plaintext, "album%salbumArtist%sapi_key%sartist%smethodtrack.updateNowPlayingsk%stimestamp%strack%s%s",
|
||||||
|
songStruct->album, songStruct->artist, configObj->lastfm_api_key, songStruct->artist,
|
||||||
|
configObj->lastfm_api_session_key, currentTime_string, songStruct->title, configObj->lastfm_api_secret);
|
||||||
|
}
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed.");
|
||||||
|
return; // TODO handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t sig_md5_bytes[16];
|
||||||
|
char* sig_md5_text = NULL; // TODO do I have to free this? Also is be used in crypto.c
|
||||||
|
md5String(sig_plaintext, sig_md5_bytes);
|
||||||
|
free(sig_plaintext);
|
||||||
|
rc = asprintf(&sig_md5_text, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||||
|
sig_md5_bytes[0], sig_md5_bytes[1], sig_md5_bytes[2], sig_md5_bytes[3],
|
||||||
|
sig_md5_bytes[4], sig_md5_bytes[5], sig_md5_bytes[6], sig_md5_bytes[7],
|
||||||
|
sig_md5_bytes[8], sig_md5_bytes[9], sig_md5_bytes[10], sig_md5_bytes[11],
|
||||||
|
sig_md5_bytes[12], sig_md5_bytes[13], sig_md5_bytes[14], sig_md5_bytes[15]);
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed.");
|
||||||
|
return; // TODO handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// URI encode strings
|
||||||
|
char* uri_songTitle = lcue_uriescape(songStruct->title, (unsigned int)strlen(songStruct->title));
|
||||||
|
char* uri_songArtist = lcue_uriescape(songStruct->artist, (unsigned int)strlen(songStruct->artist));
|
||||||
|
char* uri_songAlbum = lcue_uriescape(songStruct->album, (unsigned int)strlen(songStruct->album));
|
||||||
|
if (uri_songTitle == NULL || uri_songArtist == NULL || uri_songAlbum == NULL) {
|
||||||
|
logger_log_error(__func__, "lcue_uriescape() error.");
|
||||||
|
free(currentTime_string);
|
||||||
|
free(sig_md5_text);
|
||||||
|
if (uri_songTitle != NULL) { free(uri_songTitle); }
|
||||||
|
if (uri_songArtist != NULL) { free(uri_songArtist); }
|
||||||
|
if (uri_songAlbum != NULL) { free(uri_songAlbum); }
|
||||||
|
return; // TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the payload
|
||||||
|
char* payload = NULL;
|
||||||
|
if (finalize) {
|
||||||
|
rc = asprintf(&payload,
|
||||||
|
"%s?method=track.scrobble&api_key=%s×tamp=%s&track=%s&artist=%s&album=%s&albumArtist=%s&sk=%s&api_sig=%s&format=json",
|
||||||
|
lastFmScrobbleURL, configObj->lastfm_api_key, currentTime_string, uri_songTitle,
|
||||||
|
uri_songArtist, uri_songAlbum, uri_songArtist,
|
||||||
|
configObj->lastfm_api_session_key, sig_md5_text);
|
||||||
|
} else {
|
||||||
|
rc = asprintf(&payload,
|
||||||
|
"%s?method=track.updateNowPlaying&api_key=%s×tamp=%s&track=%s&artist=%s&album=%s&albumArtist=%s&sk=%s&api_sig=%s&format=json",
|
||||||
|
lastFmScrobbleURL, configObj->lastfm_api_key, currentTime_string, uri_songTitle,
|
||||||
|
uri_songArtist, uri_songAlbum, uri_songArtist,
|
||||||
|
configObj->lastfm_api_session_key, sig_md5_text);
|
||||||
|
}
|
||||||
|
free(currentTime_string);
|
||||||
|
free(sig_md5_text);
|
||||||
|
free(uri_songTitle);
|
||||||
|
free(uri_songAlbum);
|
||||||
|
free(uri_songArtist);
|
||||||
|
if (rc == -1) {
|
||||||
|
logger_log_error(__func__, "asprintf() failed.");
|
||||||
|
return; // TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send scrobble and receive response
|
||||||
|
opensubsonic_httpClientRequest_t* httpReq;
|
||||||
|
opensubsonic_httpClient_prepareRequest(&httpReq);
|
||||||
|
|
||||||
|
httpReq->requestUrl = strdup(payload);
|
||||||
|
free(payload);
|
||||||
|
httpReq->scrobbler = SCROBBLER_LASTFM;
|
||||||
|
httpReq->method = HTTP_METHOD_POST;
|
||||||
|
opensubsonic_httpClient_request(&httpReq);
|
||||||
|
|
||||||
|
if (httpReq->responseCode != HTTP_CODE_SUCCESS) {
|
||||||
|
logger_log_error(__func__, "HTTP POST did not return success (%d).", httpReq->responseCode);
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the scrobble response
|
||||||
|
cJSON* root = cJSON_Parse(httpReq->responseMsg);
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
if (root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* inner_root = NULL;
|
||||||
|
cJSON* scrobbles_root = NULL; // Parent of inner_root, only used on final scrobble
|
||||||
|
if (finalize) {
|
||||||
|
// Make an object from scrobbles
|
||||||
|
scrobbles_root = cJSON_GetObjectItemCaseSensitive(root, "scrobbles");
|
||||||
|
if (scrobbles_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - scrobbles does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from scrobble
|
||||||
|
inner_root = cJSON_GetObjectItemCaseSensitive(scrobbles_root, "scrobble");
|
||||||
|
if (inner_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - scrobble does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Make an object from nowplaying
|
||||||
|
inner_root = cJSON_GetObjectItemCaseSensitive(root, "nowplaying");
|
||||||
|
if (inner_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - nowplaying does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from artist, track, albumArtist, and album, and fetch codes
|
||||||
|
cJSON* artist_root = cJSON_GetObjectItemCaseSensitive(inner_root, "artist");
|
||||||
|
if (artist_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - artist does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* artist_corrected = cJSON_GetObjectItemCaseSensitive(artist_root, "corrected");
|
||||||
|
if (cJSON_IsString(artist_corrected) && artist_corrected->valuestring != NULL) {
|
||||||
|
if (*(artist_corrected->valuestring) == '\0') {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - artist/corrected is empty.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
char* endptr;
|
||||||
|
int corrected = (int)strtol(artist_corrected->valuestring, &endptr, 10);
|
||||||
|
if (*endptr != '\0') {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - artist/corrected strtol/endptr is not empty.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
if (corrected == 1) {
|
||||||
|
logger_log_important(__func__, "Warning - Artist has been autocorrected.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - artist/corrected does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* track_root = cJSON_GetObjectItemCaseSensitive(inner_root, "track");
|
||||||
|
if (track_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - track does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* track_corrected = cJSON_GetObjectItemCaseSensitive(track_root, "corrected");
|
||||||
|
if (cJSON_IsString(track_corrected) && track_corrected->valuestring != NULL) {
|
||||||
|
if (*(track_corrected->valuestring) == '\0') {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - track/corrected is empty.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
char* endptr;
|
||||||
|
int corrected = (int)strtol(track_corrected->valuestring, &endptr, 10);
|
||||||
|
if (*endptr != '\0') {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - track/corrected strtol/endptr is not empty.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
if (corrected == 1) {
|
||||||
|
logger_log_important(__func__, "Warning - Track has been autocorrected.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - track/corrected does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* albumArtist_root = cJSON_GetObjectItemCaseSensitive(inner_root, "albumArtist");
|
||||||
|
if (albumArtist_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - albumArtist does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* albumArtist_corrected = cJSON_GetObjectItemCaseSensitive(albumArtist_root, "corrected");
|
||||||
|
if (cJSON_IsString(albumArtist_corrected) && albumArtist_corrected->valuestring != NULL) {
|
||||||
|
if (*(albumArtist_corrected->valuestring) == '\0') {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - albumArtist/corrected is empty.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
char* endptr;
|
||||||
|
int corrected = (int)strtol(albumArtist_corrected->valuestring, &endptr, 10);
|
||||||
|
if (*endptr != '\0') {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - albumArtist/corrected strtol/endptr is not empty.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
if (corrected == 1) {
|
||||||
|
logger_log_important(__func__, "Warning - Album Artist has been autocorrected.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - albumArtist/corrected does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(inner_root, "album");
|
||||||
|
if (album_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - album does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* album_corrected = cJSON_GetObjectItemCaseSensitive(album_root, "corrected");
|
||||||
|
if (cJSON_IsString(album_corrected) && album_corrected->valuestring != NULL) {
|
||||||
|
if (*(album_corrected->valuestring) == '\0') {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - album/corrected is empty.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
char* endptr;
|
||||||
|
int corrected = (int)strtol(album_corrected->valuestring, &endptr, 10);
|
||||||
|
if (*endptr != '\0') {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - album/corrected strtol/endptr is not empty.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
if (corrected == 1) {
|
||||||
|
logger_log_important(__func__, "Warning - Album has been autocorrected.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - album/corrected does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an object from ignoredMessage, and check return code
|
||||||
|
cJSON* ignoredMessage_root = cJSON_GetObjectItemCaseSensitive(inner_root, "ignoredMessage");
|
||||||
|
if (ignoredMessage_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - ignoredMessage does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* ignoredMessage_code = cJSON_GetObjectItemCaseSensitive(ignoredMessage_root, "code");
|
||||||
|
if (cJSON_IsString(ignoredMessage_code) && ignoredMessage_code->valuestring != NULL) {
|
||||||
|
if (*(ignoredMessage_code->valuestring) == '\0') {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - ignoredMessage/code is empty.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
char* endptr;
|
||||||
|
int code = (int)strtol(ignoredMessage_code->valuestring, &endptr, 10);
|
||||||
|
if (*endptr != '\0') {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - ignoredMessage/code strtol/endptr is not empty.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
if (code == 0) {
|
||||||
|
if (!finalize) {
|
||||||
|
logger_log_general(__func__, "In progress scrobble was successful.");
|
||||||
|
} else {
|
||||||
|
logger_log_general(__func__, "Final scrobble 1/2 was successful.");
|
||||||
|
}
|
||||||
|
} else if (code == 1) {
|
||||||
|
logger_log_error(__func__, "Artist was ignored.");
|
||||||
|
} else if (code == 2) {
|
||||||
|
logger_log_error(__func__, "Track was ignored.");
|
||||||
|
} else if (code == 3) {
|
||||||
|
logger_log_error(__func__, "Timestamp was too old.");
|
||||||
|
} else if (code == 4) {
|
||||||
|
logger_log_error(__func__, "Timestamp was too new.");
|
||||||
|
} else if (code == 5) {
|
||||||
|
logger_log_error(__func__, "Daily scrobble limit exceeded.");
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Unknown error code received (%d)", code);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - ignoredMessage/code does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalize) {
|
||||||
|
cJSON* attr_root = cJSON_GetObjectItemCaseSensitive(scrobbles_root, "@attr");
|
||||||
|
if (attr_root == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - @attr does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* attr_ignored = cJSON_GetObjectItemCaseSensitive(attr_root, "ignored");
|
||||||
|
if (cJSON_IsNumber(attr_ignored)) {
|
||||||
|
if (attr_ignored->valueint != 0) {
|
||||||
|
logger_log_important(__func__, "Warning - @attr/ignored is not 0 (%d).", attr_ignored->valueint);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - @attr/ignored does not exist.");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* attr_accepted = cJSON_GetObjectItemCaseSensitive(attr_root, "accepted");
|
||||||
|
if (cJSON_IsNumber(attr_accepted)) {
|
||||||
|
if (attr_accepted->valueint != 1) {
|
||||||
|
logger_log_important(__func__, "Warning - @attr/accepted is not 1 (%d).", attr_accepted->valueint);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON - @attr/accepted does not exist");
|
||||||
|
cJSON_Delete(root);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, attr_ignored and attr_accepted are both known to be valid
|
||||||
|
if (attr_ignored->valueint == 0 && attr_accepted->valueint == 1) {
|
||||||
|
logger_log_general(__func__, "Final scrobble 2/2 was successful.");
|
||||||
|
} else {
|
||||||
|
logger_log_important(__func__, "Final scobble 2/2 was not successful (ignored: %d, accepted: %d",
|
||||||
|
attr_ignored->valueint, attr_accepted->valueint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
}
|
||||||
9
src/libopensubsonic/scrobble_lastFm.h
Normal file
9
src/libopensubsonic/scrobble_lastFm.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#ifndef _SCROBBLE_LASTFM_H
|
||||||
|
#define _SCROBBLE_LASTFM_H
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "endpoint_getSong.h"
|
||||||
|
|
||||||
|
char* opensubsonic_authenticate_lastFm(void);
|
||||||
|
void opensubsonic_scrobble_lastFm(bool finalize, opensubsonic_getSong_struct* songStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
141
src/libopensubsonic/scrobble_listenBrainz.c
Normal file
141
src/libopensubsonic/scrobble_listenBrainz.c
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "endpoint_getSong.h"
|
||||||
|
#include "httpclient.h"
|
||||||
|
#include "scrobble_listenBrainz.h"
|
||||||
|
#include "../DarwinHttpClient.h"
|
||||||
|
|
||||||
|
const char* listenBrainzScrobbleURL = "https://api.listenbrainz.org/1/submit-listens";
|
||||||
|
|
||||||
|
void opensubsonic_scrobble_listenBrainz(bool finalize, opensubsonic_getSong_struct* songStruct) {
|
||||||
|
if (finalize) {
|
||||||
|
logger_log_general(__func__, "Performing final scrobble to ListenBrainz.");
|
||||||
|
} else {
|
||||||
|
logger_log_general(__func__, "Performing in-progress scrobble to ListenBrainz.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the current UNIX timestamp
|
||||||
|
time_t currentTime;
|
||||||
|
currentTime = time(NULL);
|
||||||
|
|
||||||
|
// Form the JSON body
|
||||||
|
cJSON* rootObj = cJSON_CreateObject();
|
||||||
|
|
||||||
|
// Add ["listen_type"]
|
||||||
|
if (finalize) {
|
||||||
|
cJSON* listenTypeObj = cJSON_CreateString("single");
|
||||||
|
cJSON_AddItemToObject(rootObj, "listen_type", listenTypeObj);
|
||||||
|
} else {
|
||||||
|
cJSON* listenTypeObj = cJSON_CreateString("playing_now");
|
||||||
|
cJSON_AddItemToObject(rootObj, "listen_type", listenTypeObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ["payload"]
|
||||||
|
cJSON* payloadObj = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToObject(rootObj, "payload", payloadObj);
|
||||||
|
|
||||||
|
// Add ["payload"][]
|
||||||
|
cJSON* payloadContainedObj = cJSON_CreateObject();
|
||||||
|
cJSON_AddItemToArray(payloadObj, payloadContainedObj);
|
||||||
|
|
||||||
|
// Add ["listened_at"] if 'finalize' is true
|
||||||
|
if (finalize) {
|
||||||
|
cJSON* listenedAtObj = cJSON_CreateNumber((long)currentTime);
|
||||||
|
cJSON_AddItemToObject(payloadContainedObj, "listened_at", listenedAtObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ["track_metadata"]
|
||||||
|
cJSON* trackMetadataObj = cJSON_CreateObject();
|
||||||
|
cJSON_AddItemToObject(payloadContainedObj, "track_metadata", trackMetadataObj);
|
||||||
|
|
||||||
|
// Add ["track_metadata"]["additional_info"]
|
||||||
|
cJSON* additionalInfoObj = cJSON_CreateObject();
|
||||||
|
cJSON_AddItemToObject(trackMetadataObj, "additional_info", additionalInfoObj);
|
||||||
|
|
||||||
|
// Add ["track_metadata"]["additional_info"]["media_player"]
|
||||||
|
cJSON* mediaPlayerObj = cJSON_CreateString("OSSP");
|
||||||
|
cJSON_AddItemToObject(additionalInfoObj, "media_player", mediaPlayerObj);
|
||||||
|
|
||||||
|
// Add ["track_metadata"]["additional_info"]["submission_client"]
|
||||||
|
cJSON* submissionClientObj = cJSON_CreateString("OSSP ListenBrainz Scrobbler");
|
||||||
|
cJSON_AddItemToObject(additionalInfoObj, "submission_client", submissionClientObj);
|
||||||
|
|
||||||
|
// Add ["track_metadata"]["additional_info"]["submission_client_version"]
|
||||||
|
cJSON* submissionClientVersionObj = cJSON_CreateString("1.0");
|
||||||
|
cJSON_AddItemToObject(additionalInfoObj, "submission_client_version", submissionClientVersionObj);
|
||||||
|
|
||||||
|
// Add ["track_metadata"]["artist_name"]
|
||||||
|
if (songStruct->artist != NULL) {
|
||||||
|
cJSON* artistNameObj = cJSON_CreateString(songStruct->artist);
|
||||||
|
cJSON_AddItemToObject(trackMetadataObj, "artist_name", artistNameObj);
|
||||||
|
} else {
|
||||||
|
printf("[ListenBrainz Scrobbler] Song Artist ([\"artist\"]) is null in songStruct\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ["track_metadata"]["track_name"]
|
||||||
|
if (songStruct->title != NULL) {
|
||||||
|
cJSON* artistTitleObj = cJSON_CreateString(songStruct->title);
|
||||||
|
cJSON_AddItemToObject(trackMetadataObj, "track_name", artistTitleObj);
|
||||||
|
} else {
|
||||||
|
printf("[ListenBrainz Scrobbler] Song Title ([\"title\"]) is null in songStruct\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ["track_metadata"]["release_name"]
|
||||||
|
if (songStruct->album != NULL) {
|
||||||
|
cJSON* artistReleaseObj = cJSON_CreateString(songStruct->artist);
|
||||||
|
cJSON_AddItemToObject(trackMetadataObj, "release_name", artistReleaseObj);
|
||||||
|
} else {
|
||||||
|
printf("[ListenBrainz Scrobbler] Song Album ([\"album\"]) is null in songStruct\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the assembled JSON
|
||||||
|
char* payload = cJSON_PrintUnformatted(rootObj);
|
||||||
|
cJSON_Delete(rootObj);
|
||||||
|
|
||||||
|
// Send payload and receive JSON response
|
||||||
|
opensubsonic_httpClientRequest_t* httpReq;
|
||||||
|
opensubsonic_httpClient_prepareRequest(&httpReq);
|
||||||
|
|
||||||
|
httpReq->requestUrl = strdup(listenBrainzScrobbleURL);
|
||||||
|
httpReq->requestBody = strdup(payload);
|
||||||
|
free(payload);
|
||||||
|
httpReq->isBodyRequired = true;
|
||||||
|
httpReq->scrobbler = SCROBBLER_LISTENBRAINZ;
|
||||||
|
httpReq->method = HTTP_METHOD_POST;
|
||||||
|
opensubsonic_httpClient_request(&httpReq);
|
||||||
|
|
||||||
|
// Check response
|
||||||
|
if (httpReq->responseCode != HTTP_CODE_SUCCESS) {
|
||||||
|
logger_log_error(__func__, "HTTP POST did not return success (%d).", httpReq->responseCode);
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* responseObj = cJSON_Parse(httpReq->responseMsg);
|
||||||
|
opensubsonic_httpClient_cleanup(&httpReq);
|
||||||
|
if (responseObj == NULL) {
|
||||||
|
logger_log_error(__func__, "Error parsing JSON.");
|
||||||
|
// TODO return error
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* status = cJSON_GetObjectItemCaseSensitive(responseObj, "status");
|
||||||
|
if (cJSON_IsString(status) && status->valuestring != NULL) {
|
||||||
|
if (strcmp(status->valuestring, "ok") == 0) {
|
||||||
|
if (finalize) {
|
||||||
|
logger_log_general(__func__, "Final scrobble was successful.");
|
||||||
|
} else {
|
||||||
|
logger_log_general(__func__, "In progress scrobble was successful.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Something went wrong scrobbling to ListenBrainz.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger_log_error(__func__, "Something went wrong scrobbling to ListenBrainz.");
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(responseObj);
|
||||||
|
}
|
||||||
7
src/libopensubsonic/scrobble_listenBrainz.h
Normal file
7
src/libopensubsonic/scrobble_listenBrainz.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef _SCROBBLE_LISTENBRAINZ_H
|
||||||
|
#define _SCROBBLE_LISTENBRAINZ_H
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
void opensubsonic_scrobble_listenBrainz(bool finalize, opensubsonic_getSong_struct* songStruct);
|
||||||
|
|
||||||
|
#endif
|
||||||
106
src/libopensubsonic/utils.c
Normal file
106
src/libopensubsonic/utils.c
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ACRONYMS:
|
||||||
|
* OSS_Psoj -> Opensubsonic_PullStringOutofJson
|
||||||
|
* OSS_Pioj -> Opensubsonic_PullIntOutofJson
|
||||||
|
* OSS_Ploj -> Opensubsonic_PullLongOutofJson
|
||||||
|
* OSS_Pboj -> Opensubsonic_PullBoolOutofJson
|
||||||
|
*/
|
||||||
|
|
||||||
|
void OSS_Psoj(char** dest, cJSON* obj, char* child) {
|
||||||
|
if (obj == NULL) {
|
||||||
|
//void* ret_addr = __builtin_return_address(0);
|
||||||
|
//Dl_info info;
|
||||||
|
//if (dladdr(ret_addr, &info)) {
|
||||||
|
// logger_log_error(info.dli_sname, "Parent object is null.");
|
||||||
|
//}
|
||||||
|
} else {
|
||||||
|
cJSON* childObj = cJSON_GetObjectItem(obj, child);
|
||||||
|
|
||||||
|
if (cJSON_IsString(childObj) && childObj->valuestring != NULL) {
|
||||||
|
*dest = strdup(childObj->valuestring);
|
||||||
|
} else {
|
||||||
|
//void* ret_addr = __builtin_return_address(0);
|
||||||
|
//Dl_info info;
|
||||||
|
//if (dladdr(ret_addr, &info)) {
|
||||||
|
// logger_log_error(info.dli_sname, "Object %s is not a string or string is null.", child);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OSS_Pioj(int* dest, cJSON* obj, char* child) {
|
||||||
|
if (obj == NULL) {
|
||||||
|
//void* ret_addr = __builtin_return_address(0);
|
||||||
|
//Dl_info info;
|
||||||
|
//if (dladdr(ret_addr, &info)) {
|
||||||
|
// logger_log_error(info.dli_sname, "Parent object is null.");
|
||||||
|
//}
|
||||||
|
} else {
|
||||||
|
cJSON* childObj = cJSON_GetObjectItem(obj, child);
|
||||||
|
|
||||||
|
if (cJSON_IsNumber(childObj)) {
|
||||||
|
*dest = childObj->valueint;
|
||||||
|
} else {
|
||||||
|
//void* ret_addr = __builtin_return_address(0);
|
||||||
|
//Dl_info info;
|
||||||
|
//if (dladdr(ret_addr, &info)) {
|
||||||
|
// logger_log_error(info.dli_sname, "Object %s is not an int.", child);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OSS_Ploj(long* dest, cJSON* obj, char* child) {
|
||||||
|
if (obj == NULL) {
|
||||||
|
//void* ret_addr = __builtin_return_address(0);
|
||||||
|
//Dl_info info;
|
||||||
|
//if (dladdr(ret_addr, &info)) {
|
||||||
|
// logger_log_error(info.dli_sname, "Parent object is null.");
|
||||||
|
//}
|
||||||
|
} else {
|
||||||
|
cJSON* childObj = cJSON_GetObjectItem(obj, child);
|
||||||
|
|
||||||
|
if (cJSON_IsNumber(childObj)) {
|
||||||
|
*dest = childObj->valueint;
|
||||||
|
} else {
|
||||||
|
//void* ret_addr = __builtin_return_address(0);
|
||||||
|
//Dl_info info;
|
||||||
|
//if (dladdr(ret_addr, &info)) {
|
||||||
|
// logger_log_error(info.dli_sname, "Object %s is not a long.", child);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OSS_Pboj(bool* dest, cJSON* obj, char* child) {
|
||||||
|
if (obj == NULL) {
|
||||||
|
//void* ret_addr = __builtin_return_address(0);
|
||||||
|
//Dl_info info;
|
||||||
|
//if (dladdr(ret_addr, &info)) {
|
||||||
|
// logger_log_error(info.dli_sname, "Parent object is null.");
|
||||||
|
//}
|
||||||
|
} else {
|
||||||
|
cJSON* childObj = cJSON_GetObjectItem(obj, child);
|
||||||
|
|
||||||
|
if (cJSON_IsBool(childObj)) {
|
||||||
|
if (cJSON_IsTrue(childObj)) {
|
||||||
|
*dest = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//void* ret_addr = __builtin_return_address(0);
|
||||||
|
//Dl_info info;
|
||||||
|
//if (dladdr(ret_addr, &info)) {
|
||||||
|
// logger_log_error(info.dli_sname, "Object is not a bool.");
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
11
src/libopensubsonic/utils.h
Normal file
11
src/libopensubsonic/utils.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef _UTILS_H
|
||||||
|
#define _UTILS_H
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "../external/cJSON.h"
|
||||||
|
|
||||||
|
void OSS_Psoj(char** dest, cJSON* obj, char* child);
|
||||||
|
void OSS_Pioj(int* dest, cJSON* obj, char* child);
|
||||||
|
void OSS_Ploj(long* dest, cJSON* obj, char* child);
|
||||||
|
void OSS_Pboj(bool* dest, cJSON* obj, char* child);
|
||||||
|
|
||||||
|
#endif
|
||||||
32
src/main.c
Normal file
32
src/main.c
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "libopensubsonic/logger.h"
|
||||||
|
#include "libopensubsonic/crypto.h"
|
||||||
|
#include "libopensubsonic/httpclient.h"
|
||||||
|
#include "libopensubsonic/endpoint_ping.h"
|
||||||
|
#include "configHandler.h"
|
||||||
|
|
||||||
|
configHandler_config_t* configObj = NULL;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
rc = configHandler_Read(&configObj);
|
||||||
|
if (rc != 0) {
|
||||||
|
printf("Could not read config file!\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
opensubsonic_crypto_generateLogin();
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_PING;
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
printf("URL: %s\n", url->formedUrl);
|
||||||
|
opensubsonic_ping_struct* OSS_ping_struct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&OSS_ping_struct);
|
||||||
|
printf("PING OK: %s\n", OSS_ping_struct->status);
|
||||||
|
|
||||||
|
configHandler_Free(&configObj);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
259
src/openSubSonic.c
Normal file
259
src/openSubSonic.c
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
#include "OSSP_Bridge.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
#include "libopensubsonic/crypto.h"
|
||||||
|
#include "libopensubsonic/httpclient.h"
|
||||||
|
#include "libopensubsonic/endpoint_ping.h"
|
||||||
|
#include "libopensubsonic/endpoint_getStarred.h"
|
||||||
|
#include "libopensubsonic/endpoint_getAlbum.h"
|
||||||
|
|
||||||
|
#include "libopensubsonic/endpoint_getPlaylists.h"
|
||||||
|
#include "libopensubsonic/endpoint_getPlaylist.h"
|
||||||
|
#include "libopensubsonic/endpoint_getSong.h"
|
||||||
|
#include "libopensubsonic/endpoint_getLyricsBySongId.h"
|
||||||
|
#include "libopensubsonic/endpoint_getAlbumList.h"
|
||||||
|
#include "libopensubsonic/endpoint_scrobble.h"
|
||||||
|
#include "configHandler.h"
|
||||||
|
|
||||||
|
#include "libopensubsonic/scrobble_lastFm.h"
|
||||||
|
#include "dscrdrpc.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct memory {
|
||||||
|
char *data;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t write_to_memory(void *ptr, size_t size, size_t nmemb, void *userdata) {
|
||||||
|
struct memory *mem = (struct memory *)userdata;
|
||||||
|
size_t total_size = size * nmemb;
|
||||||
|
|
||||||
|
mem->data = realloc(mem->data, mem->size + total_size + 1);
|
||||||
|
if (!mem->data) return 0; // Fail on OOM
|
||||||
|
|
||||||
|
memcpy(&(mem->data[mem->size]), ptr, total_size);
|
||||||
|
mem->size += total_size;
|
||||||
|
mem->data[mem->size] = '\0'; // Null-terminate
|
||||||
|
|
||||||
|
return total_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int rc = 0;
|
||||||
|
configHandler_config_t* configObj = NULL;
|
||||||
|
|
||||||
|
opensubsonic_ping_struct* LOSS_PerformLogin(C_LoginStruct loginStruct) {
|
||||||
|
// Initialize configuration
|
||||||
|
configHandler_Read(&configObj);
|
||||||
|
|
||||||
|
// Generate the login token and salt
|
||||||
|
opensubsonic_crypto_generateLogin();
|
||||||
|
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_PING;
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_ping_struct* OSS_ping_struct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&OSS_ping_struct);
|
||||||
|
|
||||||
|
// Print Response
|
||||||
|
//printf("Result: %s\n", OSS_ping_struct.status);
|
||||||
|
|
||||||
|
//opensubsonic_ping_struct_free(&OSS_ping_struct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
|
||||||
|
//configHandler_Free(&config);
|
||||||
|
//opensubsonic_authenticate_lastFm();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
opensubsonic_httpClient_URL_t* urlc = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&urlc);
|
||||||
|
urlc->endpoint = OPENSUBSONIC_ENDPOINT_GETLYRICSBYSONGID;
|
||||||
|
urlc->id = strdup("pvu323svJrim683Xf8fBV8");
|
||||||
|
printf("URLC Endponit: %d\n", urlc->endpoint);
|
||||||
|
opensubsonic_httpClient_formUrl(&urlc);
|
||||||
|
printf("URL: %s\n", urlc->formedUrl);
|
||||||
|
|
||||||
|
opensubsonic_getLyricsBySongId_struct* plStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&urlc, (void**)&plStruct);
|
||||||
|
|
||||||
|
printf("2nd: %s\n", plStruct->lyrics[1].data);
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return OSS_ping_struct; // TODO rename
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LOSS_FreeLoginStruct() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
opensubsonic_getStarred_struct* LOSS_getStarred(void) {
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETSTARRED;
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
printf("STARRED URL: %s\n", url->formedUrl);
|
||||||
|
|
||||||
|
opensubsonic_getStarred_struct* getStarredStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getStarredStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
|
||||||
|
return getStarredStruct;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LOSS_delete_getStarred(opensubsonic_getStarred_struct* starredStruct) {
|
||||||
|
opensubsonic_getStarred_struct_free(&starredStruct);
|
||||||
|
}
|
||||||
|
|
||||||
|
opensubsonic_getSong_struct* LOSS_getSong(char* id) {
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETSONG;
|
||||||
|
url->id = strdup(id);
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getSong_struct* getSongStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getSongStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
|
||||||
|
return getSongStruct;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LOSS_delete_getSong(opensubsonic_getSong_struct* songStruct) {
|
||||||
|
opensubsonic_getSong_struct_free(&songStruct);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
opensubsonic_getAlbum_struct* LOSS_getAlbum(char* id) {
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETALBUM;
|
||||||
|
url->id = strdup(id);
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getAlbum_struct* alStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&alStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
|
||||||
|
return alStruct;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LOSS_deleteAlbum(opensubsonic_getAlbum_struct* albumStruct) {
|
||||||
|
//opensubsonic_getAlbum_struct_free(albumStruct); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// no error checking right now because fuck that TODO
|
||||||
|
opensubsonic_getAlbumList_struct* LOSS_getAlbumList(int type, int amount) {
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETALBUMLIST;
|
||||||
|
url->type = type;
|
||||||
|
url->amount = amount;
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
printf("%s\n", url->formedUrl);
|
||||||
|
|
||||||
|
opensubsonic_getAlbumList_struct* alStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&alStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
|
||||||
|
return alStruct;
|
||||||
|
}
|
||||||
|
|
||||||
|
opensubsonic_getPlaylists_struct* LOSS_getPlaylists(void) {
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETPLAYLISTS;
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getPlaylists_struct* alStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&alStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
|
||||||
|
return alStruct;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
char* LOSS_getCoverArt(char* id) {
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETCOVERART;
|
||||||
|
url->id = strdup(id);
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
return url->formedUrl;
|
||||||
|
// TODO need to free URL struct
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char* LOSS_Stream(char* id) {
|
||||||
|
// Perform scrobble to navidrome (Used purely for server side tracking)
|
||||||
|
opensubsonic_httpClient_URL_t* scrobble_url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&scrobble_url);
|
||||||
|
scrobble_url->endpoint = OPENSUBSONIC_ENDPOINT_SCROBBLE;
|
||||||
|
scrobble_url->id = strdup(id);
|
||||||
|
scrobble_url->submit = true;
|
||||||
|
opensubsonic_httpClient_formUrl(&scrobble_url);
|
||||||
|
opensubsonic_scrobble_struct* scrobbleStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&scrobble_url, (void**)&scrobbleStruct);
|
||||||
|
opensubsonic_scrobble_struct_free(&scrobbleStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&scrobble_url);
|
||||||
|
|
||||||
|
// Actually make the stream url
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_STREAM;
|
||||||
|
url->id = strdup(id);
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
return url->formedUrl;
|
||||||
|
// TODO need to free URL struct
|
||||||
|
}
|
||||||
|
|
||||||
|
void discordrpc_update(int type, opensubsonic_getSong_struct* songStruct) {
|
||||||
|
printf("Updating Discord RPC\n");
|
||||||
|
|
||||||
|
dscrdrpc_data* dscrdrpc = NULL;
|
||||||
|
dscrdrpc_struct_init(&dscrdrpc);
|
||||||
|
|
||||||
|
if (type == 1) {
|
||||||
|
dscrdrpc->requestType = DSCRDRPC_REQTYPE_PLAYING;
|
||||||
|
} else if (type == 2) {
|
||||||
|
dscrdrpc->requestType = DSCRDRPC_REQTYPE_KEEPALIVE;
|
||||||
|
} else if (type == 3) {
|
||||||
|
dscrdrpc->requestType = DSCRDRPC_REQTYPE_PAUSED;
|
||||||
|
}
|
||||||
|
dscrdrpc->songTitle = strdup(songStruct->title);
|
||||||
|
dscrdrpc->songArtist = strdup(songStruct->artist);
|
||||||
|
dscrdrpc->coverArtUrl = LOSS_getCoverArt(songStruct->coverArt);
|
||||||
|
dscrdrpc->deviceInfo = strdup("iPhone (iOS 16.6)");
|
||||||
|
dscrdrpc->songLength = songStruct->duration;
|
||||||
|
|
||||||
|
dscrdrpc_encrypt(&dscrdrpc);
|
||||||
|
printf("Checksum: %s\n", dscrdrpc->checksum);
|
||||||
|
|
||||||
|
dscrdrpc_struct_deinit(&dscrdrpc);
|
||||||
|
}
|
||||||
200
src/runTests.c
Normal file
200
src/runTests.c
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// Other imports
|
||||||
|
#include "configHandler.h"
|
||||||
|
#include "libopensubsonic/logger.h"
|
||||||
|
#include "libopensubsonic/httpclient.h"
|
||||||
|
#include "libopensubsonic/crypto.h"
|
||||||
|
|
||||||
|
// libopensubsonic imports
|
||||||
|
#include "libopensubsonic/endpoint_ping.h"
|
||||||
|
#include "libopensubsonic/endpoint_getStarred.h"
|
||||||
|
#include "libopensubsonic/endpoint_getPlaylists.h"
|
||||||
|
#include "libopensubsonic/endpoint_getPlaylist.h"
|
||||||
|
#include "libopensubsonic/endpoint_getSong.h"
|
||||||
|
#include "libopensubsonic/endpoint_getArtists.h"
|
||||||
|
#include "libopensubsonic/endpoint_getArtist.h"
|
||||||
|
#include "libopensubsonic/endpoint_getAlbumList.h"
|
||||||
|
#include "libopensubsonic/endpoint_getAlbum.h"
|
||||||
|
#include "libopensubsonic/endpoint_getLyricsBySongId.h"
|
||||||
|
|
||||||
|
configHandler_config_t* test_configObj = NULL;
|
||||||
|
|
||||||
|
void test_libopensubsonic_endpoint_ping(void) {
|
||||||
|
logger_log_general(__func__, "Testing ping endpoint.");
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_PING;
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_ping_struct* OSS_ping_struct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&OSS_ping_struct);
|
||||||
|
|
||||||
|
opensubsonic_ping_struct_free(&OSS_ping_struct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_libopensubsonic_endpoint_getStarred(void) {
|
||||||
|
logger_log_general(__func__, "Testing getStarred endpoint.");
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETSTARRED;
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getStarred_struct* getStarredStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getStarredStruct);
|
||||||
|
|
||||||
|
opensubsonic_getStarred_struct_free(&getStarredStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_libopensubsonic_endpoint_getPlaylists(void) {
|
||||||
|
logger_log_general(__func__, "Testing getPlaylists endpoint.");
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETPLAYLISTS;
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getPlaylists_struct* getPlaylistsStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getPlaylistsStruct);
|
||||||
|
|
||||||
|
opensubsonic_getPlaylists_struct_free(&getPlaylistsStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_libopensubsonic_endpoint_getPlaylist(void) {
|
||||||
|
logger_log_general(__func__, "Testing getPlaylist endpoint.");
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETPLAYLIST;
|
||||||
|
url->id = strdup("dkBA3oVi5zpChEVIDVJn4i");
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getPlaylist_struct* getPlaylistStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getPlaylistStruct);
|
||||||
|
|
||||||
|
opensubsonic_getPlaylist_struct_free(&getPlaylistStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_libopensubsonic_endpoint_getSong(void) {
|
||||||
|
logger_log_general(__func__, "Testing getSong endpoint.");
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETSONG;
|
||||||
|
url->id = strdup("c24SgyyHpe86IsAwSV73rx");
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getSong_struct* getSongStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getSongStruct);
|
||||||
|
|
||||||
|
opensubsonic_getSong_struct_free(&getSongStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_libopensubsonic_endpoint_getArtists(void) {
|
||||||
|
logger_log_general(__func__, "Testing getArtists endpoint.");
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETARTISTS;
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getArtists_struct* getArtistsStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getArtistsStruct);
|
||||||
|
|
||||||
|
opensubsonic_getArtists_struct_free(&getArtistsStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_libopensubsonic_endpoint_getArtist(void) {
|
||||||
|
logger_log_general(__func__, "Testing getArtist endpoint.");
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETARTIST;
|
||||||
|
url->id = strdup("3mZKW6zlodlLW4x4QLDhPr");
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getArtist_struct* getArtistStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getArtistStruct);
|
||||||
|
|
||||||
|
opensubsonic_getArtist_struct_free(&getArtistStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_libopensubsonic_endpoint_getAlbumList(void) {
|
||||||
|
logger_log_general(__func__, "Testing getAlbumList endpoint.");
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETALBUMLIST;
|
||||||
|
url->type = OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RECENT;
|
||||||
|
url->amount = 5;
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getAlbumList_struct* getAlbumListStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getAlbumListStruct);
|
||||||
|
|
||||||
|
opensubsonic_getAlbumList_struct_free(&getAlbumListStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_libopensubsonic_endpoint_getAlbum(void) {
|
||||||
|
logger_log_general(__func__, "Testing getAlbum endpoint.");
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETALBUM;
|
||||||
|
url->id = strdup("5OjqcCVp8LbDfamfubZxFN");
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getAlbum_struct* getAlbumStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getAlbumStruct);
|
||||||
|
|
||||||
|
opensubsonic_getAlbum_struct_free(&getAlbumStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_libopensubsonic_endpoint_getLyricsBySongId(void) {
|
||||||
|
logger_log_general(__func__, "Testing getLyricsBySongId endpoint.");
|
||||||
|
|
||||||
|
opensubsonic_httpClient_URL_t* url = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t));
|
||||||
|
opensubsonic_httpClient_URL_prepare(&url);
|
||||||
|
url->endpoint = OPENSUBSONIC_ENDPOINT_GETLYRICSBYSONGID;
|
||||||
|
url->id = strdup("pvu323svJrim683Xf8fBV8");
|
||||||
|
opensubsonic_httpClient_formUrl(&url);
|
||||||
|
|
||||||
|
opensubsonic_getLyricsBySongId_struct* getLyricsBySongIdStruct;
|
||||||
|
opensubsonic_httpClient_fetchResponse(&url, (void**)&getLyricsBySongIdStruct);
|
||||||
|
|
||||||
|
opensubsonic_getLyricsBySongId_struct_free(&getLyricsBySongIdStruct);
|
||||||
|
opensubsonic_httpClient_URL_cleanup(&url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void runTests(void) {
|
||||||
|
// Read config file
|
||||||
|
configHandler_Read(&test_configObj);
|
||||||
|
|
||||||
|
// Run tests
|
||||||
|
test_libopensubsonic_endpoint_ping();
|
||||||
|
test_libopensubsonic_endpoint_getStarred();
|
||||||
|
test_libopensubsonic_endpoint_getPlaylists();
|
||||||
|
test_libopensubsonic_endpoint_getPlaylist();
|
||||||
|
test_libopensubsonic_endpoint_getSong();
|
||||||
|
test_libopensubsonic_endpoint_getArtists();
|
||||||
|
test_libopensubsonic_endpoint_getAlbumList();
|
||||||
|
test_libopensubsonic_endpoint_getAlbum();
|
||||||
|
test_libopensubsonic_endpoint_getLyricsBySongId();
|
||||||
|
|
||||||
|
// Free config file
|
||||||
|
configHandler_Free(&test_configObj);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user