/* Remove items from playlist 'a' that are also present on playlist 'b'. */ void plist_remove_common_items (struct plist *a, struct plist *b) { int i; assert (a != NULL); assert (b != NULL); for (i = 0; i < a->num; i += 1) { if (plist_find_fname(b, a->items[i].file) != -1) plist_delete (a, i); } }
/* Add the content of playlist b to a by copying items. */ void plist_cat (struct plist *a, struct plist *b) { int i; assert (a != NULL); assert (b != NULL); for (i = 0; i < b->num; i++) { assert (b->items[i].file != NULL); if (!plist_deleted (b, i) && plist_find_fname (a, b->items[i].file) == -1) plist_add_from_item (a, &b->items[i]); } }
/* Swap the first item on the playlist with the item with file fname. */ void plist_swap_first_fname (struct plist *plist, const char *fname) { int i; assert (plist != NULL); assert (fname != NULL); i = plist_find_fname (plist, fname); if (i != -1 && i != 0) { rb_delete (plist->search_tree, fname); rb_delete (plist->search_tree, plist->items[0].file); plist_swap (plist, 0, i); rb_insert (plist->search_tree, NULL); rb_insert (plist->search_tree, (void *)(intptr_t)i); } }
/* Move to the next file depending on the options set, the user * request and whether or not there are files in the queue. */ static void go_to_another_file () { int shuffle = options_get_int ("Shuffle"); int go_next = (play_next || options_get_int("AutoNext")); int curr_playing_curr_pos; /* XXX: Shouldn't play_next be protected by mutex? */ LOCK (curr_playing_mut); LOCK (plist_mut); /* If we move forward in the playlist and there are some songs in * the queue, then play them. */ if (plist_count(&queue) && go_next) { logit ("Playing file from queue"); if (!before_queue_fname && curr_playing_fname) before_queue_fname = xstrdup (curr_playing_fname); curr_plist = &queue; curr_playing = plist_next (&queue, -1); server_queue_pop (queue.items[curr_playing].file); plist_delete (&queue, curr_playing); } else { /* If we just finished playing files from the queue and the * appropriate option is set, continue with the file played * before playing the queue. */ if (before_queue_fname && options_get_int("QueueNextSongReturn")) { free (curr_playing_fname); curr_playing_fname = before_queue_fname; before_queue_fname = NULL; } if (shuffle) { curr_plist = &shuffled_plist; if (plist_count(&playlist) && !plist_count(&shuffled_plist)) { plist_cat (&shuffled_plist, &playlist); plist_shuffle (&shuffled_plist); if (curr_playing_fname) plist_swap_first_fname (&shuffled_plist, curr_playing_fname); } } else curr_plist = &playlist; curr_playing_curr_pos = plist_find_fname (curr_plist, curr_playing_fname); /* If we came from the queue and the last file in * queue wasn't in the playlist, we try to revert to * the QueueNextSongReturn = 1 behaviour. */ if (curr_playing_curr_pos == -1 && before_queue_fname) { curr_playing_curr_pos = plist_find_fname (curr_plist, before_queue_fname); } if (play_prev && plist_count(curr_plist)) { logit ("Playing previous..."); if (curr_playing_curr_pos == -1 || started_playing_in_queue) { curr_playing = plist_prev (curr_plist, -1); started_playing_in_queue = 0; } else curr_playing = plist_prev (curr_plist, curr_playing_curr_pos); if (curr_playing == -1) { if (options_get_int("Repeat")) curr_playing = plist_last (curr_plist); logit ("Beginning of the list."); } else logit ("Previous item."); } else if (go_next && plist_count(curr_plist)) { logit ("Playing next..."); if (curr_playing_curr_pos == -1 || started_playing_in_queue) { curr_playing = plist_next (curr_plist, -1); started_playing_in_queue = 0; } else curr_playing = plist_next (curr_plist, curr_playing_curr_pos); if (curr_playing == -1 && options_get_int("Repeat")) { if (shuffle) { plist_clear (&shuffled_plist); plist_cat (&shuffled_plist, &playlist); plist_shuffle (&shuffled_plist); } curr_playing = plist_next (curr_plist, -1); logit ("Going back to the first item."); } else if (curr_playing == -1) logit ("End of the list"); else logit ("Next item"); } else if (!options_get_int("Repeat")) { curr_playing = -1; } else debug ("Repeating file"); if (before_queue_fname) free (before_queue_fname); before_queue_fname = NULL; } UNLOCK (plist_mut); UNLOCK (curr_playing_mut); }
/* Recursively add files from the directory to the playlist. * Return 1 if OK (and even some errors), 0 if the user interrupted. */ static int read_directory_recurr_internal (const char *directory, struct plist *plist, ino_t **dir_stack, int *depth) { DIR *dir; struct dirent *entry; struct stat st; if (stat(directory, &st)) { error ("Can't stat %s: %s", directory, strerror(errno)); return 0; } assert (plist != NULL); assert (directory != NULL); if (*dir_stack && dir_symlink_loop(st.st_ino, *dir_stack, *depth)) { logit ("Detected symlink loop on %s", directory); return 1; } if (!(dir = opendir(directory))) { error ("Can't read directory: %s", strerror(errno)); return 1; } (*depth)++; *dir_stack = (ino_t *)xrealloc (*dir_stack, sizeof(ino_t) * (*depth)); (*dir_stack)[*depth - 1] = st.st_ino; while ((entry = readdir(dir))) { char file[PATH_MAX]; enum file_type type; if (user_wants_interrupt()) { error ("Interrupted! Not all files read!"); break; } if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; if (snprintf(file, sizeof(file), "%s/%s", directory, entry->d_name) >= (int)sizeof(file)) { error ("Path too long!"); continue; } type = file_type (file); if (type == F_DIR) read_directory_recurr_internal(file, plist, dir_stack, depth); else if (type == F_SOUND && plist_find_fname(plist, file) == -1) plist_add (plist, file); } (*depth)--; *dir_stack = (ino_t *)xrealloc (*dir_stack, sizeof(ino_t) * (*depth)); closedir (dir); return 1; }
/* Load M3U file into plist. Return the number of items read. */ static int plist_load_m3u (struct plist *plist, const char *fname, const char *cwd, const int load_serial) { FILE *file; char *line; int last_added = -1; int after_extinf = 0; int added = 0; if (!(file = fopen(fname, "r"))) { error ("Can't open playlist file: %s", strerror(errno)); return 0; } if (flock(fileno(file), LOCK_SH) == -1) logit ("Can't flock() the playlist file: %s", strerror(errno)); while ((line = read_line(file))) { if (!strncmp(line, "#EXTINF:", sizeof("#EXTINF:")-1)) { char *comma; char *num_err; char time_text[10] = ""; int time_sec; if (after_extinf) { error ("Broken M3U file: double " "#EXTINF."); free (line); plist_delete (plist, last_added); return added; } /* Find the comma */ comma = strchr (line + (sizeof("#EXTINF:") - 1), ','); if (!comma) { error ("Broken M3U file: no comma " "in #EXTINF."); free (line); return added; } /* Get the time string */ time_text[sizeof(time_text)-1] = 0; strncpy (time_text, line + sizeof("#EXTINF:") - 1, MIN(comma - line - (sizeof("#EXTINF:") - 1), sizeof(time_text))); if (time_text[sizeof(time_text)-1]) { error ("Broken M3U file: " "wrong time."); free (line); return added; } /* Extract the time */ time_sec = strtol (time_text, &num_err, 10); if (*num_err) { error ("Broken M3U file: " "time is not a number."); free (line); return added; } after_extinf = 1; last_added = plist_add (plist, NULL); plist_set_title_tags (plist, last_added, comma + 1); if (*time_text) plist_set_item_time (plist, last_added, time_sec); } else if (line[0] != '#') { char path[2*PATH_MAX]; strip_string (line); if (strlen(line) <= PATH_MAX) { make_path (path, sizeof(path), cwd, line); if (plist_find_fname(plist, path) == -1) { if (after_extinf) plist_set_file (plist, last_added, path); else plist_add (plist, path); added++; } else if (after_extinf) plist_delete (plist, last_added); } else if (after_extinf) plist_delete (plist, last_added); after_extinf = 0; } else if (load_serial && !strncmp(line, "#MOCSERIAL: ", sizeof("#MOCSERIAL: ") - 1)) { char *serial_str = line + sizeof("#MOCSERIAL: ") - 1; if (serial_str[0]) { char *err; long serial; serial = strtol (serial_str, &err, 0); if (!*err) { plist_set_serial (plist, serial); logit ("Got MOCSERIAL tag with serial %d", (int)serial); } } } free (line); } if (flock(fileno(file), LOCK_UN) == -1) logit ("Can't flock() (unlock) the playlist file: %s", strerror(errno)); fclose (file); return added; }
/* Load PLS file into plist. Return the number of items read. */ static int plist_load_pls (struct plist *plist, const char *fname, const char *cwd) { FILE *file; char *line; long i, nitems, added = 0; char *e; if (!(file = fopen(fname, "r"))) { error ("Can't open playlist file: %s", strerror(errno)); return 0; } line = read_ini_value (file, "playlist", "NumberOfEntries"); if (!line) { /* Assume that it is a pls file version 1 - plist_load_m3u() * should handle it like an m3u file without the m3u extensions. */ fclose (file); return plist_load_m3u (plist, fname, cwd, 0); } nitems = strtol (line, &e, 10); if (*e) { error ("Broken PLS file"); free (line); return 0; } free (line); for (i = 1; i <= nitems; i++) { char *pls_file, *pls_title, *pls_length; char key[16]; int time; int last_added; char path[2*PATH_MAX]; sprintf (key, "File%ld", i); if (!(pls_file = read_ini_value(file, "playlist", key))) { error ("Broken PLS file"); break; } sprintf (key, "Title%ld", i); pls_title = read_ini_value(file, "playlist", key); sprintf (key, "Length%ld", i); pls_length = read_ini_value(file, "playlist", key); if (pls_length) { time = strtol (pls_length, &e, 10); if (*e) time = -1; } else time = -1; if (strlen(pls_file) <= PATH_MAX) { make_path (path, sizeof(path), cwd, pls_file); if (plist_find_fname(plist, path) == -1) { last_added = plist_add (plist, path); if (pls_title && pls_title[0]) plist_set_title_tags (plist, last_added, pls_title); if (time > 0) { plist->items[last_added].tags = tags_new (); plist->items[last_added].tags->time = time; plist->items[last_added].tags->filled |= TAGS_TIME; } } } free (pls_file); if (pls_title) free (pls_title); if (pls_length) free (pls_length); added++; } fclose (file); return added; }
/* Load M3U file into plist. Return the number of items read. */ static int plist_load_m3u (struct plist *plist, const char *fname, const char *cwd, const int load_serial) { FILE *file; char *line = NULL; int last_added = -1; int after_extinf = 0; int added = 0; struct flock read_lock = {.l_type = F_RDLCK, .l_whence = SEEK_SET}; file = fopen (fname, "r"); if (!file) { error_errno ("Can't open playlist file", errno); return 0; } /* Lock gets released by fclose(). */ if (fcntl (fileno (file), F_SETLKW, &read_lock) == -1) log_errno ("Can't lock the playlist file", errno); while ((line = read_line (file))) { if (!strncmp (line, "#EXTINF:", sizeof("#EXTINF:") - 1)) { char *comma, *num_err; char time_text[10] = ""; int time_sec; if (after_extinf) { error ("Broken M3U file: double #EXTINF!"); plist_delete (plist, last_added); goto err; } /* Find the comma */ comma = strchr (line + (sizeof("#EXTINF:") - 1), ','); if (!comma) { error ("Broken M3U file: no comma in #EXTINF!"); goto err; } /* Get the time string */ time_text[sizeof(time_text) - 1] = 0; strncpy (time_text, line + sizeof("#EXTINF:") - 1, MIN(comma - line - (sizeof("#EXTINF:") - 1), sizeof(time_text))); if (time_text[sizeof(time_text) - 1]) { error ("Broken M3U file: wrong time!"); goto err; } /* Extract the time. */ time_sec = strtol (time_text, &num_err, 10); if (*num_err) { error ("Broken M3U file: time is not a number!"); goto err; } after_extinf = 1; last_added = plist_add (plist, NULL); plist_set_title_tags (plist, last_added, comma + 1); if (*time_text) plist_set_item_time (plist, last_added, time_sec); } else if (line[0] != '#') { char path[2 * PATH_MAX]; strip_string (line); if (strlen (line) <= PATH_MAX) { make_path (path, sizeof(path), cwd, line); if (plist_find_fname (plist, path) == -1) { if (after_extinf) plist_set_file (plist, last_added, path); else plist_add (plist, path); added += 1; } else if (after_extinf) plist_delete (plist, last_added); } else if (after_extinf) plist_delete (plist, last_added); after_extinf = 0; } else if (load_serial && !strncmp (line, "#MOCSERIAL: ", sizeof("#MOCSERIAL: ") - 1)) { char *serial_str = line + sizeof("#MOCSERIAL: ") - 1; if (serial_str[0]) { char *err; long serial; serial = strtol (serial_str, &err, 0); if (!*err) { plist_set_serial (plist, serial); logit ("Got MOCSERIAL tag with serial %ld", serial); } } } free (line); } err: free (line); fclose (file); return added; }