static void put_playlist(sp_playlist *playlist, struct evhttp_request *request, void *userdata) { // TODO(liesen): playlist there so that signatures of all handler methods are // the same, but do they have to be? assert(playlist == NULL); sp_session *session = userdata; json_error_t loads_error; json_t *playlist_json = read_request_body_json(request, &loads_error); if (playlist_json == NULL) { send_error(request, HTTP_BADREQUEST, loads_error.text ? loads_error.text : "Unable to parse JSON"); return; } // Parse playlist if (!json_is_object(playlist_json)) { send_error(request, HTTP_BADREQUEST, "Invalid playlist object"); return; } // Get title json_t *title_json = json_object_get(playlist_json, "title"); if (title_json == NULL) { json_decref(playlist_json); send_error(request, HTTP_BADREQUEST, "Invalid playlist: title is missing"); return; } if (!json_is_string(title_json)) { json_decref(playlist_json); send_error(request, HTTP_BADREQUEST, "Invalid playlist: title is not a string"); return; } char title[kMaxPlaylistTitleLength]; strncpy(title, json_string_value(title_json), kMaxPlaylistTitleLength); json_decref(playlist_json); // Add new playlist sp_playlistcontainer *pc = sp_session_playlistcontainer(session); playlist = sp_playlistcontainer_add_new_playlist(pc, title); if (playlist == NULL) { send_error(request, HTTP_ERROR, "Unable to create playlist"); } else { register_playlist_callbacks(playlist, request, &get_playlist, &playlist_state_changed_callbacks, NULL); } }
static void get_playlist_subscribers(sp_playlist *playlist, struct evhttp_request *request, void *userdata) { assert(sp_playlist_is_loaded(playlist)); sp_session *session = userdata; register_playlist_callbacks(playlist, request, &get_playlist_subscribers_callback, &playlist_subscribers_changed_callbacks, userdata); sp_playlist_update_subscribers(session, playlist); }
static void handle_user_request(struct evhttp_request *request, char *action, const char *canonical_username, sp_session *session) { if (action == NULL) { evhttp_send_error(request, HTTP_BADREQUEST, "Bad Request"); return; } int http_method = evhttp_request_get_command(request); switch (http_method) { case EVHTTP_REQ_GET: if (strncmp(action, "playlists", 9) == 0) { sp_playlistcontainer *pc = sp_session_publishedcontainer_for_user_create( session, canonical_username); if (sp_playlistcontainer_is_loaded(pc)) { get_user_playlists(pc, request, session); } else { register_playlistcontainer_callbacks(pc, request, &get_user_playlists, &playlistcontainer_loaded_callbacks, session); } } else if (strncmp(action, "starred", 7) == 0) { sp_playlist *playlist = sp_session_starred_for_user_create(session, canonical_username); if (sp_playlist_is_loaded(playlist)) { get_playlist(playlist, request, session); } else { register_playlist_callbacks(playlist, request, &get_playlist, &playlist_state_changed_callbacks, session); } } break; case EVHTTP_REQ_PUT: case EVHTTP_REQ_POST: if (strncmp(action, "inbox", 5) == 0) { put_user_inbox(canonical_username, request, session); } break; default: evhttp_send_error(request, HTTP_BADREQUEST, "Bad Request"); break; } }
static void put_playlist_remove_tracks(sp_playlist *playlist, struct evhttp_request *request, void *userdata) { // sp_session *session = userdata; const char *uri = evhttp_request_get_uri(request); struct evkeyvalq query_fields; evhttp_parse_query(uri, &query_fields); // Parse index const char *index_field = evhttp_find_header(&query_fields, "index"); int index; if (index_field == NULL || sscanf(index_field, "%d", &index) <= 0 || index < 0) { send_error(request, HTTP_BADREQUEST, "Bad parameter: index must be numeric"); return; } const char *count_field = evhttp_find_header(&query_fields, "count"); int count; if (count_field == NULL || sscanf(count_field, "%d", &count) <= 0 || count < 1) { send_error(request, HTTP_BADREQUEST, "Bad parameter: count must be numeric and positive"); return; } int *tracks = calloc(count, sizeof(int)); for (int i = 0; i < count; i++) tracks[i] = index + i; struct playlist_handler *handler = register_playlist_callbacks( playlist, request, &get_playlist, &playlist_update_in_progress_callbacks, NULL); sp_error remove_tracks_error = sp_playlist_remove_tracks(playlist, tracks, count); if (remove_tracks_error != SP_ERROR_OK) { sp_playlist_remove_callbacks(playlist, handler->playlist_callbacks, handler); free(handler); send_error_sp(request, HTTP_BADREQUEST, remove_tracks_error); } free(tracks); }
// Request dispatcher static void handle_request(struct evhttp_request *request, void *userdata) { evhttp_connection_set_timeout(request->evcon, 1); evhttp_add_header(evhttp_request_get_output_headers(request), "Server", "[email protected]/spotify-api-server"); // Check request method int http_method = evhttp_request_get_command(request); switch (http_method) { case EVHTTP_REQ_GET: case EVHTTP_REQ_PUT: case EVHTTP_REQ_POST: break; default: evhttp_send_error(request, HTTP_NOTIMPL, "Not Implemented"); return; } struct state *state = userdata; sp_session *session = state->session; char *uri = evhttp_decode_uri(evhttp_request_get_uri(request)); char *entity = strtok(uri, "/"); if (entity == NULL) { evhttp_send_error(request, HTTP_BADREQUEST, "Bad Request"); free(uri); return; } // Handle requests to /user/<user_name>/inbox if (strncmp(entity, "user", 4) == 0) { char *username = strtok(NULL, "/"); if (username == NULL) { evhttp_send_error(request, HTTP_BADREQUEST, "Bad Request"); free(uri); return; } char *action = strtok(NULL, "/"); handle_user_request(request, action, username, session); free(uri); return; } // Handle requests to /playlist/<playlist_uri>/<action> if (strncmp(entity, "playlist", 8) != 0) { evhttp_send_error(request, HTTP_BADREQUEST, "Bad Request"); free(uri); return; } char *playlist_uri = strtok(NULL, "/"); if (playlist_uri == NULL) { switch (http_method) { case EVHTTP_REQ_PUT: case EVHTTP_REQ_POST: put_playlist(NULL, request, session); break; default: send_error(request, HTTP_BADREQUEST, "Bad Request"); break; } free(uri); return; } sp_link *playlist_link = sp_link_create_from_string(playlist_uri); if (playlist_link == NULL) { send_error(request, HTTP_NOTFOUND, "Playlist link not found"); free(uri); return; } if (sp_link_type(playlist_link) != SP_LINKTYPE_PLAYLIST) { sp_link_release(playlist_link); send_error(request, HTTP_BADREQUEST, "Not a playlist link"); free(uri); return; } sp_playlist *playlist = sp_playlist_create(session, playlist_link); sp_link_release(playlist_link); if (playlist == NULL) { send_error(request, HTTP_NOTFOUND, "Playlist not found"); free(uri); return; } sp_playlist_add_ref(playlist); // Dispatch request char *action = strtok(NULL, "/"); // Default request handler handle_playlist_fn request_callback = ¬_implemented; void *callback_userdata = session; switch (http_method) { case EVHTTP_REQ_GET: { if (action == NULL) { // Send entire playlist request_callback = &get_playlist; } else if (strncmp(action, "collaborative", 13) == 0) { request_callback = &get_playlist_collaborative; } else if (strncmp(action, "subscribers", 11) == 0) { request_callback = &get_playlist_subscribers; } } break; case EVHTTP_REQ_PUT: case EVHTTP_REQ_POST: { if (strncmp(action, "add", 3) == 0) { request_callback = &put_playlist_add_tracks; } else if (strncmp(action, "remove", 6) == 0) { request_callback = &put_playlist_remove_tracks; } else if (strncmp(action, "patch", 5) == 0) { callback_userdata = state; request_callback = &put_playlist_patch; } } break; } if (sp_playlist_is_loaded(playlist)) { request_callback(playlist, request, callback_userdata); } else { // Wait for playlist to load register_playlist_callbacks(playlist, request, request_callback, &playlist_state_changed_callbacks, callback_userdata); } free(uri); }
static void put_playlist_patch(sp_playlist *playlist, struct evhttp_request *request, void *userdata) { struct state *state = userdata; struct evbuffer *buf = evhttp_request_get_input_buffer(request); size_t buflen = evbuffer_get_length(buf); if (buflen == 0) { send_error(request, HTTP_BADREQUEST, "No body"); return; } // Read request body json_error_t loads_error; json_t *json = read_request_body_json(request, &loads_error); if (json == NULL) { send_error(request, HTTP_BADREQUEST, loads_error.text ? loads_error.text : "Unable to parse JSON"); return; } if (!json_is_array(json)) { json_decref(json); send_error(request, HTTP_BADREQUEST, "Not valid JSON array"); return; } // Handle empty array int num_tracks = json_array_size(json); if (num_tracks == 0) { send_reply(request, HTTP_OK, "OK", NULL); return; } sp_track **tracks = calloc(num_tracks, sizeof (sp_track *)); int num_valid_tracks = 0; for (int i = 0; i < num_tracks; i++) { json_t *item = json_array_get(json, i); if (!json_is_string(item)) { json_decref(item); continue; } char *uri = strdup(json_string_value(item)); sp_link *track_link = sp_link_create_from_string(uri); free(uri); if (track_link == NULL) continue; if (sp_link_type(track_link) != SP_LINKTYPE_TRACK) { sp_link_release(track_link); continue; } sp_track *track = sp_link_as_track(track_link); if (track == NULL) continue; tracks[num_valid_tracks++] = track; } json_decref(json); // Bail if no tracks could be read from input if (num_valid_tracks == 0) { send_error(request, HTTP_BADREQUEST, "No valid tracks"); free(tracks); return; } tracks = realloc(tracks, num_valid_tracks * sizeof (sp_track *)); // Apply diff apr_pool_t *pool = state->pool; svn_diff_t *diff; svn_error_t *diff_error = diff_playlist_tracks(&diff, playlist, tracks, num_valid_tracks, pool); if (diff_error != SVN_NO_ERROR) { free(tracks); svn_handle_error2(diff_error, stderr, false, "Diff"); send_error(request, HTTP_BADREQUEST, "Search failed"); return; } svn_error_t *apply_error = diff_playlist_tracks_apply(diff, playlist, tracks, num_valid_tracks, state->session); if (apply_error != SVN_NO_ERROR) { free(tracks); svn_handle_error2(apply_error, stderr, false, "Updating playlist"); send_error(request, HTTP_BADREQUEST, "Could not apply diff"); return; } if (!sp_playlist_has_pending_changes(playlist)) { free(tracks); get_playlist(playlist, request, NULL); return; } free(tracks); register_playlist_callbacks(playlist, request, &get_playlist, &playlist_update_in_progress_callbacks, NULL); }
static void put_playlist_add_tracks(sp_playlist *playlist, struct evhttp_request *request, void *userdata) { sp_session *session = userdata; const char *uri = evhttp_request_get_uri(request); struct evkeyvalq query_fields; evhttp_parse_query(uri, &query_fields); // Parse index const char *index_field = evhttp_find_header(&query_fields, "index"); int index; if (index_field == NULL || sscanf(index_field, "%d", &index) <= 0) { index = sp_playlist_num_tracks(playlist); } // Parse JSON json_error_t loads_error; json_t *json = read_request_body_json(request, &loads_error); if (json == NULL) { send_error(request, HTTP_BADREQUEST, loads_error.text ? loads_error.text : "Unable to parse JSON"); return; } if (!json_is_array(json)) { json_decref(json); send_error(request, HTTP_BADREQUEST, "Not valid JSON array"); return; } // Handle empty array int num_tracks = json_array_size(json); if (num_tracks == 0) { send_reply(request, HTTP_OK, "OK", NULL); return; } sp_track **tracks = calloc(num_tracks, sizeof (sp_track *)); int num_valid_tracks = json_to_tracks(json, tracks, num_tracks); json_decref(json); // Bail if no tracks could be read from input if (num_valid_tracks == 0) { send_error(request, HTTP_BADREQUEST, "No valid tracks"); free(tracks); return; } struct playlist_handler *handler = register_playlist_callbacks( playlist, request, &get_playlist, &playlist_update_in_progress_callbacks, NULL); sp_error add_tracks_error = sp_playlist_add_tracks(playlist, tracks, num_valid_tracks, index, session); if (add_tracks_error != SP_ERROR_OK) { sp_playlist_remove_callbacks(playlist, handler->playlist_callbacks, handler); free(handler); send_error_sp(request, HTTP_BADREQUEST, add_tracks_error); } free(tracks); }