Compare commits

...

14 Commits

Author SHA1 Message Date
2853898145 playQueue: Finished refactoring 2026-02-14 16:45:33 +10:00
9ae53e0824 discordrpc: Disabled callbacks since they weren't needed and added Local file 2026-02-14 03:47:16 +10:00
1f814fff6e localMusicHandler: General Fixes
Bugs fixed and features:
 - Creating the database and adding songs in the same run would fail
 - Songs with multiple artists are now separated with ', ' instead of the ID3 ';'
 - Excluded .mp4 files from the song list
 - Songs aren't scanned and duplicated on every start
 - General cleanup and memory leak fixes
2026-02-14 02:42:04 +10:00
26df10f8fb localMusicHandler can now recursively search and add music files and metadata to an sqlite database 2026-02-14 01:39:20 +10:00
66aa616286 Started implementing local music playback 2026-02-13 23:07:01 +10:00
ba554dc716 Finished initClientConnection() in socket 2026-02-11 17:09:50 +10:00
34f562149e Added global variable for OSSP version 2026-02-11 17:00:24 +10:00
db7fb9385d Adding beginning of AF_UNIX interface 2026-02-08 16:12:32 +10:00
cd03819c65 Adding stance on AI 2026-02-04 15:40:13 +10:00
6735603e9c General cleanup 2026-02-03 04:35:40 +10:00
8e7eb4c534 DiscordRPC: Added internet radio station support 2026-02-02 15:26:15 +10:00
9f96c574e9 Add OpenSubsonic /getInternetRadioStations endpoint
/getInternetRadioStations endpoint has been implemented and memory leak
tested. It also has been added to the memory testing build. Sorry if
there are any bugs, as I have not written memory heavy code in C in a
while.
2026-01-22 19:22:23 +10:00
4a9e1fc845 Fixed libopensubsonic's crypto implementation for Musl Linux
Musl C doesn't implement arc4random(), so use the getrandom() syscall
instead.
Also, Musl C also doesn't have a precompiler macro to be identified by,
so if the system is Linux and not Glibc, assume Musl C.
2026-01-22 17:09:21 +10:00
b4c584f60b Added Sqlite3 amalgamation (version 3.51.2) 2026-01-22 16:30:30 +10:00
23 changed files with 281707 additions and 73 deletions

View File

@@ -17,7 +17,7 @@ Discord RPC Settings:
Enable ('enable') - 'true' to enable Discord RPC or 'false' to disable it Enable ('enable') - 'true' to enable Discord RPC or 'false' to disable it
Method ('method'): Method ('method'):
- Method '0' is the official local Discord RPC method, local application to local discord client - Method '0' is the official local Discord RPC method, local application to local discord client
- Method '1' is a VERY buggy and in-development RPC method that uses a client-server model - DO NOT USE: Method '1' is a VERY buggy and in-development RPC method that uses a client-server model
to allow Discord RPC from a mobile device securely to allow Discord RPC from a mobile device securely
Show System Details ('showSystemDetails') - 'true' or 'false' Show System Details ('showSystemDetails') - 'true' or 'false'
- Want to show off / Got bragging rights on your _unique_ system?? Well this is perfect for you! - Want to show off / Got bragging rights on your _unique_ system?? Well this is perfect for you!
@@ -25,3 +25,20 @@ Show System Details ('showSystemDetails') - 'true' or 'false'
Example: 'on Linux x86_64 6.17.1-arch1-1' will be shown in the RPC dialog Example: 'on Linux x86_64 6.17.1-arch1-1' will be shown in the RPC dialog
Go on, don't be shy, show everyone you somehow have Discord and OSSP running on fucking s390x!!! Go on, don't be shy, show everyone you somehow have Discord and OSSP running on fucking s390x!!!
- Setting this to 'false' will simply instead show what playlist you are playing. - Setting this to 'false' will simply instead show what playlist you are playing.
Show Cover Art ('showCoverArt') - 'true' or 'false'
- If this is set to true, the cover art for the song playing will be shown on the Discord rich presence.
This can be disabled because the OpenSubsonic API does not have an unauthenticated way of accessing
album art from a server, which means you have to leak an authenticated server URL to Discord (Which
a lot of people would not feel comfortable doing, understandably).
- If this is set to false, Discord rich presence only shows the app icon, and does not leak any authenticated
URLs to Discord servers
Radio Sqlite3 Database Drafting:
Location: $HOME/.config/ossp/radio.db
Table: stations
- Int: id
- String: name
- String: url

View File

@@ -1,5 +1,11 @@
# OSSP (OpenSubsonicPlayer) # OSSP (OpenSubsonicPlayer)
## AI Notice
This project does not have ANY AI code other than a few snippets that will be removed in the future.<br>
Yes I comment a LOT, and unfortunately this has become a red flag for excessive AI use.<br>
I am a forgetful person, and have a habit of dropping projects for months at a time, so having
many comments allows me to come back and instantly understand what I was doing.<br>
## Notice ## Notice
OSSP is under heavy development and is NOT ready for daily use.<br> OSSP is under heavy development and is NOT ready for daily use.<br>
Also, I am NOT RESPONSIBLE if you damage your hearing and/or any of your equipment using OSSP.<br> Also, I am NOT RESPONSIBLE if you damage your hearing and/or any of your equipment using OSSP.<br>

View File

