Adding base source

This commit is contained in:
2025-09-20 18:01:59 +10:00
parent 44f27e90f6
commit 61f517d159
51 changed files with 10086 additions and 0 deletions

View 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();
}

View 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

View 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); }
}

View 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

View 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); }
}

View 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

View 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); }
}

View 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

View 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); }
}

View 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

View 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); }
}

View 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

View 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); }
}

View 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

View 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); }
}

View 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

View 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); }
}

View 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

View 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); }
}

View 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

View 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); }
}

View 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

View 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); }
}

View 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

View 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);
}

View 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

View 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);
}

View 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

View 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(&currentTime_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&timestamp=%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&timestamp=%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);
}

View 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

View 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);
}

View 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
View 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.");
//}
}
}
}

View 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