-/*
- * Copyright (C) 2007-2013 Andre Noll <maan@systemlinux.org>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
#include <regex.h>
#include <osl.h>
+#include <lopsub.h>
#include "para.h"
#include "error.h"
/** \file playlist.c Functions for loading and saving playlists. */
-/** Structure used for adding entries to a playlist. */
-struct playlist_info {
+/**
+ * The state of a playlist instance.
+ *
+ * A structure of this type is allocated and initialized at playlist load time.
+ */
+struct playlist_instance {
/** The name of the playlist. */
char *name;
/** The number of entries currently in the playlist. */
unsigned length;
+ /** Contains all valid paths of the playlist. */
+ struct osl_table *score_table;
};
-static struct playlist_info current_playlist;
+static struct playlist_instance current_playlist;
/**
* Re-insert an audio file into the tree of admissible files.
static int add_playlist_entry(char *path, void *data)
{
- struct playlist_info *playlist = data;
+ struct playlist_instance *pi = data;
struct osl_row *aft_row;
int ret = aft_get_row_of_path(path, &aft_row);
PARA_NOTICE_LOG("%s: %s\n", path, para_strerror(-ret));
return 1;
}
- ret = score_add(aft_row, -playlist->length);
+ ret = score_add(aft_row, -pi->length, pi->score_table);
if (ret < 0) {
PARA_ERROR_LOG("failed to add %s: %s\n", path, para_strerror(-ret));
return ret;
}
- playlist->length++;
- return 1;
-}
-
-/* returns -E_PLAYLIST_LOADED on _success_ to terminate the loop */
-static int load_playlist(struct osl_row *row, void *data)
-{
- struct playlist_info *playlist = data;
- struct osl_object playlist_def;
- char *playlist_name;
- int ret;
-
- ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def);
- if (ret < 0)
- goto err;
- playlist->length = 0;
- ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
- playlist_def.size, add_playlist_entry, playlist);
- osl_close_disk_object(&playlist_def);
- if (ret < 0)
- goto err;
- ret = -E_PLAYLIST_EMPTY;
- if (!playlist->length)
- goto err;
- playlist->name = para_strdup(playlist_name);
- PARA_NOTICE_LOG("loaded playlist %s (%u files)\n", playlist->name,
- playlist->length);
- return -E_PLAYLIST_LOADED;
-err:
- if (ret != -E_DUMMY_ROW)
- PARA_NOTICE_LOG("unable to load playlist (%s)\n",
- para_strerror(-ret));
+ pi->length++;
return 1;
}
static int check_playlist_path(char *path, void *data)
{
- struct para_buffer *pb = data;
+ struct afs_callback_arg *aca = data;
struct osl_row *aft_row;
int ret = aft_get_row_of_path(path, &aft_row);
- if (ret >= 0)
- return 1;
- return para_printf(pb, "%s: %s\n", path, para_strerror(-ret));
+ if (ret < 0)
+ afs_error(aca, "%s: %s\n", path, para_strerror(-ret));
+ return 1; /* do not fail the loop on bad paths */
}
static int check_playlist(struct osl_row *row, void *data)
{
- struct para_buffer *pb = data;
+ struct afs_callback_arg *aca = data;
+ struct para_buffer *pb = &aca->pbout;
struct osl_object playlist_def;
char *playlist_name;
int ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def);
- if (ret < 0)
- return para_printf(pb, "failed to get playlist data: %s\n",
+ if (ret < 0) { /* log error, but continue */
+ afs_error(aca, "failed to get playlist data: %s\n",
para_strerror(-ret));
+ return 1;
+ }
if (*playlist_name) { /* skip dummy row */
- ret = para_printf(pb, "checking playlist %s...\n", playlist_name);
- if (ret < 0)
- return ret;
- ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
- playlist_def.size, check_playlist_path, pb);
+ para_printf(pb, "checking playlist %s...\n", playlist_name);
+ for_each_line(FELF_READ_ONLY, playlist_def.data,
+ playlist_def.size, check_playlist_path, aca);
}
osl_close_disk_object(&playlist_def);
- return ret;
+ return 1;
}
/**
* Check the playlist table for inconsistencies.
*
- * \param fd The afs socket.
- * \param query Unused.
+ * \param aca This callback ignores ->query.
*
- * \return The return value of the underlying call to osl_rbtree_loop().
+ * \return Standard. Invalid paths are reported, but are not considered an
+ * error.
*/
-void playlist_check_callback(int fd, __a_unused const struct osl_object *query)
+int playlist_check_callback(struct afs_callback_arg *aca)
{
- struct para_buffer pb = {
- .max_size = shm_get_shmmax(),
- .private_data = &(struct afs_max_size_handler_data) {
- .fd = fd,
- .band = SBD_OUTPUT
- },
- .max_size_handler = afs_max_size_handler,
- };
- int ret = para_printf(&pb, "checking playlists...\n");
-
- if (ret < 0)
- return;
- osl_rbtree_loop(playlists_table, BLOBCOL_ID, &pb,
- check_playlist);
- if (pb.offset)
- pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset);
- free(pb.buf);
+ para_printf(&aca->pbout, "checking playlists...\n");
+ return osl(osl_rbtree_loop(playlists_table, BLOBCOL_ID, aca,
+ check_playlist));
}
/**
- * Close the current playlist.
+ * Free all resources of the given/current playlist.
*
- * \sa playlist_open().
+ * \param pi NULL means to unload the current playlist.
*/
-void playlist_close(void)
+void playlist_unload(struct playlist_instance *pi)
{
+ if (pi) {
+ score_close(pi->score_table);
+ free(pi->name);
+ free(pi);
+ return;
+ }
if (!current_playlist.name)
return;
+ score_clear();
free(current_playlist.name);
current_playlist.name = NULL;
+ current_playlist.length = 0;
}
/**
- * Open the given playlist.
+ * Populate the score table from the paths of a playlist database object.
*
- * \param name The name of the playlist to open.
+ * This loads the blob object which corresponds to the given name from the
+ * playlist table. Each line of the blob is regarded as a path which is looked
+ * up in the audio file table. If the path lookup succeeds, a reference to the
+ * corresponding row of the audio file table is added to the score table.
*
- * Files which are listed in the playlist, but not contained in the database
- * are ignored. This is not considered an error.
+ * \param name The name of the playlist to load.
+ * \param result Opaque, refers to the underlying score table.
+ * \param msg Error message or playlist info is returned here.
*
- * \return Standard.
+ * \return The length of the loaded playlist on success, negative error code
+ * else. Files which are listed in the playlist, but are not contained in the
+ * database are ignored. This is not considered an error.
*/
-int playlist_open(char *name)
+int playlist_load(const char *name, struct playlist_instance **result, char **msg)
{
- struct osl_object obj;
int ret;
- struct osl_row *row;
+ struct playlist_instance *pi;
+ struct osl_object playlist_def;
- obj.data = name;
- obj.size = strlen(obj.data);
- ret = osl(osl_get_row(playlists_table, BLOBCOL_NAME, &obj, &row));
- if (ret < 0) {
- PARA_NOTICE_LOG("failed to load playlist %s\n", name);
- return ret;
+ if (!name || !*name) {
+ if (msg)
+ *msg = make_message("empty playlist name\n");
+ return -ERRNO_TO_PARA_ERROR(EINVAL);
+ }
+ ret = pl_get_def_by_name(name, &playlist_def);
+ if (ret < 0)
+ goto err;
+ pi = zalloc(sizeof(*pi));
+ if (result)
+ score_open(&pi->score_table);
+ ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
+ playlist_def.size, add_playlist_entry, pi);
+ osl_close_disk_object(&playlist_def);
+ if (ret < 0)
+ goto close_score_table;
+ ret = -E_PLAYLIST_EMPTY;
+ if (pi->length == 0)
+ goto close_score_table;
+ /* success */
+ if (msg)
+ *msg = make_message("loaded playlist %s (%u files)\n", name,
+ pi->length);
+ pi->name = para_strdup(name);
+ if (result)
+ *result = pi;
+ else {
+ playlist_unload(NULL);
+ current_playlist = *pi;
}
- playlist_close();
- ret = load_playlist(row, ¤t_playlist);
- return (ret == -E_PLAYLIST_LOADED)? current_playlist.length : ret;
+ return pi->length;
+close_score_table:
+ if (result)
+ score_close(pi->score_table);
+ free(pi);
+err:
+ PARA_NOTICE_LOG("unable to load playlist %s\n", name);
+ if (msg)
+ *msg = make_message("unable to load playlist %s\n", name);
+ return ret;
+}
+
+/**
+ * Iterate over all admissible audio files of a playlist instance.
+ *
+ * This wrapper around \ref score_loop() is the playlist counterpart of \ref
+ * mood_loop().
+ *
+ * \param pi Determines the score table to iterate. Must not be NULL.
+ * \param func See \ref score_loop().
+ * \param data See \ref score_loop().
+ *
+ * \return See \ref score_loop(), \ref mood_loop().
+ */
+int playlist_loop(struct playlist_instance *pi, osl_rbtree_loop_func *func, void *data)
+{
+ return score_loop(func, pi->score_table, data);
}
static int search_path(char *path, void *data)
static int handle_audio_file_event(enum afs_events event, void *data)
{
- int ret, was_admissible = 0, is_admissible;
+ int ret;
+ bool was_admissible = false, is_admissible;
struct osl_object playlist_def;
char *new_path;
const struct osl_row *row = data;
- if (!current_playlist.name)
- return 1;
- if (event == AUDIO_FILE_RENAME) {
- ret = row_belongs_to_score_table(row, NULL);
- if (ret < 0)
- return ret;
- was_admissible = ret;
- }
+ if (event == AUDIO_FILE_RENAME)
+ was_admissible = row_belongs_to_score_table(row);
ret = get_audio_file_path_of_row(row, &new_path);
if (ret < 0)
return ret;
}
/* !was_admissible && is_admissible */
current_playlist.length++;
- return score_add(row, 0); /* play it immediately */
+ return score_add(row, 0, NULL); /* play it immediately */
}
/**
int playlists_event_handler(enum afs_events event,
__a_unused struct para_buffer *pb, void *data)
{
- int ret;
struct afsi_change_event_data *aced = data;
- switch(event) {
+ if (!current_playlist.name)
+ return 1;
+ switch (event) {
case AFSI_CHANGE:
return playlist_update_audio_file(aced->aft_row);
case AUDIO_FILE_RENAME:
case AUDIO_FILE_ADD:
return handle_audio_file_event(event, data);
case AUDIO_FILE_REMOVE:
- ret = row_belongs_to_score_table(data, NULL);
- if (ret < 0)
- return ret;
- if (!ret)
+ if (!row_belongs_to_score_table(data))
return 1;
current_playlist.length--;
return score_delete(data);