@@ -12,6 +12,8 @@ find_package(OpenGL REQUIRED)
find_package(SDL2 REQUIRED) find_package(SDL2 REQUIRED)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0) pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0)
pkg_check_modules(AVFORMAT REQUIRED libavformat)
pkg_check_modules(AVUTIL REQUIRED libavutil)
add_subdirectory(external/discord-rpc) add_subdirectory(external/discord-rpc)
@@ -24,6 +26,9 @@ add_executable(ossp MACOSX_BUNDLE
main.c main.c
configHandler.c configHandler.c
discordrpc.c discordrpc.c
localRadioDBHandler.c
localMusicHandler.cpp
socket.c
gui/gui_entry.cpp gui/gui_entry.cpp
player/player.c player/player.c
player/playQueue.cpp player/playQueue.cpp
@@ -44,6 +49,7 @@ add_executable(ossp MACOSX_BUNDLE
libopensubsonic/endpoint_getStarred.c libopensubsonic/endpoint_getStarred.c
libopensubsonic/endpoint_ping.c libopensubsonic/endpoint_ping.c
libopensubsonic/endpoint_scrobble.c libopensubsonic/endpoint_scrobble.c
libopensubsonic/endpoint_getInternetRadioStations.c
external/cJSON.c external/cJSON.c
external/cJSON_Utils.c external/cJSON_Utils.c
external/libcurl_uriescape.c external/libcurl_uriescape.c
@@ -55,6 +61,7 @@ add_executable(ossp MACOSX_BUNDLE
external/imgui/imgui_demo.cpp external/imgui/imgui_demo.cpp
external/imgui/backends/imgui_impl_sdl2.cpp external/imgui/backends/imgui_impl_sdl2.cpp
external/imgui/backends/imgui_impl_opengl2.cpp external/imgui/backends/imgui_impl_opengl2.cpp
external/sqlite3/sqlite3.c
) )
set_target_properties(ossp PROPERTIES set_target_properties(ossp PROPERTIES
@@ -62,6 +69,6 @@ set_target_properties(ossp PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER "org.hojuix.ossp" MACOSX_BUNDLE_GUI_IDENTIFIER "org.hojuix.ossp"
) )
include_directories(${GSTREAMER_INCLUDE_DIRS}) include_directories(${GSTREAMER_INCLUDE_DIRS} ${AVFORMAT_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIRS})
target_link_libraries(ossp PRIVATE OpenSSL::SSL OpenSSL::Crypto CURL::libcurl SDL2::SDL2 ${OPENGL_LIBRARIES} discord-rpc ${GSTREAMER_LIBRARIES}) target_link_libraries(ossp PRIVATE OpenSSL::SSL OpenSSL::Crypto CURL::libcurl SDL2::SDL2 ${OPENGL_LIBRARIES} discord-rpc ${GSTREAMER_LIBRARIES} ${AVFORMAT_LIBRARIES} ${AVUTIL_LIBRARIES})

View File

@@ -17,4 +17,5 @@ gcc runTests.c \
libopensubsonic/endpoint_getAlbumList.c \ libopensubsonic/endpoint_getAlbumList.c \
libopensubsonic/endpoint_getStarred.c \ libopensubsonic/endpoint_getStarred.c \
libopensubsonic/endpoint_scrobble.c \ libopensubsonic/endpoint_scrobble.c \
libopensubsonic/endpoint_getInternetRadioStations.c \
-o tests -lcurl -o tests -lcurl

View File

@@ -36,6 +36,7 @@ int configHandler_Read(configHandler_config_t** configObj) {
(*configObj)->internal_opensubsonic_clientName = NULL; (*configObj)->internal_opensubsonic_clientName = NULL;
(*configObj)->internal_opensubsonic_loginSalt = NULL; (*configObj)->internal_opensubsonic_loginSalt = NULL;
(*configObj)->internal_opensubsonic_loginToken = NULL; (*configObj)->internal_opensubsonic_loginToken = NULL;
(*configObj)->internal_ossp_version = NULL;
(*configObj)->listenbrainz_enable = false; (*configObj)->listenbrainz_enable = false;
(*configObj)->listenbrainz_token = NULL; (*configObj)->listenbrainz_token = NULL;
(*configObj)->lastfm_enable = false; (*configObj)->lastfm_enable = false;
@@ -67,10 +68,12 @@ int configHandler_Read(configHandler_config_t** configObj) {
(*configObj)->lv2_parax32_frequency_left = NULL; (*configObj)->lv2_parax32_frequency_left = NULL;
(*configObj)->lv2_parax32_frequency_right = NULL; (*configObj)->lv2_parax32_frequency_right = NULL;
(*configObj)->lv2_reverb_filter_name = NULL; (*configObj)->lv2_reverb_filter_name = NULL;
(*configObj)->local_rootdir = NULL;
// Set internal configuration values // Set internal configuration values
(*configObj)->internal_opensubsonic_version = strdup("1.8.0"); (*configObj)->internal_opensubsonic_version = strdup("1.8.0");
(*configObj)->internal_opensubsonic_clientName = strdup("Hojuix_OSSP"); (*configObj)->internal_opensubsonic_clientName = strdup("Hojuix_OSSP");
(*configObj)->internal_ossp_version = strdup("v0.4a");
// Form the path to the config JSON // Form the path to the config JSON
char* config_path = NULL; char* config_path = NULL;
@@ -463,6 +466,19 @@ int configHandler_Read(configHandler_config_t** configObj) {
(*configObj)->lv2_reverb_filter_name = strdup(calf_reverb_filter_name->valuestring); (*configObj)->lv2_reverb_filter_name = strdup(calf_reverb_filter_name->valuestring);
} }
// Make an object from local
cJSON* local_root = cJSON_GetObjectItemCaseSensitive(root, "local");
if (local_root == NULL) {
logger_log_error(__func__, "Error parsing JSON - local does not exist.");
cJSON_Delete(root);
return 1;
}
cJSON* local_root_directory = cJSON_GetObjectItemCaseSensitive(local_root, "rootDirectory");
if (cJSON_IsString(local_root_directory) && local_root_directory->valuestring != NULL) {
(*configObj)->local_rootdir = strdup(local_root_directory->valuestring);
}
cJSON_Delete(root); cJSON_Delete(root);
logger_log_general(__func__, "Successfully read configuration file."); logger_log_general(__func__, "Successfully read configuration file.");
return 0; return 0;
@@ -477,6 +493,7 @@ void configHandler_Free(configHandler_config_t** configObj) {
if ((*configObj)->internal_opensubsonic_clientName != NULL) { free((*configObj)->internal_opensubsonic_clientName); } if ((*configObj)->internal_opensubsonic_clientName != NULL) { free((*configObj)->internal_opensubsonic_clientName); }
if ((*configObj)->internal_opensubsonic_loginSalt != NULL) { free((*configObj)->internal_opensubsonic_loginSalt); } if ((*configObj)->internal_opensubsonic_loginSalt != NULL) { free((*configObj)->internal_opensubsonic_loginSalt); }
if ((*configObj)->internal_opensubsonic_loginToken != NULL) { free((*configObj)->internal_opensubsonic_loginToken); } if ((*configObj)->internal_opensubsonic_loginToken != NULL) { free((*configObj)->internal_opensubsonic_loginToken); }
if ((*configObj)->internal_ossp_version != NULL) { free((*configObj)->internal_ossp_version); }
if ((*configObj)->listenbrainz_token != NULL) { free((*configObj)->listenbrainz_token); } if ((*configObj)->listenbrainz_token != NULL) { free((*configObj)->listenbrainz_token); }
if ((*configObj)->lastfm_username != NULL) { free((*configObj)->lastfm_username); } if ((*configObj)->lastfm_username != NULL) { free((*configObj)->lastfm_username); }
if ((*configObj)->lastfm_password != NULL) { free((*configObj)->lastfm_password); } if ((*configObj)->lastfm_password != NULL) { free((*configObj)->lastfm_password); }
@@ -494,5 +511,6 @@ void configHandler_Free(configHandler_config_t** configObj) {
if ((*configObj)->lv2_parax32_frequency_left != NULL) { free((*configObj)->lv2_parax32_frequency_left); } if ((*configObj)->lv2_parax32_frequency_left != NULL) { free((*configObj)->lv2_parax32_frequency_left); }
if ((*configObj)->lv2_parax32_frequency_right != NULL) { free((*configObj)->lv2_parax32_frequency_right); } if ((*configObj)->lv2_parax32_frequency_right != NULL) { free((*configObj)->lv2_parax32_frequency_right); }
if ((*configObj)->lv2_reverb_filter_name != NULL) { free((*configObj)->lv2_reverb_filter_name); } if ((*configObj)->lv2_reverb_filter_name != NULL) { free((*configObj)->lv2_reverb_filter_name); }
if ((*configObj)->local_rootdir != NULL) { free((*configObj)->local_rootdir); }
if (*configObj != NULL) { free(*configObj); } if (*configObj != NULL) { free(*configObj); }
} }

View File

@@ -25,6 +25,7 @@ typedef struct {
char* internal_opensubsonic_clientName; // (Internal) Opensubsonic Client Name char* internal_opensubsonic_clientName; // (Internal) Opensubsonic Client Name
char* internal_opensubsonic_loginSalt; // (Internal) Opensubsonic Login Salt char* internal_opensubsonic_loginSalt; // (Internal) Opensubsonic Login Salt
char* internal_opensubsonic_loginToken; // (Internal) Opensubsonic Login Token char* internal_opensubsonic_loginToken; // (Internal) Opensubsonic Login Token
char* internal_ossp_version; // (Internal) OSSP Version
// Scrobbler Settings // Scrobbler Settings
bool listenbrainz_enable; // Enable ListenBrainz Scrobbling bool listenbrainz_enable; // Enable ListenBrainz Scrobbling
@@ -64,6 +65,9 @@ typedef struct {
char* lv2_parax32_frequency_left; char* lv2_parax32_frequency_left;
char* lv2_parax32_frequency_right; char* lv2_parax32_frequency_right;
char* lv2_reverb_filter_name; // LV2 Calf Reverb LV2 Name char* lv2_reverb_filter_name; // LV2 Calf Reverb LV2 Name
// Local Settings
char* local_rootdir; // Local Music Root Directory
} configHandler_config_t; } configHandler_config_t;
int configHandler_Read(configHandler_config_t** config); int configHandler_Read(configHandler_config_t** config);

View File

@@ -3,7 +3,6 @@
* Goldenkrew3000 2025 * Goldenkrew3000 2025
* License: GNU General Public License 3.0 * License: GNU General Public License 3.0
* Discord Local RPC Handler * Discord Local RPC Handler
* Note: This provides server auth creds (encoded) directly to Discord, could use Spotify's API instead??
*/ */
#include <inttypes.h> #include <inttypes.h>
@@ -41,31 +40,11 @@ void discordrpc_struct_deinit(discordrpc_data** discordrpc_struct) {
if (*discordrpc_struct != NULL) { free(*discordrpc_struct); } if (*discordrpc_struct != NULL) { free(*discordrpc_struct); }
} }
static void handleDiscordReady(const DiscordUser* connectedUser)
{
printf("\nDiscord: connected to user %s#%s - %s\n",
connectedUser->username,
connectedUser->discriminator,
connectedUser->userId);
}
static void handleDiscordDisconnected(int errcode, const char* message)
{
printf("\nDiscord: disconnected (%d: %s)\n", errcode, message);
}
static void handleDiscordError(int errcode, const char* message)
{
printf("\nDiscord: error (%d: %s)\n", errcode, message);
}
int discordrpc_init() { int discordrpc_init() {
printf("[DiscordRPC] Initializing...\n"); printf("[DiscordRPC] Initializing.\n");
// TODO Can I just not deal with the handler callbacks at all?
DiscordEventHandlers handlers; DiscordEventHandlers handlers;
memset(&handlers, 0, sizeof(handlers)); memset(&handlers, 0, sizeof(handlers));
handlers.ready = handleDiscordReady;
handlers.disconnected = handleDiscordDisconnected;
handlers.errored = handleDiscordError;
Discord_Initialize(discordrpc_appid, &handlers, 1, NULL); Discord_Initialize(discordrpc_appid, &handlers, 1, NULL);
// Fetch OS String for RPC (Heap-allocated) // Fetch OS String for RPC (Heap-allocated)
@@ -87,7 +66,8 @@ void discordrpc_update(discordrpc_data** discordrpc_struct) {
if ((*discordrpc_struct)->state == DISCORDRPC_STATE_IDLE) { if ((*discordrpc_struct)->state == DISCORDRPC_STATE_IDLE) {
asprintf(&detailsString, "Idle"); asprintf(&detailsString, "Idle");
presence.details = detailsString; presence.details = detailsString;
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING) { } else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_OPENSUBSONIC) {
// Playing a song
time_t currentTime = time(NULL); time_t currentTime = time(NULL);
asprintf(&detailsString, "%s", (*discordrpc_struct)->songTitle); asprintf(&detailsString, "%s", (*discordrpc_struct)->songTitle);
asprintf(&stateString, "by %s", (*discordrpc_struct)->songArtist); asprintf(&stateString, "by %s", (*discordrpc_struct)->songArtist);
@@ -99,6 +79,20 @@ void discordrpc_update(discordrpc_data** discordrpc_struct) {
if (configObj->discordrpc_showSysDetails) { if (configObj->discordrpc_showSysDetails) {
presence.largeImageText = discordrpc_osString; presence.largeImageText = discordrpc_osString;
} }
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_LOCALFILE) {
//
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_INTERNETRADIO) {
// Playing an internet radio station
time_t currentTime = time(NULL);
asprintf(&detailsString, "%s", (*discordrpc_struct)->songTitle);
asprintf(&stateString, "Internet radio station");
presence.details = detailsString;
presence.state = stateString;
presence.largeImageKey = (*discordrpc_struct)->coverArtUrl;
presence.startTimestamp = (long)currentTime;
if (configObj->discordrpc_showSysDetails) {
presence.largeImageText = discordrpc_osString;
}
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PAUSED) { } else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PAUSED) {
} }

View File

@@ -8,8 +8,10 @@
#define _DISCORDRPC_H #define _DISCORDRPC_H
#define DISCORDRPC_STATE_IDLE 0 #define DISCORDRPC_STATE_IDLE 0
#define DISCORDRPC_STATE_PLAYING 1 #define DISCORDRPC_STATE_PLAYING_OPENSUBSONIC 1
#define DISCORDRPC_STATE_PAUSED 2 #define DISCORDRPC_STATE_PLAYING_LOCALFILE 2
#define DISCORDRPC_STATE_PLAYING_INTERNETRADIO 3
#define DISCORDRPC_STATE_PAUSED 4
typedef struct { typedef struct {
int state; int state;

265952
src/external/sqlite3/sqlite3.c vendored Normal file
View File

File diff suppressed because it is too large Load Diff

13968
src/external/sqlite3/sqlite3.h vendored Normal file
View File

File diff suppressed because it is too large Load Diff

730
src/external/sqlite3/sqlite3ext.h vendored Normal file
View File

@@ -0,0 +1,730 @@
/*
** 2006 June 7
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This header file defines the SQLite interface for use by
** shared libraries that want to be imported as extensions into
** an SQLite instance. Shared libraries that intend to be loaded
** as extensions by SQLite should #include this file instead of
** sqlite3.h.
*/
#ifndef SQLITE3EXT_H
#define SQLITE3EXT_H
#include "sqlite3.h"
/*
** The following structure holds pointers to all of the SQLite API
** routines.
**
** WARNING: In order to maintain backwards compatibility, add new
** interfaces to the end of this structure only. If you insert new
** interfaces in the middle of this structure, then older different
** versions of SQLite will not be able to load each other's shared
** libraries!
*/
struct sqlite3_api_routines {
void * (*aggregate_context)(sqlite3_context*,int nBytes);
int (*aggregate_count)(sqlite3_context*);
int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
int (*bind_double)(sqlite3_stmt*,int,double);
int (*bind_int)(sqlite3_stmt*,int,int);
int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
int (*bind_null)(sqlite3_stmt*,int);
int (*bind_parameter_count)(sqlite3_stmt*);
int (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
const char * (*bind_parameter_name)(sqlite3_stmt*,int);
int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
int (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
int (*busy_timeout)(sqlite3*,int ms);
int (*changes)(sqlite3*);
int (*close)(sqlite3*);
int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
int eTextRep,const char*));
int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
int eTextRep,const void*));
const void * (*column_blob)(sqlite3_stmt*,int iCol);
int (*column_bytes)(sqlite3_stmt*,int iCol);
int (*column_bytes16)(sqlite3_stmt*,int iCol);
int (*column_count)(sqlite3_stmt*pStmt);
const char * (*column_database_name)(sqlite3_stmt*,int);
const void * (*column_database_name16)(sqlite3_stmt*,int);
const char * (*column_decltype)(sqlite3_stmt*,int i);
const void * (*column_decltype16)(sqlite3_stmt*,int);
double (*column_double)(sqlite3_stmt*,int iCol);
int (*column_int)(sqlite3_stmt*,int iCol);
sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol);
const char * (*column_name)(sqlite3_stmt*,int);
const void * (*column_name16)(sqlite3_stmt*,int);
const char * (*column_origin_name)(sqlite3_stmt*,int);
const void * (*column_origin_name16)(sqlite3_stmt*,int);
const char * (*column_table_name)(sqlite3_stmt*,int);
const void * (*column_table_name16)(sqlite3_stmt*,int);
const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
const void * (*column_text16)(sqlite3_stmt*,int iCol);
int (*column_type)(sqlite3_stmt*,int iCol);
sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
int (*complete)(const char*sql);
int (*complete16)(const void*sql);
int (*create_collation)(sqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*));
int (*create_collation16)(sqlite3*,const void*,int,void*,
int(*)(void*,int,const void*,int,const void*));
int (*create_function)(sqlite3*,const char*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*));
int (*create_function16)(sqlite3*,const void*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*));
int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
int (*data_count)(sqlite3_stmt*pStmt);
sqlite3 * (*db_handle)(sqlite3_stmt*);
int (*declare_vtab)(sqlite3*,const char*);
int (*enable_shared_cache)(int);
int (*errcode)(sqlite3*db);
const char * (*errmsg)(sqlite3*);
const void * (*errmsg16)(sqlite3*);
int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
int (*expired)(sqlite3_stmt*);
int (*finalize)(sqlite3_stmt*pStmt);
void (*free)(void*);
void (*free_table)(char**result);
int (*get_autocommit)(sqlite3*);
void * (*get_auxdata)(sqlite3_context*,int);
int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
int (*global_recover)(void);
void (*interruptx)(sqlite3*);
sqlite_int64 (*last_insert_rowid)(sqlite3*);
const char * (*libversion)(void);
int (*libversion_number)(void);
void *(*malloc)(int);
char * (*mprintf)(const char*,...);
int (*open)(const char*,sqlite3**);
int (*open16)(const void*,sqlite3**);
int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
void (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
void *(*realloc)(void*,int);
int (*reset)(sqlite3_stmt*pStmt);
void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_double)(sqlite3_context*,double);
void (*result_error)(sqlite3_context*,const char*,int);
void (*result_error16)(sqlite3_context*,const void*,int);
void (*result_int)(sqlite3_context*,int);
void (*result_int64)(sqlite3_context*,sqlite_int64);
void (*result_null)(sqlite3_context*);
void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
void (*result_value)(sqlite3_context*,sqlite3_value*);
void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
const char*,const char*),void*);
void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
char * (*xsnprintf)(int,char*,const char*,...);
int (*step)(sqlite3_stmt*);
int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
char const**,char const**,int*,int*,int*);
void (*thread_cleanup)(void);
int (*total_changes)(sqlite3*);
void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
sqlite_int64),void*);
void * (*user_data)(sqlite3_context*);
const void * (*value_blob)(sqlite3_value*);
int (*value_bytes)(sqlite3_value*);
int (*value_bytes16)(sqlite3_value*);
double (*value_double)(sqlite3_value*);
int (*value_int)(sqlite3_value*);
sqlite_int64 (*value_int64)(sqlite3_value*);
int (*value_numeric_type)(sqlite3_value*);
const unsigned char * (*value_text)(sqlite3_value*);
const void * (*value_text16)(sqlite3_value*);
const void * (*value_text16be)(sqlite3_value*);
const void * (*value_text16le)(sqlite3_value*);
int (*value_type)(sqlite3_value*);
char *(*vmprintf)(const char*,va_list);
/* Added ??? */
int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
/* Added by 3.3.13 */
int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
int (*clear_bindings)(sqlite3_stmt*);
/* Added by 3.4.1 */
int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
void (*xDestroy)(void *));
/* Added by 3.5.0 */
int (*bind_zeroblob)(sqlite3_stmt*,int,int);
int (*blob_bytes)(sqlite3_blob*);
int (*blob_close)(sqlite3_blob*);
int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
int,sqlite3_blob**);
int (*blob_read)(sqlite3_blob*,void*,int,int);
int (*blob_write)(sqlite3_blob*,const void*,int,int);
int (*create_collation_v2)(sqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*),
void(*)(void*));
int (*file_control)(sqlite3*,const char*,int,void*);
sqlite3_int64 (*memory_highwater)(int);
sqlite3_int64 (*memory_used)(void);
sqlite3_mutex *(*mutex_alloc)(int);
void (*mutex_enter)(sqlite3_mutex*);
void (*mutex_free)(sqlite3_mutex*);
void (*mutex_leave)(sqlite3_mutex*);
int (*mutex_try)(sqlite3_mutex*);
int (*open_v2)(const char*,sqlite3**,int,const char*);
int (*release_memory)(int);
void (*result_error_nomem)(sqlite3_context*);
void (*result_error_toobig)(sqlite3_context*);
int (*sleep)(int);
void (*soft_heap_limit)(int);
sqlite3_vfs *(*vfs_find)(const char*);
int (*vfs_register)(sqlite3_vfs*,int);
int (*vfs_unregister)(sqlite3_vfs*);
int (*xthreadsafe)(void);
void (*result_zeroblob)(sqlite3_context*,int);
void (*result_error_code)(sqlite3_context*,int);
int (*test_control)(int, ...);
void (*randomness)(int,void*);
sqlite3 *(*context_db_handle)(sqlite3_context*);
int (*extended_result_codes)(sqlite3*,int);
int (*limit)(sqlite3*,int,int);
sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
const char *(*sql)(sqlite3_stmt*);
int (*status)(int,int*,int*,int);
int (*backup_finish)(sqlite3_backup*);
sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
int (*backup_pagecount)(sqlite3_backup*);
int (*backup_remaining)(sqlite3_backup*);
int (*backup_step)(sqlite3_backup*,int);
const char *(*compileoption_get)(int);
int (*compileoption_used)(const char*);
int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*),
void(*xDestroy)(void*));
int (*db_config)(sqlite3*,int,...);
sqlite3_mutex *(*db_mutex)(sqlite3*);
int (*db_status)(sqlite3*,int,int*,int*,int);
int (*extended_errcode)(sqlite3*);
void (*log)(int,const char*,...);
sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
const char *(*sourceid)(void);
int (*stmt_status)(sqlite3_stmt*,int,int);
int (*strnicmp)(const char*,const char*,int);
int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
int (*wal_autocheckpoint)(sqlite3*,int);
int (*wal_checkpoint)(sqlite3*,const char*);
void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
int (*vtab_config)(sqlite3*,int op,...);
int (*vtab_on_conflict)(sqlite3*);
/* Version 3.7.16 and later */
int (*close_v2)(sqlite3*);
const char *(*db_filename)(sqlite3*,const char*);
int (*db_readonly)(sqlite3*,const char*);
int (*db_release_memory)(sqlite3*);
const char *(*errstr)(int);
int (*stmt_busy)(sqlite3_stmt*);
int (*stmt_readonly)(sqlite3_stmt*);
int (*stricmp)(const char*,const char*);
int (*uri_boolean)(const char*,const char*,int);
sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
const char *(*uri_parameter)(const char*,const char*);
char *(*xvsnprintf)(int,char*,const char*,va_list);
int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
/* Version 3.8.7 and later */
int (*auto_extension)(void(*)(void));
int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64,
void(*)(void*));
int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64,
void(*)(void*),unsigned char);
int (*cancel_auto_extension)(void(*)(void));
int (*load_extension)(sqlite3*,const char*,const char*,char**);
void *(*malloc64)(sqlite3_uint64);
sqlite3_uint64 (*msize)(void*);
void *(*realloc64)(void*,sqlite3_uint64);
void (*reset_auto_extension)(void);
void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64,
void(*)(void*));
void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64,
void(*)(void*), unsigned char);
int (*strglob)(const char*,const char*);
/* Version 3.8.11 and later */
sqlite3_value *(*value_dup)(const sqlite3_value*);
void (*value_free)(sqlite3_value*);
int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
/* Version 3.9.0 and later */
unsigned int (*value_subtype)(sqlite3_value*);
void (*result_subtype)(sqlite3_context*,unsigned int);
/* Version 3.10.0 and later */
int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
int (*strlike)(const char*,const char*,unsigned int);
int (*db_cacheflush)(sqlite3*);
/* Version 3.12.0 and later */
int (*system_errno)(sqlite3*);
/* Version 3.14.0 and later */
int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
char *(*expanded_sql)(sqlite3_stmt*);
/* Version 3.18.0 and later */
void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64);
/* Version 3.20.0 and later */
int (*prepare_v3)(sqlite3*,const char*,int,unsigned int,
sqlite3_stmt**,const char**);
int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int,
sqlite3_stmt**,const void**);
int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
void *(*value_pointer)(sqlite3_value*,const char*);
int (*vtab_nochange)(sqlite3_context*);
int (*value_nochange)(sqlite3_value*);
const char *(*vtab_collation)(sqlite3_index_info*,int);
/* Version 3.24.0 and later */
int (*keyword_count)(void);
int (*keyword_name)(int,const char**,int*);
int (*keyword_check)(const char*,int);
sqlite3_str *(*str_new)(sqlite3*);
char *(*str_finish)(sqlite3_str*);
void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
void (*str_append)(sqlite3_str*, const char *zIn, int N);
void (*str_appendall)(sqlite3_str*, const char *zIn);
void (*str_appendchar)(sqlite3_str*, int N, char C);
void (*str_reset)(sqlite3_str*);
int (*str_errcode)(sqlite3_str*);
int (*str_length)(sqlite3_str*);
char *(*str_value)(sqlite3_str*);
/* Version 3.25.0 and later */
int (*create_window_function)(sqlite3*,const char*,int,int,void*,
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
void (*xFinal)(sqlite3_context*),
void (*xValue)(sqlite3_context*),
void (*xInv)(sqlite3_context*,int,sqlite3_value**),
void(*xDestroy)(void*));
/* Version 3.26.0 and later */
const char *(*normalized_sql)(sqlite3_stmt*);
/* Version 3.28.0 and later */
int (*stmt_isexplain)(sqlite3_stmt*);
int (*value_frombind)(sqlite3_value*);
/* Version 3.30.0 and later */
int (*drop_modules)(sqlite3*,const char**);
/* Version 3.31.0 and later */
sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64);
const char *(*uri_key)(const char*,int);
const char *(*filename_database)(const char*);
const char *(*filename_journal)(const char*);
const char *(*filename_wal)(const char*);
/* Version 3.32.0 and later */
const char *(*create_filename)(const char*,const char*,const char*,
int,const char**);
void (*free_filename)(const char*);
sqlite3_file *(*database_file_object)(const char*);
/* Version 3.34.0 and later */
int (*txn_state)(sqlite3*,const char*);
/* Version 3.36.1 and later */
sqlite3_int64 (*changes64)(sqlite3*);
sqlite3_int64 (*total_changes64)(sqlite3*);
/* Version 3.37.0 and later */
int (*autovacuum_pages)(sqlite3*,
unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int),
void*, void(*)(void*));
/* Version 3.38.0 and later */
int (*error_offset)(sqlite3*);
int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**);
int (*vtab_distinct)(sqlite3_index_info*);
int (*vtab_in)(sqlite3_index_info*,int,int);
int (*vtab_in_first)(sqlite3_value*,sqlite3_value**);
int (*vtab_in_next)(sqlite3_value*,sqlite3_value**);
/* Version 3.39.0 and later */
int (*deserialize)(sqlite3*,const char*,unsigned char*,
sqlite3_int64,sqlite3_int64,unsigned);
unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*,
unsigned int);
const char *(*db_name)(sqlite3*,int);
/* Version 3.40.0 and later */
int (*value_encoding)(sqlite3_value*);
/* Version 3.41.0 and later */
int (*is_interrupted)(sqlite3*);
/* Version 3.43.0 and later */
int (*stmt_explain)(sqlite3_stmt*,int);
/* Version 3.44.0 and later */
void *(*get_clientdata)(sqlite3*,const char*);
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
/* Version 3.50.0 and later */
int (*setlk_timeout)(sqlite3*,int,int);
/* Version 3.51.0 and later */
int (*set_errmsg)(sqlite3*,int,const char*);
int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);
};
/*
** This is the function signature used for all extension entry points. It
** is also defined in the file "loadext.c".
*/
typedef int (*sqlite3_loadext_entry)(
sqlite3 *db, /* Handle to the database. */
char **pzErrMsg, /* Used to set error string on failure. */
const sqlite3_api_routines *pThunk /* Extension API function pointers. */
);
/*
** The following macros redefine the API routines so that they are
** redirected through the global sqlite3_api structure.
**
** This header file is also used by the loadext.c source file
** (part of the main SQLite library - not an extension) so that
** it can get access to the sqlite3_api_routines structure
** definition. But the main library does not want to redefine
** the API. So the redefinition macros are only valid if the
** SQLITE_CORE macros is undefined.
*/
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
#define sqlite3_aggregate_context sqlite3_api->aggregate_context
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_aggregate_count sqlite3_api->aggregate_count
#endif
#define sqlite3_bind_blob sqlite3_api->bind_blob
#define sqlite3_bind_double sqlite3_api->bind_double
#define sqlite3_bind_int sqlite3_api->bind_int
#define sqlite3_bind_int64 sqlite3_api->bind_int64
#define sqlite3_bind_null sqlite3_api->bind_null
#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count
#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index
#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name
#define sqlite3_bind_text sqlite3_api->bind_text
#define sqlite3_bind_text16 sqlite3_api->bind_text16
#define sqlite3_bind_value sqlite3_api->bind_value
#define sqlite3_busy_handler sqlite3_api->busy_handler
#define sqlite3_busy_timeout sqlite3_api->busy_timeout
#define sqlite3_changes sqlite3_api->changes
#define sqlite3_close sqlite3_api->close
#define sqlite3_collation_needed sqlite3_api->collation_needed
#define sqlite3_collation_needed16 sqlite3_api->collation_needed16
#define sqlite3_column_blob sqlite3_api->column_blob
#define sqlite3_column_bytes sqlite3_api->column_bytes
#define sqlite3_column_bytes16 sqlite3_api->column_bytes16
#define sqlite3_column_count sqlite3_api->column_count
#define sqlite3_column_database_name sqlite3_api->column_database_name
#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
#define sqlite3_column_decltype sqlite3_api->column_decltype
#define sqlite3_column_decltype16 sqlite3_api->column_decltype16
#define sqlite3_column_double sqlite3_api->column_double
#define sqlite3_column_int sqlite3_api->column_int
#define sqlite3_column_int64 sqlite3_api->column_int64
#define sqlite3_column_name sqlite3_api->column_name
#define sqlite3_column_name16 sqlite3_api->column_name16
#define sqlite3_column_origin_name sqlite3_api->column_origin_name
#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16
#define sqlite3_column_table_name sqlite3_api->column_table_name
#define sqlite3_column_table_name16 sqlite3_api->column_table_name16
#define sqlite3_column_text sqlite3_api->column_text
#define sqlite3_column_text16 sqlite3_api->column_text16
#define sqlite3_column_type sqlite3_api->column_type
#define sqlite3_column_value sqlite3_api->column_value
#define sqlite3_commit_hook sqlite3_api->commit_hook
#define sqlite3_complete sqlite3_api->complete
#define sqlite3_complete16 sqlite3_api->complete16
#define sqlite3_create_collation sqlite3_api->create_collation
#define sqlite3_create_collation16 sqlite3_api->create_collation16
#define sqlite3_create_function sqlite3_api->create_function
#define sqlite3_create_function16 sqlite3_api->create_function16
#define sqlite3_create_module sqlite3_api->create_module
#define sqlite3_create_module_v2 sqlite3_api->create_module_v2
#define sqlite3_data_count sqlite3_api->data_count
#define sqlite3_db_handle sqlite3_api->db_handle
#define sqlite3_declare_vtab sqlite3_api->declare_vtab
#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache
#define sqlite3_errcode sqlite3_api->errcode
#define sqlite3_errmsg sqlite3_api->errmsg
#define sqlite3_errmsg16 sqlite3_api->errmsg16
#define sqlite3_exec sqlite3_api->exec
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_expired sqlite3_api->expired
#endif
#define sqlite3_finalize sqlite3_api->finalize
#define sqlite3_free sqlite3_api->free
#define sqlite3_free_table sqlite3_api->free_table
#define sqlite3_get_autocommit sqlite3_api->get_autocommit
#define sqlite3_get_auxdata sqlite3_api->get_auxdata
#define sqlite3_get_table sqlite3_api->get_table
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_global_recover sqlite3_api->global_recover
#endif
#define sqlite3_interrupt sqlite3_api->interruptx
#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid
#define sqlite3_libversion sqlite3_api->libversion
#define sqlite3_libversion_number sqlite3_api->libversion_number
#define sqlite3_malloc sqlite3_api->malloc
#define sqlite3_mprintf sqlite3_api->mprintf
#define sqlite3_open sqlite3_api->open
#define sqlite3_open16 sqlite3_api->open16
#define sqlite3_prepare sqlite3_api->prepare
#define sqlite3_prepare16 sqlite3_api->prepare16
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
#define sqlite3_profile sqlite3_api->profile
#define sqlite3_progress_handler sqlite3_api->progress_handler
#define sqlite3_realloc sqlite3_api->realloc
#define sqlite3_reset sqlite3_api->reset
#define sqlite3_result_blob sqlite3_api->result_blob
#define sqlite3_result_double sqlite3_api->result_double
#define sqlite3_result_error sqlite3_api->result_error
#define sqlite3_result_error16 sqlite3_api->result_error16
#define sqlite3_result_int sqlite3_api->result_int
#define sqlite3_result_int64 sqlite3_api->result_int64
#define sqlite3_result_null sqlite3_api->result_null
#define sqlite3_result_text sqlite3_api->result_text
#define sqlite3_result_text16 sqlite3_api->result_text16
#define sqlite3_result_text16be sqlite3_api->result_text16be
#define sqlite3_result_text16le sqlite3_api->result_text16le
#define sqlite3_result_value sqlite3_api->result_value
#define sqlite3_rollback_hook sqlite3_api->rollback_hook
#define sqlite3_set_authorizer sqlite3_api->set_authorizer
#define sqlite3_set_auxdata sqlite3_api->set_auxdata
#define sqlite3_snprintf sqlite3_api->xsnprintf
#define sqlite3_step sqlite3_api->step
#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
#define sqlite3_total_changes sqlite3_api->total_changes
#define sqlite3_trace sqlite3_api->trace
#ifndef SQLITE_OMIT_DEPRECATED
#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings
#endif
#define sqlite3_update_hook sqlite3_api->update_hook
#define sqlite3_user_data sqlite3_api->user_data
#define sqlite3_value_blob sqlite3_api->value_blob
#define sqlite3_value_bytes sqlite3_api->value_bytes
#define sqlite3_value_bytes16 sqlite3_api->value_bytes16
#define sqlite3_value_double sqlite3_api->value_double
#define sqlite3_value_int sqlite3_api->value_int
#define sqlite3_value_int64 sqlite3_api->value_int64
#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type
#define sqlite3_value_text sqlite3_api->value_text
#define sqlite3_value_text16 sqlite3_api->value_text16
#define sqlite3_value_text16be sqlite3_api->value_text16be
#define sqlite3_value_text16le sqlite3_api->value_text16le
#define sqlite3_value_type sqlite3_api->value_type
#define sqlite3_vmprintf sqlite3_api->vmprintf
#define sqlite3_vsnprintf sqlite3_api->xvsnprintf
#define sqlite3_overload_function sqlite3_api->overload_function
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
#define sqlite3_clear_bindings sqlite3_api->clear_bindings
#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob
#define sqlite3_blob_bytes sqlite3_api->blob_bytes
#define sqlite3_blob_close sqlite3_api->blob_close
#define sqlite3_blob_open sqlite3_api->blob_open
#define sqlite3_blob_read sqlite3_api->blob_read
#define sqlite3_blob_write sqlite3_api->blob_write
#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2
#define sqlite3_file_control sqlite3_api->file_control
#define sqlite3_memory_highwater sqlite3_api->memory_highwater
#define sqlite3_memory_used sqlite3_api->memory_used
#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc
#define sqlite3_mutex_enter sqlite3_api->mutex_enter
#define sqlite3_mutex_free sqlite3_api->mutex_free
#define sqlite3_mutex_leave sqlite3_api->mutex_leave
#define sqlite3_mutex_try sqlite3_api->mutex_try
#define sqlite3_open_v2 sqlite3_api->open_v2
#define sqlite3_release_memory sqlite3_api->release_memory
#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem
#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig
#define sqlite3_sleep sqlite3_api->sleep
#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit
#define sqlite3_vfs_find sqlite3_api->vfs_find
#define sqlite3_vfs_register sqlite3_api->vfs_register
#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister
#define sqlite3_threadsafe sqlite3_api->xthreadsafe
#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob
#define sqlite3_result_error_code sqlite3_api->result_error_code
#define sqlite3_test_control sqlite3_api->test_control
#define sqlite3_randomness sqlite3_api->randomness
#define sqlite3_context_db_handle sqlite3_api->context_db_handle
#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes
#define sqlite3_limit sqlite3_api->limit
#define sqlite3_next_stmt sqlite3_api->next_stmt
#define sqlite3_sql sqlite3_api->sql
#define sqlite3_status sqlite3_api->status
#define sqlite3_backup_finish sqlite3_api->backup_finish
#define sqlite3_backup_init sqlite3_api->backup_init
#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount
#define sqlite3_backup_remaining sqlite3_api->backup_remaining
#define sqlite3_backup_step sqlite3_api->backup_step
#define sqlite3_compileoption_get sqlite3_api->compileoption_get
#define sqlite3_compileoption_used sqlite3_api->compileoption_used
#define sqlite3_create_function_v2 sqlite3_api->create_function_v2
#define sqlite3_db_config sqlite3_api->db_config
#define sqlite3_db_mutex sqlite3_api->db_mutex
#define sqlite3_db_status sqlite3_api->db_status
#define sqlite3_extended_errcode sqlite3_api->extended_errcode
#define sqlite3_log sqlite3_api->log
#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64
#define sqlite3_sourceid sqlite3_api->sourceid
#define sqlite3_stmt_status sqlite3_api->stmt_status
#define sqlite3_strnicmp sqlite3_api->strnicmp
#define sqlite3_unlock_notify sqlite3_api->unlock_notify
#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint
#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint
#define sqlite3_wal_hook sqlite3_api->wal_hook
#define sqlite3_blob_reopen sqlite3_api->blob_reopen
#define sqlite3_vtab_config sqlite3_api->vtab_config
#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict
/* Version 3.7.16 and later */
#define sqlite3_close_v2 sqlite3_api->close_v2
#define sqlite3_db_filename sqlite3_api->db_filename
#define sqlite3_db_readonly sqlite3_api->db_readonly
#define sqlite3_db_release_memory sqlite3_api->db_release_memory
#define sqlite3_errstr sqlite3_api->errstr
#define sqlite3_stmt_busy sqlite3_api->stmt_busy
#define sqlite3_stmt_readonly sqlite3_api->stmt_readonly
#define sqlite3_stricmp sqlite3_api->stricmp
#define sqlite3_uri_boolean sqlite3_api->uri_boolean
#define sqlite3_uri_int64 sqlite3_api->uri_int64
#define sqlite3_uri_parameter sqlite3_api->uri_parameter
#define sqlite3_uri_vsnprintf sqlite3_api->xvsnprintf
#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2
/* Version 3.8.7 and later */
#define sqlite3_auto_extension sqlite3_api->auto_extension
#define sqlite3_bind_blob64 sqlite3_api->bind_blob64
#define sqlite3_bind_text64 sqlite3_api->bind_text64
#define sqlite3_cancel_auto_extension sqlite3_api->cancel_auto_extension
#define sqlite3_load_extension sqlite3_api->load_extension
#define sqlite3_malloc64 sqlite3_api->malloc64
#define sqlite3_msize sqlite3_api->msize
#define sqlite3_realloc64 sqlite3_api->realloc64
#define sqlite3_reset_auto_extension sqlite3_api->reset_auto_extension
#define sqlite3_result_blob64 sqlite3_api->result_blob64
#define sqlite3_result_text64 sqlite3_api->result_text64
#define sqlite3_strglob sqlite3_api->strglob
/* Version 3.8.11 and later */
#define sqlite3_value_dup sqlite3_api->value_dup
#define sqlite3_value_free sqlite3_api->value_free
#define sqlite3_result_zeroblob64 sqlite3_api->result_zeroblob64
#define sqlite3_bind_zeroblob64 sqlite3_api->bind_zeroblob64
/* Version 3.9.0 and later */
#define sqlite3_value_subtype sqlite3_api->value_subtype
#define sqlite3_result_subtype sqlite3_api->result_subtype
/* Version 3.10.0 and later */
#define sqlite3_status64 sqlite3_api->status64
#define sqlite3_strlike sqlite3_api->strlike
#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush
/* Version 3.12.0 and later */
#define sqlite3_system_errno sqlite3_api->system_errno
/* Version 3.14.0 and later */
#define sqlite3_trace_v2 sqlite3_api->trace_v2
#define sqlite3_expanded_sql sqlite3_api->expanded_sql
/* Version 3.18.0 and later */
#define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid
/* Version 3.20.0 and later */
#define sqlite3_prepare_v3 sqlite3_api->prepare_v3
#define sqlite3_prepare16_v3 sqlite3_api->prepare16_v3
#define sqlite3_bind_pointer sqlite3_api->bind_pointer
#define sqlite3_result_pointer sqlite3_api->result_pointer
#define sqlite3_value_pointer sqlite3_api->value_pointer
/* Version 3.22.0 and later */
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
#define sqlite3_value_nochange sqlite3_api->value_nochange
#define sqlite3_vtab_collation sqlite3_api->vtab_collation
/* Version 3.24.0 and later */
#define sqlite3_keyword_count sqlite3_api->keyword_count
#define sqlite3_keyword_name sqlite3_api->keyword_name
#define sqlite3_keyword_check sqlite3_api->keyword_check
#define sqlite3_str_new sqlite3_api->str_new
#define sqlite3_str_finish sqlite3_api->str_finish
#define sqlite3_str_appendf sqlite3_api->str_appendf
#define sqlite3_str_vappendf sqlite3_api->str_vappendf
#define sqlite3_str_append sqlite3_api->str_append
#define sqlite3_str_appendall sqlite3_api->str_appendall
#define sqlite3_str_appendchar sqlite3_api->str_appendchar
#define sqlite3_str_reset sqlite3_api->str_reset
#define sqlite3_str_errcode sqlite3_api->str_errcode
#define sqlite3_str_length sqlite3_api->str_length
#define sqlite3_str_value sqlite3_api->str_value
/* Version 3.25.0 and later */
#define sqlite3_create_window_function sqlite3_api->create_window_function
/* Version 3.26.0 and later */
#define sqlite3_normalized_sql sqlite3_api->normalized_sql
/* Version 3.28.0 and later */
#define sqlite3_stmt_isexplain sqlite3_api->stmt_isexplain
#define sqlite3_value_frombind sqlite3_api->value_frombind
/* Version 3.30.0 and later */
#define sqlite3_drop_modules sqlite3_api->drop_modules
/* Version 3.31.0 and later */
#define sqlite3_hard_heap_limit64 sqlite3_api->hard_heap_limit64
#define sqlite3_uri_key sqlite3_api->uri_key
#define sqlite3_filename_database sqlite3_api->filename_database
#define sqlite3_filename_journal sqlite3_api->filename_journal
#define sqlite3_filename_wal sqlite3_api->filename_wal
/* Version 3.32.0 and later */
#define sqlite3_create_filename sqlite3_api->create_filename
#define sqlite3_free_filename sqlite3_api->free_filename
#define sqlite3_database_file_object sqlite3_api->database_file_object
/* Version 3.34.0 and later */
#define sqlite3_txn_state sqlite3_api->txn_state
/* Version 3.36.1 and later */
#define sqlite3_changes64 sqlite3_api->changes64
#define sqlite3_total_changes64 sqlite3_api->total_changes64
/* Version 3.37.0 and later */
#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages
/* Version 3.38.0 and later */
#define sqlite3_error_offset sqlite3_api->error_offset
#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value
#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct
#define sqlite3_vtab_in sqlite3_api->vtab_in
#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first
#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next
/* Version 3.39.0 and later */
#ifndef SQLITE_OMIT_DESERIALIZE
#define sqlite3_deserialize sqlite3_api->deserialize
#define sqlite3_serialize sqlite3_api->serialize
#endif
#define sqlite3_db_name sqlite3_api->db_name
/* Version 3.40.0 and later */
#define sqlite3_value_encoding sqlite3_api->value_encoding
/* Version 3.41.0 and later */
#define sqlite3_is_interrupted sqlite3_api->is_interrupted
/* Version 3.43.0 and later */
#define sqlite3_stmt_explain sqlite3_api->stmt_explain
/* Version 3.44.0 and later */
#define sqlite3_get_clientdata sqlite3_api->get_clientdata
#define sqlite3_set_clientdata sqlite3_api->set_clientdata
/* Version 3.50.0 and later */
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
/* Version 3.51.0 and later */
#define sqlite3_set_errmsg sqlite3_api->set_errmsg
#define sqlite3_db_status64 sqlite3_api->db_status64
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
/* This case when the file really is being compiled as a loadable
** extension */
# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0;
# define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v;
# define SQLITE_EXTENSION_INIT3 \
extern const sqlite3_api_routines *sqlite3_api;
#else
/* This case when the file is being statically linked into the
** application */
# define SQLITE_EXTENSION_INIT1 /*no-op*/
# define SQLITE_EXTENSION_INIT2(v) (void)v; /* unused parameter */
# define SQLITE_EXTENSION_INIT3 /*no-op*/
#endif
#endif /* SQLITE3EXT_H */

