RSS

(root)/calliope : /src/base/entry.c (revision 451)

Line Revision Contents
1 17 /*  Calliope Music Player
2 185  *  Copyright 2005-09 Sam Thursfield <ssssam gmail.com>
3 17  *
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 3 of the License, or
7  *  (at your option) 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 268  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 17  */
17 268
18 67 /* entry: one database (musicsource) entity. */
19
20 17 #include <string.h>
21 #include "main.h"
22 #include "entry.h"
23 #include "musicsource.h"
24
25 451 static void entry_trace (const char *format, ...);
26
27 130 #ifdef ENTRY_TRACK_REFERENCE_OWNERS
28 273 /* If reference tracking is enabled, we also track every entry that's ever allocated, to make sure
29  * they are all properly unreffed when the program closes. It's most useful in unit tests - call
30  * _entry_cleanup after each test, or during, to make sure no references have been leaked.
31  */
32 GHashTable *global_entry_table = NULL;
33
34 void _entry_cleanup() {
35         // Nothing wrong with calling this multiple times, it's only for unit tests really anyway.
36         if (global_entry_table==NULL)
37                 return;
38
39 451         #define DISPLAY_MAX 15
40 273         int count = 0;
41         GHashTableIter iter;
42         g_hash_table_iter_init (&iter, global_entry_table);
43
44         Entry *entry;
45         while (g_hash_table_iter_next(&iter, NULL, (void *)&entry)) {
46                 if (count==0)
47                         printf ("Entries still referenced at exit:\n");
48 277
49                 if ((count++) < DISPLAY_MAX) {
50 451                         //entry_dump (entry);
51                         printf ("\t%s %i\t[%x] - %i refs.\n", ENTRY_PF(entry), entry, entry->ref_count);
52 277                 };
53 273         };
54 277
55 273         if (count > 0) {
56 277                 if (count > DISPLAY_MAX) printf ("... and %i more.", count-DISPLAY_MAX);
57 273                 fflush (stdout);
58                 g_warning ("%i entries still referenced at exit.\n", count);
59         };
60
61         g_hash_table_destroy (global_entry_table);
62         global_entry_table = NULL;
63 };
64 #endif
65
66 268
67 288 static void _free_entry (Entry *entry) {
68         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
69         g_hash_table_remove (global_entry_table, entry);
70         #endif
71
72         for (int i=0; i<entry_n_properties[entry->type]; i++)
73                 #ifdef ENTRY_TRACK_REFERENCE_OWNERS
74                 _free_value (schema[entry->type][i].type, entry->property[i], TRUE, entry->id_string);
75                 #else
76                 _free_value (schema[entry->type][i].type, entry->property[i], TRUE);
77                 #endif
78         g_slice_free1 (sizeof(Entry)+sizeof(void *)*entry_n_properties[entry->type], entry);
79 };
80
81 /****************************
82  * Creation and references  *
83  *                          */
84 52
85 146 Entry *_entry_new (int type, int id, void *source) {
86 288         Entry *self = g_slice_alloc0(sizeof(Entry) + sizeof(void *)*entry_n_properties[type]);
87 275         self->type = type; self->id = 0;
88 146         self->source = source;
89 288         self->internal_ref_count = 0; self->ref_count = 1;
90 273
91         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
92         if (G_UNLIKELY(global_entry_table==NULL)) {
93                 global_entry_table = g_hash_table_new(g_direct_hash, g_direct_equal);
94                 g_atexit (_entry_cleanup);
95         };
96         g_hash_table_insert (global_entry_table, self, self);
97         #endif
98
99 451         entry_trace ("* New entry: type %s. @%x\n", entry_type_name[type], self);
100
101 17         return self;
102 };
103
104 288 void _entry_ref (Entry *self) {
105 413         g_return_if_fail (self != NULL);
106 288         g_atomic_int_inc (&self->ref_count);
107 67 };
108
109 339 gboolean _entry_unref (Entry *self) {
110         if (self==NULL) return FALSE;
111 268
112 339         if (g_atomic_int_dec_and_test(&self->ref_count)) {
113 288                 _free_entry (self);
114 339                 return TRUE;
115         };
116         return FALSE;
117 17 };
118
119 130 #ifdef ENTRY_TRACK_REFERENCE_OWNERS
120 17
121 451 #define MAKE_OWNER(n, f, l)                                         \
122         EntryReferenceOwner *owner = g_slice_new(EntryReferenceOwner);  \
123         g_warn_if_fail (n != NULL);                                     \
124 146         owner->name = n; owner->file = f; owner->line_no = l;
125 17
126 288 Entry *_entry_new_tracking (int type, int id, void *source, const char *owner_name,
127                             const char *file, int line_no) {
128 146         Entry *self = _entry_new(type, id, source); MAKE_OWNER(owner_name, file, line_no);
129         owner->number = 1; self->reference_owner_list = g_list_append(NULL, owner);
130 451         entry_trace ("* entry_new: New %s at %x; created by '%s'.\n", entry_type_name[type],
131                      self, owner_name);
132 275
133         // Generate an ID string
134         _entry_set_id (self, id);
135 17         return self;
136 };
137
138 297 void entry_ref_tracking (Entry *self, const char *owner_name, const char *file, int line_no) {
139 268         _entry_ref (self);
140         MAKE_OWNER (owner_name, file, line_no); owner->number = self->ref_count;
141 451         entry_trace ("* entry_ref: %s %i @%x: reffed by '%s'\n", ENTRY_PF(self), self, owner_name);
142 146         self->reference_owner_list = g_list_append(self->reference_owner_list, owner);
143 17 };
144
145 // Only used at the moment to make sure take_last_ref doesn't take this reference but the one below.
146 297 void entry_ref_invisible_tracking (Entry *self, const char *owner_name, const char *file,
147 288                                    int line_no) {
148 268         _entry_ref (self);
149         MAKE_OWNER (owner_name, file, line_no); owner->number = self->ref_count;
150 304
151 17         // Insert second to last.
152 288         GList *last = g_list_last(self->reference_owner_list);
153 304         self->reference_owner_list = g_list_insert_before(self->reference_owner_list, last, owner);
154 451         entry_trace ("* entry_ref_invisible: %s %i @%x: reffed by '%s'\n", ENTRY_PF(self), self,
155                      owner_name);
156 146 };
157
158
159 static int compare_owner_names( EntryReferenceOwner *a, const char *b) {
160         return strcmp (a->name, b);
161 };
162
163 static int compare_owners (EntryReferenceOwner *a, const char *b) {
164 17         return a->name!=b;
165 };
166
167 299 /* file and line_no are not used to find the reference - only owner_name. */
168 339 gboolean entry_unref_tracking (Entry *self, const char *owner_name, const char *file,  int line_no) {
169 104         if (self==NULL)
170 339                 return FALSE;
171 268
172 275         if (g_list_length(self->reference_owner_list)!=self->ref_count) {
173                 entry_dump (self);
174 268                 g_warning ("entry-unref: %s %i has %i refs but %i owners listed",
175                            entry_type_name[self->type], self->id, self->ref_count,
176 146                            g_list_length(self->reference_owner_list));
177 275         };
178 17
179 146         GList *reference_node = NULL;
180 17         if (owner_name==NULL) {
181 146                 reference_node = g_list_last(self->reference_owner_list);
182 451                 g_warning ("entry-unref: %s %i: anonymous unref (%s %i), losing %s\n", ENTRY_PF(self),
183                            file, line_no, ((EntryReferenceOwner *)reference_node->data)->name);
184 17         } else {
185 297                 // Need to be exact here, or two entries of the same type might get their references
186                 // confused. Since they own the string name it's important to not get them confused or we
187 288                 // will have a long list of invalid pointers as references.
188 268                 reference_node = g_list_find_custom(self->reference_owner_list, owner_name,
189 146                                                     (GCompareFunc)compare_owners);
190 17                 if (reference_node==NULL)
191 268                         reference_node = g_list_find_custom(self->reference_owner_list, owner_name,
192 146                                                             (GCompareFunc)compare_owner_names);
193 17         };
194 268
195 67         if (reference_node==NULL)
196 146                 g_warning ("entry-unref: '%s' [%s:%i] tried to unref %s %i but doesn't own any "
197 299                            "references\n", owner_name, file==NULL?"<unknown file>":file, line_no,
198                            ENTRY_PF(self));
199 67         else {
200 146                 g_slice_free (EntryReferenceOwner, reference_node->data);
201 268                 self->reference_owner_list = g_list_delete_link(self->reference_owner_list,
202 146                                                                 reference_node);
203 17         };
204 268
205 451         if (self->ref_count == 2)
206                 entry_trace ("* Entry unref: %s %i [%x], by %s. 1 ref left: %s.\n", ENTRY_PF(self), self,
207                              owner_name, ((EntryReferenceOwner *)self->reference_owner_list->data)->name);
208         else
209                 entry_trace ("* Entry unref: %s %i [%x], by %s. %i refs left.\n", ENTRY_PF(self), self,
210                                          owner_name, self->ref_count - 1);
211
212 341         char *id_string = self->id_string;
213 339         const gboolean destroyed = _entry_unref (self);
214 341         if (destroyed)
215 339                 g_free (id_string);
216         return destroyed;
217 17 };
218
219 288 void entry_take_last_ref_tracking (Entry *self, const char *owner_name, const char *file,
220 146                                    int line_no) {
221         GList *node = g_list_last(self->reference_owner_list);
222 268         EntryReferenceOwner *current_owner = node->data;
223
224 451         entry_trace ("* entry_take_last_ref: %s %i @%x: giving '%s' to '%s'\n", ENTRY_PF(self), self,
225                      current_owner->name, owner_name);
226 24         //printf("%s %i: Giving '%s' ref to '%s'\n", entry_type_name[self->type], self->id, current_owner->name, owner_name);
227 268         current_owner->name = owner_name; current_owner->file = file;
228 146         current_owner->line_no = line_no;
229 17 };
230
231 275
232 /* _entry_set_id: Only used when ref tracking is on, so that the entry can change its owner id to
233  *                something more useful than "A file entry" when it is assigned an ID. */
234 void _entry_set_id (Entry *self, int id) {
235         self->id = id;
236
237         char *old_id_string = self->id_string;
238
239         self->id_string = id > 0? g_strdup_printf("%s %i", ENTRY_PF(self))
240                                 : g_strdup_printf("A %s entry", entry_type_name[self->type]);
241
242         if (old_id_string!=NULL) {
243                 // Update everything that we hold a reference on to the new ID string.
244                 //
245                 for (int p=0; p<entry_n_properties[self->type]; p++) {
246                         if (IS_FOREIGN_KEY(schema[self->type][p].type)) {
247                                 Entry *foreign = entry_get_property (self, p);
248                                 if (foreign==NULL) continue;
249
250                                 GList *node;
251                                 for (node=foreign->reference_owner_list; node; node=node->next) {
252                                         EntryReferenceOwner *ref = node->data;
253                                         if (ref->name == old_id_string) {
254                                                 ref->name = self->id_string;
255                                                 break;
256                                         };
257                                 };
258
259                                 if (node==NULL)
260                                         g_warning ("%s %i links to %s %i, but doesn't seem to be in owner list :(",
261                                                    ENTRY_PF(self), ENTRY_PF(foreign));
262                         };
263                 };
264
265                 g_free (old_id_string);
266         };
267 451         
268         entry_trace ("* Set entry id: %s at @%x -> %i. id %s\n", entry_type_name[self->type], self, id,
269                      self->id_string);
270 275 };
271
272
273 17
274 297 Entry *entry_duplicate_tracking (Entry *original, const char *owner_name, const char *file,
275 288                                  int line_no) {
276         Entry *copy = _entry_new_tracking(original->type, 0, NULL, owner_name, file, line_no);
277 17         for (int i=0;i<entry_n_properties[original->type];i++)
278                 entry_set_property(copy, i, entry_get_property(original, i));
279         return copy;
280 };
281
282 297 static Entry *duplicate_tree_recursive (Entry *original, Entry *caller, Entry *caller_copy,
283 288                                         const char *owner_name, const char *file, int line_no) {
284         Entry *copy = _entry_new_tracking(original->type, 0, NULL, owner_name, file, line_no);
285 297
286 288         for (int i=0; i<entry_n_properties[original->type]; i++) {
287 92                 if (IS_FOREIGN_KEY(schema[original->type][i].type)) {
288 268                         if (entry_get_property(original, i)==caller)
289 288                                 entry_set_property (copy, i, caller_copy);
290 297                         else {
291 288                                 Entry *child = entry_get_property(original, i);
292                                 if (child!=NULL) {
293 297                                         Entry *child_copy = duplicate_tree_recursive(child, original, copy,
294 288                                                                                      owner_name, file, line_no);
295 296                                         // child_copy will be given 'owner_name' reference - here we swallow it, because
296                                         // only the root of the tree that we are duplicating should have that ref.
297 297                                         entry_take_property(copy, i, child_copy);
298 288                                 };
299                         }
300 297                 } else
301 288                         entry_set_property(copy, i, entry_get_property(original, i));
302 17         };
303 273
304 17         return copy;
305 };
306
307 288 Entry *entry_duplicate_tree_tracking(Entry *original, const char *owner_name, const char *file, int line_no) {
308         return duplicate_tree_recursive (original, NULL, NULL, owner_name, file, line_no);
309 17 };
310
311 #else
312
313 288 Entry *_entry_duplicate(Entry *original) {
314 17         Entry *copy=entry_new(original->type, 0, NULL, NULL);
315         for (int i=0;i<entry_n_properties[original->type];i++)
316                 entry_set_property(copy, i, entry_get_property(original, i));
317         return copy;
318 };
319
320 static Entry *duplicate_recursive(Entry *original, Entry *caller, Entry *caller_copy) {
321         //printf("duplicate-recursive: %s %i [caller %s %i]\n", entry_type_name[original->type], original->id, entry_type_name[caller->type], caller->id);
322 273         Entry *copy = entry_new(original->type, 0, NULL, NULL);
323 268         for (int i=0;i<entry_n_properties[original->type];i++) {
324 67                 if (IS_FOREIGN_KEY(schema[original->type][i].type)) {
325 268                         if (entry_get_property(original, i)==caller)
326 17                                 entry_set_property(copy, i, caller_copy);
327                         else {
328                                 Entry *child=entry_get_property(original, i);
329                                 if (child!=NULL) entry_set_property(copy, i, duplicate_recursive(child, original, copy));
330                         };
331                 } else entry_set_property(copy, i, entry_get_property(original, i));
332         };
333 273         if (caller!=NULL) _entry_unref(copy);
334 17         return copy;
335 };
336
337 288 Entry *_entry_duplicate_tree (Entry *original) {
338 17         return duplicate_recursive(original, NULL, NULL);
339 };
340
341 #endif
342
343 92 void entry_query_list_free(GSList *list, const char *owner_id) {
344 130         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
345 299         void unref (Entry *entry, const char *owner_id) {
346                 // NULL file is allowed, for unrefs only.
347 304                 entry_unref_tracking (entry, owner_id, NULL, 0);
348 299         };
349 17
350 304         g_slist_foreach (list, (GFunc)unref, (char *)owner_id);
351 299         #else
352         g_slist_foreach (list, (GFunc)_entry_unref, NULL);
353 17         #endif
354 299         g_slist_free (list);
355 17 };
356
357 339 Entry *entry_get_entry_for_distant_property (const Entry *self, const int apid) {
358 95         if (APID_GET_TYPE(apid)==self->type)
359 339                 return (Entry *)self;
360 95
361 339         Entry *entry = (Entry *)self;
362 95         while (entry->type!=APID_GET_TYPE(apid)) {
363 304                 int rel = entry_type_info[APID_GET_TYPE(apid)].map[entry->type];
364 95
365                 if (rel==-1)
366                         break;
367 268
368 95                 g_return_val_if_fail (IS_FOREIGN_KEY(schema[entry->type][rel].type), NULL);
369 268
370 98                 Entry *new_entry = entry_get_property(entry, rel);
371                 if (new_entry==NULL)
372 341                         g_warning("entry_get_distant_property: %s.%s [%i] is null\n",
373 339                                   PROPERTY_NAME(entry->type, rel), entry->id);
374 98                 entry = new_entry;
375 95         }
376 268
377 95         if (entry->type==APID_GET_TYPE(apid))
378 339                 return entry;
379 95         else {
380 268                 g_warning ("Unable to find %s.%s for %s %i :(", entry_type_name[APID_GET_TYPE(apid)],
381 95                            APID_GET_PROPERTY(apid).name, entry_type_name[self->type], self->id);
382                 return NULL;
383         };
384 };
385
386 339 void *entry_get_distant_property (const Entry *self, const int apid) {
387 342         //printf ("entry_get_distant_property: type %s, apid %s.%s\n", entry_type_name[self->type],
388         //        APID_NAME(apid)); fflush (stdout);
389 339         Entry *distant_entry = entry_get_entry_for_distant_property(self, apid);
390 341
391 339         g_return_val_if_fail (distant_entry!=NULL, NULL);
392         g_return_val_if_fail (distant_entry->type==APID_GET_TYPE(apid), NULL);
393 341
394 339         return entry_get_property(distant_entry, APID_GET_PROPERTY_ID(apid));
395 };
396
397 273
398 334 #ifdef ENTRY_TRACK_REFERENCE_OWNERS
399 341 void entry_set_property_tracking (Entry *self, int id, const void *value, const char *file,
400                                   int line_no) {
401 334 #else
402 341 void entry_set_property (Entry *self, int id, const void *value) {
403 334 #endif
404 48         //if (schema[self->type][id].type==PROPERTY_TYPE_STRING || schema[self->type][id].type==PROPERTY_TYPE_NAME)
405         //      printf("Setting %X %i to %s\n", self, id, value);fflush(stdout);
406
407 17         if (self->property[id]==value) return;
408 52
409         // If value needs no allocation, this is simple.
410 17         switch(schema[self->type][id].type) {
411 52                 case PROPERTY_TYPE_INT:
412 268                         self->property[id] = (void *)value;
413 52                         return;
414 130                 default: break;
415 52         };
416
417         // Otherwise, free the old value:
418 130         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
419 288         _free_value (schema[self->type][id].type, self->property[id], TRUE, self->id_string);
420 95         #else
421 288         _free_value (schema[self->type][id].type, self->property[id], TRUE);
422 95         #endif
423 268
424 52         // Check if new value is NULL, if this is invalid
425         switch(schema[self->type][id].type) {
426                 case PROPERTY_TYPE_FLOAT:
427                 case PROPERTY_TYPE_DATE:
428                 case PROPERTY_TYPE_DATE_TIME:
429 146                         g_return_if_fail (value!=NULL);
430 268                 default:;
431 52         };
432
433 67         // Now set the new value
434 52         switch(schema[self->type][id].type) {
435                 case PROPERTY_TYPE_FLOAT: {
436 146                         float *fptr = g_slice_new0(float);
437                         *fptr = *(const float *)value;
438                         self->property[id] = (void *)fptr;
439 52                         break;
440                 };
441 268
442 52                 case PROPERTY_TYPE_STRING: case PROPERTY_TYPE_NAME:
443 146                         self->property[id] = g_strdup(value);
444 52                         break;
445 268
446 52                 case PROPERTY_TYPE_DATE:
447 146                         self->property[id] = g_date_new();
448 268                         g_date_set_julian ((GDate *)self->property[id], g_date_get_julian((GDate *)value));
449 52                         break;
450 268
451 52                 case PROPERTY_TYPE_DATE_TIME:
452 146                         self->property[id] = g_slice_new(time_t);
453                         *(time_t *)self->property[id] = *(time_t *)value;
454 52                         break;
455 268
456 52                 default: {
457 146                         self->property[id] = (void *)(Entry *)value;
458 334
459 278                         // Invisible is used in entry trees, caches, db's etc: basically so take_last_ref will
460                         // always do what's expected (give away the right reference) and not give away the
461                         // cache's or the parent's reference.
462 104                         if (value!=NULL)
463 334                                 #ifdef ENTRY_TRACK_REFERENCE_OWNERS
464                                 entry_ref_invisible_tracking ((Entry *)value, self->id_string, file, line_no);
465                                 #else
466                                 entry_ref_invisible ((Entry *)value, NULL);
467                                 #endif
468 341
469 17                         break;
470                 };
471 268         }
472 17 };
473
474 288 void entry_take_property (Entry *self, int id, void *value) {
475 451         entry_trace ("* entry_take_property: [%s %i].%s <= %x (currently %x.)\n", ENTRY_PF(self),
476                      schema[self->type][id].name, value, self->property[id]);
477 17         if (self->property[id]==value) return;
478 52
479 130         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
480 288         _free_value (schema[self->type][id].type, self->property[id], TRUE, self->id_string);
481 95         #else
482 288         _free_value (schema[self->type][id].type, self->property[id], TRUE);
483 95         #endif
484 52
485 17         switch(schema[self->type][id].type) {
486 268                 case PROPERTY_TYPE_INT:         case PROPERTY_TYPE_FLOAT:
487 52                 case PROPERTY_TYPE_DATE:        case PROPERTY_TYPE_DATE_TIME:
488 268                 case PROPERTY_TYPE_STRING:      case PROPERTY_TYPE_NAME:
489 146                         self->property[id] = value; break;
490 17                 default: {
491 146                         self->property[id] = (void *)(Entry *)value;
492 130                         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
493 114                         if (value!=NULL) {
494 146                                 char *owner_id = self->id_string;
495 114                                 entry_take_last_ref ((Entry *)value, owner_id);
496                         };
497                         #endif
498 17                         break;
499                 };
500 268         }
501 67 };
502 24
503 288
504 306 char *entry_property_to_string (Entry *self, int id) {
505         return entry_value_to_string(schema[self->type][id].type, entry_get_property(self, id));
506 24 };
507
508 451 void entry_destroy (Entry *entry) {
509         for (int p=0; p<entry_n_properties[entry->type]; p++)
510                 if (IS_FOREIGN_KEY(schema[entry->type][p].type))
511                         entry_set_property (entry, p, NULL);
512 };
513
514 24
515 285 void entry_check (Entry *entry) {
516 299         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
517                 if (g_list_length(entry->reference_owner_list)!=entry->ref_count) {
518                         entry_dump (entry);
519                         g_warning ("entry_check: %s %i has %i refs but %i owners listed",
520                                            entry_type_name[entry->type], entry->id, entry->ref_count,
521                                            g_list_length(entry->reference_owner_list));
522                 };
523         #endif
524
525 285         for (int p=0; p<entry_n_properties[entry->type]; p++) {
526 288                 const EntryProperty *property = &schema[entry->type][p];
527 297
528 285                 if (property->flags & PROPERTY_NOT_NULL)
529                         if (entry_get_property(entry, p)==NULL)
530                                 g_warning ("%s %i: property %s is not supposed to be NULL.", ENTRY_PF(entry),
531                                            property->name);
532         };
533 };
534 95
535 288
536 /********************************
537  * Comparison                   *
538  *                              */
539
540 17
541 95 // We track caller so we cannot get trapped by two entry types that point
542 // to each other.
543 //
544 269 gboolean _entry_match_recursive (const Entry *self, const Entry *other, const Entry *caller) {
545 95         g_return_val_if_fail (self->type==other->type, FALSE);
546 268
547 95         for (int i=0;i<entry_n_properties[self->type];i++) {
548                 if (schema[self->type][i].flags & PROPERTY_TRIVIAL ||
549                         schema[self->type][i].flags & PROPERTY_MERGEABLE)
550                         continue;
551 268                 if (!_entry_value_match_internal(schema[self->type][i].type, self->property[i],
552                                                                          other->property[i], _entry_match_recursive, self,
553 95                                                                                  caller))
554 268                         return FALSE;
555 95         };
556         return TRUE;
557 };
558
559 17 gboolean entry_match(Entry *self, Entry *other) {
560 268         if (self==other)        return TRUE;
561 52         if (self==NULL || other==NULL) return FALSE;
562 17         if (self->source==other->source && self->id==other->id && self->id!=0) return TRUE;
563 268
564 95         return _entry_match_recursive(self, other, self);
565 17 };
566
567 int entry_match_except(Entry *self, Entry *other, int absolute_property_id) {
568         const gboolean narrate=FALSE;
569
570         if (self->source==other->source && self->id==other->id && self->id!=0) return TRUE;
571 268
572 17         // We track caller so we cannot get trapped by two entry types that point
573         // to each other.
574         //
575 269         gboolean _entry_match_except_recursive(const Entry *self, const Entry *other,
576                                                const Entry *caller) {
577 146                 g_return_val_if_fail (self->type==other->type, FALSE);
578 268
579 17                 for (int i=0;i<entry_n_properties[self->type];i++) {
580 268                         if (ABSOLUTE_PROPERTY_ID_GET_TYPE(absolute_property_id)==self->type && ABSOLUTE_PROPERTY_ID_GET_PROPERTY_ID(absolute_property_id)==i)
581 17                                 continue;
582                         if (schema[self->type][i].flags & PROPERTY_TRIVIAL ||
583                                 schema[self->type][i].flags & PROPERTY_MERGEABLE)
584                                 continue;
585 268                         if (!_entry_value_match_internal(schema[self->type][i].type, self->property[i],
586                                                          other->property[i], _entry_match_except_recursive,
587 95                                                                                          self, caller))
588 268                                 return FALSE;
589 17                 };
590                 if (narrate)
591                         printf("Matched\n");
592                 return TRUE;
593         };
594 268
595 95         return _entry_match_except_recursive(self, other, self);
596 17 };
597
598 24
599 306 int entry_compare_property (Entry *a, Entry *b, int property) {
600 96         g_return_val_if_fail (a->type==b->type, 0);
601         g_return_val_if_fail (property < entry_n_properties[a->type], 0);
602 268
603 306         return entry_value_compare(schema[a->type][property].type, entry_get_property(a, property),
604                                    entry_get_property(b, property));
605 24 };
606
607 268 /* Fills connection[E] with the property of E that you should follow to work towards an entry of
608  * 'type'. connection[E] is -1 if it's not possible to reach 'type' from E. connection[type]==-2.
609  *
610  */
611 void entry_property_map(int type, int route_apid, int *connection) {
612 24         // Strategy: try to connect any entry to an existing connection. Stop when we don't.
613         // If a route entry is specified, connect to that rather than the column target. This is
614         // because if you pass eg. artist, album, recording and composition get connected which
615 268         // is probably wrong. Instead you give one of those entries to route to, and only
616         // entries which can be linked to say recording will be connected.
617         memset (connection, -1, ENTRY_TYPE_COUNT*sizeof(int));
618
619         if (route_apid < 0)
620                 connection[type] = -2;
621         else connection[APID_GET_TYPE(route_apid)] = APID_GET_PROPERTY_ID(route_apid);
622
623         int new_connections;
624 24         do {
625 268                 new_connections = 0;
626                 for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
627                         if (connection[i]!=-1)
628 24                                 continue;
629 268
630                         for (int p=0; p<entry_n_properties[i]; p++) {
631 24                                 // Check if this links to an existing connection
632 268                                 if (schema[i][p].type < ENTRY_TYPE_COUNT) {
633 24                                         if (connection[schema[i][p].type]!=-1) {
634                                                 new_connections++;
635 268                                                 connection[i] = p;
636 24                                         };
637                                 };
638                         };
639                 };
640 268         } while (new_connections > 0);
641
642         // FIXME: no need ..
643         if (route_apid >= 0)
644                 connection[type] = -2;
645 24 };
646
647 304 /***************************************************************************************************
648  * Schema
649  */
650
651 EntryTypeInfo entry_type_info[ENTRY_TYPE_COUNT];
652
653 void schema_init() {
654 370         for (int i=0; i<ENTRY_TYPE_COUNT; i++)
655                 entry_type_info[i].shadowees = NULL;
656
657 304         for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
658                 entry_type_info[i].quark = g_quark_from_static_string(entry_type_name[i]);
659
660                 GSList **key_owners = &entry_type_info[i].foreign_key_owners;
661                 *key_owners = NULL;
662                 for (int j=0; j<ENTRY_TYPE_COUNT; j++) {
663                         for (int p=0; p<entry_n_properties[j]; p++) {
664                                 if (schema[j][p].type==i) {
665                                         *key_owners = g_slist_prepend(*key_owners, GINT_TO_POINTER(MAKE_APID(j, p)));
666                                         break;
667                                 };
668                         };
669                 };
670
671                 entry_property_map (i, -1, entry_type_info[i].map);
672
673 370                 entry_type_info[i].has_shadowing_properties = FALSE;
674 304                 for (int p=0; p<entry_n_properties[i]; p++) {
675                         if (schema[i][p].flags & PROPERTY_SHADOW) {
676                                 if (schema[i][p].type!=APID_GET_PROPERTY(schema[i][p].shadow_property_apid).type)
677                                         g_warning ("Property %s.%s shadows a property of a different type!",
678                                                    entry_type_name[i], schema[i][p].name);
679
680                                 // Because ints are stored directly in the pointer, there's no NULL value
681                                 // which could be used to mark shadowing properties. I don't think this will
682                                 // cause too much trouble however.
683                                 if (schema[i][p].type == PROPERTY_TYPE_INT)
684                                         g_warning ("%s.%s: integer properties cannot shadow I'm afraid.",
685                                                    entry_type_name[i], schema[i][p].name);
686 370
687                                 entry_type_info[i].has_shadowing_properties = TRUE;
688
689                                 const EntryType shadowee_type = APID_GET_TYPE(schema[i][p].shadow_property_apid);
690                                 GSList **list = &entry_type_info[shadowee_type].shadowees;
691 386                                 //printf ("Add %s.%s.\n", APID_NAME(MAKE_APID(i, p))); fflush (stdout);
692 370                                 (*list) = g_slist_prepend(*list, GUINT_TO_POINTER(MAKE_APID(i, p)));
693 304                         }
694                 };
695
696                 /*printf("Entry map for %s: ", entry_type_name[i]);
697                 for (int j=0; j<ENTRY_TYPE_COUNT; j++) {
698                         int p = entry_type_info[i].map[j];
699                         if (p!=-1 && j!=i)
700                                 printf ("%s.%s ", entry_type_name[j], schema[j][p].name);
701                 }
702                 printf("\n"); fflush(stdout);*/
703         };
704 };
705
706
707
708 /***************************************************************************************************
709 288  * Debugging
710  */
711
712 void entry_dump (Entry *self) {
713 17         void dump(Entry *self, Entry *caller, int offset) {
714                 if (self==NULL) return;
715                 #define PAD(off)        for (int ii=0;ii<off;ii++) putchar('\t');
716 268
717 335                 PAD (offset); printf ("<%s id=%i addr=%x ref_count=%i internal_ref_count=%i source=%X>\n",
718                                       ENTRY_PF(self), self, self->ref_count, self->internal_ref_count,
719 350                                       self->source);
720 130                 #ifdef ENTRY_TRACK_REFERENCE_OWNERS
721 277                         GList *node = self->reference_owner_list; int i = 0;
722 451                         PAD (offset+2); printf ("our id: %s.\n", self->id_string);
723 281                         while (node!=NULL && (i++) < 8) {
724 277                                 EntryReferenceOwner *owner = node->data;
725                                 PAD (offset+2); printf("Ref %i: %s [%s:%i]\n", owner->number, owner->name,
726                                                        owner->file, owner->line_no);
727                                 node = node->next;
728                         };
729                         if (node) {
730                                 PAD (offset+2); printf ("... %i more references.\n", g_list_length(node));
731 17                         };
732                 #endif
733                 for (int i=0;i<entry_n_properties[self->type];i++) {
734                         switch(schema[self->type][i].type) {
735 24                                 // FIXME: duplicated in entry_property_to_string ..
736 17                                 case PROPERTY_TYPE_INT:
737                                         PAD(offset+1); printf("<%s>%i</%s>\n", schema[self->type][i].name,
738                                           (int)self->property[i], schema[self->type][i].name);
739                                         break;
740                                 case PROPERTY_TYPE_FLOAT:
741 52                                         if (self->property[i]!=NULL) {
742                                                 PAD(offset+1); printf("<%s>%g</%s>\n", schema[self->type][i].name,
743                                                   *(float *)self->property[i], schema[self->type][i].name);
744                                         };
745                                         break;
746 17                                 case PROPERTY_TYPE_STRING: case PROPERTY_TYPE_NAME:
747                                         PAD(offset+1); printf("<%s>%s</%s>\n", schema[self->type][i].name,
748                                           (const char *)self->property[i], schema[self->type][i].name);
749                                         break;
750                                 case PROPERTY_TYPE_DATE: {
751                                         if (self->property[i]!=NULL) {
752                                                 char ohnoabuffer[256];
753                                                 g_date_strftime(ohnoabuffer, 255, "%Y-%m-%d", (GDate *)self->property[i]);
754                                                 PAD(offset+1); printf("<%s>%s</%s>\n", schema[self->type][i].name, ohnoabuffer, schema[self->type][i].name);
755                                         };
756                                         break;
757                                 };
758                                 case PROPERTY_TYPE_DATE_TIME: {
759                                         if (self->property[i]!=NULL) {
760                                                 char ohnoabuffer[256];
761                                                 struct tm brokentime; localtime_r(self->property[i], &brokentime);
762                                                 strftime(ohnoabuffer, 255, "%Y-%m-%d %H-%M-%S", &brokentime);
763                                                 PAD(offset+1); printf("<%s>%s</%s>\n", schema[self->type][i].name, ohnoabuffer, schema[self->type][i].name);
764                                         };
765                                         break;                          }
766                                 default: {
767                                         Entry *child=(Entry *)self->property[i];
768                                         if (child==caller) {
769                                                 PAD(offset+1); printf("<%s>[back-pointer]</%s>\n", schema[self->type][i].name, schema[self->type][i].name);
770                                         } else if (child!=NULL)
771                                                 dump(child, self, offset+1);
772 283                                         else
773 423                                                 if (!(schema[self->type][i].flags & PROPERTY_TRIVIAL)) {
774 283                                                         PAD (offset+1); printf ("[%s is NULL, but not flagged trivial]",
775                                                                                 schema[self->type][i].name);
776                                                 };
777 17                                 };
778                         };
779                 };
780                 PAD(offset); printf("</%s>\n", entry_type_name[self->type]);
781         };
782         dump(self, self, 0);
783 };
784 451
785 static void entry_trace (const char *format, ...) {
786         #ifdef ENTRY_TRACE
787         va_list va; va_start (va, format);
788         vprintf (format, va);
789         fflush (stdout);
790         va_end (va);
791         #endif
792 };

Loggerhead is a web-based interface for Bazaar branches