RSS

(root)/calliope : /src/base/musicsourceimporting.c (revision 452)

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 264  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 17  */
17 264
18 // This is an abstract class which contains code for an in-memory database.
19 17 // It is the basis for filesource and cdsource.
20 //
21
22 #include <string.h>
23 #include <stdlib.h>
24 #include <glib.h>
25 #include "main.h"
26 #include "musicsourceimporting.h"
27 451 #include "musicsource-private.h"
28 24 #include "musicsourceview.h"
29 17
30 445 typedef struct {
31         Entry    *entry;
32
33         /* When in a notify function, removing entries fully can mess up the entry duplicates (because
34          * it involves zeroing their foreign keys). In this case we remove the entries from the source
35          * as soon as we can (to avoid them coming back from the dead in queries) but don't unref them
36          * until we aren't notifying any more. */
37         gboolean  removed_from_source;
38 } RemovalQueueEntry;
39
40 17 struct MusicSourceImportingPrivate {
41 445         /* Removing entries gets very messy; the global removal queue keeps things in order by ensuring
42          * we do things in the right order and don't confuse ourself with things like a removal notify
43          * that causes removal of the entry it triggered on. */
44         GSList *removal_queue;
45 17 };
46
47 264 G_DEFINE_TYPE(MusicSourceImporting, music_source_importing, MUSIC_SOURCE_TYPE);
48 17
49 static void finalize(GObject *self);
50
51 static void init_db(MusicSourceImporting *self);
52 static void free_db(MusicSourceImporting *self);
53
54 451 static int find_entry (MusicSource *music_source, Entry *entry, Entry *caller);
55
56 17 static int is_empty(MusicSource *musicsource);
57 static int get_n_entries(MusicSource *musicsource, EntryType type, int *highest_id, int *n_dead);
58 static gboolean is_valid_id(MusicSource *musicsource, EntryType type, int id);
59 static Entry *query_entry(MusicSource *musicsource, EntryType type, int id);
60 102 static int query_n_relations(MusicSource *music_source, int local_id, EntryType foreign_type, int foreign_property_id, int limit);
61 17 static GSList *query_entry_children(MusicSource *musicsource, int parent_id, EntryType child_entry_type, int child_property);
62 static GSList *query_entry_children_ids(MusicSource *musicsource, int parent_id, EntryType child_entry_type, int child_property);
63 92 static GSList *query_relations(MusicSource *musicsource, EntryType local_type, int local_id, int relation_apid);
64 17 static GSList *query_ids(MusicSource *musicsource, EntryType entry_type);
65 static GSList *query_matching_except(MusicSource *musicsource, Entry *entry, int ignored_property_absolute_id);
66 24
67 215 static MusicSourceView *create_view(MusicSource *source, ViewConfig *config);
68 24
69 451 static void add_entry_internal    (MusicSource *music_source, Entry *entry);
70 static void remove_entry_internal (MusicSource *music_source, EntryType type, int id);
71 static void update_foreign_keys   (MusicSource *music_source, Entry *old_entry, Entry *new_entry);
72
73 17 static int add_entry(MusicSource *musicsource, Entry *entry);
74 static void remove_entry(MusicSource *musicsource, EntryType type, int id);
75 static Entry *checkout_entry(MusicSource *self, EntryType type, int id);
76 static void checkin_entry(MusicSource *self, Entry *entry);
77 95 static void flush(MusicSource *musicsource);
78 17 static void wipe(MusicSource *musicsource);
79 206 static void dump (MusicSource *musicsource, gboolean detailed);
80 17
81 269 static void editing_trace (const char *format, ...);
82 17
83 445 static gint removal_queue_compare_func (gconstpointer value, gconstpointer data) {
84         const RemovalQueueEntry *removal = value;
85         if (removal->entry == data)
86                 return 0;
87         return 1;
88 }
89
90 17 static void music_source_importing_class_init(MusicSourceImportingClass *cl) {
91         music_source_importing_parent_class=g_type_class_peek_parent(G_OBJECT_CLASS(cl));
92         G_OBJECT_CLASS(cl)->finalize=finalize;
93 451
94         MusicSourceClass *music_source_class = MUSIC_SOURCE_CLASS(cl);
95         music_source_class->find_entry            = find_entry;
96 17         MUSIC_SOURCE_CLASS(cl)->is_empty=is_empty;
97         MUSIC_SOURCE_CLASS(cl)->get_n_entries=get_n_entries;
98         MUSIC_SOURCE_CLASS(cl)->is_valid_id=is_valid_id;
99         MUSIC_SOURCE_CLASS(cl)->query_entry=query_entry;
100 102         MUSIC_SOURCE_CLASS(cl)->query_n_relations = query_n_relations;
101 17         MUSIC_SOURCE_CLASS(cl)->query_entry_children=query_entry_children;
102         MUSIC_SOURCE_CLASS(cl)->query_entry_children_ids=query_entry_children_ids;
103 92         MUSIC_SOURCE_CLASS(cl)->query_relations = query_relations;
104 17         MUSIC_SOURCE_CLASS(cl)->query_ids=query_ids;
105         MUSIC_SOURCE_CLASS(cl)->query_matching_except=query_matching_except;
106 24         MUSIC_SOURCE_CLASS(cl)->create_view=create_view;
107 451         music_source_class->add_entry_internal    = add_entry_internal;
108         music_source_class->update_entry_internal = NULL;
109         music_source_class->remove_entry_internal = remove_entry_internal;
110         music_source_class->update_foreign_keys   = update_foreign_keys;
111         music_source_class->add_entry             = add_entry;
112         music_source_class->remove_entry          = remove_entry;
113 17         MUSIC_SOURCE_CLASS(cl)->checkout_entry=checkout_entry;
114         MUSIC_SOURCE_CLASS(cl)->checkin_entry=checkin_entry;
115 95         MUSIC_SOURCE_CLASS(cl)->flush = flush;
116 17         MUSIC_SOURCE_CLASS(cl)->wipe=wipe;
117 206         MUSIC_SOURCE_CLASS(cl)->dump = dump;
118         g_type_class_add_private (cl, sizeof(MusicSourceImportingPrivate));
119 17 }
120
121 static void music_source_importing_init(MusicSourceImporting *self) {
122         SP=G_TYPE_INSTANCE_GET_PRIVATE(self, MUSIC_SOURCE_IMPORTING_TYPE, MusicSourceImportingPrivate);
123
124         init_db(self);
125 445
126         SP->removal_queue = NULL;
127 17 }
128
129 static void finalize(GObject *object) {
130 264         MusicSourceImporting *self=MUSIC_SOURCE_IMPORTING(object);
131 17
132         free_db(self);
133
134 445         if (SP->removal_queue != NULL)
135                 g_warning ("genericsource: removal queue not empty at finalize: %i entries.\n",
136                            g_slist_length (SP->removal_queue));
137
138 17         G_OBJECT_CLASS(music_source_importing_parent_class)->finalize(object);
139 };
140
141 static void init_db(MusicSourceImporting *self) {
142 271         for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
143 17                 self->data[i]=g_ptr_array_new();
144                 self->total[i]=0; self->dead[i]=0;
145         };
146 264
147 17         // FIXME: !!
148         // FIXME: how can we make these consistently sort last ? What
149         // if there is a band called like ZZZZ Top ??
150         self->unknown_artist_entry=entry_new(ENTRY_TYPE_ARTIST, ARTIST_ID_UNKNOWN, self, "musicsourceimporting database");
151         entry_set_property(self->unknown_artist_entry, ARTIST_NAME, "Unknown Artist");
152         entry_set_property(self->unknown_artist_entry, ARTIST_SORTNAME, "ZZZZZ");
153
154         self->various_artists_entry=entry_new(ENTRY_TYPE_ARTIST, ARTIST_ID_VARIOUS, self, "musicsourceimporting database");
155         entry_set_property(self->various_artists_entry, ARTIST_NAME, "Various Artists");
156         entry_set_property(self->various_artists_entry, ARTIST_SORTNAME, "ZZZZY");
157
158         self->total[ENTRY_TYPE_ARTIST]=2;
159 264         g_ptr_array_add(self->data[ENTRY_TYPE_ARTIST], self->unknown_artist_entry);
160 17         g_ptr_array_add(self->data[ENTRY_TYPE_ARTIST], self->various_artists_entry);
161 };
162
163 static void free_db(MusicSourceImporting *self) {
164 273         // First we NULL all of the foreign keys in the database, because otherwise the entries are all
165         // still referencing each other and nothing would really happen.
166 283         // FIXME: shouldn't have to ..
167 273         for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
168                 for (int j=0; j<self->data[i]->len; j++) {
169                         Entry *entry = g_ptr_array_index(self->data[i], j);
170                         if (entry==NULL) continue;
171
172                         for (int p=0; p<entry_n_properties[i]; p++) {
173                                 if (IS_FOREIGN_KEY(schema[i][p].type))
174                                         entry_set_property (entry, p, NULL);
175                         };
176                 };
177         };
178
179         for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
180                 int n_dead = 0;
181                 for (int j=0; j<self->data[i]->len; j++) {
182                         Entry *entry = g_ptr_array_index(self->data[i], j);
183                         if (entry!=NULL)
184                                 entry_unref (entry, "musicsourceimporting database");
185                         else n_dead++;
186                 };
187
188                 g_warn_if_fail (self->dead[i] == n_dead);
189                 g_ptr_array_free (self->data[i], TRUE);
190 17         }
191 };
192
193 451 /*************************************************************************************************
194  * Querying - internal functions.
195  */
196
197 /* FIXME: make this much faster! In importing its only caller is try_merge, by the way */
198 static int find_entry (MusicSource *music_source, Entry *entry, Entry *caller) {
199         MusicSourceImporting *self = MUSIC_SOURCE_IMPORTING(music_source);
200         for (int i=0; i<self->total[entry->type]; i++) {
201                 Entry *matching_entry = (Entry *)g_ptr_array_index(self->data[entry->type], i);
202
203                 if (matching_entry==NULL)
204                         continue;
205
206                 if (entry==matching_entry)
207                         return i + 1;
208
209                 if (entry_match (entry, matching_entry)) {
210                         return i + 1;
211                 }
212         };
213
214         return 0;
215 }
216
217
218 /*************************************************************************************************
219  * Querying - MusicSource interface
220  */
221
222 17 static gboolean is_empty(MusicSource *musicsource) {
223 264         // FIXME: we should could dead recordings I think. Where is this function even used ??
224         if (MUSIC_SOURCE_IMPORTING(musicsource)->total[ENTRY_TYPE_RECORDING]>0)
225 17                 return FALSE;
226 264         if (!music_source_get_loaded(musicsource))
227 17                 return FALSE;
228         return TRUE;
229 };
230
231 static int get_n_entries(MusicSource *musicsource, EntryType type, int *highest_id, int *n_dead) {
232 271         MusicSourceImporting *self = MUSIC_SOURCE_IMPORTING(musicsource);
233         if (highest_id!=NULL) *highest_id = self->total[type];
234         if (n_dead!=NULL) *n_dead = self->dead[type];
235         return self->total[type] - self->dead[type];
236 17 };
237
238 static gboolean is_valid_id(MusicSource *musicsource, EntryType type, int id) {
239         MusicSourceImporting *self=MUSIC_SOURCE_IMPORTING(musicsource);
240 271         if (id > self->total[type]) return FALSE;
241 17         if (g_ptr_array_index(self->data[type], id-1)==NULL) return FALSE;
242         return TRUE;
243 264 };
244 17
245 static Entry *query_entry(MusicSource *musicsource, EntryType type, int id) {
246         //printf("music-source-importing::query-entry %i %i\n", type, id);fflush(stdout);
247         MusicSourceImporting *self=MUSIC_SOURCE_IMPORTING(musicsource);
248 91         g_return_val_if_fail (id>0 && id<=self->total[type], NULL);
249 24
250 17         Entry *entry=g_ptr_array_index(self->data[type], id-1);
251 264         if (entry!=NULL)
252 17                 entry_ref(entry, "musicsourceimporting::query-entry");
253         return entry;
254 264 };
255 17
256 102 // Used mainly to find if an entry at a m:1 join is orphaned or has only one parent.
257 // FIXME: clearly a slow way of doing things !
258 264 int query_n_relations (MusicSource *musicsource, int local_id, EntryType foreign_type,
259 209                        int foreign_property_id, int limit) {
260 102         int count = 0;
261 264
262 102         MusicSourceImporting *self = MUSIC_SOURCE_IMPORTING(musicsource);
263         for (int i=0; i<self->total[foreign_type]; i++) {
264                 Entry *foreign_entry = g_ptr_array_index(self->data[foreign_type], i);
265                 if (foreign_entry!=NULL) {
266                         Entry *child_entry = entry_get_property(foreign_entry, foreign_property_id);
267 264                         //printf ("%s %i %s: %i.\n", ENTRY_PF(foreign_entry),
268                         //        schema[foreign_type][foreign_property_id].name, child_entry->id);
269 235                         fflush (stdout);
270 102                         if (child_entry->id==local_id) {
271 227                                 //printf ("%s %i is related.\n", ENTRY_PF(child_entry)); fflush (stdout);
272 102                                 count++;
273 264                                 if (count>=limit)
274 102                                         break;
275                         };
276                 };
277         };
278         return count;
279 };
280
281 17 static GSList *query_entry_children(MusicSource *musicsource, int parent_id, EntryType child_entry_type, int child_property) {
282         MusicSourceImporting *self=MUSIC_SOURCE_IMPORTING(musicsource);
283         GSList *list=NULL;
284 264
285 17         for (int i=0;i<self->total[child_entry_type];i++) {
286                 Entry *child_entry=g_ptr_array_index(self->data[child_entry_type], i);
287                 if (child_entry!=NULL) {
288                         Entry *child_parent_entry=entry_get_property(child_entry, child_property);
289                         if (child_parent_entry!=NULL && parent_id==child_parent_entry->id) {
290 264                                 entry_ref(child_entry, "musicsourceimporting::query-entry-children");
291 17                                 list=g_slist_prepend(list, child_entry);
292                         };
293                 }
294 264         };
295 17         return list;
296 264 };
297 17
298 static GSList *query_entry_children_ids(MusicSource *musicsource, int parent_id, EntryType child_entry_type, int child_property) {
299         //printf("hello!\n");fflush(stdout);
300         MusicSourceImporting *self=MUSIC_SOURCE_IMPORTING(musicsource);
301         GSList *list=NULL;
302 264
303 17         for (int i=0;i<self->total[child_entry_type];i++) {
304 92                 Entry *child_entry = g_ptr_array_index(self->data[child_entry_type], i);
305 17                 if (child_entry!=NULL) {
306 92                         Entry *child_parent_entry = entry_get_property(child_entry, child_property);
307 17                         if (child_parent_entry!=NULL && parent_id==child_parent_entry->id)
308 92                                 list = g_slist_prepend(list, (void *)child_entry->id);
309 17                 }
310 264         };
311 17         return list;
312 264 };
313 17
314 92 // FIXME: this function should subsume query_entry_children
315 264 static GSList *query_relations(MusicSource *musicsource, EntryType local_type, int local_id,
316 92                                int relation_apid) {
317 264         g_return_val_if_fail (relation_apid!=-1, NULL);
318
319 92         MusicSourceImporting *self=MUSIC_SOURCE_IMPORTING(musicsource);
320         GSList *list=NULL;
321
322         if (APID_GET_TYPE(relation_apid)==local_type) {
323                 // All entries local refers to in a property
324 264                 return query_entry_children (musicsource, local_id,
325 92                                              APID_GET_PROPERTY(relation_apid).type,
326                                                                          APID_GET_PROPERTY_ID(relation_apid));
327         } else if (APID_GET_PROPERTY(relation_apid).type==local_type) {
328                 // All entries that refer to local in a property
329                 for (int i=0; i<self->total[APID_GET_TYPE(relation_apid)]; i++) {
330                         Entry *foreign = g_ptr_array_index(self->data[APID_GET_TYPE(relation_apid)], i);
331                         if (foreign!=NULL) {
332                                 Entry *target = entry_get_property(foreign, APID_GET_PROPERTY_ID(relation_apid));
333                                 g_warn_if_fail (target->type==local_type);
334                                 if (target!=NULL && target->id==local_id) {
335                                         entry_ref (foreign, "generic-source::query-relations");
336                                         list = g_slist_prepend(list, (void *)foreign);
337                                 };
338                         }
339 264                 };
340                 return list;
341 111         } else {
342 104                 g_warning ("query-relations: %s[%i] on %s.%s :(", entry_type_name[local_type], local_id,
343                            APID_NAME(relation_apid));
344 111                 return NULL;
345         };
346 92 };
347
348 17 static GSList *query_ids(MusicSource *musicsource, EntryType entry_type) {
349         MusicSourceImporting *self=MUSIC_SOURCE_IMPORTING(musicsource);
350         GSList *list=NULL;
351 264
352 17         for (int id=1;id<=self->total[entry_type];id++) {
353                 Entry *entry=g_ptr_array_index(self->data[entry_type], id-1);
354                 if (entry!=NULL) {
355                         list=g_slist_prepend(list, GINT_TO_POINTER(entry->id));
356                 };
357         };
358
359         return list;
360 };
361
362 GSList *query_matching_except(MusicSource *musicsource, Entry *entry, int ignored_property_absolute_id) {
363         // FIXME: this needs to be really fast, because it is called on
364         // releases for every single track add !!
365         MusicSourceImporting *self=MUSIC_SOURCE_IMPORTING(musicsource);
366         GSList *result=NULL;
367 264
368 17         for (int i=0;i<self->total[entry->type];i++) {
369                 Entry *current_entry=g_ptr_array_index(self->data[entry->type], i);
370 264                 if (current_entry!=NULL && current_entry!=entry &&
371 17                   entry_match_except(entry, current_entry, ignored_property_absolute_id)) {
372                         entry_ref(current_entry, "musicsourceimporting::query-matching-except");
373 264                         result=g_slist_prepend(result, current_entry);
374 17                 };
375         };
376
377         return result;
378 };
379
380 24
381
382 215 static MusicSourceView *create_view(MusicSource *source, ViewConfig *config) {
383 51         return g_object_new(GENERIC_VIEW_TYPE, "source", source, "config", config, NULL);
384 24 };
385
386 451
387 /*************************************************************************************************
388  * Editing - internal functions.
389  *
390  * FIXME: the functions here and in Library have the same behaviour, so maybe they should become
391  *        private superclass methods. There's no technical reason to do that though, so the question
392  *        is would it make the code any clearer?
393  */
394
395 static void add_entry_internal (MusicSource *music_source, Entry *entry) {
396         MusicSourceImporting *self = MUSIC_SOURCE_IMPORTING(music_source);
397 271         entry_ref_invisible (entry, "musicsourceimporting database");
398
399         if (self->dead[entry->type] > 0) {
400 17                 // Fill in an unused ID if possible
401 271                 for (int i=0; i<self->total[entry->type]; i++) {
402 17                         if (g_ptr_array_index(self->data[entry->type], i)==NULL) {
403 275                                 _entry_set_id (entry, i+1);
404 271                                 g_ptr_array_index(self->data[entry->type], i) = entry;
405 17                                 self->dead[entry->type]--;
406                                 break;
407                         };
408                 };
409         } else {
410 271                 int id = ++self->total[entry->type];
411 275                 _entry_set_id (entry, id);
412 271                 g_ptr_array_add (self->data[entry->type], entry);
413 17         };
414 271         entry->source = self;
415 17 };
416
417 451 static void remove_entry_internal (MusicSource *music_source, EntryType type, int id) {
418         MusicSourceImporting *self = MUSIC_SOURCE_IMPORTING(music_source);
419 445         editing_trace ("remove_entry_internal: %s %i\n", entry_type_name[type], id);
420 271
421         Entry *dead_entry = g_ptr_array_index(self->data[type], id-1);
422         g_return_if_fail (dead_entry!=NULL);
423         g_return_if_fail (dead_entry->type==type && dead_entry->id==id);
424 415         
425 271         entry_unref (dead_entry, "musicsourceimporting database");
426         g_ptr_array_index(self->data[type], id-1) = NULL;
427 17         self->dead[type]++;
428 271
429         g_warn_if_fail (self->total[type]-self->dead[type] >= 0);
430 17 };
431
432 451 /* update_foreign_keys: used when entries are merged, to update properties that refer to the
433  *                      removed one 'old_entry' and should point to the kept one 'new_entry' instead
434  */
435 static void update_foreign_keys (MusicSource *music_source, Entry *from_entry, Entry *to_entry) {
436         MusicSourceImporting *self = MUSIC_SOURCE_IMPORTING(music_source);
437
438         for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
439                 for (int p=0; p<entry_n_properties[i]; p++) {
440                         if (schema[i][p].type==from_entry->type) {
441                                 for (int k=0; k<self->total[i]; k++) {
442                                         Entry *linked_entry = g_ptr_array_index(self->data[i], k);
443
444                                         if (linked_entry!=NULL) {
445                                                 if ((Entry *)entry_get_property(linked_entry, p)==from_entry) {
446                                                         entry_set_property (linked_entry, p, to_entry);
447
448                                                         // For each checked-out entry that references 'from_entry', we need to
449                                                         // unref the checkout copies of 'from_entry' now.
450                                                         // FIXME: make the checkout store a btree or something, so this is
451                                                         // actually practical.
452                                                         // FIXME: is it correct that we only unref once even if there are
453                                                         // multiple duplicate versions?
454                                                         if (_music_source_entry_is_checked_out
455                                                               (MUSIC_SOURCE(self), linked_entry->type, linked_entry->id)) {
456                                                                 editing_trace("Found. Unref dup of %s %i\n", ENTRY_PF(from_entry));
457                                                                 _music_source_unref_checkout_copies (MUSIC_SOURCE(self),
458                                                                                                      from_entry->type,
459                                                                                                      from_entry->id);
460                                                         }
461
462                                                         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
463                                                                 // Update these too !
464                                                                 for (GList *node=linked_entry->reference_owner_list; node!=NULL;
465                                                                      node=node->next) {
466                                                                         EntryReferenceOwner *owner = node->data;
467                                                                         if (owner->name==from_entry->id_string) {
468                                                                                 owner->name = to_entry->id_string;
469                                                                                 break;
470                                                                         };
471                                                                 };
472                                                         #endif
473                                                 };
474                                         }
475                                 };
476 17                         };
477                 };
478 451         };
479 }
480
481
482 17
483
484 415 /* count_references: internal routine used by 'touch'. Checks if entry is not reffed by anything,
485 445  *                   taking into account the source's removal queue. 
486 415  * 
487  * FIXME: we could use the method in library instead, which would be a lot slower ..
488 451  * Or at least, we could check the refcount is only a source and the entries when entry
489  * ref tracking is enabled, so know that this method will work at least
490  */
491 445 static int count_references (MusicSourceImporting *self, Entry *entry, EntryType caller_type,
492                              int caller_id) {
493 415         // We allow one ref for the source.
494         int ref_count = entry->ref_count - 1;
495
496         // Find anything in the removal queue with the type being counted, and ignore the
497         // references those entries contain.
498         //
499         GSList *node = entry_type_info[entry->type].foreign_key_owners;
500         while(node!=NULL) {
501                 const int apid = (int)node->data;
502 445                 if (SP->removal_queue!=NULL)
503                         for (GSList *q_node=SP->removal_queue; q_node; q_node=q_node->next) {
504                                 RemovalQueueEntry *removal = q_node->data;
505                                 Entry *q_entry = removal->entry;
506 415                                 if (q_entry->type==APID_GET_TYPE(apid) 
507                                       && entry_get_property(entry, APID_GET_PROPERTY_ID(apid))==entry)
508                                         ref_count --;
509                         };
510
511                 node = node->next;
512         };
513
514 451         // Because checkout duplicates point to real database entries, we need to ignore their
515         // references.
516 445         ref_count -= _music_source_count_checkout_duplicate_references (MUSIC_SOURCE(self), entry);
517
518         editing_trace ("Found %i refs for %s %i \n", ref_count, ENTRY_PF(entry));
519 452         //printf ("genericsource: Count_references: %s %i = %i\n", ENTRY_PF(entry), ref_count); fflush (stdout);
520 415         return ref_count;
521 };
522
523 451 /* touch: garbage collection. */
524 static void touch (MusicSourceImporting *self, Entry *entry, gboolean delete,
525                    gboolean will_be_unreffed, GSList **p_jetsam, EntryType caller_type,
526                    int caller_id) {
527         g_return_if_fail (entry != NULL);
528 269         const EntryType type = entry->type; const int id = entry->id;
529 451
530         if (ENTRY_IS_SPECIAL(type, id))
531                 return;
532 415
533 358         editing_trace ("touch: %s %i refs %i - d%i u%i.\n", entry_type_name[type], id, entry->ref_count,
534 346                        delete, will_be_unreffed);
535 269
536         if (delete) {
537                 // Search for types that point to this one (unless the link is flagged 'trivial') and
538 451                 // delete those. For example, if removing a recording this deletes all of its files.
539 269                 //
540                 for (EntryType i=0; i<ENTRY_TYPE_COUNT; i++) {
541                         // Entries linking to their own type is not allowed in Calliope anywhere
542                         // FIXME: although you don't mention that anywhere.
543                         if (i==type) continue;
544
545                         for (int p=0; p<entry_n_properties[i]; p++) {
546                                 if (schema[i][p].type!=type) continue;
547
548 415                                 // Look through all our entries of type 'i' for those linking to 'entry'
549                                 for (int k=0; k < self->total[i]; k++) {
550 269                                         Entry *child_entry = g_ptr_array_index(self->data[i], k);
551 415                                         
552 269                                         // Ignore dead entries, and the entry that triggered this update (to prevent
553                                         // traversing back down the tree again and making an infinite loop).
554                                         if (child_entry==NULL || (i==caller_type && child_entry->id==caller_id))
555                                                 continue;
556
557                                         if ((Entry *)entry_get_property(child_entry, p)!=entry) continue;
558
559 451                                         // If the foreign key is flagged trivial, just set the value to NULL if it
560                                         // references this entry instead of deleting it.
561                                         const gboolean delete_child = !(schema[i][p].flags & PROPERTY_TRIVIAL);
562                                         touch (self, child_entry, delete_child, FALSE, p_jetsam, type, id);
563 269                                 };
564                         };
565                 };
566         };
567
568         if (!delete) {
569 445                 int ref_count = count_references(self, entry, caller_type, caller_id); 
570 451
571                 // It is valid for an entry with no references to be touched with will_be_unreffed TRUE -
572                 // all the entries that point to it could be in the removal queue, even though they can't
573                 // yet have been removed.
574 415                 g_warn_if_fail (ref_count >= 0);
575                 if (will_be_unreffed) ref_count--;
576 451
577 415                 delete = (ref_count <= 0);
578 269         };
579
580 344         // Touch all entries this one links to, to see if they now need removing or 'changed' emitting.
581 269         for (int p=0; p<entry_n_properties[type]; p++) {
582                 if (IS_FOREIGN_KEY(schema[type][p].type)) {
583                         // Parent is eg a recording's composition
584                         Entry *parent = entry_get_property(entry, p);
585                         if (parent!=NULL) {
586                                 if (parent->type!=caller_type || parent->id!=caller_id)
587 445                                         touch (self, parent, FALSE, delete, p_jetsam, type, id);
588 269                         };
589                 };
590         };
591
592         if (delete) {
593 445                 if (!g_slist_find_custom (SP->removal_queue, entry, removal_queue_compare_func)) {
594                         RemovalQueueEntry *removal = g_slice_new0 (RemovalQueueEntry);
595                         removal->entry = entry;
596                         SP->removal_queue = g_slist_prepend (SP->removal_queue, removal);
597                         _music_source_queue_notify (MUSIC_SOURCE(self), entry, NULL);
598                 }
599 318
600 415                 // Also remove from jetsam.
601                 if (p_jetsam != NULL)
602                         for (GSList *node=*p_jetsam; node; node=node->next) {
603                                 Entry *jetsam_entry = node->data;
604                                 if (jetsam_entry->type==type && jetsam_entry->id==id) {
605                                         (*p_jetsam) = g_slist_remove_link (*p_jetsam, node);
606                                         break;
607 318                                 };
608 415                         };
609         };
610 269 }
611
612
613 445 static void execute_removal_queue (MusicSourceImporting *self) {
614 451         MusicSource *music_source = MUSIC_SOURCE(self);
615 445         editing_trace ("EXECUTE_REMOVAL_QUEUE! (%i entries) %i\n\n", g_slist_length (SP->removal_queue),
616 451                        _music_source_inside_notify (music_source));
617 445
618         // When we are still inside a notify function, we don't want to mess up entries that might still
619         // be referenced by checkout duplicates. We remove them from the source but keep them in the
620         // queue, so they are accessible but won't be returned in future queries.
621 451         const gboolean in_notify = _music_source_inside_notify (music_source);
622 415         
623 445         if (!in_notify) {
624                 // First zero all of their foreign keys, to prevent circular referencing keeping entries
625                 // alive. 
626                 for (GSList *q_node=SP->removal_queue; q_node; q_node=q_node->next) {
627                         RemovalQueueEntry *removal = q_node->data;
628                         Entry *removal_entry = removal->entry;
629 451                         entry_destroy (removal_entry);
630                 }
631 445         }
632
633         // Now actually delete the queued entries from the source. Any that are used in duplicates will
634         // be kept alive by references from the duplicates.
635         for (GSList *q_node=SP->removal_queue; q_node; q_node=q_node->next) {
636                 RemovalQueueEntry *removal = q_node->data;
637                 Entry *removal_entry       = removal->entry;
638
639                 editing_trace ("\t%s %i: %i.\n", ENTRY_PF(removal_entry), removal->removed_from_source);
640                 if (in_notify)
641                         entry_ref (removal->entry, "genericsource:kept alive during notify");
642
643                 if (!removal->removed_from_source)
644 451                         remove_entry_internal (music_source, removal_entry->type, removal_entry->id);
645 445                 else
646                         entry_unref (removal->entry, "genericsource:kept alive during notify");
647
648 451                 if (_music_source_inside_notify (music_source))
649 445                         // Leave the entry in the queue, so we can zero its foreign keys later on.
650                         removal->removed_from_source = TRUE;
651                 else
652                         g_slice_free (RemovalQueueEntry, removal);
653         }
654
655         if (!in_notify) {
656                 g_slist_free (SP->removal_queue);
657                 SP->removal_queue = NULL;
658         }
659 415 };
660
661 17 // FIXME: this has to be slow with all the merging and shit.
662 451 static int add_entry (MusicSource *music_source, Entry *entry) {
663         entry_take_last_ref (entry, "musicsourceimporting::add_entry");
664         Entry *result = _music_source_add_entry_recursive (music_source, entry, TRUE, entry);
665         int result_id = result->id;
666 264
667 451         entry_unref(entry, "musicsourceimporting::add_entry");
668 264         if (result!=entry)
669 451                 entry_unref(result, "_music_source_add_entry_recursive");
670 264
671 451         _music_source_emit_queued_notifications (music_source, NULL);
672 452         // FIXME: in library here we have to emit the removal queue, or tests like Album Notify 1 fail.
673         // I don't know why we don't need to in genericsource.
674 17         return result_id;
675 };
676
677 243 static void remove_entry (MusicSource *musicsource, EntryType type, int id) {
678         MusicSourceImporting *self = MUSIC_SOURCE_IMPORTING(musicsource);
679 415         g_return_if_fail (id > 0 && id <= self->total[type]);
680 451
681 243         MUSIC_SOURCE_CLASS(music_source_importing_parent_class)->remove_entry (musicsource, type, id);
682 264
683 445         g_warn_if_fail (SP->removal_queue == NULL);
684 415
685         // Touch 'entry' with delete = TRUE
686 104         Entry *entry = g_ptr_array_index(self->data[type], id-1);
687 445         touch (self, entry, TRUE, FALSE, NULL, 0, 0);
688
689 364         _music_source_emit_queued_notifications (musicsource, NULL);
690 445         execute_removal_queue (self);
691 17 };
692
693 // FIXME: surely an entry can only be checked out once at a time? I don't know,
694 // you should enforce this !
695 451 static Entry *checkout_entry (MusicSource *music_source, EntryType root_type, int root_id) {
696         MusicSourceImporting *self = MUSIC_SOURCE_IMPORTING(music_source);
697 264
698 339         Entry *entry = g_ptr_array_index(self->data[root_type], root_id-1);
699         g_return_val_if_fail (entry!=NULL, NULL);
700 342
701 451         _music_source_checkout_entry_recursive (music_source, entry);
702 342
703 339         if (entry!=NULL)
704                 entry_ref (entry, "musicsourceimporting::checkout-entry");
705         return entry;
706 17 };
707
708 336 /* checkin_entry: 'entry' has been changed, and our internal represenation needs updating. */
709 451 static void checkin_entry (MusicSource *music_source, Entry *entry) {
710 445         MusicSourceImporting *self = MUSIC_SOURCE_IMPORTING(music_source);
711 264
712 445         GSList *jetsam = NULL;
713 451         Entry *result = _music_source_checkin_entry_recursive (music_source, entry, &jetsam, entry);
714 264
715 17         // Original entry is unreffed in music_source_checkin_entry
716 451         if (entry != result)
717                 entry_unref (result, "_music_source_checkin_entry_recursive");
718 348
719 445         // Touch each jetsam entry (something which was in the tree of entries checked out, but has now
720         // been replaced by a different entry). They will be removed if they are not referenced anywhere
721 345         for (GSList *node=jetsam; node; node=node->next)
722 445                 touch (self, node->data, FALSE, FALSE, &node->next, 0, 0);
723 269         g_slist_free (jetsam);
724 418
725 445         _music_source_emit_queued_notifications (music_source, NULL);
726         execute_removal_queue (self);
727 17 };
728
729 271
730 95 static void flush(MusicSource *musicsource) {
731         // No flushing to do here since we don't have a cache.
732 };
733
734 17 static void wipe(MusicSource *musicsource) {
735 264         MusicSourceImporting *self=MUSIC_SOURCE_IMPORTING(musicsource);
736 346         _music_source_emit(musicsource, MUSIC_SOURCE_SIGNAL_WIPED, 0);
737 17         free_db(self);
738 264         init_db(self);
739 17 };
740
741 // For debugging
742 //
743 206 static void dump (MusicSource *music_source, gboolean detailed) {
744 264         MusicSourceImporting *self = MUSIC_SOURCE_IMPORTING(music_source);
745 206         for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
746                 if (!detailed) {
747 264
748 206                         int highest_id, n_dead, total = music_source_get_n_entries(music_source, i, &highest_id,
749                                                                                    &n_dead);
750 264                         printf ("%s: total %i, highest %i with %i dead.\n", entry_type_name[i], total,
751 206                                 highest_id, n_dead);
752                 } else {
753                         printf ("\n%s\n---------------\n", entry_type_name[i]);
754                         for (int j=0; j<self->total[i]; j++) {
755                                 entry_dump ((Entry *)g_ptr_array_index(self->data[i], j));
756                         };
757 17                 };
758         };
759 206         fflush (stdout);
760 17 };
761 269
762
763 static void editing_trace (const char *format, ...) {
764         #ifdef MUSIC_SOURCE_TRACE_EDITING
765         va_list va; va_start (va, format);
766 342         //vprintf (format, va);
767 337         g_logv (NULL, G_LOG_LEVEL_DEBUG, format, va);
768         va_end (va); //fflush (stdout);
769 269         #endif
770 };

Loggerhead is a web-based interface for Bazaar branches