diff options
Diffstat (limited to '')
43 files changed, 3864 insertions, 334 deletions
diff --git a/src/playlist.c b/src/playlist.c index 660dd6a83..4a1e54814 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "playlist_internal.h" #include "playlist_save.h" #include "player_control.h" @@ -34,7 +35,8 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "playlist" -void playlistVersionChange(struct playlist *playlist) +void +playlist_increment_version_all(struct playlist *playlist) { queue_modify_all(&playlist->queue); idle_add(IDLE_PLAYLIST); @@ -61,16 +63,12 @@ playlist_init(struct playlist *playlist) playlist->queued = -1; playlist->current = -1; - - playlist->prev_elapsed = g_timer_new(); } void playlist_finish(struct playlist *playlist) { queue_finish(&playlist->queue); - - g_timer_destroy(playlist->prev_elapsed); } /** @@ -91,28 +89,34 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order) g_debug("queue song %i:\"%s\"", playlist->queued, uri); g_free(uri); - queueSong(song); + pc_enqueue_song(song); } /** - * Check if the player thread has already started playing the "queued" - * song. + * Called if the player thread has started playing the "queued" song. */ -static void syncPlaylistWithQueue(struct playlist *playlist) +static void +playlist_song_started(struct playlist *playlist) { - if (pc.next_song == NULL && playlist->queued != -1) { - /* queued song has started: copy queued to current, - and notify the clients */ + assert(pc.next_song == NULL); + assert(playlist->queued >= -1); - int current = playlist->current; - playlist->current = playlist->queued; - playlist->queued = -1; + /* queued song has started: copy queued to current, + and notify the clients */ - if(playlist->queue.consume) - deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current)); + int current = playlist->current; + playlist->current = playlist->queued; + playlist->queued = -1; - idle_add(IDLE_PLAYER); + /* Pause if we are in single mode. */ + if(playlist->queue.single && !playlist->queue.repeat) { + pc_set_pause(true); } + + if(playlist->queue.consume) + playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); + + idle_add(IDLE_PLAYER); } const struct song * @@ -179,7 +183,7 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev) } void -playPlaylistOrderNumber(struct playlist *playlist, int orderNum) +playlist_play_order(struct playlist *playlist, int orderNum) { struct song *song; char *uri; @@ -193,38 +197,45 @@ playPlaylistOrderNumber(struct playlist *playlist, int orderNum) g_debug("play %i:\"%s\"", orderNum, uri); g_free(uri); - playerPlay(song); + pc_play(song); playlist->current = orderNum; } static void -playPlaylistIfPlayerStopped(struct playlist *playlist); +playlist_resume_playback(struct playlist *playlist); /** * This is the "PLAYLIST" event handler. It is invoked by the player * thread whenever it requests a new queued song, or when it exits. */ -void syncPlayerAndPlaylist(struct playlist *playlist) +void +playlist_sync(struct playlist *playlist) { if (!playlist->playing) /* this event has reached us out of sync: we aren't playing anymore; ignore the event */ return; - if (getPlayerState() == PLAYER_STATE_STOP) + player_lock(); + enum player_state pc_state = pc_get_state(); + const struct song *pc_next_song = pc.next_song; + player_unlock(); + + if (pc_state == PLAYER_STATE_STOP) /* the player thread has stopped: check if playback should be restarted with the next song. That can happen if the playlist isn't filling the queue fast enough */ - playPlaylistIfPlayerStopped(playlist); + playlist_resume_playback(playlist); else { /* check if the player thread has already started playing the queued song */ - syncPlaylistWithQueue(playlist); + if (pc_next_song == NULL && playlist->queued != -1) + playlist_song_started(playlist); /* make sure the queued song is always set (if possible) */ - if (pc.next_song == NULL) + if (pc.next_song == NULL && playlist->queued < 0) playlist_update_queued_song(playlist, NULL); } } @@ -234,14 +245,14 @@ void syncPlayerAndPlaylist(struct playlist *playlist) * decide whether to re-start playback */ static void -playPlaylistIfPlayerStopped(struct playlist *playlist) +playlist_resume_playback(struct playlist *playlist) { enum player_error error; assert(playlist->playing); - assert(getPlayerState() == PLAYER_STATE_STOP); + assert(pc_get_state() == PLAYER_STATE_STOP); - error = getPlayerError(); + error = pc_get_error(); if (error == PLAYER_ERROR_NOERROR) playlist->error_count = 0; else @@ -252,37 +263,38 @@ playPlaylistIfPlayerStopped(struct playlist *playlist) playlist->error_count >= queue_length(&playlist->queue)) /* too many errors, or critical error: stop playback */ - stopPlaylist(playlist); + playlist_stop(playlist); else /* continue playback at the next song */ - nextSongInPlaylist(playlist); + playlist_next(playlist); } bool -getPlaylistRepeatStatus(const struct playlist *playlist) +playlist_get_repeat(const struct playlist *playlist) { return playlist->queue.repeat; } bool -getPlaylistRandomStatus(const struct playlist *playlist) +playlist_get_random(const struct playlist *playlist) { return playlist->queue.random; } bool -getPlaylistSingleStatus(const struct playlist *playlist) +playlist_get_single(const struct playlist *playlist) { return playlist->queue.single; } bool -getPlaylistConsumeStatus(const struct playlist *playlist) +playlist_get_consume(const struct playlist *playlist) { return playlist->queue.consume; } -void setPlaylistRepeatStatus(struct playlist *playlist, bool status) +void +playlist_set_repeat(struct playlist *playlist, bool status) { if (status == playlist->queue.repeat) return; @@ -297,7 +309,8 @@ void setPlaylistRepeatStatus(struct playlist *playlist, bool status) idle_add(IDLE_OPTIONS); } -static void orderPlaylist(struct playlist *playlist) +static void +playlist_order(struct playlist *playlist) { if (playlist->current >= 0) /* update playlist.current, order==position now */ @@ -307,7 +320,8 @@ static void orderPlaylist(struct playlist *playlist) queue_restore_order(&playlist->queue); } -void setPlaylistSingleStatus(struct playlist *playlist, bool status) +void +playlist_set_single(struct playlist *playlist, bool status) { if (status == playlist->queue.single) return; @@ -322,7 +336,8 @@ void setPlaylistSingleStatus(struct playlist *playlist, bool status) idle_add(IDLE_OPTIONS); } -void setPlaylistConsumeStatus(struct playlist *playlist, bool status) +void +playlist_set_consume(struct playlist *playlist, bool status) { if (status == playlist->queue.consume) return; @@ -331,7 +346,8 @@ void setPlaylistConsumeStatus(struct playlist *playlist, bool status) idle_add(IDLE_OPTIONS); } -void setPlaylistRandomStatus(struct playlist *playlist, bool status) +void +playlist_set_random(struct playlist *playlist, bool status) { const struct song *queued; @@ -366,14 +382,15 @@ void setPlaylistRandomStatus(struct playlist *playlist, bool status) } else playlist->current = -1; } else - orderPlaylist(playlist); + playlist_order(playlist); playlist_update_queued_song(playlist, queued); idle_add(IDLE_OPTIONS); } -int getPlaylistCurrentSong(const struct playlist *playlist) +int +playlist_get_current_song(const struct playlist *playlist) { if (playlist->current >= 0) return queue_order_to_position(&playlist->queue, @@ -382,19 +399,15 @@ int getPlaylistCurrentSong(const struct playlist *playlist) return -1; } -int getPlaylistNextSong(const struct playlist *playlist) +int +playlist_get_next_song(const struct playlist *playlist) { if (playlist->current >= 0) { - if (playlist->queue.single == 1) - { - if (playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, - playlist->current); - else - return -1; - } - if (playlist->current + 1 < (int)queue_length(&playlist->queue)) + if (playlist->queue.single == 1 && playlist->queue.repeat == 1) + return queue_order_to_position(&playlist->queue, + playlist->current); + else if (playlist->current + 1 < (int)queue_length(&playlist->queue)) return queue_order_to_position(&playlist->queue, playlist->current + 1); else if (playlist->queue.repeat == 1) @@ -405,19 +418,19 @@ int getPlaylistNextSong(const struct playlist *playlist) } unsigned long -getPlaylistVersion(const struct playlist *playlist) +playlist_get_version(const struct playlist *playlist) { return playlist->queue.version; } int -getPlaylistLength(const struct playlist *playlist) +playlist_get_length(const struct playlist *playlist) { return queue_length(&playlist->queue); } unsigned -getPlaylistSongId(const struct playlist *playlist, unsigned song) +playlist_get_song_id(const struct playlist *playlist, unsigned song) { return queue_position_to_id(&playlist->queue, song); } diff --git a/src/playlist.h b/src/playlist.h index 57b2450fa..3ba90ff91 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,7 +23,6 @@ #include "queue.h" #include <stdbool.h> -#include <stdio.h> #define PLAYLIST_COMMENT '#' @@ -82,21 +81,16 @@ struct playlist { * This variable is only valid if #playing is true. */ int queued; - - /** - * This timer tracks the time elapsed since the last "prev" - * command. If that is less than one second ago, "prev" jumps - * to the previous song instead of rewinding the current song. - */ - GTimer *prev_elapsed; }; /** the global playlist object */ extern struct playlist g_playlist; -void initPlaylist(void); +void +playlist_global_init(void); -void finishPlaylist(void); +void +playlist_global_finish(void); void playlist_init(struct playlist *playlist); @@ -116,11 +110,8 @@ playlist_get_queue(const struct playlist *playlist) return &playlist->queue; } -void readPlaylistState(FILE *); - -void savePlaylistState(FILE *); - -void clearPlaylist(struct playlist *playlist); +void +playlist_clear(struct playlist *playlist); #ifndef WIN32 /** @@ -133,90 +124,111 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid, #endif enum playlist_result -addToPlaylist(struct playlist *playlist, const char *file, unsigned *added_id); +playlist_append_uri(struct playlist *playlist, const char *file, + unsigned *added_id); enum playlist_result -addSongToPlaylist(struct playlist *playlist, +playlist_append_song(struct playlist *playlist, struct song *song, unsigned *added_id); enum playlist_result -deleteFromPlaylist(struct playlist *playlist, unsigned song); +playlist_delete(struct playlist *playlist, unsigned song); + +/** + * Deletes a range of songs from the playlist. + * + * @param start the position of the first song to delete + * @param end the position after the last song to delete + */ +enum playlist_result +playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end); enum playlist_result -deleteFromPlaylistById(struct playlist *playlist, unsigned song); +playlist_delete_id(struct playlist *playlist, unsigned song); -void stopPlaylist(struct playlist *playlist); +void +playlist_stop(struct playlist *playlist); enum playlist_result -playPlaylist(struct playlist *playlist, int song); +playlist_play(struct playlist *playlist, int song); enum playlist_result -playPlaylistById(struct playlist *playlist, int song); +playlist_play_id(struct playlist *playlist, int song); -void nextSongInPlaylist(struct playlist *playlist); +void +playlist_next(struct playlist *playlist); -void syncPlayerAndPlaylist(struct playlist *playlist); +void +playlist_sync(struct playlist *playlist); -void previousSongInPlaylist(struct playlist *playlist); +void +playlist_previous(struct playlist *playlist); -void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end); +void +playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end); void -deleteASongFromPlaylist(struct playlist *playlist, const struct song *song); +playlist_delete_song(struct playlist *playlist, const struct song *song); enum playlist_result -moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to); +playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to); enum playlist_result -moveSongInPlaylistById(struct playlist *playlist, unsigned id, int to); +playlist_move_id(struct playlist *playlist, unsigned id, int to); enum playlist_result -swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2); +playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2); enum playlist_result -swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2); +playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2); bool -getPlaylistRepeatStatus(const struct playlist *playlist); +playlist_get_repeat(const struct playlist *playlist); -void setPlaylistRepeatStatus(struct playlist *playlist, bool status); +void +playlist_set_repeat(struct playlist *playlist, bool status); bool -getPlaylistRandomStatus(const struct playlist *playlist); +playlist_get_random(const struct playlist *playlist); -void setPlaylistRandomStatus(struct playlist *playlist, bool status); +void +playlist_set_random(struct playlist *playlist, bool status); bool -getPlaylistSingleStatus(const struct playlist *playlist); +playlist_get_single(const struct playlist *playlist); -void setPlaylistSingleStatus(struct playlist *playlist, bool status); +void +playlist_set_single(struct playlist *playlist, bool status); bool -getPlaylistConsumeStatus(const struct playlist *playlist); +playlist_get_consume(const struct playlist *playlist); -void setPlaylistConsumeStatus(struct playlist *playlist, bool status); +void +playlist_set_consume(struct playlist *playlist, bool status); -int getPlaylistCurrentSong(const struct playlist *playlist); +int +playlist_get_current_song(const struct playlist *playlist); -int getPlaylistNextSong(const struct playlist *playlist); +int +playlist_get_next_song(const struct playlist *playlist); unsigned -getPlaylistSongId(const struct playlist *playlist, unsigned song); +playlist_get_song_id(const struct playlist *playlist, unsigned song); -int getPlaylistLength(const struct playlist *playlist); +int +playlist_get_length(const struct playlist *playlist); unsigned long -getPlaylistVersion(const struct playlist *playlist); +playlist_get_version(const struct playlist *playlist); enum playlist_result -seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time); +playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time); enum playlist_result -seekSongInPlaylistById(struct playlist *playlist, +playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time); -void playlistVersionChange(struct playlist *playlist); - -int is_valid_playlist_name(const char *utf8path); +void +playlist_increment_version_all(struct playlist *playlist); #endif diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c new file mode 100644 index 000000000..39513e710 --- /dev/null +++ b/src/playlist/asx_playlist_plugin.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/asx_playlist_plugin.h" +#include "playlist_plugin.h" +#include "input_stream.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "asx" + +/** + * This is the state object for the GLib XML parser. + */ +struct asx_parser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + GSList *songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, ENTRY, + } state; + + /** + * The current tag within the "entry" element. This is only + * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + enum tag_type tag; + + /** + * The current song. It is allocated after the "location" + * element. + */ + struct song *song; +}; + +static const gchar * +get_attribute(const gchar **attribute_names, const gchar **attribute_values, + const gchar *name) +{ + for (unsigned i = 0; attribute_names[i] != NULL; ++i) + if (g_ascii_strcasecmp(attribute_names[i], name) == 0) + return attribute_values[i]; + + return NULL; +} + +static void +asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct asx_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + if (g_ascii_strcasecmp(element_name, "entry") == 0) { + parser->state = ENTRY; + parser->song = song_remote_new("asx:"); + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case ENTRY: + if (g_ascii_strcasecmp(element_name, "ref") == 0) { + const gchar *href = get_attribute(attribute_names, + attribute_values, + "href"); + if (href != NULL) { + /* create new song object, and copy + the existing tag over; we cannot + replace the existing song's URI, + because that attribute is + immutable */ + struct song *song = song_remote_new(href); + + if (parser->song != NULL) { + song->tag = parser->song->tag; + parser->song->tag = NULL; + song_free(parser->song); + } + + parser->song = song; + } + } else if (g_ascii_strcasecmp(element_name, "author") == 0) + /* is that correct? or should it be COMPOSER + or PERFORMER? */ + parser->tag = TAG_ARTIST; + else if (g_ascii_strcasecmp(element_name, "title") == 0) + parser->tag = TAG_TITLE; + + break; + } +} + +static void +asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct asx_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case ENTRY: + if (g_ascii_strcasecmp(element_name, "entry") == 0) { + if (strcmp(parser->song->uri, "asx:") != 0) + parser->songs = g_slist_prepend(parser->songs, + parser->song); + else + song_free(parser->song); + + parser->state = ROOT; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void +asx_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct asx_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case ENTRY: + if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = tag_new(); + tag_add_item_n(parser->song->tag, parser->tag, + text, text_len); + } + + break; + } +} + +static const GMarkupParser asx_parser = { + .start_element = asx_start_element, + .end_element = asx_end_element, + .text = asx_text, +}; + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +asx_parser_destroy(gpointer data) +{ + struct asx_parser *parser = data; + + if (parser->state >= ENTRY) + song_free(parser->song); + + g_slist_foreach(parser->songs, song_free_callback, NULL); + g_slist_free(parser->songs); +} + +/* + * The playlist object + * + */ + +struct asx_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static struct playlist_provider * +asx_open_stream(struct input_stream *is) +{ + struct asx_parser parser = { + .songs = NULL, + .state = ROOT, + }; + struct asx_playlist *playlist; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the ASX XML file */ + + context = g_markup_parse_context_new(&asx_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, asx_parser_destroy); + + while (true) { + nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_markup_parse_context_free(context); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + } + + success = g_markup_parse_context_end_parse(context, &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + /* create a #asx_playlist object from the parsed song list */ + + playlist = g_new(struct asx_playlist, 1); + playlist_provider_init(&playlist->base, &asx_playlist_plugin); + playlist->songs = g_slist_reverse(parser.songs); + parser.songs = NULL; + + g_markup_parse_context_free(context); + + return &playlist->base; +} + +static void +asx_close(struct playlist_provider *_playlist) +{ + struct asx_playlist *playlist = (struct asx_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + g_free(playlist); +} + +static struct song * +asx_read(struct playlist_provider *_playlist) +{ + struct asx_playlist *playlist = (struct asx_playlist *)_playlist; + struct song *song; + + if (playlist->songs == NULL) + return NULL; + + song = playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, song); + + return song; +} + +static const char *const asx_suffixes[] = { + "asx", + NULL +}; + +static const char *const asx_mime_types[] = { + "video/x-ms-asf", + NULL +}; + +const struct playlist_plugin asx_playlist_plugin = { + .name = "asx", + + .open_stream = asx_open_stream, + .close = asx_close, + .read = asx_read, + + .suffixes = asx_suffixes, + .mime_types = asx_mime_types, +}; diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h new file mode 100644 index 000000000..7ce91aa41 --- /dev/null +++ b/src/playlist/asx_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin asx_playlist_plugin; + +#endif diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c new file mode 100644 index 000000000..b22712bc7 --- /dev/null +++ b/src/playlist/cue_playlist_plugin.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/cue_playlist_plugin.h" +#include "playlist_plugin.h" +#include "tag.h" +#include "song.h" +#include "cue/cue_tag.h" + +#include <glib.h> +#include <libcue/libcue.h> +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cue" + +struct cue_playlist { + struct playlist_provider base; + + struct Cd *cd; + + unsigned next; +}; + +static struct playlist_provider * +cue_playlist_open_uri(const char *uri) +{ + struct cue_playlist *playlist; + FILE *file; + struct Cd *cd; + + file = fopen(uri, "rt"); + if (file == NULL) + return NULL; + + cd = cue_parse_file(file); + fclose(file); + if (cd == NULL) + return NULL; + + playlist = g_new(struct cue_playlist, 1); + playlist_provider_init(&playlist->base, &cue_playlist_plugin); + playlist->cd = cd; + playlist->next = 1; + + return &playlist->base; +} + +static void +cue_playlist_close(struct playlist_provider *_playlist) +{ + struct cue_playlist *playlist = (struct cue_playlist *)_playlist; + + cd_delete(playlist->cd); + g_free(playlist); +} + +static struct song * +cue_playlist_read(struct playlist_provider *_playlist) +{ + struct cue_playlist *playlist = (struct cue_playlist *)_playlist; + struct Track *track; + struct tag *tag; + const char *filename; + struct song *song; + + track = cd_get_track(playlist->cd, playlist->next); + if (track == NULL) + return NULL; + + tag = cue_tag(playlist->cd, playlist->next); + if (tag == NULL) + return NULL; + + ++playlist->next; + + filename = track_get_filename(track); + if (*filename == 0 || filename[0] == '.' || + strchr(filename, '/') != NULL) { + /* unsafe characters found, bail out */ + tag_free(tag); + return NULL; + } + + song = song_remote_new(filename); + song->tag = tag; + song->start_ms = ((track_get_start(track) + + track_get_index(track, 1) + - track_get_zero_pre(track)) * 1000) / 75; + + /* append pregap of the next track to the end of this one */ + track = cd_get_track(playlist->cd, playlist->next); + if (track != NULL) + song->end_ms = ((track_get_start(track) + + track_get_index(track, 1) + - track_get_zero_pre(track)) * 1000) / 75; + else + song->end_ms = 0; + + return song; +} + +static const char *const cue_playlist_suffixes[] = { + "cue", + NULL +}; + +static const char *const cue_playlist_mime_types[] = { + "application/x-cue", + NULL +}; + +const struct playlist_plugin cue_playlist_plugin = { + .name = "cue", + + .open_uri = cue_playlist_open_uri, + .close = cue_playlist_close, + .read = cue_playlist_read, + + .suffixes = cue_playlist_suffixes, + .mime_types = cue_playlist_mime_types, +}; diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/cue_playlist_plugin.h new file mode 100644 index 000000000..c89ec55c5 --- /dev/null +++ b/src/playlist/cue_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin cue_playlist_plugin; + +#endif diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c new file mode 100644 index 000000000..9a04aa066 --- /dev/null +++ b/src/playlist/extm3u_playlist_plugin.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/extm3u_playlist_plugin.h" +#include "playlist_plugin.h" +#include "text_input_stream.h" +#include "uri.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <string.h> +#include <stdlib.h> + +struct extm3u_playlist { + struct playlist_provider base; + + struct text_input_stream *tis; +}; + +static struct playlist_provider * +extm3u_open_stream(struct input_stream *is) +{ + struct extm3u_playlist *playlist; + const char *line; + + playlist = g_new(struct extm3u_playlist, 1); + playlist->tis = text_input_stream_new(is); + + line = text_input_stream_read(playlist->tis); + if (line == NULL || strcmp(line, "#EXTM3U") != 0) { + /* no EXTM3U header: fall back to the plain m3u + plugin */ + text_input_stream_free(playlist->tis); + g_free(playlist); + return NULL; + } + + playlist_provider_init(&playlist->base, &extm3u_playlist_plugin); + return &playlist->base; +} + +static void +extm3u_close(struct playlist_provider *_playlist) +{ + struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist; + + text_input_stream_free(playlist->tis); + g_free(playlist); +} + +/** + * Parse a EXTINF line. + * + * @param line the rest of the input line after the colon + */ +static struct tag * +extm3u_parse_tag(const char *line) +{ + long duration; + char *endptr; + const char *name; + struct tag *tag; + + duration = strtol(line, &endptr, 10); + if (endptr[0] != ',') + /* malformed line */ + return NULL; + + if (duration < 0) + /* 0 means unknown duration */ + duration = 0; + + name = g_strchug(endptr + 1); + if (*name == 0 && duration == 0) + /* no information available; don't allocate a tag + object */ + return NULL; + + tag = tag_new(); + tag->time = duration; + + /* unfortunately, there is no real specification for the + EXTM3U format, so we must assume that the string after the + comma is opaque, and is just the song name*/ + if (*name != 0) + tag_add_item(tag, TAG_NAME, name); + + return tag; +} + +static struct song * +extm3u_read(struct playlist_provider *_playlist) +{ + struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist; + struct tag *tag = NULL; + const char *line; + struct song *song; + + do { + line = text_input_stream_read(playlist->tis); + if (line == NULL) { + if (tag != NULL) + tag_free(tag); + return NULL; + } + + if (g_str_has_prefix(line, "#EXTINF:")) { + if (tag != NULL) + tag_free(tag); + tag = extm3u_parse_tag(line + 8); + continue; + } + + while (*line != 0 && g_ascii_isspace(*line)) + ++line; + } while (line[0] == '#' || *line == 0); + + song = song_remote_new(line); + song->tag = tag; + return song; +} + +static const char *const extm3u_suffixes[] = { + "m3u", + NULL +}; + +static const char *const extm3u_mime_types[] = { + "audio/x-mpegurl", + NULL +}; + +const struct playlist_plugin extm3u_playlist_plugin = { + .name = "extm3u", + + .open_stream = extm3u_open_stream, + .close = extm3u_close, + .read = extm3u_read, + + .suffixes = extm3u_suffixes, + .mime_types = extm3u_mime_types, +}; diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h new file mode 100644 index 000000000..fa726c5f6 --- /dev/null +++ b/src/playlist/extm3u_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin extm3u_playlist_plugin; + +#endif diff --git a/src/playlist/flac_playlist_plugin.c b/src/playlist/flac_playlist_plugin.c new file mode 100644 index 000000000..9d66fb331 --- /dev/null +++ b/src/playlist/flac_playlist_plugin.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/flac_playlist_plugin.h" +#include "playlist_plugin.h" +#include "tag.h" +#include "song.h" +#include "decoder/flac_metadata.h" + +#include <FLAC/metadata.h> + +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "flac" + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + +struct flac_playlist { + struct playlist_provider base; + + char *uri; + + FLAC__StreamMetadata *cuesheet; + FLAC__StreamMetadata streaminfo; + + unsigned next_track; +}; + +static struct playlist_provider * +flac_playlist_open_uri(const char *uri) +{ + if (!g_path_is_absolute(uri)) + /* only local files supported */ + return NULL; + + FLAC__StreamMetadata *cuesheet; + if (!FLAC__metadata_get_cuesheet(uri, &cuesheet)) + return NULL; + + struct flac_playlist *playlist = g_new(struct flac_playlist, 1); + playlist_provider_init(&playlist->base, &flac_playlist_plugin); + + if (!FLAC__metadata_get_streaminfo(uri, &playlist->streaminfo)) { + FLAC__metadata_object_delete(playlist->cuesheet); + g_free(playlist); + return NULL; + } + + playlist->uri = g_strdup(uri); + playlist->cuesheet = cuesheet; + playlist->next_track = 0; + + return &playlist->base; +} + +static void +flac_playlist_close(struct playlist_provider *_playlist) +{ + struct flac_playlist *playlist = (struct flac_playlist *)_playlist; + + g_free(playlist->uri); + FLAC__metadata_object_delete(playlist->cuesheet); + g_free(playlist); +} + +static struct song * +flac_playlist_read(struct playlist_provider *_playlist) +{ + struct flac_playlist *playlist = (struct flac_playlist *)_playlist; + const FLAC__StreamMetadata_CueSheet *cs = + &playlist->cuesheet->data.cue_sheet; + + /* find the next audio track */ + + while (playlist->next_track < cs->num_tracks && + (cs->tracks[playlist->next_track].number > cs->num_tracks || + cs->tracks[playlist->next_track].type != 0)) + ++playlist->next_track; + + if (playlist->next_track >= cs->num_tracks) + return NULL; + + FLAC__uint64 start = cs->tracks[playlist->next_track].offset; + ++playlist->next_track; + FLAC__uint64 end = playlist->next_track < cs->num_tracks + ? cs->tracks[playlist->next_track].offset + : playlist->streaminfo.data.stream_info.total_samples; + + struct song *song = song_file_new(playlist->uri, NULL); + song->start_ms = start * 1000 / + playlist->streaminfo.data.stream_info.sample_rate; + song->end_ms = end * 1000 / + playlist->streaminfo.data.stream_info.sample_rate; + + char track[16]; + g_snprintf(track, sizeof(track), "%u", playlist->next_track); + song->tag = flac_tag_load(playlist->uri, track); + if (song->tag == NULL) + song->tag = tag_new(); + + song->tag->time = end > start + ? ((end - start - 1 + + playlist->streaminfo.data.stream_info.sample_rate) / + playlist->streaminfo.data.stream_info.sample_rate) + : 0; + + tag_clear_items_by_type(song->tag, TAG_TRACK); + tag_add_item(song->tag, TAG_TRACK, track); + + return song; +} + +static const char *const flac_playlist_suffixes[] = { + "flac", + NULL +}; + +static const char *const flac_playlist_mime_types[] = { + "application/flac", + "application/x-flac", + "audio/flac", + "audio/x-flac", + NULL +}; + +const struct playlist_plugin flac_playlist_plugin = { + .name = "flac", + + .open_uri = flac_playlist_open_uri, + .close = flac_playlist_close, + .read = flac_playlist_read, + + .suffixes = flac_playlist_suffixes, + .mime_types = flac_playlist_mime_types, +}; + +#else /* FLAC_API_VERSION_CURRENT <= 7 */ + +static bool +flac_playlist_init(G_GNUC_UNUSED const struct config_param *param) +{ + /* this libFLAC version does not support embedded CUE sheets; + disable this plugin */ + return false; +} + +const struct playlist_plugin flac_playlist_plugin = { + .name = "flac", + .init = flac_playlist_init, +}; + +#endif diff --git a/src/playlist/flac_playlist_plugin.h b/src/playlist/flac_playlist_plugin.h new file mode 100644 index 000000000..7b141264f --- /dev/null +++ b/src/playlist/flac_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin flac_playlist_plugin; + +#endif diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c new file mode 100644 index 000000000..afb3979d9 --- /dev/null +++ b/src/playlist/lastfm_playlist_plugin.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/lastfm_playlist_plugin.h" +#include "playlist_plugin.h" +#include "playlist_list.h" +#include "conf.h" +#include "uri.h" +#include "song.h" +#include "input_stream.h" +#include "glib_compat.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +struct lastfm_playlist { + struct playlist_provider base; + + struct input_stream *is; + + struct playlist_provider *xspf; +}; + +static struct { + char *user; + char *md5; +} lastfm_config; + +static bool +lastfm_init(const struct config_param *param) +{ + const char *user = config_get_block_string(param, "user", NULL); + const char *passwd = config_get_block_string(param, "password", NULL); + + if (user == NULL || passwd == NULL) { + g_debug("disabling the last.fm playlist plugin " + "because account is not configured"); + return false; + } + + lastfm_config.user = g_uri_escape_string(user, NULL, false); + +#if GLIB_CHECK_VERSION(2,16,0) + if (strlen(passwd) != 32) + lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, + passwd, strlen(passwd)); + else +#endif + lastfm_config.md5 = g_strdup(passwd); + + return true; +} + +static void +lastfm_finish(void) +{ + g_free(lastfm_config.user); + g_free(lastfm_config.md5); +} + +/** + * Simple data fetcher. + * @param url path or url of data to fetch. + * @return data fetched, or NULL on error. Must be freed with g_free. + */ +static char * +lastfm_get(const char *url) +{ + struct input_stream *input_stream; + GError *error = NULL; + int ret; + char buffer[4096]; + size_t length = 0, nbytes; + + input_stream = input_stream_open(url, &error); + if (input_stream == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + while (!input_stream->ready) { + ret = input_stream_buffer(input_stream, &error); + if (ret < 0) { + input_stream_close(input_stream); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + do { + nbytes = input_stream_read(input_stream, buffer + length, + sizeof(buffer) - length, &error); + if (nbytes == 0) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + if (input_stream_eof(input_stream)) + break; + + /* I/O error */ + input_stream_close(input_stream); + return NULL; + } + + length += nbytes; + } while (length < sizeof(buffer)); + + input_stream_close(input_stream); + return g_strndup(buffer, length); +} + +/** + * Ini-style value fetcher. + * @param response data through which to search. + * @param name name of value to search for. + * @return value for param name in param reponse or NULL on error. Free with g_free. + */ +static char * +lastfm_find(const char *response, const char *name) +{ + size_t name_length = strlen(name); + + while (true) { + const char *eol = strchr(response, '\n'); + if (eol == NULL) + return NULL; + + if (strncmp(response, name, name_length) == 0 && + response[name_length] == '=') { + response += name_length + 1; + return g_strndup(response, eol - response); + } + + response = eol + 1; + } +} + +static struct playlist_provider * +lastfm_open_uri(const char *uri) +{ + struct lastfm_playlist *playlist; + GError *error = NULL; + char *p, *q, *response, *session; + + /* handshake */ + + p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?" + "version=1.1.1&platform=linux&" + "username=", lastfm_config.user, "&" + "passwordmd5=", lastfm_config.md5, "&" + "debug=0&partner=", NULL); + response = lastfm_get(p); + g_free(p); + if (response == NULL) + return NULL; + + /* extract session id from response */ + + session = lastfm_find(response, "session"); + g_free(response); + if (session == NULL) { + g_warning("last.fm handshake failed"); + return NULL; + } + + q = g_uri_escape_string(session, NULL, false); + g_free(session); + session = q; + + g_debug("session='%s'", session); + + /* "adjust" last.fm radio */ + + if (strlen(uri) > 9) { + char *escaped_uri; + + escaped_uri = g_uri_escape_string(uri, NULL, false); + + p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?" + "session=", session, "&url=", escaped_uri, "&debug=0", + NULL); + g_free(escaped_uri); + + response = lastfm_get(p); + g_free(response); + g_free(p); + + if (response == NULL) { + g_free(session); + return NULL; + } + } + + /* create the playlist object */ + + playlist = g_new(struct lastfm_playlist, 1); + playlist_provider_init(&playlist->base, &lastfm_playlist_plugin); + + /* open the last.fm playlist */ + + p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?" + "sk=", session, "&discovery=0&desktop=1.5.1.31879", + NULL); + g_free(session); + + playlist->is = input_stream_open(p, &error); + g_free(p); + + if (playlist->is == NULL) { + if (error != NULL) { + g_warning("Failed to load XSPF playlist: %s", + error->message); + g_error_free(error); + } else + g_warning("Failed to load XSPF playlist"); + g_free(playlist); + return NULL; + } + + while (!playlist->is->ready) { + int ret = input_stream_buffer(playlist->is, &error); + if (ret < 0) { + input_stream_close(playlist->is); + g_free(playlist); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + if (ret == 0) + /* nothing was buffered - wait */ + g_usleep(10000); + } + + /* last.fm does not send a MIME type, we have to fake it here + :-( */ + g_free(playlist->is->mime); + playlist->is->mime = g_strdup("application/xspf+xml"); + + /* parse the XSPF playlist */ + + playlist->xspf = playlist_list_open_stream(playlist->is, NULL); + if (playlist->xspf == NULL) { + input_stream_close(playlist->is); + g_free(playlist); + g_warning("Failed to parse XSPF playlist"); + return NULL; + } + + return &playlist->base; +} + +static void +lastfm_close(struct playlist_provider *_playlist) +{ + struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; + + playlist_plugin_close(playlist->xspf); + input_stream_close(playlist->is); + g_free(playlist); +} + +static struct song * +lastfm_read(struct playlist_provider *_playlist) +{ + struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; + + return playlist_plugin_read(playlist->xspf); +} + +static const char *const lastfm_schemes[] = { + "lastfm", + NULL +}; + +const struct playlist_plugin lastfm_playlist_plugin = { + .name = "lastfm", + + .init = lastfm_init, + .finish = lastfm_finish, + .open_uri = lastfm_open_uri, + .close = lastfm_close, + .read = lastfm_read, + + .schemes = lastfm_schemes, +}; diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h new file mode 100644 index 000000000..363377c21 --- /dev/null +++ b/src/playlist/lastfm_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin lastfm_playlist_plugin; + +#endif diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c new file mode 100644 index 000000000..221c27277 --- /dev/null +++ b/src/playlist/m3u_playlist_plugin.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/m3u_playlist_plugin.h" +#include "playlist_plugin.h" +#include "text_input_stream.h" +#include "uri.h" +#include "song.h" + +#include <glib.h> + +struct m3u_playlist { + struct playlist_provider base; + + struct text_input_stream *tis; +}; + +static struct playlist_provider * +m3u_open_stream(struct input_stream *is) +{ + struct m3u_playlist *playlist = g_new(struct m3u_playlist, 1); + + playlist_provider_init(&playlist->base, &m3u_playlist_plugin); + playlist->tis = text_input_stream_new(is); + + return &playlist->base; +} + +static void +m3u_close(struct playlist_provider *_playlist) +{ + struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist; + + text_input_stream_free(playlist->tis); + g_free(playlist); +} + +static struct song * +m3u_read(struct playlist_provider *_playlist) +{ + struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist; + const char *line; + + do { + line = text_input_stream_read(playlist->tis); + if (line == NULL) + return NULL; + + while (*line != 0 && g_ascii_isspace(*line)) + ++line; + } while (line[0] == '#' || *line == 0); + + return song_remote_new(line); +} + +static const char *const m3u_suffixes[] = { + "m3u", + NULL +}; + +static const char *const m3u_mime_types[] = { + "audio/x-mpegurl", + NULL +}; + +const struct playlist_plugin m3u_playlist_plugin = { + .name = "m3u", + + .open_stream = m3u_open_stream, + .close = m3u_close, + .read = m3u_read, + + .suffixes = m3u_suffixes, + .mime_types = m3u_mime_types, +}; diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h new file mode 100644 index 000000000..98dcc4729 --- /dev/null +++ b/src/playlist/m3u_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin m3u_playlist_plugin; + +#endif diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c new file mode 100644 index 000000000..2a36f12f5 --- /dev/null +++ b/src/playlist/pls_playlist_plugin.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/pls_playlist_plugin.h" +#include "playlist_plugin.h" +#include "input_stream.h" +#include "uri.h" +#include "song.h" +#include "tag.h" +#include <glib.h> + +struct pls_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist) +{ + gchar *key; + gchar *value; + int length; + GError *error = NULL; + int num_entries = g_key_file_get_integer(keyfile, "playlist", + "NumberOfEntries", &error); + if (error) { + g_debug("Invalid PLS file: '%s'", error->message); + g_error_free(error); + error = NULL; + + /* Hack to work around shoutcast failure to comform to spec */ + num_entries = g_key_file_get_integer(keyfile, "playlist", + "numberofentries", &error); + if (error) { + g_error_free(error); + error = NULL; + } + } + + while (num_entries > 0) { + struct song *song; + key = g_strdup_printf("File%i", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + &error); + if(error) { + g_debug("Invalid PLS entry %s: '%s'",key, error->message); + g_error_free(error); + g_free(key); + return; + } + g_free(key); + + song = song_remote_new(value); + g_free(value); + + key = g_strdup_printf("Title%i", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + &error); + g_free(key); + if(error == NULL && value){ + if (song->tag == NULL) + song->tag = tag_new(); + tag_add_item(song->tag,TAG_TITLE, value); + } + /* Ignore errors? Most likely value not present */ + if(error) g_error_free(error); + error = NULL; + g_free(value); + + key = g_strdup_printf("Length%i", num_entries); + length = g_key_file_get_integer(keyfile, "playlist", key, + &error); + g_free(key); + if(error == NULL && length > 0){ + if (song->tag == NULL) + song->tag = tag_new(); + song->tag->time = length; + } + /* Ignore errors? Most likely value not present */ + if(error) g_error_free(error); + error = NULL; + + playlist->songs = g_slist_prepend(playlist->songs, song); + num_entries--; + } + +} + +static struct playlist_provider * +pls_open_stream(struct input_stream *is) +{ + GError *error = NULL; + size_t nbytes; + char buffer[1024]; + bool success; + GKeyFile *keyfile; + struct pls_playlist *playlist; + GString *kf_data = g_string_new(""); + + do { + nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_string_free(kf_data, TRUE); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + kf_data = g_string_append_len(kf_data, buffer,nbytes); + /* Limit to 64k */ + } while(kf_data->len < 65536); + + if (kf_data->len == 0) { + g_warning("KeyFile parser failed: No Data"); + g_string_free(kf_data, TRUE); + return NULL; + } + + keyfile = g_key_file_new(); + success = g_key_file_load_from_data(keyfile, + kf_data->str, kf_data->len, + G_KEY_FILE_NONE, &error); + + g_string_free(kf_data, TRUE); + + if (!success) { + g_warning("KeyFile parser failed: %s", error->message); + g_error_free(error); + g_key_file_free(keyfile); + return NULL; + } + + playlist = g_new(struct pls_playlist, 1); + playlist_provider_init(&playlist->base, &pls_playlist_plugin); + playlist->songs = NULL; + + pls_parser(keyfile, playlist); + + g_key_file_free(keyfile); + return &playlist->base; +} + + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +pls_close(struct playlist_provider *_playlist) +{ + struct pls_playlist *playlist = (struct pls_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + + g_free(playlist); + +} + +static struct song * +pls_read(struct playlist_provider *_playlist) +{ + struct pls_playlist *playlist = (struct pls_playlist *)_playlist; + struct song *song; + + if (playlist->songs == NULL) + return NULL; + + song = playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, song); + + return song; +} + +static const char *const pls_suffixes[] = { + "pls", + NULL +}; + +static const char *const pls_mime_types[] = { + "audio/x-scpls", + NULL +}; + +const struct playlist_plugin pls_playlist_plugin = { + .name = "pls", + + .open_stream = pls_open_stream, + .close = pls_close, + .read = pls_read, + + .suffixes = pls_suffixes, + .mime_types = pls_mime_types, +}; diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h new file mode 100644 index 000000000..c3bcf3f05 --- /dev/null +++ b/src/playlist/pls_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin pls_playlist_plugin; + +#endif diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c new file mode 100644 index 000000000..50f6bd1e7 --- /dev/null +++ b/src/playlist/xspf_playlist_plugin.c @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/xspf_playlist_plugin.h" +#include "playlist_plugin.h" +#include "input_stream.h" +#include "uri.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "xspf" + +/** + * This is the state object for the GLib XML parser. + */ +struct xspf_parser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + GSList *songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, PLAYLIST, TRACKLIST, TRACK, + LOCATION, + } state; + + /** + * The current tag within the "track" element. This is only + * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + enum tag_type tag; + + /** + * The current song. It is allocated after the "location" + * element. + */ + struct song *song; +}; + +static void +xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + G_GNUC_UNUSED const gchar **attribute_names, + G_GNUC_UNUSED const gchar **attribute_values, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct xspf_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + if (strcmp(element_name, "playlist") == 0) + parser->state = PLAYLIST; + + break; + + case PLAYLIST: + if (strcmp(element_name, "trackList") == 0) + parser->state = TRACKLIST; + + break; + + case TRACKLIST: + if (strcmp(element_name, "track") == 0) { + parser->state = TRACK; + parser->song = NULL; + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case TRACK: + if (strcmp(element_name, "location") == 0) + parser->state = LOCATION; + else if (strcmp(element_name, "title") == 0) + parser->tag = TAG_TITLE; + else if (strcmp(element_name, "creator") == 0) + /* TAG_COMPOSER would be more correct + according to the XSPF spec */ + parser->tag = TAG_ARTIST; + else if (strcmp(element_name, "annotation") == 0) + parser->tag = TAG_COMMENT; + else if (strcmp(element_name, "album") == 0) + parser->tag = TAG_ALBUM; + else if (strcmp(element_name, "trackNum") == 0) + parser->tag = TAG_TRACK; + + break; + + case LOCATION: + break; + } +} + +static void +xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct xspf_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case PLAYLIST: + if (strcmp(element_name, "playlist") == 0) + parser->state = ROOT; + + break; + + case TRACKLIST: + if (strcmp(element_name, "tracklist") == 0) + parser->state = PLAYLIST; + + break; + + case TRACK: + if (strcmp(element_name, "track") == 0) { + if (parser->song != NULL) + parser->songs = g_slist_prepend(parser->songs, + parser->song); + + parser->state = TRACKLIST; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + + case LOCATION: + parser->state = TRACK; + break; + } +} + +static void +xspf_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct xspf_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + case PLAYLIST: + case TRACKLIST: + break; + + case TRACK: + if (parser->song != NULL && + parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = tag_new(); + tag_add_item_n(parser->song->tag, parser->tag, + text, text_len); + } + + break; + + case LOCATION: + if (parser->song == NULL) { + char *uri = g_strndup(text, text_len); + parser->song = song_remote_new(uri); + g_free(uri); + } + + break; + } +} + +static const GMarkupParser xspf_parser = { + .start_element = xspf_start_element, + .end_element = xspf_end_element, + .text = xspf_text, +}; + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +xspf_parser_destroy(gpointer data) +{ + struct xspf_parser *parser = data; + + if (parser->state >= TRACK && parser->song != NULL) + song_free(parser->song); + + g_slist_foreach(parser->songs, song_free_callback, NULL); + g_slist_free(parser->songs); +} + +/* + * The playlist object + * + */ + +struct xspf_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static struct playlist_provider * +xspf_open_stream(struct input_stream *is) +{ + struct xspf_parser parser = { + .songs = NULL, + .state = ROOT, + }; + struct xspf_playlist *playlist; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the XSPF XML file */ + + context = g_markup_parse_context_new(&xspf_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, xspf_parser_destroy); + + while (true) { + nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_markup_parse_context_free(context); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + } + + success = g_markup_parse_context_end_parse(context, &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + /* create a #xspf_playlist object from the parsed song list */ + + playlist = g_new(struct xspf_playlist, 1); + playlist_provider_init(&playlist->base, &xspf_playlist_plugin); + playlist->songs = g_slist_reverse(parser.songs); + parser.songs = NULL; + + g_markup_parse_context_free(context); + + return &playlist->base; +} + +static void +xspf_close(struct playlist_provider *_playlist) +{ + struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + g_free(playlist); +} + +static struct song * +xspf_read(struct playlist_provider *_playlist) +{ + struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist; + struct song *song; + + if (playlist->songs == NULL) + return NULL; + + song = playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, song); + + return song; +} + +static const char *const xspf_suffixes[] = { + "xspf", + NULL +}; + +static const char *const xspf_mime_types[] = { + "application/xspf+xml", + NULL +}; + +const struct playlist_plugin xspf_playlist_plugin = { + .name = "xspf", + + .open_stream = xspf_open_stream, + .close = xspf_close, + .read = xspf_read, + + .suffixes = xspf_suffixes, + .mime_types = xspf_mime_types, +}; diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h new file mode 100644 index 000000000..ea832207d --- /dev/null +++ b/src/playlist/xspf_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin xspf_playlist_plugin; + +#endif diff --git a/src/playlist_any.c b/src/playlist_any.c new file mode 100644 index 000000000..39e21b178 --- /dev/null +++ b/src/playlist_any.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_any.h" +#include "playlist_list.h" +#include "playlist_mapper.h" +#include "uri.h" +#include "input_stream.h" + +#include <assert.h> + +static struct playlist_provider * +playlist_open_remote(const char *uri, struct input_stream **is_r) +{ + assert(uri_has_scheme(uri)); + + struct playlist_provider *playlist = playlist_list_open_uri(uri); + if (playlist != NULL) { + *is_r = NULL; + return playlist; + } + + GError *error = NULL; + struct input_stream *is = input_stream_open(uri, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("Failed to open %s: %s", + uri, error->message); + g_error_free(error); + } + + return NULL; + } + + playlist = playlist_list_open_stream(is, uri); + if (playlist == NULL) { + input_stream_close(is); + return NULL; + } + + *is_r = is; + return playlist; +} + +struct playlist_provider * +playlist_open_any(const char *uri, struct input_stream **is_r) +{ + return uri_has_scheme(uri) + ? playlist_open_remote(uri, is_r) + : playlist_mapper_open(uri, is_r); +} diff --git a/src/playlist_any.h b/src/playlist_any.h new file mode 100644 index 000000000..6fed97d15 --- /dev/null +++ b/src/playlist_any.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_ANY_H +#define MPD_PLAYLIST_ANY_H + +#include <stdbool.h> + +struct playlist_provider; +struct input_stream; + +/** + * Opens a playlist from the specified URI, which can be either an + * absolute remote URI (with a scheme) or a relative path to the + * music orplaylist directory. + * + * @param is_r on success, an input_stream object may be returned + * here, which must be closed after the playlist_provider object is + * freed + */ +struct playlist_provider * +playlist_open_any(const char *uri, struct input_stream **is_r); + +#endif diff --git a/src/playlist_control.c b/src/playlist_control.c index 4c156f0f5..ce9bc8442 100644 --- a/src/playlist_control.c +++ b/src/playlist_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "playlist_internal.h" #include "player_control.h" #include "idle.h" @@ -31,15 +32,7 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "playlist" -enum { - /** - * When the "prev" command is received, rewind the current - * track if this number of seconds has already elapsed. - */ - PLAYLIST_PREV_UNLESS_ELAPSED = 10, -}; - -void stopPlaylist(struct playlist *playlist) +void playlist_stop(struct playlist *playlist) { if (!playlist->playing) return; @@ -47,7 +40,7 @@ void stopPlaylist(struct playlist *playlist) assert(playlist->current >= 0); g_debug("stop"); - playerWait(); + pc_stop(); playlist->queued = -1; playlist->playing = false; @@ -69,11 +62,11 @@ void stopPlaylist(struct playlist *playlist) } } -enum playlist_result playPlaylist(struct playlist *playlist, int song) +enum playlist_result playlist_play(struct playlist *playlist, int song) { unsigned i = song; - clearPlayerError(); + pc_clear_error(); if (song == -1) { /* play any song ("current" song, or the first song */ @@ -84,7 +77,7 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song) if (playlist->playing) { /* already playing: unpause playback, just in case it was paused, and return */ - playerSetPause(0); + pc_set_pause(false); return PLAYLIST_RESULT_SUCCESS; } @@ -116,28 +109,28 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song) playlist->stop_on_error = false; playlist->error_count = 0; - playPlaylistOrderNumber(playlist, i); + playlist_play_order(playlist, i); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playPlaylistById(struct playlist *playlist, int id) +playlist_play_id(struct playlist *playlist, int id) { int song; if (id == -1) { - return playPlaylist(playlist, id); + return playlist_play(playlist, id); } song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playPlaylist(playlist, song); + return playlist_play(playlist, song); } void -nextSongInPlaylist(struct playlist *playlist) +playlist_next(struct playlist *playlist) { int next_order; int current; @@ -155,12 +148,8 @@ nextSongInPlaylist(struct playlist *playlist) next_order = queue_next_order(&playlist->queue, playlist->current); if (next_order < 0) { - /* cancel single */ - playlist->queue.single = false; - idle_add(IDLE_OPTIONS); - /* no song after this one: stop playback */ - stopPlaylist(playlist); + playlist_stop(playlist); /* reset "current song" */ playlist->current = -1; @@ -177,50 +166,42 @@ nextSongInPlaylist(struct playlist *playlist) queue_shuffle_order(&playlist->queue); /* note that playlist->current and playlist->queued are - now invalid, but playPlaylistOrderNumber() will + now invalid, but playlist_play_order() will discard them anyway */ } - playPlaylistOrderNumber(playlist, next_order); + playlist_play_order(playlist, next_order); } /* Consume mode removes each played songs. */ if(playlist->queue.consume) - deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current)); + playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); } -void previousSongInPlaylist(struct playlist *playlist) +void playlist_previous(struct playlist *playlist) { if (!playlist->playing) return; - if (g_timer_elapsed(playlist->prev_elapsed, NULL) >= 1.0 && - getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) { - /* re-start playing the current song (just like the - "prev" button on CD players) */ + assert(queue_length(&playlist->queue) > 0); - playPlaylistOrderNumber(playlist, playlist->current); + if (playlist->current > 0) { + /* play the preceding song */ + playlist_play_order(playlist, + playlist->current - 1); + } else if (playlist->queue.repeat) { + /* play the last song in "repeat" mode */ + playlist_play_order(playlist, + queue_length(&playlist->queue) - 1); } else { - if (playlist->current > 0) { - /* play the preceding song */ - playPlaylistOrderNumber(playlist, - playlist->current - 1); - } else if (playlist->queue.repeat) { - /* play the last song in "repeat" mode */ - playPlaylistOrderNumber(playlist, - queue_length(&playlist->queue) - 1); - } else { - /* re-start playing the current song if it's - the first one */ - playPlaylistOrderNumber(playlist, playlist->current); - } + /* re-start playing the current song if it's + the first one */ + playlist_play_order(playlist, playlist->current); } - - g_timer_start(playlist->prev_elapsed); } enum playlist_result -seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) +playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time) { const struct song *queued; unsigned i; @@ -236,7 +217,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) else i = song; - clearPlayerError(); + pc_clear_error(); playlist->stop_on_error = true; playlist->error_count = 0; @@ -244,7 +225,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) /* seeking is not within the current song - first start playing the new song */ - playPlaylistOrderNumber(playlist, i); + playlist_play_order(playlist, i); queued = NULL; } @@ -262,11 +243,11 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) } enum playlist_result -seekSongInPlaylistById(struct playlist *playlist, unsigned id, float seek_time) +playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time) { int song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return seekSongInPlaylist(playlist, song, seek_time); + return playlist_seek_song(playlist, song, seek_time); } diff --git a/src/playlist_database.c b/src/playlist_database.c new file mode 100644 index 000000000..0a8a6f139 --- /dev/null +++ b/src/playlist_database.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_database.h" +#include "playlist_vector.h" +#include "text_file.h" + +#include <string.h> +#include <stdlib.h> + +static GQuark +playlist_database_quark(void) +{ + return g_quark_from_static_string("playlist_database"); +} + +void +playlist_vector_save(FILE *fp, const struct playlist_vector *pv) +{ + for (const struct playlist_metadata *pm = pv->head; + pm != NULL; pm = pm->next) + fprintf(fp, PLAYLIST_META_BEGIN "%s\n" + "mtime: %li\n" + "playlist_end\n", + pm->name, (long)pm->mtime); +} + +bool +playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name, + GString *buffer, GError **error_r) +{ + struct playlist_metadata pm = { + .mtime = 0, + }; + char *line, *colon; + const char *value; + + while ((line = read_text_line(fp, buffer)) != NULL && + strcmp(line, "playlist_end") != 0) { + colon = strchr(line, ':'); + if (colon == NULL || colon == line) { + g_set_error(error_r, playlist_database_quark(), 0, + "unknown line in db: %s", line); + return false; + } + + *colon++ = 0; + value = g_strchug(colon); + + if (strcmp(line, "mtime") == 0) + pm.mtime = strtol(value, NULL, 10); + else { + g_set_error(error_r, playlist_database_quark(), 0, + "unknown line in db: %s", line); + return false; + } + } + + playlist_vector_update_or_add(pv, name, pm.mtime); + return true; +} diff --git a/src/playlist_database.h b/src/playlist_database.h new file mode 100644 index 000000000..7e114abdd --- /dev/null +++ b/src/playlist_database.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_DATABASE_H +#define MPD_PLAYLIST_DATABASE_H + +#include "check.h" + +#include <stdbool.h> +#include <stdio.h> +#include <glib.h> + +#define PLAYLIST_META_BEGIN "playlist_begin: " + +struct playlist_vector; + +void +playlist_vector_save(FILE *fp, const struct playlist_vector *pv); + +bool +playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name, + GString *buffer, GError **error_r); + +#endif diff --git a/src/playlist_edit.c b/src/playlist_edit.c index b83dc0933..c54b72750 100644 --- a/src/playlist_edit.c +++ b/src/playlist_edit.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ * */ +#include "config.h" #include "playlist_internal.h" #include "player_control.h" #include "database.h" @@ -35,16 +36,16 @@ #include <unistd.h> #include <stdlib.h> -static void incrPlaylistVersion(struct playlist *playlist) +static void playlist_increment_version(struct playlist *playlist) { queue_increment_version(&playlist->queue); idle_add(IDLE_PLAYLIST); } -void clearPlaylist(struct playlist *playlist) +void playlist_clear(struct playlist *playlist) { - stopPlaylist(playlist); + playlist_stop(playlist); /* make sure there are no references to allocated songs anymore */ @@ -58,7 +59,7 @@ void clearPlaylist(struct playlist *playlist) playlist->current = -1; - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); } #ifndef WIN32 @@ -86,41 +87,12 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid, if (song == NULL) return PLAYLIST_RESULT_NO_SUCH_SONG; - return addSongToPlaylist(playlist, song, added_id); + return playlist_append_song(playlist, song, added_id); } #endif -static struct song * -song_by_url(const char *url) -{ - struct song *song; - - song = db_get_song(url); - if (song != NULL) - return song; - - if (uri_has_scheme(url)) - return song_remote_new(url); - - return NULL; -} - -enum playlist_result -addToPlaylist(struct playlist *playlist, const char *url, unsigned *added_id) -{ - struct song *song; - - g_debug("add to playlist: %s", url); - - song = song_by_url(url); - if (song == NULL) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return addSongToPlaylist(playlist, song, added_id); -} - enum playlist_result -addSongToPlaylist(struct playlist *playlist, +playlist_append_song(struct playlist *playlist, struct song *song, unsigned *added_id) { const struct song *queued; @@ -147,7 +119,7 @@ addSongToPlaylist(struct playlist *playlist, queue_length(&playlist->queue)); } - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); @@ -157,8 +129,38 @@ addSongToPlaylist(struct playlist *playlist, return PLAYLIST_RESULT_SUCCESS; } +static struct song * +song_by_uri(const char *uri) +{ + struct song *song; + + song = db_get_song(uri); + if (song != NULL) + return song; + + if (uri_has_scheme(uri)) + return song_remote_new(uri); + + return NULL; +} + +enum playlist_result +playlist_append_uri(struct playlist *playlist, const char *uri, + unsigned *added_id) +{ + struct song *song; + + g_debug("add to playlist: %s", uri); + + song = song_by_uri(uri); + if (song == NULL) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return playlist_append_song(playlist, song, added_id); +} + enum playlist_result -swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2) +playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2) { const struct song *queued; @@ -188,7 +190,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2) playlist->current = song1; } - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); @@ -196,7 +198,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2) } enum playlist_result -swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2) +playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2) { int song1 = queue_id_to_position(&playlist->queue, id1); int song2 = queue_id_to_position(&playlist->queue, id2); @@ -204,28 +206,25 @@ swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2) if (song1 < 0 || song2 < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return swapSongsInPlaylist(playlist, song1, song2); + return playlist_swap_songs(playlist, song1, song2); } -enum playlist_result -deleteFromPlaylist(struct playlist *playlist, unsigned song) +static void +playlist_delete_internal(struct playlist *playlist, unsigned song, + const struct song **queued_p) { - const struct song *queued; unsigned songOrder; - if (song >= queue_length(&playlist->queue)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); + assert(song < queue_length(&playlist->queue)); songOrder = queue_position_to_order(&playlist->queue, song); if (playlist->playing && playlist->current == (int)songOrder) { - bool paused = getPlayerState() == PLAYER_STATE_PAUSE; + bool paused = pc_get_state() == PLAYER_STATE_PAUSE; /* the current song is going to be deleted: stop the player */ - playerWait(); + pc_stop(); playlist->playing = false; /* see which song is going to be played instead */ @@ -237,13 +236,13 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song) if (playlist->current >= 0 && !paused) /* play the song after the deleted one */ - playPlaylistOrderNumber(playlist, playlist->current); + playlist_play_order(playlist, playlist->current); else /* no songs left to play, stop playback completely */ - stopPlaylist(playlist); + playlist_stop(playlist); - queued = NULL; + *queued_p = NULL; } else if (playlist->current == (int)songOrder) /* there's a "current song" but we're not playing currently - clear "current" */ @@ -256,41 +255,80 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song) queue_delete(&playlist->queue, song); - incrPlaylistVersion(playlist); - /* update the "current" and "queued" variables */ if (playlist->current > (int)songOrder) { playlist->current--; } +} + +enum playlist_result +playlist_delete(struct playlist *playlist, unsigned song) +{ + const struct song *queued; + + if (song >= queue_length(&playlist->queue)) + return PLAYLIST_RESULT_BAD_RANGE; + + queued = playlist_get_queued_song(playlist); + + playlist_delete_internal(playlist, song, &queued); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -deleteFromPlaylistById(struct playlist *playlist, unsigned id) +playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end) +{ + const struct song *queued; + + if (start >= queue_length(&playlist->queue)) + return PLAYLIST_RESULT_BAD_RANGE; + + if (end > queue_length(&playlist->queue)) + end = queue_length(&playlist->queue); + + if (start >= end) + return PLAYLIST_RESULT_SUCCESS; + + queued = playlist_get_queued_song(playlist); + + do { + playlist_delete_internal(playlist, --end, &queued); + } while (end != start); + + playlist_increment_version(playlist); + playlist_update_queued_song(playlist, queued); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_delete_id(struct playlist *playlist, unsigned id) { int song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return deleteFromPlaylist(playlist, song); + return playlist_delete(playlist, song); } void -deleteASongFromPlaylist(struct playlist *playlist, const struct song *song) +playlist_delete_song(struct playlist *playlist, const struct song *song) { for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i) if (song == queue_get(&playlist->queue, i)) - deleteFromPlaylist(playlist, i); + playlist_delete(playlist, i); pc_song_deleted(song); } enum playlist_result -moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to) +playlist_move_range(struct playlist *playlist, + unsigned start, unsigned end, int to) { const struct song *queued; int currentSong; @@ -342,7 +380,7 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, } } - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); @@ -350,16 +388,17 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, } enum playlist_result -moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to) +playlist_move_id(struct playlist *playlist, unsigned id1, int to) { int song = queue_id_to_position(&playlist->queue, id1); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return moveSongRangeInPlaylist(playlist, song, song+1, to); + return playlist_move_range(playlist, song, song+1, to); } -void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end) +void +playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end) { const struct song *queued; @@ -399,7 +438,7 @@ void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end) queue_shuffle_range(&playlist->queue, start, end); - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); } diff --git a/src/playlist_global.c b/src/playlist_global.c index fa810bbc3..2833b62ed 100644 --- a/src/playlist_global.c +++ b/src/playlist_global.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "playlist.h" #include "playlist_state.h" #include "event_pipe.h" @@ -37,10 +38,11 @@ playlist_tag_event(void) static void playlist_event(void) { - syncPlayerAndPlaylist(&g_playlist); + playlist_sync(&g_playlist); } -void initPlaylist(void) +void +playlist_global_init(void) { playlist_init(&g_playlist); @@ -48,17 +50,8 @@ void initPlaylist(void) event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event); } -void finishPlaylist(void) +void +playlist_global_finish(void) { playlist_finish(&g_playlist); } - -void savePlaylistState(FILE *fp) -{ - playlist_state_save(fp, &g_playlist); -} - -void readPlaylistState(FILE *fp) -{ - playlist_state_restore(fp, &g_playlist); -} diff --git a/src/playlist_internal.h b/src/playlist_internal.h index af880691b..9d205188f 100644 --- a/src/playlist_internal.h +++ b/src/playlist_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -47,6 +47,6 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev); void -playPlaylistOrderNumber(struct playlist *playlist, int orderNum); +playlist_play_order(struct playlist *playlist, int orderNum); #endif diff --git a/src/playlist_list.c b/src/playlist_list.c new file mode 100644 index 000000000..9bd73ea93 --- /dev/null +++ b/src/playlist_list.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_list.h" +#include "playlist_plugin.h" +#include "playlist/extm3u_playlist_plugin.h" +#include "playlist/m3u_playlist_plugin.h" +#include "playlist/xspf_playlist_plugin.h" +#include "playlist/lastfm_playlist_plugin.h" +#include "playlist/pls_playlist_plugin.h" +#include "playlist/asx_playlist_plugin.h" +#include "playlist/cue_playlist_plugin.h" +#include "playlist/flac_playlist_plugin.h" +#include "input_stream.h" +#include "uri.h" +#include "utils.h" +#include "conf.h" +#include "glib_compat.h" +#include "mpd_error.h" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +static const struct playlist_plugin *const playlist_plugins[] = { + &extm3u_playlist_plugin, + &m3u_playlist_plugin, + &xspf_playlist_plugin, + &pls_playlist_plugin, + &asx_playlist_plugin, +#ifdef ENABLE_LASTFM + &lastfm_playlist_plugin, +#endif +#ifdef HAVE_CUE + &cue_playlist_plugin, +#endif +#ifdef HAVE_FLAC + &flac_playlist_plugin, +#endif + NULL +}; + +/** which plugins have been initialized successfully? */ +static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)]; + +/** + * Find the "playlist" configuration block for the specified plugin. + * + * @param plugin_name the name of the playlist plugin + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +playlist_plugin_config(const char *plugin_name) +{ + const struct config_param *param = NULL; + + assert(plugin_name != NULL); + + while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) { + const char *name = + config_get_block_string(param, "name", NULL); + if (name == NULL) + MPD_ERROR("playlist configuration without 'plugin' name in line %d", + param->line); + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return NULL; +} + +void +playlist_list_global_init(void) +{ + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + const struct config_param *param = + playlist_plugin_config(plugin->name); + + if (!config_get_block_bool(param, "enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + playlist_plugins_enabled[i] = + playlist_plugin_init(playlist_plugins[i], param); + } +} + +void +playlist_list_global_finish(void) +{ + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) + if (playlist_plugins_enabled[i]) + playlist_plugin_finish(playlist_plugins[i]); +} + +static struct playlist_provider * +playlist_list_open_uri_scheme(const char *uri, bool *tried) +{ + char *scheme; + struct playlist_provider *playlist = NULL; + + assert(uri != NULL); + + scheme = g_uri_parse_scheme(uri); + if (scheme == NULL) + return NULL; + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + assert(!tried[i]); + + if (playlist_plugins_enabled[i] && plugin->open_uri != NULL && + plugin->schemes != NULL && + string_array_contains(plugin->schemes, scheme)) { + playlist = playlist_plugin_open_uri(plugin, uri); + if (playlist != NULL) + break; + + tried[i] = true; + } + } + + g_free(scheme); + return playlist; +} + +static struct playlist_provider * +playlist_list_open_uri_suffix(const char *uri, const bool *tried) +{ + const char *suffix; + struct playlist_provider *playlist = NULL; + + assert(uri != NULL); + + suffix = uri_get_suffix(uri); + if (suffix == NULL) + return NULL; + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && !tried[i] && + plugin->open_uri != NULL && plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) { + playlist = playlist_plugin_open_uri(plugin, uri); + if (playlist != NULL) + break; + } + } + + return playlist; +} + +struct playlist_provider * +playlist_list_open_uri(const char *uri) +{ + struct playlist_provider *playlist; + /** this array tracks which plugins have already been tried by + playlist_list_open_uri_scheme() */ + bool tried[G_N_ELEMENTS(playlist_plugins) - 1]; + + assert(uri != NULL); + + memset(tried, false, sizeof(tried)); + + playlist = playlist_list_open_uri_scheme(uri, tried); + if (playlist == NULL) + playlist = playlist_list_open_uri_suffix(uri, tried); + + return playlist; +} + +static struct playlist_provider * +playlist_list_open_stream_mime2(struct input_stream *is, const char *mime) +{ + struct playlist_provider *playlist; + + assert(is != NULL); + assert(mime != NULL); + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && + plugin->open_stream != NULL && + plugin->mime_types != NULL && + string_array_contains(plugin->mime_types, mime)) { + /* rewind the stream, so each plugin gets a + fresh start */ + input_stream_seek(is, 0, SEEK_SET, NULL); + + playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != NULL) + return playlist; + } + } + + return NULL; +} + +static struct playlist_provider * +playlist_list_open_stream_mime(struct input_stream *is) +{ + assert(is->mime != NULL); + + const char *semicolon = strchr(is->mime, ';'); + if (semicolon == NULL) + return playlist_list_open_stream_mime2(is, is->mime); + + if (semicolon == is->mime) + return NULL; + + /* probe only the portion before the semicolon*/ + char *mime = g_strndup(is->mime, semicolon - is->mime); + struct playlist_provider *playlist = + playlist_list_open_stream_mime2(is, mime); + g_free(mime); + return playlist; +} + +static struct playlist_provider * +playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix) +{ + struct playlist_provider *playlist; + + assert(is != NULL); + assert(suffix != NULL); + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && + plugin->open_stream != NULL && + plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) { + /* rewind the stream, so each plugin gets a + fresh start */ + input_stream_seek(is, 0, SEEK_SET, NULL); + + playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != NULL) + return playlist; + } + } + + return NULL; +} + +struct playlist_provider * +playlist_list_open_stream(struct input_stream *is, const char *uri) +{ + const char *suffix; + struct playlist_provider *playlist; + + GError *error = NULL; + while (!is->ready) { + int ret = input_stream_buffer(is, &error); + if (ret < 0) { + input_stream_close(is); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + if (is->mime != NULL) { + playlist = playlist_list_open_stream_mime(is); + if (playlist != NULL) + return playlist; + } + + suffix = uri != NULL ? uri_get_suffix(uri) : NULL; + if (suffix != NULL) { + playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist != NULL) + return playlist; + } + + return NULL; +} + +bool +playlist_suffix_supported(const char *suffix) +{ + assert(suffix != NULL); + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) + return true; + } + + return false; +} + +struct playlist_provider * +playlist_list_open_path(const char *path_fs, struct input_stream **is_r) +{ + GError *error = NULL; + const char *suffix; + struct input_stream *is; + struct playlist_provider *playlist; + + assert(path_fs != NULL); + + suffix = uri_get_suffix(path_fs); + if (suffix == NULL || !playlist_suffix_supported(suffix)) + return NULL; + + is = input_stream_open(path_fs, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + while (!is->ready) { + int ret = input_stream_buffer(is, &error); + if (ret < 0) { + input_stream_close(is); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist != NULL) + *is_r = is; + else + input_stream_close(is); + + return playlist; +} diff --git a/src/playlist_list.h b/src/playlist_list.h new file mode 100644 index 000000000..3710589a2 --- /dev/null +++ b/src/playlist_list.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_LIST_H +#define MPD_PLAYLIST_LIST_H + +#include <stdbool.h> + +struct playlist_provider; +struct input_stream; + +/** + * Initializes all playlist plugins. + */ +void +playlist_list_global_init(void); + +/** + * Deinitializes all playlist plugins. + */ +void +playlist_list_global_finish(void); + +/** + * Opens a playlist by its URI. + */ +struct playlist_provider * +playlist_list_open_uri(const char *uri); + +/** + * Opens a playlist from an input stream. + * + * @param is an #input_stream object which is open and ready + * @param uri optional URI which was used to open the stream; may be + * used to select the appropriate playlist plugin + */ +struct playlist_provider * +playlist_list_open_stream(struct input_stream *is, const char *uri); + +/** + * Determines if there is a playlist plugin which can handle the + * specified file name suffix. + */ +bool +playlist_suffix_supported(const char *suffix); + +/** + * Opens a playlist from a local file. + * + * @param path_fs the path of the playlist file + * @param is_r on success, an input_stream object is returned here, + * which must be closed after the playlist_provider object is freed + * @return a playlist, or NULL on error + */ +struct playlist_provider * +playlist_list_open_path(const char *path_fs, struct input_stream **is_r); + +#endif diff --git a/src/playlist_mapper.c b/src/playlist_mapper.c new file mode 100644 index 000000000..99b322073 --- /dev/null +++ b/src/playlist_mapper.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_mapper.h" +#include "playlist_list.h" +#include "stored_playlist.h" +#include "mapper.h" +#include "uri.h" + +#include <assert.h> + +static struct playlist_provider * +playlist_open_path(const char *path_fs, struct input_stream **is_r) +{ + struct playlist_provider *playlist; + + playlist = playlist_list_open_uri(path_fs); + if (playlist != NULL) + *is_r = NULL; + else + playlist = playlist_list_open_path(path_fs, is_r); + + return playlist; +} + +/** + * Load a playlist from the configured playlist directory. + */ +static struct playlist_provider * +playlist_open_in_playlist_dir(const char *uri, struct input_stream **is_r) +{ + char *path_fs; + + assert(spl_valid_name(uri)); + + const char *playlist_directory_fs = map_spl_path(); + if (playlist_directory_fs == NULL) + return NULL; + + path_fs = g_build_filename(playlist_directory_fs, uri, NULL); + + struct playlist_provider *playlist = playlist_open_path(path_fs, is_r); + g_free(path_fs); + + return playlist; +} + +/** + * Load a playlist from the configured music directory. + */ +static struct playlist_provider * +playlist_open_in_music_dir(const char *uri, struct input_stream **is_r) +{ + char *path_fs; + + assert(uri_safe_local(uri)); + + path_fs = map_uri_fs(uri); + if (path_fs == NULL) + return NULL; + + struct playlist_provider *playlist = playlist_open_path(path_fs, is_r); + g_free(path_fs); + + return playlist; +} + +struct playlist_provider * +playlist_mapper_open(const char *uri, struct input_stream **is_r) +{ + struct playlist_provider *playlist; + + if (spl_valid_name(uri)) { + playlist = playlist_open_in_playlist_dir(uri, is_r); + if (playlist != NULL) + return playlist; + } + + if (uri_safe_local(uri)) { + playlist = playlist_open_in_music_dir(uri, is_r); + if (playlist != NULL) + return playlist; + } + + return NULL; +} diff --git a/src/playlist_mapper.h b/src/playlist_mapper.h new file mode 100644 index 000000000..b98af1b13 --- /dev/null +++ b/src/playlist_mapper.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_MAPPER_H +#define MPD_PLAYLIST_MAPPER_H + +struct input_stream; + +/** + * Opens a playlist from an URI relative to the playlist or music + * directory. + * + * @param is_r on success, an input_stream object may be returned + * here, which must be closed after the playlist_provider object is + * freed + */ +struct playlist_provider * +playlist_mapper_open(const char *uri, struct input_stream **is_r); + +#endif diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h new file mode 100644 index 000000000..3d840573e --- /dev/null +++ b/src/playlist_plugin.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_PLUGIN_H + +#include <stdbool.h> +#include <stddef.h> + +struct config_param; +struct input_stream; +struct tag; + +/** + * An object which provides the contents of a playlist. + */ +struct playlist_provider { + const struct playlist_plugin *plugin; +}; + +static inline void +playlist_provider_init(struct playlist_provider *playlist, + const struct playlist_plugin *plugin) +{ + playlist->plugin = plugin; +} + +struct playlist_plugin { + const char *name; + + /** + * Initialize the plugin. Optional method. + * + * @param param a configuration block for this plugin, or NULL + * if none is configured + * @return true if the plugin was initialized successfully, + * false if the plugin is not available + */ + bool (*init)(const struct config_param *param); + + /** + * Deinitialize a plugin which was initialized successfully. + * Optional method. + */ + void (*finish)(void); + + /** + * Opens the playlist on the specified URI. This URI has + * either matched one of the schemes or one of the suffixes. + */ + struct playlist_provider *(*open_uri)(const char *uri); + + /** + * Opens the playlist in the specified input stream. It has + * either matched one of the suffixes or one of the MIME + * types. + */ + struct playlist_provider *(*open_stream)(struct input_stream *is); + + void (*close)(struct playlist_provider *playlist); + + struct song *(*read)(struct playlist_provider *playlist); + + const char *const*schemes; + const char *const*suffixes; + const char *const*mime_types; +}; + +/** + * Initialize a plugin. + * + * @param param a configuration block for this plugin, or NULL if none + * is configured + * @return true if the plugin was initialized successfully, false if + * the plugin is not available + */ +static inline bool +playlist_plugin_init(const struct playlist_plugin *plugin, + const struct config_param *param) +{ + return plugin->init != NULL + ? plugin->init(param) + : true; +} + +/** + * Deinitialize a plugin which was initialized successfully. + */ +static inline void +playlist_plugin_finish(const struct playlist_plugin *plugin) +{ + if (plugin->finish != NULL) + plugin->finish(); +} + +static inline struct playlist_provider * +playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri) +{ + return plugin->open_uri(uri); +} + +static inline struct playlist_provider * +playlist_plugin_open_stream(const struct playlist_plugin *plugin, + struct input_stream *is) +{ + return plugin->open_stream(is); +} + +static inline void +playlist_plugin_close(struct playlist_provider *playlist) +{ + playlist->plugin->close(playlist); +} + +static inline struct song * +playlist_plugin_read(struct playlist_provider *playlist) +{ + return playlist->plugin->read(playlist); +} + +#endif diff --git a/src/playlist_print.c b/src/playlist_print.c index fd61ab62c..89ab2e5ab 100644 --- a/src/playlist_print.c +++ b/src/playlist_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "playlist_print.h" +#include "playlist_list.h" +#include "playlist_plugin.h" +#include "playlist_any.h" +#include "playlist_song.h" #include "queue_print.h" #include "stored_playlist.h" #include "song_print.h" #include "song.h" #include "database.h" #include "client.h" +#include "input_stream.h" void playlist_print_uris(struct client *client, const struct playlist *playlist) @@ -69,7 +75,7 @@ playlist_print_id(struct client *client, const struct playlist *playlist, bool playlist_print_current(struct client *client, const struct playlist *playlist) { - int current_position = getPlaylistCurrentSong(playlist); + int current_position = playlist_get_current_song(playlist); if (current_position < 0) return false; @@ -138,3 +144,41 @@ spl_print(struct client *client, const char *name_utf8, bool detail) spl_free(list); return true; } + +static void +playlist_provider_print(struct client *client, const char *uri, + struct playlist_provider *playlist, bool detail) +{ + struct song *song; + char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; + + while ((song = playlist_plugin_read(playlist)) != NULL) { + song = playlist_check_translate_song(song, base_uri); + if (song == NULL) + continue; + + if (detail) + song_print_info(client, song); + else + song_print_uri(client, song); + } + + g_free(base_uri); +} + +bool +playlist_file_print(struct client *client, const char *uri, bool detail) +{ + struct input_stream *is; + struct playlist_provider *playlist = playlist_open_any(uri, &is); + if (playlist == NULL) + return false; + + playlist_provider_print(client, uri, playlist, detail); + playlist_plugin_close(playlist); + + if (is != NULL) + input_stream_close(is); + + return true; +} diff --git a/src/playlist_print.h b/src/playlist_print.h index 0cfe80776..b3a0446ed 100644 --- a/src/playlist_print.h +++ b/src/playlist_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -101,4 +101,15 @@ playlist_print_changes_position(struct client *client, bool spl_print(struct client *client, const char *name_utf8, bool detail); +/** + * Send the playlist file to the client. + * + * @param client the client which requested the playlist + * @param uri the URI of the playlist file in UTF-8 encoding + * @param detail true if all details should be printed + * @return true on success, false if the playlist does not exist + */ +bool +playlist_file_print(struct client *client, const char *uri, bool detail); + #endif diff --git a/src/playlist_queue.c b/src/playlist_queue.c new file mode 100644 index 000000000..635e23a28 --- /dev/null +++ b/src/playlist_queue.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_queue.h" +#include "playlist_plugin.h" +#include "playlist_any.h" +#include "playlist_song.h" +#include "song.h" +#include "input_stream.h" + +enum playlist_result +playlist_load_into_queue(const char *uri, struct playlist_provider *source, + struct playlist *dest) +{ + enum playlist_result result; + struct song *song; + char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; + + while ((song = playlist_plugin_read(source)) != NULL) { + song = playlist_check_translate_song(song, base_uri); + if (song == NULL) + continue; + + result = playlist_append_song(dest, song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + if (!song_in_database(song)) + song_free(song); + g_free(base_uri); + return result; + } + } + + g_free(base_uri); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_open_into_queue(const char *uri, struct playlist *dest) +{ + struct input_stream *is; + struct playlist_provider *playlist = playlist_open_any(uri, &is); + if (playlist == NULL) + return PLAYLIST_RESULT_NO_SUCH_LIST; + + enum playlist_result result = + playlist_load_into_queue(uri, playlist, dest); + playlist_plugin_close(playlist); + + if (is != NULL) + input_stream_close(is); + + return result; +} diff --git a/src/playlist_queue.h b/src/playlist_queue.h new file mode 100644 index 000000000..530d4b4be --- /dev/null +++ b/src/playlist_queue.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! \file + * \brief Glue between playlist plugin and the play queue + */ + +#ifndef MPD_PLAYLIST_QUEUE_H +#define MPD_PLAYLIST_QUEUE_H + +#include "playlist.h" + +struct playlist_provider; +struct playlist; + +/** + * Loads the contents of a playlist and append it to the specified + * play queue. + * + * @param uri the URI of the playlist, used to resolve relative song + * URIs + */ +enum playlist_result +playlist_load_into_queue(const char *uri, struct playlist_provider *source, + struct playlist *dest); + +/** + * Opens a playlist with a playlist plugin and append to the specified + * play queue. + */ +enum playlist_result +playlist_open_into_queue(const char *uri, struct playlist *dest); + +#endif + diff --git a/src/playlist_save.c b/src/playlist_save.c index 13dbc721d..8ddc93ec9 100644 --- a/src/playlist_save.c +++ b/src/playlist_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "playlist_save.h" #include "stored_playlist.h" #include "song.h" @@ -54,7 +55,7 @@ playlist_print_uri(FILE *file, const char *uri) char *s; if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) && - uri[0] != '/') + !g_path_is_absolute(uri)) s = map_uri_fs(uri); else s = utf8_to_fs_charset(uri); @@ -118,7 +119,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8) for (unsigned i = 0; i < list->len; ++i) { const char *temp = g_ptr_array_index(list, i); - if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { /* for windows compatibility, convert slashes */ char *temp2 = g_strdup(temp); char *p = temp2; @@ -127,7 +128,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8) *p = '/'; p++; } - if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { g_warning("can't add file \"%s\"", temp2); } g_free(temp2); diff --git a/src/playlist_save.h b/src/playlist_save.h index 8669ca025..a0131cf7f 100644 --- a/src/playlist_save.h +++ b/src/playlist_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_song.c b/src/playlist_song.c new file mode 100644 index 000000000..20fac59d8 --- /dev/null +++ b/src/playlist_song.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_song.h" +#include "database.h" +#include "mapper.h" +#include "song.h" +#include "uri.h" +#include "ls.h" +#include "tag.h" + +#include <assert.h> +#include <string.h> + +static void +merge_song_metadata(struct song *dest, const struct song *base, + const struct song *add) +{ + dest->tag = base->tag != NULL + ? (add->tag != NULL + ? tag_merge(base->tag, add->tag) + : tag_dup(base->tag)) + : (add->tag != NULL + ? tag_dup(add->tag) + : NULL); + + dest->mtime = base->mtime; + dest->start_ms = add->start_ms; + dest->end_ms = add->end_ms; +} + +static struct song * +apply_song_metadata(struct song *dest, const struct song *src) +{ + struct song *tmp; + + assert(dest != NULL); + assert(src != NULL); + + if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0) + return dest; + + if (song_in_database(dest)) { + char *path_fs = map_song_fs(dest); + if (path_fs == NULL) + return dest; + + tmp = song_file_new(path_fs, NULL); + g_free(path_fs); + + merge_song_metadata(tmp, dest, src); + } else { + tmp = song_file_new(dest->uri, NULL); + merge_song_metadata(tmp, dest, src); + song_free(dest); + } + + return tmp; +} + +struct song * +playlist_check_translate_song(struct song *song, const char *base_uri) +{ + struct song *dest; + + if (song_in_database(song)) + /* already ok */ + return song; + + char *uri = song->uri; + + if (uri_has_scheme(uri)) { + if (uri_supported_scheme(uri)) + /* valid remote song */ + return song; + else { + /* unsupported remote song */ + song_free(song); + return NULL; + } + } + + if (g_path_is_absolute(uri)) { + /* XXX fs_charset vs utf8? */ + char *prefix = base_uri != NULL + ? map_uri_fs(base_uri) + : map_directory_fs(db_get_root()); + + if (prefix == NULL || !g_str_has_prefix(uri, prefix) || + uri[strlen(prefix)] != '/') { + /* local files must be relative to the music + directory */ + g_free(prefix); + song_free(song); + return NULL; + } + + uri += strlen(prefix) + 1; + g_free(prefix); + } + + if (base_uri != NULL) + uri = g_build_filename(base_uri, uri, NULL); + else + uri = g_strdup(uri); + + if (uri_has_scheme(base_uri)) { + dest = song_remote_new(uri); + g_free(uri); + } else { + dest = db_get_song(uri); + g_free(uri); + if (dest == NULL) { + /* not found in database */ + song_free(song); + return dest; + } + } + + dest = apply_song_metadata(dest, song); + song_free(song); + + return dest; +} diff --git a/src/playlist_song.h b/src/playlist_song.h new file mode 100644 index 000000000..5a2e4c2b0 --- /dev/null +++ b/src/playlist_song.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_SONG_H +#define MPD_PLAYLIST_SONG_H + +/** + * Verifies the song, returns NULL if it is unsafe. Translate the + * song to a new song object within the database, if it is a local + * file. The old song object is freed. + */ +struct song * +playlist_check_translate_song(struct song *song, const char *base_uri); + +#endif diff --git a/src/playlist_state.c b/src/playlist_state.c index af0f7982b..bb9897e01 100644 --- a/src/playlist_state.c +++ b/src/playlist_state.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,11 +22,13 @@ * */ +#include "config.h" #include "playlist_state.h" #include "playlist.h" #include "player_control.h" #include "queue_save.h" #include "path.h" +#include "text_file.h" #include <string.h> #include <stdlib.h> @@ -39,6 +41,8 @@ #define PLAYLIST_STATE_FILE_CURRENT "current: " #define PLAYLIST_STATE_FILE_TIME "time: " #define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " +#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: " +#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: " #define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" #define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" @@ -51,57 +55,65 @@ void playlist_state_save(FILE *fp, const struct playlist *playlist) { - fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE); + struct player_status player_status; + + pc_get_status(&player_status); + + fputs(PLAYLIST_STATE_FILE_STATE, fp); if (playlist->playing) { - switch (getPlayerState()) { + switch (player_status.state) { case PLAYER_STATE_PAUSE: - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE); + fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); break; default: - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY); + fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); } - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT, + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", queue_order_to_position(&playlist->queue, playlist->current)); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME, - getPlayerElapsedTime()); - } else - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP); - - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM, - playlist->queue.random); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT, - playlist->queue.repeat); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_SINGLE, - playlist->queue.single); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CONSUME, + fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", + (int)player_status.elapsed_time); + } else { + fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp); + + if (playlist->current >= 0) + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", + queue_order_to_position(&playlist->queue, + playlist->current)); + } + + fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random); + fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist->queue.repeat); + fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist->queue.single); + fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist->queue.consume); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE, - (int)(getPlayerCrossFade())); - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN); + fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", + (int)(pc_get_cross_fade())); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc_get_mixramp_db()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", + pc_get_mixramp_delay()); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); queue_save(fp, &playlist->queue); - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); } static void -playlist_state_load(FILE *fp, struct playlist *playlist, char *buffer) +playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) { - int song; - - if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) { + const char *line = read_text_line(fp, buffer); + if (line == NULL) { g_warning("No playlist in state file"); return; } - while (!g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_PLAYLIST_END)) { - g_strchomp(buffer); - - song = queue_load_song(&playlist->queue, buffer); + while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + queue_load_song(fp, buffer, line, &playlist->queue); - if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) { - g_warning("'%s' not found in state file", - PLAYLIST_STATE_FILE_PLAYLIST_END); + line = read_text_line(fp, buffer); + if (line == NULL) { + g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END + "' not found in state file"); break; } } @@ -109,87 +121,116 @@ playlist_state_load(FILE *fp, struct playlist *playlist, char *buffer) queue_increment_version(&playlist->queue); } -void -playlist_state_restore(FILE *fp, struct playlist *playlist) +bool +playlist_state_restore(const char *line, FILE *fp, GString *buffer, + struct playlist *playlist) { int current = -1; int seek_time = 0; int state = PLAYER_STATE_STOP; - char buffer[PLAYLIST_BUFFER_SIZE]; bool random_mode = false; - while (fgets(buffer, sizeof(buffer), fp)) { - g_strchomp(buffer); + if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) + return false; - if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_STATE)) { - if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]), - PLAYLIST_STATE_FILE_STATE_PLAY) == 0) { - state = PLAYER_STATE_PLAY; - } else - if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]), - PLAYLIST_STATE_FILE_STATE_PAUSE) - == 0) { - state = PLAYER_STATE_PAUSE; - } - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) { + line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; + + if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0) + state = PLAYER_STATE_PLAY; + else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) + state = PLAYER_STATE_PAUSE; + + while ((line = read_text_line(fp, buffer)) != NULL) { + if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { seek_time = - atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)])); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_REPEAT)) { + atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) { if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), "1") == 0) { - setPlaylistRepeatStatus(playlist, true); + playlist_set_repeat(playlist, true); } else - setPlaylistRepeatStatus(playlist, false); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_SINGLE)) { + playlist_set_repeat(playlist, false); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_SINGLE)]), + (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), "1") == 0) { - setPlaylistSingleStatus(playlist, true); + playlist_set_single(playlist, true); } else - setPlaylistSingleStatus(playlist, false); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CONSUME)) { + playlist_set_single(playlist, false); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_CONSUME)]), + (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), "1") == 0) { - setPlaylistConsumeStatus(playlist, true); + playlist_set_consume(playlist, true); } else - setPlaylistConsumeStatus(playlist, false); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) { - setPlayerCrossFade(atoi - (& - (buffer - [strlen - (PLAYLIST_STATE_FILE_CROSSFADE)]))); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) { + playlist_set_consume(playlist, false); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { + pc_set_cross_fade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { + pc_set_mixramp_db(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { + pc_set_mixramp_delay(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { random_mode = - strcmp(buffer + strlen(PLAYLIST_STATE_FILE_RANDOM), + strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), "1") == 0; - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CURRENT)) { - current = atoi(&(buffer + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CURRENT)) { + current = atoi(&(line [strlen (PLAYLIST_STATE_FILE_CURRENT)])); - } else if (g_str_has_prefix(buffer, + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - if (state == PLAYER_STATE_STOP) - current = -1; - playlist_state_load(fp, playlist, buffer); + playlist_state_load(fp, buffer, playlist); } } - setPlaylistRandomStatus(playlist, random_mode); + playlist_set_random(playlist, random_mode); - if (state != PLAYER_STATE_STOP && !queue_is_empty(&playlist->queue)) { + if (!queue_is_empty(&playlist->queue)) { if (!queue_valid_position(&playlist->queue, current)) current = 0; - if (seek_time == 0) - playPlaylist(playlist, current); + /* enable all devices for the first time; this must be + called here, after the audio output states were + restored, before playback begins */ + if (state != PLAYER_STATE_STOP) + pc_update_audio(); + + if (state == PLAYER_STATE_STOP /* && config_option */) + playlist->current = current; + else if (seek_time == 0) + playlist_play(playlist, current); else - seekSongInPlaylist(playlist, current, seek_time); + playlist_seek_song(playlist, current, seek_time); if (state == PLAYER_STATE_PAUSE) - playerPause(); + pc_pause(); } + + return true; +} + +unsigned +playlist_state_get_hash(const struct playlist *playlist) +{ + struct player_status player_status; + + pc_get_status(&player_status); + + return playlist->queue.version ^ + (player_status.state != PLAYER_STATE_STOP + ? ((int)player_status.elapsed_time << 8) + : 0) ^ + (playlist->current >= 0 + ? (queue_order_to_position(&playlist->queue, + playlist->current) << 16) + : 0) ^ + ((int)pc_get_cross_fade() << 20) ^ + (player_status.state << 24) ^ + (playlist->queue.random << 27) ^ + (playlist->queue.repeat << 28) ^ + (playlist->queue.single << 29) ^ + (playlist->queue.consume << 30) ^ + (playlist->queue.random << 31); } diff --git a/src/playlist_state.h b/src/playlist_state.h index 989430264..8ca3657f2 100644 --- a/src/playlist_state.h +++ b/src/playlist_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,8 @@ #ifndef PLAYLIST_STATE_H #define PLAYLIST_STATE_H +#include <glib.h> +#include <stdbool.h> #include <stdio.h> struct playlist; @@ -32,7 +34,17 @@ struct playlist; void playlist_state_save(FILE *fp, const struct playlist *playlist); -void -playlist_state_restore(FILE *fp, struct playlist *playlist); +bool +playlist_state_restore(const char *line, FILE *fp, GString *buffer, + struct playlist *playlist); + +/** + * Generates a hash number for the current state of the playlist and + * the playback options. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +unsigned +playlist_state_get_hash(const struct playlist *playlist); #endif diff --git a/src/playlist_vector.c b/src/playlist_vector.c new file mode 100644 index 000000000..7c1765a98 --- /dev/null +++ b/src/playlist_vector.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_vector.h" + +#include <assert.h> +#include <string.h> +#include <glib.h> + +static struct playlist_metadata * +playlist_metadata_new(const char *name, time_t mtime) +{ + assert(name != NULL); + + struct playlist_metadata *pm = g_slice_new(struct playlist_metadata); + pm->name = g_strdup(name); + pm->mtime = mtime; + return pm; +} + +static void +playlist_metadata_free(struct playlist_metadata *pm) +{ + assert(pm != NULL); + assert(pm->name != NULL); + + g_free(pm->name); + g_slice_free(struct playlist_metadata, pm); +} + +void +playlist_vector_deinit(struct playlist_vector *pv) +{ + assert(pv != NULL); + + while (pv->head != NULL) { + struct playlist_metadata *pm = pv->head; + pv->head = pm->next; + playlist_metadata_free(pm); + } +} + +static struct playlist_metadata ** +playlist_vector_find_p(struct playlist_vector *pv, const char *name) +{ + assert(pv != NULL); + assert(name != NULL); + + struct playlist_metadata **pmp = &pv->head; + + for (;;) { + struct playlist_metadata *pm = *pmp; + if (pm == NULL) + return NULL; + + if (strcmp(pm->name, name) == 0) + return pmp; + + pmp = &pm->next; + } +} + +struct playlist_metadata * +playlist_vector_find(struct playlist_vector *pv, const char *name) +{ + struct playlist_metadata **pmp = playlist_vector_find_p(pv, name); + return pmp != NULL ? *pmp : NULL; +} + +void +playlist_vector_add(struct playlist_vector *pv, + const char *name, time_t mtime) +{ + struct playlist_metadata *pm = playlist_metadata_new(name, mtime); + pm->next = pv->head; + pv->head = pm; +} + +bool +playlist_vector_update_or_add(struct playlist_vector *pv, + const char *name, time_t mtime) +{ + struct playlist_metadata **pmp = playlist_vector_find_p(pv, name); + if (pmp != NULL) { + struct playlist_metadata *pm = *pmp; + if (mtime == pm->mtime) + return false; + + pm->mtime = mtime; + } else + playlist_vector_add(pv, name, mtime); + + return true; +} + +bool +playlist_vector_remove(struct playlist_vector *pv, const char *name) +{ + struct playlist_metadata **pmp = playlist_vector_find_p(pv, name); + if (pmp == NULL) + return false; + + struct playlist_metadata *pm = *pmp; + *pmp = pm->next; + + playlist_metadata_free(pm); + return true; +} diff --git a/src/playlist_vector.h b/src/playlist_vector.h new file mode 100644 index 000000000..566de5f00 --- /dev/null +++ b/src/playlist_vector.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_VECTOR_H +#define MPD_PLAYLIST_VECTOR_H + +#include <stdbool.h> +#include <stddef.h> +#include <sys/time.h> + +/** + * A directory entry pointing to a playlist file. + */ +struct playlist_metadata { + struct playlist_metadata *next; + + /** + * The UTF-8 encoded name of the playlist file. + */ + char *name; + + time_t mtime; +}; + +struct playlist_vector { + struct playlist_metadata *head; +}; + +static inline void +playlist_vector_init(struct playlist_vector *pv) +{ + pv->head = NULL; +} + +void +playlist_vector_deinit(struct playlist_vector *pv); + +struct playlist_metadata * +playlist_vector_find(struct playlist_vector *pv, const char *name); + +void +playlist_vector_add(struct playlist_vector *pv, + const char *name, time_t mtime); + +/** + * @return true if the vector or one of its items was modified + */ +bool +playlist_vector_update_or_add(struct playlist_vector *pv, + const char *name, time_t mtime); + +bool +playlist_vector_remove(struct playlist_vector *pv, const char *name); + +#endif /* SONGVEC_H */ |