'smart qotes' now will replace '<', '>' and '&' inside 'pre' and 'code' tags
[k8lowj.git] / src / journalstore-xml.c
blob5f99a26841cb94ec063e272483b7bf337f560cd2
1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2005 Evan Martin <evan@livejournal.com>
4 * vim: tabstop=4 shiftwidth=4 noexpandtab :
5 */
6 #ifndef HAVE_SQLITE3
8 #include "glib-all.h"
9 #include <errno.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #ifndef G_OS_WIN32
14 #include <unistd.h>
15 #endif
17 #ifndef G_OS_WIN32
18 #include <netinet/in.h>
19 #else
20 #include <winsock2.h>
21 #endif
23 #include <libxml/parser.h>
25 #include "journalstore.h"
27 #include "conf.h"
28 #include "jam_xml.h"
29 #include "util.h"
31 #define JOURNAL_STORE_INDEX_VERSION 3
32 #define JOURNAL_STORE_XML_VERSION 2
34 struct _JournalStore {
35 JamAccount *account;
37 char *path;
38 /* the index is a flat array of itemid->time_t.
39 * it's small enough to scan through when
40 * we need to do a reverse lookup. */
41 GArray *index;
43 /* has the file format changed in such a way that we need to resync? */
44 gboolean invalid;
46 /* we cache the "current" xml doc around in memory,
47 * because we usually want to grab multiple entries from
48 * one document. */
49 xmlDocPtr xml_doc; int xml_year, xml_mon; gboolean xml_dirty;
52 JamAccount*
53 journal_store_get_account(JournalStore *js) {
54 return js->account;
57 #define index_at(idx, i) g_array_index(idx, time_t, i)
58 #define index_get(idx, i) ntohl(index_at(idx, i))
59 #define index_set(idx, i, val) index_at(idx, i) = htonl(val)
61 static gboolean
62 index_load(JournalStore *js, GError **err) {
63 char *path;
64 struct stat statbuf;
65 FILE *f;
66 int itemcount;
67 int ret;
68 guint32 ver;
70 path = g_build_filename(js->path, "index", NULL);
72 if (stat(path, &statbuf) < 0 && errno == ENOENT) {
73 g_free(path);
74 return TRUE;
77 f = fopen(path, "rb");
78 g_free(path);
79 if (f == NULL) {
80 g_print("XXX index fopen: %s\n", g_strerror(errno));
81 return FALSE;
84 itemcount = statbuf.st_size / sizeof(time_t);
85 g_array_set_size(js->index, itemcount);
86 ret = (int)fread(js->index->data, sizeof(time_t), itemcount, f);
87 if (ret < itemcount) {
88 g_print("XXX index fread read too little\n");
89 return FALSE;
92 ver = index_get(js->index, 0);
93 if ((ver & 0xFF000000) != 0) {
94 /* byte order is messed up in older versions. fix it here. yuck. */
95 int i;
96 for (i = 0; i < itemcount; i++) {
97 guint32 v = index_at(js->index, i);
98 index_at(js->index, i) =
99 ((v & 0x000000FF) << 24) |
100 ((v & 0x0000FF00) << 8) |
101 ((v & 0x00FF0000) >> 8) |
102 ((v & 0xFF000000) >> 24);
104 ver = index_get(js->index, 0);
107 if (ver < JOURNAL_STORE_INDEX_VERSION) {
108 /* file format somehow changed. clear the index. */
109 g_array_set_size(js->index, 1);
110 index_set(js->index, 0, JOURNAL_STORE_INDEX_VERSION);
111 js->invalid = TRUE;
114 return TRUE;
117 static gboolean
118 index_write(const char *storepath, GArray *idx, GError **err) {
119 char *path;
120 FILE *f;
121 size_t wrote;
123 path = g_build_filename(storepath, "index", NULL);
125 f = fopen(path, "wb");
126 g_free(path);
127 if (f == NULL) {
128 g_set_error(err, 0, 0, _("Error opening index: %s"),
129 g_strerror(errno));
130 return FALSE;
133 wrote = fwrite(idx->data, sizeof(time_t), idx->len, f);
134 if (wrote < idx->len) {
135 g_set_error(err, 0, 0, _("Error writing index: %s"),
136 g_strerror(errno));
137 fclose(f);
138 return FALSE;
140 fclose(f);
142 return TRUE;
144 static gboolean
145 index_save(JournalStore *js, GError **err) {
146 return index_write(js->path, js->index, err);
149 static void
150 time_to_docidx(const time_t *entrytime, int *year, int *mon, int *day) {
151 struct tm *lt;
152 if (entrytime) {
153 lt = gmtime(entrytime);
154 *year = lt->tm_year+1900;
155 *mon = lt->tm_mon+1;
156 *day = lt->tm_mday;
157 } else {
158 *year = *mon = *day = 0;
161 static char*
162 docidx_to_str(char *base, int year, int mon) {
163 return g_strdup_printf("%s/%d/%02d.xml", base, year, mon);
166 static void
167 delete_unused_whitespace_r(xmlNodePtr node) {
168 xmlNodePtr next;
170 /* whitespace is significant within many nodes, like the event,
171 * but we only get isolated pure-whitespace nodes when whitespace
172 * is used alongside nodes. all journal content is escaped, so
173 * all nodes that contain both whitespace and nodes should be
174 * stripped. */
176 for (node = node->xmlChildrenNode; node; node = next) {
177 next = node->next;
178 if (xmlIsBlankNode(node)) {
179 xmlUnlinkNode(node);
180 xmlFreeNode(node);
181 } else {
182 delete_unused_whitespace_r(node);
187 static void
188 delete_unused_whitespace(xmlDocPtr doc) {
189 delete_unused_whitespace_r(xmlDocGetRootElement(doc));
192 static xmlDocPtr
193 make_new_doc(int year, int mon) {
194 xmlDocPtr doc;
195 xmlNodePtr node;
196 jam_xmlNewDoc(&doc, &node, "entrymonth");
197 jam_xmlSetIntProp(node, "version", JOURNAL_STORE_XML_VERSION);
198 jam_xmlSetIntProp(node, "year", year);
199 jam_xmlSetIntProp(node, "month", mon);
200 return doc;
203 static gboolean
204 switch_xml_file(JournalStore *js, int year, int mon, GError **err) {
205 char *path;
206 struct stat statbuf;
207 xmlDocPtr doc = NULL;
209 /* are we already there? */
210 if (year == js->xml_year && mon == js->xml_mon) {
211 return TRUE;
214 /* otherwise, switch to this file.
215 * XXX protective locking would be good. */
216 /* first write out the old file, if we have one. */
217 if (js->xml_year && js->xml_dirty) {
218 char *tmppath;
219 path = docidx_to_str(js->path, js->xml_year, js->xml_mon);
220 if (!verify_path(path, FALSE, err)) {
221 g_free(path);
222 return FALSE;
224 /* write to a temp file and then rename,
225 * to avoid losing data if we die mid-write. */
226 tmppath = g_strconcat(path, ".tmp", NULL);
227 if (xmlSaveFormatFile(tmppath, js->xml_doc, TRUE) < 0) {
228 g_set_error(err, 0, 0, _("Error writing journal xml file to %s: %s"),
229 tmppath, g_strerror(errno));
230 g_free(tmppath); g_free(path);
231 return FALSE;
233 if (rename(tmppath, path) < 0) {
234 g_set_error(err, 0, 0, _("Error renaming journal xml file %s to %s: %s"),
235 tmppath, path, g_strerror(errno));
236 g_free(tmppath); g_free(path);
237 return FALSE;
239 g_free(tmppath);
240 g_free(path);
243 /* then switch to the new file, if we have one. */
244 if (year) {
245 path = docidx_to_str(js->path, year, mon);
246 if (stat(path, &statbuf) < 0 && errno == ENOENT) {
247 doc = make_new_doc(year, mon);
248 } else {
249 int ver;
250 doc = xmlParseFile(path);
252 if (!jam_xmlGetIntProp(xmlDocGetRootElement(doc), "version", &ver))
253 ver = 0;
255 if (ver < JOURNAL_STORE_XML_VERSION) {
256 /* out of date document. */
257 xmlFreeDoc(doc);
258 doc = make_new_doc(year, mon);
259 } else {
260 /* if there is any whitespace in nodes where we don't
261 * care about whitespace, libxml thinks that the whitespace
262 * was important and won't reformat it correctly.
263 * so we need to delete all the whitespace on load. */
264 delete_unused_whitespace(doc);
266 if (!doc) {
267 g_set_error(err, 0, 0,
268 _("Error parsing journal XML file %s"), path);
269 g_free(path);
270 return FALSE;
274 g_free(path);
275 js->xml_dirty = FALSE;
277 js->xml_doc = doc;
278 js->xml_year = year;
279 js->xml_mon = mon;
281 return TRUE;
284 static gboolean
285 switch_xml_file_from_time(JournalStore *js, time_t *entrytime, GError **err) {
286 int year, mon, day;
287 time_to_docidx(entrytime, &year, &mon, &day);
288 return switch_xml_file(js, year, mon, err);
291 time_t
292 journal_store_lookup_entry_time(JournalStore *js, int itemid) {
293 if (itemid >= (int)js->index->len)
294 return 0;
295 return index_get(js->index, itemid);
298 static xmlNodePtr
299 make_day_node(xmlDocPtr doc, int day) {
300 xmlNodePtr newnode;
301 newnode = xmlNewDocNode(doc, NULL, BAD_CAST "day", NULL);
302 jam_xmlSetIntProp(newnode, "number", day);
303 return newnode;
306 static xmlNodePtr
307 find_day(xmlDocPtr doc, int day, gboolean create) {
308 xmlNodePtr root, node, newnode;
309 xmlChar *eday;
310 int fday;
312 root = xmlDocGetRootElement(doc);
314 for (node = root->xmlChildrenNode; node; node = node->next) {
315 if ((eday = xmlGetProp(node, BAD_CAST "number")) != NULL) {
316 fday = atoi((char*)eday);
317 xmlFree(eday);
318 if (fday == day) {
319 return node; /* found it. */
320 } else if (fday > day) {
321 /* we didn't find it, but we know where to insert it. */
322 if (create) {
323 newnode = make_day_node(doc, day);
324 xmlAddPrevSibling(node, newnode);
325 return newnode;
327 return NULL;
331 if (create) {
332 /* we're either the first day inserted or last day for the month. */
333 newnode = make_day_node(doc, day);
334 xmlAddChild(root, newnode);
335 return newnode;
337 return NULL;
340 static xmlNodePtr
341 find_entry(xmlNodePtr day, int itemid) {
342 xmlNodePtr node;
343 xmlChar *eitemid;
344 for (node = day->xmlChildrenNode; node; node = node->next) {
345 if ((eitemid = xmlGetProp(node, BAD_CAST "itemid")) != NULL) {
346 if (atoi((char*)eitemid) == itemid) {
347 xmlFree(eitemid);
348 break;
350 xmlFree(eitemid);
353 return node;
356 static gboolean
357 remove_old(JournalStore *js, int itemid, GError **err) {
358 time_t entrytime;
359 int year, mon, day;
360 char *path = NULL;
361 struct stat statbuf;
362 xmlDocPtr doc = NULL;
363 xmlNodePtr nday, node;
365 entrytime = journal_store_lookup_entry_time(js, itemid);
366 if (entrytime == 0)
367 return TRUE; /* this entry isn't in the index. */
369 /* are we already there? */
370 time_to_docidx(&entrytime, &year, &mon, &day);
371 if (year == js->xml_year && mon == js->xml_mon) {
372 /* remove this from the in-memory doc. */
373 doc = js->xml_doc;
374 } else {
375 path = docidx_to_str(js->path, year, mon);
376 if (!stat(path, &statbuf) && errno == ENOENT) {
377 /* no document means there's nothing to delete. */
378 g_free(path);
379 return TRUE;
381 doc = xmlParseFile(path);
384 nday = find_day(doc, day, FALSE);
385 if (nday) {
386 /* find the entry node and remove it. */
387 node = find_entry(nday, itemid);
388 if (node) {
389 xmlUnlinkNode(node);
390 xmlFreeNode(node);
393 /* and delete day if it's empty. */
394 if (nday->xmlChildrenNode == NULL) {
395 xmlUnlinkNode(nday);
396 xmlFreeNode(nday);
400 /* if we deleted from somewhere other than the current file,
401 * we want to save it out immediately. */
402 if (path) {
403 xmlSaveFormatFile(path, doc, TRUE);
404 xmlFreeDoc(doc);
405 g_free(path);
406 } else {
407 js->xml_dirty = TRUE;
410 return TRUE;
413 gboolean
414 journal_store_put(JournalStore *js, LJEntry *entry, GError **err) {
415 time_t entrytime;
416 xmlNodePtr node, newnode;
418 entrytime = lj_timegm(&entry->time);
420 if (!switch_xml_file_from_time(js, &entrytime, err))
421 return FALSE;
423 if (!remove_old(js, entry->itemid, err))
424 return FALSE;
426 /* write main xml. */
427 node = find_day(js->xml_doc, entry->time.tm_mday, TRUE);
428 newnode = lj_entry_to_xml_node(entry, js->xml_doc);
429 /* XXX sort here. */
430 xmlAddChild(node, newnode);
431 js->xml_dirty = TRUE;
433 /* write index. */
434 if (entry->itemid+1 > (int)js->index->len)
435 g_array_set_size(js->index, entry->itemid+1);
436 index_set(js->index, entry->itemid, entrytime);
438 return TRUE;
441 gboolean
442 journal_store_put_group(JournalStore *js, LJEntry **entries, int c, GError **err) {
443 int i;
445 for (i = 0; i < c; i++) {
446 if (!journal_store_put(js, entries[i], err))
447 return FALSE;
450 if (!switch_xml_file_from_time(js, NULL, err))
451 return FALSE;
452 if (!index_save(js, err))
453 return FALSE;
454 return TRUE;
458 void
459 journal_store_free(JournalStore *js) {
460 if (js->index) {
461 index_save(js, NULL);
462 g_array_free(js->index, TRUE);
464 g_free(js->path);
465 g_free(js);
468 guint32
469 journal_store_get_month_entries(JournalStore *js, int year, int mon) {
470 guint32 days;
471 xmlNodePtr nday;
472 int day;
473 GError *err = NULL;
475 days = 0;
476 if (!switch_xml_file(js, year, mon, &err)) {
477 g_warning("journalstore couldn't switch files: %s\n", err->message);
478 g_error_free(err);
479 return 0;
481 nday = xmlDocGetRootElement(js->xml_doc)->xmlChildrenNode;
482 for (; nday; nday = nday->next) {
483 if (jam_xmlGetIntProp(nday, "number", &day))
484 days |= 1 << day;
486 return days;
489 static void
490 call_summarycb(JournalStore *js, xmlNodePtr nentry,
491 JournalStoreSummaryCallback cb_func, gpointer cb_data) {
492 xmlNodePtr nchild;
493 xmlChar *sitemid;
494 xmlChar *event = NULL;
495 xmlChar *subject = NULL;
496 struct tm etm;
497 int itemid = 0;
498 const char *summary;
499 LJSecurity sec = {0};
501 for (nchild = nentry->xmlChildrenNode; nchild; nchild = nchild->next) {
502 if (xmlStrcmp(nchild->name, BAD_CAST "event") == 0) {
503 event = xmlNodeListGetString(js->xml_doc,
504 nchild->xmlChildrenNode, TRUE);
505 sitemid = xmlGetProp(nentry, BAD_CAST "itemid");
506 itemid = 0;
507 if (sitemid) {
508 itemid = atoi((char*)sitemid);
509 xmlFree(sitemid);
511 } else if (xmlStrcmp(nchild->name, BAD_CAST "time") == 0) {
512 lj_ljdate_to_tm((const char*)XML_GET_CONTENT(nchild->xmlChildrenNode), &etm);
513 } else if (xmlStrcmp(nchild->name, BAD_CAST "subject") == 0) {
514 subject = xmlNodeListGetString(js->xml_doc,
515 nchild->xmlChildrenNode, TRUE);
516 } else if (xmlStrcmp(nchild->name, BAD_CAST "security") == 0) {
517 xmlChar *type = NULL, *mask = NULL;
518 type = xmlGetProp(nchild, BAD_CAST "type");
519 mask = xmlGetProp(nchild, BAD_CAST "mask");
520 lj_security_from_strings(&sec, (char*)type, (char*)mask);
521 if (type) xmlFree(type);
522 if (mask) xmlFree(mask);
526 summary = lj_get_summary((char*)subject, (char*)event);
527 cb_func(itemid, lj_timegm(&etm), summary, &sec, cb_data);
528 xmlFree(event);
529 if (subject) {
530 xmlFree(subject);
531 subject = NULL;
535 gboolean
536 journal_store_get_day_entries(JournalStore *js, int year, int mon, int day,
537 JournalStoreSummaryCallback cb_func, gpointer cb_data) {
538 xmlNodePtr nday, nentry;
540 switch_xml_file(js, year, mon, NULL);
541 nday = find_day(js->xml_doc, day, FALSE);
543 if (!nday) /* no entries today. */
544 return TRUE;
546 for (nentry = nday->xmlChildrenNode; nentry; nentry = nentry->next)
547 call_summarycb(js, nentry, cb_func, cb_data);
549 return TRUE;
552 typedef struct {
553 JournalStoreScanCallback scan_cb;
554 gpointer scan_data;
555 JournalStoreSummaryCallback summary_cb;
556 gpointer summary_data;
557 int matchcount;
558 } Scan;
560 static gboolean
561 match_node(JournalStore *js, xmlNodePtr node, const Scan *scan) {
562 xmlChar *content;
563 gboolean found;
565 content = xmlNodeListGetString(js->xml_doc, node->xmlChildrenNode, TRUE);
566 found = scan->scan_cb((const char*)content, scan->scan_data);
567 xmlFree(content);
568 return found;
571 static gboolean
572 match_entry(JournalStore *js, xmlNodePtr nentry, const Scan *scan) {
573 xmlNodePtr nchild;
574 gboolean matched;
576 nchild = nentry->xmlChildrenNode;
577 matched = FALSE;
578 for ( ; nchild; nchild = nchild->next) {
579 if (xmlStrcmp(nchild->name, BAD_CAST "event") == 0)
580 matched = matched || match_node(js, nchild, scan);
581 else if (xmlStrcmp(nchild->name, BAD_CAST "subject") == 0)
582 matched = matched || match_node(js, nchild, scan);
584 return matched;
587 static gboolean
588 scan_month(JournalStore *js, int year, int month, Scan *scan) {
589 xmlNodePtr nday, nentry;
590 if (!switch_xml_file(js, year, month, NULL))
591 return FALSE;
593 nday = xmlDocGetRootElement(js->xml_doc)->xmlChildrenNode;
594 for (; nday; nday = nday->next) {
595 for (nentry = nday->xmlChildrenNode; nentry; nentry = nentry->next) {
596 if (match_entry(js, nentry, scan)) {
597 call_summarycb(js, nentry,
598 scan->summary_cb, scan->summary_data);
599 if (++scan->matchcount == MAX_MATCHES)
600 return FALSE;
604 return TRUE;
607 gboolean
608 journal_store_scan(JournalStore *js,
609 JournalStoreScanCallback scan_cb, gpointer scan_data,
610 JournalStoreSummaryCallback cb_func, gpointer cb_data) {
611 GDir *journaldir, *yeardir;
612 const char *yearname;
613 char *yearpath;
614 int year, month;
615 Scan scan = {
616 scan_cb, scan_data,
617 cb_func, cb_data,
621 journaldir = g_dir_open(js->path, 0, NULL);
622 if (!journaldir)
623 return FALSE;
625 yearname = g_dir_read_name(journaldir);
626 while (yearname && scan.matchcount < MAX_MATCHES) {
627 yearpath = g_build_filename(js->path, yearname, NULL);
628 yeardir = g_dir_open(yearpath, 0, NULL);
629 g_free(yearpath);
631 if (yeardir) {
632 year = atoi(yearname);
633 for (month = 1; month <= 12; month++) {
634 if (!scan_month(js, year, month, &scan))
635 break;
637 g_dir_close(yeardir);
639 yearname = g_dir_read_name(journaldir);
641 g_dir_close(journaldir);
643 return TRUE;
647 gboolean
648 journal_store_find_relative_by_time(JournalStore *js, time_t when,
649 int *ritemid, int dir, GError *err) {
650 time_t candidate;
651 gint i;
653 int fitemid = 0;
654 time_t ftime = 0;
656 /* XXX need to handle items with same date. */
657 if (dir < 0) {
658 for (i = js->index->len-1; i >= 1; i--) {
659 if (index_get(js->index, i) == when)
660 continue; /* skip self */
661 candidate = journal_store_lookup_entry_time(js, i);
662 if (candidate < when && candidate > ftime) {
663 ftime = candidate;
664 fitemid = i;
667 } else if (dir > 0) {
668 for (i = 1; i < (int)js->index->len; i++) {
669 if (index_get(js->index, i) == when)
670 continue; /* skip self */
671 candidate = journal_store_lookup_entry_time(js, i);
672 if (candidate > when) {
673 if (!ftime || (ftime && candidate < ftime)) {
674 ftime = candidate;
675 fitemid = i;
681 if (fitemid) {
682 *ritemid = fitemid;
683 return TRUE;
685 return FALSE;
688 LJEntry *
689 journal_store_get_entry(JournalStore *js, int itemid) {
690 time_t entrytime;
691 xmlNodePtr nday, nentry;
692 struct tm *etm;
693 GError *err = NULL;
695 entrytime = journal_store_lookup_entry_time(js, itemid);
696 if (!switch_xml_file_from_time(js, &entrytime, &err)) {
697 g_warning("journalstore couldn't switch files: %s\n", err->message);
698 g_error_free(err);
699 return NULL;
702 etm = gmtime(&entrytime);
703 nday = find_day(js->xml_doc, etm->tm_mday, FALSE);
704 if (nday) {
705 nentry = find_entry(nday, itemid);
706 if (nentry)
707 return lj_entry_new_from_xml_node(js->xml_doc, nentry);
709 return NULL;
713 journal_store_get_latest_id(JournalStore *js) {
714 int itemid;
715 for (itemid = js->index->len-1; itemid; itemid--)
716 if (journal_store_lookup_entry_time(js, itemid))
717 return itemid;
718 return 0; /* no non-deleted messages in store */
722 journal_store_get_count(JournalStore *js) {
723 int itemid, count = 0;
724 for (itemid = 0; itemid < (int)js->index->len; itemid++)
725 if (index_at(js->index, itemid) != 0)
726 count++;
727 return count;
730 gboolean
731 journal_store_get_invalid(JournalStore *js) {
732 return js->invalid;
735 static char *
736 journal_store_make_path(JamAccount *acc) {
737 return conf_make_account_path(acc, "journal");
740 JournalStore*
741 journal_store_open(JamAccount *acc, gboolean create, GError **err) {
742 JournalStore *js;
743 struct stat statbuf;
745 js = g_new0(JournalStore, 1);
746 js->account = acc;
747 js->path = journal_store_make_path(acc);
748 js->index = g_array_new(FALSE, TRUE, sizeof(time_t));
750 if (!create && !g_file_test(js->path, G_FILE_TEST_EXISTS)) {
751 g_set_error(err, 0, 0, _("No offline copy of this journal."));
752 goto err;
755 /* need at least one slot for the version.
756 * if the on-disk index is an older version,
757 * loading the index will overwrite this version anyway. */
758 g_array_set_size(js->index, 1);
759 index_set(js->index, 0, JOURNAL_STORE_INDEX_VERSION);
761 if (!verify_path(js->path, TRUE, err))
762 goto err;
764 if (!index_load(js, err))
765 goto err;
767 return js;
769 err:
770 journal_store_free(js);
771 return NULL;
774 static void
775 reindex_month(char *storepath, int year, int mon, GArray *idx) {
776 char *xmlpath;
777 xmlDocPtr doc;
778 xmlNodePtr nday, nentry, nchild;
779 struct tm etm;
781 xmlpath = docidx_to_str(storepath, year, mon);
782 doc = xmlParseFile(xmlpath);
783 if (!doc)
784 return;
786 nday = xmlDocGetRootElement(doc)->xmlChildrenNode;
787 for (; nday; nday = nday->next) {
788 for (nentry = nday->xmlChildrenNode; nentry; nentry = nentry->next) {
789 xmlChar *sitemid;
790 int itemid = 0;
792 if ((sitemid = xmlGetProp(nentry, BAD_CAST "itemid")) != NULL) {
793 itemid = atoi((char*)sitemid);
794 xmlFree(sitemid);
796 for (nchild = nentry->xmlChildrenNode; nchild; nchild = nchild->next) {
797 if (xmlStrcmp(nchild->name, BAD_CAST "time") == 0) {
798 lj_ljdate_to_tm((const char*)XML_GET_CONTENT(nchild->xmlChildrenNode),
799 &etm);
800 if (itemid > 0) {
801 if (itemid+1 > (int)idx->len)
802 g_array_set_size(idx, itemid+1);
803 index_set(idx, itemid, lj_timegm(&etm));
805 break;
810 xmlFreeDoc(doc);
813 gboolean
814 journal_store_reindex(JamAccount *acc, GError **err) {
815 char *storepath;
816 GDir *journaldir, *yeardir;
817 const char *yearname, *monthname;
818 char *yearpath;
819 int year, month;
820 GArray *index;
821 gboolean ret;
823 storepath = journal_store_make_path(acc);
825 /* ick, duplication of the store code. */
826 journaldir = g_dir_open(storepath, 0, NULL);
827 if (!journaldir)
828 return FALSE;
830 index = g_array_new(FALSE, TRUE, sizeof(time_t));
831 g_array_set_size(index, 1);
832 index_set(index, 0, JOURNAL_STORE_INDEX_VERSION);
834 yearname = g_dir_read_name(journaldir);
835 while (yearname) {
836 yearpath = g_build_filename(storepath, yearname, NULL);
837 yeardir = g_dir_open(yearpath, 0, NULL);
838 g_free(yearpath);
840 if (yeardir) {
841 year = atoi(yearname);
843 monthname = g_dir_read_name(yeardir);
844 while (monthname) {
845 month = atoi(monthname);
846 reindex_month(storepath, year, month, index);
847 monthname = g_dir_read_name(yeardir);
849 g_dir_close(yeardir);
851 yearname = g_dir_read_name(journaldir);
853 g_dir_close(journaldir);
855 ret = index_write(storepath, index, err);
856 g_free(storepath);
857 g_array_free(index, TRUE);
859 return ret;
862 #endif