make header_len an integer
[paraslash.git] / plm_dbtool.c
index fd18f25..3b551fc 100644 (file)
  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
  */
 
-/** \file plm_dbtool.c Simple playlist manager for paraslash  */
+/** \file plm_dbtool.c Playlist manager for paraslash  */
 
-#include <sys/time.h> /* gettimeofday */
-#include "server.cmdline.h"
 #include "server.h"
 #include "db.h"
 #include "error.h"
 #include "net.h"
 #include "string.h"
+#include "ipc.h"
 
-#define MAX_PLAYLIST_LEN 10000
+/**
+ * structure used for transmission of the playlist
+ *
+ * There's one such struct which gets initialized during startup. It lives in
+ * shared memory and is used by com_lpl().
+ */
+struct plm_client_data {
+/** allocated and set by com_lpl() (child) */
+       int shm_id;
+/** the size of the shared memory area identified by \a shm_id */
+       size_t size;
+/** initially locked, gets unlocked by parent when it is done */
+       int mutex;
+/** return value, set by parent */
+       int retval;
+};
+
+/** data specific to the plm database tool */
+struct private_plm_data {
+/** guards against concurrent client access */
+       int client_mutex;
+/** guards against concurrent parent-child access */
+       int server_mutex;
+/** pointer to the client data */
+       struct plm_client_data *client_data;
+/** id of the shm corresponding to \a client_data */
+       int client_data_shm_id;
+};
+
+/** we refuse to load playlists bigger than that */
 #define MAX_PLAYLIST_BYTES (1024 * 1024)
 
 static unsigned playlist_len, playlist_size, current_playlist_entry;
 static char **playlist;
+static struct dbtool *self;
 
 static int com_ppl(int, int, char **);
 static int com_lpl(int, int, char **);
@@ -53,8 +82,8 @@ static struct server_command cmds[] = {
 .description = "load playlist",
 .synopsis = "lpl",
 .help =
-"Read a new playlist from stdin"
-
+"Read a new playlist from stdin. Example:\n"
+"\tfind /audio -name '*.mp3' | para_client lpl"
 }, {
 .name = NULL,
 }
