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 }