systemSetup

system setup, configuration and dotfiles
git clone https://noulin.net/git/systemSetup.git
Log | Files | Refs | README | LICENSE

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 }