]> git.cworth.org Git - mnemon/blob - mnemon.c
039f0ca0115325a6163018d47f1f1b68e53529a2
[mnemon] / mnemon.c
1 /*
2  * Copyright © 2006 Carl Worth
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA."
17  */
18
19 /* for asprintf */
20 #define _GNU_SOURCE
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24
25 #include <sys/types.h>
26 #include <dirent.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <assert.h>
30
31 typedef struct _item {
32     int count;
33     char *challenge;
34     char *response;
35 } item_t;
36
37 typedef struct _bin {
38     int count;
39     int items_size;
40     int num_items;
41     item_t **items;
42 } bin_t;
43
44 typedef struct _category {
45     char *name;
46     int items_size;
47     int num_items;
48     item_t *items;
49 } category_t;
50
51 typedef struct _mnemon {
52     char *dir_name;
53
54     int categories_size;
55     int num_categories;
56     category_t *categories;
57
58     int bins_size;
59     int num_bins;
60     bin_t *bins;
61 } mnemon_t;
62
63 static void *
64 xmalloc (size_t size)
65 {
66     void *ret;
67
68     ret = malloc (size);
69     if (ret == NULL) {
70         fprintf (stderr, "Error: out of memory\n");
71         exit (1);
72     }
73
74     return ret;
75 }
76
77 static void *
78 xrealloc (void *ptr, size_t size)
79 {
80     void *ret;
81
82     ret = realloc (ptr, size);
83     if (ret == NULL) {
84         fprintf (stderr, "Error: out of memory\n");
85         exit (1);
86     }
87
88     return ret;
89 }
90
91 static char *
92 xstrdup (const char *s)
93 {
94     char *ret;
95
96     ret = strdup (s);
97     if (s == NULL) {
98         fprintf (stderr, "Error: out of memory\n");
99         exit (1);
100     }
101
102     return ret;
103 }
104
105 static void
106 xasprintf (char **strp, const char *fmt, ...)
107 {
108     va_list ap;
109     int ret;
110
111     va_start (ap, fmt);
112     ret = vasprintf (strp, fmt, ap);
113     va_end (ap);
114
115     if (ret < 0) {
116         fprintf (stderr, "Error: out of memory\n");
117         exit (1);
118     }
119 }
120
121 static void
122 item_init (item_t       *item,
123            int           count,
124            const char   *challenge,
125            const char   *response)
126 {
127     item->count = count;
128
129     item->challenge = xmalloc (strlen (challenge) + 1 +
130                                strlen (response) + 1);
131     item->response = item->challenge + strlen (challenge) + 1;
132
133     strcpy (item->challenge, challenge);
134     strcpy (item->response, response);
135 }
136
137 static void
138 item_fini (item_t *item)
139 {
140     /* item->response shares allocation with item->challenge, so
141      * doesn't require a separate call to free */
142     free (item->challenge);
143 }
144
145 static void
146 category_init (category_t *category,
147                const char *name)
148 {
149     category->name = xstrdup (name);
150     
151     category->items_size = 0;
152     category->num_items = 0;
153     category->items = NULL;
154 }
155
156 static void
157 category_fini (category_t *category)
158 {
159     int i;
160
161     for (i = 0; i < category->num_items; i++)
162         item_fini (&category->items[i]);
163
164     free (category->items);
165
166     free (category->name);
167 }
168
169 static void
170 category_grow (category_t *category)
171 {
172     if (category->items_size)
173         category->items_size *= 2;
174     else
175         category->items_size = 1;
176
177     category->items = xrealloc (category->items,
178                                 category->items_size * sizeof (item_t));
179 }
180
181 static item_t *
182 category_add_item (category_t   *category,
183                    int           count,
184                    const char   *challenge,
185                    const char   *response)
186 {
187     item_t *item;
188
189     if (category->num_items == category->items_size)
190         category_grow (category);
191
192     item = &category->items[category->num_items++];
193
194     item_init (item, count, challenge, response);
195
196     return item;
197 }
198
199 static void
200 category_print (category_t      *category,
201                 FILE            *file)
202 {
203     int i;
204     item_t *item;
205
206     for (i = 0; i < category->num_items; i++) {
207         item = &category->items[i];
208         if (i != 0)
209             fprintf (file, "\n");
210         fprintf (file, "%d\n%s\n%s\n",
211                  item->count,
212                  item->challenge,
213                  item->response);
214     }
215 }
216
217 static void
218 bin_init (bin_t *bin,
219           int    count)
220 {
221     bin->count = count;
222
223     bin->items_size = 0;
224     bin->num_items = 0;
225     bin->items = NULL;
226 }
227
228 static void
229 bin_fini (bin_t *bin)
230 {
231     free (bin->items);
232 }
233
234 static void
235 bin_grow (bin_t *bin)
236 {
237     if (bin->items_size)
238         bin->items_size *= 2;
239     else
240         bin->items_size = 1;
241
242     bin->items = xrealloc (bin->items,
243                            bin->items_size * sizeof (item_t*));
244 }
245
246 static void
247 bin_add_item (bin_t     *bin,
248               item_t    *item)
249 {
250     assert (item->count == bin->count);
251
252     if (bin->num_items == bin->items_size)
253         bin_grow (bin);
254
255     bin->items[bin->num_items++] = item;
256 }
257
258 static void
259 mnemon_init (mnemon_t *mnemon)
260 {
261     char *home;
262
263     home = getenv ("HOME");
264     if (home == NULL)
265         home = "";
266
267     xasprintf (&mnemon->dir_name, "%s/.mnemon", getenv ("HOME"));
268
269     mnemon->categories_size = 0;
270     mnemon->num_categories = 0;
271     mnemon->categories = NULL;
272
273     mnemon->bins_size = 0;
274     mnemon->num_bins = 0;
275     mnemon->bins = NULL;
276 }
277
278 static void
279 mnemon_fini (mnemon_t *mnemon)
280 {
281     int i;
282
283     for (i = 0; i < mnemon->num_bins; i++)
284         bin_fini (&mnemon->bins[i]);
285     free (mnemon->bins);
286
287     for (i = 0; i < mnemon->num_categories; i++)
288         category_fini (&mnemon->categories[i]);
289     free (mnemon->categories);
290
291     free (mnemon->dir_name);
292 }
293
294 static void
295 mnemon_categories_grow (mnemon_t *mnemon)
296 {
297     if (mnemon->categories_size)
298         mnemon->categories_size *= 2;
299     else
300         mnemon->categories_size = 1;
301
302     mnemon->categories = xrealloc (mnemon->categories,
303                                    mnemon->categories_size * sizeof (category_t));
304 }
305
306 static category_t *
307 mnemon_get_category (mnemon_t   *mnemon,
308                      const char *name)
309 {
310     int i;
311     category_t *category;
312
313     for (i = 0; i < mnemon->num_categories; i++)
314         if (strcmp (mnemon->categories[i].name, name) == 0)
315             return &mnemon->categories[i];
316
317     mnemon_categories_grow (mnemon);
318
319     category = &mnemon->categories[mnemon->num_categories++];
320
321     category_init (category, name);
322
323     return category;
324 }
325
326 static void
327 mnemon_bins_grow (mnemon_t *mnemon)
328 {
329     if (mnemon->bins_size)
330         mnemon->bins_size *= 2;
331     else
332         mnemon->bins_size = 1;
333
334     mnemon->bins = xrealloc (mnemon->bins,
335                              mnemon->bins_size * sizeof (bin_t));
336 }
337
338 static bin_t *
339 mnemon_get_bin (mnemon_t        *mnemon,
340                 int              count)
341 {
342     int i;
343     bin_t *bin;
344
345     for (i = 0; i < mnemon->num_bins; i++)
346         if (mnemon->bins[i].count == count)
347             return &mnemon->bins[i];
348         else if (mnemon->bins[i].count > count)
349             break;
350
351     mnemon_bins_grow (mnemon);
352
353     bin = &mnemon->bins[i];
354
355     /* Make room to insert new bin at its sorted location. */
356     memmove (bin + 1, bin, (mnemon->num_bins - i) * sizeof (bin_t));
357     mnemon->num_bins++;
358
359     bin_init (bin, count);
360
361     return bin;
362 }
363
364 static void
365 chomp (char *s)
366 {
367     int len = strlen (s);
368     if (len == 0)
369         return;
370     if (s[len - 1] == '\n')
371         s[len - 1] = '\0';
372 }
373
374 static void
375 mnemon_load_category (mnemon_t          *mnemon,
376                       const char        *name)
377 {
378     FILE *file;
379     char *line = NULL, *end;
380     size_t line_size = 0;
381     ssize_t bytes_read;
382     int line_count = 0;
383     char *path;
384     category_t *category;
385     int i;
386
387     path = xmalloc (strlen (mnemon->dir_name) + 1 + strlen (name) + 1);
388     sprintf (path, "%s/%s", mnemon->dir_name, name);
389
390     file = fopen (path, "r");
391     if (file == NULL) {
392         fprintf (stderr, "Error: Failed to open %s: %s\n",
393                  path, strerror (errno));
394         exit (1);
395     }
396
397     category = mnemon_get_category (mnemon, name);
398
399     while (1) {
400         int count;
401         char *challenge, *response;
402
403         /* Read bin number (ignoring blank separator lines) */
404         do {
405             bytes_read = getline (&line, &line_size, file);
406             if (bytes_read == -1)
407                 goto END_OF_FILE;
408             line_count++;
409             chomp (line);
410         } while (*line == '\0');
411
412         count = strtol (line, &end, 10);
413         if (*end != '\0') {
414             fprintf (stderr, "Failed to parse bin number from \"%s\" at %s:%d\n",
415                      line, path, line_count);
416             exit (1);
417         }
418
419         /* Read challenge */
420         bytes_read = getline (&line, &line_size, file);
421         if (bytes_read == -1)
422             break;
423         line_count++;
424         chomp (line);
425         challenge = strdup (line);
426
427         /* Read response */
428         bytes_read = getline (&line, &line_size, file);
429         if (bytes_read == -1)
430             break;
431         line_count++;
432         chomp (line);
433         response = line;
434
435         category_add_item (category, count, challenge, response);
436
437         free (challenge);
438     }
439   END_OF_FILE:
440
441     free (line);
442     fclose (file);
443     free (path);
444
445     /* Resize category items to fit exactly. */
446     category->items_size = category->num_items;
447     category->items = xrealloc (category->items, category->items_size * sizeof (item_t));
448
449     /* Now that the category is completely loaded, with stable
450      * pointers to every item, we can add each item to its appropriate
451      * bin. */
452     for (i = 0; i < category->num_items; i++) {
453         item_t *item = &category->items[i];
454         bin_t *bin = mnemon_get_bin (mnemon, item->count);
455
456         bin_add_item (bin, item);
457     }
458 }
459
460 static void
461 mnemon_load (mnemon_t *mnemon)
462 {
463     DIR *dir;
464     struct dirent *dirent;
465
466     dir = opendir (mnemon->dir_name);
467     if (dir == NULL) {
468         fprintf (stderr, "Error: Failed to open directory %s: %s\n",
469                  mnemon->dir_name, strerror (errno));
470         exit (1);
471     }
472
473     while (1) {
474         dirent = readdir (dir);
475         if (dirent == NULL)
476             break;
477
478         if (dirent->d_type == DT_REG) {
479             /* Ignore files matching *~, (yes, this shouldn't be
480              * hard-coded in such an ad-hoc way, but there you go. */
481             if (dirent->d_name[strlen(dirent->d_name)-1] != '~')
482                 mnemon_load_category (mnemon, dirent->d_name);
483         }
484     }
485
486     closedir (dir);
487 }
488
489 static void
490 mnemon_save (mnemon_t *mnemon)
491 {
492     int i, err;
493     char *filename, *lock_filename;
494     FILE *file;
495     category_t *category;
496
497     for (i = 0; i < mnemon->num_categories; i++) {
498         category = &mnemon->categories[i];
499
500         xasprintf (&filename, "%s/%s",
501                    mnemon->dir_name, category->name);
502         xasprintf (&lock_filename, "%s/.#%s",
503                    mnemon->dir_name, category->name);
504
505         file = fopen (lock_filename, "w");
506         if (file == NULL) {
507             fprintf (stderr, "Error: Failed to open %s for writing: %s\n",
508                      lock_filename, strerror (errno));
509             continue;
510         }
511
512         category_print (category, file);
513
514         fclose (file);
515
516         err = rename (lock_filename, filename);
517         if (err < 0) {
518             fprintf (stderr, "Error: Failes to rename %s to %s: %s\n",
519                      lock_filename, filename, strerror (errno));
520             continue;
521         }
522
523         free (filename);
524         free (lock_filename);
525     }
526 }
527
528 int
529 main (int argc, char *argv[])
530 {
531     mnemon_t mnemon;
532
533     mnemon_init (&mnemon);
534
535     mnemon_load (&mnemon);
536
537     mnemon_save (&mnemon);
538
539     mnemon_fini (&mnemon);
540
541     return 0;
542 }