From 4a1bff500a6544846f17b75ce50eddf33d1b47fc Mon Sep 17 00:00:00 2001 From: chme Date: Tue, 24 Feb 2015 19:24:53 +0100 Subject: [PATCH 1/2] [mpd] fixes for commands noidle, list (handle special case with second argument 'album'), addid (sql error if virtual path contains character ') and fix error messages in various commands --- src/mpd.c | 54 ++++++++++++++++++++++++++++++---------------------- src/player.c | 11 +++++------ 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/mpd.c b/src/mpd.c index 22369f46..595122c4 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -885,7 +885,7 @@ mpd_command_next(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { DPRINTF(E_LOG, L_MPD, "Player returned an error for start after nextitem\n"); - ret = asprintf(errmsg, "Player returned an error for start after nextitem\n"); + ret = asprintf(errmsg, "Player returned an error for start after nextitem"); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1079,7 +1079,7 @@ mpd_command_previous(struct evbuffer *evbuf, int argc, char **argv, char **errms if (ret < 0) { DPRINTF(E_LOG, L_MPD, "Player returned an error for start after previtem\n"); - ret = asprintf(errmsg, "Player returned an error for start after previtem\n"); + ret = asprintf(errmsg, "Player returned an error for start after previtem"); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1124,7 +1124,7 @@ mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (songpos != 0) { DPRINTF(E_LOG, L_MPD, "Given song is not the current playing one, seeking is not supported\n"); - ret = asprintf(errmsg, "Given song is not the current playing one, seeking is not supported\n"); + ret = asprintf(errmsg, "Given song is not the current playing one, seeking is not supported"); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1138,7 +1138,7 @@ mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { DPRINTF(E_DBG, L_MPD, "Failed to seek current song to time %d msec\n", seek_target_msec); - ret = asprintf(errmsg, "Failed to seek current song to time %d msec\n", seek_target_msec); + ret = asprintf(errmsg, "Failed to seek current song to time %d msec", seek_target_msec); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1148,7 +1148,7 @@ mpd_command_seek(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { DPRINTF(E_LOG, L_MPD, "Player returned an error for start after seekcur\n"); - ret = asprintf(errmsg, "Player returned an error for start after seekcur\n"); + ret = asprintf(errmsg, "Player returned an error for start after seekcur"); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1195,7 +1195,7 @@ mpd_command_seekid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (status.id != id) { DPRINTF(E_LOG, L_MPD, "Given song is not the current playing one, seeking is not supported\n"); - ret = asprintf(errmsg, "Given song is not the current playing one, seeking is not supported\n"); + ret = asprintf(errmsg, "Given song is not the current playing one, seeking is not supported"); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1209,7 +1209,7 @@ mpd_command_seekid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { DPRINTF(E_DBG, L_MPD, "Failed to seek current song to time %d msec\n", seek_target_msec); - ret = asprintf(errmsg, "Failed to seek current song to time %d msec\n", seek_target_msec); + ret = asprintf(errmsg, "Failed to seek current song to time %d msec", seek_target_msec); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1219,7 +1219,7 @@ mpd_command_seekid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { DPRINTF(E_LOG, L_MPD, "Player returned an error for start after seekcur\n"); - ret = asprintf(errmsg, "Player returned an error for start after seekcur\n"); + ret = asprintf(errmsg, "Player returned an error for start after seekcur"); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1257,7 +1257,7 @@ mpd_command_seekcur(struct evbuffer *evbuf, int argc, char **argv, char **errmsg if (ret < 0) { DPRINTF(E_DBG, L_MPD, "Failed to seek current song to time %d msec\n", seek_target_msec); - ret = asprintf(errmsg, "Failed to seek current song to time %d msec\n", seek_target_msec); + ret = asprintf(errmsg, "Failed to seek current song to time %d msec", seek_target_msec); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1267,7 +1267,7 @@ mpd_command_seekcur(struct evbuffer *evbuf, int argc, char **argv, char **errmsg if (ret < 0) { DPRINTF(E_LOG, L_MPD, "Player returned an error for start after seekcur\n"); - ret = asprintf(errmsg, "Player returned an error for start after seekcur\n"); + ret = asprintf(errmsg, "Player returned an error for start after seekcur"); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1324,7 +1324,7 @@ mpd_command_add(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (!ps) { DPRINTF(E_DBG, L_MPD, "Failed to add song '%s' to playlist\n", argv[1]); - ret = asprintf(errmsg, "Failed to add song '%s' to playlist\n", argv[1]); + ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1373,7 +1373,7 @@ mpd_command_addid(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (!ps) { DPRINTF(E_DBG, L_MPD, "Failed to add song '%s' to playlist\n", argv[1]); - ret = asprintf(errmsg, "Failed to add song '%s' to playlist\n", argv[1]); + ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1567,7 +1567,7 @@ mpd_command_playlistid(struct evbuffer *evbuf, int argc, char **argv, char **err if (ret < 0) { DPRINTF(E_LOG, L_MPD, "Error adding media info for file with id: %d\n", queue->queue[i]); - ret = asprintf(errmsg, "Error adding media info for file with id: %d\n", queue->queue[i]); + ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue->queue[i]); queue_free(queue); @@ -1634,7 +1634,7 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e if (ret < 0) { DPRINTF(E_LOG, L_MPD, "Error adding media info for file with id: %d\n", queue->queue[i]); - ret = asprintf(errmsg, "Error adding media info for file with id: %d\n", queue->queue[i]); + ret = asprintf(errmsg, "Error adding media info for file with id: %d", queue->queue[i]); queue_free(queue); @@ -1701,7 +1701,7 @@ mpd_command_load(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) free_pli(pli, 0); DPRINTF(E_DBG, L_MPD, "Failed to add song '%s' to playlist\n", argv[1]); - ret = asprintf(errmsg, "Failed to add song '%s' to playlist\n", argv[1]); + ret = asprintf(errmsg, "Failed to add song '%s' to playlist", argv[1]); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); return ACK_ERROR_UNKNOWN; @@ -1779,6 +1779,11 @@ mpd_get_query_params_find(int argc, char **argv, struct query_params *qp) { c1 = sqlite3_mprintf("(f.title = '%q')", argv[i + 1]); } + else if (i == 0 && argc == 1) + { + // Special case: a single token is allowed if listing albums for an artist + c1 = sqlite3_mprintf("(f.album_artist = '%q')", argv[i]); + } else { DPRINTF(E_WARN, L_MPD, "Parameter '%s' is not supported by forked-daapd and will be ignored\n", argv[i]); @@ -1868,13 +1873,16 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) char *type; int ret; - if (argc < 2 || (argc % 2) != 0) + if (argc < 2 || ((argc % 2) != 0)) { - DPRINTF(E_LOG, L_MPD, "Missing argument(s) for command 'list'\n"); - ret = asprintf(errmsg, "Missing argument(s) for command 'list'"); - if (ret < 0) - DPRINTF(E_LOG, L_MPD, "Out of memory\n"); - return ACK_ERROR_ARG; + if (argc != 3 || (0 != strcasecmp(argv[1], "album"))) + { + DPRINTF(E_LOG, L_MPD, "Missing argument(s) for command 'list'\n"); + ret = asprintf(errmsg, "Missing argument(s) for command 'list'"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_ARG; + } } memset(&qp, 0, sizeof(struct query_params)); @@ -1976,7 +1984,7 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) if (ret < 0) { DPRINTF(E_LOG, L_MPD, "Could not start query for path '%s'\n", argv[1]); - ret = asprintf(errmsg, "Could not start query for path '%s'\n", argv[1]); + ret = asprintf(errmsg, "Could not start query for path '%s'", argv[1]); if (ret < 0) DPRINTF(E_LOG, L_MPD, "Out of memory\n"); @@ -2629,7 +2637,7 @@ static struct command mpd_handlers[] = .handler = mpd_command_idle }, { - .mpdcommand = "idle", + .mpdcommand = "noidle", .handler = mpd_command_noidle }, { diff --git a/src/player.c b/src/player.c index c876d6f5..448e937a 100644 --- a/src/player.c +++ b/src/player.c @@ -1028,7 +1028,6 @@ player_queue_make_mpd(char *path, int recursive) { struct query_params qp; struct player_source *ps; - int ret; memset(&qp, 0, sizeof(struct query_params)); @@ -1038,20 +1037,20 @@ player_queue_make_mpd(char *path, int recursive) if (recursive) { - ret = asprintf(&(qp.filter), "f.virtual_path LIKE '/%s%%'", path); - if (ret < 0) + qp.filter = sqlite3_mprintf("f.virtual_path LIKE '/%q%%'", path); + if (!qp.filter) DPRINTF(E_DBG, L_PLAYER, "Out of memory\n"); } else { - ret = asprintf(&(qp.filter), "f.virtual_path LIKE '/%s'", path); - if (ret < 0) + qp.filter = sqlite3_mprintf("f.virtual_path LIKE '/%q'", path); + if (!qp.filter) DPRINTF(E_DBG, L_PLAYER, "Out of memory\n"); } ps = player_queue_make(&qp, NULL); - free(qp.filter); + sqlite3_free(qp.filter); return ps; } From f41e7014e420c5c6048199a1c73f62c4a83e4573 Mon Sep 17 00:00:00 2001 From: chme Date: Mon, 23 Feb 2015 18:43:38 +0100 Subject: [PATCH 2/2] [mpd] add support for commands 'listplaylists', 'listplaylist', 'listplaylistinfo', 'ping'; add support for additional tags --- src/db.c | 5 + src/db.h | 2 + src/mpd.c | 374 +++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 336 insertions(+), 45 deletions(-) diff --git a/src/db.c b/src/db.c index edb130d3..7a976eb4 100644 --- a/src/db.c +++ b/src/db.c @@ -280,6 +280,7 @@ static const char *sort_clause[] = "ORDER BY f.album_sort ASC, f.disc ASC, f.track ASC", "ORDER BY f.album_artist_sort ASC", "ORDER BY f.type DESC, f.special_id ASC, f.title ASC", + "ORDER BY f.year ASC", }; static char *db_path; @@ -1546,6 +1547,10 @@ db_query_start(struct query_params *qp) ret = db_build_query_browse(qp, "composer", "composer_sort", &query); break; + case Q_BROWSE_YEARS: + ret = db_build_query_browse(qp, "year", "year", &query); + break; + default: DPRINTF(E_LOG, L_DB, "Unknown query type\n"); return -1; diff --git a/src/db.h b/src/db.h index e1370b91..82f5e9e9 100644 --- a/src/db.h +++ b/src/db.h @@ -22,6 +22,7 @@ enum sort_type { S_ALBUM, S_ARTIST, S_PLAYLIST, + S_YEAR, }; #define Q_F_BROWSE (1 << 15) @@ -38,6 +39,7 @@ enum query_type { Q_GROUP_ARTISTS = (1 << 8), Q_GROUP_ITEMS = (1 << 9), Q_GROUP_DIRS = Q_F_BROWSE | (1 << 10), + Q_BROWSE_YEARS = Q_F_BROWSE | (1 << 11), }; #define ARTWORK_UNKNOWN 0 diff --git a/src/mpd.c b/src/mpd.c index 595122c4..6ee6b291 100644 --- a/src/mpd.c +++ b/src/mpd.c @@ -169,6 +169,17 @@ exit_cb(int fd, short what, void *arg) event_add(g_exitev, NULL); } +static void +mpd_time(char *buffer, size_t bufferlen, time_t t) +{ + struct tm tm; + const struct tm *tm2 = gmtime_r(&t, &tm); + if (tm2 == NULL) + return; + + strftime(buffer, bufferlen, "%FT%TZ", tm2); +} + /* * Parses a rage argument of the form START:END (the END item is not included in the range) * into its start and end position. @@ -348,43 +359,50 @@ mpd_parse_args(char *args, int *argc, char **argv) static int mpd_add_mediainfo(struct evbuffer *evbuf, struct media_file_info *mfi, int pos_pl) { + char modified[32]; int ret; - if (pos_pl < 0) + mpd_time(modified, sizeof(modified), mfi->time_modified); + + ret = evbuffer_add_printf(evbuf, + "file: %s\n" + "Last-Modified: %s\n" + "Time: %d\n" + "Artist: %s\n" + "AlbumArtist: %s\n" + "ArtistSort: %s\n" + "AlbumArtistSort: %s\n" + "Album: %s\n" + "Title: %s\n" + "Track: %d\n" + "Date: %d\n" + "Genre: %s\n" + "Disc: %d\n", + (mfi->virtual_path + 1), + modified, + (mfi->song_length / 1000), + mfi->artist, + mfi->album_artist, + mfi->artist_sort, + mfi->album_artist_sort, + mfi->album, + mfi->title, + mfi->track, + mfi->year, + mfi->genre, + mfi->disc); + + if (pos_pl >= 0) { ret = evbuffer_add_printf(evbuf, - "file: %s\n" - "Time: %d\n" - "Artist: %s\n" - "Album: %s\n" - "Title: %s\n" - "Id: %d\n", - (mfi->virtual_path + 1), - (mfi->song_length / 1000), - mfi->artist, - mfi->album, - mfi->title, - mfi->id); - } - else - { - ret = evbuffer_add_printf(evbuf, - "file: %s\n" - "Time: %d\n" - "Artist: %s\n" - "Album: %s\n" - "Title: %s\n" - "Pos: %d\n" - "Id: %d\n", - (mfi->virtual_path + 1), - (mfi->song_length / 1000), - mfi->artist, - mfi->album, - mfi->title, - pos_pl, - mfi->id); + "Pos: %d\n", + pos_pl); } + ret = evbuffer_add_printf(evbuf, + "Id: %d\n", + mfi->id); + return ret; } @@ -445,9 +463,19 @@ mpd_add_mediainfo_byid(struct evbuffer *evbuf, int id, int pos_pl) static int mpd_add_db_media_file_info(struct evbuffer *evbuf, struct db_media_file_info *dbmfi) { + char modified[32]; + uint32_t time_modified; uint32_t songlength; int ret; + if (safe_atou32(dbmfi->time_modified, &time_modified) != 0) + { + DPRINTF(E_LOG, L_MPD, "Error converting time modified to uint32_t: %s\n", dbmfi->time_modified); + return -1; + } + + mpd_time(modified, sizeof(modified), time_modified); + if (safe_atou32(dbmfi->song_length, &songlength) != 0) { DPRINTF(E_LOG, L_MPD, "Error converting song length to uint32_t: %s\n", dbmfi->song_length); @@ -456,16 +484,32 @@ mpd_add_db_media_file_info(struct evbuffer *evbuf, struct db_media_file_info *db ret = evbuffer_add_printf(evbuf, "file: %s\n" + "Last-Modified: %s\n" "Time: %d\n" "Artist: %s\n" + "AlbumArtist: %s\n" + "ArtistSort: %s\n" + "AlbumArtistSort: %s\n" "Album: %s\n" "Title: %s\n" + "Track: %s\n" + "Date: %s\n" + "Genre: %s\n" + "Disc: %s\n" "Id: %s\n", (dbmfi->virtual_path + 1), + modified, (songlength / 1000), dbmfi->artist, + dbmfi->album_artist, + dbmfi->artist_sort, + dbmfi->album_artist_sort, dbmfi->album, dbmfi->title, + dbmfi->track, + dbmfi->year, + dbmfi->genre, + dbmfi->disc, dbmfi->id); return ret; @@ -1651,6 +1695,218 @@ mpd_command_playlistinfo(struct evbuffer *evbuf, int argc, char **argv, char **e return 0; } +static int +mpd_command_plchanges(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) +{ + return 0; +} + +/* + * Command handler function for 'listplaylist' + * Lists all songs in the playlist given by virtual-path in argv[1]. + */ +static int +mpd_command_listplaylist(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) +{ + char path[PATH_MAX]; + struct playlist_info *pli; + struct query_params qp; + struct db_media_file_info dbmfi; + int ret; + + if (argc < 2) + { + DPRINTF(E_LOG, L_MPD, "Missing argument for command 'load'\n"); + ret = asprintf(errmsg, "Missing argument for command 'load'"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_ARG; + } + + if (strncmp(argv[1], "/", 1) == 0) + { + ret = snprintf(path, sizeof(path), "%s", argv[1]); + } + else + { + ret = snprintf(path, sizeof(path), "/%s", argv[1]); + } + + pli = db_pl_fetch_byvirtualpath(path); + if (!pli) + { + DPRINTF(E_LOG, L_MPD, "Playlist not found for path '%s'\n", argv[1]); + ret = asprintf(errmsg, "Playlist not found for path '%s'", argv[1]); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_ARG; + } + + memset(&qp, 0, sizeof(struct query_params)); + + qp.type = Q_PLITEMS; + qp.idx_type = I_NONE; + qp.id = pli->id; + + ret = db_query_start(&qp); + if (ret < 0) + { + db_query_end(&qp); + + free_pli(pli, 0); + + DPRINTF(E_LOG, L_MPD, "Could not start query\n"); + ret = asprintf(errmsg, "Could not start query"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_UNKNOWN; + } + + while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id)) + { + evbuffer_add_printf(evbuf, + "file: %s\n", + (dbmfi.virtual_path + 1)); + } + + db_query_end(&qp); + + free_pli(pli, 0); + + return 0; +} + +/* + * Command handler function for 'listplaylistinfo' + * Lists all songs in the playlist given by virtual-path in argv[1] with metadata. + */ +static int +mpd_command_listplaylistinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) +{ + char path[PATH_MAX]; + struct playlist_info *pli; + struct query_params qp; + struct db_media_file_info dbmfi; + int ret; + + if (argc < 2) + { + DPRINTF(E_LOG, L_MPD, "Missing argument for command 'load'\n"); + ret = asprintf(errmsg, "Missing argument for command 'load'"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_ARG; + } + + if (strncmp(argv[1], "/", 1) == 0) + { + ret = snprintf(path, sizeof(path), "%s", argv[1]); + } + else + { + ret = snprintf(path, sizeof(path), "/%s", argv[1]); + } + + pli = db_pl_fetch_byvirtualpath(path); + if (!pli) + { + DPRINTF(E_LOG, L_MPD, "Playlist not found for path '%s'\n", argv[1]); + ret = asprintf(errmsg, "Playlist not found for path '%s'", argv[1]); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_ARG; + } + + memset(&qp, 0, sizeof(struct query_params)); + + qp.type = Q_PLITEMS; + qp.idx_type = I_NONE; + qp.id = pli->id; + + ret = db_query_start(&qp); + if (ret < 0) + { + db_query_end(&qp); + + free_pli(pli, 0); + + DPRINTF(E_LOG, L_MPD, "Could not start query\n"); + ret = asprintf(errmsg, "Could not start query"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_UNKNOWN; + } + + while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id)) + { + ret = mpd_add_db_media_file_info(evbuf, &dbmfi); + if (ret < 0) + { + DPRINTF(E_LOG, L_MPD, "Error adding song to the evbuffer, song id: %s\n", dbmfi.id); + } + } + + db_query_end(&qp); + + free_pli(pli, 0); + + return 0; +} + +/* + * Command handler function for 'listplaylists' + * Lists all playlists with their last modified date. + */ +static int +mpd_command_listplaylists(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) +{ + struct query_params qp; + struct db_playlist_info dbpli; + char modified[32]; + uint32_t time_modified; + int ret; + + memset(&qp, 0, sizeof(struct query_params)); + + qp.type = Q_PL; + qp.sort = S_PLAYLIST; + qp.idx_type = I_NONE; + qp.filter = "(f.type = 0)"; + + ret = db_query_start(&qp); + if (ret < 0) + { + db_query_end(&qp); + + DPRINTF(E_LOG, L_MPD, "Could not start query\n"); + ret = asprintf(errmsg, "Could not start query"); + if (ret < 0) + DPRINTF(E_LOG, L_MPD, "Out of memory\n"); + return ACK_ERROR_UNKNOWN; + } + + while (((ret = db_query_fetch_pl(&qp, &dbpli)) == 0) && (dbpli.id)) + { + if (safe_atou32(dbpli.db_timestamp, &time_modified) != 0) + { + DPRINTF(E_LOG, L_MPD, "Error converting time modified to uint32_t: %s\n", dbpli.db_timestamp); + return -1; + } + + mpd_time(modified, sizeof(modified), time_modified); + + evbuffer_add_printf(evbuf, + "playlist: %s\n" + "Last-Modified: %s\n", + (dbpli.virtual_path + 1), + modified); + } + + db_query_end(&qp); + + return 0; +} + /* * Command handler function for 'load' * Adds the playlist given by virtual-path in argv[1] to the queue. @@ -1871,6 +2127,8 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) struct query_params qp; struct db_group_info dbgri; char *type; + char *browse_item; + char *sort_item; int ret; if (argc < 2 || ((argc % 2) != 0)) @@ -1900,6 +2158,12 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) qp.sort = S_ALBUM; type = "Album: "; } + else if (0 == strcasecmp(argv[1], "date")) + { + qp.type = Q_BROWSE_YEARS; + qp.sort = S_YEAR; + type = "Date: "; + } else { DPRINTF(E_WARN, L_MPD, "Unsupported type argument for command 'list': %s\n", argv[1]); @@ -1925,12 +2189,25 @@ mpd_command_list(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) return ACK_ERROR_UNKNOWN; } - while ((ret = db_query_fetch_group(&qp, &dbgri)) == 0) + if (qp.type == Q_BROWSE_YEARS) { - evbuffer_add_printf(evbuf, - "%s%s\n", - type, - dbgri.itemname); + while (((ret = db_query_fetch_string_sort(&qp, &browse_item, &sort_item)) == 0) && (browse_item)) + { + evbuffer_add_printf(evbuf, + "%s%s\n", + type, + browse_item); + } + } + else + { + while ((ret = db_query_fetch_group(&qp, &dbgri)) == 0) + { + evbuffer_add_printf(evbuf, + "%s%s\n", + type, + dbgri.itemname); + } } db_query_end(&qp); @@ -1949,6 +2226,7 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) char parent[PATH_MAX]; struct filelist_info *fi; struct media_file_info *mfi; + char modified[32]; int ret; if (argc < 2 || strlen(argv[1]) == 0 @@ -1996,17 +2274,23 @@ mpd_command_lsinfo(struct evbuffer *evbuf, int argc, char **argv, char **errmsg) { if (fi->type == F_DIR) { + mpd_time(modified, sizeof(modified), fi->time_modified); + evbuffer_add_printf(evbuf, "directory: %s\n" - "Last-Modified: 2014-07-11T14:13:56Z\n", //TODO Send correct last modified timestamp - (fi->virtual_path + 1)); + "Last-Modified: %s\n", + (fi->virtual_path + 1), + modified); } else if (fi->type == F_PLAYLIST) { + mpd_time(modified, sizeof(modified), fi->time_modified); + evbuffer_add_printf(evbuf, "playlist: %s\n" - "Last-Modified: 2014-07-11T14:13:56Z\n", //TODO Send correct last modified timestamp - (fi->virtual_path + 1)); + "Last-Modified: %s\n", + (fi->virtual_path + 1), + modified); } else if (fi->type == F_FILE) { @@ -2794,10 +3078,12 @@ static struct command mpd_handlers[] = .mpdcommand = "playlistsearch", .handler = mpd_command_playlistsearch }, + */ { .mpdcommand = "plchanges", .handler = mpd_command_plchanges }, + /* { .mpdcommand = "plchangesposid", .handler = mpd_command_plchangesposid @@ -2839,7 +3125,6 @@ static struct command mpd_handlers[] = /* * Stored playlists */ - /* { .mpdcommand = "listplaylist", .handler = mpd_command_listplaylist @@ -2851,8 +3136,7 @@ static struct command mpd_handlers[] = { .mpdcommand = "listplaylists", .handler = mpd_command_listplaylists - },load - */ + }, { .mpdcommand = "load", .handler = mpd_command_load @@ -3006,11 +3290,11 @@ static struct command mpd_handlers[] = .mpdcommand = "password", .handler = mpd_command_password }, + */ { .mpdcommand = "ping", - .handler = mpd_command_ping + .handler = mpd_command_ignore }, - */ /* * Audio output devices