@@ -63,54 +92,95 @@ static struct server_command cmds[] = {
 static void playlist_add(char *path)
 {
        if (playlist_len >= playlist_size) {
-               if (playlist_size >= MAX_PLAYLIST_LEN)
-                       return;
-               playlist_size *= 2;
+               playlist_size = 2 * playlist_size + 1;
                playlist = para_realloc(playlist, playlist_size * sizeof(char *));
        }
-       PARA_DEBUG_LOG("adding #%d: %s\n", playlist_len, path);
-       playlist[playlist_len] = para_strdup(path);
-       playlist_len++;
+       PARA_DEBUG_LOG("adding #%d/%d: %s\n", playlist_len, playlist_size, path);
+       playlist[playlist_len++] = para_strdup(path);
+}
+
+static int send_playlist_to_server(const char *buf, size_t size)
+{
+       struct private_plm_data *ppd = self->private_data;
+       int ret, shm_mutex = -1, shm_id = -1;
+       void *shm = NULL;
+
+       PARA_DEBUG_LOG("new playlist (%d bytes)\n", size);
+
+       ret = mutex_new();
+       if (ret < 0)
+               return ret;
+       shm_mutex = ret;
+
+       ret = shm_new(size);
+       if (ret < 0)
+               goto out;
+       shm_id = ret;
+
+       ret = shm_attach(shm_id, ATTACH_RW, &shm);
+       if (ret < 0)
+               goto out;
+       mutex_lock(shm_mutex);
+       memcpy(shm, buf, size);
+       mutex_lock(ppd->client_mutex);
+       mutex_lock(ppd->server_mutex);
+       ppd->client_data->size = size;
+       ppd->client_data->shm_id = shm_id;
+       ppd->client_data->mutex = shm_mutex;
+       kill(getppid(), SIGUSR1); /* wake up the server */
+       mutex_unlock(ppd->server_mutex);
+       mutex_lock(shm_mutex); /* wait until server is done */
+       mutex_unlock(shm_mutex);
+       ret = ppd->client_data->retval;
+       mutex_unlock(ppd->client_mutex);
+       shm_detach(shm);
+out:
+       if (shm_id >= 0)
+               shm_destroy(shm_id);
+       mutex_destroy(shm_mutex);
+       PARA_DEBUG_LOG("returning %d\n", ret);
+       return ret;
 }
 
 static int com_lpl(int fd, __unused int argc, __unused char *argv[])
 {
-       unsigned i, loaded = 0;
-       char buf[_POSIX_PATH_MAX];
+       unsigned loaded = 0;
+       size_t bufsize = 4096; /* guess that's enough */
+       char *buf = para_malloc(bufsize);
        ssize_t ret;
-
-       PARA_DEBUG_LOG("freeing playlist (%d entries)\n", playlist_len);
-       for (i = 0; i < playlist_len; i++)
-               free(playlist[i]);
-       current_playlist_entry = 0;
-       playlist_len = 0;
        ret = send_buffer(fd, AWAITING_DATA_MSG);
        if (ret < 0)
-               return ret;
+               goto out;
 again:
-       ret = recv_bin_buffer(fd, buf + loaded, sizeof(buf) - loaded);
+       ret = recv_bin_buffer(fd, buf + loaded, bufsize - loaded);
        if (ret < 0)
-               goto err_out;
+               goto out;
        if (!ret) {
-               PARA_DEBUG_LOG("loaded playlist (%d entries)\n", playlist_len);
-               return playlist_len;
+               ret = send_playlist_to_server(buf, loaded);
+               goto out;
        }
        loaded += ret;
-       loaded = for_each_line(buf, loaded, &playlist_add, 0);
-       if (loaded >= sizeof(buf))
-               goto err_out;
+       ret = -E_LOAD_PLAYLIST;
+       if (loaded >= MAX_PLAYLIST_BYTES)
+               goto out;
+       if (loaded >= bufsize) {
+               bufsize *= 2;
+               buf = para_realloc(buf, bufsize);
+       }
        goto again;
-err_out:
-       return -E_LOAD_PLAYLIST;
+out:
+       free(buf);
+       return ret;
 }
 
 static int com_ppl(int fd, __unused int argc, __unused char *argv[])
 {
        unsigned i;
 
-       PARA_DEBUG_LOG("sending playlist (%d entries)\n", playlist_len);
+       PARA_DEBUG_LOG("sending playlist to client (%d entries)\n", playlist_len);
        for (i = 0; i < playlist_len; i++) {
-               int ret = send_buffer(fd, playlist[i]);
+               int ret = send_va_buffer(fd, "%s\n", playlist[
+                       (i + current_playlist_entry) % playlist_len]);
                if (ret < 0)
                        return ret;
        }
@@ -122,7 +192,6 @@ static char **plm_get_audio_file_list(unsigned int num)
        char **file_list;
        unsigned i;
 
-       return NULL;
        num = MIN(num, playlist_len);
        if (!num)
                return NULL;
@@ -135,13 +204,74 @@ static char **plm_get_audio_file_list(unsigned int num)
        return file_list;
 }
 
+static void free_playlist_contents(void)
+{
+       int i;
+
+       PARA_DEBUG_LOG("freeing playlist (%d entries)\n", playlist_len);
+       for (i = 0; i < playlist_len; i++)
+               free(playlist[i]);
+       current_playlist_entry = 0;
+       playlist_len = 0;
+}
+
 static void plm_shutdown(void)
 {
-       /* free the playlist */
+       struct private_plm_data *ppd = self->private_data;
+
+       shm_detach(ppd->client_data);
+       shm_destroy(ppd->client_data_shm_id);
+       mutex_destroy(ppd->server_mutex);
+       mutex_destroy(ppd->client_mutex);
+       free(ppd);
+       free_playlist_contents();
+       free(playlist);
+       playlist = NULL;
+       playlist_len = 0;
+       playlist_size = 0;
+}
+
+static void plm_post_select(__unused fd_set *rfds, __unused fd_set *wfds)
+{
+       struct private_plm_data *ppd = self->private_data;
+       struct plm_client_data *pcd = ppd->client_data;
+       int ret;
+       void *shm;
+
+       mutex_lock(ppd->server_mutex);
+       if (!pcd->size)
+               goto out;
+       free_playlist_contents();
+       ret = shm_attach(pcd->shm_id, ATTACH_RW, &shm);
+       if (ret < 0) {
+               PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
+               goto out;
+       }
+       PARA_DEBUG_LOG("loading new playlist (%d bytes)\n", pcd->size);
+       ret = for_each_line((char *)shm, pcd->size, &playlist_add, 0);
+       shm_detach(shm);
+       PARA_NOTICE_LOG("new playlist (%d entries)\n", playlist_len);
+       pcd->retval = 1;
+       pcd->size = 0;
+       mutex_unlock(pcd->mutex);
+out:
+       mutex_unlock(ppd->server_mutex);
+}
+
+void plm_update_audio_file(char *audio_file)
+{
+       unsigned i;
+
+       for (i = 0; i < playlist_len; i++) {
+               unsigned j = (current_playlist_entry + i) % playlist_len;
+               if (strcmp(playlist[j], audio_file))
+                       continue;
+               current_playlist_entry = (j + 1) % playlist_len;
+       }
 }
 
 /**
- *  the init function for the plm database tool
+ * the init function for the plm database tool
  *
  * Init all function pointers of \a db
  *
@@ -149,11 +279,53 @@ static void plm_shutdown(void)
  */
 int plm_dbtool_init(struct dbtool *db)
 {
-       playlist = para_calloc(100 * sizeof(char *)); /* guess 100 is enough */
-       playlist_size = 100;
-       sprintf(mmd->dbinfo, "plm initialized");
+       int ret;
+       struct private_plm_data *ppd = NULL;
+       void *shm = NULL;
+
+       self = db;
        db->cmd_list = cmds;
        db->get_audio_file_list = plm_get_audio_file_list;
        db->shutdown = plm_shutdown;
+       db->post_select = plm_post_select;
+       db->update_audio_file = plm_update_audio_file;
+       ppd = para_calloc(sizeof(struct private_plm_data));
+       db->private_data = ppd;
+
+       ppd->client_mutex = -1;
+       ppd->server_mutex = -1;
+       ppd->client_data_shm_id = -1;
+       ppd->client_data = NULL;
+
+       ret = mutex_new();
+       if (ret < 0)
+               goto err_out;
+       ppd->client_mutex = ret;
+
+       ret = mutex_new();
+       if (ret < 0)
+               goto err_out;
+       ppd->server_mutex = ret;
+
+       ret = shm_new(sizeof(struct plm_client_data));
+       if (ret < 0)
+               goto err_out;
+       ppd->client_data_shm_id = ret;
+
+       ret = shm_attach(ppd->client_data_shm_id, ATTACH_RW, &shm);
+       if (ret < 0)
+               goto err_out;
+       ppd->client_data = shm;
+       ppd->client_data->size = 0;
+       sprintf(mmd->dbinfo, "plm initialized");
        return 1;
+err_out:
+       if (ppd->client_data_shm_id >= 0)
+               shm_destroy(ppd->client_data_shm_id);
+       if (ppd->client_mutex >= 0)
+               mutex_destroy(ppd->client_mutex);
+       if (ppd->server_mutex >= 0)
+               mutex_destroy(ppd->server_mutex);
+       free(ppd);
+       return ret;
 }