diff options
author | Eric Wong <normalperson@yhbt.net> | 2008-10-06 18:32:27 +0200 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2008-10-06 18:32:27 +0200 |
commit | 37a8239f442e54de8e55de41f95b8e4a4c972701 (patch) | |
tree | 31da96e4791d2f89d3480191e76aa097e3ccaddb | |
parent | 700f18eee55cd6a5906bd526f62e958647cea221 (diff) | |
download | mpd-37a8239f442e54de8e55de41f95b8e4a4c972701.tar.gz mpd-37a8239f442e54de8e55de41f95b8e4a4c972701.tar.xz mpd-37a8239f442e54de8e55de41f95b8e4a4c972701.zip |
directory: simplify list update handling logic
Now the "update" command can be issued multiple times regardless
of whether the client is in list mode or not.
We serialize the update tasks to prevent updates from trampling
over each other and will spawn another update task
once the current one is finished updating and reaped.
Right now we cap the queue size to 32 which is probably enough (I
bet most people usually run update with no argument anyways);
but we can make it grow/shrink dynamically if needed. There'll
still be a hard-coded limit to prevent DoS attacks, though.
-rw-r--r-- | src/command.c | 79 | ||||
-rw-r--r-- | src/directory.c | 105 | ||||
-rw-r--r-- | src/directory.h | 10 |
3 files changed, 83 insertions, 111 deletions
diff --git a/src/command.c b/src/command.c index 822ee05bf..3dda2f3c2 100644 --- a/src/command.c +++ b/src/command.c @@ -36,6 +36,7 @@ #include "tag.h" #include "client.h" #include "tag_print.h" +#include "path.h" #include "os_compat.h" #define COMMAND_PLAY "play" @@ -153,8 +154,6 @@ static const char check_non_negative[] = "\"%s\" is not an integer >= 0"; static const char *current_command; static int command_listNum; -static CommandEntry *getCommandEntryFromString(char *string, int permission); - static List *commandList; static CommandEntry *newCommandEntry(void) @@ -814,55 +813,31 @@ static int handlePlaylistMove(struct client *client, return print_playlist_result(client, result); } -static int listHandleUpdate(struct client *client, - mpd_unused int argc, - char *argv[], - struct strnode *cmdnode, CommandEntry * cmd) +static int print_update_result(struct client *client, int ret) { - List *pathList = makeList(NULL, 1); - CommandEntry *nextCmd = NULL; - struct strnode *next = cmdnode->next; - - if (argc == 2) - insertInList(pathList, argv[1], NULL); - else - insertInList(pathList, "", NULL); - - if (next) - nextCmd = getCommandEntryFromString(next->data, - client_get_permission(client)); - - if (cmd != nextCmd) { - int ret = updateInit(pathList); - if (ret == -1) - command_error(client, ACK_ERROR_UPDATE_ALREADY, - "already updating"); - - return ret; + if (ret >= 0) { + client_printf(client, "updating_db: %i\n", ret); + return 0; } - - return 0; + if (ret == -2) + command_error(client, ACK_ERROR_ARG, "invalid path"); + else + command_error(client, ACK_ERROR_UPDATE_ALREADY, + "already updating"); + return -1; } static int handleUpdate(struct client *client, mpd_unused int argc, char *argv[]) { - int ret; + char *path = NULL; - if (argc == 2) { - List *pathList = makeList(NULL, 1); - insertInList(pathList, argv[1], NULL); - ret = updateInit(pathList); - } else { - ret = updateInit(NULL); + assert(argc <= 2); + if (argc == 2 && !(path = sanitizePathDup(argv[1]))) { + command_error(client, ACK_ERROR_ARG, "invalid path"); + return -1; } - - if (ret == -1) - command_error(client, ACK_ERROR_UPDATE_ALREADY, - "already updating"); - - client_printf(client, "updating_db: %i\n", ret); - return 0; + return print_update_result(client, directory_update_init(path)); } static int handleNext(mpd_unused struct client *client, @@ -1307,7 +1282,7 @@ void initCommands(void) addCommand(COMMAND_PLAYLISTINFO, PERMISSION_READ, 0, 1, handlePlaylistInfo, NULL); addCommand(COMMAND_FIND, PERMISSION_READ, 2, -1, handleFind, NULL); addCommand(COMMAND_SEARCH, PERMISSION_READ, 2, -1, handleSearch, NULL); - addCommand(COMMAND_UPDATE, PERMISSION_ADMIN, 0, 1, handleUpdate, listHandleUpdate); + addCommand(COMMAND_UPDATE, PERMISSION_ADMIN, 0, 1, handleUpdate, NULL); addCommand(COMMAND_NEXT, PERMISSION_CONTROL, 0, 0, handleNext, NULL); addCommand(COMMAND_PREVIOUS, PERMISSION_CONTROL, 0, 0, handlePrevious, NULL); addCommand(COMMAND_LISTALL, PERMISSION_READ, 0, 1, handleListAll, NULL); @@ -1420,24 +1395,6 @@ static CommandEntry *getCommandEntryAndCheckArgcAndPermission(struct client *cli return cmd; } -static CommandEntry *getCommandEntryFromString(char *string, int permission) -{ - CommandEntry *cmd = NULL; - char *argv[COMMAND_ARGV_MAX] = { NULL }; - char *duplicated = xstrdup(string); - int argc = buffer2array(duplicated, argv, COMMAND_ARGV_MAX); - - if (0 == argc) - goto out; - - cmd = getCommandEntryAndCheckArgcAndPermission(0, permission, - argc, argv); - -out: - free(duplicated); - return cmd; -} - static int processCommandInternal(struct client *client, char *commandString, struct strnode *cmdnode) { diff --git a/src/directory.c b/src/directory.c index 82ee0854d..cc0027d96 100644 --- a/src/directory.c +++ b/src/directory.c @@ -53,13 +53,18 @@ enum update_progress { UPDATE_PROGRESS_DONE = 2 } progress; +/* make this dynamic?, or maybe this is big enough... */ +static char *update_paths[32]; +static size_t update_paths_nr; + static Directory *music_root; static time_t directory_dbModTime; static pthread_t update_thr; -static int directory_updateJobId; +static const int update_task_id_max = 1 << 15; +static int update_task_id; static enum update_return addToDirectory(Directory * directory, const char *name); @@ -99,74 +104,84 @@ static char *getDbFile(void) int isUpdatingDB(void) { - return (progress != UPDATE_PROGRESS_IDLE) ? directory_updateJobId : 0; + return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0; } -void reap_update_task(void) +static void * update_task(void *_path) { - enum update_return ret; - - if (progress != UPDATE_PROGRESS_DONE) - return; - if (pthread_join(update_thr, (void **)&ret)) - FATAL("error joining update thread: %s\n", strerror(errno)); - if (ret == UPDATE_RETURN_UPDATED) - playlistVersionChange(); - progress = UPDATE_PROGRESS_IDLE; -} - -static void * update_task(void *arg) -{ - List *path_list = (List *)arg; enum update_return ret = UPDATE_RETURN_NOUPDATE; - if (path_list) { - ListNode *node = path_list->firstNode; - - while (node) { - switch (updatePath(node->key)) { - case UPDATE_RETURN_ERROR: - ret = UPDATE_RETURN_ERROR; - goto out; - case UPDATE_RETURN_NOUPDATE: - break; - case UPDATE_RETURN_UPDATED: - ret = UPDATE_RETURN_UPDATED; - } - node = node->nextNode; - } - freeList(path_list); + if (_path) { + ret = updatePath((char *)_path); + free(_path); } else { ret = updateDirectory(music_root); } if (ret == UPDATE_RETURN_UPDATED && writeDirectoryDB() < 0) ret = UPDATE_RETURN_ERROR; -out: progress = UPDATE_PROGRESS_DONE; wakeup_main_task(); return (void *)ret; } -int updateInit(List * path_list) +static void spawn_update_task(char *path) { pthread_attr_t attr; - if (progress != UPDATE_PROGRESS_IDLE) - return -1; + assert(pthread_equal(pthread_self(), main_task)); progress = UPDATE_PROGRESS_RUNNING; - pthread_attr_init(&attr); - if (pthread_create(&update_thr, &attr, update_task, path_list)) + if (pthread_create(&update_thr, &attr, update_task, path)) FATAL("Failed to spawn update task: %s\n", strerror(errno)); - directory_updateJobId++; - if (directory_updateJobId > 1 << 15) - directory_updateJobId = 1; - DEBUG("updateInit: spawned update thread for update job id %i\n", - (int)directory_updateJobId); + if (++update_task_id > update_task_id_max) + update_task_id = 1; + DEBUG("spawned thread for update job id %i\n", update_task_id); +} + +void reap_update_task(void) +{ + enum update_return ret; + + assert(pthread_equal(pthread_self(), main_task)); - return (int)directory_updateJobId; + if (progress != UPDATE_PROGRESS_DONE) + return; + if (pthread_join(update_thr, (void **)&ret)) + FATAL("error joining update thread: %s\n", strerror(errno)); + if (ret == UPDATE_RETURN_UPDATED) + playlistVersionChange(); + + if (update_paths_nr) { + char *path = update_paths[0]; + memmove(&update_paths[0], &update_paths[1], + --update_paths_nr * sizeof(char *)); + spawn_update_task(path); + } else { + progress = UPDATE_PROGRESS_IDLE; + } +} + +int directory_update_init(char *path) +{ + assert(pthread_equal(pthread_self(), main_task)); + + if (progress != UPDATE_PROGRESS_IDLE) { + int next_task_id; + + if (!path) + return -1; + if (update_paths_nr == ARRAY_SIZE(update_paths)) + return -1; + assert(update_paths_nr < ARRAY_SIZE(update_paths)); + update_paths[update_paths_nr++] = path; + next_task_id = update_task_id + update_paths_nr; + + return next_task_id > update_task_id_max ? 1 : next_task_id; + } + spawn_update_task(path); + return update_task_id; } static void directory_set_stat(Directory * dir, const struct stat *st) diff --git a/src/directory.h b/src/directory.h index 42d0e2bb6..764902071 100644 --- a/src/directory.h +++ b/src/directory.h @@ -42,12 +42,12 @@ void reap_update_task(void); int isUpdatingDB(void); -/** - * Starts the tag cache update in the specified location(s). Returns - * the job id on success, -1 on error or 0 if an update is already - * running. +/* + * returns the non-negative update job ID on success, + * returns -1 if busy + * @path will be freed by this function and should not be reused */ -int updateInit(List * pathList); +int directory_update_init(char *path); void directory_init(void); |