choices.c (7551B)
1 #define _GNU_SOURCE // strcasestr 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <pthread.h> 6 #include <unistd.h> 7 #include <errno.h> 8 9 #include "options.h" 10 #include "choices.h" 11 #include "match.h" 12 13 /* Initial size of buffer for storing input in memory */ 14 #define INITIAL_BUFFER_CAPACITY 4096 15 16 /* Initial size of choices array */ 17 #define INITIAL_CHOICE_CAPACITY 128 18 19 static int cmpchoice(const void *_idx1, const void *_idx2) { 20 const struct scored_result *a = _idx1; 21 const struct scored_result *b = _idx2; 22 23 if (a->score == b->score) { 24 /* To ensure a stable sort, we must also sort by the string 25 * pointers. We can do this since we know all the strings are 26 * from a contiguous memory segment (buffer in choices_t). 27 */ 28 if (a->str < b->str) { 29 return -1; 30 } else { 31 return 1; 32 } 33 } else if (a->score < b->score) { 34 return 1; 35 } else { 36 return -1; 37 } 38 } 39 40 static void *safe_realloc(void *buffer, size_t size) { 41 buffer = realloc(buffer, size); 42 if (!buffer) { 43 fprintf(stderr, "Error: Can't allocate memory (%zu bytes)\n", size); 44 abort(); 45 } 46 47 return buffer; 48 } 49 50 void choices_fread(choices_t *c, FILE *file, char input_delimiter) { 51 /* Save current position for parsing later */ 52 size_t buffer_start = c->buffer_size; 53 54 /* Resize buffer to at least one byte more capacity than our current 55 * size. This uses a power of two of INITIAL_BUFFER_CAPACITY. 56 * This must work even when c->buffer is NULL and c->buffer_size is 0 57 */ 58 size_t capacity = INITIAL_BUFFER_CAPACITY; 59 while (capacity <= c->buffer_size) 60 capacity *= 2; 61 c->buffer = safe_realloc(c->buffer, capacity); 62 63 /* Continue reading until we get a "short" read, indicating EOF */ 64 while ((c->buffer_size += fread(c->buffer + c->buffer_size, 1, capacity - c->buffer_size, file)) == capacity) { 65 capacity *= 2; 66 c->buffer = safe_realloc(c->buffer, capacity); 67 } 68 c->buffer = safe_realloc(c->buffer, c->buffer_size + 1); 69 c->buffer[c->buffer_size++] = '\0'; 70 71 /* Truncate buffer to used size, (maybe) freeing some memory for 72 * future allocations. 73 */ 74 75 /* Tokenize input and add to choices */ 76 const char *line_end = c->buffer + c->buffer_size; 77 char *line = c->buffer + buffer_start; 78 do { 79 char *nl = strchr(line, input_delimiter); 80 if (nl) 81 *nl++ = '\0'; 82 83 /* Skip empty lines */ 84 if (*line) 85 choices_add(c, line); 86 87 line = nl; 88 } while (line && line < line_end); 89 } 90 91 static void choices_resize(choices_t *c, size_t new_capacity) { 92 c->strings = safe_realloc(c->strings, new_capacity * sizeof(const char *)); 93 c->capacity = new_capacity; 94 } 95 96 static void choices_reset_search(choices_t *c) { 97 free(c->results); 98 c->selection = c->available = 0; 99 c->results = NULL; 100 } 101 102 void choices_init(choices_t *c, options_t *options) { 103 c->strings = NULL; 104 c->results = NULL; 105 106 c->buffer_size = 0; 107 c->buffer = NULL; 108 109 c->capacity = c->size = 0; 110 choices_resize(c, INITIAL_CHOICE_CAPACITY); 111 112 if (options->workers) { 113 c->worker_count = options->workers; 114 } else { 115 c->worker_count = (int)sysconf(_SC_NPROCESSORS_ONLN); 116 } 117 118 choices_reset_search(c); 119 } 120 121 void choices_destroy(choices_t *c) { 122 free(c->buffer); 123 c->buffer = NULL; 124 c->buffer_size = 0; 125 126 free(c->strings); 127 c->strings = NULL; 128 c->capacity = c->size = 0; 129 130 free(c->results); 131 c->results = NULL; 132 c->available = c->selection = 0; 133 } 134 135 void choices_add(choices_t *c, const char *choice) { 136 /* Previous search is now invalid */ 137 choices_reset_search(c); 138 139 if (c->size == c->capacity) { 140 choices_resize(c, c->capacity * 2); 141 } 142 c->strings[c->size++] = choice; 143 } 144 145 size_t choices_available(choices_t *c) { 146 return c->available; 147 } 148 149 #define BATCH_SIZE 512 150 151 struct result_list { 152 struct scored_result *list; 153 size_t size; 154 }; 155 156 struct search_job { 157 pthread_mutex_t lock; 158 choices_t *choices; 159 const char *search; 160 size_t processed; 161 struct worker *workers; 162 }; 163 164 struct worker { 165 pthread_t thread_id; 166 struct search_job *job; 167 unsigned int worker_num; 168 struct result_list result; 169 }; 170 171 static void worker_get_next_batch(struct search_job *job, size_t *start, size_t *end) { 172 pthread_mutex_lock(&job->lock); 173 174 *start = job->processed; 175 176 job->processed += BATCH_SIZE; 177 if (job->processed > job->choices->size) { 178 job->processed = job->choices->size; 179 } 180 181 *end = job->processed; 182 183 pthread_mutex_unlock(&job->lock); 184 } 185 186 static struct result_list merge2(struct result_list list1, struct result_list list2) { 187 size_t result_index = 0, index1 = 0, index2 = 0; 188 189 struct result_list result; 190 result.size = list1.size + list2.size; 191 result.list = malloc(result.size * sizeof(struct scored_result)); 192 if (!result.list) { 193 fprintf(stderr, "Error: Can't allocate memory\n"); 194 abort(); 195 } 196 197 while(index1 < list1.size && index2 < list2.size) { 198 if (cmpchoice(&list1.list[index1], &list2.list[index2]) < 0) { 199 result.list[result_index++] = list1.list[index1++]; 200 } else { 201 result.list[result_index++] = list2.list[index2++]; 202 } 203 } 204 205 while(index1 < list1.size) { 206 result.list[result_index++] = list1.list[index1++]; 207 } 208 while(index2 < list2.size) { 209 result.list[result_index++] = list2.list[index2++]; 210 } 211 212 free(list1.list); 213 free(list2.list); 214 215 return result; 216 } 217 218 static void *choices_search_worker(void *data) { 219 struct worker *w = (struct worker *)data; 220 struct search_job *job = w->job; 221 const choices_t *c = job->choices; 222 struct result_list *result = &w->result; 223 224 size_t start, end; 225 226 for(;;) { 227 worker_get_next_batch(job, &start, &end); 228 229 if(start == end) { 230 break; 231 } 232 233 for(size_t i = start; i < end; i++) { 234 if (has_match(job->search, c->strings[i])) { 235 result->list[result->size].str = c->strings[i]; 236 result->list[result->size].score = match(job->search, c->strings[i]); 237 result->size++; 238 } 239 } 240 } 241 242 /* Sort the partial result */ 243 qsort(result->list, result->size, sizeof(struct scored_result), cmpchoice); 244 245 /* Fan-in, merging results */ 246 for(unsigned int step = 0;; step++) { 247 if (w->worker_num % (2 << step)) 248 break; 249 250 unsigned int next_worker = w->worker_num | (1 << step); 251 if (next_worker >= c->worker_count) 252 break; 253 254 if ((errno = pthread_join(job->workers[next_worker].thread_id, NULL))) { 255 perror("pthread_join"); 256 exit(EXIT_FAILURE); 257 } 258 259 w->result = merge2(w->result, job->workers[next_worker].result); 260 } 261 262 return NULL; 263 } 264 265 void choices_search(choices_t *c, const char *search) { 266 choices_reset_search(c); 267 268 struct scored_result *list = malloc(c->size * sizeof(struct scored_result)); /* FIXME: This is overkill */ 269 size_t size = 0; 270 271 char *s = strdup(search); 272 size_t len = strlen(search); 273 274 for (size_t a = 0; a < len ; a++) { 275 if (s[a] == ' ') s[a] = 0; 276 } 277 278 for(size_t i = 0 ; i < c->size ; i++) { 279 char *p = s; 280 const char *t = c->strings[i]; 281 size_t lenh = strlen(c->strings[i]); 282 int foundAllWords = 1; 283 do { 284 char *h = strcasestr(t, p); 285 if (!h) { 286 foundAllWords = 0; 287 break; 288 } 289 t = h + strlen(p); 290 p += strlen(p)+1; 291 } while((t < c->strings[i] + lenh) && (p < s + len)); 292 293 if (foundAllWords) { 294 // match 295 list[size].str = c->strings[i]; 296 list[size].score = 1; 297 size++; 298 } 299 } 300 301 c->results = list; 302 c->available = size; 303 } 304 305 const char *choices_get(choices_t *c, size_t n) { 306 if (n < c->available) { 307 return c->results[n].str; 308 } else { 309 return NULL; 310 } 311 } 312 313 score_t choices_getscore(choices_t *c, size_t n) { 314 return c->results[n].score; 315 } 316 317 void choices_prev(choices_t *c) { 318 if (c->available) 319 c->selection = (c->selection + c->available - 1) % c->available; 320 } 321 322 void choices_next(choices_t *c) { 323 if (c->available) 324 c->selection = (c->selection + 1) % c->available; 325 }