mpd_update_library.c (5977B)
1 #define _POSIX_C_SOURCE 200809L 2 3 #include "mpd_common.h" 4 5 #include <errno.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <time.h> 9 #include <unistd.h> 10 11 #define UPDATE_RETRIES 5U 12 13 static void usage(FILE *file, const char *program) { 14 fprintf(file, "Usage: %s [--no-wait] [--rescan] [FILE|-]\n", program); 15 } 16 17 static int compare_strings(const void *left, const void *right) { 18 const char *const *a = left; 19 const char *const *b = right; 20 return strcmp(*a, *b); 21 } 22 23 static bool top_directories(const struct mt_strvec *paths, 24 struct mt_strvec *directories) { 25 for (size_t i = 0; i < paths->len; i++) { 26 const char *slash = strchr(paths->items[i], '/'); 27 size_t length = slash == NULL ? strlen(paths->items[i]) 28 : (size_t)(slash - paths->items[i]); 29 if (!mt_strvec_push_n(directories, paths->items[i], length)) { 30 perror("mpd: store update directory"); 31 return false; 32 } 33 } 34 35 qsort(directories->items, directories->len, sizeof(*directories->items), 36 compare_strings); 37 size_t output = 0; 38 for (size_t i = 0; i < directories->len; i++) { 39 if (output > 0U && 40 strcmp(directories->items[output - 1U], directories->items[i]) == 0) { 41 free(directories->items[i]); 42 } else { 43 directories->items[output++] = directories->items[i]; 44 } 45 } 46 directories->len = output; 47 return true; 48 } 49 50 static void retry_delay(unsigned attempt) { 51 unsigned milliseconds = 250U << attempt; 52 if (milliseconds > 4000U) 53 milliseconds = 4000U; 54 struct timespec delay = {.tv_sec = (time_t)(milliseconds / 1000U), 55 .tv_nsec = (long)(milliseconds % 1000U) * 1000000L}; 56 while (nanosleep(&delay, &delay) < 0 && errno == EINTR) { 57 } 58 } 59 60 static struct mpd_connection *reconnect(unsigned attempt) { 61 if (attempt > 0U) 62 retry_delay(attempt - 1U); 63 return mt_connect(); 64 } 65 66 static bool submit_directory(struct mpd_connection **connection, 67 const char *directory, bool rescan) { 68 for (unsigned attempt = 0; attempt < UPDATE_RETRIES; attempt++) { 69 if (*connection == NULL) 70 *connection = reconnect(attempt); 71 if (*connection == NULL) 72 continue; 73 74 unsigned id = rescan ? mpd_run_rescan(*connection, directory) 75 : mpd_run_update(*connection, directory); 76 if (id != 0U) { 77 printf("Submitted update for '%s' (job %u).\n", directory, id); 78 return true; 79 } 80 81 enum mpd_error error = mpd_connection_get_error(*connection); 82 mt_report_error(*connection, rescan ? "submit rescan" : "submit update"); 83 if (error == MPD_ERROR_SERVER || error == MPD_ERROR_ARGUMENT) 84 return false; 85 mpd_connection_free(*connection); 86 *connection = NULL; 87 } 88 fprintf(stderr, "mpd: exhausted retries for '%s'\n", directory); 89 return false; 90 } 91 92 static bool wait_for_updates(struct mpd_connection **connection) { 93 for (unsigned failures = 0; failures < UPDATE_RETRIES;) { 94 if (*connection == NULL) 95 *connection = reconnect(failures); 96 if (*connection == NULL) { 97 failures++; 98 continue; 99 } 100 101 struct mpd_status *status = mpd_run_status(*connection); 102 if (status == NULL) { 103 mt_report_error(*connection, "check update status"); 104 mpd_connection_free(*connection); 105 *connection = NULL; 106 failures++; 107 continue; 108 } 109 unsigned update_id = mpd_status_get_update_id(status); 110 mpd_status_free(status); 111 if (update_id == 0U) 112 return true; 113 114 enum mpd_idle event = mpd_run_idle_mask(*connection, MPD_IDLE_UPDATE); 115 if ((event & MPD_IDLE_UPDATE) != 0) { 116 failures = 0; 117 continue; 118 } 119 mt_report_error(*connection, "wait for database update"); 120 mpd_connection_free(*connection); 121 *connection = NULL; 122 failures++; 123 } 124 fprintf(stderr, "mpd: exhausted retries while waiting for updates\n"); 125 return false; 126 } 127 128 int main(int argc, char **argv) { 129 bool wait = true; 130 bool rescan = false; 131 const char *path = NULL; 132 for (int i = 1; i < argc; i++) { 133 if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { 134 usage(stdout, argv[0]); 135 return 0; 136 } 137 if (strcmp(argv[i], "--no-wait") == 0) { 138 wait = false; 139 } else if (strcmp(argv[i], "--rescan") == 0) { 140 rescan = true; 141 } else if (argv[i][0] == '-' && strcmp(argv[i], "-") != 0) { 142 usage(stderr, argv[0]); 143 return 2; 144 } else if (path == NULL) { 145 path = argv[i]; 146 } else { 147 usage(stderr, argv[0]); 148 return 2; 149 } 150 } 151 152 struct mt_strvec paths; 153 struct mt_strvec directories; 154 mt_strvec_init(&paths); 155 mt_strvec_init(&directories); 156 bool ok = true; 157 158 if (path == NULL && isatty(STDIN_FILENO)) { 159 ok = mt_strvec_push(&directories, ""); 160 } else { 161 bool close_input; 162 FILE *input = mt_open_input(path, &close_input); 163 if (input == NULL) 164 ok = false; 165 else { 166 ok = mt_read_lines(input, &paths); 167 if (close_input && fclose(input) != 0) { 168 perror("mpd: close input"); 169 ok = false; 170 } 171 if (ok) 172 ok = top_directories(&paths, &directories); 173 } 174 } 175 if (!ok) { 176 mt_strvec_clear(&paths); 177 mt_strvec_clear(&directories); 178 return 1; 179 } 180 if (directories.len == 0U) { 181 printf("No directories found in input.\n"); 182 mt_strvec_clear(&paths); 183 mt_strvec_clear(&directories); 184 return 0; 185 } 186 187 struct mpd_connection *connection = NULL; 188 for (size_t i = 0; i < directories.len; i++) { 189 if (!submit_directory(&connection, directories.items[i], rescan)) { 190 ok = false; 191 break; 192 } 193 } 194 if (ok && wait) { 195 printf("Waiting for database updates to finish...\n"); 196 ok = wait_for_updates(&connection); 197 } 198 if (ok) 199 printf("Processed %zu director%s successfully.\n", directories.len, 200 directories.len == 1U ? "y" : "ies"); 201 202 if (connection != NULL) 203 mpd_connection_free(connection); 204 mt_strvec_clear(&paths); 205 mt_strvec_clear(&directories); 206 return ok ? 0 : 1; 207 }