tools

various tools I have been using throughout the years
Log | Files | Refs | README | LICENSE

mpd_edit_queue.c (6387B)


      1 #include <mpd/client.h>
      2 #include <stdio.h>
      3 #include <stdlib.h>
      4 #include <string.h>
      5 #include <stdbool.h>
      6 #include <unistd.h>
      7 #include <sys/wait.h>
      8 #include <ctype.h>
      9 #include <fcntl.h>
     10 
     11 #define DEFAULT_HOST "localhost"
     12 #define DEFAULT_PORT 6600
     13 
     14 struct mpd_connection *conn() {
     15     const char *host = getenv("MPD_HOST");
     16     if (host == NULL || strlen(host) == 0) host = DEFAULT_HOST;
     17     
     18     unsigned port = DEFAULT_PORT;
     19     const char *port_str = getenv("MPD_PORT");
     20     if (port_str != NULL && strlen(port_str) > 0) port = (unsigned)atoi(port_str);
     21 
     22     struct mpd_connection *c = mpd_connection_new(host, port, 0);
     23     if (c == NULL) {
     24         fprintf(stderr, "Failed to create MPD connection object\n");
     25         return NULL;
     26     }
     27 
     28     if (mpd_connection_get_error(c) != MPD_ERROR_SUCCESS) {
     29         fprintf(stderr, "Error connecting to MPD (%s:%u): %s\n", host, port, mpd_connection_get_error_message(c));
     30         mpd_connection_free(c);
     31         return NULL;
     32     }
     33     return c;
     34 }
     35 
     36 char* trim_whitespace(char* str) {
     37     char *end;
     38     while(isspace((unsigned char)*str)) str++;
     39     if(*str == 0) return str;
     40     end = str + strlen(str) - 1;
     41     while(end > str && isspace((unsigned char)*end)) end--;
     42     end[1] = '\0';
     43     return str;
     44 }
     45 
     46 int main() {
     47     struct mpd_connection *c = conn();
     48     if (c == NULL) return 1;
     49 
     50     char tmp_template[] = "/tmp/mpd_queue_XXXXXX";
     51     int fd = mkstemp(tmp_template);
     52     if (fd == -1) {
     53         perror("mkstemp");
     54         mpd_connection_free(c);
     55         return 1;
     56     }
     57 
     58     FILE *f = fdopen(fd, "w");
     59     if (f == NULL) {
     60         perror("fdopen");
     61         close(fd);
     62         unlink(tmp_template);
     63         mpd_connection_free(c);
     64         return 1;
     65     }
     66 
     67     if (!mpd_send_list_queue_meta(c)) {
     68         fprintf(stderr, "Error sending queue request\n");
     69         fclose(f);
     70         unlink(tmp_template);
     71         mpd_connection_free(c);
     72         return 1;
     73     }
     74 
     75     struct mpd_entity *entity;
     76     while ((entity = mpd_recv_entity(c)) != NULL) {
     77         if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
     78             const struct mpd_song *song = mpd_entity_get_song(entity);
     79             fprintf(f, "%s\n", mpd_song_get_uri(song));
     80         }
     81         mpd_entity_free(entity);
     82     }
     83     fclose(f);
     84 
     85     if (mpd_connection_get_error(c) != MPD_ERROR_SUCCESS) {
     86         fprintf(stderr, "Error reading queue: %s\n", mpd_connection_get_error_message(c));
     87         unlink(tmp_template);
     88         mpd_connection_free(c);
     89         return 1;
     90     }
     91     mpd_response_finish(c);
     92 
     93     const char *editor = getenv("EDITOR");
     94     if (editor == NULL || strlen(editor) == 0) editor = "vi";
     95 
     96     pid_t pid = fork();
     97     if (pid == -1) {
     98         perror("fork");
     99         unlink(tmp_template);
    100         mpd_connection_free(c);
    101         return 1;
    102     } else if (pid == 0) {
    103         execlp(editor, editor, tmp_template, NULL);
    104         perror("execlp");
    105         exit(1);
    106     }
    107 
    108     int wstatus;
    109     waitpid(pid, &wstatus, 0);
    110 
    111     /* Re-open temp file with O_NOFOLLOW to prevent symlink attacks */
    112     int fd2 = open(tmp_template, O_RDONLY | O_NOFOLLOW);
    113     if (fd2 == -1) {
    114         perror("open");
    115         unlink(tmp_template);
    116         mpd_connection_free(c);
    117         return 1;
    118     }
    119     f = fdopen(fd2, "r");
    120     if (f == NULL) {
    121         perror("fdopen");
    122         close(fd2);
    123         unlink(tmp_template);
    124         mpd_connection_free(c);
    125         return 1;
    126     }
    127 
    128     char **new_tracks = NULL;
    129     int track_count = 0;
    130     char *line = NULL;
    131     size_t len = 0;
    132     ssize_t read_bytes;
    133     char *current_file = NULL;
    134     enum mpd_state state;
    135     int current_pos;
    136     int total_queue_len;
    137     int new_pos = -1;
    138 
    139     while ((read_bytes = getline(&line, &len, f)) != -1) {
    140         char *trimmed = trim_whitespace(line);
    141         if (strlen(trimmed) == 0 || trimmed[0] == '#') continue;
    142 
    143         char **temp_tracks = realloc(new_tracks, sizeof(char *) * (track_count + 1));
    144         if (temp_tracks == NULL) {
    145             perror("realloc");
    146             goto cleanup;
    147         }
    148         new_tracks = temp_tracks;
    149         
    150         new_tracks[track_count] = strdup(trimmed);
    151         if (new_tracks[track_count] == NULL) {
    152             perror("strdup");
    153             goto cleanup;
    154         }
    155         track_count++;
    156     }
    157 
    158     /* Re-query MPD state after editor to avoid stale data */
    159     struct mpd_status *status = mpd_run_status(c);
    160     if (status == NULL) {
    161         fprintf(stderr, "Error getting status: %s\n", mpd_connection_get_error_message(c));
    162         goto cleanup;
    163     }
    164     state = mpd_status_get_state(status);
    165     current_pos = mpd_status_get_song_pos(status);
    166     total_queue_len = mpd_status_get_queue_length(status);
    167     mpd_status_free(status);
    168 
    169     if (state == MPD_STATE_PLAY || state == MPD_STATE_PAUSE) {
    170         struct mpd_song *song = mpd_run_current_song(c);
    171         if (song != NULL) {
    172             current_file = strdup(mpd_song_get_uri(song));
    173             mpd_song_free(song);
    174         }
    175     }
    176 
    177     /* Compute new_pos from fresh current_file */
    178     for (int i = 0; i < track_count; i++) {
    179         if (current_file && strcmp(new_tracks[i], current_file) == 0 && new_pos == -1) {
    180             new_pos = i;
    181         }
    182     }
    183 
    184     if (!mpd_command_list_begin(c, false)) {
    185         fprintf(stderr, "Failed to start command list: %s\n", mpd_connection_get_error_message(c));
    186         goto cleanup;
    187     }
    188 
    189     if (state == MPD_STATE_STOP || current_file == NULL || new_pos == -1) {
    190         mpd_send_clear(c);
    191         for (int i = 0; i < track_count; i++) {
    192             mpd_send_add(c, new_tracks[i]);
    193         }
    194         if (state != MPD_STATE_STOP) mpd_send_play(c);
    195     } else {
    196         for (int i = total_queue_len - 1; i >= 0; i--) {
    197             if (i != current_pos) mpd_send_delete(c, i);
    198         }
    199         for (int i = 0; i < track_count; i++) {
    200             if (i != new_pos) mpd_send_add(c, new_tracks[i]);
    201         }
    202         if (new_pos > 0) mpd_send_move(c, 0, new_pos);
    203     }
    204 
    205     if (!mpd_command_list_end(c) || !mpd_response_finish(c)) {
    206         fprintf(stderr, "Failed to execute command list: %s\n", mpd_connection_get_error_message(c));
    207     } else {
    208         printf("Queue updated successfully.\n");
    209     }
    210 
    211 cleanup:
    212     free(line);
    213     if (f) fclose(f);
    214     unlink(tmp_template);
    215     if (current_file) free(current_file);
    216     for (int i = 0; i < track_count; i++) free(new_tracks[i]);
    217     free(new_tracks);
    218     mpd_connection_free(c);
    219     return 0;
    220 }