View File

@@ -4,20 +4,45 @@
#include "../configHandler.h" #include "../configHandler.h"
#include "logger.h" #include "logger.h"
// If the platform is Linux, but the libc isn't Glibc, assume Musl C
// Yes, I know this ignores the existence of others such as Mlibc or Uclibc, but
// Musl C doesn't define a precompiler macro to be identified, and this works for now
#if defined(__linux__) && !defined(__GLIBC__)
#define __MUSL__
#endif
#if __NetBSD__ // Functions for NetBSD to use KERN_ARND #if __NetBSD__ // Functions for NetBSD to use KERN_ARND
#include <sys/types.h> #include <sys/types.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
#endif #endif
#if defined(__MUSL__) // Musl C does not contain arc4random(), use Linux getrandom() instead
#include <sys/random.h>
#endif
static int rc = 0; static int rc = 0;
extern configHandler_config_t* configObj; extern configHandler_config_t* configObj;
// Use arc4random() to generate cryptographically secure bytes. Should work on all BSD-style systems // Use arc4random() to generate cryptographically secure bytes. Should work on all BSD-style systems
#if !defined(__MUSL__) // If not Musl
void crypto_secure_arc4random_generate(unsigned char* bytes, size_t length) { void crypto_secure_arc4random_generate(unsigned char* bytes, size_t length) {
for (size_t i = 0; i < length; i++) { for (size_t i = 0; i < length; i++) {
bytes[i] = arc4random() & 0xFF; bytes[i] = arc4random() & 0xFF;
} }
} }
#endif
// Use getrandom() instead of arc4random() for Musl C Linux
#if defined(__MUSL__)
void crypto_secure_getrandom_generate(unsigned char* bytes, size_t length) {
if (getrandom(bytes, length, 0) != length) {
printf("[libopensubsonic/crypto] getrandom() failed. Using insecure bytes.\n");
for (size_t i = 0; i < length; i++) {
bytes[i] = 0xFF;
}
}
}
#endif
// Use the arandom sysctl on NetBSD to generate cryptographically secure bytes. // Use the arandom sysctl on NetBSD to generate cryptographically secure bytes.
#if __NetBSD__ #if __NetBSD__
@@ -42,8 +67,12 @@ void crypto_secure_generate_salt(void) {
// Generate cryptographically secure salt bytes using OS-native functions // Generate cryptographically secure salt bytes using OS-native functions
#if __NetBSD__ #if __NetBSD__
crypto_secure_netbsd_arandom_generate(salt_bytes, 8); crypto_secure_netbsd_arandom_generate(salt_bytes, 8);
#else
#if defined(__MUSL__)
crypto_secure_getrandom_generate(salt_bytes, 8);
#else #else
crypto_secure_arc4random_generate(salt_bytes, 8); crypto_secure_arc4random_generate(salt_bytes, 8);
#endif
#endif #endif
// Convert to a string hex representation // Convert to a string hex representation

View File

@@ -0,0 +1,110 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../external/cJSON.h"
#include "logger.h"
#include "utils.h"
#include "endpoint_getInternetRadioStations.h"
int opensubsonic_getInternetRadioStations_parse(char* data, opensubsonic_getInternetRadioStations_struct** getInternetRadioStationsStruct) {
// Allocate and initialize struct
*getInternetRadioStationsStruct = malloc(sizeof(opensubsonic_getInternetRadioStations_struct));
(*getInternetRadioStationsStruct)->status = NULL;
(*getInternetRadioStationsStruct)->errorCode = 0;
(*getInternetRadioStationsStruct)->errorMessage = NULL;
(*getInternetRadioStationsStruct)->radioStationCount = 0;
(*getInternetRadioStationsStruct)->radioStations = NULL;
// Parse 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(&(*getInternetRadioStationsStruct)->status, subsonic_root, "status");
// Check if API has returned an error
if (strstr((*getInternetRadioStationsStruct)->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(&(*getInternetRadioStationsStruct)->errorCode, subsonic_error, "code");
OSS_Psoj(&(*getInternetRadioStationsStruct)->errorMessage, subsonic_error, "message");
logger_log_error(__func__, "Error noted in JSON - Code %d: %s", (*getInternetRadioStationsStruct)->errorCode, (*getInternetRadioStationsStruct)->errorMessage);
cJSON_Delete(root);
return 1;
}
// Make an object from 'internetRadioStations'
cJSON* internetRadioStations_root = cJSON_GetObjectItemCaseSensitive(subsonic_root, "internetRadioStations");
if (internetRadioStations_root == NULL) {
logger_log_error(__func__, "Error handling JSON - internetRadioStations does not exist.");
cJSON_Delete(root);
return 1;
}
// Make an object from 'internetRadioStation
cJSON* internetRadioStation_root = cJSON_GetObjectItemCaseSensitive(internetRadioStations_root, "internetRadioStation");
if (internetRadioStation_root == NULL) {
logger_log_error(__func__, "Error handling JSON - internetRadioStation does not exist.");
cJSON_Delete(root);
return 1;
}
// Get the amount of radio stiations, then allocate and initialize structs
(*getInternetRadioStationsStruct)->radioStationCount = cJSON_GetArraySize(internetRadioStation_root);
if ((*getInternetRadioStationsStruct)->radioStationCount != 0) {
// If there are no radio stations, don't allocate anything, but let the program run through this.
// Cleanup then happens at the same point, but the following allocation and fetching steps wont happen
// Basically keeps the code cleaner for this edge case (Although this is probably present in other parts **TODO**)
(*getInternetRadioStationsStruct)->radioStations = malloc((*getInternetRadioStationsStruct)->radioStationCount * sizeof(opensubsonic_getInternetRadioStations_radioStations_struct));
}
for (int i = 0; i < (*getInternetRadioStationsStruct)->radioStationCount; i++) {
(*getInternetRadioStationsStruct)->radioStations[i].id = NULL;
(*getInternetRadioStationsStruct)->radioStations[i].name = NULL;
(*getInternetRadioStationsStruct)->radioStations[i].streamUrl = NULL;
}
for (int i = 0; i < (*getInternetRadioStationsStruct)->radioStationCount; i++) {
cJSON* curr_idx_root = cJSON_GetArrayItem(internetRadioStation_root, i);
if (curr_idx_root != NULL) {
OSS_Psoj(&(*getInternetRadioStationsStruct)->radioStations[i].id, curr_idx_root, "id");
OSS_Psoj(&(*getInternetRadioStationsStruct)->radioStations[i].name, curr_idx_root, "name");
OSS_Psoj(&(*getInternetRadioStationsStruct)->radioStations[i].streamUrl, curr_idx_root, "streamUrl");
}
}
cJSON_Delete(root);
return 0;
}
void opensubsonic_getInternetRadioStations_struct_free(opensubsonic_getInternetRadioStations_struct** getInternetRadioStationsStruct) {
logger_log_general(__func__, "Freeing /getInternetRadioStations endpoint heap objects.");
if ((*getInternetRadioStationsStruct)->status != NULL) { free((*getInternetRadioStationsStruct)->status); }
if ((*getInternetRadioStationsStruct)->errorMessage != NULL) { free((*getInternetRadioStationsStruct)->errorMessage); }
for (int i = 0; i < (*getInternetRadioStationsStruct)->radioStationCount; i++) {
if ((*getInternetRadioStationsStruct)->radioStations[i].id != NULL) { free((*getInternetRadioStationsStruct)->radioStations[i].id); }
if ((*getInternetRadioStationsStruct)->radioStations[i].name != NULL) { free((*getInternetRadioStationsStruct)->radioStations[i].name); }
if ((*getInternetRadioStationsStruct)->radioStations[i].streamUrl != NULL) { free((*getInternetRadioStationsStruct)->radioStations[i].streamUrl); }
}
if ((*getInternetRadioStationsStruct)->radioStations != NULL) { free((*getInternetRadioStationsStruct)->radioStations); }
if (*getInternetRadioStationsStruct != NULL) { free(*getInternetRadioStationsStruct); }
}

View File

@@ -0,0 +1,29 @@
#ifndef _ENDPOINT_GETINTERNETRADIOSTATIONS_H
#define _ENDPOINT_GETINTERNETRADIOSTATIONS_H
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
typedef struct {
char* id;
char* name;
char* streamUrl;
} opensubsonic_getInternetRadioStations_radioStations_struct;
typedef struct {
char* status;
int errorCode;
char* errorMessage;
int radioStationCount;
opensubsonic_getInternetRadioStations_radioStations_struct* radioStations;
} opensubsonic_getInternetRadioStations_struct;
int opensubsonic_getInternetRadioStations_parse(char* data, opensubsonic_getInternetRadioStations_struct** getInternetRadioStationsStruct);
void opensubsonic_getInternetRadioStations_struct_free(opensubsonic_getInternetRadioStations_struct** getInternetRadioStationsStruct);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // _ENDPOINT_GETINTERNETRADIOSTATIONS_H

View File

@@ -19,6 +19,7 @@
#include "endpoint_getAlbumList.h" #include "endpoint_getAlbumList.h"
#include "endpoint_getAlbum.h" #include "endpoint_getAlbum.h"
#include "endpoint_scrobble.h" #include "endpoint_scrobble.h"
#include "endpoint_getInternetRadioStations.h"
static int rc = 0; static int rc = 0;
extern configHandler_config_t* configObj; extern configHandler_config_t* configObj;
@@ -214,6 +215,12 @@ void opensubsonic_httpClient_formUrl(opensubsonic_httpClient_URL_t** urlObj) {
(*urlObj)->id, submitString); (*urlObj)->id, submitString);
free(submitString); free(submitString);
break; break;
case OPENSUBSONIC_ENDPOINT_GETINTERNETRADIOSTATIONS:
rc = asprintf(&url, "%s://%s/rest/getInternetRadioStations?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;
default: default:
logger_log_error(__func__, "Unknown endpoint requested."); logger_log_error(__func__, "Unknown endpoint requested.");
break; break;
@@ -270,6 +277,9 @@ void opensubsonic_httpClient_fetchResponse(opensubsonic_httpClient_URL_t** urlOb
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_SCROBBLE) { } else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_SCROBBLE) {
opensubsonic_scrobble_struct** scrobbleStruct = (opensubsonic_scrobble_struct**)responseObj; opensubsonic_scrobble_struct** scrobbleStruct = (opensubsonic_scrobble_struct**)responseObj;
opensubsonic_scrobble_parse(httpReq->responseMsg, scrobbleStruct); opensubsonic_scrobble_parse(httpReq->responseMsg, scrobbleStruct);
} else if ((*urlObj)->endpoint == OPENSUBSONIC_ENDPOINT_GETINTERNETRADIOSTATIONS) {
opensubsonic_getInternetRadioStations_struct** getInternetRadioStationsStruct = (opensubsonic_getInternetRadioStations_struct**)responseObj;
opensubsonic_getInternetRadioStations_parse(httpReq->responseMsg, getInternetRadioStationsStruct);
} else { } else {
logger_log_error(__func__, "Unknown endpoint requested."); logger_log_error(__func__, "Unknown endpoint requested.");
} }

