| 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