Add categories and bins to store data
[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
349     mnemon_bins_grow (mnemon);
350
351     bin = &mnemon->bins[mnemon->num_bins++];
352
353     bin_init (bin, count);
354
355     return bin;
356 }
357
358 static void
359 mnemon_add_item (mnemon_t       *mnemon,
360                  category_t     *category,
361                  int             count,
362                  const char     *challenge,
363                  const char     *response)
364 {
365     item_t *item;
366     bin_t *bin;
367
368     item = category_add_item (category, count, challenge, response);
369
370     bin = mnemon_get_bin (mnemon, count);
371
372     bin_add_item (bin, item);
373 }
374
375 static void
376 chomp (char *s)
377 {
378     int len = strlen (s);
379     if (len == 0)
380         return;
381     if (s[len - 1] == '\n')
382         s[len - 1] = '\0';
383 }
384
385 static void
386 mnemon_load_category (mnemon_t          *mnemon,
387                       const char        *name)
388 {
389     FILE *file;
390     char *line = NULL, *end;
391     size_t line_size = 0;
392     ssize_t bytes_read;
393     int line_count = 0;
394     char *path;
395     category_t *category;
396
397     path = xmalloc (strlen (mnemon->dir_name) + 1 + strlen (name) + 1);
398     sprintf (path, "%s/%s", mnemon->dir_name, name);
399
400     file = fopen (path, "r");
401     if (file == NULL) {
402         fprintf (stderr, "Error: Failed to open %s: %s\n",
403                  path, strerror (errno));
404         exit (1);
405     }
406
407     category = mnemon_get_category (mnemon, name);
408
409     while (1) {
410         int count;
411         char *challenge, *response;
412
413         /* Read bin number (ignoring blank separator lines) */
414         do {
415             bytes_read = getline (&line, &line_size, file);
416             if (bytes_read == -1)
417                 goto END_OF_FILE;
418             line_count++;
419             chomp (line);
420         } while (*line == '\0');
421
422         count = strtol (line, &end, 10);
423         if (*end != '\0') {
424             fprintf (stderr, "Failed to parse bin number from \"%s\" at %s:%d\n",
425                      line, path, line_count);
426             exit (1);
427         }
428
429         /* Read challenge */
430         bytes_read = getline (&line, &line_size, file);
431         if (bytes_read == -1)
432             break;
433         line_count++;
434         chomp (line);
435         challenge = strdup (line);
436
437         /* Read response */
438         bytes_read = getline (&line, &line_size, file);
439         if (bytes_read == -1)
440             break;
441         line_count++;
442         chomp (line);
443         response = line;
444
445         mnemon_add_item (mnemon, category, count, challenge, response);
446
447         free (challenge);
448     }
449   END_OF_FILE:
450
451     free (line);
452     fclose (file);
453     free (path);
454 }
455
456 static void
457 mnemon_load (mnemon_t *mnemon)
458 {
459     DIR *dir;
460     struct dirent *dirent;
461
462     dir = opendir (mnemon->dir_name);
463     if (dir == NULL) {
464         fprintf (stderr, "Error: Failed to open directory %s: %s\n",
465                  mnemon->dir_name, strerror (errno));
466         exit (1);
467     }
468
469     while (1) {
470         dirent = readdir (dir);
471         if (dirent == NULL)
472             break;
473
474         if (dirent->d_type == DT_REG) {
475             /* Ignore files matching *~, (yes, this shouldn't be
476              * hard-coded in such an ad-hoc way, but there you go. */
477             if (dirent->d_name[strlen(dirent->d_name)-1] != '~')
478                 mnemon_load_category (mnemon, dirent->d_name);
479         }
480     }
481
482     closedir (dir);
483 }
484
485 static void
486 mnemon_save (mnemon_t *mnemon)
487 {
488     int i, err;
489     char *filename, *lock_filename;
490     FILE *file;
491     category_t *category;
492
493     for (i = 0; i < mnemon->num_categories; i++) {
494         category = &mnemon->categories[i];
495
496         xasprintf (&filename, "%s/%s",
497                    mnemon->dir_name, category->name);
498         xasprintf (&lock_filename, "%s/.#%s",
499                    mnemon->dir_name, category->name);
500         file = fopen (lock_filename, "w");
501         if (file == NULL) {
502             fprintf (stderr, "Error: Failed to open %s for writing: %s\n",
503                      lock_filename, strerror (errno));
504             continue;
505         }
506
507         category_print (category, file);
508
509         err = rename (lock_filename, filename);
510         if (err < 0) {
511             fprintf (stderr, "Error: Failes to rename %s to %s: %s\n",
512                      lock_filename, filename, strerror (errno));
513             continue;
514         }
515     }
516 }
517
518 int
519 main (int argc, char *argv[])
520 {
521     mnemon_t mnemon;
522
523     mnemon_init (&mnemon);
524
525     mnemon_load (&mnemon);
526
527     mnemon_save (&mnemon);
528
529     mnemon_fini (&mnemon);
530
531     return 0;
532 }