View File

@@ -26,6 +26,7 @@ extern "C" {
#define OPENSUBSONIC_ENDPOINT_GETLYRICSBYSONGID 311 #define OPENSUBSONIC_ENDPOINT_GETLYRICSBYSONGID 311
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST 312 #define OPENSUBSONIC_ENDPOINT_GETALBUMLIST 312
#define OPENSUBSONIC_ENDPOINT_SCROBBLE 313 #define OPENSUBSONIC_ENDPOINT_SCROBBLE 313
#define OPENSUBSONIC_ENDPOINT_GETINTERNETRADIOSTATIONS 314
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RANDOM 501 #define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_RANDOM 501
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_NEWEST 502 #define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_NEWEST 502
#define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_HIGHEST 503 #define OPENSUBSONIC_ENDPOINT_GETALBUMLIST_HIGHEST 503

257
src/localMusicHandler.cpp Normal file
View File

@@ -0,0 +1,257 @@
/*
* OpenSubsonicPlayer
* Goldenkrew3000 2025
* License: GNU General Public License 3.0
* Info: Local Music File Handler
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/dict.h>
#include <libavformat/avio.h>
#include "external/sqlite3/sqlite3.h"
}
#include <iostream>
#include <regex>
#include <vector>
#include <deque>
#include "configHandler.h"
#include "localMusicHandler.hpp"
/*
* I'm sorry for this messy and probably unreliable code
* This is the first time I have ever written something like this
* And why C++? Easy to store all of the temporary data without a ton of boilerplate
*/
extern configHandler_config_t* configObj;
std::vector<std::string> localMusicHandler_allFiles;
class localMusicHandler_AudioObject {
public:
std::string path;
std::string songTitle;
std::string albumTitle;
std::string artistTitle;
std::string track;
std::string totalTracks;
uint64_t filesize;
std::string uid;
};
std::deque<localMusicHandler_AudioObject> localMusicHandler_audioItems;
static sqlite3* sqlite_db = NULL;
static char* sqlite_errorMsg = NULL;
void localMusicHandler_scan() {
static int rc = 0;
printf("[LocalMusicHandler] Scanning local music directory recursively for files.\n");
// TODO clear all vectors
// Scan music directory (defined in config file) for all files
localMusicHandler_scanDirectory(configObj->local_rootdir);
printf("[LocalMusicHandler] Found %d files.\n", localMusicHandler_allFiles.size());
// Scan each file to find only music files, and pull ID3 tags
for (int i = 0; i < localMusicHandler_allFiles.size(); i++) {
localMusicHandler_scanFile(i);
}
printf("[LocalMusicHandler] Found %d songs.\n", localMusicHandler_audioItems.size());
// Generate a unique ID for each music file
printf("[LocalMusicHandler] Generating unique IDs for each song.\n");
for (int i = 0; i < localMusicHandler_audioItems.size(); i++) {
// TODO: Technically there is a VERY SMALL chance that 2 id's repeat in the DB
// Figure out what to do about that later
localMusicHandler_generateUid(i);
}
// Store in database
rc = localMusicHandler_initDatabase();
if (rc == -1) {
// ERROR
} else if (rc == 0) {
// Table just made, songs not loaded in yet
for (int i = 0; i < localMusicHandler_audioItems.size(); i++) {
localMusicHandler_moveSongsToDatabase(i);
}
} else if (rc == 1) {
// Table was already made, assume songs were loaded in before
}
}
void localMusicHandler_scanDirectory(char* directory) {
struct dirent* dp;
DIR* dir = opendir(directory);
char path[1000]; // TODO Prevent potential buffer overflow
while ((dp = readdir(dir)) != NULL) {
if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0) {
sprintf(path, "%s/%s", directory, dp->d_name);
struct stat statbuf;
stat(path, &statbuf);
if (S_ISDIR(statbuf.st_mode)) {
localMusicHandler_scanDirectory(path);
} else if (S_ISREG(statbuf.st_mode)) {
localMusicHandler_allFiles.push_back(path);
}
}
}
closedir(dir);
}
void localMusicHandler_scanFile(int idx) {
AVFormatContext* ctx = NULL;
AVDictionaryEntry* tag = NULL;
static int rc = -1;
rc = avformat_open_input(&ctx, localMusicHandler_allFiles[idx].c_str(), NULL, NULL);
if (rc < 0) {
printf("[LocalMusicHandler] avformat_open_input() failed on idx %d (%s).\n", idx, localMusicHandler_allFiles[idx].c_str());
return;
}
// Ignore files that aren't audio
if (
strcmp(ctx->iformat->name, "lrc") == 0 || // .lrc files
strcmp(ctx->iformat->name, "image2") == 0 || // Pictures
strcmp(ctx->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0 // .mp4 files
) {
avformat_close_input(&ctx);
return;
}
localMusicHandler_AudioObject audioObject;
audioObject.path = localMusicHandler_allFiles[idx].c_str();
// Get file size (Using libav for this since the file is already opened using it)
audioObject.filesize = 0; // If the following fetch fails, set it to a known value beforehand
if (ctx->pb) {
uint64_t fsize = avio_size(ctx->pb);
if (fsize > 0) {
audioObject.filesize = fsize;
}
}
// Set all strings to known good values before fetching tags that possible don't exist
// NOTE: Honestly don't know if C++ does this by default, but I am not trusting it either way
audioObject.songTitle = "";
audioObject.albumTitle = "";
audioObject.artistTitle = "";
audioObject.track = "";
audioObject.totalTracks = "";
while ((tag = av_dict_get(ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
if (strcmp(tag->key, "title") == 0) {
audioObject.songTitle = tag->value;
} else if (strcmp(tag->key, "album") == 0) {
audioObject.albumTitle = tag->value;
} else if (strcmp(tag->key, "artist") == 0) {
// In ID3, multiple artists are stored as 'Artist A;Artist B'. Replace ';' with ', '
audioObject.artistTitle = std::regex_replace(tag->value, std::regex(";"), ", ");
} else if (strcmp(tag->key, "track") == 0) {
audioObject.track = tag->value;
} else if (strcmp(tag->key, "totaltracks") == 0) {
audioObject.totalTracks = tag->value;
}
}
localMusicHandler_audioItems.push_back(audioObject);
avformat_close_input(&ctx);
}
void localMusicHandler_generateUid(int idx) {
// TODO: Add other operating support here, such as in libopensubsonic/crypto.c
char uuidBytes[20];
char uuidString[40];
for (int i = 0; i < 20; i++) {
uuidBytes[i] = arc4random() & 0xFF;
}
snprintf(uuidString, 40, "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x",
uuidBytes[0], uuidBytes[1], uuidBytes[2], uuidBytes[3], uuidBytes[4],
uuidBytes[5], uuidBytes[6], uuidBytes[7], uuidBytes[8], uuidBytes[9],
uuidBytes[10], uuidBytes[11], uuidBytes[12], uuidBytes[13], uuidBytes[14],
uuidBytes[15], uuidBytes[16], uuidBytes[17], uuidBytes[18], uuidBytes[19]
);
localMusicHandler_audioItems[idx].uid = uuidString;
}
int localMusicHandler_initDatabase() {
// Code returns: -1 -> Error, 0 -> No songs in table, 1 -> Songs already in table (Table already existed)
static int createTable = 0;
static int rc = 0;
char* dbPath = NULL;
rc = asprintf(&dbPath, "%s/.config/ossp/local.db", getenv("HOME"));
if (rc == -1) {
printf("[LocalMusicHandler] asprintf() failed.\n");
return -1;
}
struct stat st;
if (stat(dbPath, &st) == 0) {
printf("[LocalMusicHandler] Database found, is %ld bytes.\n", st.st_size);
} else {
printf("[LocalMusicHandler] Database does not exist, creating.\n");
createTable = 1;
}
rc = sqlite3_open(dbPath, &sqlite_db);
if (rc) {
printf("[LocalMusicHandler] Could not create database: %s\n", sqlite3_errmsg(sqlite_db));
free(dbPath);
return -1;
} else {
printf("[LocalMusicHandler] Created/Opened database.\n");
}
if (createTable == 1) {
const char* sqlQuery = "CREATE TABLE local_songs(uid TEXT, songTitle TEXT, albumTitle TEXT, artistTitle TEXT, track TEXT, totalTracks TEXT, path TEXT, filesize INT)";
rc = sqlite3_exec(sqlite_db, sqlQuery, NULL, 0, &sqlite_errorMsg);
if (rc != SQLITE_OK) {
printf("[LocalMusicHandler] Could not make table: %s\n", sqlite_errorMsg);
sqlite3_free(sqlite_errorMsg);
free(dbPath);
return -1;
}
printf("[LocalMusicHandler] Made table.\n");
free(dbPath);
return 0;
}
free(dbPath);
return 1;
}
void localMusicHandler_moveSongsToDatabase(int idx) {
sqlite3_stmt* sqlite_stmt;
const char* sqlQuery = "INSERT INTO local_songs VALUES(?, ?, ?, ?, ?, ?, ?, ?)";
if (sqlite3_prepare_v2(sqlite_db, sqlQuery, -1, &sqlite_stmt, NULL) != SQLITE_OK) {
printf("[LocalMusicHandler] Prepare error: %s\n", sqlite3_errmsg(sqlite_db));
return; // TODO
}
sqlite3_bind_text(sqlite_stmt, 1, localMusicHandler_audioItems[idx].uid.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(sqlite_stmt, 2, localMusicHandler_audioItems[idx].songTitle.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(sqlite_stmt, 3, localMusicHandler_audioItems[idx].albumTitle.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(sqlite_stmt, 4, localMusicHandler_audioItems[idx].artistTitle.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(sqlite_stmt, 5, localMusicHandler_audioItems[idx].track.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(sqlite_stmt, 6, localMusicHandler_audioItems[idx].totalTracks.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(sqlite_stmt, 7, localMusicHandler_audioItems[idx].path.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(sqlite_stmt, 8, localMusicHandler_audioItems[idx].filesize);
if (sqlite3_step(sqlite_stmt) != SQLITE_DONE) {
printf("[LocalMusicHandler] Execution error: %s\n", sqlite3_errmsg(sqlite_db));
}
sqlite3_finalize(sqlite_stmt);
}

26
src/localMusicHandler.hpp Normal file
View File

@@ -0,0 +1,26 @@
/*
* OpenSubsonicPlayer
* Goldenkrew3000 2025
* License: GNU General Public License 3.0
*/
#ifndef _LOCALMUSICHANDLER_H
#define _LOCALMUSICHANDLER_H
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
void localMusicHandler_scan();
void localMusicHandler_scanDirectory(char* directory);
void localMusicHandler_scanFile(int idx);
void localMusicHandler_generateUid(int idx);
int localMusicHandler_initDatabase();
void localMusicHandler_moveSongsToDatabase(int idx);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // _LOCALMUSICHANDLER_H

View File

@@ -20,69 +20,105 @@
#include "playQueue.hpp" #include "playQueue.hpp"
// C++ interface for storing song queue data (C interface is in the header) // C++ interface for storing song queue data (C interface is in the header)
class SongObject { class OSSPQ_SongObject {
public: public:
std::string title; std::string title;
std::string album;
std::string artist; std::string artist;
std::string id; std::string id;
std::string streamUrl;
std::string coverArtUrl;
long duration; long duration;
int mode;
}; };
// NOTE: Acronym is OpenSubsonicPlayerQueue // NOTE: Acronym is OpenSubsonicPlayerQueue
std::deque<SongObject> OSSPQ_Items; std::deque<OSSPQ_SongObject> OSSPQ_SongQueue;
int internal_OSSPQ_AppendToEnd(char* title, char* artist, char* id, long duration) { int OSSPQ_AppendToEnd(char* title, char* album, char* artist, char* id, char* streamUrl, char* coverArtUrl, long duration, int mode) {
// Append a new song to the end of the queue OSSPQ_SongObject songObject;
printf("Title: %s\nArtist: %s\nID: %s\nDuration: %ld\n", title, artist, id, duration);
// TODO: Find a neater way of converting a C string to a C++ string?? if (mode == OSSPQ_MODE_OPENSUBSONIC || mode == OSSPQ_MODE_LOCALFILE) {
std::string cpp_title(title); // Both Local File and OpenSubsonic playback both take the same arguments
std::string cpp_artist(artist); if (mode == OSSPQ_MODE_OPENSUBSONIC) {
std::string cpp_id(id); printf("[OSSPQ] Appending OpenSubsonic Song: %s by %s.\n", title, artist);
SongObject songObject; } else if (mode == OSSPQ_MODE_LOCALFILE) {
songObject.title = cpp_title; printf("[OSSPQ] Appending Local Song: %s by %s.\n", title, artist);
songObject.artist = cpp_artist; }
songObject.id = cpp_id;
songObject.title = title;
songObject.album = album;
songObject.artist = artist;
songObject.id = id;
songObject.streamUrl = streamUrl;
songObject.coverArtUrl = coverArtUrl;
songObject.duration = duration; songObject.duration = duration;
OSSPQ_Items.push_back(songObject); songObject.mode = mode;
} else if (mode == OSSPQ_MODE_INTERNETRADIO) {
printf("[OSSPQ] Appending Internet Radio Station: %s.\n", title);
songObject.title = title;
songObject.id = id;
songObject.streamUrl = streamUrl;
}
OSSPQ_SongQueue.push_back(songObject);
return 0; return 0;
} }
OSSPQ_SongStruct* internal_OSSPQ_PopFromFront() { OSSPQ_SongStruct* OSSPQ_PopFromFront() {
if (OSSPQ_Items.empty()) { // Check if song queue is empty, if not, then pop oldest
// No items in play queue if (OSSPQ_SongQueue.empty()) {
return NULL; return NULL;
} }
OSSPQ_SongObject songObject = OSSPQ_SongQueue.front();
OSSPQ_SongQueue.pop_front();
// Pull the first song off the song queue // Allocate, initialize, and fill C compatible song object
SongObject songObject = OSSPQ_Items.front(); OSSPQ_SongStruct* songObjectC = (OSSPQ_SongStruct*)malloc(sizeof(OSSPQ_SongStruct));
OSSPQ_Items.pop_front(); songObjectC->title = NULL;
songObjectC->album = NULL;
songObjectC->artist = NULL;
songObjectC->id = NULL;
songObjectC->streamUrl = NULL;
songObjectC->coverArtUrl = NULL;
songObjectC->duration = 0;
songObjectC->mode = 0;
// Move song data into a C readable format if (songObject.mode == OSSPQ_MODE_OPENSUBSONIC || songObject.mode == OSSPQ_MODE_LOCALFILE) {
// NOTE: I am initializing the variables to a known value just in case there is missing information in songObject songObjectC->title = strdup(songObject.title.c_str());
OSSPQ_SongStruct* playQueueObject = (OSSPQ_SongStruct*)malloc(sizeof(OSSPQ_SongStruct)); songObjectC->album = strdup(songObject.album.c_str());
playQueueObject->title = NULL; songObjectC->artist = strdup(songObject.artist.c_str());
playQueueObject->artist = NULL; songObjectC->id = strdup(songObject.id.c_str());
playQueueObject->id = NULL; songObjectC->streamUrl = strdup(songObject.streamUrl.c_str());
playQueueObject->duration = 0; songObjectC->coverArtUrl = strdup(songObject.coverArtUrl.c_str());
songObjectC->duration = songObject.duration;
playQueueObject->title = strdup(songObject.title.c_str()); songObjectC->mode = songObject.mode;
playQueueObject->artist = strdup(songObject.artist.c_str()); } else if (songObject.mode == OSSPQ_MODE_INTERNETRADIO) {
playQueueObject->id = strdup(songObject.id.c_str()); songObjectC->title = strdup(songObject.title.c_str());
playQueueObject->duration = songObject.duration; songObjectC->id = strdup(songObject.id.c_str());
return playQueueObject; songObjectC->streamUrl = strdup(songObject.streamUrl.c_str());
} }
void internal_OSSPQ_FreeSongObject(OSSPQ_SongStruct* songObject) { return songObjectC;
if (songObject->title != NULL) { free(songObject->title); }
if (songObject->artist != NULL) { free(songObject->artist); }
if (songObject->id != NULL) { free(songObject->id); }
if (songObject != NULL) { free(songObject); }
} }
void OSSPQ_FreeSongObjectC(OSSPQ_SongStruct* songObjectC) {
printf("[OSSPQ] Freeing SongObjectC.\n");
if (songObjectC->title != NULL) { free(songObjectC->title); }
if (songObjectC->album != NULL) { free(songObjectC->album); }
if (songObjectC->artist != NULL) { free(songObjectC->artist); }
if (songObjectC->id != NULL) { free(songObjectC->id); }
if (songObjectC->streamUrl != NULL) { free(songObjectC->streamUrl); }
if (songObjectC->coverArtUrl != NULL) { free(songObjectC->coverArtUrl); }
if (songObjectC != NULL) { free(songObjectC); }
}
// TODO Bullshit functions for dealing with Imgui
char* internal_OSSPQ_GetTitleAtIndex(int idx) { char* internal_OSSPQ_GetTitleAtIndex(int idx) {
return (char*)OSSPQ_Items[idx].title.c_str(); return (char*)OSSPQ_SongQueue[idx].title.c_str();
} }
int internal_OSSPQ_GetItemCount() { int internal_OSSPQ_GetItemCount() {
return OSSPQ_Items.size(); return OSSPQ_SongQueue.size();
} }

View File

@@ -7,6 +7,10 @@
#ifndef _PLAYQUEUE_H #ifndef _PLAYQUEUE_H
#define _PLAYQUEUE_H #define _PLAYQUEUE_H
#define OSSPQ_MODE_OPENSUBSONIC 101
#define OSSPQ_MODE_LOCALFILE 102
#define OSSPQ_MODE_INTERNETRADIO 103
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif // __cplusplus #endif // __cplusplus
@@ -14,14 +18,20 @@ extern "C" {
// C interface for sending song queue data (C++ interface is in the C++ file) // C interface for sending song queue data (C++ interface is in the C++ file)
typedef struct { typedef struct {
char* title; char* title;
char* album;
char* artist; char* artist;
char* id; char* id;
char* streamUrl;
char* coverArtUrl;
long duration; long duration;
int mode;
} OSSPQ_SongStruct; } OSSPQ_SongStruct;
int internal_OSSPQ_AppendToEnd(char* title, char* artist, char* id, long duration); int OSSPQ_AppendToEnd(char* title, char* album, char* artist, char* id, char* streamUrl, char* coverArtUrl, long duration, int mode);
OSSPQ_SongStruct* internal_OSSPQ_PopFromFront(); OSSPQ_SongStruct* OSSPQ_PopFromFront();
void internal_OSSPQ_FreeSongObject(OSSPQ_SongStruct* songObject); void OSSPQ_FreeSongObjectC(OSSPQ_SongStruct* songObjectC);
// TODO
char* internal_OSSPQ_GetTitleAtIndex(int idx); char* internal_OSSPQ_GetTitleAtIndex(int idx);
int internal_OSSPQ_GetItemCount(); int internal_OSSPQ_GetItemCount();

View File

@@ -19,6 +19,7 @@
#include "libopensubsonic/endpoint_getAlbumList.h" #include "libopensubsonic/endpoint_getAlbumList.h"
#include "libopensubsonic/endpoint_getAlbum.h" #include "libopensubsonic/endpoint_getAlbum.h"
#include "libopensubsonic/endpoint_getLyricsBySongId.h" #include "libopensubsonic/endpoint_getLyricsBySongId.h"
#include "libopensubsonic/endpoint_getInternetRadioStations.h"
configHandler_config_t* configObj = NULL; configHandler_config_t* configObj = NULL;
@@ -179,6 +180,21 @@ void test_libopensubsonic_endpoint_getLyricsBySongId(void) {
opensubsonic_httpClient_URL_cleanup(&url); opensubsonic_httpClient_URL_cleanup(&url);
} }
void test_libopensubsonic_endpoint_getInternetRadioStations(void) {
logger_log_general(__func__, "Testing getInternetRadioStations endpoint.");
opensubsonic_httpClient_URL_t* url = malloc(sizeof(opensubsonic_httpClient_URL_t));
opensubsonic_httpClient_URL_prepare(&url);
url->endpoint = OPENSUBSONIC_ENDPOINT_GETINTERNETRADIOSTATIONS;
opensubsonic_httpClient_formUrl(&url);
opensubsonic_getInternetRadioStations_struct* getInternetRadioStationsStruct;
opensubsonic_httpClient_fetchResponse(&url, (void**)&getInternetRadioStationsStruct);
opensubsonic_getInternetRadioStations_struct_free(&getInternetRadioStationsStruct);
opensubsonic_httpClient_URL_cleanup(&url);
}
int main(void) { int main(void) {
int rc = 0; int rc = 0;
@@ -200,6 +216,7 @@ int main(void) {
test_libopensubsonic_endpoint_getAlbumList(); test_libopensubsonic_endpoint_getAlbumList();
test_libopensubsonic_endpoint_getAlbum(); test_libopensubsonic_endpoint_getAlbum();
test_libopensubsonic_endpoint_getLyricsBySongId(); test_libopensubsonic_endpoint_getLyricsBySongId();
test_libopensubsonic_endpoint_getInternetRadioStations();
// Free config file // Free config file
configHandler_Free(&configObj); configHandler_Free(&configObj);

380
src/socket.c Normal file
View File

@@ -0,0 +1,380 @@
/*
* OpenSubsonicPlayer
* Goldenkrew3000 2025
* License: GNU General Public License 3.0
* Info: Socket Handler
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "external/cJSON.h"
#include "configHandler.h"
#include "socket.h"
#define SOCKET_PATH "/tmp/ossp_socket" // TODO Make this configurable through the configuration file
static int server_fd = -1;
static int client_fd = -1;
static int rc = -1;
socklen_t client_len;
struct sockaddr_un server_addr;
struct sockaddr_un client_addr;
extern configHandler_config_t* configObj;
void socketHandler_read();
int SockSig_Length = 4;
const uint32_t OSSP_Sock_ACK = 0x7253FF87;
const uint32_t OSSP_Sock_CliConn = 0xE3566C2E;
const uint32_t OSSP_Sock_GetConnInfo = 0x8E4F6B01;
const uint32_t OSSP_Sock_Size = 0x1F7E8BCF;
const uint32_t OSSP_Sock_ClientGetReq = 0x210829CF;
void socket_setup() {
printf("[SocketHandler] Initializing.\n");
// Create server socket, and ensure that the socket file is removed
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
printf("[SocketHandler] Could not open server socket.\n");
// TODO
}
unlink(SOCKET_PATH);
// Bind server socket to SOCKET_PATH
memset(&server_addr, 0, sizeof(struct sockaddr_un));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
rc = bind(server_fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_un));
if (rc == -1) {
printf("[SocketHandler] Could not bind server socket.\n");
// TODO
}
rc = listen(server_fd, 5);
if (rc == -1) {
printf("[SocketHandler] Could not listen on server socket.\n");
// TODO
}
// Wait for connection
bool isServerWaiting = true;
while (isServerWaiting) {
client_fd = accept(server_fd, NULL, NULL); // TODO
if (client_fd == -1) {
printf("[SocketHandler] Error accepting connection.\n");
} else {
printf("[SocketHandler] Accepted connection.\n");
isServerWaiting = false;
}
}
//socketHandler_read();
socketHandler_initClientConnection();
printf("------------------------------\n");
// Receive ClientGetReq with Size
int size = 0;
socketHandler_receiveCliGetReq(&size);
printf("Size to alloc: %d bytes\n", size);
char* reqBuf = malloc(size);
// Send ACK
socketHandler_sendAck();
// Receive JSON data
socketHandler_receiveJson(&reqBuf, size);
printf("Received JSON: %s\n", reqBuf);
}
void socketHandler_cleanup() {
printf("[SocketHandler] Cleaning up.\n");
close(client_fd);
close(server_fd);
unlink(SOCKET_PATH);
}
// Byte Array to uint32_t Big Endian
uint32_t util_byteArrToUint32BE(char buf[]) {
// NOTE: I could use a combination of memcpy() and htons() here, but bitshifting is a single move
uint32_t retVal = 0x0;
retVal = buf[3] | buf[2] << 8 | buf[1] << 16 | buf[0] << 24;
return retVal;
}
// Byte Array to uint32_t Little Endian
uint32_t util_byteArrToUint32LE(char buf[]) {
uint32_t retVal = 0x0;
retVal = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24;
return retVal;
}
int socketHandler_initClientConnection() {
/*
* - Wait for client to send OSSP_Sock_CliConn
* - Send OSSP_Sock_ACK back
* - Wait for client to send OSSP_Sock_GetConnInfo
* - Send back OSSP_Sock_Size
* - Wait for client to send OSSP_Sock_ACK
* - Send JSON
* - Wait for client to send OSSP_Sock_ACK
*/
if (socketHandler_receiveCliConn() != 0) {
printf("[SocketHandler] initClientConnection() failed.\n");
return 1;
}
if (socketHandler_sendAck() != 0) {
printf("[SocketHandler] initClientConnection() failed.\n");
return 1;
}
if (socketHandler_receiveGetConnInfo() != 0) {
printf("[SockerHandler] initClientConnection() failed.\n");
return 1;
}
// Form JSON of connection info
char* serverAddr = NULL;
rc = asprintf(&serverAddr, "%s://%s", configObj->opensubsonic_protocol, configObj->opensubsonic_server);
if (rc == -1) {
printf("[SocketHandler] asprintf() failed.\n");
return 1;
}
cJSON* connInfoObj = cJSON_CreateObject();
cJSON_AddItemToObject(connInfoObj, "ossp_version", cJSON_CreateString(configObj->internal_ossp_version));
cJSON_AddItemToObject(connInfoObj, "server_addr", cJSON_CreateString(serverAddr));
char* connInfoStr = cJSON_PrintUnformatted(connInfoObj);
int connInfoLen = strlen(connInfoStr);
free(serverAddr);
cJSON_Delete(connInfoObj);
if (socketHandler_sendSize(connInfoLen) != 0) {
printf("[SockerHandler] initClientConnection() failed.\n");
return 1;
}
if (socketHandler_receiveAck() != 0) {
printf("[SockerHandler] initClientConnection() failed.\n");
return 1;
}
if (socketHandler_sendJson(connInfoStr, connInfoLen) != 0) {
printf("[SockerHandler] initClientConnection() failed.\n");
return 1;
}
if (socketHandler_receiveAck() != 0) {
printf("[SockerHandler] initClientConnection() failed.\n");
return 1;
}
return 0;
}
int socketHandler_sendAck() {
printf("[SocketHandler] Sending OSSP_Sock_ACK.\n");
rc = send(client_fd, &OSSP_Sock_ACK, SockSig_Length, 0);
if (rc != SockSig_Length) {
printf("[SocketHandler] Failed to send OSSP_Sock_ACK.\n");
return 1;
}
return 0;
}
int socketHandler_receiveAck() {
char buf[4] = { 0x00 };
rc = read(client_fd, buf, SockSig_Length);
if (rc != SockSig_Length) {
printf("[SocketHandler] Failed to receive OSSP_Sock_ACK (Signature is the wrong length).\n");
return 1;
}
uint32_t AckBE = util_byteArrToUint32LE(buf);
rc = memcmp(&AckBE, &OSSP_Sock_ACK, SockSig_Length);
if (rc != 0) {
printf("[SocketHandler] Failed to receive OSSP_Sock_ACK (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_ACK, AckBE);
return 1;
}
printf("[SocketHandler] Received OSSP_Sock_ACK.\n");
return 0;
}
int socketHandler_receiveCliConn() {
char buf[4] = { 0x00 };
rc = read(client_fd, buf, SockSig_Length);
if (rc != SockSig_Length) {
printf("[SocketHandler] Failed to receive OSSP_Sock_CliConn (Signature is the wrong length).\n");
return 1;
}
//uint32_t CliConnBE = util_byteArrToUint32BE(buf);
uint32_t CliConnBE = util_byteArrToUint32LE(buf);
rc = memcmp(&CliConnBE, &OSSP_Sock_CliConn, SockSig_Length);
if (rc != 0) {
printf("[SocketHandler] Failed to receive OSSP_Sock_CliConn (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_CliConn, CliConnBE);
return 1;
}
printf("[SocketHandler] Received OSSP_Sock_CliConn.\n");
return 0;
}
int socketHandler_receiveGetConnInfo() {
char buf[4] = { 0x00 };
rc = read(client_fd, buf, SockSig_Length);
if (rc != SockSig_Length) {
printf("[SocketHandler] Failed to receive OSSP_Sock_GetConnInfo (Signature is the wrong length).\n");
return 1;
}
uint32_t GetConnInfoBE = util_byteArrToUint32LE(buf);
rc = memcmp(&GetConnInfoBE, &OSSP_Sock_GetConnInfo, SockSig_Length);
if (rc != 0) {
printf("[SocketHandler] Failed to receive OSSP_Sock_GetConnInfo (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_GetConnInfo, GetConnInfoBE);
return 1;
}
printf("[SocketHandler] Received OSSP_Sock_GetConnInfo.\n");
return 0;
}
int socketHandler_sendSize(uint32_t size) {
printf("[SocketHandler] Sending OSSP_Sock_Size.\n");
OSSP_Sock_Size_t sizeData;
sizeData.signature = OSSP_Sock_Size;
sizeData.size = size;
rc = send(client_fd, (char*)&sizeData, 8, 0);
if (rc != 8) {
printf("[SocketHandler] Failed to send OSSP_Sock_Size.\n");
return 1;
}
return 0;
}
int socketHandler_receiveSize(uint32_t* size) {
char buf[8] = { 0x00 };
rc = read(client_fd, buf, 8);
if (rc != 8) {
printf("[SocketHandler] Failed to receive OSSP_Sock_Size (Invalid size).\n");
return 1;
}
OSSP_Sock_Size_t* sizeData = (OSSP_Sock_Size_t*)&buf;
rc = memcmp(&sizeData->signature, &OSSP_Sock_Size, 4);
if (rc != 0) {
printf("[SocketHandler] Failed to receive OSSP_Sock_Size (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_Size, sizeData->signature);
return 1;
}
printf("[SocketHandler] Received OSSP_Sock_Size.\n");
*size = sizeData->size;
return 0;
}
int socketHandler_receiveCliGetReq(int* size) {
char buf[8] = { 0x00 };
rc = read(client_fd, buf, 8);
if (rc != 8) {
printf("[SocketHandler] Failed to receive OSSP_Sock_ClientGetReq (Invalid size).\n");
return 1;
}
OSSP_Sock_ClientGetReq_t* clientGetReq = (OSSP_Sock_ClientGetReq_t*)&buf;
rc = memcmp(&clientGetReq->signature, &OSSP_Sock_ClientGetReq, 4);
if (rc != 0) {
printf("[SocketHandler] Failed to receive OSSP_Sock_ClientGetReq (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_ClientGetReq, clientGetReq->signature);
return 1;
}
printf("[SocketHandler] Received OSSP_Sock_ClientGetReq.\n");
*size = clientGetReq->size;
return 0;
}
int socketHandler_receiveJson(char** data, int size) {
rc = read(client_fd, *data, size);
if (rc != size) {
printf("[SocketHandler] Failed to receive generic JSON data.\n");
return 1;
}
printf("[SocketHandler] Received generic JSON data.\n");
return 0;
}
int socketHandler_sendJson(char* json, int size) {
printf("[SocketHandler] Sending JSON.\n");
rc = send(client_fd, json, size, 0);
if (rc != size) {
printf("[SocketHandler] Failed to send JSON.\n");
return 1;
}
return 0;
}
int socketHandler_processClientGetReq() {
// Step 1 - Client sends GetReq with size
// Step 2 - Server allocates memory for future client request
// Step 3 - Server responds ACK
// Step 4 - --
}

30
src/socket.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef _SOCKET_H
#define _SOCKET_H
#include <stdint.h>
typedef struct {
uint32_t signature;
uint32_t size;
} __attribute__((packed)) OSSP_Sock_Size_t;
typedef struct {
uint32_t signature;
uint32_t size;
} __attribute__((packed)) OSSP_Sock_ClientGetReq_t;
void socket_setup();
int socketHandler_initClientConnection();
int socketHandler_sendAck();
int socketHandler_receiveAck();
int socketHandler_receiveCliConn();
int socketHandler_receiveGetConnInfo();
int socketHandler_sendSize(uint32_t size);
int socketHandler_receiveSize(uint32_t* size);
int socketHandler_receiveCliGetReq(int* size);
int socketHandler_receiveJson(char** data, int size);
int socketHandler_sendJson(char* json, int size);
#endif