mirror of
https://github.com/Goldenkrew3000/OSSP_OpenSource.git
synced 2025-12-20 00:34:44 +10:00
Adding base source
This commit is contained in:
100
src/libopensubsonic/crypto.c
Normal file
100
src/libopensubsonic/crypto.c
Normal file
@@ -0,0 +1,100 @@
|
||||
#include <stdio.h>
|
||||
#include "crypto.h"
|
||||
#include "../external/md5.h"
|
||||
#include "../configHandler.h"
|
||||
#include "logger.h"
|
||||
|
||||
#if __NetBSD__ // Functions for NetBSD to use KERN_ARND
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
static int rc = 0;
|
||||
extern configHandler_config_t* configObj;
|
||||
|
||||
// Use arc4random() to generate cryptographically secure bytes. Should work on all BSD-style systems
|
||||
void crypto_secure_arc4random_generate(unsigned char* bytes, size_t length) {
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
bytes[i] = arc4random() & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the arandom sysctl on NetBSD to generate cryptographically secure bytes.
|
||||
#if __NetBSD__
|
||||
void crypto_secure_netbsd_arandom_generate(unsigned char* bytes, size_t length) {
|
||||
// Setup the sysctl MIB for kern.arandom
|
||||
int mib[2];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_ARND;
|
||||
|
||||
// Read random bytes
|
||||
if (sysctl(mib, 2, bytes, &length, NULL, 0) == -1) {
|
||||
logger_log_error(__func__, "sysctl() error.");
|
||||
exit(EXIT_FAILURE); // TODO handle error better
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Generate an Opensubsonic Login Salt
|
||||
void crypto_secure_generate_salt(void) {
|
||||
uint8_t salt_bytes[8];
|
||||
|
||||
// Generate cryptographically secure salt bytes using OS-native functions
|
||||
#if __NetBSD__
|
||||
crypto_secure_netbsd_arandom_generate(salt_bytes, 8);
|
||||
#else
|
||||
crypto_secure_arc4random_generate(salt_bytes, 8);
|
||||
#endif
|
||||
|
||||
// Convert to a string hex representation
|
||||
char* loginSalt = NULL;
|
||||
rc = asprintf(&loginSalt, "%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
salt_bytes[0], salt_bytes[1], salt_bytes[2], salt_bytes[3],
|
||||
salt_bytes[4], salt_bytes[5], salt_bytes[6], salt_bytes[7]);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed.");
|
||||
return; // TODO return proper error
|
||||
}
|
||||
|
||||
// TODO Fix this hack - Copy login salt to config
|
||||
configObj->internal_opensubsonic_loginSalt = strdup(loginSalt);
|
||||
free(loginSalt);
|
||||
}
|
||||
|
||||
// Generate an MD5 checksum in string hex representation of the account password and salt
|
||||
void crypto_secure_generate_token(void) {
|
||||
uint8_t md5_bytes[16];
|
||||
char* token_plaintext = NULL;
|
||||
|
||||
// Concatenate account password and salt into single string
|
||||
rc = asprintf(&token_plaintext, "%s%s", configObj->opensubsonic_password, configObj->internal_opensubsonic_loginSalt);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed.");
|
||||
return; // TODO return error
|
||||
}
|
||||
|
||||
// Generate an MD5 checksum of the plaintext token
|
||||
md5String(token_plaintext, md5_bytes);
|
||||
free(token_plaintext);
|
||||
|
||||
// Convert the MD5 checksum bytes into string hex representation
|
||||
char* loginToken = NULL;
|
||||
rc = asprintf(&loginToken, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
md5_bytes[0], md5_bytes[1], md5_bytes[2], md5_bytes[3],
|
||||
md5_bytes[4], md5_bytes[5], md5_bytes[6], md5_bytes[7],
|
||||
md5_bytes[8], md5_bytes[9], md5_bytes[10], md5_bytes[11],
|
||||
md5_bytes[12], md5_bytes[13], md5_bytes[14], md5_bytes[15]);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed.");
|
||||
return; // TODO return error
|
||||
}
|
||||
|
||||
// TODO Fix this hack - Copy login token to config
|
||||
configObj->internal_opensubsonic_loginToken = strdup(loginToken);
|
||||
free(loginToken);
|
||||
}
|
||||
|
||||
void opensubsonic_crypto_generateLogin(void) {
|
||||
crypto_secure_generate_salt();
|
||||
crypto_secure_generate_token();
|
||||
}
|
||||
14
src/libopensubsonic/crypto.h
Normal file
14
src/libopensubsonic/crypto.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef _CRYPTO_H
|
||||
#define _CRYPTO_H
|
||||
|
||||
// OS-native cryptographic generation functions
|
||||
void crypto_secure_arc4random_generate(unsigned char* bytes, size_t length);
|
||||
#if __NetBSD__
|
||||
void crypto_secure_netbsd_arandom_generate(unsigned char* bytes, size_t length);
|
||||
#endif
|
||||
|
||||
void crypto_secure_generate_salt(void);
|
||||
void crypto_secure_generate_token(void);
|
||||
void opensubsonic_crypto_generateLogin(void);
|
||||
|
||||
#endif
|
||||
174
src/libopensubsonic/endpoint_getAlbum.c
Normal file
174
src/libopensubsonic/endpoint_getAlbum.c
Normal file
@@ -0,0 +1,174 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_getAlbum.h"
|
||||
|
||||
// Returns 1 if failure occured, else 0
|
||||
int opensubsonic_getAlbum_parse(char* data, opensubsonic_getAlbum_struct** getAlbumStruct) {
|
||||
// Allocate and initialize
|
||||
(*getAlbumStruct) = (opensubsonic_getAlbum_struct*)malloc(sizeof(opensubsonic_getAlbum_struct));
|
||||
(*getAlbumStruct)->status = NULL;
|
||||
(*getAlbumStruct)->errorCode = 0;
|
||||
(*getAlbumStruct)->errorMessage = NULL;
|
||||
(*getAlbumStruct)->id = NULL;
|
||||
(*getAlbumStruct)->parent = NULL;
|
||||
(*getAlbumStruct)->album = NULL;
|
||||
(*getAlbumStruct)->title = NULL;
|
||||
(*getAlbumStruct)->name = NULL;
|
||||
(*getAlbumStruct)->coverArt = NULL;
|
||||
(*getAlbumStruct)->created = NULL;
|
||||
(*getAlbumStruct)->duration = 0;
|
||||
(*getAlbumStruct)->playCount = 0;
|
||||
(*getAlbumStruct)->artistId = NULL;
|
||||
(*getAlbumStruct)->artist = NULL;
|
||||
(*getAlbumStruct)->year = 0;
|
||||
(*getAlbumStruct)->genre = NULL;
|
||||
(*getAlbumStruct)->songCount = 0;
|
||||
(*getAlbumStruct)->songs = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check for error
|
||||
OSS_Psoj(&(*getAlbumStruct)->status, subsonic_root, "status");
|
||||
if (strstr((*getAlbumStruct)->status, "ok") == NULL) {
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Pioj(&(*getAlbumStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*getAlbumStruct)->errorMessage, subsonic_error, "message");
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getAlbumStruct)->errorCode, (*getAlbumStruct)->errorMessage);
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Process contents
|
||||
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "album");
|
||||
if (album_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - album does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getAlbumStruct)->id, album_root, "id");
|
||||
OSS_Psoj(&(*getAlbumStruct)->parent, album_root, "parent");
|
||||
OSS_Psoj(&(*getAlbumStruct)->album, album_root, "album");
|
||||
OSS_Psoj(&(*getAlbumStruct)->title, album_root, "title");
|
||||
OSS_Psoj(&(*getAlbumStruct)->name, album_root, "name");
|
||||
OSS_Psoj(&(*getAlbumStruct)->coverArt, album_root, "coverArt");
|
||||
OSS_Psoj(&(*getAlbumStruct)->created, album_root, "created");
|
||||
OSS_Ploj(&(*getAlbumStruct)->duration, album_root, "duration");
|
||||
OSS_Pioj(&(*getAlbumStruct)->playCount, album_root, "playCount");
|
||||
OSS_Psoj(&(*getAlbumStruct)->artistId, album_root, "artistId");
|
||||
OSS_Psoj(&(*getAlbumStruct)->artist, album_root, "artist");
|
||||
OSS_Pioj(&(*getAlbumStruct)->year, album_root, "year");
|
||||
OSS_Psoj(&(*getAlbumStruct)->genre, album_root, "genre");
|
||||
|
||||
cJSON* song_root = cJSON_GetObjectItemCaseSensitive(album_root, "song");
|
||||
if (song_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - song does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
(*getAlbumStruct)->songCount = cJSON_GetArraySize(song_root);
|
||||
|
||||
// Allocate and initialize
|
||||
(*getAlbumStruct)->songs = (opensubsonic_getAlbum_song_struct*)malloc((*getAlbumStruct)->songCount * sizeof(opensubsonic_getAlbum_song_struct));
|
||||
|
||||
for (int i = 0; i < (*getAlbumStruct)->songCount; i++) {
|
||||
(*getAlbumStruct)->songs[i].id = NULL;
|
||||
(*getAlbumStruct)->songs[i].parent = NULL;
|
||||
(*getAlbumStruct)->songs[i].title = NULL;
|
||||
(*getAlbumStruct)->songs[i].albumId = NULL;
|
||||
(*getAlbumStruct)->songs[i].album = NULL;
|
||||
(*getAlbumStruct)->songs[i].artistId = NULL;
|
||||
(*getAlbumStruct)->songs[i].artist = NULL;
|
||||
(*getAlbumStruct)->songs[i].coverArt = NULL;
|
||||
(*getAlbumStruct)->songs[i].duration = 0;
|
||||
(*getAlbumStruct)->songs[i].bitRate = 0;
|
||||
(*getAlbumStruct)->songs[i].bitDepth = 0;
|
||||
(*getAlbumStruct)->songs[i].samplingRate = 0;
|
||||
(*getAlbumStruct)->songs[i].channelCount = 0;
|
||||
(*getAlbumStruct)->songs[i].track = 0;
|
||||
(*getAlbumStruct)->songs[i].year = 0;
|
||||
(*getAlbumStruct)->songs[i].genre = NULL;
|
||||
(*getAlbumStruct)->songs[i].size = 0;
|
||||
(*getAlbumStruct)->songs[i].discNumber = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (*getAlbumStruct)->songCount; i++) {
|
||||
cJSON* array_song_root = cJSON_GetArrayItem(song_root, i);
|
||||
if (array_song_root != NULL) {
|
||||
OSS_Psoj(&(*getAlbumStruct)->songs[i].id, array_song_root, "id");
|
||||
OSS_Psoj(&(*getAlbumStruct)->songs[i].parent, array_song_root, "parent");
|
||||
OSS_Psoj(&(*getAlbumStruct)->songs[i].title, array_song_root, "title");
|
||||
OSS_Psoj(&(*getAlbumStruct)->songs[i].albumId, array_song_root, "albumId");
|
||||
OSS_Psoj(&(*getAlbumStruct)->songs[i].album, array_song_root, "album");
|
||||
OSS_Psoj(&(*getAlbumStruct)->songs[i].artistId, array_song_root, "artistId");
|
||||
OSS_Psoj(&(*getAlbumStruct)->songs[i].artist, array_song_root, "artist");
|
||||
OSS_Psoj(&(*getAlbumStruct)->songs[i].coverArt, array_song_root, "coverArt");
|
||||
OSS_Ploj(&(*getAlbumStruct)->songs[i].duration, array_song_root, "duration");
|
||||
OSS_Pioj(&(*getAlbumStruct)->songs[i].bitRate, array_song_root, "bitRate");
|
||||
OSS_Pioj(&(*getAlbumStruct)->songs[i].bitDepth, array_song_root, "bitDepth");
|
||||
OSS_Ploj(&(*getAlbumStruct)->songs[i].samplingRate, array_song_root, "samplingRate");
|
||||
OSS_Pioj(&(*getAlbumStruct)->songs[i].channelCount, array_song_root, "channelCount");
|
||||
OSS_Pioj(&(*getAlbumStruct)->songs[i].track, array_song_root, "track");
|
||||
OSS_Pioj(&(*getAlbumStruct)->songs[i].year, array_song_root, "year");
|
||||
OSS_Psoj(&(*getAlbumStruct)->songs[i].genre, array_song_root, "genre");
|
||||
OSS_Ploj(&(*getAlbumStruct)->songs[i].size, array_song_root, "size");
|
||||
OSS_Pioj(&(*getAlbumStruct)->songs[i].discNumber, array_song_root, "discNumber");
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void opensubsonic_getAlbum_struct_free(opensubsonic_getAlbum_struct** getAlbumStruct) {
|
||||
logger_log_general(__func__, "Freeing /getAlbum endpoint heap objects.");
|
||||
if ((*getAlbumStruct)->status != NULL) { free((*getAlbumStruct)->status); }
|
||||
if ((*getAlbumStruct)->errorMessage != NULL) { free((*getAlbumStruct)->errorMessage); }
|
||||
if ((*getAlbumStruct)->id != NULL) { free((*getAlbumStruct)->id); }
|
||||
if ((*getAlbumStruct)->parent != NULL) { free((*getAlbumStruct)->parent); }
|
||||
if ((*getAlbumStruct)->album != NULL) { free((*getAlbumStruct)->album); }
|
||||
if ((*getAlbumStruct)->title != NULL) { free((*getAlbumStruct)->title); }
|
||||
if ((*getAlbumStruct)->name != NULL) { free((*getAlbumStruct)->name); }
|
||||
if ((*getAlbumStruct)->coverArt != NULL) { free((*getAlbumStruct)->coverArt); }
|
||||
if ((*getAlbumStruct)->created != NULL) { free((*getAlbumStruct)->created); }
|
||||
if ((*getAlbumStruct)->artistId != NULL) { free((*getAlbumStruct)->artistId); }
|
||||
if ((*getAlbumStruct)->artist != NULL) { free((*getAlbumStruct)->artist); }
|
||||
if ((*getAlbumStruct)->genre != NULL) { free((*getAlbumStruct)->genre); }
|
||||
for (int i = 0; i < (*getAlbumStruct)->songCount; i++) {
|
||||
if ((*getAlbumStruct)->songs[i].id != NULL) { free((*getAlbumStruct)->songs[i].id); }
|
||||
if ((*getAlbumStruct)->songs[i].parent != NULL) { free((*getAlbumStruct)->songs[i].parent); }
|
||||
if ((*getAlbumStruct)->songs[i].title != NULL) { free((*getAlbumStruct)->songs[i].title); }
|
||||
if ((*getAlbumStruct)->songs[i].albumId != NULL) { free((*getAlbumStruct)->songs[i].albumId); }
|
||||
if ((*getAlbumStruct)->songs[i].album != NULL) { free((*getAlbumStruct)->songs[i].album); }
|
||||
if ((*getAlbumStruct)->songs[i].artistId != NULL) { free((*getAlbumStruct)->songs[i].artistId); }
|
||||
if ((*getAlbumStruct)->songs[i].artist != NULL) { free((*getAlbumStruct)->songs[i].artist); }
|
||||
if ((*getAlbumStruct)->songs[i].coverArt != NULL) { free((*getAlbumStruct)->songs[i].coverArt); }
|
||||
if ((*getAlbumStruct)->songs[i].genre != NULL) { free((*getAlbumStruct)->songs[i].genre); }
|
||||
}
|
||||
if ((*getAlbumStruct)->songs != NULL) { free((*getAlbumStruct)->songs); }
|
||||
if (*getAlbumStruct != NULL) { free(*getAlbumStruct); }
|
||||
}
|
||||
52
src/libopensubsonic/endpoint_getAlbum.h
Normal file
52
src/libopensubsonic/endpoint_getAlbum.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef _ENDPOINT_GETALBUM_H
|
||||
#define _ENDPOINT_GETALBUM_H
|
||||
|
||||
typedef struct {
|
||||
char* id;
|
||||
char* parent;
|
||||
char* title;
|
||||
// 'isDir', 'isVideo', 'type' excluded
|
||||
char* albumId;
|
||||
char* album;
|
||||
char* artistId;
|
||||
char* artist;
|
||||
char* coverArt;
|
||||
long duration;
|
||||
int bitRate;
|
||||
int bitDepth;
|
||||
long samplingRate;
|
||||
int channelCount;
|
||||
int track; // Use songCount index instead
|
||||
int year;
|
||||
char* genre;
|
||||
long size;
|
||||
int discNumber;
|
||||
// 'suffix', 'contentType', 'path' excluded
|
||||
} opensubsonic_getAlbum_song_struct;
|
||||
|
||||
typedef struct {
|
||||
char* status;
|
||||
int errorCode;
|
||||
char* errorMessage;
|
||||
char* id;
|
||||
char* parent;
|
||||
char* album;
|
||||
char* title;
|
||||
char* name;
|
||||
// 'isDir' excluded
|
||||
char* coverArt;
|
||||
char* created;
|
||||
long duration;
|
||||
int playCount;
|
||||
char* artistId;
|
||||
char* artist;
|
||||
int year;
|
||||
char* genre;
|
||||
int songCount; // Counted, not retrieved from JSON
|
||||
opensubsonic_getAlbum_song_struct* songs;
|
||||
} opensubsonic_getAlbum_struct;
|
||||
|
||||
int opensubsonic_getAlbum_parse(char* data, opensubsonic_getAlbum_struct** getAlbumStruct);
|
||||
void opensubsonic_getAlbum_struct_free(opensubsonic_getAlbum_struct** getAlbumStruct);
|
||||
|
||||
#endif
|
||||
134
src/libopensubsonic/endpoint_getAlbumList.c
Normal file
134
src/libopensubsonic/endpoint_getAlbumList.c
Normal file
@@ -0,0 +1,134 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_getAlbumList.h"
|
||||
|
||||
// Returns 1 if failure occured, else 0
|
||||
int opensubsonic_getAlbumList_parse(char* data, opensubsonic_getAlbumList_struct** getAlbumListStruct) {
|
||||
// Allocate struct and initialize variables
|
||||
(*getAlbumListStruct) = (opensubsonic_getAlbumList_struct*)malloc(sizeof(opensubsonic_getAlbumList_struct));
|
||||
(*getAlbumListStruct)->status = NULL;
|
||||
(*getAlbumListStruct)->errorCode = 0;
|
||||
(*getAlbumListStruct)->errorMessage = NULL;
|
||||
(*getAlbumListStruct)->albumCount = 0;
|
||||
(*getAlbumListStruct)->albums = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getAlbumListStruct)->status, subsonic_root, "status");
|
||||
|
||||
// Check if API has returned an error
|
||||
if (strstr((*getAlbumListStruct)->status, "ok") == NULL) {
|
||||
// API has not returned 'ok' in status, fetch error, and return
|
||||
// Check if an error is present
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
// Error not defined in JSON
|
||||
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Pioj(&(*getAlbumListStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*getAlbumListStruct)->errorMessage, subsonic_error, "message");
|
||||
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getAlbumListStruct)->errorCode, (*getAlbumListStruct)->errorMessage);
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cJSON* albumList_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "albumList");
|
||||
if (albumList_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - albumList does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(albumList_root, "album");
|
||||
if (album_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - album does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
(*getAlbumListStruct)->albumCount = cJSON_GetArraySize(album_root);
|
||||
|
||||
// Allocate and initialize
|
||||
(*getAlbumListStruct)->albums = (opensubsonic_getAlbumList_album_struct*)malloc((*getAlbumListStruct)->albumCount * sizeof(opensubsonic_getAlbumList_album_struct));
|
||||
for (int i = 0; i < (*getAlbumListStruct)->albumCount; i++) {
|
||||
(*getAlbumListStruct)->albums[i].id = NULL;
|
||||
(*getAlbumListStruct)->albums[i].parent = NULL;
|
||||
(*getAlbumListStruct)->albums[i].album = NULL;
|
||||
(*getAlbumListStruct)->albums[i].title = NULL;
|
||||
(*getAlbumListStruct)->albums[i].name = NULL;
|
||||
(*getAlbumListStruct)->albums[i].coverArt = NULL;
|
||||
(*getAlbumListStruct)->albums[i].songCount = 0;
|
||||
(*getAlbumListStruct)->albums[i].created = NULL;
|
||||
(*getAlbumListStruct)->albums[i].duration = 0;
|
||||
(*getAlbumListStruct)->albums[i].playCount = 0;
|
||||
(*getAlbumListStruct)->albums[i].artistId = NULL;
|
||||
(*getAlbumListStruct)->albums[i].artist = NULL;
|
||||
(*getAlbumListStruct)->albums[i].year = 0;
|
||||
(*getAlbumListStruct)->albums[i].genre = NULL;
|
||||
|
||||
}
|
||||
|
||||
for (int i = 0; i < (*getAlbumListStruct)->albumCount; i++) {
|
||||
cJSON* array_album_root = cJSON_GetArrayItem(album_root, i);
|
||||
if (array_album_root != NULL) {
|
||||
OSS_Psoj(&(*getAlbumListStruct)->albums[i].id, array_album_root, "id");
|
||||
OSS_Psoj(&(*getAlbumListStruct)->albums[i].parent, array_album_root, "parent");
|
||||
OSS_Psoj(&(*getAlbumListStruct)->albums[i].album, array_album_root, "album");
|
||||
OSS_Psoj(&(*getAlbumListStruct)->albums[i].title, array_album_root, "title");
|
||||
OSS_Psoj(&(*getAlbumListStruct)->albums[i].name, array_album_root, "name");
|
||||
OSS_Psoj(&(*getAlbumListStruct)->albums[i].coverArt, array_album_root, "coverArt");
|
||||
OSS_Pioj(&(*getAlbumListStruct)->albums[i].songCount, array_album_root, "songCount");
|
||||
OSS_Psoj(&(*getAlbumListStruct)->albums[i].created, array_album_root, "created");
|
||||
OSS_Ploj(&(*getAlbumListStruct)->albums[i].duration, array_album_root, "duration");
|
||||
OSS_Pioj(&(*getAlbumListStruct)->albums[i].playCount, array_album_root, "playCount");
|
||||
OSS_Psoj(&(*getAlbumListStruct)->albums[i].artistId, array_album_root, "artistId");
|
||||
OSS_Psoj(&(*getAlbumListStruct)->albums[i].artist, array_album_root, "artist");
|
||||
OSS_Pioj(&(*getAlbumListStruct)->albums[i].year, array_album_root, "year");
|
||||
OSS_Psoj(&(*getAlbumListStruct)->albums[i].genre, array_album_root, "genre");
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void opensubsonic_getAlbumList_struct_free(opensubsonic_getAlbumList_struct** getAlbumListStruct) {
|
||||
logger_log_general(__func__, "Freeing /getAlbumList endpoint heap objects.");
|
||||
if ((*getAlbumListStruct)->status != NULL) { free((*getAlbumListStruct)->status); }
|
||||
if ((*getAlbumListStruct)->errorMessage != NULL) { free((*getAlbumListStruct)->errorMessage); }
|
||||
for (int i = 0; i < (*getAlbumListStruct)->albumCount; i++) {
|
||||
if ((*getAlbumListStruct)->albums[i].id != NULL) { free((*getAlbumListStruct)->albums[i].id); }
|
||||
if ((*getAlbumListStruct)->albums[i].parent != NULL) { free((*getAlbumListStruct)->albums[i].parent); }
|
||||
if ((*getAlbumListStruct)->albums[i].album != NULL) { free((*getAlbumListStruct)->albums[i].album); }
|
||||
if ((*getAlbumListStruct)->albums[i].title != NULL) { free((*getAlbumListStruct)->albums[i].title); }
|
||||
if ((*getAlbumListStruct)->albums[i].name != NULL) { free((*getAlbumListStruct)->albums[i].name); }
|
||||
if ((*getAlbumListStruct)->albums[i].coverArt != NULL) { free((*getAlbumListStruct)->albums[i].coverArt); }
|
||||
if ((*getAlbumListStruct)->albums[i].created != NULL) { free((*getAlbumListStruct)->albums[i].created); }
|
||||
if ((*getAlbumListStruct)->albums[i].artistId != NULL) { free((*getAlbumListStruct)->albums[i].artistId); }
|
||||
if ((*getAlbumListStruct)->albums[i].artist != NULL) { free((*getAlbumListStruct)->albums[i].artist); }
|
||||
if ((*getAlbumListStruct)->albums[i].genre != NULL) { free((*getAlbumListStruct)->albums[i].genre); }
|
||||
}
|
||||
if ((*getAlbumListStruct)->albums != NULL) { free((*getAlbumListStruct)->albums); }
|
||||
if (*getAlbumListStruct != NULL) { free(*getAlbumListStruct); }
|
||||
}
|
||||
33
src/libopensubsonic/endpoint_getAlbumList.h
Normal file
33
src/libopensubsonic/endpoint_getAlbumList.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef _ENDPOINT_GETALBUMLIST_H
|
||||
#define _ENDPOINT_GETALBUMLIST_H
|
||||
|
||||
typedef struct {
|
||||
char* id;
|
||||
char* parent;
|
||||
char* album;
|
||||
char* title;
|
||||
char* name;
|
||||
// 'isDir' excluded
|
||||
char* coverArt;
|
||||
int songCount; // Only use as a guideline, count manually when possible
|
||||
char* created;
|
||||
long duration;
|
||||
int playCount;
|
||||
char* artistId;
|
||||
char* artist;
|
||||
int year;
|
||||
char* genre;
|
||||
} opensubsonic_getAlbumList_album_struct;
|
||||
|
||||
typedef struct {
|
||||
char* status;
|
||||
int errorCode;
|
||||
char* errorMessage;
|
||||
int albumCount; // Album count (Counted from array, NOT from JSON)
|
||||
opensubsonic_getAlbumList_album_struct* albums;
|
||||
} opensubsonic_getAlbumList_struct;
|
||||
|
||||
int opensubsonic_getAlbumList_parse(char* data, opensubsonic_getAlbumList_struct** getAlbumListStruct);
|
||||
void opensubsonic_getAlbumList_struct_free(opensubsonic_getAlbumList_struct** getAlbumListStruct);
|
||||
|
||||
#endif
|
||||
163
src/libopensubsonic/endpoint_getArtist.c
Normal file
163
src/libopensubsonic/endpoint_getArtist.c
Normal file
@@ -0,0 +1,163 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_getArtist.h"
|
||||
|
||||
// Parse the JSON returned from the /rest/getArtist endpoint
|
||||
// Returns 1 if failure occured, else 0
|
||||
int opensubsonic_getArtist_parse(char* data, opensubsonic_getArtist_struct** getArtistStruct) {
|
||||
// Allocate struct
|
||||
*getArtistStruct = (opensubsonic_getArtist_struct*)malloc(sizeof(opensubsonic_getArtist_struct));
|
||||
|
||||
// Initialize struct variables
|
||||
(*getArtistStruct)->status = NULL;
|
||||
(*getArtistStruct)->errorCode = 0;
|
||||
(*getArtistStruct)->errorMessage = NULL;
|
||||
(*getArtistStruct)->artistId = NULL;
|
||||
(*getArtistStruct)->artistName = NULL;
|
||||
(*getArtistStruct)->coverArt = NULL;
|
||||
(*getArtistStruct)->albumCount = 0;
|
||||
(*getArtistStruct)->userRating = 0;
|
||||
(*getArtistStruct)->artistImageUrl = NULL;
|
||||
(*getArtistStruct)->starred = NULL;
|
||||
(*getArtistStruct)->albums = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getArtistStruct)->status, subsonic_root, "status");
|
||||
|
||||
// Check if API has returned an error
|
||||
if (strstr((*getArtistStruct)->status, "ok") == NULL) {
|
||||
// API has not returned 'ok' in status, fetch error, and return
|
||||
// Check if an error is present
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
// Error not defined in JSON
|
||||
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Pioj(&(*getArtistStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*getArtistStruct)->errorMessage, subsonic_error, "message");
|
||||
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getArtistStruct)->errorCode, (*getArtistStruct)->errorMessage);
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from artist
|
||||
cJSON* artist_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "artist");
|
||||
if (artist_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - artist does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getArtistStruct)->artistId, artist_root, "id");
|
||||
OSS_Psoj(&(*getArtistStruct)->artistName, artist_root, "name");
|
||||
OSS_Psoj(&(*getArtistStruct)->coverArt, artist_root, "coverArt");
|
||||
OSS_Pioj(&(*getArtistStruct)->userRating, artist_root, "userRating");
|
||||
OSS_Psoj(&(*getArtistStruct)->artistImageUrl, artist_root, "artistImageUrl");
|
||||
OSS_Psoj(&(*getArtistStruct)->starred, artist_root, "starred");
|
||||
|
||||
// Make an object from album
|
||||
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(artist_root, "album");
|
||||
if (album_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - album does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Count the amount of albums
|
||||
(*getArtistStruct)->albumCount = cJSON_GetArraySize(album_root);
|
||||
|
||||
// Allocate memory for albums
|
||||
(*getArtistStruct)->albums = (opensubsonic_getArtist_album_struct*)malloc((*getArtistStruct)->albumCount * sizeof(opensubsonic_getArtist_album_struct));
|
||||
|
||||
// Initialize variables
|
||||
for (int i = 0; i < (*getArtistStruct)->albumCount; i++) {
|
||||
(*getArtistStruct)->albums[i].id = NULL;
|
||||
(*getArtistStruct)->albums[i].parent = NULL;
|
||||
(*getArtistStruct)->albums[i].title = NULL;
|
||||
(*getArtistStruct)->albums[i].name = NULL;
|
||||
(*getArtistStruct)->albums[i].coverArt = NULL;
|
||||
(*getArtistStruct)->albums[i].songCount = 0;
|
||||
(*getArtistStruct)->albums[i].created = NULL;
|
||||
(*getArtistStruct)->albums[i].duration = 0;
|
||||
(*getArtistStruct)->albums[i].playCount = 0;
|
||||
(*getArtistStruct)->albums[i].artistId = NULL;
|
||||
(*getArtistStruct)->albums[i].artist = NULL;
|
||||
(*getArtistStruct)->albums[i].year = 0;
|
||||
(*getArtistStruct)->albums[i].genre = NULL;
|
||||
(*getArtistStruct)->albums[i].userRating = 0;
|
||||
(*getArtistStruct)->albums[i].starred = NULL;
|
||||
}
|
||||
|
||||
// Go through the album array
|
||||
for (int i = 0; i < (*getArtistStruct)->albumCount; i++) {
|
||||
cJSON* array_album_root = cJSON_GetArrayItem(album_root, i);
|
||||
if (array_album_root != NULL) {
|
||||
OSS_Psoj(&(*getArtistStruct)->albums[i].id, array_album_root, "id");
|
||||
OSS_Psoj(&(*getArtistStruct)->albums[i].parent, array_album_root, "parent");
|
||||
OSS_Psoj(&(*getArtistStruct)->albums[i].title, array_album_root, "title");
|
||||
OSS_Psoj(&(*getArtistStruct)->albums[i].name, array_album_root, "name");
|
||||
OSS_Psoj(&(*getArtistStruct)->albums[i].coverArt, array_album_root, "coverArt");
|
||||
OSS_Pioj(&(*getArtistStruct)->albums[i].songCount, array_album_root, "songCount");
|
||||
OSS_Psoj(&(*getArtistStruct)->albums[i].created, array_album_root, "created");
|
||||
OSS_Ploj(&(*getArtistStruct)->albums[i].duration, array_album_root, "duration");
|
||||
OSS_Pioj(&(*getArtistStruct)->albums[i].playCount, array_album_root, "playCount");
|
||||
OSS_Psoj(&(*getArtistStruct)->albums[i].artistId, array_album_root, "artistId");
|
||||
OSS_Psoj(&(*getArtistStruct)->albums[i].artist, array_album_root, "artist");
|
||||
OSS_Pioj(&(*getArtistStruct)->albums[i].year, array_album_root, "year");
|
||||
OSS_Psoj(&(*getArtistStruct)->albums[i].genre, array_album_root, "genre");
|
||||
OSS_Pioj(&(*getArtistStruct)->albums[i].userRating, array_album_root, "userRating");
|
||||
OSS_Psoj(&(*getArtistStruct)->albums[i].starred, array_album_root, "starred");
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Free the dynamically allocated elements of the opensubsonic_getArtist_struct structure and the opensubsonic_getArtist_album_struct array structs.
|
||||
void opensubsonic_getArtist_struct_free(opensubsonic_getArtist_struct** getArtistStruct) {
|
||||
logger_log_general(__func__, "Freeing /getArtist endpoint heap objects.");
|
||||
if((*getArtistStruct)->status != NULL) { free((*getArtistStruct)->status); }
|
||||
if((*getArtistStruct)->errorMessage != NULL) { free((*getArtistStruct)->errorMessage); }
|
||||
if((*getArtistStruct)->artistId != NULL) { free((*getArtistStruct)->artistId); }
|
||||
if((*getArtistStruct)->artistName != NULL) { free((*getArtistStruct)->artistName); }
|
||||
if((*getArtistStruct)->coverArt != NULL) { free((*getArtistStruct)->coverArt); }
|
||||
if((*getArtistStruct)->artistImageUrl != NULL) { free((*getArtistStruct)->artistImageUrl); }
|
||||
if((*getArtistStruct)->starred != NULL) { free((*getArtistStruct)->starred); }
|
||||
for (int i = 0; i < (*getArtistStruct)->albumCount; i++) {
|
||||
if((*getArtistStruct)->albums[i].id != NULL) { free((*getArtistStruct)->albums[i].id); }
|
||||
if((*getArtistStruct)->albums[i].parent != NULL) { free((*getArtistStruct)->albums[i].parent); }
|
||||
if((*getArtistStruct)->albums[i].title != NULL) { free((*getArtistStruct)->albums[i].title); }
|
||||
if((*getArtistStruct)->albums[i].name != NULL) { free((*getArtistStruct)->albums[i].name); }
|
||||
if((*getArtistStruct)->albums[i].coverArt != NULL) { free((*getArtistStruct)->albums[i].coverArt); }
|
||||
if((*getArtistStruct)->albums[i].created != NULL) { free((*getArtistStruct)->albums[i].created); }
|
||||
if((*getArtistStruct)->albums[i].artistId != NULL) { free((*getArtistStruct)->albums[i].artistId); }
|
||||
if((*getArtistStruct)->albums[i].artist != NULL) { free((*getArtistStruct)->albums[i].artist); }
|
||||
if((*getArtistStruct)->albums[i].genre != NULL) { free((*getArtistStruct)->albums[i].genre); }
|
||||
if((*getArtistStruct)->albums[i].starred != NULL) { free((*getArtistStruct)->albums[i].starred); }
|
||||
}
|
||||
if((*getArtistStruct)->albums != NULL) { free((*getArtistStruct)->albums); }
|
||||
if(*getArtistStruct != NULL) { free(*getArtistStruct); }
|
||||
}
|
||||
41
src/libopensubsonic/endpoint_getArtist.h
Normal file
41
src/libopensubsonic/endpoint_getArtist.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef _ENDPOINT_GETARTIST_H
|
||||
#define _ENDPOINT_GETARTIST_H
|
||||
|
||||
typedef struct {
|
||||
char* id; // Album ID
|
||||
char* parent; // Parent ID
|
||||
char* title; // Album Title
|
||||
char* name; // Album Name
|
||||
// 'isDir' excluded
|
||||
char* coverArt; // Cover art ID
|
||||
int songCount;
|
||||
char* created; // Time created
|
||||
long duration; // Duration of the album in seconds
|
||||
int playCount;
|
||||
char* artistId; // Artist ID
|
||||
char* artist; // Artist Name
|
||||
int year; // Year released
|
||||
char* genre;
|
||||
int userRating;
|
||||
char* starred; // Time starred
|
||||
} opensubsonic_getArtist_album_struct;
|
||||
|
||||
typedef struct {
|
||||
char* status;
|
||||
int errorCode;
|
||||
char* errorMessage;
|
||||
char* artistId; // Artist ID
|
||||
char* artistName; // Artist name
|
||||
char* coverArt; // Artist cover art ID
|
||||
int userRating;
|
||||
char* artistImageUrl;
|
||||
char* starred; // Time starred
|
||||
// 'musicBrainzId', 'sorted', 'roles' excluded
|
||||
int albumCount; // Album count (Counted from array, NOT from JSON)
|
||||
opensubsonic_getArtist_album_struct* albums;
|
||||
} opensubsonic_getArtist_struct;
|
||||
|
||||
int opensubsonic_getArtist_parse(char* data, opensubsonic_getArtist_struct** getArtistStruct);
|
||||
void opensubsonic_getArtist_struct_free(opensubsonic_getArtist_struct** getArtistStruct);
|
||||
|
||||
#endif
|
||||
144
src/libopensubsonic/endpoint_getArtists.c
Normal file
144
src/libopensubsonic/endpoint_getArtists.c
Normal file
@@ -0,0 +1,144 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_getArtists.h"
|
||||
|
||||
// Parse the JSON returned from the /rest/getArtists endpoint
|
||||
// Returns 1 if failure occured, else 0
|
||||
int opensubsonic_getArtists_parse(char* data, opensubsonic_getArtists_struct** getArtistsStruct) {
|
||||
// Allocate struct
|
||||
*getArtistsStruct = (opensubsonic_getArtists_struct*)malloc(sizeof(opensubsonic_getArtists_struct));
|
||||
|
||||
// Initialize struct variables
|
||||
(*getArtistsStruct)->status = NULL;
|
||||
(*getArtistsStruct)->errorMessage = NULL;
|
||||
(*getArtistsStruct)->errorCode = 0;
|
||||
(*getArtistsStruct)->artistCount = 0;
|
||||
(*getArtistsStruct)->artists = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getArtistsStruct)->status, subsonic_root, "status");
|
||||
|
||||
// Check if API has returned an error
|
||||
if (strstr((*getArtistsStruct)->status, "ok") == NULL) {
|
||||
// API has not returned 'ok' in status, fetch error, and return
|
||||
// Check if an error is present
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
// Error not defined in JSON
|
||||
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Pioj(&(*getArtistsStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*getArtistsStruct)->errorMessage, subsonic_error, "message");
|
||||
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getArtistsStruct)->errorCode, (*getArtistsStruct)->errorMessage);
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from artists
|
||||
cJSON* artists_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "artists");
|
||||
if (artists_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - artists does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from index
|
||||
cJSON* index_root = cJSON_GetObjectItemCaseSensitive(artists_root, "index");
|
||||
if (index_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - index does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Count the amount of artists present for malloc (Copied from function below)
|
||||
int totalArtistIndex = 0;
|
||||
for (int i = 0; i < cJSON_GetArraySize(index_root); i++) {
|
||||
cJSON* array_letter_root = cJSON_GetArrayItem(index_root, i);
|
||||
|
||||
cJSON* current_letter_artists_array = cJSON_GetObjectItemCaseSensitive(array_letter_root, "artist");
|
||||
if (current_letter_artists_array != NULL) {
|
||||
// Array of artists starting with the same letter
|
||||
for (int j = 0; j < cJSON_GetArraySize(current_letter_artists_array); j++) {
|
||||
cJSON* current_letter_artist_array_layer_b = cJSON_GetArrayItem(current_letter_artists_array, j);
|
||||
if (current_letter_artist_array_layer_b != NULL) {
|
||||
// Increment total artist index
|
||||
(*getArtistsStruct)->artistCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate memory for opensubsonic_getArtists_artist_struct inside opensubsonic_getArtists_struct (Where the artist data is held)
|
||||
(*getArtistsStruct)->artists = (opensubsonic_getArtists_artist_struct*)malloc((*getArtistsStruct)->artistCount * sizeof(opensubsonic_getArtists_artist_struct));
|
||||
|
||||
// Initialize struct variables
|
||||
for (int i = 0; i < (*getArtistsStruct)->artistCount; i++) {
|
||||
(*getArtistsStruct)->artists[i].name = NULL;
|
||||
(*getArtistsStruct)->artists[i].id = NULL;
|
||||
(*getArtistsStruct)->artists[i].coverArt = NULL;
|
||||
(*getArtistsStruct)->artists[i].albumCount = 0;
|
||||
}
|
||||
|
||||
// Go through the alphabet array (Each element in this array is the first letter of the artist, organized)
|
||||
int currentArtistIndex = 0;
|
||||
for (int i = 0; i < cJSON_GetArraySize(index_root); i++) {
|
||||
cJSON* array_letter_root = cJSON_GetArrayItem(index_root, i);
|
||||
|
||||
cJSON* current_letter_artists_array = cJSON_GetObjectItemCaseSensitive(array_letter_root, "artist");
|
||||
if (current_letter_artists_array != NULL) {
|
||||
// Array of artists starting with the same letter
|
||||
for (int j = 0; j < cJSON_GetArraySize(current_letter_artists_array); j++) {
|
||||
cJSON* current_letter_artist_array_layer_b = cJSON_GetArrayItem(current_letter_artists_array, j);
|
||||
if (current_letter_artist_array_layer_b != NULL) {
|
||||
// Fetch information
|
||||
OSS_Psoj(&(*getArtistsStruct)->artists[currentArtistIndex].name, current_letter_artist_array_layer_b, "name");
|
||||
OSS_Psoj(&(*getArtistsStruct)->artists[currentArtistIndex].id, current_letter_artist_array_layer_b, "id");
|
||||
OSS_Psoj(&(*getArtistsStruct)->artists[currentArtistIndex].coverArt, current_letter_artist_array_layer_b, "coverArt");
|
||||
OSS_Pioj(&(*getArtistsStruct)->artists[currentArtistIndex].albumCount, current_letter_artist_array_layer_b, "albumCount");
|
||||
|
||||
// Increment current artist index
|
||||
currentArtistIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Free the opensubsonic_getArtists_struct structure.
|
||||
void opensubsonic_getArtists_struct_free(opensubsonic_getArtists_struct** getArtistsStruct) {
|
||||
logger_log_general(__func__, "Freeing /getArtists endpoint heap objects.");
|
||||
if ((*getArtistsStruct)->status != NULL) { free((*getArtistsStruct)->status); }
|
||||
if ((*getArtistsStruct)->errorMessage != NULL) { free((*getArtistsStruct)->errorMessage); }
|
||||
for (size_t i = 0; i < (*getArtistsStruct)->artistCount; i++) {
|
||||
if ((*getArtistsStruct)->artists[i].name != NULL) { free((*getArtistsStruct)->artists[i].name); }
|
||||
if ((*getArtistsStruct)->artists[i].id != NULL) { free((*getArtistsStruct)->artists[i].id); }
|
||||
if ((*getArtistsStruct)->artists[i].coverArt != NULL) { free((*getArtistsStruct)->artists[i].coverArt); }
|
||||
}
|
||||
if ((*getArtistsStruct)->artists != NULL) { free((*getArtistsStruct)->artists); }
|
||||
if (*getArtistsStruct != NULL) { free(*getArtistsStruct); }
|
||||
}
|
||||
22
src/libopensubsonic/endpoint_getArtists.h
Normal file
22
src/libopensubsonic/endpoint_getArtists.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef _ENDPOINT_GETARTISTS_H
|
||||
#define _ENDPOINT_GETARTISTS_H
|
||||
|
||||
typedef struct {
|
||||
char* id;
|
||||
char* name;
|
||||
char* coverArt;
|
||||
int albumCount;
|
||||
} opensubsonic_getArtists_artist_struct;
|
||||
|
||||
typedef struct {
|
||||
char* status;
|
||||
int errorCode;
|
||||
char* errorMessage;
|
||||
int artistCount;
|
||||
opensubsonic_getArtists_artist_struct* artists;
|
||||
} opensubsonic_getArtists_struct;
|
||||
|
||||
int opensubsonic_getArtists_parse(char* data, opensubsonic_getArtists_struct** getArtistsStruct);
|
||||
void opensubsonic_getArtists_struct_free(opensubsonic_getArtists_struct** getArtistsStruct);
|
||||
|
||||
#endif
|
||||
129
src/libopensubsonic/endpoint_getLyricsBySongId.c
Normal file
129
src/libopensubsonic/endpoint_getLyricsBySongId.c
Normal file
@@ -0,0 +1,129 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_getLyricsBySongId.h"
|
||||
|
||||
// Returns 1 if failure occured, else 0
|
||||
int opensubsonic_getLyricsBySongId_parse(char* data, opensubsonic_getLyricsBySongId_struct** getLyricsBySongIdStruct) {
|
||||
// Allocate struct
|
||||
*getLyricsBySongIdStruct = malloc(sizeof(opensubsonic_getLyricsBySongId_struct));
|
||||
|
||||
// Initialize struct variables
|
||||
(*getLyricsBySongIdStruct)->status = NULL;
|
||||
(*getLyricsBySongIdStruct)->errorCode = 0;
|
||||
(*getLyricsBySongIdStruct)->errorMessage = NULL;
|
||||
(*getLyricsBySongIdStruct)->displayArtist = NULL;
|
||||
(*getLyricsBySongIdStruct)->displayTitle = NULL;
|
||||
(*getLyricsBySongIdStruct)->lyricsAmount = 0;
|
||||
(*getLyricsBySongIdStruct)->lyrics = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getLyricsBySongIdStruct)->status, subsonic_root, "status");
|
||||
|
||||
// Check if API has returned an error
|
||||
if (strstr((*getLyricsBySongIdStruct)->status, "ok") == NULL) {
|
||||
// API has not returned 'ok' in status, fetch error, and return
|
||||
// Check if an error is present
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
// Error not defined in JSON
|
||||
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Pioj(&(*getLyricsBySongIdStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*getLyricsBySongIdStruct)->errorMessage, subsonic_error, "message");
|
||||
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getLyricsBySongIdStruct)->errorCode, (*getLyricsBySongIdStruct)->errorMessage);
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from 'lyricsList'
|
||||
cJSON* lyricsList_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "lyricsList");
|
||||
if (lyricsList_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - lyricsList does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from 'structuredLyrics'
|
||||
cJSON* structuredLyrics_root = cJSON_GetObjectItemCaseSensitive(lyricsList_root, "structuredLyrics");
|
||||
if (structuredLyrics_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - structuredLyrics does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from the first index of 'structuredLyrics'
|
||||
cJSON* structuredLyrics_idx0_root = cJSON_GetArrayItem(structuredLyrics_root, 0);
|
||||
if (structuredLyrics_idx0_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - structuredLyrics (idx 0) does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getLyricsBySongIdStruct)->displayArtist, structuredLyrics_idx0_root, "displayArtist");
|
||||
OSS_Psoj(&(*getLyricsBySongIdStruct)->displayTitle, structuredLyrics_idx0_root, "displayTitle");
|
||||
|
||||
// Make an object from 'line'
|
||||
cJSON* line_root = cJSON_GetObjectItemCaseSensitive(structuredLyrics_idx0_root, "line");
|
||||
if (line_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - line does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Count amount of objects in 'line' and allocate memory
|
||||
(*getLyricsBySongIdStruct)->lyricsAmount = cJSON_GetArraySize(line_root);
|
||||
(*getLyricsBySongIdStruct)->lyrics = (opensubsonic_getLyricsBySongId_lyric_struct*)malloc((*getLyricsBySongIdStruct)->lyricsAmount * sizeof(opensubsonic_getLyricsBySongId_lyric_struct));
|
||||
|
||||
// Initialize variables
|
||||
for (int i = 0; i < (*getLyricsBySongIdStruct)->lyricsAmount; i++) {
|
||||
(*getLyricsBySongIdStruct)->lyrics[i].data = NULL;
|
||||
(*getLyricsBySongIdStruct)->lyrics[i].offset = 0;
|
||||
}
|
||||
|
||||
// Extract lyrics
|
||||
for (int i = 0; i < (*getLyricsBySongIdStruct)->lyricsAmount; i++) {
|
||||
cJSON* curr_idx_root = cJSON_GetArrayItem(line_root, i);
|
||||
if (curr_idx_root != NULL) {
|
||||
OSS_Psoj(&(*getLyricsBySongIdStruct)->lyrics[i].data, curr_idx_root, "value");
|
||||
OSS_Ploj(&(*getLyricsBySongIdStruct)->lyrics[i].offset, curr_idx_root, "start");
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void opensubsonic_getLyricsBySongId_struct_free(opensubsonic_getLyricsBySongId_struct** getLyricsBySongIdStruct) {
|
||||
logger_log_general(__func__, "Freeing /getLyricsBySongId endpoint heap objects.");
|
||||
if ((*getLyricsBySongIdStruct)->status != NULL) { free((*getLyricsBySongIdStruct)->status); }
|
||||
if ((*getLyricsBySongIdStruct)->errorMessage != NULL) { free((*getLyricsBySongIdStruct)->errorMessage); }
|
||||
if ((*getLyricsBySongIdStruct)->displayArtist != NULL) { free((*getLyricsBySongIdStruct)->displayArtist); }
|
||||
if ((*getLyricsBySongIdStruct)->displayTitle != NULL) { free((*getLyricsBySongIdStruct)->displayTitle); }
|
||||
for (int i = 0; i < (*getLyricsBySongIdStruct)->lyricsAmount; i++) {
|
||||
if ((*getLyricsBySongIdStruct)->lyrics[i].data != NULL) { free((*getLyricsBySongIdStruct)->lyrics[i].data); }
|
||||
}
|
||||
if ((*getLyricsBySongIdStruct)->lyrics != NULL) { free((*getLyricsBySongIdStruct)->lyrics); }
|
||||
if (*getLyricsBySongIdStruct != NULL) { free(*getLyricsBySongIdStruct); }
|
||||
}
|
||||
22
src/libopensubsonic/endpoint_getLyricsBySongId.h
Normal file
22
src/libopensubsonic/endpoint_getLyricsBySongId.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef _ENDPOINT_GETLYRICSBYSONGID_H
|
||||
#define _ENDPOINT_GETLYRICSBYSONGID_H
|
||||
|
||||
typedef struct {
|
||||
char* data;
|
||||
long offset;
|
||||
} opensubsonic_getLyricsBySongId_lyric_struct;
|
||||
|
||||
typedef struct {
|
||||
char* status;
|
||||
int errorCode;
|
||||
char* errorMessage;
|
||||
char* displayArtist;
|
||||
char* displayTitle;
|
||||
int lyricsAmount;
|
||||
opensubsonic_getLyricsBySongId_lyric_struct* lyrics;
|
||||
} opensubsonic_getLyricsBySongId_struct;
|
||||
|
||||
int opensubsonic_getLyricsBySongId_parse(char* data, opensubsonic_getLyricsBySongId_struct** getLyricsBySongIdStruct);
|
||||
void opensubsonic_getLyricsBySongId_struct_free(opensubsonic_getLyricsBySongId_struct** getLyricsBySongIdStruct);
|
||||
|
||||
#endif
|
||||
166
src/libopensubsonic/endpoint_getPlaylist.c
Normal file
166
src/libopensubsonic/endpoint_getPlaylist.c
Normal file
@@ -0,0 +1,166 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_getPlaylist.h"
|
||||
|
||||
int opensubsonic_getPlaylist_parse(char* data, opensubsonic_getPlaylist_struct** getPlaylistStruct) {
|
||||
// Allocate struct
|
||||
*getPlaylistStruct = (opensubsonic_getPlaylist_struct*)malloc(sizeof(opensubsonic_getPlaylist_struct));
|
||||
|
||||
// Initialize struct variables
|
||||
(*getPlaylistStruct)->status = NULL;
|
||||
(*getPlaylistStruct)->errorCode = 0;
|
||||
(*getPlaylistStruct)->errorMessage = NULL;
|
||||
(*getPlaylistStruct)->id = NULL;
|
||||
(*getPlaylistStruct)->name = NULL;
|
||||
(*getPlaylistStruct)->owner = NULL;
|
||||
(*getPlaylistStruct)->isPublic = false;
|
||||
(*getPlaylistStruct)->created = NULL;
|
||||
(*getPlaylistStruct)->changed = NULL;
|
||||
(*getPlaylistStruct)->songCount = 0;
|
||||
(*getPlaylistStruct)->duration = 0;
|
||||
(*getPlaylistStruct)->songs = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getPlaylistStruct)->status, subsonic_root, "status");
|
||||
|
||||
// Check if API has returned an error
|
||||
if (strstr((*getPlaylistStruct)->status, "ok") == NULL) {
|
||||
// API has not returned 'ok' in status, fetch error, and return
|
||||
// Check if an error is present
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
// Error not defined in JSON
|
||||
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Pioj(&(*getPlaylistStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->errorMessage, subsonic_error, "message");
|
||||
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getPlaylistStruct)->errorCode, (*getPlaylistStruct)->errorMessage);
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from 'playlist'
|
||||
cJSON* playlist_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "playlist");
|
||||
if (playlist_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - playlist does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getPlaylistStruct)->id, playlist_root, "id");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->name, playlist_root, "name");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->owner, playlist_root, "owner");
|
||||
OSS_Pboj(&(*getPlaylistStruct)->isPublic, playlist_root, "public");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->created, playlist_root, "created");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->changed, playlist_root, "changed");
|
||||
OSS_Ploj(&(*getPlaylistStruct)->duration, playlist_root, "duration");
|
||||
|
||||
// Make an object from 'entry'
|
||||
cJSON* entry_root = cJSON_GetObjectItemCaseSensitive(playlist_root, "entry");
|
||||
if (entry_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - entry does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get the amount of songs in the playlist, and allocate memory
|
||||
(*getPlaylistStruct)->songCount = cJSON_GetArraySize(entry_root);
|
||||
(*getPlaylistStruct)->songs = (opensubsonic_getPlaylist_songs_struct*)malloc((*getPlaylistStruct)->songCount * sizeof(opensubsonic_getPlaylist_songs_struct));
|
||||
|
||||
// Initialize struct variables
|
||||
for (int i = 0; i < (*getPlaylistStruct)->songCount; i++) {
|
||||
(*getPlaylistStruct)->songs[i].id = NULL;
|
||||
(*getPlaylistStruct)->songs[i].parent = NULL;
|
||||
(*getPlaylistStruct)->songs[i].title = NULL;
|
||||
(*getPlaylistStruct)->songs[i].albumId = NULL;
|
||||
(*getPlaylistStruct)->songs[i].album = NULL;
|
||||
(*getPlaylistStruct)->songs[i].artistId = NULL;
|
||||
(*getPlaylistStruct)->songs[i].artist = NULL;
|
||||
(*getPlaylistStruct)->songs[i].coverArt = NULL;
|
||||
(*getPlaylistStruct)->songs[i].duration = 0;
|
||||
(*getPlaylistStruct)->songs[i].bitRate = 0;
|
||||
(*getPlaylistStruct)->songs[i].bitDepth = 0;
|
||||
(*getPlaylistStruct)->songs[i].samplingRate = 0;
|
||||
(*getPlaylistStruct)->songs[i].channelCount = 0;
|
||||
(*getPlaylistStruct)->songs[i].userRating = 0;
|
||||
(*getPlaylistStruct)->songs[i].track = 0;
|
||||
(*getPlaylistStruct)->songs[i].year = 0;
|
||||
(*getPlaylistStruct)->songs[i].genre = NULL;
|
||||
(*getPlaylistStruct)->songs[i].size = 0;
|
||||
(*getPlaylistStruct)->songs[i].discNumber = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (*getPlaylistStruct)->songCount; i++) {
|
||||
cJSON* curr_idx_root = cJSON_GetArrayItem(entry_root, i);
|
||||
if (curr_idx_root != NULL) {
|
||||
OSS_Psoj(&(*getPlaylistStruct)->songs[i].id, curr_idx_root, "id");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->songs[i].parent, curr_idx_root, "parent");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->songs[i].title, curr_idx_root, "title");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->songs[i].albumId, curr_idx_root, "albumId");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->songs[i].album, curr_idx_root, "album");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->songs[i].artistId, curr_idx_root, "artistId");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->songs[i].artist, curr_idx_root, "artist");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->songs[i].coverArt, curr_idx_root, "coverArt");
|
||||
OSS_Ploj(&(*getPlaylistStruct)->songs[i].duration, curr_idx_root, "duration");
|
||||
OSS_Pioj(&(*getPlaylistStruct)->songs[i].bitRate, curr_idx_root, "bitRate");
|
||||
OSS_Pioj(&(*getPlaylistStruct)->songs[i].bitDepth, curr_idx_root, "bitDepth");
|
||||
OSS_Ploj(&(*getPlaylistStruct)->songs[i].samplingRate, curr_idx_root, "samplingRate");
|
||||
OSS_Pioj(&(*getPlaylistStruct)->songs[i].channelCount, curr_idx_root, "channelCount");
|
||||
OSS_Pioj(&(*getPlaylistStruct)->songs[i].userRating, curr_idx_root, "userRating");
|
||||
OSS_Pioj(&(*getPlaylistStruct)->songs[i].track, curr_idx_root, "track");
|
||||
OSS_Pioj(&(*getPlaylistStruct)->songs[i].year, curr_idx_root, "year");
|
||||
OSS_Psoj(&(*getPlaylistStruct)->songs[i].genre, curr_idx_root, "genre");
|
||||
OSS_Ploj(&(*getPlaylistStruct)->songs[i].size, curr_idx_root, "size");
|
||||
OSS_Pioj(&(*getPlaylistStruct)->songs[i].discNumber, curr_idx_root, "discNumber");
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void opensubsonic_getPlaylist_struct_free(opensubsonic_getPlaylist_struct** getPlaylistStruct) {
|
||||
logger_log_general(__func__, "Freeing /getPlaylist endpoint heap objects.");
|
||||
if ((*getPlaylistStruct)->status != NULL) { free((*getPlaylistStruct)->status); }
|
||||
if ((*getPlaylistStruct)->errorMessage != NULL) { free((*getPlaylistStruct)->errorMessage); }
|
||||
if ((*getPlaylistStruct)->id != NULL) { free((*getPlaylistStruct)->id); }
|
||||
if ((*getPlaylistStruct)->name != NULL) { free((*getPlaylistStruct)->name); }
|
||||
if ((*getPlaylistStruct)->owner != NULL) { free((*getPlaylistStruct)->owner); }
|
||||
if ((*getPlaylistStruct)->created != NULL) { free((*getPlaylistStruct)->created); }
|
||||
if ((*getPlaylistStruct)->changed != NULL) { free((*getPlaylistStruct)->changed); }
|
||||
for (int i = 0; i < (*getPlaylistStruct)->songCount; i++) {
|
||||
if((*getPlaylistStruct)->songs[i].id != NULL) { free((*getPlaylistStruct)->songs[i].id); }
|
||||
if((*getPlaylistStruct)->songs[i].parent != NULL) { free((*getPlaylistStruct)->songs[i].parent); }
|
||||
if((*getPlaylistStruct)->songs[i].title != NULL) { free((*getPlaylistStruct)->songs[i].title); }
|
||||
if((*getPlaylistStruct)->songs[i].albumId != NULL) { free((*getPlaylistStruct)->songs[i].albumId); }
|
||||
if((*getPlaylistStruct)->songs[i].album != NULL) { free((*getPlaylistStruct)->songs[i].album); }
|
||||
if((*getPlaylistStruct)->songs[i].artistId != NULL) { free((*getPlaylistStruct)->songs[i].artistId); }
|
||||
if((*getPlaylistStruct)->songs[i].artist != NULL) { free((*getPlaylistStruct)->songs[i].artist); }
|
||||
if((*getPlaylistStruct)->songs[i].coverArt != NULL) { free((*getPlaylistStruct)->songs[i].coverArt); }
|
||||
if((*getPlaylistStruct)->songs[i].genre != NULL) { free((*getPlaylistStruct)->songs[i].genre); }
|
||||
}
|
||||
if ((*getPlaylistStruct)->songs != NULL) { free((*getPlaylistStruct)->songs); }
|
||||
if (*getPlaylistStruct != NULL) { free(*getPlaylistStruct); }
|
||||
}
|
||||
49
src/libopensubsonic/endpoint_getPlaylist.h
Normal file
49
src/libopensubsonic/endpoint_getPlaylist.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef _ENDPOINT_GETPLAYLIST_H
|
||||
#define _ENDPOINT_GETPLAYLIST_H
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
char* id;
|
||||
char* parent;
|
||||
char* title;
|
||||
// 'isDir', 'isVideo', and 'type' are not needed
|
||||
char* albumId;
|
||||
char* album; // Album Name
|
||||
char* artistId;
|
||||
char* artist; // Artist Name
|
||||
char* coverArt; // Cover Art ID
|
||||
long duration; // Duration (seconds)
|
||||
// Do not completely rely on the next 4 values if you can detect them manually
|
||||
int bitRate;
|
||||
int bitDepth;
|
||||
long samplingRate;
|
||||
int channelCount;
|
||||
int userRating;
|
||||
// Not sure what 'averageRating' is used for
|
||||
int track;
|
||||
int year; // Year released
|
||||
char* genre;
|
||||
long size; // Size in bytes
|
||||
int discNumber;
|
||||
// 'suffix', 'contentType', and 'path' not needed
|
||||
} opensubsonic_getPlaylist_songs_struct;
|
||||
|
||||
typedef struct {
|
||||
char* status;
|
||||
int errorCode;
|
||||
char* errorMessage;
|
||||
char* id;
|
||||
char* name;
|
||||
char* owner;
|
||||
bool isPublic; // 'public'
|
||||
char* created;
|
||||
char* changed;
|
||||
long duration;
|
||||
int songCount; // Counts are not reliable from navidrome, counted manually
|
||||
opensubsonic_getPlaylist_songs_struct* songs;
|
||||
} opensubsonic_getPlaylist_struct;
|
||||
|
||||
int opensubsonic_getPlaylist_parse(char* data, opensubsonic_getPlaylist_struct** getPlaylistStruct);
|
||||
void opensubsonic_getPlaylist_struct_free(opensubsonic_getPlaylist_struct** getPlaylistStruct);
|
||||
|
||||
#endif
|
||||
121
src/libopensubsonic/endpoint_getPlaylists.c
Normal file
121
src/libopensubsonic/endpoint_getPlaylists.c
Normal file
@@ -0,0 +1,121 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_getPlaylists.h"
|
||||
|
||||
// Parse the JSON returned from the /rest/getPlaylists endpoint
|
||||
// Returns 1 if failure occured, else 0
|
||||
int opensubsonic_getPlaylists_parse(char* data, opensubsonic_getPlaylists_struct** getPlaylistsStruct) {
|
||||
// Allocate struct
|
||||
*getPlaylistsStruct = (opensubsonic_getPlaylists_struct*)malloc(sizeof(opensubsonic_getPlaylists_struct));
|
||||
|
||||
// Initialize struct variables
|
||||
(*getPlaylistsStruct)->status = NULL;
|
||||
(*getPlaylistsStruct)->errorCode = 0;
|
||||
(*getPlaylistsStruct)->errorMessage = NULL;
|
||||
(*getPlaylistsStruct)->playlistCount = 0;
|
||||
(*getPlaylistsStruct)->playlists = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getPlaylistsStruct)->status, subsonic_root, "status");
|
||||
|
||||
// Check if API has returned an error
|
||||
if (strstr((*getPlaylistsStruct)->status, "ok") == NULL) {
|
||||
// API has not returned 'ok' in status, fetch error, and return
|
||||
// Check if an error is present
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
// Error not defined in JSON
|
||||
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Pioj(&(*getPlaylistsStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*getPlaylistsStruct)->errorMessage, subsonic_error, "message");
|
||||
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getPlaylistsStruct)->errorCode, (*getPlaylistsStruct)->errorMessage);
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from playlists
|
||||
cJSON* playlists_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "playlists");
|
||||
if (playlists_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - playlists does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from playlist
|
||||
cJSON* playlist_root = cJSON_GetObjectItemCaseSensitive(playlists_root, "playlist");
|
||||
if (playlist_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - playlist does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get the amount of playlists
|
||||
(*getPlaylistsStruct)->playlistCount = cJSON_GetArraySize(playlist_root);
|
||||
|
||||
// Allocate memory for opensubsonic_getPlaylists_playlist_struct inside opensubsonic_getPlaylists_struct
|
||||
(*getPlaylistsStruct)->playlists = (opensubsonic_getPlaylists_playlist_struct*)malloc((*getPlaylistsStruct)->playlistCount * sizeof(opensubsonic_getPlaylists_playlist_struct));
|
||||
|
||||
// Initialize struct variables
|
||||
for (int i = 0; i < (*getPlaylistsStruct)->playlistCount; i++) {
|
||||
(*getPlaylistsStruct)->playlists[i].id = NULL;
|
||||
(*getPlaylistsStruct)->playlists[i].name = NULL;
|
||||
(*getPlaylistsStruct)->playlists[i].songCount = 0;
|
||||
(*getPlaylistsStruct)->playlists[i].duration = 0;
|
||||
(*getPlaylistsStruct)->playlists[i].owner = NULL;
|
||||
(*getPlaylistsStruct)->playlists[i].coverArt = NULL;
|
||||
}
|
||||
|
||||
// Extract the data from the playlist array
|
||||
for (int i = 0; i < (*getPlaylistsStruct)->playlistCount; i++) {
|
||||
cJSON* current_index_root = cJSON_GetArrayItem(playlist_root, i);
|
||||
if (current_index_root != NULL) {
|
||||
// Fetch playlist information
|
||||
OSS_Psoj(&(*getPlaylistsStruct)->playlists[i].id, current_index_root, "id");
|
||||
OSS_Psoj(&(*getPlaylistsStruct)->playlists[i].name, current_index_root, "name");
|
||||
OSS_Pioj(&(*getPlaylistsStruct)->playlists[i].songCount, current_index_root, "songCount");
|
||||
OSS_Ploj(&(*getPlaylistsStruct)->playlists[i].duration, current_index_root, "duration");
|
||||
OSS_Psoj(&(*getPlaylistsStruct)->playlists[i].owner, current_index_root, "owner");
|
||||
OSS_Psoj(&(*getPlaylistsStruct)->playlists[i].coverArt, current_index_root, "coverArt");
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void opensubsonic_getPlaylists_struct_free(opensubsonic_getPlaylists_struct** getPlaylistsStruct) {
|
||||
logger_log_general(__func__, "Freeing /getPlaylists endpoint heap objects.");
|
||||
if ((*getPlaylistsStruct)->status != NULL) { free((*getPlaylistsStruct)->status); }
|
||||
if ((*getPlaylistsStruct)->errorMessage != NULL) { free((*getPlaylistsStruct)->errorMessage); }
|
||||
for (size_t i = 0; i < (*getPlaylistsStruct)->playlistCount; i++) {
|
||||
if ((*getPlaylistsStruct)->playlists[i].id != NULL) { free((*getPlaylistsStruct)->playlists[i].id); }
|
||||
if ((*getPlaylistsStruct)->playlists[i].name != NULL) { free((*getPlaylistsStruct)->playlists[i].name); }
|
||||
if ((*getPlaylistsStruct)->playlists[i].owner != NULL) { free((*getPlaylistsStruct)->playlists[i].owner); }
|
||||
if ((*getPlaylistsStruct)->playlists[i].coverArt != NULL) { free((*getPlaylistsStruct)->playlists[i].coverArt); }
|
||||
}
|
||||
if ((*getPlaylistsStruct)->playlists != NULL) { free((*getPlaylistsStruct)->playlists); }
|
||||
if (*getPlaylistsStruct != NULL) { free(*getPlaylistsStruct); }
|
||||
}
|
||||
26
src/libopensubsonic/endpoint_getPlaylists.h
Normal file
26
src/libopensubsonic/endpoint_getPlaylists.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef _ENDPOINT_GETPLAYLISTS_H
|
||||
#define _ENDPOINT_GETPLAYLISTS_H
|
||||
|
||||
typedef struct {
|
||||
char* id; // Album ID
|
||||
char* name; // Album name
|
||||
int songCount; // Number of songs in the album
|
||||
long duration; // Duration of the album in seconds
|
||||
// 'public' doesn't seem useful here
|
||||
char* owner; // Username of the owner of the album
|
||||
// 'created' and 'changed' do not seem useful here either
|
||||
char* coverArt; // Cover art ID of the album
|
||||
} opensubsonic_getPlaylists_playlist_struct;
|
||||
|
||||
typedef struct {
|
||||
char* status;
|
||||
int errorCode;
|
||||
char* errorMessage;
|
||||
int playlistCount;
|
||||
opensubsonic_getPlaylists_playlist_struct* playlists;
|
||||
} opensubsonic_getPlaylists_struct;
|
||||
|
||||
int opensubsonic_getPlaylists_parse(char* data, opensubsonic_getPlaylists_struct** getPlaylistsStruct);
|
||||
void opensubsonic_getPlaylists_struct_free(opensubsonic_getPlaylists_struct** getPlaylistsStruct);
|
||||
|
||||
#endif
|
||||
137
src/libopensubsonic/endpoint_getSong.c
Normal file
137
src/libopensubsonic/endpoint_getSong.c
Normal file
@@ -0,0 +1,137 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_getSong.h"
|
||||
|
||||
// Parse the JSON returned from the /rest/getSong endpoint
|
||||
// Returns 1 if failure occured, else 0
|
||||
int opensubsonic_getSong_parse(char* data, opensubsonic_getSong_struct** getSongStruct) {
|
||||
// Allocate struct
|
||||
*getSongStruct = (opensubsonic_getSong_struct*)malloc(sizeof(opensubsonic_getSong_struct));
|
||||
|
||||
// Initialize struct variables
|
||||
(*getSongStruct)->status = NULL;
|
||||
(*getSongStruct)->errorCode = 0;
|
||||
(*getSongStruct)->errorMessage = NULL;
|
||||
(*getSongStruct)->id = NULL;
|
||||
(*getSongStruct)->parent = NULL;
|
||||
(*getSongStruct)->title = NULL;
|
||||
(*getSongStruct)->album = NULL;
|
||||
(*getSongStruct)->artist = NULL;
|
||||
(*getSongStruct)->track = 0;
|
||||
(*getSongStruct)->year = 0;
|
||||
(*getSongStruct)->coverArt = NULL;
|
||||
(*getSongStruct)->size = 0;
|
||||
(*getSongStruct)->starred = NULL;
|
||||
(*getSongStruct)->duration = 0;
|
||||
(*getSongStruct)->bitRate = 0;
|
||||
(*getSongStruct)->bitDepth = 0;
|
||||
(*getSongStruct)->samplingRate = 0;
|
||||
(*getSongStruct)->channelCount = 0;
|
||||
(*getSongStruct)->played = NULL;
|
||||
(*getSongStruct)->discNumber = 0;
|
||||
(*getSongStruct)->created = NULL;
|
||||
(*getSongStruct)->albumId = NULL;
|
||||
(*getSongStruct)->artistId = NULL;
|
||||
(*getSongStruct)->displayArtist = NULL;
|
||||
(*getSongStruct)->displayAlbumArtist = NULL;
|
||||
(*getSongStruct)->displayComposer = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*getSongStruct)->status, subsonic_root, "status");
|
||||
|
||||
// Check if API has returned an error
|
||||
if (strstr((*getSongStruct)->status, "ok") == NULL) {
|
||||
// API has not returned 'ok' in status, fetch error, and return
|
||||
// Check if an error is present
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
// Error not defined in JSON
|
||||
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Pioj(&(*getSongStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*getSongStruct)->errorMessage, subsonic_error, "message");
|
||||
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getSongStruct)->errorCode, (*getSongStruct)->errorMessage);
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from song
|
||||
cJSON* song_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "song");
|
||||
if (song_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - song does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Fetch song information
|
||||
OSS_Psoj(&(*getSongStruct)->id, song_root, "id");
|
||||
OSS_Psoj(&(*getSongStruct)->parent, song_root, "parent");
|
||||
OSS_Psoj(&(*getSongStruct)->title, song_root, "title");
|
||||
OSS_Psoj(&(*getSongStruct)->album, song_root, "album");
|
||||
OSS_Psoj(&(*getSongStruct)->artist, song_root, "artist");
|
||||
OSS_Pioj(&(*getSongStruct)->track, song_root, "track");
|
||||
OSS_Pioj(&(*getSongStruct)->year, song_root, "year");
|
||||
OSS_Psoj(&(*getSongStruct)->coverArt, song_root, "coverArt");
|
||||
OSS_Ploj(&(*getSongStruct)->size, song_root, "size");
|
||||
OSS_Psoj(&(*getSongStruct)->starred, song_root, "starred");
|
||||
OSS_Ploj(&(*getSongStruct)->duration, song_root, "duration");
|
||||
OSS_Pioj(&(*getSongStruct)->bitRate, song_root, "bitRate");
|
||||
OSS_Pioj(&(*getSongStruct)->bitDepth, song_root, "bitDepth");
|
||||
OSS_Ploj(&(*getSongStruct)->samplingRate, song_root, "samplingRate");
|
||||
OSS_Pioj(&(*getSongStruct)->channelCount, song_root, "channelCount");
|
||||
OSS_Psoj(&(*getSongStruct)->played, song_root, "played");
|
||||
OSS_Pioj(&(*getSongStruct)->discNumber, song_root, "discNumber");
|
||||
OSS_Psoj(&(*getSongStruct)->created, song_root, "created");
|
||||
OSS_Psoj(&(*getSongStruct)->albumId, song_root, "albumId");
|
||||
OSS_Psoj(&(*getSongStruct)->artistId, song_root, "artistId");
|
||||
OSS_Psoj(&(*getSongStruct)->displayArtist, song_root, "displayArtist");
|
||||
OSS_Psoj(&(*getSongStruct)->displayAlbumArtist, song_root, "displayAlbumArtist");
|
||||
OSS_Psoj(&(*getSongStruct)->displayComposer, song_root, "displayComposer");
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Free the dynamically allocated elements of the opensubsonic_getSong_struct structure
|
||||
void opensubsonic_getSong_struct_free(opensubsonic_getSong_struct** getSongStruct) {
|
||||
logger_log_general(__func__, "Freeing /getSong endpoint heap objects.");
|
||||
if ((*getSongStruct)->status != NULL) { free((*getSongStruct)->status); }
|
||||
if ((*getSongStruct)->errorMessage != NULL) { free((*getSongStruct)->errorMessage); }
|
||||
if ((*getSongStruct)->id != NULL) { free((*getSongStruct)->id); }
|
||||
if ((*getSongStruct)->parent != NULL) { free((*getSongStruct)->parent); }
|
||||
if ((*getSongStruct)->title != NULL) { free((*getSongStruct)->title); }
|
||||
if ((*getSongStruct)->album != NULL) { free((*getSongStruct)->album); }
|
||||
if ((*getSongStruct)->artist != NULL) { free((*getSongStruct)->artist); }
|
||||
if ((*getSongStruct)->coverArt != NULL) { free((*getSongStruct)->coverArt); }
|
||||
if ((*getSongStruct)->starred != NULL) { free((*getSongStruct)->starred); }
|
||||
if ((*getSongStruct)->played != NULL) { free((*getSongStruct)->played); }
|
||||
if ((*getSongStruct)->created != NULL) { free((*getSongStruct)->created); }
|
||||
if ((*getSongStruct)->albumId != NULL) { free((*getSongStruct)->albumId); }
|
||||
if ((*getSongStruct)->artistId != NULL) { free((*getSongStruct)->artistId); }
|
||||
if ((*getSongStruct)->displayArtist != NULL) { free((*getSongStruct)->displayArtist); }
|
||||
if ((*getSongStruct)->displayAlbumArtist != NULL) { free((*getSongStruct)->displayAlbumArtist); }
|
||||
if ((*getSongStruct)->displayComposer != NULL) { free((*getSongStruct)->displayComposer); }
|
||||
if (*getSongStruct != NULL) { free(*getSongStruct); }
|
||||
}
|
||||
39
src/libopensubsonic/endpoint_getSong.h
Normal file
39
src/libopensubsonic/endpoint_getSong.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef _ENDPOINT_GETSONG_H
|
||||
#define _ENDPOINT_GETSONG_H
|
||||
|
||||
typedef struct {
|
||||
char* status; // Request status
|
||||
int errorCode; // Request error code (0 if none)
|
||||
char* errorMessage; // Request error message (NULL if none)
|
||||
char* id; // Song ID
|
||||
char* parent; // Song parent
|
||||
// 'isDir' excluded
|
||||
char* title; // Song title
|
||||
char* album; // Song album
|
||||
char* artist; // Song artist
|
||||
int track; // Track number
|
||||
int year; // Year released
|
||||
char* coverArt; // Cover art ID
|
||||
long size; // Size of the song in bytes
|
||||
// 'contentType', 'suffix' excluded
|
||||
char* starred; // Time starred
|
||||
long duration; // Duration of the song in seconds
|
||||
int bitRate; // Bitrate of the song
|
||||
int bitDepth; // Bit depth
|
||||
long samplingRate; // Sample rate
|
||||
int channelCount; // Channel count
|
||||
char* played; // Time last played (TODO probably)
|
||||
int discNumber; // Disc number
|
||||
char* created; // Date added (TODO probably)
|
||||
char* albumId; // Album ID
|
||||
char* artistId; // Artist ID
|
||||
// 'type', 'mediaType', 'isVideo', 'bpm', 'comment', 'sortName', 'musicBrainzId', 'genres', 'artists', 'albumArtists', 'contributors', 'moods', 'replayGain' excluded
|
||||
char* displayArtist; // Display Artist
|
||||
char* displayAlbumArtist; // Display Album Artist
|
||||
char* displayComposer; // Display Composer
|
||||
} opensubsonic_getSong_struct;
|
||||
|
||||
int opensubsonic_getSong_parse(char* data, opensubsonic_getSong_struct** getSongStruct);
|
||||
void opensubsonic_getSong_struct_free(opensubsonic_getSong_struct** getSongStruct);
|
||||
|
||||
#endif
|
||||
260
src/libopensubsonic/endpoint_getStarred.c
Normal file
260
src/libopensubsonic/endpoint_getStarred.c
Normal file
@@ -0,0 +1,260 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_getStarred.h"
|
||||
|
||||
// Returns 1 if failure occured, else 0
|
||||
int opensubsonic_getStarred_parse(char* data, opensubsonic_getStarred_struct** getStarredStruct) {
|
||||
// Allocate and initialize
|
||||
*getStarredStruct = (opensubsonic_getStarred_struct*)malloc(sizeof(opensubsonic_getStarred_struct));
|
||||
(*getStarredStruct)->status = NULL;
|
||||
(*getStarredStruct)->errorMessage = NULL;
|
||||
(*getStarredStruct)->errorCode = 0;
|
||||
(*getStarredStruct)->artistCount = -1;
|
||||
(*getStarredStruct)->albumCount = -1;
|
||||
(*getStarredStruct)->songCount = -1;
|
||||
(*getStarredStruct)->artists = NULL;
|
||||
(*getStarredStruct)->albums = NULL;
|
||||
(*getStarredStruct)->songs = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check for error
|
||||
OSS_Psoj(&(*getStarredStruct)->status, subsonic_root, "status");
|
||||
if (strstr((*getStarredStruct)->status, "ok") == NULL) {
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Pioj(&(*getStarredStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*getStarredStruct)->errorMessage, subsonic_error, "message");
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getStarredStruct)->errorCode, (*getStarredStruct)->errorMessage);
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from starred
|
||||
cJSON* starred_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "starred");
|
||||
if (starred_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - starred does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from artist, album, and song
|
||||
cJSON* artist_root = cJSON_GetObjectItemCaseSensitive(starred_root, "artist");
|
||||
if (artist_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - artist does not exist.");
|
||||
} else {
|
||||
(*getStarredStruct)->artistCount = 0;
|
||||
}
|
||||
|
||||
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(starred_root, "album");
|
||||
if (album_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - album does not exist.");
|
||||
} else {
|
||||
(*getStarredStruct)->albumCount = 0;
|
||||
}
|
||||
|
||||
cJSON* song_root = cJSON_GetObjectItemCaseSensitive(starred_root, "song");
|
||||
if (song_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - song does not exist.");
|
||||
} else {
|
||||
(*getStarredStruct)->songCount = 0;
|
||||
}
|
||||
|
||||
// Extract starred artists
|
||||
if ((*getStarredStruct)->artistCount != -1) {
|
||||
// Count, allocate, initialize, and extract
|
||||
(*getStarredStruct)->artistCount = cJSON_GetArraySize(artist_root);
|
||||
(*getStarredStruct)->artists = (opensubsonic_getStarred_artist_struct*)malloc((*getStarredStruct)->artistCount * sizeof(opensubsonic_getStarred_artist_struct));
|
||||
|
||||
for (int i = 0; i < (*getStarredStruct)->artistCount; i++) {
|
||||
(*getStarredStruct)->artists[i].id = NULL;
|
||||
(*getStarredStruct)->artists[i].name = NULL;
|
||||
(*getStarredStruct)->artists[i].coverArt = NULL;
|
||||
(*getStarredStruct)->artists[i].starred = NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (*getStarredStruct)->artistCount; i++) {
|
||||
cJSON* array_artist_root = cJSON_GetArrayItem(artist_root, i);
|
||||
if (array_artist_root != NULL) {
|
||||
OSS_Psoj(&(*getStarredStruct)->artists[i].id, array_artist_root, "id");
|
||||
OSS_Psoj(&(*getStarredStruct)->artists[i].name, array_artist_root, "name");
|
||||
OSS_Psoj(&(*getStarredStruct)->artists[i].coverArt, array_artist_root, "coverArt");
|
||||
OSS_Psoj(&(*getStarredStruct)->artists[i].starred, array_artist_root, "starred");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No starred artists
|
||||
(*getStarredStruct)->artistCount = 0;
|
||||
}
|
||||
|
||||
// Extract starred albums
|
||||
if ((*getStarredStruct)->albumCount != -1) {
|
||||
// Count, allocate, initialize, and extract
|
||||
(*getStarredStruct)->albumCount = cJSON_GetArraySize(album_root);
|
||||
(*getStarredStruct)->albums = (opensubsonic_getStarred_album_struct*)malloc((*getStarredStruct)->albumCount * sizeof(opensubsonic_getStarred_album_struct));
|
||||
|
||||
for (int i = 0; i < (*getStarredStruct)->albumCount; i++) {
|
||||
(*getStarredStruct)->albums[i].id = NULL;
|
||||
(*getStarredStruct)->albums[i].parent = NULL;
|
||||
(*getStarredStruct)->albums[i].album = NULL;
|
||||
(*getStarredStruct)->albums[i].title = NULL;
|
||||
(*getStarredStruct)->albums[i].name = NULL;
|
||||
(*getStarredStruct)->albums[i].coverArt = NULL;
|
||||
(*getStarredStruct)->albums[i].songCount = 0;
|
||||
(*getStarredStruct)->albums[i].created = NULL;
|
||||
(*getStarredStruct)->albums[i].duration = 0;
|
||||
(*getStarredStruct)->albums[i].playCount = 0;
|
||||
(*getStarredStruct)->albums[i].artistId = NULL;
|
||||
(*getStarredStruct)->albums[i].artist = NULL;
|
||||
(*getStarredStruct)->albums[i].year = 0;
|
||||
(*getStarredStruct)->albums[i].genre = NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (*getStarredStruct)->albumCount; i++) {
|
||||
cJSON* array_album_root = cJSON_GetArrayItem(album_root, i);
|
||||
if (array_album_root != NULL) {
|
||||
OSS_Psoj(&(*getStarredStruct)->albums[i].id, array_album_root, "id");
|
||||
OSS_Psoj(&(*getStarredStruct)->albums[i].parent, array_album_root, "parent");
|
||||
OSS_Psoj(&(*getStarredStruct)->albums[i].album, array_album_root, "album");
|
||||
OSS_Psoj(&(*getStarredStruct)->albums[i].title, array_album_root, "title");
|
||||
OSS_Psoj(&(*getStarredStruct)->albums[i].name, array_album_root, "name");
|
||||
OSS_Psoj(&(*getStarredStruct)->albums[i].coverArt, array_album_root, "coverArt");
|
||||
OSS_Pioj(&(*getStarredStruct)->albums[i].songCount, array_album_root, "songCount");
|
||||
OSS_Psoj(&(*getStarredStruct)->albums[i].created, array_album_root, "created");
|
||||
OSS_Ploj(&(*getStarredStruct)->albums[i].duration, array_album_root, "duration");
|
||||
OSS_Pioj(&(*getStarredStruct)->albums[i].playCount, array_album_root, "playCount");
|
||||
OSS_Psoj(&(*getStarredStruct)->albums[i].artistId, array_album_root, "artistId");
|
||||
OSS_Psoj(&(*getStarredStruct)->albums[i].artist, array_album_root, "artist");
|
||||
OSS_Pioj(&(*getStarredStruct)->albums[i].year, array_album_root, "year");
|
||||
OSS_Psoj(&(*getStarredStruct)->albums[i].genre, array_album_root, "genre");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No starred albums
|
||||
(*getStarredStruct)->albumCount = 0;
|
||||
}
|
||||
|
||||
// Extract starred songs
|
||||
if ((*getStarredStruct)->songCount != -1) {
|
||||
// Count, allocate, initialize, and extract
|
||||
(*getStarredStruct)->songCount = cJSON_GetArraySize(song_root);
|
||||
(*getStarredStruct)->songs = (opensubsonic_getStarred_song_struct*)malloc((*getStarredStruct)->songCount * sizeof(opensubsonic_getStarred_song_struct));
|
||||
|
||||
for (int i = 0; i < (*getStarredStruct)->songCount; i++) {
|
||||
(*getStarredStruct)->songs[i].id = NULL;
|
||||
(*getStarredStruct)->songs[i].parent = NULL;
|
||||
(*getStarredStruct)->songs[i].title = NULL;
|
||||
(*getStarredStruct)->songs[i].album = NULL;
|
||||
(*getStarredStruct)->songs[i].artist = NULL;
|
||||
(*getStarredStruct)->songs[i].track = 0;
|
||||
(*getStarredStruct)->songs[i].year = 0;
|
||||
(*getStarredStruct)->songs[i].coverArt = NULL;
|
||||
(*getStarredStruct)->songs[i].size = 0;
|
||||
(*getStarredStruct)->songs[i].starred = NULL;
|
||||
(*getStarredStruct)->songs[i].duration = 0;
|
||||
(*getStarredStruct)->songs[i].bitRate = 0;
|
||||
(*getStarredStruct)->songs[i].bitDepth = 0;
|
||||
(*getStarredStruct)->songs[i].samplingRate = 0;
|
||||
(*getStarredStruct)->songs[i].channelCount = 0;
|
||||
(*getStarredStruct)->songs[i].playCount = 0;
|
||||
(*getStarredStruct)->songs[i].discNumber = 0;
|
||||
(*getStarredStruct)->songs[i].created = NULL;
|
||||
(*getStarredStruct)->songs[i].albumId = NULL;
|
||||
(*getStarredStruct)->songs[i].artistId = NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (*getStarredStruct)->songCount; i++) {
|
||||
cJSON* array_song_root = cJSON_GetArrayItem(song_root, i);
|
||||
if (array_song_root != NULL) {
|
||||
OSS_Psoj(&(*getStarredStruct)->songs[i].id, array_song_root, "id");
|
||||
OSS_Psoj(&(*getStarredStruct)->songs[i].parent, array_song_root, "parent");
|
||||
OSS_Psoj(&(*getStarredStruct)->songs[i].title, array_song_root, "title");
|
||||
OSS_Psoj(&(*getStarredStruct)->songs[i].album, array_song_root, "album");
|
||||
OSS_Psoj(&(*getStarredStruct)->songs[i].artist, array_song_root, "artist");
|
||||
OSS_Pioj(&(*getStarredStruct)->songs[i].track, array_song_root, "track");
|
||||
OSS_Pioj(&(*getStarredStruct)->songs[i].year, array_song_root, "year");
|
||||
OSS_Psoj(&(*getStarredStruct)->songs[i].coverArt, array_song_root, "coverArt");
|
||||
OSS_Ploj(&(*getStarredStruct)->songs[i].size, array_song_root, "size");
|
||||
OSS_Psoj(&(*getStarredStruct)->songs[i].starred, array_song_root, "starred");
|
||||
OSS_Ploj(&(*getStarredStruct)->songs[i].duration, array_song_root, "duration");
|
||||
OSS_Pioj(&(*getStarredStruct)->songs[i].bitRate, array_song_root, "bitRate");
|
||||
OSS_Pioj(&(*getStarredStruct)->songs[i].bitDepth, array_song_root, "bitDepth");
|
||||
OSS_Ploj(&(*getStarredStruct)->songs[i].samplingRate, array_song_root, "samplingRate");
|
||||
OSS_Pioj(&(*getStarredStruct)->songs[i].channelCount, array_song_root, "channelCount");
|
||||
OSS_Pioj(&(*getStarredStruct)->songs[i].playCount, array_song_root, "playCount");
|
||||
OSS_Pioj(&(*getStarredStruct)->songs[i].discNumber, array_song_root, "discNumber");
|
||||
OSS_Psoj(&(*getStarredStruct)->songs[i].created, array_song_root, "created");
|
||||
OSS_Psoj(&(*getStarredStruct)->songs[i].albumId, array_song_root, "albumId");
|
||||
OSS_Psoj(&(*getStarredStruct)->songs[i].artistId, array_song_root, "artistId");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No starred songs
|
||||
(*getStarredStruct)->songCount = 0;
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void opensubsonic_getStarred_struct_free(opensubsonic_getStarred_struct** getStarredStruct) {
|
||||
logger_log_general(__func__, "Freeing /getStarred endpoint heap objects.");
|
||||
if ((*getStarredStruct)->status != NULL) { free((*getStarredStruct)->status); }
|
||||
if ((*getStarredStruct)->errorMessage != NULL) { free((*getStarredStruct)->errorMessage); }
|
||||
for (int i = 0; i < (*getStarredStruct)->artistCount; i++) {
|
||||
if ((*getStarredStruct)->artists[i].id != NULL) { free((*getStarredStruct)->artists[i].id); }
|
||||
if ((*getStarredStruct)->artists[i].name != NULL) { free((*getStarredStruct)->artists[i].name); }
|
||||
if ((*getStarredStruct)->artists[i].coverArt != NULL) { free((*getStarredStruct)->artists[i].coverArt); }
|
||||
if ((*getStarredStruct)->artists[i].starred != NULL) { free((*getStarredStruct)->artists[i].starred); }
|
||||
}
|
||||
for (int i = 0; i < (*getStarredStruct)->albumCount; i++) {
|
||||
if ((*getStarredStruct)->albums[i].id != NULL) { free((*getStarredStruct)->albums[i].id); }
|
||||
if ((*getStarredStruct)->albums[i].parent != NULL) { free((*getStarredStruct)->albums[i].parent); }
|
||||
if ((*getStarredStruct)->albums[i].album != NULL) { free((*getStarredStruct)->albums[i].album); }
|
||||
if ((*getStarredStruct)->albums[i].title != NULL) { free((*getStarredStruct)->albums[i].title); }
|
||||
if ((*getStarredStruct)->albums[i].name != NULL) { free((*getStarredStruct)->albums[i].name); }
|
||||
if ((*getStarredStruct)->albums[i].coverArt != NULL) { free((*getStarredStruct)->albums[i].coverArt); }
|
||||
if ((*getStarredStruct)->albums[i].created != NULL) { free((*getStarredStruct)->albums[i].created); }
|
||||
if ((*getStarredStruct)->albums[i].artistId != NULL) { free((*getStarredStruct)->albums[i].artistId); }
|
||||
if ((*getStarredStruct)->albums[i].artist != NULL) { free((*getStarredStruct)->albums[i].artist); }
|
||||
if ((*getStarredStruct)->albums[i].genre != NULL) { free((*getStarredStruct)->albums[i].genre); }
|
||||
}
|
||||
for (int i = 0; i < (*getStarredStruct)->songCount; i++) {
|
||||
if ((*getStarredStruct)->songs[i].id != NULL) { free((*getStarredStruct)->songs[i].id); }
|
||||
if ((*getStarredStruct)->songs[i].parent != NULL) { free((*getStarredStruct)->songs[i].parent); }
|
||||
if ((*getStarredStruct)->songs[i].title != NULL) { free((*getStarredStruct)->songs[i].title); }
|
||||
if ((*getStarredStruct)->songs[i].album != NULL) { free((*getStarredStruct)->songs[i].album); }
|
||||
if ((*getStarredStruct)->songs[i].artist != NULL) { free((*getStarredStruct)->songs[i].artist); }
|
||||
if ((*getStarredStruct)->songs[i].coverArt != NULL) { free((*getStarredStruct)->songs[i].coverArt); }
|
||||
if ((*getStarredStruct)->songs[i].starred != NULL) { free((*getStarredStruct)->songs[i].starred); }
|
||||
if ((*getStarredStruct)->songs[i].created != NULL) { free((*getStarredStruct)->songs[i].created); }
|
||||
if ((*getStarredStruct)->songs[i].albumId != NULL) { free((*getStarredStruct)->songs[i].albumId); }
|
||||
if ((*getStarredStruct)->songs[i].artistId != NULL) { free((*getStarredStruct)->songs[i].artistId); }
|
||||
}
|
||||
if ((*getStarredStruct)->artists != NULL) { free((*getStarredStruct)->artists); }
|
||||
if ((*getStarredStruct)->albums != NULL) { free((*getStarredStruct)->albums); }
|
||||
if ((*getStarredStruct)->songs != NULL) { free((*getStarredStruct)->songs); }
|
||||
if (*getStarredStruct != NULL) { free(*getStarredStruct); }
|
||||
}
|
||||
71
src/libopensubsonic/endpoint_getStarred.h
Normal file
71
src/libopensubsonic/endpoint_getStarred.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef _ENDPOINT_GETSTARRED_H
|
||||
#define _ENDPOINT_GETSTARRED_H
|
||||
|
||||
typedef struct {
|
||||
char* id;
|
||||
char* name;
|
||||
char* coverArt;
|
||||
char* starred;
|
||||
} opensubsonic_getStarred_artist_struct;
|
||||
|
||||
typedef struct {
|
||||
char* id;
|
||||
char* parent;
|
||||
char* album;
|
||||
char* title;
|
||||
char* name;
|
||||
// 'isDir' excluded
|
||||
char* coverArt;
|
||||
int songCount; // Do not rely on this
|
||||
char* created;
|
||||
long duration;
|
||||
int playCount;
|
||||
char* artistId;
|
||||
char* artist;
|
||||
int year;
|
||||
char* genre;
|
||||
} opensubsonic_getStarred_album_struct;
|
||||
|
||||
typedef struct {
|
||||
char* id;
|
||||
char* parent;
|
||||
// 'isDir' excluded
|
||||
char* title;
|
||||
char* album;
|
||||
char* artist;
|
||||
int track;
|
||||
int year;
|
||||
char* coverArt;
|
||||
long size;
|
||||
// 'contentType', 'suffix' excluded
|
||||
char* starred;
|
||||
long duration;
|
||||
int bitRate;
|
||||
int bitDepth;
|
||||
long samplingRate;
|
||||
int channelCount;
|
||||
// 'path' excluded
|
||||
int playCount;
|
||||
int discNumber;
|
||||
char* created;
|
||||
char* albumId;
|
||||
char* artistId;
|
||||
// 'type', 'isVideo' excluded
|
||||
} opensubsonic_getStarred_song_struct;
|
||||
|
||||
typedef struct {
|
||||
char* status;
|
||||
int errorCode;
|
||||
char* errorMessage;
|
||||
int artistCount;
|
||||
int albumCount;
|
||||
int songCount;
|
||||
opensubsonic_getStarred_artist_struct* artists;
|
||||
opensubsonic_getStarred_album_struct* albums;
|
||||
opensubsonic_getStarred_song_struct* songs;
|
||||
} opensubsonic_getStarred_struct;
|
||||
|
||||
int opensubsonic_getStarred_parse(char* data, opensubsonic_getStarred_struct** getStarredStruct);
|
||||
void opensubsonic_getStarred_struct_free(opensubsonic_getStarred_struct** getStarredStruct);
|
||||
|
||||
#endif
|
||||
73
src/libopensubsonic/endpoint_ping.c
Normal file
73
src/libopensubsonic/endpoint_ping.c
Normal file
@@ -0,0 +1,73 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_ping.h"
|
||||
|
||||
// Parse the JSON returned from the /rest/ping endpoint
|
||||
// Returns 1 if failure occured, else 0
|
||||
int opensubsonic_ping_parse(char* data, opensubsonic_ping_struct** pingStruct) {
|
||||
// Allocate on the heap
|
||||
*pingStruct = (opensubsonic_ping_struct*)malloc(sizeof(opensubsonic_ping_struct));
|
||||
|
||||
// Initialize struct variables
|
||||
(*pingStruct)->status = NULL;
|
||||
(*pingStruct)->version = NULL;
|
||||
(*pingStruct)->serverType = NULL;
|
||||
(*pingStruct)->serverVersion = NULL;
|
||||
(*pingStruct)->openSubsonicCapable = false;
|
||||
(*pingStruct)->error = false;
|
||||
(*pingStruct)->errorCode = 0;
|
||||
(*pingStruct)->errorMessage = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Psoj(&(*pingStruct)->status, subsonic_root, "status");
|
||||
OSS_Psoj(&(*pingStruct)->version, subsonic_root, "version");
|
||||
OSS_Psoj(&(*pingStruct)->serverType, subsonic_root, "type");
|
||||
OSS_Psoj(&(*pingStruct)->serverVersion, subsonic_root, "serverVersion");
|
||||
OSS_Pboj(&(*pingStruct)->openSubsonicCapable, subsonic_root, "openSubsonic");
|
||||
|
||||
// Check if an error is present
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
// Error did not occur, return
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
(*pingStruct)->error = true;
|
||||
|
||||
// From this point on, error has occured, capture error information
|
||||
OSS_Pioj(&(*pingStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*pingStruct)->errorMessage, subsonic_error, "message");
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*pingStruct)->errorCode, (*pingStruct)->errorMessage);
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Free the dynamically allocated elements of the opensubsonic_ping_struct structure
|
||||
void opensubsonic_ping_struct_free(opensubsonic_ping_struct** pingStruct) {
|
||||
logger_log_general(__func__, "Freeing /ping endpoint heap objects.");
|
||||
if ((*pingStruct)->status != NULL) { free((*pingStruct)->status); }
|
||||
if ((*pingStruct)->version != NULL) { free((*pingStruct)->version); }
|
||||
if ((*pingStruct)->serverType != NULL) { free((*pingStruct)->serverType); }
|
||||
if ((*pingStruct)->serverVersion != NULL) { free((*pingStruct)->serverVersion); }
|
||||
if ((*pingStruct)->errorMessage != NULL) { free((*pingStruct)->errorMessage); }
|
||||
if (*pingStruct != NULL) { free(*pingStruct); }
|
||||
}
|
||||
20
src/libopensubsonic/endpoint_ping.h
Normal file
20
src/libopensubsonic/endpoint_ping.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef _ENDPOINT_PING_H
|
||||
#define _ENDPOINT_PING_H
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
char* status;
|
||||
char* version;
|
||||
char* serverType;
|
||||
char* serverVersion;
|
||||
bool openSubsonicCapable;
|
||||
bool error;
|
||||
int errorCode;
|
||||
char* errorMessage;
|
||||
} opensubsonic_ping_struct;
|
||||
|
||||
int opensubsonic_ping_parse(char* data, opensubsonic_ping_struct** pingStruct);
|
||||
void opensubsonic_ping_struct_free(opensubsonic_ping_struct** pingStruct);
|
||||
|
||||
#endif
|
||||
58
src/libopensubsonic/endpoint_scrobble.c
Normal file
58
src/libopensubsonic/endpoint_scrobble.c
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
#include "endpoint_scrobble.h"
|
||||
|
||||
// Returns 1 if failure occured, else 0
|
||||
int opensubsonic_scrobble_parse(char* data, opensubsonic_scrobble_struct** scrobbleStruct) {
|
||||
// Allocate and initialize
|
||||
(*scrobbleStruct) = (opensubsonic_scrobble_struct*)malloc(sizeof(opensubsonic_scrobble_struct));
|
||||
(*scrobbleStruct)->status = NULL;
|
||||
(*scrobbleStruct)->errorCode = 0;
|
||||
(*scrobbleStruct)->errorMessage = NULL;
|
||||
|
||||
// Parse the JSON
|
||||
cJSON* root = cJSON_Parse(data);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make an object from subsonic-response
|
||||
cJSON* subsonic_root = cJSON_GetObjectItemCaseSensitive(root, "subsonic-response");
|
||||
if (subsonic_root == NULL) {
|
||||
logger_log_error(__func__, "Error handling JSON - subsonic-response does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check for error
|
||||
OSS_Psoj(&(*scrobbleStruct)->status, subsonic_root, "status");
|
||||
if (strstr((*scrobbleStruct)->status, "ok") == NULL) {
|
||||
cJSON* subsonic_error = cJSON_GetObjectItemCaseSensitive(subsonic_root, "error");
|
||||
if (subsonic_error == NULL) {
|
||||
logger_log_error(__func__, "API has indicated failure through status, but error does not exist.");
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
OSS_Pioj(&(*scrobbleStruct)->errorCode, subsonic_error, "code");
|
||||
OSS_Psoj(&(*scrobbleStruct)->errorMessage, subsonic_error, "message");
|
||||
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*scrobbleStruct)->errorCode, (*scrobbleStruct)->errorMessage);
|
||||
cJSON_Delete(root);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void opensubsonic_scrobble_struct_free(opensubsonic_scrobble_struct** scrobbleStruct) {
|
||||
logger_log_general(__func__, "Freeing /scrobble endpoint heap objects.");
|
||||
if ((*scrobbleStruct)->status != NULL) { free((*scrobbleStruct)->status); }
|
||||
if ((*scrobbleStruct)->errorMessage != NULL) { free((*scrobbleStruct)->errorMessage); }
|
||||
if (*scrobbleStruct != NULL) { free(*scrobbleStruct); }
|
||||
}
|
||||
13
src/libopensubsonic/endpoint_scrobble.h
Normal file
13
src/libopensubsonic/endpoint_scrobble.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef _ENDPOINT_SCROBBLE_H
|
||||
#define _ENDPOINT_SCROBBLE_H
|
||||
|
||||
typedef struct {
|
||||
char* status;
|
||||
int errorCode;
|
||||
char* errorMessage;
|
||||
} opensubsonic_scrobble_struct;
|
||||
|
||||
int opensubsonic_scrobble_parse(char* data, opensubsonic_scrobble_struct** scrobbleStruct);
|
||||
void opensubsonic_scrobble_struct_free(opensubsonic_scrobble_struct** scrobbleStruct);
|
||||
|
||||
#endif
|
||||
428
src/libopensubsonic/httpclient.c
Normal file
428
src/libopensubsonic/httpclient.c
Normal file
@@ -0,0 +1,428 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <curl/curl.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "httpclient.h"
|
||||
#include "logger.h"
|
||||
#include "../configHandler.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include "endpoint_ping.h"
|
||||
#include "endpoint_getStarred.h"
|
||||
#include "endpoint_getSong.h"
|
||||
#include "endpoint_getPlaylists.h"
|
||||
#include "endpoint_getPlaylist.h"
|
||||
#include "endpoint_getArtists.h"
|
||||
#include "endpoint_getArtist.h"
|
||||
#include "endpoint_getLyricsBySongId.h"
|
||||
#include "endpoint_getAlbumList.h"
|
||||
#include "endpoint_getAlbum.h"
|
||||
#include "endpoint_scrobble.h"
|
||||
|
||||
static int rc = 0;
|
||||
extern configHandler_config_t* configObj;
|
||||
|
||||
void opensubsonic_httpClient_URL_prepare(opensubsonic_httpClient_URL_t** urlObj) {
|
||||
// Initialize struct variables
|
||||
(*urlObj)->endpoint = 0;
|
||||
(*urlObj)->id = NULL;
|
||||
(*urlObj)->type = 0;
|
||||
(*urlObj)->amount = 0;
|
||||
(*urlObj)->submit = false;
|
||||
(*urlObj)->formedUrl = NULL;
|
||||
}
|
||||
|
||||
void opensubsonic_httpClient_URL_cleanup(opensubsonic_httpClient_URL_t** urlObj) {
|
||||
if ((*urlObj)->formedUrl != NULL) { free((*urlObj)->formedUrl); }
|
||||
if ((*urlObj)->id != NULL) { free((*urlObj)->id); }
|
||||
if (*urlObj != NULL) { free(*urlObj); }
|
||||
}
|
||||
|
||||
void opensubsonic_httpClient_formUrl(opensubsonic_httpClient_URL_t** urlObj) {
|
||||
// TODO fix hack, add error checking,
|
||||
|
||||
char* url = NULL;
|
||||
|
||||
switch ((*urlObj)->endpoint) {
|
||||
case OPENSUBSONIC_ENDPOINT_PING:
|
||||
rc = asprintf(&url, "%s://%s/rest/ping?u=%s&t=%s&s=%s&f=json&v=%s&c=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETSTARRED:
|
||||
rc = asprintf(&url, "%s://%s/rest/getStarred?u=%s&t=%s&s=%s&f=json&v=%s&c=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETSONG:
|
||||
if ((*urlObj)->id == NULL) {
|
||||
logger_log_error(__func__, "(getSong) ID is null.");
|
||||
// TODO handle error
|
||||
break;
|
||||
}
|
||||
rc = asprintf(&url, "%s://%s/rest/getSong?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||
(*urlObj)->id);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_STREAM: // Does not have a fetchResponse counterpart
|
||||
if ((*urlObj)->id == NULL) {
|
||||
logger_log_error(__func__, "(stream) ID is null.");
|
||||
// TODO handle error
|
||||
break;
|
||||
}
|
||||
rc = asprintf(&url, "%s://%s/rest/stream?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||
(*urlObj)->id);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETCOVERART: // Does not have a fetchResponse counterpart
|
||||
if ((*urlObj)->id == NULL) {
|
||||
logger_log_error(__func__, "(getCoverArt) ID is null.");
|
||||
// TODO handle error
|
||||
break;
|
||||
}
|
||||
rc = asprintf(&url, "%s://%s/rest/getCoverArt?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||
(*urlObj)->id);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETALBUM:
|
||||
if ((*urlObj)->id == NULL) {
|
||||
logger_log_error(__func__, "(getAlbum) ID is null.");
|
||||
// TODO handle error
|
||||
break;
|
||||
}
|
||||
rc = asprintf(&url, "%s://%s/rest/getAlbum?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||
(*urlObj)->id);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETPLAYLISTS:
|
||||
rc = asprintf(&url, "%s://%s/rest/getPlaylists?u=%s&t=%s&s=%s&f=json&v=%s&c=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETPLAYLIST:
|
||||
if ((*urlObj)->id == NULL) {
|
||||
logger_log_error(__func__, "(getPlaylist) ID is null.");
|
||||
// TODO handle error
|
||||
break;
|
||||
}
|
||||
rc = asprintf(&url, "%s://%s/rest/getPlaylist?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||
(*urlObj)->id);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETARTISTS:
|
||||
rc = asprintf(&url, "%s://%s/rest/getArtists?u=%s&t=%s&s=%s&f=json&v=%s&c=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETARTIST:
|
||||
if ((*urlObj)->id == NULL) {
|
||||
logger_log_error(__func__, "(getArtist) ID is null.");
|
||||
// TODO handle error
|
||||
break;
|
||||
}
|
||||
rc = asprintf(&url, "%s://%s/rest/getArtist?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||
(*urlObj)->id);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETLYRICSBYSONGID:
|
||||
if ((*urlObj)->id == NULL) {
|
||||
logger_log_error(__func__, "(getArtist) ID is null.");
|
||||
// TODO handle error
|
||||
break;
|
||||
}
|
||||
rc = asprintf(&url, "%s://%s/rest/getLyricsBySongId?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||
(*urlObj)->id);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST:
|
||||
if ((*urlObj)->type == 0) {
|
||||
logger_log_error(__func__, "(getAlbumList) Type is 0.");
|
||||
// TODO handle error
|
||||
break;
|
||||
}
|
||||
if ((*urlObj)->amount == 0) {
|
||||
logger_log_error(__func__, "(getAlbumList) Amount is 0.");
|
||||
// TODO handle error
|
||||
break;
|
||||
}
|
||||
|
||||
char* typeString = NULL;
|
||||
switch ((*urlObj)->type) {
|
||||
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RANDOM:
|
||||
rc = asprintf(&typeString, "random");
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST_NEWEST:
|
||||
rc = asprintf(&typeString, "newest");
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST_HIGHEST:
|
||||
rc = asprintf(&typeString, "highest");
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST_FREQUENT:
|
||||
rc = asprintf(&typeString, "frequent");
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RECENT:
|
||||
rc = asprintf(&typeString, "recent");
|
||||
break;
|
||||
default:
|
||||
logger_log_error(__func__, "(getAlbumList) Unknown type requested.");
|
||||
// TODO handle error
|
||||
}
|
||||
|
||||
rc = asprintf(&url, "%s://%s/rest/getAlbumList?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&type=%s&size=%d",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||
typeString, (*urlObj)->amount);
|
||||
free(typeString);
|
||||
break;
|
||||
case OPENSUBSONIC_ENDPOINT_SCROBBLE:
|
||||
if ((*urlObj)->id == NULL) {
|
||||
logger_log_error(__func__, "(scrobble) ID is null.");
|
||||
// TODO handle error
|
||||
break;
|
||||
}
|
||||
|
||||
char* submitString = NULL;
|
||||
if ((*urlObj)->submit) {
|
||||
rc = asprintf(&submitString, "true");
|
||||
} else {
|
||||
rc = asprintf(&submitString, "false");
|
||||
}
|
||||
rc = asprintf(&url, "%s://%s/rest/scrobble?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s&submission=%s",
|
||||
configObj->opensubsonic_protocol, configObj->opensubsonic_server, configObj->opensubsonic_username,
|
||||
configObj->internal_opensubsonic_loginToken, configObj->internal_opensubsonic_loginSalt,
|
||||
configObj->internal_opensubsonic_version, configObj->internal_opensubsonic_clientName,
|
||||
(*urlObj)->id, submitString);
|
||||
free(submitString);
|
||||
break;
|
||||
default:
|
||||
logger_log_error(__func__, "Unknown endpoint requested.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() error.");
|
||||
// TODO handle error
|
||||
}
|
||||
|
||||
// HACK
|
||||
(*urlObj)->formedUrl = strdup(url); free(url);
|
||||
}
|
||||
|
||||
void opensubsonic_httpClient_fetchResponse(opensubsonic_httpClient_URL_t** urlObj, void** responseObj) {
|
||||
// Make and prepare HTTP object
|
||||
opensubsonic_httpClientRequest_t* httpReq;
|
||||
opensubsonic_httpClient_prepareRequest(&httpReq);
|
||||
httpReq->method = HTTP_METHOD_GET;
|
||||
httpReq->requestUrl = strdup((*urlObj)->formedUrl);
|
||||
opensubsonic_httpClient_request(&httpReq);
|
||||
|
||||
// Cannot use a switch statement here due to maintaining compatibility with < C23
|
||||
if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_PING) {
|
||||
opensubsonic_ping_struct** pingStruct = (opensubsonic_ping_struct**)responseObj;
|
||||
opensubsonic_ping_parse(httpReq->responseMsg, pingStruct);
|
||||
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETSTARRED) {
|
||||
opensubsonic_getStarred_struct** getStarredStruct = (opensubsonic_getStarred_struct**)responseObj;
|
||||
opensubsonic_getStarred_parse(httpReq->responseMsg, getStarredStruct);
|
||||
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETSONG) {
|
||||
opensubsonic_getSong_struct** getSongStruct = (opensubsonic_getSong_struct**)responseObj;
|
||||
opensubsonic_getSong_parse(httpReq->responseMsg, getSongStruct);
|
||||
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETALBUM) {
|
||||
opensubsonic_getAlbum_struct** getAlbumStruct = (opensubsonic_getAlbum_struct**)responseObj;
|
||||
opensubsonic_getAlbum_parse(httpReq->responseMsg, getAlbumStruct);
|
||||
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETPLAYLISTS) {
|
||||
opensubsonic_getPlaylists_struct** getPlaylistsStruct = (opensubsonic_getPlaylists_struct**)responseObj;
|
||||
opensubsonic_getPlaylists_parse(httpReq->responseMsg, getPlaylistsStruct);
|
||||
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETPLAYLIST) {
|
||||
opensubsonic_getPlaylist_struct** getPlaylistStruct = (opensubsonic_getPlaylist_struct**)responseObj;
|
||||
opensubsonic_getPlaylist_parse(httpReq->responseMsg, getPlaylistStruct);
|
||||
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETARTISTS) {
|
||||
opensubsonic_getArtists_struct** getArtistsStruct = (opensubsonic_getArtists_struct**)responseObj;
|
||||
opensubsonic_getArtists_parse(httpReq->responseMsg, getArtistsStruct);
|
||||
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETARTIST) {
|
||||
opensubsonic_getArtist_struct** getArtistStruct = (opensubsonic_getArtist_struct**)responseObj;
|
||||
opensubsonic_getArtist_parse(httpReq->responseMsg, getArtistStruct);
|
||||
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETLYRICSBYSONGID) {
|
||||
opensubsonic_getLyricsBySongId_struct** getLyricsBySongIdStruct = (opensubsonic_getLyricsBySongId_struct**)responseObj;
|
||||
opensubsonic_getLyricsBySongId_parse(httpReq->responseMsg, getLyricsBySongIdStruct);
|
||||
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETALBUMLIST) {
|
||||
opensubsonic_getAlbumList_struct** getAlbumListStruct = (opensubsonic_getAlbumList_struct**)responseObj;
|
||||
opensubsonic_getAlbumList_parse(httpReq->responseMsg, getAlbumListStruct);
|
||||
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_SCROBBLE) {
|
||||
opensubsonic_scrobble_struct** scrobbleStruct = (opensubsonic_scrobble_struct**)responseObj;
|
||||
opensubsonic_scrobble_parse(httpReq->responseMsg, scrobbleStruct);
|
||||
} else {
|
||||
logger_log_error(__func__, "Unknown endpoint requested.");
|
||||
}
|
||||
|
||||
// Cleanup HTTP object
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Contact the /rest/getAlbum endpoint
|
||||
int opensubsonic_getAlbum(const char* protocol_ptr, const char* server_ptr, const char* user_ptr, char* login_token_ptr, char* login_salt_ptr, const char* opensubsonic_version_ptr, const char* client_name_ptr, char* id, char** response) {
|
||||
// Generate full URL, perform HTTP GET, and free the full URL
|
||||
int rc = 0;
|
||||
char* full_url = malloc(256);
|
||||
snprintf(full_url, 256, "%s://%s/rest/getAlbum?u=%s&t=%s&s=%s&f=json&v=%s&c=%s&id=%s", protocol_ptr, server_ptr, user_ptr, login_token_ptr, login_salt_ptr, opensubsonic_version_ptr, client_name_ptr, id);
|
||||
//rc = opensubsonic_http_json_get(full_url, response);
|
||||
free(full_url);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
// TODO COVER ART - Returns JSON on error.
|
||||
// {"subsonic-response":{"status":"failed","version":"1.16.1","type":"navidrome","serverVersion":"0.53.1-FREEBSD (1ba390a)","openSubsonic":true,"error":{"code":70,"message":"Artwork not found"}}}
|
||||
// Contact the /rest/getCoverArt endpoint (Returns binary data)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Functions for preparing / freeing a HTTP Request struct
|
||||
void opensubsonic_httpClient_prepareRequest(opensubsonic_httpClientRequest_t** httpReq) {
|
||||
// Allocate struct
|
||||
*httpReq = (opensubsonic_httpClientRequest_t*)malloc(sizeof(opensubsonic_httpClientRequest_t));
|
||||
|
||||
// Initialize struct variables
|
||||
(*httpReq)->requestUrl = NULL;
|
||||
(*httpReq)->requestBody = NULL;
|
||||
(*httpReq)->method = 0;
|
||||
(*httpReq)->isBodyRequired = false;
|
||||
(*httpReq)->scrobbler = 0;
|
||||
(*httpReq)->responseCode = 0;
|
||||
(*httpReq)->responseMsg = NULL;
|
||||
}
|
||||
|
||||
void opensubsonic_httpClient_cleanup(opensubsonic_httpClientRequest_t** httpReq) {
|
||||
// Free heap-allocated struct variables
|
||||
if ((*httpReq)->requestUrl != NULL) { free((*httpReq)->requestUrl); }
|
||||
if ((*httpReq)->requestBody != NULL) { free((*httpReq)->requestBody); }
|
||||
if ((*httpReq)->responseMsg != NULL) { free((*httpReq)->responseMsg); }
|
||||
|
||||
// Free struct
|
||||
free(*httpReq);
|
||||
}
|
||||
|
||||
// Perform HTTP POST for Scrobbling (This function is a wrapper around OS-specific networking functions)
|
||||
int opensubsonic_httpClient_request(opensubsonic_httpClientRequest_t** httpReq) {
|
||||
logger_log_general(__func__, "Performing HTTP Request.");
|
||||
//#if defined(__APPLE__) && defined(__MACH__)
|
||||
//XNU_HttpRequest(httpReq);
|
||||
//#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
UNIX_HttpRequest(httpReq);
|
||||
//#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct memory {
|
||||
char *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static size_t write_to_memory(void *ptr, size_t size, size_t nmemb, void *userdata) {
|
||||
struct memory *mem = (struct memory *)userdata;
|
||||
size_t total_size = size * nmemb;
|
||||
|
||||
mem->data = realloc(mem->data, mem->size + total_size + 1);
|
||||
if (!mem->data) return 0; // Fail on OOM
|
||||
|
||||
memcpy(&(mem->data[mem->size]), ptr, total_size);
|
||||
mem->size += total_size;
|
||||
mem->data[mem->size] = '\0'; // Null-terminate
|
||||
|
||||
return total_size;
|
||||
}
|
||||
|
||||
void UNIX_HttpRequest(opensubsonic_httpClientRequest_t** httpReq) {
|
||||
CURL* curl_handle = curl_easy_init();
|
||||
struct curl_slist* header_list = NULL;
|
||||
struct memory chunk = {0};
|
||||
long httpCode = 0;
|
||||
|
||||
if (curl_handle) {
|
||||
// Set method
|
||||
if ((*httpReq)->method == HTTP_METHOD_GET) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "GET");
|
||||
} else if ((*httpReq)->method == HTTP_METHOD_POST) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "POST");
|
||||
if ((*httpReq)->isBodyRequired == false) {
|
||||
header_list = curl_slist_append(header_list, "Content-Length: 0");
|
||||
}
|
||||
}
|
||||
|
||||
// Set scrobbler information
|
||||
if ((*httpReq)->scrobbler == SCROBBLER_LISTENBRAINZ && (*httpReq)->method == HTTP_METHOD_POST) {
|
||||
header_list = curl_slist_append(header_list, "Content-Type: application/json");
|
||||
char* authString = NULL;
|
||||
rc = asprintf(&authString, "Authorization: Token %s", configObj->listenbrainz_token);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() error.");
|
||||
// TODO handle error
|
||||
}
|
||||
printf("CODE: %s\n", authString);
|
||||
header_list = curl_slist_append(header_list, authString); // TODO Check does this copy the string?
|
||||
// TODO free auth string
|
||||
}
|
||||
|
||||
if ((*httpReq)->isBodyRequired == true && (*httpReq)->scrobbler == 0) {
|
||||
header_list = curl_slist_append(header_list, "X-Organization: Hojuix");
|
||||
header_list = curl_slist_append(header_list, "X-Application: OSSP");
|
||||
}
|
||||
|
||||
if (header_list != NULL) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, header_list);
|
||||
}
|
||||
|
||||
if ((*httpReq)->isBodyRequired == true) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, (*httpReq)->requestBody);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, (long)strlen((*httpReq)->requestBody));
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "OSSP/1.0 (avery@hojuix.org)");
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, (*httpReq)->requestUrl);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 50L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_TCP_KEEPALIVE,0L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_to_memory);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
|
||||
CURLcode res = curl_easy_perform(curl_handle);
|
||||
curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl_handle);
|
||||
|
||||
(*httpReq)->responseMsg = strdup(chunk.data);
|
||||
(*httpReq)->responseCode = (int)httpCode;
|
||||
free(chunk.data);
|
||||
}
|
||||
75
src/libopensubsonic/httpclient.h
Normal file
75
src/libopensubsonic/httpclient.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#ifndef _HTTPCLIENT_H
|
||||
#define _HTTPCLIENT_H
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define SCROBBLER_LISTENBRAINZ 101
|
||||
#define SCROBBLER_LASTFM 102
|
||||
#define HTTP_METHOD_GET 201
|
||||
#define HTTP_METHOD_POST 202
|
||||
#define HTTP_CODE_SUCCESS 200
|
||||
#define HTTP_CODE_NOT_AUTHORIZED 403
|
||||
#define OPENSUBSONIC_ENDPOINT_PING 301
|
||||
#define OPENSUBSONIC_ENDPOINT_GETSTARRED 302
|
||||
#define OPENSUBSONIC_ENDPOINT_GETSONG 303
|
||||
#define OPENSUBSONIC_ENDPOINT_STREAM 304
|
||||
#define OPENSUBSONIC_ENDPOINT_GETCOVERART 305
|
||||
#define OPENSUBSONIC_ENDPOINT_GETALBUM 306
|
||||
#define OPENSUBSONIC_ENDPOINT_GETPLAYLISTS 307
|
||||
#define OPENSUBSONIC_ENDPOINT_GETPLAYLIST 308
|
||||
#define OPENSUBSONIC_ENDPOINT_GETARTISTS 309
|
||||
#define OPENSUBSONIC_ENDPOINT_GETARTIST 310
|
||||
#define OPENSUBSONIC_ENDPOINT_GETLYRICSBYSONGID 311
|
||||
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST 312
|
||||
#define OPENSUBSONIC_ENDPOINT_SCROBBLE 313
|
||||
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RANDOM 501
|
||||
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_NEWEST 502
|
||||
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_HIGHEST 503
|
||||
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_FREQUENT 504
|
||||
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RECENT 505
|
||||
|
||||
typedef struct {
|
||||
int endpoint; // Endpoint
|
||||
char* id; // ID
|
||||
int type; // Type of request (As used in the /getAlbumList endpoint)
|
||||
int amount; // Amount of items to return (Also as used in the /getAlbumList endpoint)
|
||||
bool submit; // Submit scrobble (used for the /scrobble endpoint)
|
||||
char* formedUrl; // Final URL
|
||||
} opensubsonic_httpClient_URL_t; // Forms authenticated URLs with required parameters
|
||||
|
||||
typedef struct {
|
||||
// Request Information
|
||||
char* requestUrl;
|
||||
char* requestBody;
|
||||
int method;
|
||||
bool isBodyRequired;
|
||||
int scrobbler;
|
||||
|
||||
// Response Information
|
||||
int responseCode;
|
||||
char* responseMsg;
|
||||
} opensubsonic_httpClientRequest_t; // OS-agnostic HTTP interface
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
void UNIX_HttpRequest(opensubsonic_httpClientRequest_t** httpReq);
|
||||
#endif
|
||||
|
||||
void opensubsonic_httpClient_URL_prepare(opensubsonic_httpClient_URL_t** urlObj);
|
||||
void opensubsonic_httpClient_URL_cleanup(opensubsonic_httpClient_URL_t** urlObj);
|
||||
void opensubsonic_httpClient_formUrl(opensubsonic_httpClient_URL_t** urlObj);
|
||||
void opensubsonic_httpClient_fetchResponse(opensubsonic_httpClient_URL_t** urlObj, void** responseObj);
|
||||
void opensubsonic_httpClient_prepareRequest(opensubsonic_httpClientRequest_t** httpReq);
|
||||
void opensubsonic_httpClient_cleanup(opensubsonic_httpClientRequest_t** httpReq);
|
||||
int opensubsonic_httpClient_request(opensubsonic_httpClientRequest_t** httpReq);
|
||||
|
||||
void UNIX_HttpRequest(opensubsonic_httpClientRequest_t** httpReq);
|
||||
|
||||
// DEPRECATED - TO BE REMOVED SOON - APART OF THE OLD INFRASTRUCTURE
|
||||
typedef struct {
|
||||
char* memory;
|
||||
size_t size;
|
||||
} binary_response_struct;
|
||||
|
||||
int opensubsonic_getAlbum(const char* protocol_ptr, const char* server_ptr, const char* user_ptr, char* login_token_ptr, char* login_salt_ptr, const char* opensubsonic_version_ptr, const char* client_name_ptr, char* id, char** response);
|
||||
|
||||
#endif
|
||||
74
src/libopensubsonic/logger.c
Normal file
74
src/libopensubsonic/logger.c
Normal file
@@ -0,0 +1,74 @@
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include "logger.h"
|
||||
|
||||
#define LOGGER_COLOR_BLACK "\033[0;30m"
|
||||
#define LOGGER_COLOR_RED "\033[0;31m"
|
||||
#define LOGGER_COLOR_GREEN "\033[0;32m"
|
||||
#define LOGGER_COLOR_YELLOW "\033[0;33m"
|
||||
#define LOGGER_COLOR_BLUE "\033[0;34m"
|
||||
#define LOGGER_COLOR_PURPLE "\033[0;35m"
|
||||
#define LOGGER_COLOR_CYAN "\033[0;36m"
|
||||
#define LOGGER_COLOR_WHITE "\033[0;37m"
|
||||
|
||||
#define LOGGER_MODE_EVERYTHING 1
|
||||
|
||||
void logger_init(int mode) {
|
||||
printf("%s +\n", __func__);
|
||||
|
||||
printf("%s HAHAHA %s HAHAHA\n", LOGGER_COLOR_RED, LOGGER_COLOR_WHITE);
|
||||
|
||||
printf("%s -\n", __func__);
|
||||
}
|
||||
|
||||
// Log entry into / exit from function
|
||||
// Action 1 = Entry, Action 2 = Exit
|
||||
void logger_log_function(const char* function, int action) {
|
||||
if (action == 1) {
|
||||
printf("%s%s +%s\n", LOGGER_COLOR_YELLOW, function, LOGGER_COLOR_WHITE);
|
||||
} else if (action == 2) {
|
||||
printf("%s%s -%s\n", LOGGER_COLOR_YELLOW, function, LOGGER_COLOR_WHITE);
|
||||
} else {
|
||||
printf("%sERROR: Invalid logger action!%s\n", LOGGER_COLOR_RED, LOGGER_COLOR_WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
// Log error
|
||||
void logger_log_error(const char* function, const char* format, ...) {
|
||||
printf("%s(%s) ERROR - ", LOGGER_COLOR_RED, function);
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
vprintf(format, argptr);
|
||||
va_end(argptr);
|
||||
printf("%s\n", LOGGER_COLOR_WHITE);
|
||||
}
|
||||
|
||||
// Log general information
|
||||
void logger_log_general(const char* function, const char* format, ...) {
|
||||
printf("%s(%s) ", LOGGER_COLOR_YELLOW, function);
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
vprintf(format, argptr);
|
||||
va_end(argptr);
|
||||
printf("%s\n", LOGGER_COLOR_WHITE);
|
||||
}
|
||||
|
||||
// Log important information
|
||||
void logger_log_important(const char* function, const char* format, ...) {
|
||||
printf("%s(%s) ", LOGGER_COLOR_GREEN, function);
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
vprintf(format, argptr);
|
||||
va_end(argptr);
|
||||
printf("%s\n", LOGGER_COLOR_WHITE);
|
||||
}
|
||||
|
||||
// Log title
|
||||
void logger_log_title(const char* function, const char* format, ...) {
|
||||
printf("%s(%s) ", LOGGER_COLOR_PURPLE, function);
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
vprintf(format, argptr);
|
||||
va_end(argptr);
|
||||
printf("%s\n", LOGGER_COLOR_WHITE);
|
||||
}
|
||||
11
src/libopensubsonic/logger.h
Normal file
11
src/libopensubsonic/logger.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef _LOGGER_H
|
||||
#define _LOGGER_H
|
||||
|
||||
void logger_init(int mode);
|
||||
void logger_log_function(const char* function, int action);
|
||||
void logger_log_error(const char* function, const char* format, ...);
|
||||
void logger_log_general(const char* function, const char* format, ...);
|
||||
void logger_log_important(const char* function, const char* format, ...);
|
||||
void logger_log_title(const char* function, const char* format, ...);
|
||||
|
||||
#endif
|
||||
530
src/libopensubsonic/scrobble_lastFm.c
Normal file
530
src/libopensubsonic/scrobble_lastFm.c
Normal file
@@ -0,0 +1,530 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include "scrobble_lastFm.h"
|
||||
#include "logger.h"
|
||||
#include "httpclient.h"
|
||||
#include "../external/cJSON.h"
|
||||
#include "../external/md5.h"
|
||||
#include "../external/libcurl_uriescape.h"
|
||||
#include "../configHandler.h"
|
||||
#include "../DarwinHttpClient.h"
|
||||
#include "../configHandler.h"
|
||||
|
||||
const char* lastFmScrobbleURL = "https://ws.audioscrobbler.com/2.0/";
|
||||
static int rc = 0;
|
||||
extern configHandler_config_t* configObj;
|
||||
|
||||
/*
|
||||
* Authenticate with LastFM using the method.getMobileSession endpoint
|
||||
* Receives credentials from the config file, and returns the token as a heap allocated char*, or NULL if failure
|
||||
*/
|
||||
char* opensubsonic_authenticate_lastFm(void) {
|
||||
logger_log_general(__func__, "Attempting to authenticate with LastFM.");
|
||||
|
||||
// Check to make sure there are credentials present
|
||||
if (configObj->lastfm_username == NULL || configObj->lastfm_username[0] == '\0' ||
|
||||
configObj->lastfm_password == NULL || configObj->lastfm_password[0] == '\0' ||
|
||||
configObj->lastfm_api_key == NULL || configObj->lastfm_api_key[0] == '\0' ||
|
||||
configObj->lastfm_api_secret == NULL || configObj->lastfm_api_secret[0] == '\0') {
|
||||
logger_log_error(__func__, "LastFM Username/Password/API Key/API Secret is not in the config file.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Assemble the signature
|
||||
char* sig_plaintext = NULL;
|
||||
rc = asprintf(&sig_plaintext, "api_key%smethodauth.getMobileSessionpassword%susername%s%s",
|
||||
configObj->lastfm_api_key, configObj->lastfm_password,
|
||||
configObj->lastfm_username, configObj->lastfm_api_secret);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t sig_md5_bytes[16];
|
||||
char* sig_md5_text = NULL;
|
||||
md5String(sig_plaintext, sig_md5_bytes);
|
||||
free(sig_plaintext);
|
||||
rc = asprintf(&sig_md5_text, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
sig_md5_bytes[0], sig_md5_bytes[1], sig_md5_bytes[2], sig_md5_bytes[3],
|
||||
sig_md5_bytes[4], sig_md5_bytes[5], sig_md5_bytes[6], sig_md5_bytes[7],
|
||||
sig_md5_bytes[8], sig_md5_bytes[9], sig_md5_bytes[10], sig_md5_bytes[11],
|
||||
sig_md5_bytes[12], sig_md5_bytes[13], sig_md5_bytes[14], sig_md5_bytes[15]);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Assemble the payload
|
||||
char* payload = NULL;
|
||||
rc = asprintf(&payload, "%s?method=auth.getMobileSession&api_key=%s&username=%s&password=%s&api_sig=%s&format=json",
|
||||
lastFmScrobbleURL, configObj->lastfm_api_key, configObj->lastfm_username,
|
||||
configObj->lastfm_password, sig_md5_text);
|
||||
free(sig_md5_text);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Send payload and receive JSON response
|
||||
opensubsonic_httpClientRequest_t* httpReq;
|
||||
opensubsonic_httpClient_prepareRequest(&httpReq);
|
||||
|
||||
httpReq->requestUrl = strdup(payload);
|
||||
free(payload);
|
||||
httpReq->scrobbler = SCROBBLER_LASTFM;
|
||||
httpReq->method = HTTP_METHOD_POST;
|
||||
opensubsonic_httpClient_request(&httpReq);
|
||||
|
||||
if (httpReq->responseCode != HTTP_CODE_SUCCESS && httpReq->responseCode != HTTP_CODE_NOT_AUTHORIZED) {
|
||||
logger_log_error(__func__, "HTTP POST returned invalid code (%d).", httpReq->responseCode);
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
// Parse response JSON
|
||||
cJSON* root = cJSON_Parse(httpReq->responseMsg);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
if (httpReq->responseCode == HTTP_CODE_SUCCESS) {
|
||||
// Make an object from session
|
||||
cJSON* session_root = cJSON_GetObjectItemCaseSensitive(root, "session");
|
||||
if (session_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - session does not exist.");
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* name = cJSON_GetObjectItemCaseSensitive(session_root, "name");
|
||||
if (cJSON_IsString(name) && name->valuestring != NULL) {
|
||||
printf("Fetched username: %s\n", name->valuestring);
|
||||
}
|
||||
|
||||
cJSON* key = cJSON_GetObjectItemCaseSensitive(session_root, "key");
|
||||
if (cJSON_IsString(key) && key->valuestring != NULL) {
|
||||
printf("Fetched key: %s\n", key->valuestring);
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - key does not exist.");
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
} else if (httpReq->responseCode == HTTP_CODE_NOT_AUTHORIZED) {
|
||||
cJSON* error = cJSON_GetObjectItemCaseSensitive(root, "error");
|
||||
if (cJSON_IsNumber(error)) {
|
||||
int code = error->valueint;
|
||||
switch (code) {
|
||||
case 2:
|
||||
logger_log_error(__func__, "Invalid service (%d).", code);
|
||||
break;
|
||||
case 3:
|
||||
logger_log_error(__func__, "Invalid method (%d).", code);
|
||||
break;
|
||||
case 4:
|
||||
logger_log_error(__func__, "Auth error (%d).", code);
|
||||
break;
|
||||
case 5:
|
||||
logger_log_error(__func__, "Invalid format (%d).", code);
|
||||
break;
|
||||
case 6:
|
||||
logger_log_error(__func__, "Invalid parameters (%d).", code);
|
||||
break;
|
||||
case 7:
|
||||
logger_log_error(__func__, "Invalid resource (%d).", code);
|
||||
break;
|
||||
case 8:
|
||||
logger_log_error(__func__, "Operation failed (%d).", code);
|
||||
break;
|
||||
case 9:
|
||||
logger_log_error(__func__, "Invalid session key (%d).", code);
|
||||
break;
|
||||
case 10:
|
||||
logger_log_error(__func__, "Invalid API key (%d).", code);
|
||||
break;
|
||||
case 11:
|
||||
logger_log_error(__func__, "Service offline (%d).", code);
|
||||
break;
|
||||
case 13:
|
||||
logger_log_error(__func__, "Invalid method signature (%d).", code);
|
||||
break;
|
||||
case 16:
|
||||
logger_log_error(__func__, "Temporary error processing request (%d).", code);
|
||||
break;
|
||||
case 26:
|
||||
logger_log_error(__func__, "Suspended API key (%d).", code);
|
||||
break;
|
||||
case 29:
|
||||
logger_log_error(__func__, "Rate limit exceeded (%d).", code);
|
||||
break;
|
||||
default:
|
||||
logger_log_error(__func__, "Unknown error (%d).", code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
cJSON_Delete(root);
|
||||
|
||||
// TODO fix error codes, do something with the code etc
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a scrobble to LastFM
|
||||
* If 'finalize' is true, it sends to the track.scrobble endpoint
|
||||
* If 'finalize' is false, it sends to the track.updateNowPlaying endpoint
|
||||
*/
|
||||
void opensubsonic_scrobble_lastFm(bool finalize, opensubsonic_getSong_struct* songStruct) {
|
||||
if (finalize) {
|
||||
logger_log_general(__func__, "Performing final scrobble to LastFM.");
|
||||
} else {
|
||||
logger_log_general(__func__, "Performing in-progress scrobble to LastFM.");
|
||||
}
|
||||
|
||||
// Fetch the current UNIX timestamp
|
||||
time_t currentTime;
|
||||
char* currentTime_string;
|
||||
currentTime = time(NULL);
|
||||
rc = asprintf(¤tTime_string, "%ld", currentTime);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed.");
|
||||
return; // TODO return error
|
||||
}
|
||||
|
||||
// Assemble the signature
|
||||
char* sig_plaintext = NULL;
|
||||
if (finalize) {
|
||||
rc = asprintf(&sig_plaintext, "album%salbumArtist%sapi_key%sartist%smethodtrack.scrobblesk%stimestamp%strack%s%s",
|
||||
songStruct->album, songStruct->artist, configObj->lastfm_api_key, songStruct->artist,
|
||||
configObj->lastfm_api_session_key, currentTime_string, songStruct->title, configObj->lastfm_api_secret);
|
||||
} else {
|
||||
rc = asprintf(&sig_plaintext, "album%salbumArtist%sapi_key%sartist%smethodtrack.updateNowPlayingsk%stimestamp%strack%s%s",
|
||||
songStruct->album, songStruct->artist, configObj->lastfm_api_key, songStruct->artist,
|
||||
configObj->lastfm_api_session_key, currentTime_string, songStruct->title, configObj->lastfm_api_secret);
|
||||
}
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed.");
|
||||
return; // TODO handle error
|
||||
}
|
||||
|
||||
uint8_t sig_md5_bytes[16];
|
||||
char* sig_md5_text = NULL; // TODO do I have to free this? Also is be used in crypto.c
|
||||
md5String(sig_plaintext, sig_md5_bytes);
|
||||
free(sig_plaintext);
|
||||
rc = asprintf(&sig_md5_text, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
sig_md5_bytes[0], sig_md5_bytes[1], sig_md5_bytes[2], sig_md5_bytes[3],
|
||||
sig_md5_bytes[4], sig_md5_bytes[5], sig_md5_bytes[6], sig_md5_bytes[7],
|
||||
sig_md5_bytes[8], sig_md5_bytes[9], sig_md5_bytes[10], sig_md5_bytes[11],
|
||||
sig_md5_bytes[12], sig_md5_bytes[13], sig_md5_bytes[14], sig_md5_bytes[15]);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed.");
|
||||
return; // TODO handle error
|
||||
}
|
||||
|
||||
// URI encode strings
|
||||
char* uri_songTitle = lcue_uriescape(songStruct->title, (unsigned int)strlen(songStruct->title));
|
||||
char* uri_songArtist = lcue_uriescape(songStruct->artist, (unsigned int)strlen(songStruct->artist));
|
||||
char* uri_songAlbum = lcue_uriescape(songStruct->album, (unsigned int)strlen(songStruct->album));
|
||||
if (uri_songTitle == NULL || uri_songArtist == NULL || uri_songAlbum == NULL) {
|
||||
logger_log_error(__func__, "lcue_uriescape() error.");
|
||||
free(currentTime_string);
|
||||
free(sig_md5_text);
|
||||
if (uri_songTitle != NULL) { free(uri_songTitle); }
|
||||
if (uri_songArtist != NULL) { free(uri_songArtist); }
|
||||
if (uri_songAlbum != NULL) { free(uri_songAlbum); }
|
||||
return; // TODO return error
|
||||
}
|
||||
|
||||
// Assemble the payload
|
||||
char* payload = NULL;
|
||||
if (finalize) {
|
||||
rc = asprintf(&payload,
|
||||
"%s?method=track.scrobble&api_key=%s×tamp=%s&track=%s&artist=%s&album=%s&albumArtist=%s&sk=%s&api_sig=%s&format=json",
|
||||
lastFmScrobbleURL, configObj->lastfm_api_key, currentTime_string, uri_songTitle,
|
||||
uri_songArtist, uri_songAlbum, uri_songArtist,
|
||||
configObj->lastfm_api_session_key, sig_md5_text);
|
||||
} else {
|
||||
rc = asprintf(&payload,
|
||||
"%s?method=track.updateNowPlaying&api_key=%s×tamp=%s&track=%s&artist=%s&album=%s&albumArtist=%s&sk=%s&api_sig=%s&format=json",
|
||||
lastFmScrobbleURL, configObj->lastfm_api_key, currentTime_string, uri_songTitle,
|
||||
uri_songArtist, uri_songAlbum, uri_songArtist,
|
||||
configObj->lastfm_api_session_key, sig_md5_text);
|
||||
}
|
||||
free(currentTime_string);
|
||||
free(sig_md5_text);
|
||||
free(uri_songTitle);
|
||||
free(uri_songAlbum);
|
||||
free(uri_songArtist);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed.");
|
||||
return; // TODO return error
|
||||
}
|
||||
|
||||
// Send scrobble and receive response
|
||||
opensubsonic_httpClientRequest_t* httpReq;
|
||||
opensubsonic_httpClient_prepareRequest(&httpReq);
|
||||
|
||||
httpReq->requestUrl = strdup(payload);
|
||||
free(payload);
|
||||
httpReq->scrobbler = SCROBBLER_LASTFM;
|
||||
httpReq->method = HTTP_METHOD_POST;
|
||||
opensubsonic_httpClient_request(&httpReq);
|
||||
|
||||
if (httpReq->responseCode != HTTP_CODE_SUCCESS) {
|
||||
logger_log_error(__func__, "HTTP POST did not return success (%d).", httpReq->responseCode);
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
// Parse the scrobble response
|
||||
cJSON* root = cJSON_Parse(httpReq->responseMsg);
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* inner_root = NULL;
|
||||
cJSON* scrobbles_root = NULL; // Parent of inner_root, only used on final scrobble
|
||||
if (finalize) {
|
||||
// Make an object from scrobbles
|
||||
scrobbles_root = cJSON_GetObjectItemCaseSensitive(root, "scrobbles");
|
||||
if (scrobbles_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - scrobbles does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
// Make an object from scrobble
|
||||
inner_root = cJSON_GetObjectItemCaseSensitive(scrobbles_root, "scrobble");
|
||||
if (inner_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - scrobble does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
} else {
|
||||
// Make an object from nowplaying
|
||||
inner_root = cJSON_GetObjectItemCaseSensitive(root, "nowplaying");
|
||||
if (inner_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - nowplaying does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
}
|
||||
|
||||
// Make an object from artist, track, albumArtist, and album, and fetch codes
|
||||
cJSON* artist_root = cJSON_GetObjectItemCaseSensitive(inner_root, "artist");
|
||||
if (artist_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - artist does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* artist_corrected = cJSON_GetObjectItemCaseSensitive(artist_root, "corrected");
|
||||
if (cJSON_IsString(artist_corrected) && artist_corrected->valuestring != NULL) {
|
||||
if (*(artist_corrected->valuestring) == '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - artist/corrected is empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
char* endptr;
|
||||
int corrected = (int)strtol(artist_corrected->valuestring, &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - artist/corrected strtol/endptr is not empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
if (corrected == 1) {
|
||||
logger_log_important(__func__, "Warning - Artist has been autocorrected.");
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - artist/corrected does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* track_root = cJSON_GetObjectItemCaseSensitive(inner_root, "track");
|
||||
if (track_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - track does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* track_corrected = cJSON_GetObjectItemCaseSensitive(track_root, "corrected");
|
||||
if (cJSON_IsString(track_corrected) && track_corrected->valuestring != NULL) {
|
||||
if (*(track_corrected->valuestring) == '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - track/corrected is empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
char* endptr;
|
||||
int corrected = (int)strtol(track_corrected->valuestring, &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - track/corrected strtol/endptr is not empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
if (corrected == 1) {
|
||||
logger_log_important(__func__, "Warning - Track has been autocorrected.");
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - track/corrected does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* albumArtist_root = cJSON_GetObjectItemCaseSensitive(inner_root, "albumArtist");
|
||||
if (albumArtist_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - albumArtist does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* albumArtist_corrected = cJSON_GetObjectItemCaseSensitive(albumArtist_root, "corrected");
|
||||
if (cJSON_IsString(albumArtist_corrected) && albumArtist_corrected->valuestring != NULL) {
|
||||
if (*(albumArtist_corrected->valuestring) == '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - albumArtist/corrected is empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
char* endptr;
|
||||
int corrected = (int)strtol(albumArtist_corrected->valuestring, &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - albumArtist/corrected strtol/endptr is not empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
if (corrected == 1) {
|
||||
logger_log_important(__func__, "Warning - Album Artist has been autocorrected.");
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - albumArtist/corrected does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(inner_root, "album");
|
||||
if (album_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - album does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* album_corrected = cJSON_GetObjectItemCaseSensitive(album_root, "corrected");
|
||||
if (cJSON_IsString(album_corrected) && album_corrected->valuestring != NULL) {
|
||||
if (*(album_corrected->valuestring) == '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - album/corrected is empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
char* endptr;
|
||||
int corrected = (int)strtol(album_corrected->valuestring, &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - album/corrected strtol/endptr is not empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
if (corrected == 1) {
|
||||
logger_log_important(__func__, "Warning - Album has been autocorrected.");
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - album/corrected does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
// Make an object from ignoredMessage, and check return code
|
||||
cJSON* ignoredMessage_root = cJSON_GetObjectItemCaseSensitive(inner_root, "ignoredMessage");
|
||||
if (ignoredMessage_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - ignoredMessage does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* ignoredMessage_code = cJSON_GetObjectItemCaseSensitive(ignoredMessage_root, "code");
|
||||
if (cJSON_IsString(ignoredMessage_code) && ignoredMessage_code->valuestring != NULL) {
|
||||
if (*(ignoredMessage_code->valuestring) == '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - ignoredMessage/code is empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
char* endptr;
|
||||
int code = (int)strtol(ignoredMessage_code->valuestring, &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - ignoredMessage/code strtol/endptr is not empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
if (code == 0) {
|
||||
if (!finalize) {
|
||||
logger_log_general(__func__, "In progress scrobble was successful.");
|
||||
} else {
|
||||
logger_log_general(__func__, "Final scrobble 1/2 was successful.");
|
||||
}
|
||||
} else if (code == 1) {
|
||||
logger_log_error(__func__, "Artist was ignored.");
|
||||
} else if (code == 2) {
|
||||
logger_log_error(__func__, "Track was ignored.");
|
||||
} else if (code == 3) {
|
||||
logger_log_error(__func__, "Timestamp was too old.");
|
||||
} else if (code == 4) {
|
||||
logger_log_error(__func__, "Timestamp was too new.");
|
||||
} else if (code == 5) {
|
||||
logger_log_error(__func__, "Daily scrobble limit exceeded.");
|
||||
} else {
|
||||
logger_log_error(__func__, "Unknown error code received (%d)", code);
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - ignoredMessage/code does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
if (finalize) {
|
||||
cJSON* attr_root = cJSON_GetObjectItemCaseSensitive(scrobbles_root, "@attr");
|
||||
if (attr_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - @attr does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* attr_ignored = cJSON_GetObjectItemCaseSensitive(attr_root, "ignored");
|
||||
if (cJSON_IsNumber(attr_ignored)) {
|
||||
if (attr_ignored->valueint != 0) {
|
||||
logger_log_important(__func__, "Warning - @attr/ignored is not 0 (%d).", attr_ignored->valueint);
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - @attr/ignored does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* attr_accepted = cJSON_GetObjectItemCaseSensitive(attr_root, "accepted");
|
||||
if (cJSON_IsNumber(attr_accepted)) {
|
||||
if (attr_accepted->valueint != 1) {
|
||||
logger_log_important(__func__, "Warning - @attr/accepted is not 1 (%d).", attr_accepted->valueint);
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - @attr/accepted does not exist");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
// At this point, attr_ignored and attr_accepted are both known to be valid
|
||||
if (attr_ignored->valueint == 0 && attr_accepted->valueint == 1) {
|
||||
logger_log_general(__func__, "Final scrobble 2/2 was successful.");
|
||||
} else {
|
||||
logger_log_important(__func__, "Final scobble 2/2 was not successful (ignored: %d, accepted: %d",
|
||||
attr_ignored->valueint, attr_accepted->valueint);
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
9
src/libopensubsonic/scrobble_lastFm.h
Normal file
9
src/libopensubsonic/scrobble_lastFm.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef _SCROBBLE_LASTFM_H
|
||||
#define _SCROBBLE_LASTFM_H
|
||||
#include <stdbool.h>
|
||||
#include "endpoint_getSong.h"
|
||||
|
||||
char* opensubsonic_authenticate_lastFm(void);
|
||||
void opensubsonic_scrobble_lastFm(bool finalize, opensubsonic_getSong_struct* songStruct);
|
||||
|
||||
#endif
|
||||
141
src/libopensubsonic/scrobble_listenBrainz.c
Normal file
141
src/libopensubsonic/scrobble_listenBrainz.c
Normal file
@@ -0,0 +1,141 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "endpoint_getSong.h"
|
||||
#include "httpclient.h"
|
||||
#include "scrobble_listenBrainz.h"
|
||||
#include "../DarwinHttpClient.h"
|
||||
|
||||
const char* listenBrainzScrobbleURL = "https://api.listenbrainz.org/1/submit-listens";
|
||||
|
||||
void opensubsonic_scrobble_listenBrainz(bool finalize, opensubsonic_getSong_struct* songStruct) {
|
||||
if (finalize) {
|
||||
logger_log_general(__func__, "Performing final scrobble to ListenBrainz.");
|
||||
} else {
|
||||
logger_log_general(__func__, "Performing in-progress scrobble to ListenBrainz.");
|
||||
}
|
||||
|
||||
// Fetch the current UNIX timestamp
|
||||
time_t currentTime;
|
||||
currentTime = time(NULL);
|
||||
|
||||
// Form the JSON body
|
||||
cJSON* rootObj = cJSON_CreateObject();
|
||||
|
||||
// Add ["listen_type"]
|
||||
if (finalize) {
|
||||
cJSON* listenTypeObj = cJSON_CreateString("single");
|
||||
cJSON_AddItemToObject(rootObj, "listen_type", listenTypeObj);
|
||||
} else {
|
||||
cJSON* listenTypeObj = cJSON_CreateString("playing_now");
|
||||
cJSON_AddItemToObject(rootObj, "listen_type", listenTypeObj);
|
||||
}
|
||||
|
||||
// Add ["payload"]
|
||||
cJSON* payloadObj = cJSON_CreateArray();
|
||||
cJSON_AddItemToObject(rootObj, "payload", payloadObj);
|
||||
|
||||
// Add ["payload"][]
|
||||
cJSON* payloadContainedObj = cJSON_CreateObject();
|
||||
cJSON_AddItemToArray(payloadObj, payloadContainedObj);
|
||||
|
||||
// Add ["listened_at"] if 'finalize' is true
|
||||
if (finalize) {
|
||||
cJSON* listenedAtObj = cJSON_CreateNumber((long)currentTime);
|
||||
cJSON_AddItemToObject(payloadContainedObj, "listened_at", listenedAtObj);
|
||||
}
|
||||
|
||||
// Add ["track_metadata"]
|
||||
cJSON* trackMetadataObj = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(payloadContainedObj, "track_metadata", trackMetadataObj);
|
||||
|
||||
// Add ["track_metadata"]["additional_info"]
|
||||
cJSON* additionalInfoObj = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(trackMetadataObj, "additional_info", additionalInfoObj);
|
||||
|
||||
// Add ["track_metadata"]["additional_info"]["media_player"]
|
||||
cJSON* mediaPlayerObj = cJSON_CreateString("OSSP");
|
||||
cJSON_AddItemToObject(additionalInfoObj, "media_player", mediaPlayerObj);
|
||||
|
||||
// Add ["track_metadata"]["additional_info"]["submission_client"]
|
||||
cJSON* submissionClientObj = cJSON_CreateString("OSSP ListenBrainz Scrobbler");
|
||||
cJSON_AddItemToObject(additionalInfoObj, "submission_client", submissionClientObj);
|
||||
|
||||
// Add ["track_metadata"]["additional_info"]["submission_client_version"]
|
||||
cJSON* submissionClientVersionObj = cJSON_CreateString("1.0");
|
||||
cJSON_AddItemToObject(additionalInfoObj, "submission_client_version", submissionClientVersionObj);
|
||||
|
||||
// Add ["track_metadata"]["artist_name"]
|
||||
if (songStruct->artist != NULL) {
|
||||
cJSON* artistNameObj = cJSON_CreateString(songStruct->artist);
|
||||
cJSON_AddItemToObject(trackMetadataObj, "artist_name", artistNameObj);
|
||||
} else {
|
||||
printf("[ListenBrainz Scrobbler] Song Artist ([\"artist\"]) is null in songStruct\n");
|
||||
}
|
||||
|
||||
// Add ["track_metadata"]["track_name"]
|
||||
if (songStruct->title != NULL) {
|
||||
cJSON* artistTitleObj = cJSON_CreateString(songStruct->title);
|
||||
cJSON_AddItemToObject(trackMetadataObj, "track_name", artistTitleObj);
|
||||
} else {
|
||||
printf("[ListenBrainz Scrobbler] Song Title ([\"title\"]) is null in songStruct\n");
|
||||
}
|
||||
|
||||
// Add ["track_metadata"]["release_name"]
|
||||
if (songStruct->album != NULL) {
|
||||
cJSON* artistReleaseObj = cJSON_CreateString(songStruct->artist);
|
||||
cJSON_AddItemToObject(trackMetadataObj, "release_name", artistReleaseObj);
|
||||
} else {
|
||||
printf("[ListenBrainz Scrobbler] Song Album ([\"album\"]) is null in songStruct\n");
|
||||
}
|
||||
|
||||
// Print the assembled JSON
|
||||
char* payload = cJSON_PrintUnformatted(rootObj);
|
||||
cJSON_Delete(rootObj);
|
||||
|
||||
// Send payload and receive JSON response
|
||||
opensubsonic_httpClientRequest_t* httpReq;
|
||||
opensubsonic_httpClient_prepareRequest(&httpReq);
|
||||
|
||||
httpReq->requestUrl = strdup(listenBrainzScrobbleURL);
|
||||
httpReq->requestBody = strdup(payload);
|
||||
free(payload);
|
||||
httpReq->isBodyRequired = true;
|
||||
httpReq->scrobbler = SCROBBLER_LISTENBRAINZ;
|
||||
httpReq->method = HTTP_METHOD_POST;
|
||||
opensubsonic_httpClient_request(&httpReq);
|
||||
|
||||
// Check response
|
||||
if (httpReq->responseCode != HTTP_CODE_SUCCESS) {
|
||||
logger_log_error(__func__, "HTTP POST did not return success (%d).", httpReq->responseCode);
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* responseObj = cJSON_Parse(httpReq->responseMsg);
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
if (responseObj == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* status = cJSON_GetObjectItemCaseSensitive(responseObj, "status");
|
||||
if (cJSON_IsString(status) && status->valuestring != NULL) {
|
||||
if (strcmp(status->valuestring, "ok") == 0) {
|
||||
if (finalize) {
|
||||
logger_log_general(__func__, "Final scrobble was successful.");
|
||||
} else {
|
||||
logger_log_general(__func__, "In progress scrobble was successful.");
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Something went wrong scrobbling to ListenBrainz.");
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Something went wrong scrobbling to ListenBrainz.");
|
||||
}
|
||||
|
||||
cJSON_Delete(responseObj);
|
||||
}
|
||||
7
src/libopensubsonic/scrobble_listenBrainz.h
Normal file
7
src/libopensubsonic/scrobble_listenBrainz.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef _SCROBBLE_LISTENBRAINZ_H
|
||||
#define _SCROBBLE_LISTENBRAINZ_H
|
||||
#include <stdbool.h>
|
||||
|
||||
void opensubsonic_scrobble_listenBrainz(bool finalize, opensubsonic_getSong_struct* songStruct);
|
||||
|
||||
#endif
|
||||
106
src/libopensubsonic/utils.c
Normal file
106
src/libopensubsonic/utils.c
Normal file
@@ -0,0 +1,106 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dlfcn.h>
|
||||
#include "../external/cJSON.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
|
||||
/*
|
||||
* ACRONYMS:
|
||||
* OSS_Psoj -> Opensubsonic_PullStringOutofJson
|
||||
* OSS_Pioj -> Opensubsonic_PullIntOutofJson
|
||||
* OSS_Ploj -> Opensubsonic_PullLongOutofJson
|
||||
* OSS_Pboj -> Opensubsonic_PullBoolOutofJson
|
||||
*/
|
||||
|
||||
void OSS_Psoj(char** dest, cJSON* obj, char* child) {
|
||||
if (obj == NULL) {
|
||||
//void* ret_addr = __builtin_return_address(0);
|
||||
//Dl_info info;
|
||||
//if (dladdr(ret_addr, &info)) {
|
||||
// logger_log_error(info.dli_sname, "Parent object is null.");
|
||||
//}
|
||||
} else {
|
||||
cJSON* childObj = cJSON_GetObjectItem(obj, child);
|
||||
|
||||
if (cJSON_IsString(childObj) && childObj->valuestring != NULL) {
|
||||
*dest = strdup(childObj->valuestring);
|
||||
} else {
|
||||
//void* ret_addr = __builtin_return_address(0);
|
||||
//Dl_info info;
|
||||
//if (dladdr(ret_addr, &info)) {
|
||||
// logger_log_error(info.dli_sname, "Object %s is not a string or string is null.", child);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OSS_Pioj(int* dest, cJSON* obj, char* child) {
|
||||
if (obj == NULL) {
|
||||
//void* ret_addr = __builtin_return_address(0);
|
||||
//Dl_info info;
|
||||
//if (dladdr(ret_addr, &info)) {
|
||||
// logger_log_error(info.dli_sname, "Parent object is null.");
|
||||
//}
|
||||
} else {
|
||||
cJSON* childObj = cJSON_GetObjectItem(obj, child);
|
||||
|
||||
if (cJSON_IsNumber(childObj)) {
|
||||
*dest = childObj->valueint;
|
||||
} else {
|
||||
//void* ret_addr = __builtin_return_address(0);
|
||||
//Dl_info info;
|
||||
//if (dladdr(ret_addr, &info)) {
|
||||
// logger_log_error(info.dli_sname, "Object %s is not an int.", child);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OSS_Ploj(long* dest, cJSON* obj, char* child) {
|
||||
if (obj == NULL) {
|
||||
//void* ret_addr = __builtin_return_address(0);
|
||||
//Dl_info info;
|
||||
//if (dladdr(ret_addr, &info)) {
|
||||
// logger_log_error(info.dli_sname, "Parent object is null.");
|
||||
//}
|
||||
} else {
|
||||
cJSON* childObj = cJSON_GetObjectItem(obj, child);
|
||||
|
||||
if (cJSON_IsNumber(childObj)) {
|
||||
*dest = childObj->valueint;
|
||||
} else {
|
||||
//void* ret_addr = __builtin_return_address(0);
|
||||
//Dl_info info;
|
||||
//if (dladdr(ret_addr, &info)) {
|
||||
// logger_log_error(info.dli_sname, "Object %s is not a long.", child);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OSS_Pboj(bool* dest, cJSON* obj, char* child) {
|
||||
if (obj == NULL) {
|
||||
//void* ret_addr = __builtin_return_address(0);
|
||||
//Dl_info info;
|
||||
//if (dladdr(ret_addr, &info)) {
|
||||
// logger_log_error(info.dli_sname, "Parent object is null.");
|
||||
//}
|
||||
} else {
|
||||
cJSON* childObj = cJSON_GetObjectItem(obj, child);
|
||||
|
||||
if (cJSON_IsBool(childObj)) {
|
||||
if (cJSON_IsTrue(childObj)) {
|
||||
*dest = true;
|
||||
}
|
||||
} else {
|
||||
//void* ret_addr = __builtin_return_address(0);
|
||||
//Dl_info info;
|
||||
//if (dladdr(ret_addr, &info)) {
|
||||
// logger_log_error(info.dli_sname, "Object is not a bool.");
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/libopensubsonic/utils.h
Normal file
11
src/libopensubsonic/utils.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef _UTILS_H
|
||||
#define _UTILS_H
|
||||
#include <stdbool.h>
|
||||
#include "../external/cJSON.h"
|
||||
|
||||
void OSS_Psoj(char** dest, cJSON* obj, char* child);
|
||||
void OSS_Pioj(int* dest, cJSON* obj, char* child);
|
||||
void OSS_Ploj(long* dest, cJSON* obj, char* child);
|
||||
void OSS_Pboj(bool* dest, cJSON* obj, char* child);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user